diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 7d83c0851eb8c..742fcfa820356 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -28,6 +28,7 @@ disabled: - x-pack/test/defend_workflows_cypress/cli_config.ts - x-pack/test/defend_workflows_cypress/config.ts - x-pack/test/defend_workflows_cypress/endpoint_config.ts + - x-pack/test/defend_workflows_cypress/endpoint_serverless_config.ts - x-pack/plugins/observability_onboarding/e2e/ftr_config_open.ts - x-pack/plugins/observability_onboarding/e2e/ftr_config_runner.ts - x-pack/plugins/observability_onboarding/e2e/ftr_config.ts @@ -79,24 +80,12 @@ disabled: - x-pack/test_serverless/functional/config.base.ts - x-pack/test_serverless/shared/config.base.ts - # Serverless configs, currently only for manual tests runs, CI integration planned - - x-pack/test_serverless/api_integration/test_suites/common/config.ts - - x-pack/test_serverless/api_integration/test_suites/observability/config.ts - - x-pack/test_serverless/api_integration/test_suites/observability/config.feature_flags.ts - - x-pack/test_serverless/api_integration/test_suites/search/config.ts - - x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts - - x-pack/test_serverless/api_integration/test_suites/security/config.ts - - x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts - - x-pack/test_serverless/functional/test_suites/common/config.ts - - x-pack/test_serverless/functional/test_suites/observability/config.ts + # Serverless feature flag config files (move to enabled after tests are added) - x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts - - x-pack/test_serverless/functional/test_suites/observability/config.examples.ts - - x-pack/test_serverless/functional/test_suites/search/config.ts + - x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts - x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts - - x-pack/test_serverless/functional/test_suites/search/config.examples.ts - - x-pack/test_serverless/functional/test_suites/security/config.ts + - x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts - x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts - - x-pack/test_serverless/functional/test_suites/security/config.examples.ts defaultQueue: 'n2-4-spot' enabled: @@ -171,6 +160,7 @@ enabled: - x-pack/test/alerting_api_integration/security_and_spaces/group1/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group2/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts + - x-pack/test/alerting_api_integration/security_and_spaces/group3/config_with_schedule_circuit_breaker.ts - x-pack/test/alerting_api_integration/security_and_spaces/group2/config_non_dedicated_task_runner.ts - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/config.ts - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.ts @@ -243,6 +233,7 @@ enabled: - x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts + - x-pack/test/disable_ems/config.ts - x-pack/test/encrypted_saved_objects_api_integration/config.ts - x-pack/test/examples/config.ts - x-pack/test/fleet_api_integration/config.agent.ts @@ -409,6 +400,17 @@ enabled: - x-pack/test/ui_capabilities/spaces_only/config.ts - x-pack/test/upgrade_assistant_integration/config.js - x-pack/test/usage_collection/config.ts + - x-pack/test_serverless/api_integration/test_suites/observability/config.ts + - x-pack/test_serverless/api_integration/test_suites/search/config.ts + - x-pack/test_serverless/api_integration/test_suites/security/config.ts + - x-pack/test_serverless/functional/test_suites/observability/config.ts + - x-pack/test_serverless/functional/test_suites/observability/config.examples.ts + - x-pack/test_serverless/api_integration/test_suites/observability/config.feature_flags.ts + - x-pack/test_serverless/functional/test_suites/search/config.ts + - x-pack/test_serverless/functional/test_suites/search/config.examples.ts + - x-pack/test_serverless/functional/test_suites/search/config.screenshots.ts + - x-pack/test_serverless/functional/test_suites/security/config.ts + - x-pack/test_serverless/functional/test_suites/security/config.examples.ts - x-pack/performance/journeys/ecommerce_dashboard.ts - x-pack/performance/journeys/ecommerce_dashboard_map_only.ts - x-pack/performance/journeys/flight_dashboard.ts @@ -416,6 +418,7 @@ enabled: - x-pack/performance/journeys/many_fields_discover.ts - x-pack/performance/journeys/many_fields_lens_editor.ts - x-pack/performance/journeys/many_fields_transform.ts + - x-pack/performance/journeys/tsdb_logs_data_visualizer.ts - x-pack/performance/journeys/promotion_tracking_dashboard.ts - x-pack/performance/journeys/web_logs_dashboard.ts - x-pack/performance/journeys/data_stress_test_lens.ts diff --git a/.buildkite/hooks/post-command b/.buildkite/hooks/post-command index d9a8ad668da0b..13e0f4d795828 100755 --- a/.buildkite/hooks/post-command +++ b/.buildkite/hooks/post-command @@ -1,3 +1,7 @@ #!/usr/bin/env bash -.buildkite/scripts/lifecycle/post_command.sh +if [[ "$BUILDKITE_AGENT_NAME" =~ ^bk-agent ]]; then + echo "Pipeline file triggered from outside the kibana executors, skipping post_command" +else + .buildkite/scripts/lifecycle/post_command.sh +fi diff --git a/.buildkite/hooks/pre-command b/.buildkite/hooks/pre-command index 58a2c5f0b499d..95e2975d094c3 100644 --- a/.buildkite/hooks/pre-command +++ b/.buildkite/hooks/pre-command @@ -1,3 +1,7 @@ #!/usr/bin/env bash -source .buildkite/scripts/lifecycle/pre_command.sh +if [[ "$BUILDKITE_AGENT_NAME" =~ ^bk-agent ]]; then + echo "Pipeline file triggered from outside the kibana executors, skipping pre_command" +else + source .buildkite/scripts/lifecycle/pre_command.sh +fi diff --git a/.buildkite/pipelines/flaky_tests/groups.json b/.buildkite/pipelines/flaky_tests/groups.json index cf61ff8cba2e8..2d715fcd8f27d 100644 --- a/.buildkite/pipelines/flaky_tests/groups.json +++ b/.buildkite/pipelines/flaky_tests/groups.json @@ -1,21 +1,37 @@ { "groups": [ + { + "key": "cypress/security_solution", + "name": "Security Solution - Cypress" + }, + { + "key": "cypress/security_serverless", + "name": "[Serverless] Security Solution - Cypress" + }, { "key": "cypress/security_solution_investigations", "name": "Security Solution Investigations - Cypress" }, { - "key": "cypress/security_solution", - "name": "Security Solution - Cypress" + "key": "cypress/security_serverless_investigations", + "name": "[Serverless] Security Solution Investigations - Cypress" }, { "key": "cypress/security_solution_explore", "name": "Security Solution Explore - Cypress" }, + { + "key": "cypress/security_serverless_explore", + "name": "[Serverless] Security Solution Explore - Cypress" + }, { "key": "cypress/osquery_cypress", "name": "Osquery - Cypress" }, + { + "key": "cypress/security_serverless_osquery", + "name": "[Serverless] Osquery - Cypress" + }, { "key": "cypress/fleet_cypress", "name": "Fleet - Cypress" diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 521a0c76317a0..133004f468948 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -78,16 +78,44 @@ steps: - exit_status: '*' limit: 1 - - command: '.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh' - label: Trigger unsupported ftr tests - timeout_in_minutes: 10 - depends_on: - - build + - command: .buildkite/scripts/steps/functional/security_serverless.sh + label: 'Serverless Security Cypress Tests' agents: - queue: 'kibana-default' + queue: n2-4-spot + depends_on: build + timeout_in_minutes: 40 + parallelism: 2 + retry: + automatic: + - exit_status: '*' + limit: 1 + + - command: .buildkite/scripts/steps/functional/security_serverless_explore.sh + label: 'Serverless Explore - Security Solution Cypress Tests' + agents: + queue: n2-4-spot + depends_on: build + timeout_in_minutes: 60 + parallelism: 2 + retry: + automatic: + - exit_status: '*' + limit: 1 + + - command: .buildkite/scripts/steps/functional/security_serverless_investigations.sh + label: 'Serverless Investigations - Security Solution Cypress Tests' + agents: + queue: n2-4-spot + depends_on: build + timeout_in_minutes: 120 + parallelism: 2 + retry: + automatic: + - exit_status: '*' + limit: 1 - - command: '.buildkite/scripts/steps/functional/on_merge_serverless_ftrs.sh' - label: Trigger serverless ftr tests + - command: '.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh' + label: Trigger unsupported ftr tests timeout_in_minutes: 10 depends_on: - build diff --git a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml index 30e7929fd6e19..8d67cbf37d8a0 100644 --- a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml +++ b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml @@ -91,13 +91,11 @@ steps: queue: n2-4-spot depends_on: build timeout_in_minutes: 120 - parallelism: 2 + parallelism: 6 retry: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" - command: .buildkite/scripts/steps/functional/osquery_cypress.sh label: 'Osquery Cypress Tests' @@ -110,8 +108,6 @@ steps: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-osquery/**/*" - command: .buildkite/scripts/steps/functional/synthetics_plugin.sh label: 'Synthetics @elastic/synthetics Tests' diff --git a/.buildkite/pipelines/pipeline.kibana-serverless-release.yaml b/.buildkite/pipelines/pipeline.kibana-serverless-release.yaml index 59fe89cc69e4a..9c3e235c5564a 100644 --- a/.buildkite/pipelines/pipeline.kibana-serverless-release.yaml +++ b/.buildkite/pipelines/pipeline.kibana-serverless-release.yaml @@ -2,8 +2,14 @@ steps: - label: ":releasethekraken: Release kibana" # https://regex101.com/r/tY52jo/1 if: build.tag =~ /^deploy@\d+\$/ - trigger: gpctl-promote + trigger: gpctl-promote-with-e2e-tests build: env: SERVICE_COMMIT_HASH: "${BUILDKITE_COMMIT:0:12}" - REMOTE_SERVICE_CONFIG: https://raw.githubusercontent.com/elastic/serverless-gitops/main/gen/gpctl/kibana/tagged-release.yaml + REMOTE_SERVICE_CONFIG: https://raw.githubusercontent.com/elastic/serverless-gitops/main/gen/gpctl/kibana/config.yaml + SERVICE: kibana-controller + NAMESPACE: kibana-ci + IMAGE_NAME: kibana-serverless + +notify: + - slack: "#kibana-mission-control" diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index 7a4291fab7003..158c22c0bb0c5 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -56,98 +56,17 @@ steps: - exit_status: '*' limit: 1 - - command: SERVERLESS_ENVIRONMENT=observability .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Observability Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - soft_fail: - - exit_status: 10 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: SERVERLESS_ENVIRONMENT=observability.examples .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Observability Examples Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - soft_fail: - - exit_status: 10 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: SERVERLESS_ENVIRONMENT=search .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Search Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - soft_fail: - - exit_status: 10 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: SERVERLESS_ENVIRONMENT=search.examples .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Search Examples Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - soft_fail: - - exit_status: 10 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: SERVERLESS_ENVIRONMENT=security .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Security Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - soft_fail: - - exit_status: 10 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: SERVERLESS_ENVIRONMENT=security.examples .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Security Examples Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - soft_fail: - - exit_status: 10 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - command: .buildkite/scripts/steps/functional/security_serverless.sh label: 'Serverless Security Cypress Tests' agents: queue: n2-4-spot depends_on: build timeout_in_minutes: 40 - parallelism: 16 - soft_fail: true + parallelism: 2 retry: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" # status_exception: Native role management is not enabled in this Elasticsearch instance # - command: .buildkite/scripts/steps/functional/security_serverless_defend_workflows.sh @@ -156,13 +75,10 @@ steps: # queue: n2-4-spot # depends_on: build # timeout_in_minutes: 40 - # soft_fail: true # retry: # automatic: # - exit_status: '*' # limit: 1 - # artifact_paths: - # - "target/kibana-security-solution/**/*" - command: .buildkite/scripts/steps/functional/security_serverless_investigations.sh label: 'Serverless Security Investigations Cypress Tests' @@ -170,14 +86,11 @@ steps: queue: n2-4-spot depends_on: build timeout_in_minutes: 40 - parallelism: 6 - soft_fail: true + parallelism: 2 retry: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" - command: .buildkite/scripts/steps/functional/security_serverless_explore.sh label: 'Serverless Security Explore Cypress Tests' @@ -186,13 +99,10 @@ steps: depends_on: build timeout_in_minutes: 40 parallelism: 2 - soft_fail: true retry: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" - command: .buildkite/scripts/steps/lint.sh label: 'Linting' diff --git a/.buildkite/pipelines/pull_request/defend_workflows.yml b/.buildkite/pipelines/pull_request/defend_workflows.yml index 3a50e3ece206e..c60030497b623 100644 --- a/.buildkite/pipelines/pull_request/defend_workflows.yml +++ b/.buildkite/pipelines/pull_request/defend_workflows.yml @@ -2,27 +2,25 @@ steps: - command: .buildkite/scripts/steps/functional/defend_workflows.sh label: 'Defend Workflows Cypress Tests' agents: - queue: n2-4-spot + queue: n2-4-virt depends_on: build timeout_in_minutes: 60 - parallelism: 2 + parallelism: 6 retry: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" - - command: .buildkite/scripts/steps/functional/defend_workflows_vagrant.sh - label: 'Defend Workflows Endpoint Cypress Tests' + - command: .buildkite/scripts/steps/functional/defend_workflows_serverless.sh + label: 'Defend Workflows Cypress Tests on Serverless' agents: - queue: n2-4-virt + queue: n2-4-spot depends_on: build - timeout_in_minutes: 60 - parallelism: 6 + timeout_in_minutes: 120 + parallelism: 2 retry: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" + + diff --git a/.buildkite/pipelines/pull_request/fleet_cypress.yml b/.buildkite/pipelines/pull_request/fleet_cypress.yml index fab4f90c5bfea..fcccdfe7a6799 100644 --- a/.buildkite/pipelines/pull_request/fleet_cypress.yml +++ b/.buildkite/pipelines/pull_request/fleet_cypress.yml @@ -4,7 +4,8 @@ steps: agents: queue: n2-4-spot depends_on: build - timeout_in_minutes: 120 + timeout_in_minutes: 50 + parallelism: 6 retry: automatic: - exit_status: '-1' diff --git a/.buildkite/pipelines/pull_request/response_ops.yml b/.buildkite/pipelines/pull_request/response_ops.yml index b2ef5199fab91..38ca242949d86 100644 --- a/.buildkite/pipelines/pull_request/response_ops.yml +++ b/.buildkite/pipelines/pull_request/response_ops.yml @@ -10,5 +10,3 @@ steps: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" diff --git a/.buildkite/pipelines/pull_request/response_ops_cases.yml b/.buildkite/pipelines/pull_request/response_ops_cases.yml index af2e58b65ab34..312c62c01c732 100644 --- a/.buildkite/pipelines/pull_request/response_ops_cases.yml +++ b/.buildkite/pipelines/pull_request/response_ops_cases.yml @@ -9,5 +9,3 @@ steps: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-security-solution/**/*" diff --git a/.buildkite/pipelines/pull_request/security_solution.yml b/.buildkite/pipelines/pull_request/security_solution.yml index 58b416548ec5f..2b7a6faaf212c 100644 --- a/.buildkite/pipelines/pull_request/security_solution.yml +++ b/.buildkite/pipelines/pull_request/security_solution.yml @@ -10,8 +10,6 @@ steps: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - 'target/kibana-security-solution/**/*' - command: .buildkite/scripts/steps/functional/security_solution_explore.sh label: 'Explore - Security Solution Cypress Tests' @@ -24,8 +22,6 @@ steps: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - 'target/kibana-security-solution/**/*' - command: .buildkite/scripts/steps/functional/security_solution_investigations.sh label: 'Investigations - Security Solution Cypress Tests' @@ -38,8 +34,6 @@ steps: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - 'target/kibana-security-solution/**/*' - command: .buildkite/scripts/steps/functional/security_solution_burn.sh label: 'Security Solution Cypress tests, burning changed specs' @@ -51,8 +45,6 @@ steps: retry: automatic: false soft_fail: true - artifact_paths: - - 'target/kibana-security-solution/**/*' - command: .buildkite/scripts/steps/code_generation/security_solution_codegen.sh label: 'Security Solution OpenAPI codegen' diff --git a/.buildkite/pipelines/pull_request/threat_intelligence.yml b/.buildkite/pipelines/pull_request/threat_intelligence.yml index f9b9050d28d95..b91be5faffdec 100644 --- a/.buildkite/pipelines/pull_request/threat_intelligence.yml +++ b/.buildkite/pipelines/pull_request/threat_intelligence.yml @@ -10,5 +10,3 @@ steps: automatic: - exit_status: '*' limit: 1 - artifact_paths: - - "target/kibana-threat-intelligence/**/*" diff --git a/.buildkite/pipelines/quality-gates/pipeline.kibana-tests.yaml b/.buildkite/pipelines/quality-gates/pipeline.kibana-tests.yaml index 27e55dfced9d7..467df501bc9ca 100644 --- a/.buildkite/pipelines/quality-gates/pipeline.kibana-tests.yaml +++ b/.buildkite/pipelines/quality-gates/pipeline.kibana-tests.yaml @@ -1,10 +1,27 @@ +# This pipeline serves as the entry point for your service's quality gates definitions. When +# properly configured, it will be invoked automatically as part of the automated +# promotion process once a new version was rolled out in one of the various cloud stages. +# +# The updated environment is provided via ENVIRONMENT variable. The seedling +# step will branch and execute pipeline snippets at the following location: +# pipeline.tests-qa.yaml +# pipeline.tests-staging.yaml +# pipeline.tests-production.yaml +# +# Docs: https://docs.elastic.dev/serverless/qualitygates + env: + TEAM_CHANNEL: "#kibana-mission-control" ENVIRONMENT: ${ENVIRONMENT?} steps: - label: ":pipeline::grey_question::seedling: Trigger Kibana Tests for ${ENVIRONMENT}" env: QG_PIPELINE_LOCATION: ".buildkite/pipelines/quality-gates" - command: "make -C /agent run-environment-tests" + command: "make -C /agent run-environment-tests" # will trigger https://buildkite.com/elastic/kibana-tests agents: image: "docker.elastic.co/ci-agent-images/quality-gate-seedling:0.0.2" + +notify: + - slack: "${TEAM_CHANNEL?}" + if: build.branch == "main" && build.state == "failed" diff --git a/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml b/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml index 5a0738ead7d9c..2161b000dc412 100644 --- a/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml +++ b/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml @@ -1,10 +1,20 @@ +# These pipeline steps constitute the quality gate for your service within the production environment. +# Incorporate any necessary additional logic to validate the service's integrity. +# A failure in this pipeline build will prevent further progression to the subsequent stage. + steps: - - label: ":pipeline::fleet::seedling: Trigger Observability Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Observability specific Kibana tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" + - label: ":pipeline::rocket::seedling: Trigger control-plane e2e tests" + trigger: "ess-k8s-production-e2e-tests" # https://buildkite.com/elastic/ess-k8s-production-e2e-tests + build: + env: + REGION_ID: aws-us-east-1 + NAME_PREFIX: ci_test_kibana-promotion_ + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-production.yaml)" + + - wait: ~ - - label: ":pipeline::lock::seedling: Trigger Security Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Security specific Kibana tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" + - label: ":judge::seedling: Trigger Manual Tests Phase" + command: "make -C /agent trigger-manual-verification-phase" + if: build.branch == "main" + agents: + image: "docker.elastic.co/ci-agent-images/manual-verification-agent:0.0.2" diff --git a/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml b/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml index 669b306bc2ceb..5321f24ae6e3b 100644 --- a/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml +++ b/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml @@ -1,20 +1,54 @@ +# These pipeline steps constitute the quality gate for your service within the QA environment. +# Incorporate any necessary additional logic to validate the service's integrity. +# A failure in this pipeline build will prevent further progression to the subsequent stage. + steps: - - label: ":pipeline::kibana::seedling: Trigger Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Kibana specific tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" + - label: ":pipeline::kibana::seedling: Trigger SLO check" + trigger: "serverless-quality-gates" # https://buildkite.com/elastic/serverless-quality-gates + build: + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)" + env: + TARGET_ENV: qa + CHECK_SLO: true + CHECK_SLO_TAG: kibana + CHECK_SLO_WAITING_PERIOD: 10m + CHECK_SLO_BURN_RATE_THRESHOLD: 0.1 + soft_fail: true + + - label: ":pipeline::kibana::seedling: Trigger Kibana Serverless Tests for ${ENVIRONMENT}" + trigger: appex-qa-serverless-kibana-ftr-tests # https://buildkite.com/elastic/appex-qa-serverless-kibana-ftr-tests + soft_fail: true # Remove this before release or when tests stabilize + build: + env: + ENVIRONMENT: ${ENVIRONMENT} + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)" + + - group: ":female-detective: Security Solution Tests" + key: "security" + steps: + - label: ":pipeline::female-detective::seedling: Trigger Security Solution quality gate script" + command: .buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh + + - label: ":pipeline::ship::seedling: Trigger Fleet serverless smoke tests for ${ENVIRONMENT}" + trigger: fleet-smoke-tests # https://buildkite.com/elastic/fleet-smoke-tests + soft_fail: true # Remove this before release + build: + env: + ENVIRONMENT: ${ENVIRONMENT} + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)" - - label: ":pipeline::fleet::seedling: Trigger Fleet Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Fleet specific Kibana tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" + - label: ":pipeline::rocket::seedling: Trigger control-plane e2e tests" + trigger: "ess-k8s-qa-e2e-tests-daily" # https://buildkite.com/elastic/ess-k8s-qa-e2e-tests-daily + build: + env: + REGION_ID: aws-eu-west-1 + NAME_PREFIX: ci_test_kibana-promotion_ + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)" - - label: ":pipeline::lock::seedling: Trigger Security Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Security specific Kibana tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" + - wait: ~ - - label: ":pipeline::lock::seedling: Trigger Control Plane Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Control Plane specific Kibana tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" + - label: ":judge::seedling: Trigger Manual Tests Phase" + command: "make -C /agent trigger-manual-verification-phase" + if: build.branch == "main" + agents: + image: "docker.elastic.co/ci-agent-images/manual-verification-agent:0.0.2" diff --git a/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml b/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml index 5a0738ead7d9c..6a5edc3a97073 100644 --- a/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml +++ b/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml @@ -1,10 +1,30 @@ +# These pipeline steps constitute the quality gate for your service within the staging environment. +# Incorporate any necessary additional logic to validate the service's integrity. +# A failure in this pipeline build will prevent further progression to the subsequent stage. + steps: - - label: ":pipeline::fleet::seedling: Trigger Observability Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Observability specific Kibana tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" - - - label: ":pipeline::lock::seedling: Trigger Security Kibana Tests for ${ENVIRONMENT}" - command: echo "replace me with Security specific Kibana tests" - agent: - image: "docker.elastic.co/ci-agent-images/basic-buildkite-agent:1688566364" + - label: ":pipeline::kibana::seedling: Trigger SLO check" + trigger: "serverless-quality-gates" # https://buildkite.com/elastic/serverless-quality-gates + build: + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)" + env: + TARGET_ENV: staging + CHECK_SLO: true + CHECK_SLO_TAG: kibana + soft_fail: true + + - label: ":pipeline::rocket::seedling: Trigger control-plane e2e tests" + trigger: "ess-k8s-staging-e2e-tests" # https://buildkite.com/elastic/ess-k8s-staging-e2e-tests + build: + env: + REGION_ID: aws-us-east-1 + NAME_PREFIX: ci_test_kibana-promotion_ + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)" + + - wait: ~ + + - label: ":judge::seedling: Trigger Manual Tests Phase" + command: "make -C /agent trigger-manual-verification-phase" + if: build.branch == "main" + agents: + image: "docker.elastic.co/ci-agent-images/manual-verification-agent:0.0.2" diff --git a/.buildkite/pipelines/serverless.yml b/.buildkite/pipelines/serverless.yml deleted file mode 100644 index be9816545e2bf..0000000000000 --- a/.buildkite/pipelines/serverless.yml +++ /dev/null @@ -1,142 +0,0 @@ -steps: - - command: .buildkite/scripts/lifecycle/pre_build.sh - label: Pre-Build - timeout_in_minutes: 10 - agents: - queue: kibana-default - retry: - automatic: - - exit_status: '*' - limit: 1 - - - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins - agents: - queue: n2-16-spot - key: build - if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: SERVERLESS_ENVIRONMENT=observability .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Observability Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - exit_status: '*' - limit: 1 - - - command: SERVERLESS_ENVIRONMENT=observability.examples .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Observability Examples Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - exit_status: '*' - limit: 1 - - - command: SERVERLESS_ENVIRONMENT=search .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Search Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - exit_status: '*' - limit: 1 - - - command: SERVERLESS_ENVIRONMENT=search.examples .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Search Examples Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - exit_status: '*' - limit: 1 - - - command: SERVERLESS_ENVIRONMENT=security .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Security Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - exit_status: '*' - limit: 1 - - - command: SERVERLESS_ENVIRONMENT=security.examples .buildkite/scripts/steps/functional/serverless_ftr.sh - label: 'Serverless Security Examples Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - exit_status: '*' - limit: 1 - - - command: .buildkite/scripts/steps/functional/security_serverless.sh - label: 'Serverless Security Cypress Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 40 - parallelism: 16 - retry: - automatic: - - exit_status: '*' - limit: 1 - artifact_paths: - - "target/kibana-security-serverless/**/*" - - - command: .buildkite/scripts/steps/functional/security_serverless_explore.sh - label: 'Serverless Explore - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 60 - parallelism: 4 - retry: - automatic: - - exit_status: '*' - limit: 1 - artifact_paths: - - "target/kibana-security-serverless/**/*" - - - command: .buildkite/scripts/steps/functional/security_serverless_investigations.sh - label: 'Serverless Investigations - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 120 - parallelism: 6 - retry: - automatic: - - exit_status: '*' - limit: 1 - artifact_paths: - - "target/kibana-security-serverless/**/*" diff --git a/.buildkite/scripts/lifecycle/post_command.sh b/.buildkite/scripts/lifecycle/post_command.sh index 77f11120a4abe..01877bedbef8c 100755 --- a/.buildkite/scripts/lifecycle/post_command.sh +++ b/.buildkite/scripts/lifecycle/post_command.sh @@ -31,8 +31,10 @@ if [[ "$IS_TEST_EXECUTION_STEP" == "true" ]]; then buildkite-agent artifact upload '.es/**/*.hprof' buildkite-agent artifact upload 'data/es_debug_*.tar.gz' - echo "--- Run Failed Test Reporter" - node scripts/report_failed_tests --build-url="${BUILDKITE_BUILD_URL}#${BUILDKITE_JOB_ID}" 'target/junit/**/*.xml' + if [[ $BUILDKITE_COMMAND_EXIT_STATUS -ne 0 ]]; then + echo "--- Run Failed Test Reporter" + node scripts/report_failed_tests --build-url="${BUILDKITE_BUILD_URL}#${BUILDKITE_JOB_ID}" 'target/junit/**/*.xml' + fi if [[ -d 'target/test_failures' ]]; then buildkite-agent artifact upload 'target/test_failures/**/*' diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index cadfa23b53f9d..80d1312af6e64 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -119,6 +119,15 @@ const uploadPipeline = (pipelineContent: string | object) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/apm_cypress.yml')); } + if ( + (await doAnyChangesMatch([/^x-pack\/plugins\/observability_onboarding/])) || + GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + ) { + pipeline.push( + getPipeline('.buildkite/pipelines/pull_request/observability_onboarding_cypress.yml') + ); + } + if ( (await doAnyChangesMatch([/^x-pack\/plugins\/profiling/])) || GITHUB_PR_LABELS.includes('ci:all-cypress-suites') @@ -132,6 +141,7 @@ const uploadPipeline = (pipelineContent: string | object) => { ) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/fleet_cypress.yml')); pipeline.push(getPipeline('.buildkite/pipelines/pull_request/defend_workflows.yml')); + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/osquery_cypress.yml')); } if ( diff --git a/.buildkite/scripts/steps/artifacts/docker_image.sh b/.buildkite/scripts/steps/artifacts/docker_image.sh index 3743aedfdc655..6a0c23384ef65 100755 --- a/.buildkite/scripts/steps/artifacts/docker_image.sh +++ b/.buildkite/scripts/steps/artifacts/docker_image.sh @@ -98,12 +98,14 @@ steps: - label: ":argo: Update kibana image tag for kibana-controller using gpctl" async: true branches: main - trigger: gpctl-promote + trigger: gpctl-promote-with-e2e-tests build: env: SERVICE_COMMIT_HASH: "$GIT_ABBREV_COMMIT" - REMOTE_SERVICE_CONFIG: https://raw.githubusercontent.com/elastic/serverless-gitops/main/gen/gpctl/kibana/config.yaml - + SERVICE: kibana-controller + NAMESPACE: kibana-ci + IMAGE_NAME: kibana-serverless + REMOTE_SERVICE_CONFIG: https://raw.githubusercontent.com/elastic/serverless-gitops/main/gen/gpctl/kibana/dev.yaml EOF else diff --git a/.buildkite/scripts/steps/functional/defend_workflows.sh b/.buildkite/scripts/steps/functional/defend_workflows.sh index 555d6cba2d374..c85321869f836 100755 --- a/.buildkite/scripts/steps/functional/defend_workflows.sh +++ b/.buildkite/scripts/steps/functional/defend_workflows.sh @@ -12,4 +12,5 @@ echo "--- Defend Workflows Cypress tests" cd x-pack/plugins/security_solution -yarn cypress:dw:run; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:dw:run; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/defend_workflows_serverless.sh b/.buildkite/scripts/steps/functional/defend_workflows_serverless.sh new file mode 100755 index 0000000000000..1a5357744d687 --- /dev/null +++ b/.buildkite/scripts/steps/functional/defend_workflows_serverless.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/steps/functional/common.sh +source .buildkite/scripts/steps/functional/common_cypress.sh + +export JOB=kibana-defend-workflows-serverless-cypress +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} + +echo "--- Defend Workflows Cypress tests on Serverless" + +cd x-pack/plugins/security_solution + +yarn cypress:dw:serverless:run; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/defend_workflows_vagrant.sh b/.buildkite/scripts/steps/functional/defend_workflows_vagrant.sh deleted file mode 100755 index 57b7b43163400..0000000000000 --- a/.buildkite/scripts/steps/functional/defend_workflows_vagrant.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source .buildkite/scripts/steps/functional/common.sh -source .buildkite/scripts/steps/functional/common_cypress.sh - -export JOB=kibana-defend-workflows-endpoint-cypress -export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} - -echo "--- Defend Workflows Endpoint Cypress tests" - -cd x-pack/plugins/security_solution - -yarn cypress:dw:endpoint:run; status=$?; yarn junit:merge && exit $status diff --git a/.buildkite/scripts/steps/functional/fleet_cypress.sh b/.buildkite/scripts/steps/functional/fleet_cypress.sh index a77d912a59fff..d6554b7198b83 100755 --- a/.buildkite/scripts/steps/functional/fleet_cypress.sh +++ b/.buildkite/scripts/steps/functional/fleet_cypress.sh @@ -3,12 +3,14 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh +source .buildkite/scripts/steps/functional/common_cypress.sh export JOB=kibana-fleet-cypress +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} -echo "--- Fleet Cypress tests" +echo "--- Fleet Cypress tests (Chrome)" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --config x-pack/test/fleet_cypress/cli_config.ts +cd x-pack/plugins/fleet + +set +e +yarn cypress:run:reporter; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/on_merge_serverless_ftrs.sh b/.buildkite/scripts/steps/functional/on_merge_serverless_ftrs.sh deleted file mode 100755 index 96a7d82498105..0000000000000 --- a/.buildkite/scripts/steps/functional/on_merge_serverless_ftrs.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -if [[ "$BUILDKITE_BRANCH" == "main" ]]; then - echo "--- Trigger serverless ftr tests" - ts-node .buildkite/scripts/steps/trigger_pipeline.ts kibana-serverless "$BUILDKITE_BRANCH" "$BUILDKITE_COMMIT" "$BUILDKITE_BUILD_ID" -fi diff --git a/.buildkite/scripts/steps/functional/osquery_cypress.sh b/.buildkite/scripts/steps/functional/osquery_cypress.sh index c6cc98d71ce07..da27c27559a45 100755 --- a/.buildkite/scripts/steps/functional/osquery_cypress.sh +++ b/.buildkite/scripts/steps/functional/osquery_cypress.sh @@ -12,4 +12,6 @@ export JOB=kibana-osquery-cypress echo "--- Osquery Cypress tests" -yarn --cwd x-pack/plugins/osquery cypress:run +cd x-pack/plugins/osquery + +yarn --cwd x-pack/plugins/osquery cypress:run \ No newline at end of file diff --git a/.buildkite/scripts/steps/functional/osquery_cypress_burn.sh b/.buildkite/scripts/steps/functional/osquery_cypress_burn.sh index b7fd648e53939..8aa5f28ce67cd 100755 --- a/.buildkite/scripts/steps/functional/osquery_cypress_burn.sh +++ b/.buildkite/scripts/steps/functional/osquery_cypress_burn.sh @@ -14,4 +14,4 @@ buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" 'fals echo "--- Osquery Cypress tests, burning changed specs (Chrome)" -yarn --cwd x-pack/plugins/osquery cypress:changed-specs-only +yarn --cwd x-pack/plugins/osquery cypress:changed-specs-only \ No newline at end of file diff --git a/.buildkite/scripts/steps/functional/profiling_cypress.sh b/.buildkite/scripts/steps/functional/profiling_cypress.sh index 38799acc90778..1f6fb316b77ad 100644 --- a/.buildkite/scripts/steps/functional/profiling_cypress.sh +++ b/.buildkite/scripts/steps/functional/profiling_cypress.sh @@ -13,5 +13,5 @@ echo "--- Profiling Cypress Tests" cd "$XPACK_DIR" -node plugins/profiling/scripts/test/e2e.js \ +NODE_OPTIONS=--openssl-legacy-provider node plugins/profiling/scripts/test/e2e.js \ --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ \ No newline at end of file diff --git a/.buildkite/scripts/steps/functional/response_ops.sh b/.buildkite/scripts/steps/functional/response_ops.sh index 1c065b2373b66..47e19393d8dc9 100755 --- a/.buildkite/scripts/steps/functional/response_ops.sh +++ b/.buildkite/scripts/steps/functional/response_ops.sh @@ -12,4 +12,5 @@ echo "--- Response Ops Cypress Tests on Security Solution" cd x-pack/test/security_solution_cypress -yarn cypress:run:respops:ess; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:run:respops:ess; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/response_ops_cases.sh b/.buildkite/scripts/steps/functional/response_ops_cases.sh index 52eb3fce1985e..739cca4e97dc0 100755 --- a/.buildkite/scripts/steps/functional/response_ops_cases.sh +++ b/.buildkite/scripts/steps/functional/response_ops_cases.sh @@ -12,4 +12,5 @@ echo "--- Response Ops Cases Cypress Tests on Security Solution" cd x-pack/test/security_solution_cypress -yarn cypress:run:cases:ess; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:run:cases:ess; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/security_serverless.sh b/.buildkite/scripts/steps/functional/security_serverless.sh index 0a14478414bf3..9903f44da0373 100644 --- a/.buildkite/scripts/steps/functional/security_serverless.sh +++ b/.buildkite/scripts/steps/functional/security_serverless.sh @@ -12,4 +12,5 @@ echo "--- Security Serverless Cypress Tests" cd x-pack/test/security_solution_cypress -yarn cypress:run:serverless; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:run:serverless; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/security_serverless_explore.sh b/.buildkite/scripts/steps/functional/security_serverless_explore.sh index 805f4fe147180..52237e4d8f902 100644 --- a/.buildkite/scripts/steps/functional/security_serverless_explore.sh +++ b/.buildkite/scripts/steps/functional/security_serverless_explore.sh @@ -12,4 +12,5 @@ echo "--- Explore - Security Solution Cypress Tests" cd x-pack/test/security_solution_cypress -yarn cypress:explore:run:serverless; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:explore:run:serverless; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/security_serverless_investigations.sh b/.buildkite/scripts/steps/functional/security_serverless_investigations.sh index 15f249d474c40..6a79a92871fb2 100644 --- a/.buildkite/scripts/steps/functional/security_serverless_investigations.sh +++ b/.buildkite/scripts/steps/functional/security_serverless_investigations.sh @@ -12,4 +12,5 @@ echo "--- Investigations Cypress Tests on Serverless" cd x-pack/test/security_solution_cypress -yarn cypress:investigations:run:serverless; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:investigations:run:serverless; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/security_solution.sh b/.buildkite/scripts/steps/functional/security_solution.sh index f02419a0b6f8c..fdba86f4dee4e 100755 --- a/.buildkite/scripts/steps/functional/security_solution.sh +++ b/.buildkite/scripts/steps/functional/security_solution.sh @@ -12,4 +12,5 @@ echo "--- Security Solution Cypress tests (Chrome)" cd x-pack/test/security_solution_cypress -yarn cypress:run:ess; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:run:ess; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/security_solution_explore.sh b/.buildkite/scripts/steps/functional/security_solution_explore.sh index e7d21b61f7209..e5513e63a7664 100644 --- a/.buildkite/scripts/steps/functional/security_solution_explore.sh +++ b/.buildkite/scripts/steps/functional/security_solution_explore.sh @@ -12,4 +12,5 @@ echo "--- Explore Cypress Tests on Security Solution" cd x-pack/test/security_solution_cypress -yarn cypress:explore:run:ess; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:explore:run:ess; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/security_solution_investigations.sh b/.buildkite/scripts/steps/functional/security_solution_investigations.sh index 217e79bc9ecfc..d26261b638d5a 100644 --- a/.buildkite/scripts/steps/functional/security_solution_investigations.sh +++ b/.buildkite/scripts/steps/functional/security_solution_investigations.sh @@ -12,4 +12,5 @@ echo "--- Investigations - Security Solution Cypress Tests" cd x-pack/test/security_solution_cypress -yarn cypress:investigations:run:ess; status=$?; yarn junit:merge && exit $status +set +e +yarn cypress:investigations:run:ess; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/serverless_ftr.sh b/.buildkite/scripts/steps/functional/serverless_ftr.sh deleted file mode 100755 index 335b4b97f1445..0000000000000 --- a/.buildkite/scripts/steps/functional/serverless_ftr.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source .buildkite/scripts/steps/functional/common.sh - -export JOB="kibana-serverless-$SERVERLESS_ENVIRONMENT" - -if [[ "$SERVERLESS_ENVIRONMENT" == "search" ]]; then - SERVERLESS_CONFIGS=( - "x-pack/test_serverless/api_integration/test_suites/search/config.ts" - "x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts" - "x-pack/test_serverless/functional/test_suites/search/config.ts" - ) -elif [[ "$SERVERLESS_ENVIRONMENT" == "search.examples" ]]; then - SERVERLESS_CONFIGS=( - "x-pack/test_serverless/functional/test_suites/search/config.examples.ts" - ) -elif [[ "$SERVERLESS_ENVIRONMENT" == "observability" ]]; then - SERVERLESS_CONFIGS=( - "x-pack/test_serverless/api_integration/test_suites/observability/config.ts" - "x-pack/test_serverless/api_integration/test_suites/observability/config.feature_flags.ts" - "x-pack/test_serverless/functional/test_suites/observability/config.ts" - "x-pack/test_serverless/functional/test_suites/observability/cypress/config_headless.ts" - ) -elif [[ "$SERVERLESS_ENVIRONMENT" == "observability.examples" ]]; then - SERVERLESS_CONFIGS=( - "x-pack/test_serverless/functional/test_suites/observability/config.examples.ts" - ) -elif [[ "$SERVERLESS_ENVIRONMENT" == "security" ]]; then - SERVERLESS_CONFIGS=( - "x-pack/test_serverless/api_integration/test_suites/security/config.ts" - "x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts" - "x-pack/test_serverless/functional/test_suites/security/config.ts" - ) -elif [[ "$SERVERLESS_ENVIRONMENT" == "security.examples" ]]; then - SERVERLESS_CONFIGS=( - "x-pack/test_serverless/functional/test_suites/security/config.examples.ts" - ) -fi - -EXIT_CODE=0 -OFFENDING_CONFIG= - -for CONFIG in "${SERVERLESS_CONFIGS[@]}" -do - echo "--- $ node scripts/functional_tests --bail --config $CONFIG" - set +e; - node ./scripts/functional_tests \ - --bail \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --config="$CONFIG" - LAST_CODE=$? - set -e; - - if [ $LAST_CODE -ne 0 ]; then - EXIT_CODE=10 - OFFENDING_CONFIG=$CONFIG - fi -done - -echo "--- Serverless FTR Results for $JOB" -if [ $EXIT_CODE -eq 0 ]; then - echo "✅ Success!" -elif [ $EXIT_CODE -eq 10 ]; then - echo "❌ Failed in config: $OFFENDING_CONFIG, exit code set to 10 for soft-failure" -else - echo "❌ Failed." -fi - -exit $EXIT_CODE diff --git a/.buildkite/scripts/steps/test/jest_integration.sh b/.buildkite/scripts/steps/test/jest_integration.sh index fd7b9a1d6ad54..6ebff3ae984b8 100755 --- a/.buildkite/scripts/steps/test/jest_integration.sh +++ b/.buildkite/scripts/steps/test/jest_integration.sh @@ -8,5 +8,9 @@ is_test_execution_step .buildkite/scripts/bootstrap.sh +echo '--- Docker login' +echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co +trap 'docker logout docker.elastic.co' EXIT + echo '--- Jest Integration Tests' .buildkite/scripts/steps/test/jest_parallel.sh jest.integration.config.js diff --git a/.eslintrc.js b/.eslintrc.js index 5b5ef73ed3806..6657ba3cb1f01 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -909,11 +909,15 @@ module.exports = { { files: [ 'x-pack/plugins/apm/**/*.{js,mjs,ts,tsx}', - 'x-pack/plugins/observability/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/exploratory_view/**/*.{js,mjs,ts,tsx}', - 'x-pack/plugins/ux/**/*.{js,mjs,ts,tsx}', - 'x-pack/plugins/synthetics/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/infra/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/observability/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/observability_ai_assistant/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/observability_onboarding/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/observability_shared/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/profiling/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/synthetics/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/ux/**/*.{js,mjs,ts,tsx}', ], rules: { '@kbn/telemetry/event_generating_elements_should_be_instrumented': 'error', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b3c7089ed6b4c..89d44a44a209b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -297,6 +297,7 @@ x-pack/plugins/cross_cluster_replication @elastic/platform-deployment-management packages/kbn-crypto @elastic/kibana-security packages/kbn-crypto-browser @elastic/kibana-core x-pack/plugins/custom_branding @elastic/appex-sharedux +packages/kbn-custom-integrations @elastic/infra-monitoring-ui src/plugins/custom_integrations @elastic/fleet packages/kbn-cypress-config @elastic/kibana-operations x-pack/plugins/dashboard_enhanced @elastic/kibana-presentation @@ -363,6 +364,7 @@ packages/kbn-eslint-plugin-telemetry @elastic/actionable-observability x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin @elastic/kibana-security packages/kbn-event-annotation-common @elastic/kibana-visualizations packages/kbn-event-annotation-components @elastic/kibana-visualizations +src/plugins/event_annotation_listing @elastic/kibana-visualizations src/plugins/event_annotation @elastic/kibana-visualizations x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops x-pack/plugins/event_log @elastic/response-ops @@ -480,7 +482,13 @@ packages/kbn-managed-vscode-config @elastic/kibana-operations packages/kbn-managed-vscode-config-cli @elastic/kibana-operations packages/kbn-management/cards_navigation @elastic/platform-deployment-management src/plugins/management @elastic/platform-deployment-management +packages/kbn-management/settings/components/field_input @elastic/platform-deployment-management +packages/kbn-management/settings/components/field_row @elastic/platform-deployment-management +packages/kbn-management/settings/field_definition @elastic/platform-deployment-management +packages/kbn-management/settings/setting_ids @elastic/appex-sharedux @elastic/platform-deployment-management packages/kbn-management/settings/section_registry @elastic/appex-sharedux @elastic/platform-deployment-management +packages/kbn-management/settings/types @elastic/platform-deployment-management +packages/kbn-management/settings/utilities @elastic/platform-deployment-management packages/kbn-management/storybook/config @elastic/platform-deployment-management test/plugin_functional/plugins/management_test_plugin @elastic/kibana-app-services packages/kbn-mapbox-gl @elastic/kibana-gis @@ -488,6 +496,7 @@ x-pack/examples/third_party_maps_source_example @elastic/kibana-gis src/plugins/maps_ems @elastic/kibana-gis x-pack/plugins/maps @elastic/kibana-gis x-pack/packages/maps/vector_tile_utils @elastic/kibana-gis +x-pack/plugins/metrics_data_access @elastic/infra-monitoring-ui x-pack/packages/ml/agg_utils @elastic/ml-ui x-pack/packages/ml/anomaly_utils @elastic/ml-ui x-pack/packages/ml/category_validator @elastic/ml-ui @@ -546,7 +555,9 @@ packages/kbn-plugin-helpers @elastic/kibana-operations examples/portable_dashboards_example @elastic/kibana-presentation examples/preboot_example @elastic/kibana-security @elastic/kibana-core src/plugins/presentation_util @elastic/kibana-presentation +x-pack/plugins/profiling_data_access @elastic/profiling-ui x-pack/plugins/profiling @elastic/profiling-ui +packages/kbn-profiling-utils @elastic/profiling-ui x-pack/packages/kbn-random-sampling @elastic/kibana-visualizations packages/kbn-react-field @elastic/kibana-data-discovery packages/react/kibana_context/common @elastic/appex-sharedux @@ -596,6 +607,7 @@ src/plugins/screenshot_mode @elastic/appex-sharedux x-pack/examples/screenshotting_example @elastic/appex-sharedux x-pack/plugins/screenshotting @elastic/kibana-reporting-services packages/kbn-search-api-panels @elastic/enterprise-search-frontend +packages/kbn-search-connectors @elastic/enterprise-search-frontend examples/search_examples @elastic/kibana-data-discovery packages/kbn-search-response-warnings @elastic/kibana-data-discovery x-pack/plugins/searchprofiler @elastic/platform-deployment-management @@ -632,9 +644,13 @@ packages/kbn-securitysolution-utils @elastic/security-detection-engine packages/kbn-server-http-tools @elastic/kibana-core packages/kbn-server-route-repository @elastic/apm-ui x-pack/plugins/serverless @elastic/appex-sharedux +packages/serverless/settings/common @elastic/appex-sharedux @elastic/platform-deployment-management x-pack/plugins/serverless_observability @elastic/appex-sharedux @elastic/apm-ui +packages/serverless/settings/observability_project @elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management packages/serverless/project_switcher @elastic/appex-sharedux x-pack/plugins/serverless_search @elastic/enterprise-search-frontend +packages/serverless/settings/search_project @elastic/enterprise-search-frontend @elastic/platform-deployment-management +packages/serverless/settings/security_project @elastic/security-solution @elastic/platform-deployment-management packages/serverless/storybook/config @elastic/appex-sharedux packages/serverless/types @elastic/appex-sharedux test/plugin_functional/plugins/session_notifications @elastic/kibana-core @@ -709,6 +725,7 @@ test/server_integration/plugins/status_plugin_b @elastic/kibana-core packages/kbn-std @elastic/kibana-core packages/kbn-stdio-dev-helpers @elastic/kibana-operations packages/kbn-storybook @elastic/kibana-operations +packages/kbn-subscription-tracking @elastic/security-threat-hunting-investigations x-pack/plugins/synthetics @elastic/uptime x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture @elastic/response-ops x-pack/test/plugin_api_perf/plugins/task_manager_performance @elastic/response-ops @@ -751,6 +768,7 @@ test/plugin_functional/plugins/ui_settings_plugin @elastic/kibana-core packages/kbn-ui-shared-deps-npm @elastic/kibana-operations packages/kbn-ui-shared-deps-src @elastic/kibana-operations packages/kbn-ui-theme @elastic/kibana-operations +packages/kbn-unified-data-table @elastic/kibana-data-discovery packages/kbn-unified-doc-viewer @elastic/kibana-data-discovery examples/unified_doc_viewer @elastic/kibana-core src/plugins/unified_doc_viewer @elastic/kibana-data-discovery @@ -793,6 +811,7 @@ src/plugins/visualizations @elastic/kibana-visualizations x-pack/plugins/watcher @elastic/platform-deployment-management packages/kbn-web-worker-stub @elastic/kibana-operations packages/kbn-whereis-pkg-cli @elastic/kibana-operations +packages/kbn-xstate-utils @elastic/infra-monitoring-ui packages/kbn-yarn-lock-validator @elastic/kibana-operations #### ## Everything below this line overrides the default assignments for each package. @@ -909,6 +928,7 @@ x-pack/plugins/infra/server/lib/alerting @elastic/actionable-observability /x-pack/test/functional/apps/monitoring @elastic/infra-monitoring-ui /x-pack/test/api_integration/apis/monitoring @elastic/infra-monitoring-ui /x-pack/test/api_integration/apis/monitoring_collection @elastic/infra-monitoring-ui +/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer @elastic/infra-monitoring-ui # Fleet /fleet_packages.json @elastic/fleet @@ -956,6 +976,7 @@ x-pack/plugins/infra/server/lib/alerting @elastic/actionable-observability /test/functional/apps/dashboard_elements/ @elastic/kibana-presentation /test/functional/services/dashboard/ @elastic/kibana-presentation /x-pack/test/functional/apps/canvas/ @elastic/kibana-presentation +/x-pack/test_serverless/functional/test_suites/search/dashboards/ @elastic/kibana-presentation /test/plugin_functional/test_suites/panel_actions @elastic/kibana-presentation #CC# /src/plugins/kibana_react/public/code_editor/ @elastic/kibana-presentation @@ -1010,7 +1031,7 @@ x-pack/plugins/infra/server/lib/alerting @elastic/actionable-observability /.buildkite/ @elastic/kibana-operations /kbn_pm/ @elastic/kibana-operations /x-pack/dev-tools @elastic/kibana-operations -catalog-info.yaml @elastic/kibana-operations @elastic/kibana-tech-leads +/catalog-info.yaml @elastic/kibana-operations @elastic/kibana-tech-leads # Appex QA /src/dev/code_coverage @elastic/appex-qa @@ -1023,6 +1044,7 @@ catalog-info.yaml @elastic/kibana-operations @elastic/kibana-tech-leads /packages/kbn-test/src/functional_test_runner @elastic/appex-qa /packages/kbn-performance-testing-dataset-extractor @elastic/appex-qa /x-pack/test_serverless/**/*config.base.ts @elastic/appex-qa +/x-pack/test_serverless/**/deployment_agnostic_services.ts @elastic/appex-qa # Core /config/ @elastic/kibana-core @@ -1084,12 +1106,19 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/test/functional/services/cases/ @elastic/response-ops /x-pack/test/functional_with_es_ssl/apps/cases/ @elastic/response-ops /x-pack/test/api_integration/apis/cases/ @elastic/response-ops +/x-pack/test_serverless/functional/test_suites/observability/cases @elastic/response-ops +/x-pack/test_serverless/functional/test_suites/search/cases/ @elastic/response-ops +/x-pack/test_serverless/functional/test_suites/security/ftr/cases/ @elastic/response-ops +/x-pack/test_serverless/api_integration/test_suites/search/cases/ @elastic/response-ops +/x-pack/test_serverless/api_integration/test_suites/observability/cases/ @elastic/response-ops +/x-pack/test_serverless/api_integration/test_suites/security/cases/ @elastic/response-ops # Enterprise Search /x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend /x-pack/plugins/enterprise_search/public/applications/shared/doc_links @elastic/ent-search-docs-team # Management Experience - Deployment Management +/x-pack/test_serverless/functional/test_suites/common/index_management/ @elastic/platform-deployment-management #CC# /x-pack/plugins/cross_cluster_replication/ @elastic/platform-deployment-management # Security Solution @@ -1291,14 +1320,10 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/plugins/security_solution/server/lists_integration/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lib/license/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/fleet_integration/ @elastic/security-defend-workflows -/x-pack/plugins/security_solution/scripts/endpoint/event_filters/ @elastic/security-defend-workflows -/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/ @elastic/security-defend-workflows +/x-pack/plugins/security_solution/scripts/endpoint/ @elastic/security-defend-workflows /x-pack/test/security_solution_endpoint/ @elastic/security-defend-workflows /x-pack/test/security_solution_endpoint_api_int/ @elastic/security-defend-workflows /x-pack/test_serverless/shared/lib/security/kibana_roles/ @elastic/security-defend-workflows -/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management @elastic/security-defend-workflows -/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management @elastic/security-defend-workflows -/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management @elastic/security-defend-workflows /x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management @elastic/security-defend-workflows /x-pack/plugins/security_solution_serverless/server/endpoint @elastic/security-defend-workflows @@ -1314,6 +1339,7 @@ x-pack/test/security_solution_cypress/config.ts @elastic/security-engineering-pr x-pack/test/security_solution_cypress/runner.ts @elastic/security-engineering-productivity x-pack/test/security_solution_cypress/serverless_config.ts @elastic/security-engineering-productivity x-pack/test/security_solution_cypress/cypress/tags.ts @elastic/security-engineering-productivity +x-pack/plugins/security_solution/scripts/run_cypress @MadameSheema @patrykkopycinski @oatkiller @maximpn @banderror ## Security Solution sub teams - adaptive-workload-protection x-pack/plugins/security_solution/public/common/components/sessions_viewer @elastic/kibana-cloud-security-posture @@ -1374,6 +1400,9 @@ x-pack/plugins/translations/translations # Profiling api integration testing x-pack/test/profiling_api_integration @elastic/profiling-ui +# Observability shared profiling +x-pack/plugins/observability_shared/public/components/profiling @elastic/profiling-ui + # Shared UX packages/react @elastic/appex-sharedux diff --git a/.github/workflows/create-deploy-tag.yml b/.github/workflows/create-deploy-tag.yml index a2c0bd0539984..abe2e131165ec 100644 --- a/.github/workflows/create-deploy-tag.yml +++ b/.github/workflows/create-deploy-tag.yml @@ -19,7 +19,7 @@ concurrency: jobs: create-deploy-tag: # Temporary, we need a way to limit this to a GitHub team instead of specific users - if: contains('["watson","clintandrewhall","kobelb","lukeelmers","thomasneirynck"]', github.triggering_actor) + if: contains('["watson","clintandrewhall","kobelb","lukeelmers","thomasneirynck","jbudz","mistic","delanni","Ikuni17"]', github.triggering_actor) runs-on: ubuntu-latest permissions: contents: write @@ -63,7 +63,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "A new has been promoted to QA 🎉\n\nOnce promotion is complete, please begin any required manual testing.\n\n*Remember:* Promotion to Staging is currently a manual process and will proceed once the build is signed off in QA." + "text": "Promotion of a new to QA has been initiated!\n\nOnce promotion is complete, please begin any required manual testing.\n\n*Remember:* Promotion to Staging is currently a manual process and will proceed once the build is signed off in QA." } }, { @@ -75,7 +75,7 @@ jobs: }, { "type": "mrkdwn", - "text": "*Workflow run:*\n" + "text": "*Commit:*\n" }, { "type": "mrkdwn", @@ -87,50 +87,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "Expected Staging promotion date:" - }, - "accessory": { - "type": "datepicker", - "placeholder": { - "type": "plain_text", - "text": "Select a date", - "emoji": true - }, - "action_id": "datepicker-action" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Useful links:*\n\n • \n • " - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Day 1 to-do list*" - }, - "accessory": { - "type": "checkboxes", - "options": [ - { - "text": { - "type": "mrkdwn", - "text": "Verify successful promotion to QA" - }, - "value": "value-0" - }, - { - "text": { - "type": "mrkdwn", - "text": "Notify Security Solution team to beging manual testing" - }, - "value": "value-1" - } - ], - "action_id": "checkboxes-action" + "text": "${{ join(fromJSON(env.JSON_USEFUL_LINKS_ARRAY), '\n • ') }}" } } ] @@ -138,6 +95,16 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.DEPLOY_TAGGER_SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + JSON_USEFUL_LINKS_ARRAY: | + [ + "*Useful links:*\\n", + "", + "", + "", + " (use Elastic Cloud Staging VPN)", + "", + "" + ] - name: Post Slack failure message if: failure() uses: slackapi/slack-github-action@v1.24.0 @@ -149,7 +116,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "Promotion of to QA failed ⛔️" + "text": "Creation of deploy tag on failed ⛔️" } }, { @@ -161,7 +128,7 @@ jobs: }, { "type": "mrkdwn", - "text": "*Workflow run:*\n" + "text": "*Commit:*\n" } ] }, @@ -169,7 +136,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*Useful links:*\n\n • " + "text": "${{ join(fromJSON(env.JSON_USEFUL_LINKS_ARRAY), '\n • ') }}" } } ] @@ -177,3 +144,9 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.DEPLOY_TAGGER_SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + JSON_USEFUL_LINKS_ARRAY: | + [ + "*Useful links:*\\n", + "", + "" + ] diff --git a/.gitignore b/.gitignore index 0107ffdef5e19..3a5dda1378c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,8 @@ npm-debug.log* ## @cypress/snapshot from apm plugin /snapshots.js /apm-diagnostics*.json +/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/*.actual.png +/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/*.diff.png # transpiled cypress config x-pack/plugins/fleet/cypress.config.d.ts diff --git a/.i18nrc.json b/.i18nrc.json index 534a18b14ff86..512f65223740e 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -13,6 +13,7 @@ "contentManagement": "packages/content-management", "core": ["src/core", "packages/core"], "customIntegrations": "src/plugins/custom_integrations", + "customIntegrationsPackage": "packages/kbn-custom-integrations", "dashboard": "src/plugins/dashboard", "domDragDrop": "packages/kbn-dom-drag-drop", "controls": "src/plugins/controls", @@ -43,6 +44,7 @@ "expressionShape": "src/plugins/expression_shape", "expressionTagcloud": "src/plugins/chart_expressions/expression_tagcloud", "eventAnnotation": "src/plugins/event_annotation", + "eventAnnotationListing": "src/plugins/event_annotation_listing", "eventAnnotationCommon": "packages/kbn-event-annotation-common", "eventAnnotationComponents": "packages/kbn-event-annotation-components", "fieldFormats": "src/plugins/field_formats", @@ -90,6 +92,7 @@ "savedObjects": "src/plugins/saved_objects", "savedObjectsFinder": "src/plugins/saved_objects_finder", "savedObjectsManagement": "src/plugins/saved_objects_management", + "searchConnectors": "packages/kbn-search-connectors", "server": "src/legacy/server", "share": "src/plugins/share", "sharedUXPackages": "packages/shared-ux", @@ -126,7 +129,8 @@ "unifiedDocViewer": ["src/plugins/unified_doc_viewer", "packages/kbn-unified-doc-viewer"], "unifiedSearch": "src/plugins/unified_search", "unifiedFieldList": "packages/kbn-unified-field-list", - "unifiedHistogram": "src/plugins/unified_histogram" + "unifiedHistogram": "src/plugins/unified_histogram", + "unifiedDataTable": "packages/kbn-unified-data-table" }, "translations": [] } diff --git a/api_docs/actions.devdocs.json b/api_docs/actions.devdocs.json index ab2fd467cd958..4e648524c704d 100644 --- a/api_docs/actions.devdocs.json +++ b/api_docs/actions.devdocs.json @@ -2094,7 +2094,9 @@ "signature": [ "(requesterId: string, actionsToExecute: ", "ExecuteOptions", - "[]) => Promise" + "[]) => Promise<", + "ExecutionResponse", + ">" ], "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", "deprecated": false, @@ -2967,7 +2969,9 @@ "signature": [ "(requesterId: string, actionsToExecute: ", "ExecuteOptions", - "[]) => Promise" + "[]) => Promise<", + "ExecutionResponse", + ">" ], "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", "deprecated": false, @@ -3249,7 +3253,9 @@ }, ">; bulkEnqueueExecution: (options: ", "ExecuteOptions", - "[]) => Promise; ephemeralEnqueuedExecution: (options: ", + "[]) => Promise<", + "ExecutionResponse", + ">; ephemeralEnqueuedExecution: (options: ", "ExecuteOptions", ") => Promise<", { @@ -3509,6 +3515,38 @@ "trackAdoption": false, "children": [], "returnComment": [] + }, + { + "parentPluginId": "actions", + "id": "def-server.PluginSetupContract.setEnabledConnectorTypes", + "type": "Function", + "tags": [], + "label": "setEnabledConnectorTypes", + "description": [], + "signature": [ + "(connectorTypes: string[]) => void" + ], + "path": "x-pack/plugins/actions/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "actions", + "id": "def-server.PluginSetupContract.setEnabledConnectorTypes.$1", + "type": "Array", + "tags": [], + "label": "connectorTypes", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/actions/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "lifecycle": "setup", diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 9e00a4bb2b49a..a91635a8423f5 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 267 | 0 | 261 | 30 | +| 269 | 0 | 263 | 31 | ## Client diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 4401ed9fbf670..4acf7fa44d036 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.devdocs.json b/api_docs/aiops.devdocs.json index 7d557f4d3c601..9ac0267216277 100644 --- a/api_docs/aiops.devdocs.json +++ b/api_docs/aiops.devdocs.json @@ -676,6 +676,20 @@ "path": "x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "aiops", + "id": "def-public.AiopsAppDependencies.isServerless", + "type": "CompoundType", + "tags": [], + "label": "isServerless", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -759,6 +773,22 @@ "path": "x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "aiops", + "id": "def-public.ChangePointDetectionAppStateProps.isServerless", + "type": "CompoundType", + "tags": [], + "label": "isServerless", + "description": [ + "Optional flag to indicate whether kibana is running in serverless" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -842,6 +872,22 @@ "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "aiops", + "id": "def-public.LogCategorizationAppStateProps.isServerless", + "type": "CompoundType", + "tags": [], + "label": "isServerless", + "description": [ + "Optional flag to indicate whether kibana is running in serverless" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -941,6 +987,22 @@ "path": "x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "aiops", + "id": "def-public.LogRateAnalysisAppStateProps.isServerless", + "type": "CompoundType", + "tags": [], + "label": "isServerless", + "description": [ + "Optional flag to indicate whether kibana is running in serverless" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1172,6 +1234,22 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "aiops", + "id": "def-public.LogRateAnalysisContentWrapperProps.isServerless", + "type": "CompoundType", + "tags": [], + "label": "isServerless", + "description": [ + "Optional flag to indicate whether kibana is running in serverless" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index a8e8744a274bb..7ae72539c33e5 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 61 | 1 | 3 | 0 | +| 66 | 1 | 4 | 1 | ## Client diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index b6e47ef82750d..00ded98b3bd48 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -2820,7 +2820,7 @@ "label": "monitoring", "description": [], "signature": [ - "Readonly<{} & { run: Readonly<{} & { history: Readonly<{ outcome?: Readonly<{ warning?: \"execute\" | \"license\" | \"validate\" | \"timeout\" | \"unknown\" | \"read\" | \"decrypt\" | \"disabled\" | \"maxExecutableActions\" | \"maxAlerts\" | null | undefined; outcomeOrder?: number | undefined; outcomeMsg?: string[] | null | undefined; } & { outcome: \"warning\" | \"succeeded\" | \"failed\"; alertsCount: Readonly<{ recovered?: number | null | undefined; active?: number | null | undefined; new?: number | null | undefined; ignored?: number | null | undefined; } & {}>; }> | undefined; duration?: number | undefined; } & { timestamp: number; success: boolean; }>[]; calculated_metrics: Readonly<{ p50?: number | undefined; p95?: number | undefined; p99?: number | undefined; } & { success_ratio: number; }>; last_run: Readonly<{} & { timestamp: string; metrics: Readonly<{ duration?: number | undefined; total_search_duration_ms?: number | null | undefined; total_indexing_duration_ms?: number | null | undefined; total_alerts_detected?: number | null | undefined; total_alerts_created?: number | null | undefined; gap_duration_s?: number | null | undefined; } & {}>; }>; }>; }> | undefined" + "Readonly<{} & { run: Readonly<{} & { history: Readonly<{ outcome?: Readonly<{ warning?: \"execute\" | \"license\" | \"validate\" | \"timeout\" | \"unknown\" | \"read\" | \"decrypt\" | \"disabled\" | \"maxExecutableActions\" | \"maxAlerts\" | \"maxQueuedActions\" | null | undefined; outcomeOrder?: number | undefined; outcomeMsg?: string[] | null | undefined; } & { outcome: \"warning\" | \"succeeded\" | \"failed\"; alertsCount: Readonly<{ recovered?: number | null | undefined; active?: number | null | undefined; new?: number | null | undefined; ignored?: number | null | undefined; } & {}>; }> | undefined; duration?: number | undefined; } & { timestamp: number; success: boolean; }>[]; calculated_metrics: Readonly<{ p50?: number | undefined; p95?: number | undefined; p99?: number | undefined; } & { success_ratio: number; }>; last_run: Readonly<{} & { timestamp: string; metrics: Readonly<{ duration?: number | undefined; total_search_duration_ms?: number | null | undefined; total_indexing_duration_ms?: number | null | undefined; total_alerts_detected?: number | null | undefined; total_alerts_created?: number | null | undefined; gap_duration_s?: number | null | undefined; } & {}>; }>; }>; }> | undefined" ], "path": "x-pack/plugins/alerting/server/application/rule/types/rule.ts", "deprecated": false, @@ -2876,7 +2876,7 @@ "label": "lastRun", "description": [], "signature": [ - "Readonly<{ warning?: \"execute\" | \"license\" | \"validate\" | \"timeout\" | \"unknown\" | \"read\" | \"decrypt\" | \"disabled\" | \"maxExecutableActions\" | \"maxAlerts\" | null | undefined; outcomeOrder?: number | undefined; outcomeMsg?: string[] | null | undefined; } & { outcome: \"warning\" | \"succeeded\" | \"failed\"; alertsCount: Readonly<{ recovered?: number | null | undefined; active?: number | null | undefined; new?: number | null | undefined; ignored?: number | null | undefined; } & {}>; }> | null | undefined" + "Readonly<{ warning?: \"execute\" | \"license\" | \"validate\" | \"timeout\" | \"unknown\" | \"read\" | \"decrypt\" | \"disabled\" | \"maxExecutableActions\" | \"maxAlerts\" | \"maxQueuedActions\" | null | undefined; outcomeOrder?: number | undefined; outcomeMsg?: string[] | null | undefined; } & { outcome: \"warning\" | \"succeeded\" | \"failed\"; alertsCount: Readonly<{ recovered?: number | null | undefined; active?: number | null | undefined; new?: number | null | undefined; ignored?: number | null | undefined; } & {}>; }> | null | undefined" ], "path": "x-pack/plugins/alerting/server/application/rule/types/rule.ts", "deprecated": false, @@ -3314,7 +3314,7 @@ }, { "plugin": "observability", - "path": "x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts" + "path": "x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts" }, { "plugin": "infra", @@ -3348,10 +3348,6 @@ "plugin": "stackAlerts", "path": "x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts" }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts" - }, { "plugin": "stackAlerts", "path": "x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.ts" @@ -4354,7 +4350,7 @@ "label": "AlertingRulesConfig", "description": [], "signature": [ - "Pick; run: Readonly<{ timeout?: string | undefined; ruleTypeOverrides?: Readonly<{ timeout?: string | undefined; } & { id: string; }>[] | undefined; } & { actions: Readonly<{ connectorTypeOverrides?: Readonly<{ max?: number | undefined; } & { id: string; }>[] | undefined; } & { max: number; }>; alerts: Readonly<{} & { max: number; }>; }>; }>, \"minimumScheduleInterval\"> & { isUsingSecurity: boolean; }" + "Pick; maxScheduledPerMinute: number; run: Readonly<{ timeout?: string | undefined; ruleTypeOverrides?: Readonly<{ timeout?: string | undefined; } & { id: string; }>[] | undefined; } & { actions: Readonly<{ connectorTypeOverrides?: Readonly<{ max?: number | undefined; } & { id: string; }>[] | undefined; } & { max: number; }>; alerts: Readonly<{} & { max: number; }>; }>; }>, \"minimumScheduleInterval\" | \"maxScheduledPerMinute\"> & { isUsingSecurity: boolean; }" ], "path": "x-pack/plugins/alerting/server/config.ts", "deprecated": false, @@ -4891,7 +4887,7 @@ }, " | undefined; getTags: (params: Readonly<{ search?: string | undefined; perPage?: number | undefined; } & { page: number; }>) => Promise<", "GetTagsResult", - ">; getAlertFromRaw: (params: ", + ">; getScheduleFrequency: () => Promise>; getAlertFromRaw: (params: ", "GetAlertFromRawParams", ") => ", { @@ -5109,50 +5105,44 @@ }, { "parentPluginId": "alerting", - "id": "def-common.formatDefaultAggregationResult", + "id": "def-common.convertDurationToFrequency", "type": "Function", "tags": [], - "label": "formatDefaultAggregationResult", + "label": "convertDurationToFrequency", "description": [], "signature": [ - "(aggregations: ", - { - "pluginId": "alerting", - "scope": "common", - "docId": "kibAlertingPluginApi", - "section": "def-common.DefaultRuleAggregationResult", - "text": "DefaultRuleAggregationResult" - }, - ") => ", - { - "pluginId": "alerting", - "scope": "common", - "docId": "kibAlertingPluginApi", - "section": "def-common.RuleAggregationFormattedResult", - "text": "RuleAggregationFormattedResult" - } + "(duration: string, denomination: number) => number" ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", + "path": "x-pack/plugins/alerting/common/parse_duration.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "alerting", - "id": "def-common.formatDefaultAggregationResult.$1", - "type": "Object", + "id": "def-common.convertDurationToFrequency.$1", + "type": "string", "tags": [], - "label": "aggregations", + "label": "duration", "description": [], "signature": [ - { - "pluginId": "alerting", - "scope": "common", - "docId": "kibAlertingPluginApi", - "section": "def-common.DefaultRuleAggregationResult", - "text": "DefaultRuleAggregationResult" - } + "string" ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", + "path": "x-pack/plugins/alerting/common/parse_duration.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "alerting", + "id": "def-common.convertDurationToFrequency.$2", + "type": "number", + "tags": [], + "label": "denomination", + "description": [], + "signature": [ + "number" + ], + "path": "x-pack/plugins/alerting/common/parse_duration.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -5363,41 +5353,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "alerting", - "id": "def-common.getDefaultRuleAggregation", - "type": "Function", - "tags": [], - "label": "getDefaultRuleAggregation", - "description": [], - "signature": [ - "(params?: GetDefaultRuleAggregationParams | undefined) => Record" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "alerting", - "id": "def-common.getDefaultRuleAggregation.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "GetDefaultRuleAggregationParams | undefined" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "alerting", "id": "def-common.getDurationNumberInItsUnit", @@ -5838,126 +5793,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions", - "type": "Interface", - "tags": [], - "label": "AggregateOptions", - "description": [], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions.search", - "type": "string", - "tags": [], - "label": "search", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions.defaultSearchOperator", - "type": "CompoundType", - "tags": [], - "label": "defaultSearchOperator", - "description": [], - "signature": [ - "\"AND\" | \"OR\" | undefined" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions.searchFields", - "type": "Array", - "tags": [], - "label": "searchFields", - "description": [], - "signature": [ - "string[] | undefined" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions.hasReference", - "type": "Object", - "tags": [], - "label": "hasReference", - "description": [], - "signature": [ - "{ type: string; id: string; } | undefined" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions.filter", - "type": "CompoundType", - "tags": [], - "label": "filter", - "description": [], - "signature": [ - "string | ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.KueryNode", - "text": "KueryNode" - }, - " | undefined" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions.page", - "type": "number", - "tags": [], - "label": "page", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.AggregateOptions.perPage", - "type": "number", - "tags": [], - "label": "perPage", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "alerting", "id": "def-common.AlertingFrameworkHealth", @@ -6665,104 +6500,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "alerting", - "id": "def-common.DefaultRuleAggregationResult", - "type": "Interface", - "tags": [], - "label": "DefaultRuleAggregationResult", - "description": [], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "alerting", - "id": "def-common.DefaultRuleAggregationResult.status", - "type": "Object", - "tags": [], - "label": "status", - "description": [], - "signature": [ - "{ buckets: { key: string; doc_count: number; }[]; }" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.DefaultRuleAggregationResult.outcome", - "type": "Object", - "tags": [], - "label": "outcome", - "description": [], - "signature": [ - "{ buckets: { key: string; doc_count: number; }[]; }" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.DefaultRuleAggregationResult.muted", - "type": "Object", - "tags": [], - "label": "muted", - "description": [], - "signature": [ - "{ buckets: { key: number; key_as_string: string; doc_count: number; }[]; }" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.DefaultRuleAggregationResult.enabled", - "type": "Object", - "tags": [], - "label": "enabled", - "description": [], - "signature": [ - "{ buckets: { key: number; key_as_string: string; doc_count: number; }[]; }" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.DefaultRuleAggregationResult.snoozed", - "type": "Object", - "tags": [], - "label": "snoozed", - "description": [], - "signature": [ - "{ count: { doc_count: number; }; }" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.DefaultRuleAggregationResult.tags", - "type": "Object", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "{ buckets: { key: string; doc_count: number; }[]; }" - ], - "path": "x-pack/plugins/alerting/common/default_rule_aggregation.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "alerting", "id": "def-common.ExecutionDuration", @@ -8229,104 +7966,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "alerting", - "id": "def-common.RuleAggregationFormattedResult", - "type": "Interface", - "tags": [], - "label": "RuleAggregationFormattedResult", - "description": [], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "alerting", - "id": "def-common.RuleAggregationFormattedResult.ruleExecutionStatus", - "type": "Object", - "tags": [], - "label": "ruleExecutionStatus", - "description": [], - "signature": [ - "{ [status: string]: number; }" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.RuleAggregationFormattedResult.ruleLastRunOutcome", - "type": "Object", - "tags": [], - "label": "ruleLastRunOutcome", - "description": [], - "signature": [ - "{ [status: string]: number; }" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.RuleAggregationFormattedResult.ruleEnabledStatus", - "type": "Object", - "tags": [], - "label": "ruleEnabledStatus", - "description": [], - "signature": [ - "{ enabled: number; disabled: number; }" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.RuleAggregationFormattedResult.ruleMutedStatus", - "type": "Object", - "tags": [], - "label": "ruleMutedStatus", - "description": [], - "signature": [ - "{ muted: number; unmuted: number; }" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.RuleAggregationFormattedResult.ruleSnoozedStatus", - "type": "Object", - "tags": [], - "label": "ruleSnoozedStatus", - "description": [], - "signature": [ - "{ snoozed: number; }" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "alerting", - "id": "def-common.RuleAggregationFormattedResult.ruleTags", - "type": "Array", - "tags": [], - "label": "ruleTags", - "description": [], - "signature": [ - "string[]" - ], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "alerting", "id": "def-common.RuleExecutionStatus", @@ -10727,13 +10366,7 @@ "description": [], "signature": [ "Pick<", - { - "pluginId": "alerting", - "scope": "common", - "docId": "kibAlertingPluginApi", - "section": "def-common.AggregateOptions", - "text": "AggregateOptions" - }, + "AggregateOptions", ", \"search\" | \"filter\"> & { after?: ", "AggregationsCompositeAggregateKey", " | undefined; maxTags?: number | undefined; }" diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 389d01d2ec502..6a749b86b426b 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 790 | 1 | 759 | 49 | +| 767 | 1 | 736 | 50 | ## Client diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index e1b038257f9bf..7c0688de124a7 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -408,7 +408,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/samples\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search 2023-10-31\" | \"POST /api/apm/services/{serviceName}/annotation 2023-10-31\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/services/{serviceName}/alerts_count\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service-group/counts\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\" | \"GET /internal/apm/traces/{traceId}/spans/{spanId}\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /api/apm/settings/agent-configuration 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/view 2023-10-31\" | \"DELETE /api/apm/settings/agent-configuration 2023-10-31\" | \"PUT /api/apm/settings/agent-configuration 2023-10-31\" | \"POST /api/apm/settings/agent-configuration/search 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/environments 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/agent_name 2023-10-31\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps 2023-10-31\" | \"POST /api/apm/sourcemaps 2023-10-31\" | \"DELETE /api/apm/sourcemaps/{id} 2023-10-31\" | \"POST /api/apm/androidmaps 2023-10-31\" | \"POST /internal/apm/sourcemaps/migrate_fleet_artifacts\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema 2023-10-31\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys 2023-10-31\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/get_latest_agent_versions\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\" | \"GET /internal/apm/mobile-services/{serviceName}/most_used_charts\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/http_requests\" | \"GET /internal/apm/mobile-services/{serviceName}/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/location/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/terms\" | \"GET /internal/apm/mobile-services/{serviceName}/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/detailed_statistics\" | \"GET /internal/apm/diagnostics\" | \"POST /internal/apm/assistant/get_apm_timeseries\" | \"GET /internal/apm/assistant/get_service_summary\" | \"GET /internal/apm/assistant/get_error_document\" | \"POST /internal/apm/assistant/get_correlation_values\" | \"GET /internal/apm/assistant/get_downstream_dependencies\" | \"GET /internal/apm/assistant/get_services_list\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/samples\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search 2023-10-31\" | \"POST /api/apm/services/{serviceName}/annotation 2023-10-31\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/services/{serviceName}/alerts_count\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service-group/counts\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\" | \"GET /internal/apm/traces/{traceId}/spans/{spanId}\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /api/apm/settings/agent-configuration 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/view 2023-10-31\" | \"DELETE /api/apm/settings/agent-configuration 2023-10-31\" | \"PUT /api/apm/settings/agent-configuration 2023-10-31\" | \"POST /api/apm/settings/agent-configuration/search 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/environments 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/agent_name 2023-10-31\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps 2023-10-31\" | \"POST /api/apm/sourcemaps 2023-10-31\" | \"DELETE /api/apm/sourcemaps/{id} 2023-10-31\" | \"POST /internal/apm/sourcemaps/migrate_fleet_artifacts\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema 2023-10-31\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys 2023-10-31\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/get_latest_agent_versions\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\" | \"GET /internal/apm/mobile-services/{serviceName}/most_used_charts\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/http_requests\" | \"GET /internal/apm/mobile-services/{serviceName}/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/location/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/terms\" | \"GET /internal/apm/mobile-services/{serviceName}/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/detailed_statistics\" | \"GET /internal/apm/diagnostics\" | \"POST /internal/apm/assistant/get_apm_timeseries\" | \"GET /internal/apm/assistant/get_service_summary\" | \"GET /internal/apm/assistant/get_error_document\" | \"POST /internal/apm/assistant/get_correlation_values\" | \"GET /internal/apm/assistant/get_downstream_dependencies\" | \"POST /internal/apm/assistant/get_services_list\" | \"GET /internal/apm/services/{serviceName}/profiling/flamegraph\" | \"GET /internal/apm/services/{serviceName}/profiling/functions\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -455,19 +455,127 @@ "label": "APMServerRouteRepository", "description": [], "signature": [ - "{ \"GET /internal/apm/assistant/get_services_list\": { endpoint: \"GET /internal/apm/assistant/get_services_list\"; params?: ", + "{ \"GET /internal/apm/services/{serviceName}/profiling/functions\": { endpoint: \"GET /internal/apm/services/{serviceName}/profiling/functions\"; params?: ", "TypeC", - "<{ query: ", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", "IntersectionC", "<[", "TypeC", "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", "StringC", - "; end: ", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">]>; }>, ", + "TypeC", + "<{ documentType: ", + "UnionC", + "<[", + "LiteralC", + "<", + "ApmDocumentType", + ".ServiceTransactionMetric>, ", + "LiteralC", + "<", + "ApmDocumentType", + ".TransactionMetric>, ", + "LiteralC", + "<", + "ApmDocumentType", + ".TransactionEvent>]>; rollupInterval: ", + "UnionC", + "<[", + "LiteralC", + "<", + "RollupInterval", + ".OneMinute>, ", + "LiteralC", + "<", + "RollupInterval", + ".TenMinutes>, ", + "LiteralC", + "<", + "RollupInterval", + ".SixtyMinutes>, ", + "LiteralC", + "<", + "RollupInterval", + ".None>]>; }>, ", + "TypeC", + "<{ startIndex: ", + "Type", + "; endIndex: ", + "Type", + "; }>]>; }> | undefined; handler: ({}: ", + "APMRouteHandlerResources", + " & { params: { path: { serviceName: string; }; query: { start: number; end: number; } & { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", + "Branded", + "; } & { documentType: ", + "ApmDocumentType", + ".TransactionMetric | ", + "ApmDocumentType", + ".ServiceTransactionMetric | ", + "ApmDocumentType", + ".TransactionEvent; rollupInterval: ", + "RollupInterval", + "; } & { startIndex: number; endIndex: number; }; }; }) => Promise<{ functions: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctions", + "text": "TopNFunctions" + }, + "; hostNames: string[]; } | undefined>; } & ", + "APMRouteCreateOptions", + "; \"GET /internal/apm/services/{serviceName}/profiling/flamegraph\": { endpoint: \"GET /internal/apm/services/{serviceName}/profiling/flamegraph\"; params?: ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", "StringC", - "; }>, ", - "PartialC", - "<{ 'service.environment': ", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "TypeC", + "<{ environment: ", "UnionC", "<[", "LiteralC", @@ -485,29 +593,43 @@ "section": "def-common.NonEmptyStringBrand", "text": "NonEmptyStringBrand" }, - ">]>; healthStatus: ", - "ArrayC", + ">]>; }>, ", + "TypeC", + "<{ documentType: ", + "UnionC", + "<[", + "LiteralC", + "<", + "ApmDocumentType", + ".ServiceTransactionMetric>, ", + "LiteralC", + "<", + "ApmDocumentType", + ".TransactionMetric>, ", + "LiteralC", "<", + "ApmDocumentType", + ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "ServiceHealthStatus", - ".unknown>, ", + "RollupInterval", + ".OneMinute>, ", "LiteralC", "<", - "ServiceHealthStatus", - ".healthy>, ", + "RollupInterval", + ".TenMinutes>, ", "LiteralC", "<", - "ServiceHealthStatus", - ".warning>, ", + "RollupInterval", + ".SixtyMinutes>, ", "LiteralC", "<", - "ServiceHealthStatus", - ".critical>]>>; }>]>; }> | undefined; handler: ({}: ", + "RollupInterval", + ".None>]>; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", - " & { params: { query: { start: string; end: string; } & { 'service.environment'?: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", + " & { params: { path: { serviceName: string; }; query: { start: number; end: number; } & { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", "Branded", " | undefined; healthStatus?: ", + ">; } & { documentType: ", + "ApmDocumentType", + ".TransactionMetric | ", + "ApmDocumentType", + ".ServiceTransactionMetric | ", + "ApmDocumentType", + ".TransactionEvent; rollupInterval: ", + "RollupInterval", + "; }; }; }) => Promise<{ flamegraph: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.BaseFlameGraph", + "text": "BaseFlameGraph" + }, + "; hostNames: string[]; } | undefined>; } & ", + "APMRouteCreateOptions", + "; \"POST /internal/apm/assistant/get_services_list\": { endpoint: \"POST /internal/apm/assistant/get_services_list\"; params?: ", + "TypeC", + "<{ body: ", + "IntersectionC", + "<[", + "TypeC", + "<{ start: ", + "StringC", + "; end: ", + "StringC", + "; }>, ", + "PartialC", + "<{ 'service.environment': ", + "StringC", + "; healthStatus: ", + "ArrayC", + "<", + "UnionC", + "<[", + "LiteralC", + "<", + "ServiceHealthStatus", + ".unknown>, ", + "LiteralC", + "<", + "ServiceHealthStatus", + ".healthy>, ", + "LiteralC", + "<", + "ServiceHealthStatus", + ".warning>, ", + "LiteralC", + "<", + "ServiceHealthStatus", + ".critical>]>>; }>]>; }> | undefined; handler: ({}: ", + "APMRouteHandlerResources", + " & { params: { body: { start: string; end: string; } & { 'service.environment'?: string | undefined; healthStatus?: ", "ServiceHealthStatus", "[] | undefined; }; }; }) => Promise<{ content: ApmServicesListContent; }>; } & ", "APMRouteCreateOptions", @@ -711,25 +887,7 @@ "IntersectionC", "<[", "TypeC", - "<{ 'service.environment': ", - "UnionC", - "<[", - "LiteralC", - "<\"ENVIRONMENT_NOT_DEFINED\">, ", - "LiteralC", - "<\"ENVIRONMENT_ALL\">, ", - "BrandC", - "<", - "StringC", - ", ", - { - "pluginId": "@kbn/io-ts-utils", - "scope": "common", - "docId": "kibKbnIoTsUtilsPluginApi", - "section": "def-common.NonEmptyStringBrand", - "text": "NonEmptyStringBrand" - }, - ">]>; 'service.name': ", + "<{ 'service.name': ", "StringC", "; title: ", "StringC", @@ -813,23 +971,15 @@ "StringC", "; offset: ", "StringC", + "; 'service.environment': ", + "StringC", "; }>]>>; start: ", "StringC", "; end: ", "StringC", "; }>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", - " & { params: { body: { stats: ({ 'service.environment': \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", - "Branded", - "; 'service.name': string; title: string; timeseries: ({ name: ", + " & { params: { body: { stats: ({ 'service.name': string; title: string; timeseries: ({ name: ", "ApmTimeseriesType", ".transactionThroughput | ", "ApmTimeseriesType", @@ -845,7 +995,7 @@ "LatencyAggregationType", "; } & { 'transaction.type'?: string | undefined; }) | { name: ", "ApmTimeseriesType", - ".errorEventRate; }; } & { filter?: string | undefined; offset?: string | undefined; })[]; start: string; end: string; }; }; }) => Promise<{ content: Omit<", + ".errorEventRate; }; } & { filter?: string | undefined; offset?: string | undefined; 'service.environment'?: string | undefined; })[]; start: string; end: string; }; }; }) => Promise<{ content: Omit<", "ApmTimeseries", ", \"data\">[]; data: ", "ApmTimeseries", @@ -3193,28 +3343,6 @@ "APMRouteHandlerResources", ") => Promise; } & ", "APMRouteCreateOptions", - "; \"POST /api/apm/androidmaps 2023-10-31\": { endpoint: \"POST /api/apm/androidmaps 2023-10-31\"; params?: ", - "TypeC", - "<{ body: ", - "TypeC", - "<{ service_name: ", - "StringC", - "; service_version: ", - "StringC", - "; map_file: ", - "Type", - "; }>; }> | undefined; handler: ({}: ", - "APMRouteHandlerResources", - " & { params: { body: { service_name: string; service_version: string; map_file: string; }; }; }) => Promise<", - { - "pluginId": "fleet", - "scope": "server", - "docId": "kibFleetPluginApi", - "section": "def-server.Artifact", - "text": "Artifact" - }, - " | undefined>; } & ", - "APMRouteCreateOptions", "; \"DELETE /api/apm/sourcemaps/{id} 2023-10-31\": { endpoint: \"DELETE /api/apm/sourcemaps/{id} 2023-10-31\"; params?: ", "TypeC", "<{ path: ", @@ -4883,9 +5011,13 @@ "TypeC", "<{ entryTransactionId: ", "StringC", - "; }>]>; }> | undefined; handler: ({}: ", + "; }>, ", + "PartialC", + "<{ maxTraceItems: ", + "Type", + "; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", - " & { params: { path: { traceId: string; }; query: { start: number; end: number; } & { entryTransactionId: string; }; }; }) => Promise<{ traceItems: ", + " & { params: { path: { traceId: string; }; query: { start: number; end: number; } & { entryTransactionId: string; } & { maxTraceItems?: number | undefined; }; }; }) => Promise<{ traceItems: ", "TraceItems", "; entryTransaction?: ", "Transaction", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index d8e50b0782113..ce75d2091dff4 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 4b3bcd2d2ae4e..28d9e9cc6b2bd 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/asset_manager.devdocs.json b/api_docs/asset_manager.devdocs.json index fcc5f81df2922..7f2baa50411c4 100644 --- a/api_docs/asset_manager.devdocs.json +++ b/api_docs/asset_manager.devdocs.json @@ -22,7 +22,7 @@ "label": "AssetManagerConfig", "description": [], "signature": [ - "{ readonly alphaEnabled?: boolean | undefined; readonly sourceIndices: Readonly<{} & { metrics: string; traces: string; logs: string; serviceMetrics: string; serviceLogs: string; }>; readonly lockedSource: \"assets\" | \"signals\"; }" + "{ readonly alphaEnabled?: boolean | undefined; readonly sourceIndices: Readonly<{} & { metrics: string; logs: string; }>; readonly lockedSource: \"assets\" | \"signals\"; }" ], "path": "x-pack/plugins/asset_manager/server/types.ts", "deprecated": false, diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index 1c921b0135d29..10d7d21c36031 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 2d20ae08d90ab..82d51bbf0ad43 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index 23886a991e581..a9ea5c5a4e228 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 2f3b23becc49d..a8ab44ac80268 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index 15f422cdf22ba..5d00886d09ee8 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -1091,7 +1091,14 @@ "\nReturn the UI capabilities for each type of operation. These strings must match the values defined in the UI\nhere: x-pack/plugins/cases/public/client/helpers/capabilities.ts" ], "signature": [ - "() => { all: readonly [\"create_cases\", \"read_cases\", \"update_cases\", \"push_cases\", \"cases_connectors\"]; read: readonly [\"read_cases\", \"cases_connectors\"]; delete: readonly [\"delete_cases\"]; }" + "() => ", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.CasesUiCapabilities", + "text": "CasesUiCapabilities" + } ], "path": "x-pack/plugins/cases/common/utils/capabilities.ts", "deprecated": false, @@ -1108,7 +1115,14 @@ "label": "getApiTags", "description": [], "signature": [ - "(owner: \"cases\" | \"observability\" | \"securitySolution\") => { all: readonly [\"casesSuggestUserProfiles\", \"bulkGetUserProfiles\", \"casesGetConnectorsConfigure\", string, string]; read: readonly [\"casesSuggestUserProfiles\", \"bulkGetUserProfiles\", \"casesGetConnectorsConfigure\", string]; delete: readonly [string]; }" + "(owner: \"cases\" | \"observability\" | \"securitySolution\") => ", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.CasesApiTags", + "text": "CasesApiTags" + } ], "path": "x-pack/plugins/cases/common/utils/api_tags.ts", "deprecated": false, @@ -1236,6 +1250,62 @@ } ], "interfaces": [ + { + "parentPluginId": "cases", + "id": "def-common.CasesApiTags", + "type": "Interface", + "tags": [], + "label": "CasesApiTags", + "description": [], + "path": "x-pack/plugins/cases/common/utils/api_tags.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cases", + "id": "def-common.CasesApiTags.all", + "type": "Object", + "tags": [], + "label": "all", + "description": [], + "signature": [ + "readonly string[]" + ], + "path": "x-pack/plugins/cases/common/utils/api_tags.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cases", + "id": "def-common.CasesApiTags.read", + "type": "Object", + "tags": [], + "label": "read", + "description": [], + "signature": [ + "readonly string[]" + ], + "path": "x-pack/plugins/cases/common/utils/api_tags.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cases", + "id": "def-common.CasesApiTags.delete", + "type": "Object", + "tags": [], + "label": "delete", + "description": [], + "signature": [ + "readonly string[]" + ], + "path": "x-pack/plugins/cases/common/utils/api_tags.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "cases", "id": "def-common.CasesPermissions", @@ -1327,6 +1397,62 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "cases", + "id": "def-common.CasesUiCapabilities", + "type": "Interface", + "tags": [], + "label": "CasesUiCapabilities", + "description": [], + "path": "x-pack/plugins/cases/common/utils/capabilities.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cases", + "id": "def-common.CasesUiCapabilities.all", + "type": "Object", + "tags": [], + "label": "all", + "description": [], + "signature": [ + "readonly string[]" + ], + "path": "x-pack/plugins/cases/common/utils/capabilities.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cases", + "id": "def-common.CasesUiCapabilities.read", + "type": "Object", + "tags": [], + "label": "read", + "description": [], + "signature": [ + "readonly string[]" + ], + "path": "x-pack/plugins/cases/common/utils/capabilities.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cases", + "id": "def-common.CasesUiCapabilities.delete", + "type": "Object", + "tags": [], + "label": "delete", + "description": [], + "signature": [ + "readonly string[]" + ], + "path": "x-pack/plugins/cases/common/utils/capabilities.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "cases", "id": "def-common.Ecs", @@ -2031,6 +2157,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "cases", + "id": "def-common.CASES_CONNECTORS_CAPABILITY", + "type": "string", + "tags": [], + "label": "CASES_CONNECTORS_CAPABILITY", + "description": [], + "signature": [ + "\"cases_connectors\"" + ], + "path": "x-pack/plugins/cases/common/constants/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "cases", "id": "def-common.CASES_URL", @@ -2651,6 +2792,23 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "cases", + "id": "def-common.GET_CONNECTORS_CONFIGURE_API_TAG", + "type": "string", + "tags": [], + "label": "GET_CONNECTORS_CONFIGURE_API_TAG", + "description": [ + "\nThis tag is registered for the connectors (configure) get API" + ], + "signature": [ + "\"casesGetConnectorsConfigure\"" + ], + "path": "x-pack/plugins/cases/common/constants/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "cases", "id": "def-common.GetRelatedCasesByAlertResponse", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index a913aa0945465..4b24bf6e480e2 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 94 | 0 | 75 | 27 | +| 104 | 0 | 84 | 27 | ## Client diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index d5a1edb002467..dd65a4f0b3499 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.devdocs.json b/api_docs/cloud.devdocs.json index ea1d5a9f36d07..c9f43718fec8d 100644 --- a/api_docs/cloud.devdocs.json +++ b/api_docs/cloud.devdocs.json @@ -191,7 +191,7 @@ "label": "serverless", "description": [], "signature": [ - "{ project_id: string; } | undefined" + "{ project_id: string; project_name?: string | undefined; } | undefined" ], "path": "x-pack/plugins/cloud/public/plugin.tsx", "deprecated": false, @@ -454,7 +454,7 @@ "\nServerless configuration" ], "signature": [ - "{ projectId?: string | undefined; }" + "{ projectId?: string | undefined; projectName?: string | undefined; }" ], "path": "x-pack/plugins/cloud/public/types.ts", "deprecated": false, @@ -790,7 +790,7 @@ "\nServerless configuration" ], "signature": [ - "{ projectId?: string | undefined; }" + "{ projectId?: string | undefined; projectName?: string | undefined; }" ], "path": "x-pack/plugins/cloud/public/types.ts", "deprecated": false, @@ -1053,7 +1053,7 @@ "\nServerless configuration.\n" ], "signature": [ - "{ projectId?: string | undefined; }" + "{ projectId?: string | undefined; projectName?: string | undefined; }" ], "path": "x-pack/plugins/cloud/server/plugin.ts", "deprecated": false, diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 7aae586da2bf1..4b2589f34500d 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index c6ef27a4028fe..ee5825e0c7f5f 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_chat_provider.mdx b/api_docs/cloud_chat_provider.mdx index 0b0dbf26b397e..1677327833a80 100644 --- a/api_docs/cloud_chat_provider.mdx +++ b/api_docs/cloud_chat_provider.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChatProvider title: "cloudChatProvider" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChatProvider plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChatProvider'] --- import cloudChatProviderObj from './cloud_chat_provider.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 02ad60cf1a945..09d27e90753ac 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 00833fb50673a..1311eff4de07f 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.devdocs.json b/api_docs/cloud_experiments.devdocs.json index d08957de16642..da90785517ae6 100644 --- a/api_docs/cloud_experiments.devdocs.json +++ b/api_docs/cloud_experiments.devdocs.json @@ -117,7 +117,7 @@ "\nFetch the configuration assigned to variation `configKey`. If nothing is found, fallback to `defaultValue`." ], "signature": [ - "(featureFlagName: \"security-solutions.add-integrations-url\", defaultValue: Data) => Promise" + "(featureFlagName: \"security-solutions.add-integrations-url\" | \"security-solutions.guided-onboarding-content\", defaultValue: Data) => Promise" ], "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", "deprecated": false, @@ -126,14 +126,14 @@ { "parentPluginId": "cloudExperiments", "id": "def-common.CloudExperimentsPluginStart.getVariation.$1", - "type": "string", + "type": "CompoundType", "tags": [], "label": "featureFlagName", "description": [ "The name of the key to find the config variation. {@link CloudExperimentsFeatureFlagNames }." ], "signature": [ - "\"security-solutions.add-integrations-url\"" + "\"security-solutions.add-integrations-url\" | \"security-solutions.guided-onboarding-content\"" ], "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", "deprecated": false, @@ -227,7 +227,7 @@ "\nThe names of the feature flags declared in Kibana.\nValid keys are defined in {@link FEATURE_FLAG_NAMES}. When using a new feature flag, add the name to the list.\n" ], "signature": [ - "\"security-solutions.add-integrations-url\"" + "\"security-solutions.add-integrations-url\" | \"security-solutions.guided-onboarding-content\"" ], "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", "deprecated": false, diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 79a3fadafe3b1..e4168c0ce5928 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index b2729c117f55e..820aa2cfb41fd 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index cf0bc5870fc3f..d083f5d8d02c0 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 6b2585a637149..c271b141ab8e0 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 52e16d4727595..7b07f1ec9f15c 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index d0dd1b4d42fe6..f8512ea315c69 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index eef050e768f79..e573ebfd7f812 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 5b5b9823e115b..ba465451ac4c3 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index a637553e5a090..fa360c4f06091 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -5147,36 +5147,31 @@ }, { "parentPluginId": "data", - "id": "def-public.parseSearchSourceJSON", + "id": "def-public.OpenIncompleteResultsModalButton", "type": "Function", "tags": [], - "label": "parseSearchSourceJSON", + "label": "OpenIncompleteResultsModalButton", "description": [], "signature": [ - "(searchSourceJSON: string) => ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SerializedSearchSourceFields", - "text": "SerializedSearchSourceFields" - } + "(props: ", + "OpenIncompleteResultsModalButtonProps", + ") => JSX.Element" ], - "path": "src/plugins/data/common/search/search_source/parse_json.ts", + "path": "src/plugins/data/public/incomplete_results_modal/index.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-public.parseSearchSourceJSON.$1", - "type": "string", + "id": "def-public.OpenIncompleteResultsModalButton.$1", + "type": "Object", "tags": [], - "label": "searchSourceJSON", + "label": "props", "description": [], "signature": [ - "string" + "OpenIncompleteResultsModalButtonProps" ], - "path": "src/plugins/data/common/search/search_source/parse_json.ts", + "path": "src/plugins/data/public/incomplete_results_modal/index.tsx", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -5187,31 +5182,36 @@ }, { "parentPluginId": "data", - "id": "def-public.ShardFailureOpenModalButton", + "id": "def-public.parseSearchSourceJSON", "type": "Function", "tags": [], - "label": "ShardFailureOpenModalButton", + "label": "parseSearchSourceJSON", "description": [], "signature": [ - "(props: ", - "ShardFailureOpenModalButtonProps", - ") => JSX.Element" + "(searchSourceJSON: string) => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SerializedSearchSourceFields", + "text": "SerializedSearchSourceFields" + } ], - "path": "src/plugins/data/public/shard_failure_modal/index.tsx", + "path": "src/plugins/data/common/search/search_source/parse_json.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "data", - "id": "def-public.ShardFailureOpenModalButton.$1", - "type": "Object", + "id": "def-public.parseSearchSourceJSON.$1", + "type": "string", "tags": [], - "label": "props", + "label": "searchSourceJSON", "description": [], "signature": [ - "ShardFailureOpenModalButtonProps" + "string" ], - "path": "src/plugins/data/public/shard_failure_modal/index.tsx", + "path": "src/plugins/data/common/search/search_source/parse_json.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -9595,104 +9595,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "data", - "id": "def-public.ShardFailureRequest", - "type": "Interface", - "tags": [], - "label": "ShardFailureRequest", - "description": [], - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "data", - "id": "def-public.ShardFailureRequest.docvalue_fields", - "type": "Array", - "tags": [], - "label": "docvalue_fields", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-public.ShardFailureRequest._source", - "type": "Unknown", - "tags": [], - "label": "_source", - "description": [], - "signature": [ - "unknown" - ], - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-public.ShardFailureRequest.query", - "type": "Unknown", - "tags": [], - "label": "query", - "description": [], - "signature": [ - "unknown" - ], - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-public.ShardFailureRequest.script_fields", - "type": "Unknown", - "tags": [], - "label": "script_fields", - "description": [], - "signature": [ - "unknown" - ], - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-public.ShardFailureRequest.sort", - "type": "Unknown", - "tags": [], - "label": "sort", - "description": [], - "signature": [ - "unknown" - ], - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-public.ShardFailureRequest.stored_fields", - "type": "Array", - "tags": [], - "label": "stored_fields", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [ @@ -10152,7 +10054,7 @@ "section": "def-common.TimeRange", "text": "TimeRange" }, - " | undefined; disableShardWarnings?: boolean | undefined; }" + " | undefined; disableWarningToasts?: boolean | undefined; }" ], "path": "src/plugins/data/common/search/expressions/kibana_context_type.ts", "deprecated": false, @@ -12801,23 +12703,23 @@ "references": [ { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/data_view_select.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx" }, { "plugin": "infra", @@ -13385,27 +13287,27 @@ }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx" }, { "plugin": "securitySolution", @@ -13415,6 +13317,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx" @@ -13489,7 +13395,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts" + "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_context.ts" }, { "plugin": "threatIntelligence", @@ -13680,21 +13586,13 @@ "path": "packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts" }, { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx" - }, - { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/table_list.tsx" + "plugin": "@kbn/unified-field-list", + "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" }, - { - "plugin": "@kbn/unified-field-list", - "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" - }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" @@ -13803,18 +13701,6 @@ "plugin": "timelines", "path": "x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts" }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, { "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" @@ -13841,35 +13727,11 @@ }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx" }, { "plugin": "stackAlerts", @@ -13947,6 +13809,18 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/persistence.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/sourcerer/create_sourcerer_data_view.ts" @@ -13969,15 +13843,15 @@ }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" + "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" + "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/services/es_index_service.ts" + "path": "x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts" }, { "plugin": "transform", @@ -14087,6 +13961,14 @@ "plugin": "dataViewManagement", "path": "src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx" }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx" + }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/table_list.tsx" + }, { "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/control/list_control_factory.ts" @@ -14836,14 +14718,14 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" - }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/services/persistence/deserialize.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" + }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/datasource.test.ts" @@ -21150,27 +21032,27 @@ }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx" }, { "plugin": "securitySolution", @@ -21180,6 +21062,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx" @@ -21254,7 +21140,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts" + "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_context.ts" }, { "plugin": "threatIntelligence", @@ -21445,21 +21331,13 @@ "path": "packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts" }, { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx" - }, - { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/table_list.tsx" + "plugin": "@kbn/unified-field-list", + "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" }, - { - "plugin": "@kbn/unified-field-list", - "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" - }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" @@ -21568,18 +21446,6 @@ "plugin": "timelines", "path": "x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts" }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, { "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" @@ -21606,35 +21472,11 @@ }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx" }, { "plugin": "stackAlerts", @@ -21712,6 +21554,18 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/persistence.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/sourcerer/create_sourcerer_data_view.ts" @@ -21734,15 +21588,15 @@ }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" + "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" + "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/services/es_index_service.ts" + "path": "x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts" }, { "plugin": "transform", @@ -21852,6 +21706,14 @@ "plugin": "dataViewManagement", "path": "src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx" }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx" + }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/table_list.tsx" + }, { "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/control/list_control_factory.ts" @@ -22601,14 +22463,14 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" - }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/services/persistence/deserialize.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" + }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/datasource.test.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 2911aebd8a829..837057cfc86dc 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3310 | 33 | 2583 | 26 | +| 3308 | 33 | 2577 | 24 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index a24f30fead3d0..ebb986dc9cebc 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3310 | 33 | 2583 | 26 | +| 3308 | 33 | 2577 | 24 | ## Client diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index 3b5cd7151721e..20e66366a56cb 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -915,6 +915,75 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-public.SearchResponseIncompleteWarning", + "type": "Interface", + "tags": [], + "label": "SearchResponseIncompleteWarning", + "description": [ + "\nA warning object for a search response with incomplete ES results\nES returns incomplete results when:\n1) Set timeout flag on search and the timeout expires on cluster\n2) Some shard failures on a cluster\n3) skipped remote(s) (skip_unavailable=true)\n a. all shards failed\n b. disconnected/not-connected" + ], + "path": "src/plugins/data/public/search/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-public.SearchResponseIncompleteWarning.type", + "type": "string", + "tags": [], + "label": "type", + "description": [ + "\ntype: for sorting out incomplete warnings" + ], + "signature": [ + "\"incomplete\"" + ], + "path": "src/plugins/data/public/search/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-public.SearchResponseIncompleteWarning.message", + "type": "string", + "tags": [], + "label": "message", + "description": [ + "\nmessage: human-friendly message" + ], + "path": "src/plugins/data/public/search/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-public.SearchResponseIncompleteWarning.clusters", + "type": "Object", + "tags": [], + "label": "clusters", + "description": [ + "\nclusters: cluster details." + ], + "signature": [ + "{ [x: string]: ", + { + "pluginId": "@kbn/es-types", + "scope": "common", + "docId": "kibKbnEsTypesPluginApi", + "section": "def-common.ClusterDetails", + "text": "ClusterDetails" + }, + "; }" + ], + "path": "src/plugins/data/public/search/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-public.SearchSessionInfoProvider", @@ -1448,9 +1517,13 @@ "\nA warning object for a search response with warnings" ], "signature": [ - "SearchResponseTimeoutWarning", - " | ", - "SearchResponseShardFailureWarning" + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataSearchPluginApi", + "section": "def-public.SearchResponseIncompleteWarning", + "text": "SearchResponseIncompleteWarning" + } ], "path": "src/plugins/data/public/search/types.ts", "deprecated": false, @@ -3110,19 +3183,15 @@ "signature": [ "{ search?: string | undefined; page?: number | undefined; filter?: any; aggs?: Record | undefined; namespaces?: string[] | undefined; perPage?: number | undefined; fields?: string[] | undefined; sortField?: string | undefined; preference?: string | undefined; pit?: ", + "> | undefined; namespaces?: string[] | undefined; perPage?: number | undefined; fields?: string[] | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; searchFields?: string[] | undefined; hasReference?: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-common.SavedObjectsPitParams", - "text": "SavedObjectsPitParams" + "section": "def-common.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" }, - " | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; searchFields?: string[] | undefined; sortOrder?: ", - "SortOrder", - " | undefined; searchAfter?: ", - "SortResults", - " | undefined; rootSearchFields?: string[] | undefined; hasReference?: ", + " | ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", @@ -3130,15 +3199,19 @@ "section": "def-common.SavedObjectsFindOptionsReference", "text": "SavedObjectsFindOptionsReference" }, - " | ", + "[] | undefined; sortField?: string | undefined; preference?: string | undefined; pit?: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-common.SavedObjectsFindOptionsReference", - "text": "SavedObjectsFindOptionsReference" + "section": "def-common.SavedObjectsPitParams", + "text": "SavedObjectsPitParams" }, - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + " | undefined; sortOrder?: ", + "SortOrder", + " | undefined; searchAfter?: ", + "SortResults", + " | undefined; rootSearchFields?: string[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", @@ -7144,7 +7217,7 @@ "section": "def-common.RequestAdapter", "text": "RequestAdapter" }, - " | undefined, abortSignal?: AbortSignal | undefined, searchSessionId?: string | undefined, disableShardFailureWarning?: boolean | undefined) => Promise<", + " | undefined, abortSignal?: AbortSignal | undefined, searchSessionId?: string | undefined, disableWarningToasts?: boolean | undefined) => Promise<", "SearchResponse", " boolean" + ], + "path": "src/plugins/data_views/public/data_views_service_public.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "initialIsOpen": false @@ -6550,6 +6546,22 @@ ], "returnComment": [] }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewsServicePublicDeps.getRollupsEnabled", + "type": "Function", + "tags": [], + "label": "getRollupsEnabled", + "description": [], + "signature": [ + "() => boolean" + ], + "path": "src/plugins/data_views/public/data_views_service_public.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "dataViews", "id": "def-public.DataViewsServicePublicDeps.scriptedFieldsEnabled", @@ -7937,7 +7949,24 @@ "path": "src/plugins/data_views/public/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewsPublicPluginSetup.enableRollups", + "type": "Function", + "tags": [], + "label": "enableRollups", + "description": [], + "signature": [ + "() => void" + ], + "path": "src/plugins/data_views/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], "lifecycle": "setup", "initialIsOpen": true }, @@ -8041,27 +8070,27 @@ }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx" }, { "plugin": "securitySolution", @@ -8071,6 +8100,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx" @@ -8145,7 +8178,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts" + "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_context.ts" }, { "plugin": "threatIntelligence", @@ -8332,21 +8365,13 @@ "path": "packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts" }, { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx" - }, - { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/table_list.tsx" + "plugin": "@kbn/unified-field-list", + "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" }, - { - "plugin": "@kbn/unified-field-list", - "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" - }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" @@ -8455,18 +8480,6 @@ "plugin": "timelines", "path": "x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts" }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, { "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" @@ -8493,35 +8506,11 @@ }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx" }, { "plugin": "stackAlerts", @@ -8599,6 +8588,18 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/persistence.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/sourcerer/create_sourcerer_data_view.ts" @@ -8621,15 +8622,15 @@ }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" + "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" + "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/services/es_index_service.ts" + "path": "x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts" }, { "plugin": "transform", @@ -8739,6 +8740,14 @@ "plugin": "dataViewManagement", "path": "src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx" }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx" + }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/table_list.tsx" + }, { "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/control/list_control_factory.ts" @@ -9508,14 +9517,14 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" - }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/services/persistence/deserialize.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" + }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/datasource.test.ts" @@ -15115,27 +15124,27 @@ }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts" + "path": "x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx" }, { "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx" + "path": "x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx" }, { "plugin": "securitySolution", @@ -15145,6 +15154,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx" @@ -15219,7 +15232,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts" + "path": "x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_context.ts" }, { "plugin": "threatIntelligence", @@ -15406,21 +15419,13 @@ "path": "packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts" }, { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx" - }, - { - "plugin": "@kbn/event-annotation-components", - "path": "packages/kbn-event-annotation-components/components/table_list.tsx" + "plugin": "@kbn/unified-field-list", + "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" }, - { - "plugin": "@kbn/unified-field-list", - "path": "packages/kbn-unified-field-list/src/services/field_stats/load_field_stats.ts" - }, { "plugin": "lens", "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" @@ -15529,18 +15534,6 @@ "plugin": "timelines", "path": "x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts" }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, { "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" @@ -15567,35 +15560,11 @@ }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" - }, - { - "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx" + "path": "x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx" }, { "plugin": "stackAlerts", @@ -15673,6 +15642,18 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/persistence.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/sourcerer/create_sourcerer_data_view.ts" @@ -15695,15 +15676,15 @@ }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" + "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts" + "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" }, { "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/services/es_index_service.ts" + "path": "x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts" }, { "plugin": "transform", @@ -15813,6 +15794,14 @@ "plugin": "dataViewManagement", "path": "src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx" }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx" + }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/components/table_list.tsx" + }, { "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/control/list_control_factory.ts" @@ -16582,14 +16571,14 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" - }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/services/persistence/deserialize.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts" + }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/state_management/datasource.test.ts" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 894fa34f5c587..4a5945bee01ce 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1034 | 0 | 254 | 2 | +| 1037 | 0 | 257 | 2 | ## Client diff --git a/api_docs/data_visualizer.devdocs.json b/api_docs/data_visualizer.devdocs.json index 9f100882c91c9..d583f8f3d85b7 100644 --- a/api_docs/data_visualizer.devdocs.json +++ b/api_docs/data_visualizer.devdocs.json @@ -433,15 +433,7 @@ "label": "IndexDataVisualizerSpec", "description": [], "signature": [ - "React.FunctionComponent<{ getAdditionalLinks?: ", - { - "pluginId": "dataVisualizer", - "scope": "public", - "docId": "kibDataVisualizerPluginApi", - "section": "def-public.GetAdditionalLinks", - "text": "GetAdditionalLinks" - }, - " | undefined; }>" + "React.FunctionComponent" ], "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx", "deprecated": false, @@ -489,15 +481,7 @@ "label": "DataVisualizerPluginStart", "description": [], "signature": [ - "{ getFileDataVisualizerComponent: () => Promise<() => React.FC>; getIndexDataVisualizerComponent: () => Promise<() => React.FC<{ getAdditionalLinks?: ", - { - "pluginId": "dataVisualizer", - "scope": "public", - "docId": "kibDataVisualizerPluginApi", - "section": "def-public.GetAdditionalLinks", - "text": "GetAdditionalLinks" - }, - " | undefined; }>>; getDataComparisonComponent: () => Promise<() => React.FC<", + "{ getFileDataVisualizerComponent: () => Promise<() => React.FC>; getIndexDataVisualizerComponent: () => Promise<() => React.FC>; getDataComparisonComponent: () => Promise<() => React.FC<", "DataComparisonDetectionAppStateProps", ">>; getMaxBytesFormatted: () => string; }" ], diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 60b95dfb340ea..88cac35c7d98e 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index b96f1f5177af6..a9ef857b7bf51 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -18,11 +18,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | | ml, stackAlerts | - | | | ruleRegistry, ml, securitySolution, observability, infra, monitoring, stackAlerts, synthetics, transform, uptime | - | -| | @kbn/es-query, @kbn/visualization-ui-components, securitySolution, observability, timelines, lists, threatIntelligence, savedSearch, dataViews, logsShared, savedObjectsManagement, unifiedSearch, controls, @kbn/unified-field-list, @kbn/event-annotation-components, lens, triggersActionsUi, dataVisualizer, ml, visTypeTimeseries, apm, exploratoryView, fleet, stackAlerts, infra, canvas, enterpriseSearch, graph, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, data | - | -| | @kbn/es-query, @kbn/visualization-ui-components, securitySolution, observability, timelines, lists, threatIntelligence, savedSearch, dataViews, logsShared, savedObjectsManagement, unifiedSearch, controls, @kbn/unified-field-list, @kbn/event-annotation-components, lens, triggersActionsUi, dataVisualizer, ml, visTypeTimeseries, apm, exploratoryView, fleet, stackAlerts, infra, canvas, enterpriseSearch, graph, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, data | - | -| | @kbn/es-query, @kbn/visualization-ui-components, securitySolution, observability, timelines, lists, threatIntelligence, savedSearch, data, logsShared, savedObjectsManagement, unifiedSearch, controls, @kbn/unified-field-list, @kbn/event-annotation-components, lens, triggersActionsUi, dataVisualizer, ml, visTypeTimeseries, apm, exploratoryView, fleet, stackAlerts, infra, canvas, enterpriseSearch, graph, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega | - | +| | @kbn/es-query, @kbn/visualization-ui-components, securitySolution, observability, timelines, lists, threatIntelligence, savedSearch, dataViews, logsShared, savedObjectsManagement, unifiedSearch, controls, @kbn/unified-field-list, lens, triggersActionsUi, dataVisualizer, ml, apm, exploratoryView, fleet, stackAlerts, infra, canvas, enterpriseSearch, graph, visTypeTimeseries, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, data | - | +| | @kbn/es-query, @kbn/visualization-ui-components, securitySolution, observability, timelines, lists, threatIntelligence, savedSearch, dataViews, logsShared, savedObjectsManagement, unifiedSearch, controls, @kbn/unified-field-list, lens, triggersActionsUi, dataVisualizer, ml, apm, exploratoryView, fleet, stackAlerts, infra, canvas, enterpriseSearch, graph, visTypeTimeseries, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, data | - | +| | @kbn/es-query, @kbn/visualization-ui-components, securitySolution, observability, timelines, lists, threatIntelligence, savedSearch, data, logsShared, savedObjectsManagement, unifiedSearch, controls, @kbn/unified-field-list, lens, triggersActionsUi, dataVisualizer, ml, apm, exploratoryView, fleet, stackAlerts, infra, canvas, enterpriseSearch, graph, visTypeTimeseries, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega | - | | | home, data, esUiShared, savedObjectsManagement, ml, exploratoryView, fleet, observability, apm, indexLifecycleManagement, observabilityOnboarding, synthetics, upgradeAssistant, uptime, ux, kibanaOverview | - | -| | share, uiActions, guidedOnboarding, home, management, spaces, security, savedObjects, serverless, visualizations, controls, dashboard, savedObjectsTagging, expressionXY, lens, expressionMetricVis, expressionGauge, alerting, triggersActionsUi, cases, licenseManagement, advancedSettings, maps, dataVisualizer, aiops, ml, exploratoryView, fleet, observability, infra, profiling, apm, expressionImage, expressionMetric, expressionError, expressionRevealImage, expressionRepeatImage, expressionShape, indexManagement, crossClusterReplication, enterpriseSearch, globalSearchBar, graph, grokdebugger, indexLifecycleManagement, ingestPipelines, logstash, monitoring, observabilityOnboarding, osquery, devTools, painlessLab, remoteClusters, rollup, searchprofiler, newsfeed, securitySolution, snapshotRestore, synthetics, transform, upgradeAssistant, uptime, ux, watcher, cloudDataMigration, console, filesManagement, kibanaOverview, visDefaultEditor, expressionHeatmap, expressionLegacyMetricVis, expressionPartitionVis, expressionTagcloud, visTypeTable, visTypeTimelion, visTypeTimeseries, visTypeVega, visTypeVislib | - | +| | share, uiActions, guidedOnboarding, home, serverless, management, spaces, security, savedObjects, indexManagement, visualizations, controls, dashboard, savedObjectsTagging, expressionXY, lens, expressionMetricVis, expressionGauge, alerting, triggersActionsUi, cases, licenseManagement, advancedSettings, maps, dataVisualizer, aiops, ml, exploratoryView, fleet, observability, infra, profiling, apm, expressionImage, expressionMetric, expressionError, expressionRevealImage, expressionRepeatImage, expressionShape, crossClusterReplication, enterpriseSearch, globalSearchBar, graph, grokdebugger, indexLifecycleManagement, ingestPipelines, logstash, monitoring, observabilityOnboarding, osquery, devTools, painlessLab, remoteClusters, rollup, searchprofiler, newsfeed, securitySolution, snapshotRestore, synthetics, transform, upgradeAssistant, uptime, ux, watcher, cloudDataMigration, console, filesManagement, kibanaOverview, visDefaultEditor, expressionHeatmap, expressionLegacyMetricVis, expressionPartitionVis, expressionTagcloud, visTypeTable, visTypeTimelion, visTypeTimeseries, visTypeVega, visTypeVislib | - | | | encryptedSavedObjects, actions, data, ml, securitySolution, logstash, cloudChat | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core, savedObjects, presentationUtil, visualizations, dataVisualizer, ml, aiops, dashboardEnhanced, graph, lens, securitySolution, eventAnnotation, @kbn/core-saved-objects-browser-mocks | - | @@ -36,7 +36,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-saved-objects-api-server-internal, @kbn/core-saved-objects-import-export-server-internal, @kbn/core-saved-objects-server-internal, @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, home, fleet, osquery, securitySolution, @kbn/core-saved-objects-browser-mocks, graph, lists, alerting | - | | | alerting, discover, securitySolution | - | | | securitySolution | - | -| | inspector, data, licensing, security, savedObjects, dataViewEditor, unifiedSearch, embeddable, visualizations, controls, dashboard, savedObjectsTagging, eventAnnotation, dataViewFieldEditor, lens, triggersActionsUi, cases, observabilityShared, telemetry, advancedSettings, maps, exploratoryView, fleet, observability, banners, reporting, timelines, cloudSecurityPosture, runtimeFields, indexManagement, dashboardEnhanced, imageEmbeddable, graph, monitoring, securitySolution, synthetics, transform, uptime, cloudLinks, console, dataViewManagement, filesManagement, uiActions, visTypeVislib | - | +| | inspector, data, licensing, security, savedObjects, runtimeFields, indexManagement, dataViewEditor, unifiedSearch, embeddable, visualizations, controls, dashboard, savedObjectsTagging, dataViewFieldEditor, lens, triggersActionsUi, cases, observabilityShared, telemetry, advancedSettings, maps, exploratoryView, fleet, observability, banners, reporting, timelines, cloudSecurityPosture, dashboardEnhanced, imageEmbeddable, graph, monitoring, securitySolution, synthetics, uptime, cloudLinks, console, dataViewManagement, eventAnnotationListing, filesManagement, uiActions, visTypeVislib | - | | | observability, @kbn/securitysolution-data-table, securitySolution | - | | | @kbn/securitysolution-data-table, securitySolution | - | | | securitySolution | - | @@ -45,7 +45,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | securitySolution | - | | | securitySolution | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, savedObjectsTaggingOss, savedObjectsTagging, securitySolution, lists, upgradeAssistant, savedObjectsManagement, @kbn/core-saved-objects-api-server, @kbn/core-saved-objects-import-export-server-internal, home, canvas, savedObjects, @kbn/core-saved-objects-browser-mocks, @kbn/core-ui-settings-server-internal | - | -| | @kbn/core-saved-objects-migration-server-internal, actions, dataViews, data, alerting, lens, lists, savedObjectsTagging, securitySolution, cases, visualizations, savedSearch, canvas, graph, maps, dashboard, @kbn/core-test-helpers-so-type-serializer | - | +| | @kbn/core-saved-objects-migration-server-internal, actions, dataViews, data, alerting, lens, lists, cases, savedObjectsTagging, securitySolution, savedSearch, canvas, graph, visualizations, maps, dashboard, @kbn/core-test-helpers-so-type-serializer | - | | | lists, securitySolution, @kbn/securitysolution-io-ts-list-types | - | | | lists, securitySolution, @kbn/securitysolution-io-ts-list-types | - | | | lists, securitySolution, @kbn/securitysolution-io-ts-list-types | - | @@ -66,11 +66,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | discover | - | | | data, discover, imageEmbeddable, embeddable | - | | | @kbn/core-saved-objects-browser-mocks, discover, @kbn/core-saved-objects-browser-internal | - | -| | advancedSettings, discover | - | +| | advancedSettings, discover, @kbn/management-settings-field-definition | - | | | @kbn/core-saved-objects-api-server-internal | - | | | @kbn/core-saved-objects-api-server-internal | - | | | @kbn/core-saved-objects-api-server-internal, canvas, @kbn/core-saved-objects-browser-internal | - | -| | @kbn/core-saved-objects-server-internal, @kbn/core-plugins-server-internal, savedObjectsTagging, @kbn/core-saved-objects-server-mocks | - | | | @kbn/core, kibanaUtils, expressions, data, savedObjectsTaggingOss, embeddable, visualizations, controls, savedObjectsTagging, uiActionsEnhanced, lens, maps, canvas, dashboardEnhanced, globalSearchProviders, @kbn/core-saved-objects-api-browser, savedObjects, savedObjectsManagement, eventAnnotation, graph, dashboard | - | | | @kbn/core-saved-objects-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, home, savedObjects, visualizations, lens, visTypeTimeseries, @kbn/core-saved-objects-browser-mocks | - | | | @kbn/core-saved-objects-browser-internal, savedObjects, @kbn/core-saved-objects-browser-mocks | - | @@ -99,9 +98,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-saved-objects-browser-internal | - | | | home, osquery | - | | | @kbn/core-root-browser-internal, @kbn/core-saved-objects-browser-mocks | - | -| | visTypeTimeseries, graph, dataViewManagement, dataViews | - | -| | visTypeTimeseries, graph, dataViewManagement, dataViews | - | -| | visTypeTimeseries, graph, dataViewManagement | - | +| | graph, visTypeTimeseries, dataViewManagement, dataViews | - | +| | graph, visTypeTimeseries, dataViewManagement, dataViews | - | +| | graph, visTypeTimeseries, dataViewManagement | - | | | visualizations, graph | - | | | @kbn/core, lens, savedObjects | - | | | dataViews, maps | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 5864b2813f5eb..ed8aea16c2757 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -107,7 +107,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [plugin_context.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts#:~:text=legacy), [plugin_context.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts#:~:text=legacy) | - | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin.ts#:~:text=AsyncPlugin), [plugin.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin.ts#:~:text=AsyncPlugin) | 8.8.0 | | | [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs) | - | -| | [plugin_context.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts#:~:text=getAllIndices), [plugin_context.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts#:~:text=getAllIndices) | - | @@ -247,7 +246,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [utils.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/utils.ts#:~:text=convertToMultiNamespaceTypeVersion), [internal_transforms.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts#:~:text=convertToMultiNamespaceTypeVersion), [internal_transforms.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts#:~:text=convertToMultiNamespaceTypeVersion), [internal_transforms.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts#:~:text=convertToMultiNamespaceTypeVersion), [validate_migrations.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/validate_migrations.ts#:~:text=convertToMultiNamespaceTypeVersion), [validate_migrations.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/validate_migrations.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion)+ 17 more | - | +| | [utils.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/utils.ts#:~:text=convertToMultiNamespaceTypeVersion), [internal_transforms.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts#:~:text=convertToMultiNamespaceTypeVersion), [internal_transforms.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts#:~:text=convertToMultiNamespaceTypeVersion), [internal_transforms.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts#:~:text=convertToMultiNamespaceTypeVersion), [validate_migrations.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/validate_migrations.ts#:~:text=convertToMultiNamespaceTypeVersion), [validate_migrations.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/validate_migrations.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion), [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts#:~:text=convertToMultiNamespaceTypeVersion)+ 18 more | - | @@ -266,15 +265,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [import_dashboards.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts#:~:text=migrationVersion) | - | | | [import_dashboards.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts#:~:text=migrationVersion), [import_dashboards.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts#:~:text=migrationVersion), [import_dashboards.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts#:~:text=migrationVersion), [import_dashboards.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts#:~:text=migrationVersion) | - | | | [collect_references_deep.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts#:~:text=SavedObjectAttributes), [collect_references_deep.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts#:~:text=SavedObjectAttributes), [collect_references_deep.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts#:~:text=SavedObjectAttributes), [collect_references_deep.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts#:~:text=SavedObjectAttributes) | - | -| | [saved_objects_service.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts#:~:text=getAllIndices) | - | - - - -## @kbn/core-saved-objects-server-mocks - -| Deprecated API | Reference location(s) | Remove By | -| ---------------|-----------|-----------| -| | [saved_objects_service.mock.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts#:~:text=getAllIndices), [saved_objects_service.mock.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts#:~:text=getAllIndices) | - | @@ -304,13 +294,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] -## @kbn/event-annotation-components +## @kbn/management-settings-field-definition | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/table_list.tsx#:~:text=title), [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/table_list.tsx#:~:text=title) | - | -| | [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/table_list.tsx#:~:text=title), [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/table_list.tsx#:~:text=title) | - | -| | [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/packages/kbn-event-annotation-components/components/table_list.tsx#:~:text=title) | - | +| | [get_definition.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-management/settings/field_definition/get_definition.ts#:~:text=metric) | - | @@ -575,7 +563,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [get_display_value.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts#:~:text=title), [inspector_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/search_source/inspect/inspector_stats.ts#:~:text=title), [response_writer.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/tabify/response_writer.ts#:~:text=title), [painless_error.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/errors/painless_error.tsx#:~:text=title), [field.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=title), [agg_config.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=title), [_terms_other_bucket_helper.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [rare_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/rare_terms.test.ts#:~:text=title)+ 3 more | - | | | [get_display_value.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts#:~:text=title), [inspector_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/search_source/inspect/inspector_stats.ts#:~:text=title), [response_writer.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/tabify/response_writer.ts#:~:text=title), [painless_error.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/errors/painless_error.tsx#:~:text=title), [field.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=title), [agg_config.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=title), [_terms_other_bucket_helper.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [rare_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/rare_terms.test.ts#:~:text=title)+ 3 more | - | | | [get_display_value.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts#:~:text=title), [inspector_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/search_source/inspect/inspector_stats.ts#:~:text=title), [response_writer.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/tabify/response_writer.ts#:~:text=title), [painless_error.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/errors/painless_error.tsx#:~:text=title), [field.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=title), [agg_config.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=title), [_terms_other_bucket_helper.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [rare_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/rare_terms.test.ts#:~:text=title)+ 3 more | - | -| | [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [shard_failure_open_modal_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.tsx#:~:text=toMountPoint), [shard_failure_open_modal_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.tsx#:~:text=toMountPoint), [handle_warnings.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/fetch/handle_warnings.tsx#:~:text=toMountPoint), [handle_warnings.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/fetch/handle_warnings.tsx#:~:text=toMountPoint), [delete_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/sessions_mgmt/components/actions/delete_button.tsx#:~:text=toMountPoint), [delete_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/sessions_mgmt/components/actions/delete_button.tsx#:~:text=toMountPoint)+ 8 more | - | +| | [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [search_interceptor.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/search_interceptor/search_interceptor.ts#:~:text=toMountPoint), [open_incomplete_results_modal_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/incomplete_results_modal/open_incomplete_results_modal_button.tsx#:~:text=toMountPoint), [open_incomplete_results_modal_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/incomplete_results_modal/open_incomplete_results_modal_button.tsx#:~:text=toMountPoint), [handle_warnings.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/fetch/handle_warnings.tsx#:~:text=toMountPoint), [handle_warnings.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/fetch/handle_warnings.tsx#:~:text=toMountPoint), [delete_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/sessions_mgmt/components/actions/delete_button.tsx#:~:text=toMountPoint), [delete_button.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/sessions_mgmt/components/actions/delete_button.tsx#:~:text=toMountPoint)+ 8 more | - | | | [get_columns.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [get_columns.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [get_columns.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks) | - | | | [session_service.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/server/search/session/session_service.ts#:~:text=authc) | - | | | [data_table.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx#:~:text=executeTriggerActions), [data_table.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx#:~:text=executeTriggerActions) | - | @@ -731,12 +719,22 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [get_table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation/public/get_table_list.tsx#:~:text=toMountPoint), [get_table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation/public/get_table_list.tsx#:~:text=toMountPoint) | - | | | [service.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation/public/event_annotation_service/service.test.ts#:~:text=SimpleSavedObject), [service.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation/public/event_annotation_service/service.test.ts#:~:text=SimpleSavedObject) | - | | | [service.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation/public/event_annotation_service/service.tsx#:~:text=SavedObjectReference), [service.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation/public/event_annotation_service/service.tsx#:~:text=SavedObjectReference), [service.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation/public/event_annotation_service/service.tsx#:~:text=SavedObjectReference) | - | +## eventAnnotationListing + +| Deprecated API | Reference location(s) | Remove By | +| ---------------|-----------|-----------| +| | [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/table_list.tsx#:~:text=title), [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/table_list.tsx#:~:text=title) | - | +| | [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/table_list.tsx#:~:text=title), [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/table_list.tsx#:~:text=title) | - | +| | [group_editor_controls.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx#:~:text=title), [table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/components/table_list.tsx#:~:text=title) | - | +| | [get_table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/get_table_list.tsx#:~:text=toMountPoint), [get_table_list.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/event_annotation_listing/public/get_table_list.tsx#:~:text=toMountPoint) | - | + + + ## exploratoryView | Deprecated API | Reference location(s) | Remove By | @@ -931,7 +929,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [deserialize.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.ts#:~:text=getNonScriptedFields), [datasource.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/state_management/datasource.test.ts#:~:text=getNonScriptedFields), [deserialize.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts#:~:text=getNonScriptedFields), [deserialize.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts#:~:text=getNonScriptedFields), [deserialize.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.ts#:~:text=getNonScriptedFields), [datasource.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/state_management/datasource.test.ts#:~:text=getNonScriptedFields), [deserialize.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts#:~:text=getNonScriptedFields), [deserialize.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts#:~:text=getNonScriptedFields) | - | | | [datasource.sagas.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/state_management/datasource.sagas.ts#:~:text=title), [persistence.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/state_management/persistence.ts#:~:text=title), [persistence.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/state_management/persistence.ts#:~:text=title), [datasource.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/state_management/datasource.test.ts#:~:text=title) | - | | | [deserialize.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.ts#:~:text=getNonScriptedFields), [datasource.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/state_management/datasource.test.ts#:~:text=getNonScriptedFields), [deserialize.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts#:~:text=getNonScriptedFields), [deserialize.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts#:~:text=getNonScriptedFields) | - | -| | [confirm_modal_promise.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/helpers/saved_objects_utils/confirm_modal_promise.tsx#:~:text=toMountPoint), [confirm_modal_promise.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/helpers/saved_objects_utils/confirm_modal_promise.tsx#:~:text=toMountPoint), [workspace_top_nav_menu.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx#:~:text=toMountPoint), [workspace_top_nav_menu.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx#:~:text=toMountPoint), [application.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/application.tsx#:~:text=toMountPoint), [application.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/application.tsx#:~:text=toMountPoint) | - | +| | [confirm_modal_promise.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/helpers/saved_objects_utils/confirm_modal_promise.tsx#:~:text=toMountPoint), [confirm_modal_promise.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/helpers/saved_objects_utils/confirm_modal_promise.tsx#:~:text=toMountPoint), [application.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/application.tsx#:~:text=toMountPoint), [application.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/application.tsx#:~:text=toMountPoint) | - | | | [application.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/application.tsx#:~:text=KibanaThemeProvider), [application.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/application.tsx#:~:text=KibanaThemeProvider), [application.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/application.tsx#:~:text=KibanaThemeProvider) | - | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/server/plugin.ts#:~:text=license%24) | 8.8.0 | | | [source_picker.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/graph/public/components/source_picker.tsx#:~:text=includeFields) | - | @@ -1234,10 +1232,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts#:~:text=alertFactory), [threshold_executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts#:~:text=alertFactory), [executor.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts#:~:text=alertFactory) | - | -| | [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [threshold_rule_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx#:~:text=title), [alert_details_app_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title)+ 2 more | - | -| | [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [threshold_rule_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx#:~:text=title), [alert_details_app_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title)+ 2 more | - | -| | [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [threshold_rule_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx#:~:text=title), [alert_details_app_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx#:~:text=title) | - | +| | [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts#:~:text=alertFactory), [custom_threshold_executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts#:~:text=alertFactory), [executor.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts#:~:text=alertFactory) | - | +| | [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [custom_threshold_rule_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx#:~:text=title), [alert_details_app_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title)+ 2 more | - | +| | [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [custom_threshold_rule_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx#:~:text=title), [alert_details_app_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title)+ 2 more | - | +| | [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [use_metrics_explorer_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts#:~:text=title), [custom_threshold_rule_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx#:~:text=title), [alert_details_app_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx#:~:text=title) | - | | | [header_menu_portal.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.tsx#:~:text=toMountPoint), [header_menu_portal.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.tsx#:~:text=toMountPoint) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=KibanaThemeProvider), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=KibanaThemeProvider), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=KibanaThemeProvider) | - | @@ -1398,7 +1396,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [request_handler_context.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/server/request_handler_context.ts#:~:text=authz) | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/types.ts#:~:text=SavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/types.ts#:~:text=SavedObject), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.ts#:~:text=SavedObject), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.ts#:~:text=SavedObject), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.test.ts#:~:text=SavedObject), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.test.ts#:~:text=SavedObject), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.test.ts#:~:text=SavedObject), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/test_utils/index.ts#:~:text=SavedObject), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/test_utils/index.ts#:~:text=SavedObject), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/test_utils/index.ts#:~:text=SavedObject)+ 3 more | - | | | [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/common/references.ts#:~:text=SavedObjectReference), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.ts#:~:text=SavedObjectReference), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.ts#:~:text=SavedObjectReference), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.ts#:~:text=SavedObjectReference), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/public/utils.ts#:~:text=SavedObjectReference)+ 11 more | - | -| | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/server/plugin.ts#:~:text=getAllIndices) | - | | | [tag.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/saved_objects_tagging/server/saved_objects/tag.ts#:~:text=convertToMultiNamespaceTypeVersion) | - | @@ -1470,12 +1467,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion)+ 12 more | - | | | [dependencies_start_mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts#:~:text=indexPatterns) | - | | | [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=migrationVersion)+ 78 more | - | -| | [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [risk_score_preview_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_preview_section.tsx#:~:text=title)+ 28 more | - | +| | [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title)+ 30 more | - | | | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | -| | [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [risk_score_preview_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_preview_section.tsx#:~:text=title)+ 28 more | - | -| | [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title), [risk_score_preview_section.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_preview_section.tsx#:~:text=title)+ 9 more | - | +| | [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title)+ 30 more | - | +| | [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx#:~:text=title)+ 10 more | - | | | [use_update_data_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/sourcerer/use_update_data_view.tsx#:~:text=toMountPoint), [use_update_data_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/sourcerer/use_update_data_view.tsx#:~:text=toMountPoint), [use_update_data_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/sourcerer/use_update_data_view.tsx#:~:text=toMountPoint), [ingest_pipelines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/explore/containers/risk_score/onboarding/api/ingest_pipelines.ts#:~:text=toMountPoint), [ingest_pipelines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/explore/containers/risk_score/onboarding/api/ingest_pipelines.ts#:~:text=toMountPoint), [ingest_pipelines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/explore/containers/risk_score/onboarding/api/ingest_pipelines.ts#:~:text=toMountPoint), [stored_scripts.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/explore/containers/risk_score/onboarding/api/stored_scripts.ts#:~:text=toMountPoint), [stored_scripts.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/explore/containers/risk_score/onboarding/api/stored_scripts.ts#:~:text=toMountPoint), [stored_scripts.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/explore/containers/risk_score/onboarding/api/stored_scripts.ts#:~:text=toMountPoint), [saved_objects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/explore/containers/risk_score/onboarding/api/saved_objects.ts#:~:text=toMountPoint)+ 5 more | - | | | [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=KibanaThemeProvider), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=KibanaThemeProvider), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=KibanaThemeProvider) | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | @@ -1486,7 +1483,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedRowRenderer), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedRowRenderer) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField), [use_data_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx#:~:text=BrowserField)+ 29 more | - | -| | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields)+ 102 more | - | +| | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields)+ 106 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyRequest), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyRequest) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyResponse), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyResponse) | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/types.ts#:~:text=SimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/types.ts#:~:text=SimpleSavedObject) | - | @@ -1550,14 +1547,14 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/es_query/index.ts#:~:text=registerNavigation) | - | -| | [rule_type.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts#:~:text=alertFactory), [rule_type.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts#:~:text=alertFactory), [get_entities_and_generate_alerts.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.ts#:~:text=alertFactory), [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts#:~:text=alertFactory), [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts#:~:text=alertFactory) | - | +| | [rule_type.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts#:~:text=alertFactory), [get_entities_and_generate_alerts.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.ts#:~:text=alertFactory), [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts#:~:text=alertFactory), [executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts#:~:text=alertFactory) | - | | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts#:~:text=fetch) | - | -| | [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=indexPatterns), [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns) | - | +| | [data_view_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/data_view_select.tsx#:~:text=indexPatterns), [boundary_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx#:~:text=indexPatterns), [boundary_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx#:~:text=indexPatterns), [entity_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx#:~:text=indexPatterns), [entity_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx#:~:text=indexPatterns) | - | | | [expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/threshold/expression.tsx#:~:text=fieldFormats) | - | -| | [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=title), [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title), [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=title)+ 8 more | - | +| | [boundary_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx#:~:text=title), [entity_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title), [boundary_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx#:~:text=title), [entity_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title) | - | | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts#:~:text=fetch) | - | -| | [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=title), [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title), [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=title)+ 8 more | - | -| | [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=title), [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title) | - | +| | [boundary_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx#:~:text=title), [entity_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title), [boundary_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx#:~:text=title), [entity_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title) | - | +| | [boundary_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx#:~:text=title), [entity_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx#:~:text=title), [data_view_select_popover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx#:~:text=title) | - | @@ -1567,7 +1564,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | ---------------|-----------|-----------| | | [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/common.ts#:~:text=alertFactory), [message_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/tls_rule/message_utils.ts#:~:text=alertFactory), [tls_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/tls_rule/tls_rule.ts#:~:text=alertFactory), [monitor_status_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts#:~:text=alertFactory) | - | | | [stderr_logs.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx#:~:text=indexPatternId) | - | -| | [toast_title.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx#:~:text=toMountPoint), [toast_title.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx#:~:text=toMountPoint), [delete_monitor.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx#:~:text=toMountPoint), [delete_monitor.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx#:~:text=toMountPoint), [delete_monitor.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx#:~:text=toMountPoint), [browser_test_results.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx#:~:text=toMountPoint), [browser_test_results.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx#:~:text=toMountPoint), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=toMountPoint), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=toMountPoint), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=toMountPoint)+ 6 more | - | +| | [toast_title.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx#:~:text=toMountPoint), [toast_title.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx#:~:text=toMountPoint), [browser_test_results.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx#:~:text=toMountPoint), [browser_test_results.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx#:~:text=toMountPoint), [delete_monitor.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx#:~:text=toMountPoint), [delete_monitor.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx#:~:text=toMountPoint), [delete_monitor.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx#:~:text=toMountPoint), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=toMountPoint), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=toMountPoint), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=toMountPoint)+ 6 more | - | | | [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks) | - | | | [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=KibanaThemeProvider), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=KibanaThemeProvider), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=KibanaThemeProvider) | - | @@ -1615,10 +1612,9 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [register_transform_health_rule_type.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts#:~:text=alertFactory) | - | -| | [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [es_index_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/services/es_index_service.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title), [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [es_index_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/services/es_index_service.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title) | - | -| | [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [es_index_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/services/es_index_service.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title), [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [es_index_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/services/es_index_service.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title) | - | -| | [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [es_index_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/services/es_index_service.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title) | - | -| | [toast_notification_text.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx#:~:text=toMountPoint), [toast_notification_text.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx#:~:text=toMountPoint), [step_create_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx#:~:text=toMountPoint), [step_create_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx#:~:text=toMountPoint), [step_create_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx#:~:text=toMountPoint), [step_create_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx#:~:text=toMountPoint), [step_create_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx#:~:text=toMountPoint), [step_details_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx#:~:text=toMountPoint), [step_details_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx#:~:text=toMountPoint), [step_details_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx#:~:text=toMountPoint)+ 19 more | - | +| | [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [use_data_view_exists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [use_data_view_exists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title) | - | +| | [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [use_data_view_exists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [use_data_view_exists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title) | - | +| | [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_search_items/common.ts#:~:text=title), [filter_term_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx#:~:text=title), [use_data_view_exists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts#:~:text=title), [transforms.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/routes/api/transforms.ts#:~:text=title), [common.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts#:~:text=title) | - | | | [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/app.tsx#:~:text=KibanaThemeProvider), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/app.tsx#:~:text=KibanaThemeProvider), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/app.tsx#:~:text=KibanaThemeProvider) | - | | | [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/server/services/license.ts#:~:text=license%24) | 8.8.0 | | | [mount_management_section.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/transform/public/app/mount_management_section.ts#:~:text=savedObjects) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index af65fb19f5342..34086fddf297e 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 5169da2cde4e7..0e2ef6eaefa4b 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.devdocs.json b/api_docs/discover.devdocs.json index 65c3fb0bef8a3..9d55f29fd0d67 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -24,6 +24,280 @@ } ], "interfaces": [ + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState", + "type": "Interface", + "tags": [], + "label": "DiscoverAppState", + "description": [], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.columns", + "type": "Array", + "tags": [], + "label": "columns", + "description": [ + "\nColumns displayed in the table" + ], + "signature": [ + "string[] | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.filters", + "type": "Array", + "tags": [], + "label": "filters", + "description": [ + "\nArray of applied filters" + ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[] | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.grid", + "type": "Object", + "tags": [], + "label": "grid", + "description": [ + "\nData Grid related state" + ], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "common", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-common.UnifiedDataTableSettings", + "text": "UnifiedDataTableSettings" + }, + " | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.hideChart", + "type": "CompoundType", + "tags": [], + "label": "hideChart", + "description": [ + "\nHide chart" + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.index", + "type": "string", + "tags": [], + "label": "index", + "description": [ + "\nid of the used data view" + ], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.interval", + "type": "string", + "tags": [], + "label": "interval", + "description": [ + "\nUsed interval of the histogram" + ], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.query", + "type": "CompoundType", + "tags": [], + "label": "query", + "description": [ + "\nLucence or KQL query" + ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + " | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.sort", + "type": "Array", + "tags": [], + "label": "sort", + "description": [ + "\nArray of the used sorting [[field,direction],...]" + ], + "signature": [ + "string[][] | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.savedQuery", + "type": "string", + "tags": [], + "label": "savedQuery", + "description": [ + "\nid of the used saved query" + ], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.viewMode", + "type": "CompoundType", + "tags": [], + "label": "viewMode", + "description": [ + "\nTable view: Documents vs Field Statistics" + ], + "signature": [ + { + "pluginId": "savedSearch", + "scope": "common", + "docId": "kibSavedSearchPluginApi", + "section": "def-common.VIEW_MODE", + "text": "VIEW_MODE" + }, + " | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.hideAggregatedPreview", + "type": "CompoundType", + "tags": [], + "label": "hideAggregatedPreview", + "description": [ + "\nHide mini distribution/preview charts when in Field Statistics mode" + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.rowHeight", + "type": "number", + "tags": [], + "label": "rowHeight", + "description": [ + "\nDocument explorer row height option" + ], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.rowsPerPage", + "type": "number", + "tags": [], + "label": "rowsPerPage", + "description": [ + "\nNumber of rows in the grid per page" + ], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.breakdownField", + "type": "string", + "tags": [], + "label": "breakdownField", + "description": [ + "\nBreakdown field of chart" + ], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "discover", "id": "def-public.DiscoverCustomizationService", @@ -991,24 +1265,6 @@ "deprecated": false, "trackAdoption": false, "children": [ - { - "parentPluginId": "discover", - "id": "def-public.DiscoverSetup.docViews", - "type": "Object", - "tags": [], - "label": "docViews", - "description": [], - "signature": [ - "{ addDocView(docViewRaw: ", - "DocViewInput", - " | ", - "DocViewInputFn", - "): void; }" - ], - "path": "src/plugins/discover/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "discover", "id": "def-public.DiscoverSetup.locator", diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index ad7a18b2ba759..2dade7597911d 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 99 | 0 | 72 | 17 | +| 113 | 0 | 72 | 15 | ## Client diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 5b0c6edf5ed30..2d15dd3b70b36 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index e51c759a4260f..3506edd0bd41b 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index 34334fb846e41..2355c09ce1195 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index 59893ec0f6f38..e0099830f1f89 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -3254,173 +3254,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot", - "type": "Class", - "tags": [], - "label": "EmbeddableRoot", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableRoot", - "text": "EmbeddableRoot" - }, - " extends React.Component" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.Unnamed", - "type": "Function", - "tags": [], - "label": "Constructor", - "description": [], - "signature": [ - "any" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.Unnamed.$1", - "type": "Object", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "Props" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.componentDidMount", - "type": "Function", - "tags": [], - "label": "componentDidMount", - "description": [], - "signature": [ - "() => void" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.componentDidUpdate", - "type": "Function", - "tags": [], - "label": "componentDidUpdate", - "description": [], - "signature": [ - "(prevProps?: Props | undefined) => void" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.componentDidUpdate.$1", - "type": "Object", - "tags": [], - "label": "prevProps", - "description": [], - "signature": [ - "Props | undefined" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] - }, - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.shouldComponentUpdate", - "type": "Function", - "tags": [], - "label": "shouldComponentUpdate", - "description": [], - "signature": [ - "({ embeddable, error, input, loading }: Props, { node }: State) => boolean" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.shouldComponentUpdate.$1", - "type": "Object", - "tags": [], - "label": "{ embeddable, error, input, loading }", - "description": [], - "signature": [ - "Props" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.shouldComponentUpdate.$2", - "type": "Object", - "tags": [], - "label": "{ node }", - "description": [], - "signature": [ - "State" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "embeddable", - "id": "def-public.EmbeddableRoot.render", - "type": "Function", - "tags": [], - "label": "render", - "description": [], - "signature": [ - "() => JSX.Element" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - } - ], - "initialIsOpen": false - }, { "parentPluginId": "embeddable", "id": "def-public.EmbeddableStateTransfer", @@ -4798,6 +4631,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "embeddable", + "id": "def-public.EmbeddableRoot", + "type": "Function", + "tags": [], + "label": "EmbeddableRoot", + "description": [], + "signature": [ + "({ embeddable, loading, error, input }: React.PropsWithChildren) => JSX.Element" + ], + "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddable", + "id": "def-public.EmbeddableRoot.$1", + "type": "CompoundType", + "tags": [], + "label": "{ embeddable, loading, error, input }", + "description": [], + "signature": [ + "React.PropsWithChildren" + ], + "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "embeddable", "id": "def-public.genericEmbeddableInputIsEqual", diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index fec72fb9822ca..2a512376e59db 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 542 | 1 | 442 | 7 | +| 534 | 1 | 434 | 7 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index cbde77f143f8c..ac8687f69f529 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 4f01a1f739e50..46c42d3501584 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.devdocs.json b/api_docs/enterprise_search.devdocs.json index 7842d688a354d..318a97a2de824 100644 --- a/api_docs/enterprise_search.devdocs.json +++ b/api_docs/enterprise_search.devdocs.json @@ -45,51 +45,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "enterpriseSearch", - "id": "def-server.CONNECTORS_INDEX", - "type": "string", - "tags": [], - "label": "CONNECTORS_INDEX", - "description": [], - "signature": [ - "\".elastic-connectors\"" - ], - "path": "x-pack/plugins/enterprise_search/server/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "enterpriseSearch", - "id": "def-server.CONNECTORS_JOBS_INDEX", - "type": "string", - "tags": [], - "label": "CONNECTORS_JOBS_INDEX", - "description": [], - "signature": [ - "\".elastic-connectors-sync-jobs\"" - ], - "path": "x-pack/plugins/enterprise_search/server/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "enterpriseSearch", - "id": "def-server.CONNECTORS_VERSION", - "type": "number", - "tags": [], - "label": "CONNECTORS_VERSION", - "description": [], - "signature": [ - "1" - ], - "path": "x-pack/plugins/enterprise_search/server/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "enterpriseSearch", "id": "def-server.CRAWLERS_INDEX", @@ -107,28 +62,13 @@ }, { "parentPluginId": "enterpriseSearch", - "id": "def-server.CURRENT_CONNECTORS_INDEX", - "type": "string", - "tags": [], - "label": "CURRENT_CONNECTORS_INDEX", - "description": [], - "signature": [ - "\".elastic-connectors-v1\"" - ], - "path": "x-pack/plugins/enterprise_search/server/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "enterpriseSearch", - "id": "def-server.CURRENT_CONNECTORS_JOB_INDEX", - "type": "string", + "id": "def-server.EnterpriseSearchPluginStart", + "type": "Type", "tags": [], - "label": "CURRENT_CONNECTORS_JOB_INDEX", + "label": "EnterpriseSearchPluginStart", "description": [], "signature": [ - "\".elastic-connectors-sync-jobs-v1\"" + "PluginStart" ], "path": "x-pack/plugins/enterprise_search/server/index.ts", "deprecated": false, @@ -303,20 +243,7 @@ "trackAdoption": false, "initialIsOpen": false } - ], - "start": { - "parentPluginId": "enterpriseSearch", - "id": "def-server.EnterpriseSearchPluginStart", - "type": "Type", - "tags": [], - "label": "EnterpriseSearchPluginStart", - "description": [], - "path": "x-pack/plugins/enterprise_search/server/index.ts", - "deprecated": false, - "trackAdoption": false, - "lifecycle": "start", - "initialIsOpen": true - } + ] }, "common": { "classes": [], diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 18f78e4eded19..45013bb12a726 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 11 | 0 | 11 | 0 | +| 6 | 0 | 6 | 0 | ## Client @@ -30,9 +30,6 @@ Contact [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/te ## Server -### Start - - ### Objects diff --git a/api_docs/es_ui_shared.devdocs.json b/api_docs/es_ui_shared.devdocs.json index 2592bf87a7c62..37cdf84e7503e 100644 --- a/api_docs/es_ui_shared.devdocs.json +++ b/api_docs/es_ui_shared.devdocs.json @@ -363,7 +363,7 @@ "label": "JsonEditor", "description": [], "signature": [ - "({ label, helpText, onUpdate, value, defaultValue, codeEditorProps, error: propsError, }: Props) => JSX.Element" + "({ label, helpText, onUpdate, value, defaultValue, codeEditorProps, error: propsError, ...rest }: Props) => JSX.Element" ], "path": "src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx", "deprecated": false, diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index a74883318db9e..33d43d630c564 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.devdocs.json b/api_docs/event_annotation.devdocs.json index e11e847c4ed5f..1eb8c01aefbad 100644 --- a/api_docs/event_annotation.devdocs.json +++ b/api_docs/event_annotation.devdocs.json @@ -1053,67 +1053,129 @@ }, { "parentPluginId": "eventAnnotation", - "id": "def-common.EventAnnotationArgs", + "id": "def-common.EventAnnotationGroupCreateIn", "type": "Type", "tags": [], - "label": "EventAnnotationArgs", + "label": "EventAnnotationGroupCreateIn", "description": [], "signature": [ { - "pluginId": "eventAnnotation", + "pluginId": "contentManagement", "scope": "common", - "docId": "kibEventAnnotationPluginApi", - "section": "def-common.ManualPointEventAnnotationArgs", - "text": "ManualPointEventAnnotationArgs" + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" }, - " | ", + "<\"event-annotation-group\", ", { "pluginId": "eventAnnotation", "scope": "common", "docId": "kibEventAnnotationPluginApi", - "section": "def-common.ManualRangeEventAnnotationArgs", - "text": "ManualRangeEventAnnotationArgs" + "section": "def-common.EventAnnotationGroupSavedObjectAttributes", + "text": "EventAnnotationGroupSavedObjectAttributes" }, - " | ", + ", ", + "CreateOptions", + ">" + ], + "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotation", + "id": "def-common.EventAnnotationGroupCreateOut", + "type": "Type", + "tags": [], + "label": "EventAnnotationGroupCreateOut", + "description": [], + "signature": [ + "{ item: ", + "EventAnnotationGroupSavedObject", + "; }" + ], + "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotation", + "id": "def-common.EventAnnotationGroupDeleteIn", + "type": "Type", + "tags": [], + "label": "EventAnnotationGroupDeleteIn", + "description": [], + "signature": [ { - "pluginId": "eventAnnotation", + "pluginId": "contentManagement", "scope": "common", - "docId": "kibEventAnnotationPluginApi", - "section": "def-common.QueryPointEventAnnotationArgs", - "text": "QueryPointEventAnnotationArgs" - } + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + "<\"event-annotation-group\", object>" ], - "path": "src/plugins/event_annotation/common/types.ts", + "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "eventAnnotation", - "id": "def-common.EventAnnotationGroupCreateIn", + "id": "def-common.EventAnnotationGroupDeleteOut", "type": "Type", "tags": [], - "label": "EventAnnotationGroupCreateIn", + "label": "EventAnnotationGroupDeleteOut", "description": [], "signature": [ { "pluginId": "contentManagement", "scope": "common", "docId": "kibContentManagementPluginApi", - "section": "def-common.CreateIn", - "text": "CreateIn" - }, - "<\"event-annotation-group\", ", + "section": "def-common.DeleteResult", + "text": "DeleteResult" + } + ], + "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotation", + "id": "def-common.EventAnnotationGroupGetIn", + "type": "Type", + "tags": [], + "label": "EventAnnotationGroupGetIn", + "description": [], + "signature": [ { - "pluginId": "eventAnnotation", + "pluginId": "contentManagement", "scope": "common", - "docId": "kibEventAnnotationPluginApi", - "section": "def-common.EventAnnotationGroupSavedObjectAttributes", - "text": "EventAnnotationGroupSavedObjectAttributes" + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" }, - ", ", - "CreateOptions", - ">" + "<\"event-annotation-group\", object>" + ], + "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotation", + "id": "def-common.EventAnnotationGroupGetOut", + "type": "Type", + "tags": [], + "label": "EventAnnotationGroupGetOut", + "description": [], + "signature": [ + "{ item: ", + "EventAnnotationGroupSavedObject", + "; meta: { outcome: \"conflict\" | \"exactMatch\" | \"aliasMatch\"; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; }; }" ], "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", "deprecated": false, @@ -1144,6 +1206,23 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "eventAnnotation", + "id": "def-common.EventAnnotationGroupSearchOut", + "type": "Type", + "tags": [], + "label": "EventAnnotationGroupSearchOut", + "description": [], + "signature": [ + "{ hits: ", + "EventAnnotationGroupSavedObject", + "[]; pagination: { total: number; cursor?: string | undefined; }; }" + ], + "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "eventAnnotation", "id": "def-common.EventAnnotationGroupUpdateIn", diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 6143c89b29960..3de8ad1d6fdef 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 195 | 0 | 195 | 5 | +| 200 | 0 | 200 | 6 | ## Client diff --git a/api_docs/event_annotation_listing.devdocs.json b/api_docs/event_annotation_listing.devdocs.json new file mode 100644 index 0000000000000..0e7d13bdd46ad --- /dev/null +++ b/api_docs/event_annotation_listing.devdocs.json @@ -0,0 +1,349 @@ +{ + "id": "eventAnnotationListing", + "client": { + "classes": [], + "functions": [ + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.AnnotationEditorControls", + "type": "Function", + "tags": [], + "label": "AnnotationEditorControls", + "description": [], + "signature": [ + "(props: ", + "Props", + ") => JSX.Element" + ], + "path": "packages/kbn-event-annotation-components/components/annotation_editor_controls/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.AnnotationEditorControls.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "Props" + ], + "path": "packages/kbn-event-annotation-components/components/annotation_editor_controls/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.getAnnotationAccessor", + "type": "Function", + "tags": [], + "label": "getAnnotationAccessor", + "description": [], + "signature": [ + "(annotation: ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + ") => ", + { + "pluginId": "@kbn/visualization-ui-components", + "scope": "public", + "docId": "kibKbnVisualizationUiComponentsPluginApi", + "section": "def-public.AccessorConfig", + "text": "AccessorConfig" + } + ], + "path": "packages/kbn-event-annotation-components/components/get_annotation_accessor.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.getAnnotationAccessor.$1", + "type": "CompoundType", + "tags": [], + "label": "annotation", + "description": [], + "signature": [ + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + } + ], + "path": "packages/kbn-event-annotation-components/components/get_annotation_accessor.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.isManualPointAnnotationConfig", + "type": "Function", + "tags": [], + "label": "isManualPointAnnotationConfig", + "description": [], + "signature": [ + "(annotation?: ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + " | undefined) => annotation is ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.PointInTimeEventAnnotationConfig", + "text": "PointInTimeEventAnnotationConfig" + } + ], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.isManualPointAnnotationConfig.$1", + "type": "CompoundType", + "tags": [], + "label": "annotation", + "description": [], + "signature": [ + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + " | undefined" + ], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.isQueryAnnotationConfig", + "type": "Function", + "tags": [], + "label": "isQueryAnnotationConfig", + "description": [], + "signature": [ + "(annotation?: ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + " | undefined) => annotation is ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.QueryPointEventAnnotationConfig", + "text": "QueryPointEventAnnotationConfig" + } + ], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.isQueryAnnotationConfig.$1", + "type": "CompoundType", + "tags": [], + "label": "annotation", + "description": [], + "signature": [ + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + " | undefined" + ], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.isRangeAnnotationConfig", + "type": "Function", + "tags": [], + "label": "isRangeAnnotationConfig", + "description": [], + "signature": [ + "(annotation?: ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + " | undefined) => annotation is ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.RangeEventAnnotationConfig", + "text": "RangeEventAnnotationConfig" + } + ], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.isRangeAnnotationConfig.$1", + "type": "CompoundType", + "tags": [], + "label": "annotation", + "description": [], + "signature": [ + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + " | undefined" + ], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.annotationsIconSet", + "type": "Array", + "tags": [], + "label": "annotationsIconSet", + "description": [], + "signature": [ + "{ value: ", + { + "pluginId": "@kbn/event-annotation-common", + "scope": "common", + "docId": "kibKbnEventAnnotationCommonPluginApi", + "section": "def-common.AvailableAnnotationIcon", + "text": "AvailableAnnotationIcon" + }, + "; label: string; icon?: string | React.ComponentClass<{}, any> | React.FunctionComponent<{}> | undefined; shouldRotate?: boolean | undefined; }[]" + ], + "path": "packages/kbn-event-annotation-components/components/annotation_editor_controls/icon_set.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.defaultAnnotationColor", + "type": "string", + "tags": [], + "label": "defaultAnnotationColor", + "description": [], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.defaultAnnotationRangeColor", + "type": "string", + "tags": [], + "label": "defaultAnnotationRangeColor", + "description": [], + "signature": [ + "\"#F04E981A\"" + ], + "path": "packages/kbn-event-annotation-common/util.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [], + "start": { + "parentPluginId": "eventAnnotationListing", + "id": "def-public.EventAnnotationListingPluginStart", + "type": "Type", + "tags": [], + "label": "EventAnnotationListingPluginStart", + "description": [], + "signature": [ + "void" + ], + "path": "src/plugins/event_annotation_listing/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx new file mode 100644 index 0000000000000..7420c4a9073d9 --- /dev/null +++ b/api_docs/event_annotation_listing.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibEventAnnotationListingPluginApi +slug: /kibana-dev-docs/api/eventAnnotationListing +title: "eventAnnotationListing" +image: https://source.unsplash.com/400x175/?github +description: API docs for the eventAnnotationListing plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] +--- +import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; + +The listing page for event annotations. + +Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 15 | 0 | 15 | 0 | + +## Client + +### Start + + +### Functions + + +### Consts, variables and types + + diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 9bf96154dbd29..13c4a74d89509 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index ad2a0e0bd2dbb..497b727c7f9ee 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index d302c947899f2..bec03291ccd0d 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 6bae86336271a..fbe7024383a94 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 2b275e3dcee13..0f62d4eba2e81 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 24bd3420a2358..e8e3b091ae0ab 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index c6219a34b0b6f..140addae88a63 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 847c71aad5042..5ec7ac253238b 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index bab457b980673..0048f1faef58a 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 56b8d2021ef89..1e39702d1f580 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 3e9f12147ab59..55268c7a1bdbe 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 4ef83001528c2..c47451ef632d2 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 1c4898d10e35f..aa5867ab87295 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index d7ef8aae7da96..d3ddf2d43e3b9 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 784b377013cc5..708adeadc3770 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.devdocs.json b/api_docs/expressions.devdocs.json index bb622685ac10c..8bd5ba8e59463 100644 --- a/api_docs/expressions.devdocs.json +++ b/api_docs/expressions.devdocs.json @@ -6975,6 +6975,20 @@ "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "expressions", + "id": "def-public.Datatable.warning", + "type": "string", + "tags": [], + "label": "warning", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -18368,6 +18382,20 @@ "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "expressions", + "id": "def-server.Datatable.warning", + "type": "string", + "tags": [], + "label": "warning", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -30133,6 +30161,20 @@ "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "expressions", + "id": "def-common.Datatable.warning", + "type": "string", + "tags": [], + "label": "warning", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -39366,7 +39408,7 @@ }, "[]; meta?: ", "DatatableMeta", - " | undefined; }" + " | undefined; warning?: string | undefined; }" ], "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, @@ -39424,7 +39466,7 @@ }, "[]; meta?: ", "DatatableMeta", - " | undefined; }" + " | undefined; warning?: string | undefined; }" ], "path": "src/plugins/expressions/common/expression_types/specs/datatable.ts", "deprecated": false, @@ -41815,7 +41857,7 @@ }, " | { [x: string]: any; })[]; type: \"datatable\"; meta?: ", "DatatableMeta", - " | undefined; }>" + " | undefined; warning?: string | undefined; }>" ], "path": "src/plugins/expressions/common/expression_functions/specs/map_column.ts", "deprecated": false, diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 64b7d3d883c47..1cf9932177e27 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2205 | 17 | 1746 | 5 | +| 2208 | 17 | 1749 | 5 | ## Client diff --git a/api_docs/features.mdx b/api_docs/features.mdx index d433e0d7ce363..07537e41fd88a 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 7b82e841bd51b..a646587639331 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.devdocs.json b/api_docs/file_upload.devdocs.json index f25468cc2ce79..5f45445c8f0f1 100644 --- a/api_docs/file_upload.devdocs.json +++ b/api_docs/file_upload.devdocs.json @@ -1124,48 +1124,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "fileUpload", - "id": "def-common.Mappings", - "type": "Interface", - "tags": [], - "label": "Mappings", - "description": [], - "path": "x-pack/plugins/file_upload/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "fileUpload", - "id": "def-common.Mappings._meta", - "type": "Object", - "tags": [], - "label": "_meta", - "description": [], - "signature": [ - "{ created_by: string; } | undefined" - ], - "path": "x-pack/plugins/file_upload/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fileUpload", - "id": "def-common.Mappings.properties", - "type": "Object", - "tags": [], - "label": "properties", - "description": [], - "signature": [ - "{ [key: string]: any; }" - ], - "path": "x-pack/plugins/file_upload/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [], diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 8e634433e6dac..2d6083153ad25 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 62 | 0 | 62 | 2 | +| 59 | 0 | 59 | 2 | ## Client diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 705919e015db5..9746dda29b014 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 9974c3202afc6..037578e36942f 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 81bd886607d5f..efa04820ce936 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -26707,6 +26707,128 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.API_VERSIONS", + "type": "Object", + "tags": [], + "label": "API_VERSIONS", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.API_VERSIONS.public", + "type": "Object", + "tags": [], + "label": "public", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.API_VERSIONS.public.v1", + "type": "string", + "tags": [], + "label": "v1", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "fleet", + "id": "def-common.API_VERSIONS.internal", + "type": "Object", + "tags": [], + "label": "internal", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.API_VERSIONS.internal.v1", + "type": "string", + "tags": [], + "label": "v1", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.APP_API_ROUTES", + "type": "Object", + "tags": [], + "label": "APP_API_ROUTES", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.APP_API_ROUTES.HEALTH_CHECK_PATTERN", + "type": "string", + "tags": [], + "label": "HEALTH_CHECK_PATTERN", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN", + "type": "string", + "tags": [], + "label": "CHECK_PERMISSIONS_PATTERN", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN", + "type": "string", + "tags": [], + "label": "GENERATE_SERVICE_TOKEN_PATTERN", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN_DEPRECATED", + "type": "string", + "tags": [], + "label": "GENERATE_SERVICE_TOKEN_PATTERN_DEPRECATED", + "description": [ + "// deprecated since 8.0" + ], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.appRoutesService", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index e23eadd716382..9f7dc59bfacf0 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1189 | 3 | 1073 | 41 | +| 1199 | 3 | 1082 | 41 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index bc9cbe06e3141..80a900d285311 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 5a273a247dc82..f15f32b1de60b 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.devdocs.json b/api_docs/home.devdocs.json index e39bab658985d..ffae4fbc9f5a5 100644 --- a/api_docs/home.devdocs.json +++ b/api_docs/home.devdocs.json @@ -693,6 +693,54 @@ "path": "src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "home", + "id": "def-public.FeatureCatalogueSolution.isVisible", + "type": "Function", + "tags": [], + "label": "isVisible", + "description": [ + "Optional function to control visibility of this solution." + ], + "signature": [ + "((capabilities: ", + { + "pluginId": "@kbn/core-capabilities-common", + "scope": "common", + "docId": "kibKbnCoreCapabilitiesCommonPluginApi", + "section": "def-common.Capabilities", + "text": "Capabilities" + }, + ") => boolean) | undefined" + ], + "path": "src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "home", + "id": "def-public.FeatureCatalogueSolution.isVisible.$1", + "type": "Object", + "tags": [], + "label": "capabilities", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-capabilities-common", + "scope": "common", + "docId": "kibKbnCoreCapabilitiesCommonPluginApi", + "section": "def-common.Capabilities", + "text": "Capabilities" + } + ], + "path": "src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 2cc874f59ccb6..9dc2f69cc95a6 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 147 | 0 | 108 | 0 | +| 149 | 0 | 109 | 0 | ## Client diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index 73b1867ba3fc9..34aaa66751a1e 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index eb6ca9dc97ce7..0c8b53158be4e 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.devdocs.json b/api_docs/index_management.devdocs.json index 6d3b01d5245cf..29f83cb5e44d5 100644 --- a/api_docs/index_management.devdocs.json +++ b/api_docs/index_management.devdocs.json @@ -1410,12 +1410,13 @@ { "parentPluginId": "indexManagement", "id": "def-common.DataStream.storageSize", - "type": "string", + "type": "CompoundType", "tags": [], "label": "storageSize", "description": [], "signature": [ - "string | undefined" + "ByteSize", + " | undefined" ], "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, @@ -1457,7 +1458,8 @@ "label": "_meta", "description": [], "signature": [ - "MetaFromEs | undefined" + "Metadata", + " | undefined" ], "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, @@ -1487,16 +1489,31 @@ "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.DataStream.lifecycle", + "type": "Object", + "tags": [], + "label": "lifecycle", + "description": [], + "signature": [ + "IndicesDataLifecycleWithRollover", + " | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/data_streams.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs", + "id": "def-common.DataStreamIndex", "type": "Interface", "tags": [], - "label": "DataStreamFromEs", + "label": "DataStreamIndex", "description": [], "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, @@ -1504,7 +1521,7 @@ "children": [ { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.name", + "id": "def-common.DataStreamIndex.name", "type": "string", "tags": [], "label": "name", @@ -1515,92 +1532,50 @@ }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.timestamp_field", - "type": "Object", - "tags": [], - "label": "timestamp_field", - "description": [], - "signature": [ - "TimestampFieldFromEs" - ], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.indices", - "type": "Array", - "tags": [], - "label": "indices", - "description": [], - "signature": [ - "DataStreamIndexFromEs", - "[]" - ], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.generation", - "type": "number", + "id": "def-common.DataStreamIndex.uuid", + "type": "string", "tags": [], - "label": "generation", + "label": "uuid", "description": [], "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.EnhancedDataStreamFromEs", + "type": "Interface", + "tags": [], + "label": "EnhancedDataStreamFromEs", + "description": [], + "signature": [ { - "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs._meta", - "type": "Object", - "tags": [], - "label": "_meta", - "description": [], - "signature": [ - "MetaFromEs | undefined" - ], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "indexManagement", + "scope": "common", + "docId": "kibIndexManagementPluginApi", + "section": "def-common.EnhancedDataStreamFromEs", + "text": "EnhancedDataStreamFromEs" }, + " extends ", + "IndicesDataStream" + ], + "path": "x-pack/plugins/index_management/common/types/data_streams.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.status", + "id": "def-common.EnhancedDataStreamFromEs.store_size", "type": "CompoundType", "tags": [], - "label": "status", - "description": [], - "signature": [ - "\"GREEN\" | \"YELLOW\" | \"RED\"" - ], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.template", - "type": "string", - "tags": [], - "label": "template", - "description": [], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.ilm_policy", - "type": "string", - "tags": [], - "label": "ilm_policy", + "label": "store_size", "description": [], "signature": [ - "string | undefined" + "ByteSize", + " | undefined" ], "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, @@ -1608,13 +1583,13 @@ }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.store_size", - "type": "string", + "id": "def-common.EnhancedDataStreamFromEs.store_size_bytes", + "type": "number", "tags": [], - "label": "store_size", + "label": "store_size_bytes", "description": [], "signature": [ - "string | undefined" + "number | undefined" ], "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, @@ -1622,10 +1597,10 @@ }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.store_size_bytes", + "id": "def-common.EnhancedDataStreamFromEs.maximum_timestamp", "type": "number", "tags": [], - "label": "store_size_bytes", + "label": "maximum_timestamp", "description": [], "signature": [ "number | undefined" @@ -1636,40 +1611,71 @@ }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.maximum_timestamp", - "type": "number", + "id": "def-common.EnhancedDataStreamFromEs.privileges", + "type": "Object", "tags": [], - "label": "maximum_timestamp", + "label": "privileges", "description": [], "signature": [ - "number | undefined" + "{ delete_index: boolean; }" ], "path": "x-pack/plugins/index_management/common/types/data_streams.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.FieldFromIndicesRequest", + "type": "Interface", + "tags": [], + "label": "FieldFromIndicesRequest", + "description": [], + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.privileges", - "type": "Object", + "id": "def-common.FieldFromIndicesRequest.commonFields", + "type": "Array", "tags": [], - "label": "privileges", + "label": "commonFields", "description": [], "signature": [ - "PrivilegesFromEs" + { + "pluginId": "indexManagement", + "scope": "common", + "docId": "kibIndexManagementPluginApi", + "section": "def-common.FieldItem", + "text": "FieldItem" + }, + "[]" ], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamFromEs.hidden", - "type": "boolean", + "id": "def-common.FieldFromIndicesRequest.indices", + "type": "Array", "tags": [], - "label": "hidden", + "label": "indices", "description": [], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", + "signature": [ + { + "pluginId": "indexManagement", + "scope": "common", + "docId": "kibIndexManagementPluginApi", + "section": "def-common.IndexWithFields", + "text": "IndexWithFields" + }, + "[]" + ], + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", "deprecated": false, "trackAdoption": false } @@ -1678,34 +1684,45 @@ }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamIndex", + "id": "def-common.FieldItem", "type": "Interface", "tags": [], - "label": "DataStreamIndex", + "label": "FieldItem", "description": [], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamIndex.name", + "id": "def-common.FieldItem.name", "type": "string", "tags": [], "label": "name", "description": [], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "indexManagement", - "id": "def-common.DataStreamIndex.uuid", + "id": "def-common.FieldItem.type", "type": "string", "tags": [], - "label": "uuid", + "label": "type", "description": [], - "path": "x-pack/plugins/index_management/common/types/data_streams.ts", + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.FieldItem.normalizedType", + "type": "string", + "tags": [], + "label": "normalizedType", + "description": [], + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", "deprecated": false, "trackAdoption": false } @@ -2013,6 +2030,106 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.IndexSettingsResponse", + "type": "Interface", + "tags": [], + "label": "IndexSettingsResponse", + "description": [], + "path": "x-pack/plugins/index_management/common/types/indices.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "indexManagement", + "id": "def-common.IndexSettingsResponse.settings", + "type": "Object", + "tags": [], + "label": "settings", + "description": [], + "signature": [ + { + "pluginId": "indexManagement", + "scope": "common", + "docId": "kibIndexManagementPluginApi", + "section": "def-common.IndexSettings", + "text": "IndexSettings" + } + ], + "path": "x-pack/plugins/index_management/common/types/indices.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.IndexSettingsResponse.defaults", + "type": "Object", + "tags": [], + "label": "defaults", + "description": [], + "signature": [ + { + "pluginId": "indexManagement", + "scope": "common", + "docId": "kibIndexManagementPluginApi", + "section": "def-common.IndexSettings", + "text": "IndexSettings" + } + ], + "path": "x-pack/plugins/index_management/common/types/indices.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.IndexWithFields", + "type": "Interface", + "tags": [], + "label": "IndexWithFields", + "description": [], + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "indexManagement", + "id": "def-common.IndexWithFields.index", + "type": "string", + "tags": [], + "label": "index", + "description": [], + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.IndexWithFields.fields", + "type": "Array", + "tags": [], + "label": "fields", + "description": [], + "signature": [ + { + "pluginId": "indexManagement", + "scope": "common", + "docId": "kibIndexManagementPluginApi", + "section": "def-common.FieldItem", + "text": "FieldItem" + }, + "[]" + ], + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "indexManagement", "id": "def-common.LegacyTemplateSerialized", @@ -2236,6 +2353,20 @@ "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.SerializedEnrichPolicy.query", + "type": "Object", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "Record | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/enrich_policies.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 6b276602ed0c2..081c1d59d4166 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/platform-deployment-management](https://github.com/orgs/elasti | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 191 | 0 | 186 | 4 | +| 197 | 0 | 192 | 3 | ## Client diff --git a/api_docs/infra.devdocs.json b/api_docs/infra.devdocs.json index 96f62ca88f47d..2e4e16322dc43 100644 --- a/api_docs/infra.devdocs.json +++ b/api_docs/infra.devdocs.json @@ -651,67 +651,6 @@ "path": "x-pack/plugins/infra/server/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "infra", - "id": "def-server.InfraPluginStart.getMetricIndices", - "type": "Function", - "tags": [], - "label": "getMetricIndices", - "description": [], - "signature": [ - "(savedObjectsClient: ", - { - "pluginId": "@kbn/core-saved-objects-api-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-common.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - }, - ", sourceId?: string | undefined) => Promise" - ], - "path": "x-pack/plugins/infra/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "infra", - "id": "def-server.InfraPluginStart.getMetricIndices.$1", - "type": "Object", - "tags": [], - "label": "savedObjectsClient", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-saved-objects-api-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-common.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - } - ], - "path": "x-pack/plugins/infra/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "infra", - "id": "def-server.InfraPluginStart.getMetricIndices.$2", - "type": "string", - "tags": [], - "label": "sourceId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/infra/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] } ], "lifecycle": "start", diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 3d00b256aada5..b21154eacb7d3 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/inf | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 45 | 0 | 42 | 11 | +| 42 | 0 | 39 | 11 | ## Client diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index b91af00a7b1ba..a6429813c2f37 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 9aec795382afe..d98566094813d 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 8fbc51a7b68c6..9d276deed7672 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 105c0d2e386a3..9b3c238b76395 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 18fa1ec5ce347..3e61e5df8d3ca 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index b21f2bd757b77..091d6f9a18bbf 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 8a0c83193084f..b494dc066ec62 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 0caf1d8eca132..6d8af2e4cace5 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 1b1fb2313ddde..d365b2fb916de 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index b8a41b63456ac..4d52cf3d8feef 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.devdocs.json b/api_docs/kbn_analytics_client.devdocs.json index 08169d4866da6..adbb90ee01e6e 100644 --- a/api_docs/kbn_analytics_client.devdocs.json +++ b/api_docs/kbn_analytics_client.devdocs.json @@ -694,6 +694,14 @@ "plugin": "security", "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" }, + { + "plugin": "@kbn/subscription-tracking", + "path": "packages/kbn-subscription-tracking/src/use_go_to_subscription.ts" + }, + { + "plugin": "@kbn/subscription-tracking", + "path": "packages/kbn-subscription-tracking/src/use_impression.ts" + }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/types.ts" @@ -710,6 +718,10 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -750,6 +762,14 @@ "plugin": "infra", "path": "x-pack/plugins/infra/public/services/telemetry/telemetry_client.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/public/services/telemetry/telemetry_client.ts" + }, { "plugin": "globalSearchBar", "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" @@ -870,6 +890,22 @@ "plugin": "security", "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/public/services/telemetry/telemetry_service.test.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/services/telemetry/telemetry_service.test.ts" diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index aa2a2fabaa159..156403ede807e 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index a204b4f414b2e..2969bbf60297e 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 8763ae8c69da2..52d9cd5e12959 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index b58602fd604ae..d98027d00da10 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 613596463a6b9..c8ef91197fafc 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 8887898a4263f..b1ba6ce855866 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.devdocs.json b/api_docs/kbn_apm_config_loader.devdocs.json index aaf18f5aab82d..e3ea4aaf0583e 100644 --- a/api_docs/kbn_apm_config_loader.devdocs.json +++ b/api_docs/kbn_apm_config_loader.devdocs.json @@ -66,7 +66,7 @@ "label": "rawKibanaConfig", "description": [], "signature": [ - "Record" + "KibanaRawConfig" ], "path": "packages/kbn-apm-config-loader/src/config.ts", "deprecated": false, @@ -298,7 +298,7 @@ "section": "def-common.ObjectType", "text": "ObjectType" }, - "<{ active: ", + "; apiKey: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; environment: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, "; globalLabels: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.ObjectType", - "text": "ObjectType" + "section": "def-common.Type", + "text": "Type" + }, + " | undefined>; }, { servicesOverrides: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" }, - "<{}>; }>" + " | undefined; } & {}>> | undefined>; }>>" ], "path": "packages/kbn-apm-config-loader/src/apm_config.ts", "deprecated": false, diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 4f6e5089e8d6c..3ac2ca40aa99e 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 74728db7e4de8..2639beeaa43be 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 615bac13b1df8..325365f86ad74 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 77f98b81aa9a5..dc74fd90a2471 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index e0961accc4a26..9c3b7688dcde1 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 97c2a284e51dd..4b645ec6864c3 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index 88f075ca55107..0b21ef1f375a7 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index f4bdbd8388c08..c6becbc673a6d 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 22f93b5c9d991..3c6e4b9422efd 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index e95d9d3ce05ea..3291a187efa84 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 0aede1cb69fb5..124e8f51c9c4e 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 6f74fac507084..239ebeb4dd0a1 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index b80687daef231..81c5b8e158a44 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.devdocs.json b/api_docs/kbn_code_editor.devdocs.json index 97a003dc517d0..eea9517c0bb23 100644 --- a/api_docs/kbn_code_editor.devdocs.json +++ b/api_docs/kbn_code_editor.devdocs.json @@ -27,7 +27,7 @@ "label": "CodeEditor", "description": [], "signature": [ - "({ languageId, value, onChange, width, options, overrideEditorWillMount, editorDidMount, editorWillMount, useDarkTheme: useDarkThemeProp, transparentBackground, suggestionProvider, signatureProvider, hoverProvider, placeholder, languageConfiguration, \"aria-label\": ariaLabel, isCopyable, allowFullScreen, }: React.PropsWithChildren<", + "({ languageId, value, onChange, width, height, options, overrideEditorWillMount, editorDidMount, editorWillMount, useDarkTheme: useDarkThemeProp, transparentBackground, suggestionProvider, signatureProvider, hoverProvider, placeholder, languageConfiguration, \"aria-label\": ariaLabel, isCopyable, allowFullScreen, }: React.PropsWithChildren<", "Props", ">) => JSX.Element" ], @@ -40,7 +40,7 @@ "id": "def-common.CodeEditor.$1", "type": "CompoundType", "tags": [], - "label": "{\n languageId,\n value,\n onChange,\n width,\n options,\n overrideEditorWillMount,\n editorDidMount,\n editorWillMount,\n useDarkTheme: useDarkThemeProp,\n transparentBackground,\n suggestionProvider,\n signatureProvider,\n hoverProvider,\n placeholder,\n languageConfiguration,\n 'aria-label': ariaLabel = i18n.translate('sharedUXPackages.codeEditor.ariaLabel', {\n defaultMessage: 'Code Editor',\n }),\n isCopyable = false,\n allowFullScreen = false,\n}", + "label": "{\n languageId,\n value,\n onChange,\n width,\n height = '100px',\n options,\n overrideEditorWillMount,\n editorDidMount,\n editorWillMount,\n useDarkTheme: useDarkThemeProp,\n transparentBackground,\n suggestionProvider,\n signatureProvider,\n hoverProvider,\n placeholder,\n languageConfiguration,\n 'aria-label': ariaLabel = i18n.translate('sharedUXPackages.codeEditor.ariaLabel', {\n defaultMessage: 'Code Editor',\n }),\n isCopyable = false,\n allowFullScreen = false,\n}", "description": [], "signature": [ "React.PropsWithChildren<", diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index a5f2b8dfd5677..7ea1e57fe6c11 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mocks.mdx b/api_docs/kbn_code_editor_mocks.mdx index e060e72cbadbe..0a3c3e632c656 100644 --- a/api_docs/kbn_code_editor_mocks.mdx +++ b/api_docs/kbn_code_editor_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mocks title: "@kbn/code-editor-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mocks'] --- import kbnCodeEditorMocksObj from './kbn_code_editor_mocks.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index ac12509b720ef..b89f56f3dac0f 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 7c1aebb5abce3..110bb966caa63 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.devdocs.json b/api_docs/kbn_config_mocks.devdocs.json index df37ecf488422..29e17d6500043 100644 --- a/api_docs/kbn_config_mocks.devdocs.json +++ b/api_docs/kbn_config_mocks.devdocs.json @@ -344,7 +344,15 @@ "section": "def-common.ChangedDeprecatedPaths", "text": "ChangedDeprecatedPaths" }, - ">, [], unknown>; } & ", + ">, [], unknown>; addDynamicConfigPaths: jest.MockInstance; setDynamicConfigOverrides: jest.MockInstance], unknown>; } & ", "IConfigService" ], "path": "packages/kbn-config-mocks/src/config_service.mock.ts", diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 032ae8e192c8b..b11890375d8a7 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.devdocs.json b/api_docs/kbn_config_schema.devdocs.json index 4d82048910c62..aa5023e4e9fcd 100644 --- a/api_docs/kbn_config_schema.devdocs.json +++ b/api_docs/kbn_config_schema.devdocs.json @@ -1878,7 +1878,105 @@ "section": "def-common.ObjectType", "text": "ObjectType" }, - "

; oneOf: { (types: [", + "

; oneOf: { (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "TypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -3413,7 +3511,105 @@ "label": "oneOf", "description": [], "signature": [ - "{ (types: [", + "{ (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "TypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 80559094e37c7..018398a19316f 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 80646894e11be..651d5dd33d006 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index e014004687bea..188215d1461fb 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index 6911bd6b6f547..170c63b1056de 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index dc335d5b2f612..a8649213f653b 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index b92cfb8c94f61..31ee048e8ad4c 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 5b5a59d7fe33f..1f7f128caf7c2 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 03899ac72f8cd..da93f6601d7d9 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 95b287d79854d..3a40ee00c7ccb 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 4de67f669be7b..6b875c00ae899 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 432fa1859b2ed..22482bfb0a5b1 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 314ad5565f41b..d36d46b053d29 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 863a037578cdb..d850a197d06f6 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 326f2c569f43d..14664ced215e9 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 1e489202736d7..307ff5c67f8e6 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 2c6795d65ccb0..17a8f43988a85 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index d29359cd4e1e7..3f78c95a4e005 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index c1c891ba95967..72aed8c8f0651 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.devdocs.json b/api_docs/kbn_core_apps_server_internal.devdocs.json index 56bb924457859..db65fdba1b5be 100644 --- a/api_docs/kbn_core_apps_server_internal.devdocs.json +++ b/api_docs/kbn_core_apps_server_internal.devdocs.json @@ -159,7 +159,23 @@ ], "interfaces": [], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-common.CoreAppConfigType", + "type": "Type", + "tags": [], + "label": "CoreAppConfigType", + "description": [], + "signature": [ + "{ readonly allowDynamicConfigOverrides: boolean; }" + ], + "path": "packages/core/apps/core-apps-server-internal/src/core_app_config.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index bf3f6967f2fae..9d048033a780a 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 8 | 0 | 8 | 1 | +| 9 | 0 | 9 | 1 | ## Common ### Functions +### Consts, variables and types + + diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 0296a7724e765..aeeba1a1c9f34 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 33f58dd0c3db7..13a5ef34d8b98 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 0afb136cb62c1..174f701127b3d 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 1754969d7445a..fbe3aead38f84 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 48d6df951b008..29dcf600a69b4 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index cefeb629c97a6..23da9dab31b70 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index ba7860603a43e..582ec194a91b8 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 0911b94ff466f..0dca85b0bb485 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index fd514328834c6..599427fab304a 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index f09f0ac46fc25..0db7b77f53e74 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 16839802bab6b..e21f08859d821 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index d636b64c37886..bb061f13a9d71 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index b7c0671187144..36d2048466be4 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index b31918997f56f..a41e13ca6562d 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index cd27cf947158d..34c35ab26a6de 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index ea4b52556d5cd..7a4385be82d90 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index d1f6d4884d42b..c604e8d061b71 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index caf61d08c5363..0adaa6f44a26e 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index c16097aaeb41b..f8168a6a7768c 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 3075ffb3a73ba..b516f6b7a6131 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 45340317183d8..afc0b1dddea91 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index d47261b041362..45b615479b210 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index cc64203043134..dad121aafb08a 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 8b038d4ec8e98..5fe6916db709e 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index ecb3d8932a4c5..5dafef2bb5fd4 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index d7f7cbe6ba47d..28b76c2947789 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index a55d59f4f90d2..303adb43b6bba 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 28f48c684b6e8..bab6031bd0b77 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index df8832912ed71..e92ac5997e51f 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index f0068604005e7..e1bbf15fdba98 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 56daa8bc04540..75589cfe131b6 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 0f12384bf42ab..02d14a42b45ea 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 98bacfc32ff4c..9397293d34512 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 262819d2c0b34..0c90b232da64e 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 48503519ba04f..3ca02a1ec246f 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 10fb7bdc62488..c458aaddb64f9 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 60a1f0befb409..27b72ceac4a6f 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index b08d9b2f532a6..b90445d1ef01e 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 83c868bc58036..b3ea65c3f7e36 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index ebefef5e85ec6..24741aeec70b2 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index bc56d11afbc7b..6c7402ede833b 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 51ff78a19b774..4d0be0440fcda 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 99c8dd424f267..ef89bd2eee7f8 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 29eb7e26c39c1..78f66331c1007 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 8a6ba161cbc87..cade525b23559 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 16cad5d8554c0..962db9aefdfe0 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 860003d098949..95191d15e3661 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 2f2fc6f5434cd..450b2834562c6 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 0e8b339d206b9..3ad961561fd4d 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index d93fac3815646..76998d550273d 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index b82dc4b068775..5d0cb49e62980 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 5ad567d58a5f1..46468d4b6d626 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index e2865a641f48a..2c5a4141cdb6c 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index 8de5d103bde3f..cf867f3206181 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 0d9d25553b9a7..014ba8ff39842 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 0476ea5bd8173..6e143cb460052 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 8cb9f0a4277e8..795ccc192ec7d 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -3503,10 +3503,6 @@ "plugin": "share", "path": "src/plugins/share/server/url_service/http/short_urls/register_resolve_route.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/legacy/aggregate.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/legacy/find.ts" @@ -3535,10 +3531,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/get_rule.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/aggregate_rules.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/find_rules.ts" @@ -3591,6 +3583,10 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/get_rule_tags.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts" @@ -3623,62 +3619,6 @@ "plugin": "ruleRegistry", "path": "x-pack/plugins/rule_registry/server/routes/get_aad_fields_by_rule_type.ts" }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/find_exception_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_item/find_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_index/find_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/internal/find_lists_by_size_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/read_exception_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list/read_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_privileges/read_list_privileges_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/summary_exception_list_route.ts" - }, { "plugin": "savedObjectsTagging", "path": "x-pack/plugins/saved_objects_tagging/server/routes/tags/get_all_tags.ts" @@ -3699,10 +3639,6 @@ "plugin": "savedObjectsTagging", "path": "x-pack/plugins/saved_objects_tagging/server/routes/internal/find_tags.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, { "plugin": "guidedOnboarding", "path": "src/plugins/guided_onboarding/server/routes/guide_state_routes.ts" @@ -3715,26 +3651,6 @@ "plugin": "guidedOnboarding", "path": "src/plugins/guided_onboarding/server/routes/config_routes.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts" @@ -3751,14 +3667,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts" @@ -3771,10 +3679,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts" @@ -3791,14 +3695,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/server/routes/config.ts" }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/routes/fields.ts" - }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" - }, { "plugin": "profiling", "path": "x-pack/plugins/profiling/server/routes/flamechart.ts" @@ -3995,6 +3891,10 @@ "plugin": "ecsDataQualityDashboard", "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_status.ts" + }, { "plugin": "globalSearch", "path": "x-pack/plugins/global_search/server/routes/get_searchable_types.ts" @@ -4167,6 +4067,10 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts" }, + { + "plugin": "enterpriseSearch", + "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts" + }, { "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts" @@ -4407,6 +4311,14 @@ "plugin": "indexLifecycleManagement", "path": "x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_repositories/register_fetch_route.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/routes/fields.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "ingestPipelines", "path": "x-pack/plugins/ingest_pipelines/server/routes/api/get.ts" @@ -4823,206 +4735,6 @@ "plugin": "@kbn/core-http-server-internal", "path": "packages/core/http/core-http-server-internal/src/http_server.test.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/types.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/data_streams/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/setup/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/output/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/output/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/settings/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/app/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/download_source/index.tsx" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/download_source/index.tsx" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_server_hosts/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_server_hosts/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_proxies/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_proxies/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/uninstall_token/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/uninstall_token/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.test.ts" - }, { "plugin": "@kbn/core-http-router-server-mocks", "path": "packages/core/http/core-http-router-server-mocks/src/router.mock.ts" @@ -5039,22 +4751,6 @@ "plugin": "actions", "path": "x-pack/plugins/actions/server/routes/get.test.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/find_rules.test.ts" @@ -5375,26 +5071,6 @@ "plugin": "actions", "path": "x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/legacy/find.test.ts" @@ -5583,18 +5259,6 @@ "plugin": "cloudChat", "path": "x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.test.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts" - }, { "plugin": "remoteClusters", "path": "x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts" @@ -5667,6 +5331,10 @@ "plugin": "spaces", "path": "x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.test.ts" + }, { "plugin": "monitoringCollection", "path": "x-pack/plugins/monitoring_collection/server/routes/api/v1/dynamic_route/get_metrics_by_type.test.ts" @@ -5783,14 +5451,6 @@ "plugin": "indexManagement", "path": "x-pack/plugins/index_management/server/test/helpers/router_mock.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.test.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.test.ts" - }, { "plugin": "security", "path": "x-pack/plugins/security/server/routes/anonymous_access/get_capabilities.test.ts" @@ -6363,7 +6023,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/aggregate_rules.ts" + "path": "x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.ts" }, { "plugin": "alerting", @@ -6470,128 +6130,36 @@ "path": "x-pack/plugins/rule_registry/server/routes/get_alert_summary.ts" }, { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/create_exception_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_item/create_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list/create_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/duplicate_exception_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/export_exception_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_index/export_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/internal/create_exception_filter_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/import_exceptions_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list/import_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/internal/create_exceptions_list_route.ts" - }, - { - "plugin": "savedObjectsTagging", - "path": "x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts" - }, - { - "plugin": "savedObjectsTagging", - "path": "x-pack/plugins/saved_objects_tagging/server/routes/tags/update_tag.ts" - }, - { - "plugin": "savedObjectsTagging", - "path": "x-pack/plugins/saved_objects_tagging/server/routes/assignments/update_tags_assignments.ts" - }, - { - "plugin": "savedObjectsTagging", - "path": "x-pack/plugins/saved_objects_tagging/server/routes/internal/bulk_delete.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/routes/job_service.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts" + "plugin": "savedObjectsTagging", + "path": "x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts" }, { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts" + "plugin": "savedObjectsTagging", + "path": "x-pack/plugins/saved_objects_tagging/server/routes/tags/update_tag.ts" }, { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts" + "plugin": "savedObjectsTagging", + "path": "x-pack/plugins/saved_objects_tagging/server/routes/assignments/update_tags_assignments.ts" }, { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts" + "plugin": "savedObjectsTagging", + "path": "x-pack/plugins/saved_objects_tagging/server/routes/internal/bulk_delete.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts" }, { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts" + "plugin": "ml", + "path": "x-pack/plugins/ml/server/routes/job_service.ts" }, { "plugin": "securitySolution", @@ -6625,18 +6193,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/exceptions/api/manage_exceptions/route.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/dashboards/routes/get_dashboards_by_tags.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.ts" @@ -6673,14 +6229,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts" }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/routes/vis.ts" - }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" - }, { "plugin": "profiling", "path": "x-pack/plugins/profiling/server/routes/setup.ts" @@ -6769,6 +6317,18 @@ "plugin": "indexManagement", "path": "x-pack/plugins/index_management/server/routes/api/component_templates/register_create_route.ts" }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/server/routes/api/enrich_policies/register_create_route.ts" + }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/server/routes/api/enrich_policies/register_create_route.ts" + }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/server/routes/api/enrich_policies/register_create_route.ts" + }, { "plugin": "remoteClusters", "path": "x-pack/plugins/remote_clusters/server/routes/api/add_route.ts" @@ -6797,6 +6357,10 @@ "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/post_knowledge_base.ts" + }, { "plugin": "globalSearch", "path": "x-pack/plugins/global_search/server/routes/find.ts" @@ -7149,6 +6713,14 @@ "plugin": "indexLifecycleManagement", "path": "x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts" }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/routes/vis.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "ingestPipelines", "path": "x-pack/plugins/ingest_pipelines/server/routes/api/create.ts" @@ -7473,186 +7045,6 @@ "plugin": "@kbn/core-http-server-internal", "path": "packages/core/http/core-http-server-internal/src/http_server.test.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/types.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/setup/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/setup/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/output/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/output/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/app/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/app/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/preconfiguration/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/preconfiguration/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/download_source/index.tsx" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/health_check/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_server_hosts/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_proxies/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/message_signing_service/index.ts" - }, { "plugin": "@kbn/core-http-router-server-mocks", "path": "packages/core/http/core-http-router-server-mocks/src/router.mock.ts" @@ -8141,6 +7533,22 @@ "plugin": "spaces", "path": "x-pack/plugins/spaces/server/routes/api/external/update_objects_spaces.test.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts" @@ -8543,30 +7951,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/update_rule.ts" }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/update_exception_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_item/update_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list/update_list_route.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, { "plugin": "guidedOnboarding", "path": "src/plugins/guided_onboarding/server/routes/plugin_state_routes.ts" @@ -8579,18 +7963,14 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/tags/routes/create_tag.ts" - }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" - }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/routes/internal/deprecations/deprecations.ts" }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/server/routes/api/indices/register_create_route.ts" + }, { "plugin": "indexManagement", "path": "x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts" @@ -8603,6 +7983,10 @@ "plugin": "indexManagement", "path": "x-pack/plugins/index_management/server/routes/api/component_templates/register_update_route.ts" }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/server/routes/api/enrich_policies/register_execute_route.ts" + }, { "plugin": "remoteClusters", "path": "x-pack/plugins/remote_clusters/server/routes/api/update_route.ts" @@ -8795,6 +8179,10 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "ingestPipelines", "path": "x-pack/plugins/ingest_pipelines/server/routes/api/update.ts" @@ -8895,58 +8283,6 @@ "plugin": "@kbn/core-http-server-internal", "path": "packages/core/http/core-http-server-internal/src/http_server.test.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/types.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/output/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/settings/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/download_source/index.tsx" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_server_hosts/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_proxies/index.ts" - }, { "plugin": "@kbn/core-http-router-server-mocks", "path": "packages/core/http/core-http-router-server-mocks/src/router.mock.ts" @@ -9071,10 +8407,6 @@ "plugin": "cases", "path": "x-pack/plugins/cases/server/routes/api/register_routes.test.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts" - }, { "plugin": "security", "path": "x-pack/plugins/security/server/routes/api_keys/update.test.ts" @@ -9285,18 +8617,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/bulk_disable_rules.ts" }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_item/patch_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list/patch_list_route.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts" @@ -9313,10 +8633,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts" }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" - }, { "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/workplace_search/security.ts" @@ -9329,6 +8645,10 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "@kbn/core-http-router-server-internal", "path": "packages/core/http/core-http-router-server-internal/src/router.ts" @@ -9341,14 +8661,6 @@ "plugin": "@kbn/core-http-server-internal", "path": "packages/core/http/core-http-server-internal/src/http_server.test.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/types.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, { "plugin": "@kbn/core-http-router-server-mocks", "path": "packages/core/http/core-http-router-server-mocks/src/router.mock.ts" @@ -9663,46 +8975,10 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts" }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/delete_exception_list_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_index/delete_list_index_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list_item/delete_list_item_route.ts" - }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/routes/list/delete_list_route.ts" - }, { "plugin": "savedObjectsTagging", "path": "x-pack/plugins/saved_objects_tagging/server/routes/tags/delete_tag.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts" @@ -9719,10 +8995,6 @@ "plugin": "observability", "path": "x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts" }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" - }, { "plugin": "assetManager", "path": "x-pack/plugins/asset_manager/server/routes/sample_assets.ts" @@ -9739,6 +9011,10 @@ "plugin": "indexManagement", "path": "x-pack/plugins/index_management/server/routes/api/component_templates/register_delete_route.ts" }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/server/routes/api/enrich_policies/register_delete_route.ts" + }, { "plugin": "remoteClusters", "path": "x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts" @@ -9747,6 +9023,10 @@ "plugin": "crossClusterReplication", "path": "x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/delete_knowledge_base.ts" + }, { "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler_extraction_rules.ts" @@ -9875,6 +9155,10 @@ "plugin": "indexLifecycleManagement", "path": "x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "ingestPipelines", "path": "x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts" @@ -9939,54 +9223,6 @@ "plugin": "@kbn/core-http-server-internal", "path": "packages/core/http/core-http-server-internal/src/http_server.test.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/types.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/output/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/download_source/index.tsx" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_server_hosts/index.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/fleet_proxies/index.ts" - }, { "plugin": "@kbn/core-http-router-server-mocks", "path": "packages/core/http/core-http-router-server-mocks/src/router.mock.ts" @@ -13745,6 +12981,62 @@ "plugin": "data", "path": "src/plugins/data/server/scripts/route.ts" }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/find_exception_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_item/find_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_index/find_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/internal/find_lists_by_size_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/read_exception_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list/read_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_privileges/read_list_privileges_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/summary_exception_list_route.ts" + }, { "plugin": "telemetry", "path": "src/plugins/telemetry/server/routes/telemetry_config.ts" @@ -13757,6 +13049,10 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/server/routes/telemetry_last_reported.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" + }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/json_schema.ts" @@ -14077,6 +13373,38 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts" @@ -14126,16 +13454,12 @@ "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts" }, { - "plugin": "@kbn/core-http-router-server-mocks", - "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts" - }, - { - "plugin": "logsShared", - "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts" }, { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + "plugin": "@kbn/core-http-router-server-mocks", + "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts" }, { "plugin": "canvas", @@ -14197,10 +13521,18 @@ "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts" }, + { + "plugin": "logsShared", + "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "fileUpload", "path": "x-pack/plugins/file_upload/server/routes.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "kubernetesSecurity", "path": "x-pack/plugins/kubernetes_security/server/routes/aggregate.ts" @@ -14257,10 +13589,6 @@ "plugin": "sessionView", "path": "x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts" }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/server/routes/api/privileges.ts" - }, { "plugin": "transform", "path": "x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts" @@ -14481,6 +13809,10 @@ "deprecated": false, "trackAdoption": true, "references": [ + { + "plugin": "@kbn/core-apps-server-internal", + "path": "packages/core/apps/core-apps-server-internal/src/core_app.ts" + }, { "plugin": "dataViews", "path": "src/plugins/data_views/server/rest_api_routes/public/runtime_fields/put_runtime_field.ts" @@ -14505,6 +13837,26 @@ "plugin": "data", "path": "src/plugins/data/server/query/routes.ts" }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/update_exception_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_item/update_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list/update_list_route.ts" + }, { "plugin": "telemetry", "path": "src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts" @@ -14513,6 +13865,10 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/server/routes/telemetry_last_reported.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts" @@ -14574,16 +13930,12 @@ "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts" }, { - "plugin": "@kbn/core-http-router-server-mocks", - "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts" - }, - { - "plugin": "logsShared", - "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/tags/routes/create_tag.ts" }, { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + "plugin": "@kbn/core-http-router-server-mocks", + "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts" }, { "plugin": "canvas", @@ -14601,6 +13953,14 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/update.ts" }, + { + "plugin": "logsShared", + "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/server/routes/api/transforms.ts" @@ -14620,6 +13980,14 @@ { "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/update.test.ts" + }, + { + "plugin": "@kbn/core-apps-server-internal", + "path": "packages/core/apps/core-apps-server-internal/src/core_app.test.ts" + }, + { + "plugin": "@kbn/core-apps-server-internal", + "path": "packages/core/apps/core-apps-server-internal/src/core_app.test.ts" } ], "returnComment": [], @@ -14773,6 +14141,62 @@ "plugin": "unifiedSearch", "path": "src/plugins/unified_search/server/autocomplete/value_suggestions_route.ts" }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/create_exception_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_item/create_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list/create_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/duplicate_exception_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/export_exception_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_index/export_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/internal/create_exception_filter_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/import_exceptions_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list/import_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/internal/create_exceptions_list_route.ts" + }, { "plugin": "telemetry", "path": "src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts" @@ -14785,6 +14209,10 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/server/routes/telemetry_usage_stats.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" + }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/annotations.ts" @@ -15073,6 +14501,10 @@ "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/system.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/routes/system.ts" + }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/alerting.ts" @@ -15125,6 +14557,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/setup/setup_health_route.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts" @@ -15153,6 +14589,46 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/exceptions/api/manage_exceptions/route.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/dashboards/routes/get_dashboards_by_tags.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/file_upload_handler.ts" @@ -15197,6 +14673,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts" + }, { "plugin": "@kbn/core-http-router-server-mocks", "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts" @@ -15209,14 +14689,6 @@ "plugin": "aiops", "path": "x-pack/plugins/aiops/server/routes/log_categorization.ts" }, - { - "plugin": "logsShared", - "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" - }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/custom_elements/create.ts" @@ -15233,6 +14705,10 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/import.ts" }, + { + "plugin": "logsShared", + "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "fileUpload", "path": "x-pack/plugins/file_upload/server/routes.ts" @@ -15249,6 +14725,10 @@ "plugin": "fileUpload", "path": "x-pack/plugins/file_upload/server/routes.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "maps", "path": "x-pack/plugins/maps/server/data_indexing/indexing_routes.ts" @@ -15417,6 +14897,18 @@ "deprecated": false, "trackAdoption": true, "references": [ + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_item/patch_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list/patch_list_route.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts" @@ -15537,6 +15029,34 @@ "plugin": "data", "path": "src/plugins/data/server/query/routes.ts" }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/delete_exception_list_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_index/delete_list_index_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list_item/delete_list_item_route.ts" + }, + { + "plugin": "lists", + "path": "x-pack/plugins/lists/server/routes/list/delete_list_route.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/security/fleet_router.ts" + }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/annotations.ts" @@ -15586,16 +15106,16 @@ "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts" }, { - "plugin": "@kbn/core-http-router-server-mocks", - "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts" }, { - "plugin": "logsShared", - "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts" }, { - "plugin": "infra", - "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + "plugin": "@kbn/core-http-router-server-mocks", + "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts" }, { "plugin": "canvas", @@ -15605,6 +15125,14 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/delete.ts" }, + { + "plugin": "logsShared", + "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts" + }, { "plugin": "maps", "path": "x-pack/plugins/maps/server/data_indexing/indexing_routes.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 1f93e3e31dae8..5beea5a93f3e7 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index a25b7c1a45c96..154222054352d 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 16437f6ff7ab3..1881c9974b76b 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 6d5adb43c3e95..e41a6a77f475f 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index e551227387d36..d844e581a2567 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index c893a0fd60007..ea024a1b9ccdb 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index ffc9151f43217..da1c0844d9f08 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index e60c05adfe04d..35cb96086d842 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index c3e0bf21e7c64..d832292d805f1 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 3e9736316992d..2e45bc70f2d04 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index ee7dd5bdf90c8..5a59bcfa28cf6 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 62d335e01c3ad..918e760c1e253 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 0518e9ee72ea5..2ffd2869cab6e 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index fdd8ff8fd822a..2216ce5e3f1c2 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index 935901a3e518a..5114b49da30be 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index a8840dd3613b3..9e6959c933574 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 213cedd70d322..44060d7d9dd50 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index ab94b922fe184..707b4faa949a1 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 36f6e7afb4b59..e74328ee67f08 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 8fb6369169096..ad94fb25fa4a9 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index a5eef520ee6ac..aa692b88141b4 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index aa5c116bc2edc..dd9f6eba201c9 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index a3277ae7a65a6..6063376d0cf19 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 6cee5807ef013..52a57bce3dce1 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index a8ea1a47da543..1716908c46c85 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index bb4195a8fe37b..97fa48a69fec5 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 88bae9bd3ea60..1e4e096e62ab6 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index aadcf67bae283..d66994b505cfe 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index e0c4fda40518a..aa5f7671bd5c3 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 1331d04ffc85c..0bb9380d89c88 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 63a4ae4dac5ef..47ae15cffa2b9 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index fff8c97f6e654..9e5ad68909f40 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index ca261ae0ea913..f51bf612b720a 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index d636320c8f81c..1c7ffef6b9f11 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 9c07d5c90c07d..ef1080b29e204 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 5097c74135732..cf4df31fb236a 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 1ab1150ff7861..4cc3b5e6540ee 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.devdocs.json b/api_docs/kbn_core_plugins_server.devdocs.json index 98b1596549758..1d895c5714572 100644 --- a/api_docs/kbn_core_plugins_server.devdocs.json +++ b/api_docs/kbn_core_plugins_server.devdocs.json @@ -436,6 +436,29 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-common.PluginConfigDescriptor.dynamicConfig", + "type": "Object", + "tags": [], + "label": "dynamicConfig", + "description": [ + "\nList of configuration properties that can be dynamically changed via the PUT /_settings API." + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "common", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-common.DynamicConfigDescriptor", + "text": "DynamicConfigDescriptor" + }, + " | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-plugins-server", "id": "def-common.PluginConfigDescriptor.schema", @@ -1050,6 +1073,31 @@ ], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-common.DynamicConfigDescriptor", + "type": "Type", + "tags": [], + "label": "DynamicConfigDescriptor", + "description": [ + "\nType defining the list of configuration properties that can be dynamically updated\nObject properties can either be fully exposed or narrowed down to specific keys.\n" + ], + "signature": [ + "{ [Key in keyof T]?: (T[Key] extends Maybe ? boolean : T[Key] extends Maybe ? boolean | ", + { + "pluginId": "@kbn/core-plugins-server", + "scope": "common", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-common.DynamicConfigDescriptor", + "text": "DynamicConfigDescriptor" + }, + " : boolean) | undefined; }" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-plugins-server", "id": "def-common.ExposedToBrowserDescriptor", @@ -1057,7 +1105,7 @@ "tags": [], "label": "ExposedToBrowserDescriptor", "description": [ - "\nType defining the list of configuration properties that will be exposed on the client-side\nObject properties can either be fully exposed\n" + "\nType defining the list of configuration properties that will be exposed on the client-side\nObject properties can either be fully exposed or narrowed down to specific keys.\n" ], "signature": [ "{ [Key in keyof T]?: (T[Key] extends Maybe ? boolean : T[Key] extends Maybe ? boolean | ", diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index ac0c38450ebcb..da6ab9479d8b3 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 58 | 0 | 26 | 0 | +| 60 | 0 | 26 | 0 | ## Common diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index bc5b5c82f43a1..ec94544400531 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 172452e6bc966..0da6f7e1ad241 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index d808516ce314b..2a50b16519f9b 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index f8eff817f64b9..337926465062a 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 8bbe2bc8dfb49..cf9585f9be816 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 53b3a6fc40e71..51dc1d6844ccf 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 85ebcb415549a..33d7aa0dec22d 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index 94d374afbba24..a1ce0b5950132 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -3623,7 +3623,7 @@ "signature": [ "{ type: string | string[]; search?: string | undefined; page?: number | undefined; filter?: any; aggs?: Record | undefined; namespaces?: string[] | undefined; perPage?: number | undefined; fields?: string[] | undefined; sortField?: string | undefined; preference?: string | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; searchFields?: string[] | undefined; hasReference?: ", + "> | undefined; namespaces?: string[] | undefined; perPage?: number | undefined; fields?: string[] | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; searchFields?: string[] | undefined; hasReference?: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", @@ -3639,7 +3639,7 @@ "section": "def-common.SavedObjectsFindOptionsReference", "text": "SavedObjectsFindOptionsReference" }, - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + "[] | undefined; sortField?: string | undefined; preference?: string | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 414f01e0491a0..7914ac70accca 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index e529c9e80e178..dbbf19f7fb5e7 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -7942,7 +7942,7 @@ "tags": [], "label": "version", "description": [ - "\nAn opaque version number which changes on each successful write operation.\nCan be used for implementing optimistic concurrency control." + "\nAn opaque version number which changes on each successful write operation.\nCan be used for implementing optimistic concurrency control.\nUnused for multi-namespace objects" ], "signature": [ "string | undefined" @@ -8028,6 +8028,22 @@ "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-common.SavedObjectsUpdateOptions.migrationVersionCompatibility", + "type": "CompoundType", + "tags": [], + "label": "migrationVersionCompatibility", + "description": [ + "{@link SavedObjectsRawDocParseOptions.migrationVersionCompatibility}" + ], + "signature": [ + "\"raw\" | \"compatible\" | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -8217,9 +8233,7 @@ "signature": [ "{ type: string | string[]; search?: string | undefined; filter?: any; aggs?: Record | undefined; namespaces?: string[] | undefined; perPage?: number | undefined; fields?: string[] | undefined; sortField?: string | undefined; preference?: string | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; searchFields?: string[] | undefined; sortOrder?: ", - "SortOrder", - " | undefined; rootSearchFields?: string[] | undefined; hasReference?: ", + "> | undefined; namespaces?: string[] | undefined; perPage?: number | undefined; fields?: string[] | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; searchFields?: string[] | undefined; hasReference?: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", @@ -8235,7 +8249,9 @@ "section": "def-common.SavedObjectsFindOptionsReference", "text": "SavedObjectsFindOptionsReference" }, - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + "[] | undefined; sortField?: string | undefined; preference?: string | undefined; sortOrder?: ", + "SortOrder", + " | undefined; rootSearchFields?: string[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 8a62fdb8fbc93..45906beb5babe 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 351 | 1 | 5 | 1 | +| 352 | 1 | 5 | 1 | ## Common diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 1d7e23551fac5..773d422ce96b1 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 56c7cc8f46ee4..de98cfc8c9ad0 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 62f4f84327978..bf4036ad176e3 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 4d11b87d5750b..5349d968d8ab8 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index b280e87a2762a..3ca08594019cd 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 095d809abc97b..79e9d8f711f45 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 5691658f02493..7e9bb86fc7c1f 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 25e6e89e0a395..91e9026d832e9 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 67ca7bc41f1af..37e220eedf12f 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json index 4788fa89a6be4..4706f5a6e2e38 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json @@ -843,6 +843,8 @@ "IndexNotGreenTimeout", " | ", "ClusterShardLimitExceeded", + " | ", + "OperationNotSupported", ", ", "AcknowledgeResponse", ">" diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index c8425be055fba..acfc8db19c4d9 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 125 | 0 | 91 | 46 | +| 125 | 0 | 91 | 47 | ## Common diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index fc4b8aeb807ff..9ac6f8f0bcf1f 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.devdocs.json b/api_docs/kbn_core_saved_objects_server.devdocs.json index 097e6c4edbe8c..f35b9a7a8e32b 100644 --- a/api_docs/kbn_core_saved_objects_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server.devdocs.json @@ -8647,7 +8647,9 @@ "type": "Interface", "tags": [], "label": "SavedObjectsRawDocSource", - "description": [], + "description": [ + "\nSaved object document as stored in `_source` of doc in ES index\nSimilar to SavedObjectDoc and excludes `version`, includes `references`, has `attributes` in [typeMapping]\n" + ], "path": "packages/core/saved-objects/core-saved-objects-server/src/serialization.ts", "deprecated": false, "trackAdoption": false, @@ -9619,52 +9621,6 @@ "trackAdoption": false, "children": [], "returnComment": [] - }, - { - "parentPluginId": "@kbn/core-saved-objects-server", - "id": "def-common.SavedObjectsServiceSetup.getAllIndices", - "type": "Function", - "tags": [ - "deprecated" - ], - "label": "getAllIndices", - "description": [ - "\nReturns all (aliases to) kibana system indices used for saved object storage.\n" - ], - "signature": [ - "() => string[]" - ], - "path": "packages/core/saved-objects/core-saved-objects-server/src/contracts.ts", - "deprecated": true, - "trackAdoption": false, - "references": [ - { - "plugin": "@kbn/core-saved-objects-server-internal", - "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts" - }, - { - "plugin": "@kbn/core-plugins-server-internal", - "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" - }, - { - "plugin": "@kbn/core-plugins-server-internal", - "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" - }, - { - "plugin": "savedObjectsTagging", - "path": "x-pack/plugins/saved_objects_tagging/server/plugin.ts" - }, - { - "plugin": "@kbn/core-saved-objects-server-mocks", - "path": "packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts" - }, - { - "plugin": "@kbn/core-saved-objects-server-mocks", - "path": "packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts" - } - ], - "children": [], - "returnComment": [] } ], "initialIsOpen": false @@ -10225,7 +10181,7 @@ "tags": [], "label": "getAllIndices", "description": [ - "\nReturns all (aliases to) kibana system indices used for saved object storage." + "\nReturns all (aliases to) kibana system indices used for saved object storage.\n" ], "signature": [ "() => string[]" @@ -10559,22 +10515,6 @@ "plugin": "lists", "path": "x-pack/plugins/lists/server/saved_objects/exception_list.ts" }, - { - "plugin": "savedObjectsTagging", - "path": "x-pack/plugins/saved_objects_tagging/server/saved_objects/tag.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts" - }, { "plugin": "cases", "path": "x-pack/plugins/cases/server/saved_object_types/cases.ts" @@ -10595,13 +10535,25 @@ "plugin": "cases", "path": "x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts" }, + { + "plugin": "savedObjectsTagging", + "path": "x-pack/plugins/saved_objects_tagging/server/saved_objects/tag.ts" + }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts" + "path": "x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts" }, { - "plugin": "visualizations", - "path": "src/plugins/visualizations/server/saved_objects/visualization.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts" }, { "plugin": "savedSearch", @@ -10619,6 +10571,10 @@ "plugin": "graph", "path": "x-pack/plugins/graph/server/saved_objects/graph_workspace.ts" }, + { + "plugin": "visualizations", + "path": "src/plugins/visualizations/server/saved_objects/visualization.ts" + }, { "plugin": "maps", "path": "x-pack/plugins/maps/server/saved_objects/setup_saved_objects.ts" @@ -10699,6 +10655,10 @@ "plugin": "@kbn/core-saved-objects-migration-server-internal", "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts" }, + { + "plugin": "@kbn/core-saved-objects-migration-server-internal", + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts" + }, { "plugin": "@kbn/core-saved-objects-migration-server-internal", "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/validate_migration.test.ts" diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 1f7ba67f59898..2bd5338599b61 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 548 | 1 | 122 | 4 | +| 547 | 1 | 121 | 4 | ## Common diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 7c94b1cb500fd..e78ecc1dcb3e7 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index e05f09dc2e643..504b9ad866c7a 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index c807548374956..f3d3b4fdd1be3 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 3f321655d78ec..516256a8d61bb 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 85fb62b5d20bc..e56a7e442fb1f 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index d3f87243ac1e2..7c95af34cc346 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index e6f162205f20b..bc25b4033f907 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 640536d4e1bdc..5d1c1087237da 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 87375b22b27b1..813b12fc132e2 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index f0c020fcef23e..51c977031f28e 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index 62838b929c0cf..ab1270093e01c 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 432fac261568c..98b356fbcf496 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index 44fb03be51546..e4bb7d83d20e4 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index e7296a9cfbdc0..998660b1f4f2b 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 0be7fa9f6b433..fda04acaf9871 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.devdocs.json b/api_docs/kbn_core_ui_settings_browser.devdocs.json index f39807e781468..4804d0fac6dac 100644 --- a/api_docs/kbn_core_ui_settings_browser.devdocs.json +++ b/api_docs/kbn_core_ui_settings_browser.devdocs.json @@ -554,7 +554,15 @@ "section": "def-common.UiSettingsScope", "text": "UiSettingsScope" }, - " | undefined; order?: number | undefined; name?: string | undefined; description?: string | undefined; category?: string[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; sensitive?: boolean | undefined; deprecation?: ", + " | undefined; order?: number | undefined; name?: string | undefined; description?: string | undefined; category?: string[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; readonlyMode?: ", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.ReadonlyModeType", + "text": "ReadonlyModeType" + }, + " | undefined; sensitive?: boolean | undefined; deprecation?: ", { "pluginId": "@kbn/core-ui-settings-common", "scope": "common", diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 63d91c0a9cefb..cf042801d7fc8 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 5e7a69d5b3157..b1af74b72ebc1 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index a82e7e55774bb..a033d70bd927d 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.devdocs.json b/api_docs/kbn_core_ui_settings_common.devdocs.json index 252e14a65d219..16e4a9397621c 100644 --- a/api_docs/kbn_core_ui_settings_common.devdocs.json +++ b/api_docs/kbn_core_ui_settings_common.devdocs.json @@ -213,6 +213,29 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-ui-settings-common", + "id": "def-common.UiSettingsParams.readonlyMode", + "type": "CompoundType", + "tags": [], + "label": "readonlyMode", + "description": [ + "a flag indicating the level of restriction of the readonly settings {@link ReadonlyModeType}" + ], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.ReadonlyModeType", + "text": "ReadonlyModeType" + }, + " | undefined" + ], + "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-ui-settings-common", "id": "def-common.UiSettingsParams.sensitive", @@ -345,6 +368,10 @@ { "plugin": "discover", "path": "src/plugins/discover/server/ui_settings.ts" + }, + { + "plugin": "@kbn/management-settings-field-definition", + "path": "packages/kbn-management/settings/field_definition/get_definition.ts" } ] }, @@ -431,6 +458,23 @@ ], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/core-ui-settings-common", + "id": "def-common.ReadonlyModeType", + "type": "Type", + "tags": [], + "label": "ReadonlyModeType", + "description": [ + "\nType for the readonly mode of the readonly settings.\n'strict' indicates that the value cannot be changed through API and is not displayed in the UI\n'ui' indicates that the value is just not displayed in the UI" + ], + "signature": [ + "\"strict\" | \"ui\"" + ], + "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-ui-settings-common", "id": "def-common.TIMEZONE_OPTIONS", diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index ae6d41edcb787..74aedb1f37b50 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 25 | 0 | 4 | 0 | +| 27 | 0 | 4 | 0 | ## Common diff --git a/api_docs/kbn_core_ui_settings_server.devdocs.json b/api_docs/kbn_core_ui_settings_server.devdocs.json index e4d4c0c5eedd1..9db8a89dd3d1f 100644 --- a/api_docs/kbn_core_ui_settings_server.devdocs.json +++ b/api_docs/kbn_core_ui_settings_server.devdocs.json @@ -525,6 +525,40 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-ui-settings-server", + "id": "def-common.UiSettingsServiceSetup.setAllowlist", + "type": "Function", + "tags": [], + "label": "setAllowlist", + "description": [ + "\nSets an allowlist of setting keys." + ], + "signature": [ + "(keys: string[]) => void" + ], + "path": "packages/core/ui-settings/core-ui-settings-server/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-ui-settings-server", + "id": "def-common.UiSettingsServiceSetup.setAllowlist.$1", + "type": "Array", + "tags": [], + "label": "keys", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/core/ui-settings/core-ui-settings-server/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 2bf42e5f20f34..5e075150532a2 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 32 | 1 | 16 | 0 | +| 34 | 1 | 17 | 0 | ## Common diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index f52ad36f695c3..c0b247c9e7040 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 66d93cfe8bbac..b2268bd2181d1 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 14314e238f71c..b049e668c467e 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index c9d5341b8d164..dfa67ee3d81ec 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index cd78ab0cb7938..c9fd7c9205458 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 77eeffed9f266..3f0032236d018 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_internal.mdx b/api_docs/kbn_core_user_settings_server_internal.mdx index 41dcf57269f9c..88fba0044a9c4 100644 --- a/api_docs/kbn_core_user_settings_server_internal.mdx +++ b/api_docs/kbn_core_user_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-internal title: "@kbn/core-user-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-internal plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-internal'] --- import kbnCoreUserSettingsServerInternalObj from './kbn_core_user_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 8d28e4363c3ac..7d58c7ad18132 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 99e823bd22274..91f2086f61cde 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index ce70608a81a63..f4c1f12435a26 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.devdocs.json b/api_docs/kbn_custom_integrations.devdocs.json new file mode 100644 index 0000000000000..931ffe65e4a92 --- /dev/null +++ b/api_docs/kbn_custom_integrations.devdocs.json @@ -0,0 +1,404 @@ +{ + "id": "@kbn/custom-integrations", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.ConnectedCustomIntegrationsButton", + "type": "Function", + "tags": [], + "label": "ConnectedCustomIntegrationsButton", + "description": [], + "signature": [ + "({ isDisabled, onClick, testSubj, }: ConnectedCustomIntegrationsButtonProps) => JSX.Element | null" + ], + "path": "packages/kbn-custom-integrations/src/components/custom_integrations_button.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.ConnectedCustomIntegrationsButton.$1", + "type": "Object", + "tags": [], + "label": "{\n isDisabled,\n onClick,\n testSubj = 'customIntegrationsPackageConnectedButton',\n}", + "description": [], + "signature": [ + "ConnectedCustomIntegrationsButtonProps" + ], + "path": "packages/kbn-custom-integrations/src/components/custom_integrations_button.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.ConnectedCustomIntegrationsForm", + "type": "Function", + "tags": [], + "label": "ConnectedCustomIntegrationsForm", + "description": [], + "signature": [ + "({ testSubjects }: Props) => JSX.Element | null" + ], + "path": "packages/kbn-custom-integrations/src/components/custom_integrations_form.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.ConnectedCustomIntegrationsForm.$1", + "type": "Object", + "tags": [], + "label": "{ testSubjects }", + "description": [], + "signature": [ + "Props" + ], + "path": "packages/kbn-custom-integrations/src/components/custom_integrations_form.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.CustomIntegrationsProvider", + "type": "Function", + "tags": [], + "label": "CustomIntegrationsProvider", + "description": [], + "signature": [ + "React.FunctionComponent>" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.CustomIntegrationsProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P & { children?: React.ReactNode; }" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.CustomIntegrationsProvider.$2", + "type": "Any", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.useConsumerCustomIntegrations", + "type": "Function", + "tags": [], + "label": "useConsumerCustomIntegrations", + "description": [], + "signature": [ + "() => { mode: ", + "Mode", + "; dispatchableEvents: { saveCreateFields: (() => void) | undefined; updateCreateFields: ((fields: Partial<{ integrationName: string; datasets: { name: string; type: \"metrics\" | \"logs\"; }[]; }>) => void) | undefined; }; }" + ], + "path": "packages/kbn-custom-integrations/src/hooks/use_consumer_custom_integrations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.useCustomIntegrations", + "type": "Function", + "tags": [], + "label": "useCustomIntegrations", + "description": [], + "signature": [ + "() => { customIntegrationsState: ", + "State", + "<", + "WithSelectedMode", + ", ", + "CreateCustomIntegrationNotificationEvent", + ", any, ", + "CustomIntegrationsTypestate", + ", ", + "ResolveTypegenMeta", + "<", + "TypegenDisabled", + ", ", + "CreateCustomIntegrationNotificationEvent", + ", ", + "BaseActionObject", + ", ", + "ServiceMap", + ">>; customIntegrationsPageSend: (event: ", + "SingleOrArray", + "<", + "Event", + "<", + "CreateCustomIntegrationNotificationEvent", + ">> | ", + "SCXML", + ".Event<", + "CreateCustomIntegrationNotificationEvent", + ">, payload?: ", + "EventData", + " | undefined) => ", + "State", + "<", + "WithSelectedMode", + ", ", + "CreateCustomIntegrationNotificationEvent", + ", any, ", + "CustomIntegrationsTypestate", + ", ", + "ResolveTypegenMeta", + "<", + "TypegenDisabled", + ", ", + "CreateCustomIntegrationNotificationEvent", + ", ", + "BaseActionObject", + ", ", + "ServiceMap", + ">>; customIntegrationsStateService: ", + "Interpreter", + "<", + "WithSelectedMode", + ", any, ", + "CreateCustomIntegrationNotificationEvent", + ", ", + "CustomIntegrationsTypestate", + ", ", + "ResolveTypegenMeta", + "<", + "TypegenDisabled", + ", ", + "CreateCustomIntegrationNotificationEvent", + ", ", + "BaseActionObject", + ", ", + "ServiceMap", + ">>; }" + ], + "path": "packages/kbn-custom-integrations/src/hooks/use_custom_integrations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.Callbacks", + "type": "Interface", + "tags": [], + "label": "Callbacks", + "description": [], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.Callbacks.onIntegrationCreation", + "type": "Function", + "tags": [], + "label": "onIntegrationCreation", + "description": [], + "signature": [ + "((integrationOptions: { integrationName: string; datasets: { name: string; type: \"metrics\" | \"logs\"; }[]; }) => void) | undefined" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.Callbacks.onIntegrationCreation.$1", + "type": "Object", + "tags": [], + "label": "integrationOptions", + "description": [], + "signature": [ + "{ integrationName: string; datasets: { name: string; type: \"metrics\" | \"logs\"; }[]; }" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.Callbacks.onIntegrationCleanup", + "type": "Function", + "tags": [], + "label": "onIntegrationCleanup", + "description": [], + "signature": [ + "((integrationName: string) => void) | undefined" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.Callbacks.onIntegrationCleanup.$1", + "type": "string", + "tags": [], + "label": "integrationName", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.Callbacks.onIntegrationCleanupFailed", + "type": "Function", + "tags": [], + "label": "onIntegrationCleanupFailed", + "description": [], + "signature": [ + "((error: ", + "IntegrationError", + ") => void) | undefined" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.Callbacks.onIntegrationCleanupFailed.$1", + "type": "Object", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "IntegrationError" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/provider.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.CustomIntegrationOptions", + "type": "Type", + "tags": [], + "label": "CustomIntegrationOptions", + "description": [], + "signature": [ + "{ integrationName: string; datasets: { name: string; type: \"metrics\" | \"logs\"; }[]; }" + ], + "path": "packages/kbn-custom-integrations/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.DispatchableEvents", + "type": "Type", + "tags": [], + "label": "DispatchableEvents", + "description": [], + "signature": [ + "{ saveCreateFields: (() => void) | undefined; updateCreateFields: ((fields: Partial<{ integrationName: string; datasets: { name: string; type: \"metrics\" | \"logs\"; }[]; }>) => void) | undefined; }" + ], + "path": "packages/kbn-custom-integrations/src/hooks/use_consumer_custom_integrations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/custom-integrations", + "id": "def-common.InitialState", + "type": "Type", + "tags": [], + "label": "InitialState", + "description": [], + "signature": [ + "{ context?: Partial<", + "CreateInitialState", + "> | undefined; } & ", + "WithSelectedMode" + ], + "path": "packages/kbn-custom-integrations/src/state_machines/custom_integrations/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx new file mode 100644 index 0000000000000..2f38d6ee1c668 --- /dev/null +++ b/api_docs/kbn_custom_integrations.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCustomIntegrationsPluginApi +slug: /kibana-dev-docs/api/kbn-custom-integrations +title: "@kbn/custom-integrations" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/custom-integrations plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] +--- +import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; + + + +Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 19 | 0 | 17 | 6 | + +## Common + +### Functions + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 05220f2cc867e..eea9b0fc62c96 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 6a68a438d2845..cc31dafcb1b0c 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 78b0ec7217583..ab7fac244ecca 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index ec7e1cdc184d3..078f368a5a527 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index 88d315d9dc031..a8a20f807f994 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 1982a451203bf..300e08db4a252 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 9ce72754e73e3..9a86ab64ded6b 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.devdocs.json b/api_docs/kbn_deeplinks_observability.devdocs.json index dbcbc0da8d191..2f03e9787f455 100644 --- a/api_docs/kbn_deeplinks_observability.devdocs.json +++ b/api_docs/kbn_deeplinks_observability.devdocs.json @@ -19,9 +19,363 @@ "common": { "classes": [], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerLocatorParams", + "type": "Interface", + "tags": [], + "label": "LogExplorerLocatorParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerLocatorParams", + "text": "LogExplorerLocatorParams" + }, + " extends ", + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + } + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerLocatorParams.dataset", + "type": "string", + "tags": [], + "label": "dataset", + "description": [ + "\nDataset name to be selected." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerNavigationParams", + "type": "Interface", + "tags": [], + "label": "LogExplorerNavigationParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + }, + " extends ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.SerializableRecord", + "text": "SerializableRecord" + } + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerNavigationParams.timeRange", + "type": "Object", + "tags": [], + "label": "timeRange", + "description": [ + "\nOptionally set the time range in the time picker." + ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerNavigationParams.refreshInterval", + "type": "Object", + "tags": [], + "label": "refreshInterval", + "description": [ + "\nOptionally set the refresh interval." + ], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.RefreshInterval", + "text": "RefreshInterval" + }, + " | undefined" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerNavigationParams.query", + "type": "CompoundType", + "tags": [], + "label": "query", + "description": [ + "\nOptionally set a query." + ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + " | undefined" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerNavigationParams.columns", + "type": "Array", + "tags": [], + "label": "columns", + "description": [ + "\nColumns displayed in the table" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerNavigationParams.sort", + "type": "Array", + "tags": [], + "label": "sort", + "description": [ + "\nArray of the used sorting [[field,direction],...]" + ], + "signature": [ + "string[][] | undefined" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LogExplorerNavigationParams.filters", + "type": "Array", + "tags": [], + "label": "filters", + "description": [ + "\nOptionally apply filters." + ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[] | undefined" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.ObservabilityOnboardingLocatorParams", + "type": "Interface", + "tags": [], + "label": "ObservabilityOnboardingLocatorParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.ObservabilityOnboardingLocatorParams", + "text": "ObservabilityOnboardingLocatorParams" + }, + " extends ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.SerializableRecord", + "text": "SerializableRecord" + } + ], + "path": "packages/deeplinks/observability/locators/observability_onboarding.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.ObservabilityOnboardingLocatorParams.source", + "type": "CompoundType", + "tags": [], + "label": "source", + "description": [ + "If given, it will load the given map else will load the create a new map page." + ], + "signature": [ + "\"customLogs\" | \"systemLogs\" | undefined" + ], + "path": "packages/deeplinks/observability/locators/observability_onboarding.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.SingleDatasetLocatorParams", + "type": "Interface", + "tags": [], + "label": "SingleDatasetLocatorParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.SingleDatasetLocatorParams", + "text": "SingleDatasetLocatorParams" + }, + " extends ", + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + } + ], + "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.SingleDatasetLocatorParams.integration", + "type": "string", + "tags": [], + "label": "integration", + "description": [ + "\nIntegration name to be selected." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.SingleDatasetLocatorParams.dataset", + "type": "string", + "tags": [], + "label": "dataset", + "description": [ + "\nDataset name to be selected.\nex: system.syslog" + ], + "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.ALL_DATASETS_LOCATOR_ID", + "type": "string", + "tags": [], + "label": "ALL_DATASETS_LOCATOR_ID", + "description": [], + "signature": [ + "\"ALL_DATASETS_LOCATOR\"" + ], + "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.AllDatasetsLocatorParams", + "type": "Type", + "tags": [], + "label": "AllDatasetsLocatorParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + } + ], + "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/deeplinks-observability", "id": "def-common.AppId", @@ -30,13 +384,34 @@ "label": "AppId", "description": [], "signature": [ - "\"metrics\" | \"apm\" | \"logs\" | \"observability-overview\" | \"observabilityOnboarding\"" + "\"metrics\" | \"apm\" | \"logs\" | \"observability-overview\" | \"observability-log-explorer\" | \"observabilityOnboarding\"" ], "path": "packages/deeplinks/observability/deep_links.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.DatasetLocatorParams", + "type": "Type", + "tags": [], + "label": "DatasetLocatorParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + } + ], + "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/deeplinks-observability", "id": "def-common.DeepLinkId", @@ -52,13 +427,28 @@ "section": "def-common.AppId", "text": "AppId" }, - " | \"discover:log-explorer\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:rules\" | \"observability-overview:alerts\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"observability-overview:slos\" | \"metrics:settings\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:metrics-hosts\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:services\" | \"apm:service-groups-list\" | \"apm:storage-explorer\"" + " | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:rules\" | \"observability-overview:alerts\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"observability-overview:slos\" | \"metrics:settings\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:metrics-hosts\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:services\" | \"apm:service-groups-list\" | \"apm:storage-explorer\"" ], "path": "packages/deeplinks/observability/deep_links.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.LOG_EXPLORER_LOCATOR_ID", + "type": "string", + "tags": [], + "label": "LOG_EXPLORER_LOCATOR_ID", + "description": [], + "signature": [ + "\"LOG_EXPLORER_LOCATOR\"" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/deeplinks-observability", "id": "def-common.OBSERVABILITY_ONBOARDING_APP_ID", @@ -73,6 +463,51 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.OBSERVABILITY_ONBOARDING_LOCATOR", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_ONBOARDING_LOCATOR", + "description": [], + "signature": [ + "\"OBSERVABILITY_ONBOARDING_LOCATOR\"" + ], + "path": "packages/deeplinks/observability/locators/observability_onboarding.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.RefreshInterval", + "type": "Type", + "tags": [], + "label": "RefreshInterval", + "description": [], + "signature": [ + "{ pause: boolean; value: number; }" + ], + "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/deeplinks-observability", + "id": "def-common.SINGLE_DATASET_LOCATOR_ID", + "type": "string", + "tags": [], + "label": "SINGLE_DATASET_LOCATOR_ID", + "description": [], + "signature": [ + "\"SINGLE_DATASET_LOCATOR\"" + ], + "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index 1ddc62ebfbc15..c413e298b6af3 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for ques | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3 | 0 | 3 | 0 | +| 24 | 0 | 14 | 0 | ## Common +### Interfaces + + ### Consts, variables and types diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 3c8263637c076..e16639dcd8817 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index 55350a41e1323..7b983fae12bc0 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index bd59fed1ed260..3ab02b69008ac 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 6b869dd3da889..3a078fae077ae 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.devdocs.json b/api_docs/kbn_default_nav_ml.devdocs.json index 2b213eea008dc..1c99e5a71cbbd 100644 --- a/api_docs/kbn_default_nav_ml.devdocs.json +++ b/api_docs/kbn_default_nav_ml.devdocs.json @@ -172,7 +172,7 @@ "label": "children", "description": [], "signature": [ - "[{ title: string; id: \"root\"; children: [{ link: \"ml:overview\"; }, { link: \"ml:notifications\"; }]; }, { title: string; id: \"anomaly_detection\"; children: [{ title: string; link: \"ml:anomalyDetection\"; }, { link: \"ml:anomalyExplorer\"; }, { link: \"ml:singleMetricViewer\"; }, { link: \"ml:settings\"; }]; }, { id: \"data_frame_analytics\"; title: string; children: [{ title: string; link: \"ml:dataFrameAnalytics\"; }, { link: \"ml:resultExplorer\"; }, { link: \"ml:analyticsMap\"; }]; }, { id: \"model_management\"; title: string; children: [{ link: \"ml:nodesOverview\"; }, { link: \"ml:nodes\"; }]; }, { id: \"data_visualizer\"; title: string; children: [{ title: string; link: \"ml:fileUpload\"; }, { title: string; link: \"ml:indexDataVisualizer\"; }]; }, { id: \"aiops_labs\"; title: string; children: [{ link: \"ml:logRateAnalysis\"; }, { link: \"ml:logPatternAnalysis\"; }, { link: \"ml:changePointDetections\"; }]; }]" + "[{ title: string; id: \"root\"; children: [{ link: \"ml:overview\"; }, { link: \"ml:notifications\"; }]; }, { title: string; id: \"anomaly_detection\"; children: [{ title: string; link: \"ml:anomalyDetection\"; }, { link: \"ml:anomalyExplorer\"; }, { link: \"ml:singleMetricViewer\"; }, { link: \"ml:settings\"; }]; }, { id: \"data_frame_analytics\"; title: string; children: [{ title: string; link: \"ml:dataFrameAnalytics\"; }, { link: \"ml:resultExplorer\"; }, { link: \"ml:analyticsMap\"; }]; }, { id: \"model_management\"; title: string; children: [{ link: \"ml:nodesOverview\"; }, { link: \"ml:nodes\"; }]; }, { id: \"data_visualizer\"; title: string; children: [{ title: string; link: \"ml:fileUpload\"; }, { title: string; link: \"ml:indexDataVisualizer\"; }, { title: string; link: \"ml:dataComparison\"; }]; }, { id: \"aiops_labs\"; title: string; children: [{ link: \"ml:logRateAnalysis\"; }, { link: \"ml:logPatternAnalysis\"; }, { link: \"ml:changePointDetections\"; }]; }]" ], "path": "packages/default-nav/ml/default_navigation.ts", "deprecated": false, diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index 28b48b2ad2ef5..df48d387adfa7 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index f1853a8bdcf51..a4643bbc1db1a 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index ae1a85ad39b38..00245bb44762b 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 02b226853401b..a0e86942182ec 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index fa9d670cd0529..6453b0855bacc 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.devdocs.json b/api_docs/kbn_discover_utils.devdocs.json index 6ae82d7acfccd..74196fab61cdd 100644 --- a/api_docs/kbn_discover_utils.devdocs.json +++ b/api_docs/kbn_discover_utils.devdocs.json @@ -587,6 +587,41 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.getFieldTypeName", + "type": "Function", + "tags": [], + "label": "getFieldTypeName", + "description": [ + "\nReturns a user-friendly name of a field type" + ], + "signature": [ + "(type: string | undefined) => string" + ], + "path": "packages/kbn-discover-utils/src/utils/get_field_type_name.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.getFieldTypeName.$1", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-discover-utils/src/utils/get_field_type_name.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.getIgnoredReason", @@ -744,6 +779,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.isKnownFieldType", + "type": "Function", + "tags": [], + "label": "isKnownFieldType", + "description": [], + "signature": [ + "(type?: string | undefined) => type is string" + ], + "path": "packages/kbn-discover-utils/src/utils/field_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.isKnownFieldType.$1", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-discover-utils/src/utils/field_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.isNestedFieldParent", @@ -875,6 +943,20 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.KNOWN_FIELD_TYPES", + "type": "Enum", + "tags": [], + "label": "KNOWN_FIELD_TYPES", + "description": [ + "\nField types for which name and description are defined" + ], + "path": "packages/kbn-discover-utils/src/utils/field_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "misc": [ @@ -970,13 +1052,13 @@ }, { "parentPluginId": "@kbn/discover-utils", - "id": "def-common.ENABLE_SQL", + "id": "def-common.ENABLE_ESQL", "type": "string", "tags": [], - "label": "ENABLE_SQL", + "label": "ENABLE_ESQL", "description": [], "signature": [ - "\"discover:enableSql\"" + "\"discover:enableESQL\"" ], "path": "packages/kbn-discover-utils/src/constants.ts", "deprecated": false, @@ -1013,6 +1095,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.KNOWN_FIELD_TYPE_LIST", + "type": "Array", + "tags": [], + "label": "KNOWN_FIELD_TYPE_LIST", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-discover-utils/src/utils/field_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.MAX_DOC_FIELDS_DISPLAYED", diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 79024d0ecffc2..33306c1c44319 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 59 | 0 | 34 | 3 | +| 65 | 0 | 38 | 3 | ## Common diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 5dd70d02389dd..33c5a44d1b312 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -300,7 +300,7 @@ "label": "enterpriseSearch", "description": [], "signature": [ - "{ readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsCORS: string; readonly behavioralAnalyticsEvents: string; readonly buildConnector: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsAzureBlobStorage: string; readonly connectorsClients: string; readonly connectorsConfluence: string; readonly connectorsContentExtraction: string; readonly connectorsDropbox: string; readonly connectorsGithub: string; readonly connectorsGoogleCloudStorage: string; readonly connectorsGoogleDrive: string; readonly connectorsGmail: string; readonly connectorsJira: string; readonly connectorsMicrosoftSQL: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsNative: string; readonly connectorsNetworkDrive: string; readonly connectorsOneDrive: string; readonly connectorsOracle: string; readonly connectorsPostgreSQL: string; readonly connectorsS3: string; readonly connectorsSalesforce: string; readonly connectorsServiceNow: string; readonly connectorsSharepoint: string; readonly connectorsSharepointOnline: string; readonly connectorsSlack: string; readonly connectorsWorkplaceSearch: string; readonly crawlerExtractionRules: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly elser: string; readonly engines: string; readonly esre: string; readonly esreFaq: string; readonly esreHelp: string; readonly esreLearn: string; readonly indexApi: string; readonly ingestionApis: string; readonly ingestPipelines: string; readonly knnSearch: string; readonly knnSearchCombine: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly machineLearningStart: string; readonly mailService: string; readonly mlDocumentEnrichment: string; readonly mlDocumentEnrichmentUpdateMappings: string; readonly searchApplicationsTemplates: string; readonly searchApplicationsSearchApi: string; readonly searchApplications: string; readonly searchApplicationsSearch: string; readonly searchTemplates: string; readonly start: string; readonly supportedNlpModels: string; readonly syncRules: string; readonly trainedModels: string; readonly textEmbedding: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" + "{ readonly aiSearchDoc: string; readonly aiSearchHelp: string; readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsCORS: string; readonly behavioralAnalyticsEvents: string; readonly buildConnector: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsAzureBlobStorage: string; readonly connectorsClients: string; readonly connectorsConfluence: string; readonly connectorsContentExtraction: string; readonly connectorsDropbox: string; readonly connectorsGithub: string; readonly connectorsGoogleCloudStorage: string; readonly connectorsGoogleDrive: string; readonly connectorsGmail: string; readonly connectorsJira: string; readonly connectorsMicrosoftSQL: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsNative: string; readonly connectorsNetworkDrive: string; readonly connectorsOneDrive: string; readonly connectorsOracle: string; readonly connectorsPostgreSQL: string; readonly connectorsS3: string; readonly connectorsSalesforce: string; readonly connectorsServiceNow: string; readonly connectorsSharepoint: string; readonly connectorsSharepointOnline: string; readonly connectorsSlack: string; readonly connectorsWorkplaceSearch: string; readonly crawlerExtractionRules: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly elser: string; readonly engines: string; readonly indexApi: string; readonly ingestionApis: string; readonly ingestPipelines: string; readonly knnSearch: string; readonly knnSearchCombine: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly machineLearningStart: string; readonly mailService: string; readonly mlDocumentEnrichment: string; readonly mlDocumentEnrichmentUpdateMappings: string; readonly searchApplicationsTemplates: string; readonly searchApplicationsSearchApi: string; readonly searchApplications: string; readonly searchApplicationsSearch: string; readonly searchLabs: string; readonly searchLabsRepo: string; readonly searchTemplates: string; readonly start: string; readonly supportedNlpModels: string; readonly syncRules: string; readonly trainedModels: string; readonly textEmbedding: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, @@ -644,7 +644,7 @@ "label": "apis", "description": [], "signature": [ - "{ readonly bulkIndexAlias: string; readonly byteSizeUnits: string; readonly createAutoFollowPattern: string; readonly createFollower: string; readonly createIndex: string; readonly createSnapshotLifecyclePolicy: string; readonly createRoleMapping: string; readonly createRoleMappingTemplates: string; readonly createRollupJobsRequest: string; readonly createApiKey: string; readonly createPipeline: string; readonly createTransformRequest: string; readonly cronExpressions: string; readonly executeWatchActionModes: string; readonly indexExists: string; readonly multiSearch: string; readonly openIndex: string; readonly putComponentTemplate: string; readonly painlessExecute: string; readonly painlessExecuteAPIContexts: string; readonly putComponentTemplateMetadata: string; readonly putSnapshotLifecyclePolicy: string; readonly putIndexTemplateV1: string; readonly putWatch: string; readonly restApis: string; readonly searchPreference: string; readonly securityApis: string; readonly simulatePipeline: string; readonly timeUnits: string; readonly unfreezeIndex: string; readonly updateTransform: string; }" + "{ readonly bulkIndexAlias: string; readonly indexStats: string; readonly byteSizeUnits: string; readonly createAutoFollowPattern: string; readonly createFollower: string; readonly createIndex: string; readonly createSnapshotLifecyclePolicy: string; readonly createRoleMapping: string; readonly createRoleMappingTemplates: string; readonly createRollupJobsRequest: string; readonly createApiKey: string; readonly createPipeline: string; readonly createTransformRequest: string; readonly cronExpressions: string; readonly executeWatchActionModes: string; readonly indexExists: string; readonly multiSearch: string; readonly openIndex: string; readonly putComponentTemplate: string; readonly painlessExecute: string; readonly painlessExecuteAPIContexts: string; readonly putComponentTemplateMetadata: string; readonly putSnapshotLifecyclePolicy: string; readonly putIndexTemplateV1: string; readonly putWatch: string; readonly restApis: string; readonly searchPreference: string; readonly securityApis: string; readonly simulatePipeline: string; readonly tasks: string; readonly timeUnits: string; readonly unfreezeIndex: string; readonly updateTransform: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, @@ -840,7 +840,7 @@ "label": "fleet", "description": [], "signature": [ - "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; }" + "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly esSettings: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, @@ -1043,6 +1043,17 @@ "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/doc-links", + "id": "def-common.DocLinksMeta.searchLabsUrl", + "type": "string", + "tags": [], + "label": "searchLabsUrl", + "description": [], + "path": "packages/kbn-doc-links/src/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 007fdabcfd5fa..0d3a6383eefef 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/docs](https://github.com/orgs/elastic/teams/docs) for question | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 73 | 0 | 73 | 2 | +| 74 | 0 | 74 | 2 | ## Common diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index f5b27c737b5a5..5f5e1b0e7b2da 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index e36c2b0e45a3a..91ac62dc1748a 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 65397b4d289ca..db073f63806e1 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index 40103298d2de8..eac5e4a3116c3 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index 8b0178fe32bd9..6c59fcbb8dd50 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.devdocs.json b/api_docs/kbn_elastic_assistant.devdocs.json index ba770f780e3fd..06e02662f3e6a 100644 --- a/api_docs/kbn_elastic_assistant.devdocs.json +++ b/api_docs/kbn_elastic_assistant.devdocs.json @@ -127,7 +127,7 @@ "\nModal container for Elastic AI Assistant conversations, receiving the page contents as context, plus whatever\ncomponent currently has focus and any specific context it may provide through the SAssInterface." ], "signature": [ - "React.NamedExoticComponent" + "React.NamedExoticComponent & { readonly type: () => JSX.Element; }" ], "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx", "deprecated": false, @@ -260,6 +260,24 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.useAssistantContext", + "type": "Function", + "tags": [], + "label": "useAssistantContext", + "description": [], + "signature": [ + "() => ", + "UseAssistantContext" + ], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant", "id": "def-public.useAssistantOverlay", @@ -827,6 +845,92 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.DeleteKnowledgeBaseResponse", + "type": "Interface", + "tags": [], + "label": "DeleteKnowledgeBaseResponse", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.DeleteKnowledgeBaseResponse.success", + "type": "boolean", + "tags": [], + "label": "success", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.GetKnowledgeBaseStatusResponse", + "type": "Interface", + "tags": [], + "label": "GetKnowledgeBaseStatusResponse", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.GetKnowledgeBaseStatusResponse.elser_exists", + "type": "boolean", + "tags": [], + "label": "elser_exists", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.GetKnowledgeBaseStatusResponse.esql_exists", + "type": "CompoundType", + "tags": [], + "label": "esql_exists", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.GetKnowledgeBaseStatusResponse.index_exists", + "type": "boolean", + "tags": [], + "label": "index_exists", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.GetKnowledgeBaseStatusResponse.pipeline_exists", + "type": "boolean", + "tags": [], + "label": "pipeline_exists", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant", "id": "def-public.Message", @@ -892,6 +996,31 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.PostKnowledgeBaseResponse", + "type": "Interface", + "tags": [], + "label": "PostKnowledgeBaseResponse", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.PostKnowledgeBaseResponse.success", + "type": "boolean", + "tags": [], + "label": "success", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant", "id": "def-public.Prompt", diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index ef07d0a947ef6..0260e95574207 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 84 | 0 | 64 | 5 | +| 94 | 0 | 74 | 6 | ## Client diff --git a/api_docs/kbn_es.devdocs.json b/api_docs/kbn_es.devdocs.json index b7cfd2bbebb2e..4d53e7ff40952 100644 --- a/api_docs/kbn_es.devdocs.json +++ b/api_docs/kbn_es.devdocs.json @@ -17,7 +17,607 @@ "objects": [] }, "common": { - "classes": [], + "classes": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster", + "type": "Class", + "tags": [], + "label": "Cluster", + "description": [], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "{ log = defaultLog, ssl = false }", + "description": [], + "signature": [ + "{ log?: ", + { + "pluginId": "@kbn/tooling-log", + "scope": "common", + "docId": "kibKbnToolingLogPluginApi", + "section": "def-common.ToolingLog", + "text": "ToolingLog" + }, + " | undefined; ssl?: boolean | undefined; }" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installSource", + "type": "Function", + "tags": [], + "label": "installSource", + "description": [ + "\nBuilds and installs ES from source" + ], + "signature": [ + "(options: ", + "InstallSourceOptions", + ") => Promise<{ installPath: string; }>" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installSource.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "InstallSourceOptions" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.downloadSnapshot", + "type": "Function", + "tags": [], + "label": "downloadSnapshot", + "description": [ + "\nDownload ES from a snapshot" + ], + "signature": [ + "(options: ", + "DownloadSnapshotOptions", + ") => Promise<{ downloadPath: string; }>" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.downloadSnapshot.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "DownloadSnapshotOptions" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installSnapshot", + "type": "Function", + "tags": [], + "label": "installSnapshot", + "description": [ + "\nDownload and installs ES from a snapshot" + ], + "signature": [ + "(options: ", + "InstallSnapshotOptions", + ") => Promise<{ installPath: string; }>" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installSnapshot.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "InstallSnapshotOptions" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installArchive", + "type": "Function", + "tags": [], + "label": "installArchive", + "description": [ + "\nInstalls ES from a local tar" + ], + "signature": [ + "(archivePath: string, options?: ", + "InstallArchiveOptions", + " | undefined) => Promise<{ installPath: string; }>" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installArchive.$1", + "type": "string", + "tags": [], + "label": "archivePath", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installArchive.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "InstallArchiveOptions", + " | undefined" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.extractDataDirectory", + "type": "Function", + "tags": [], + "label": "extractDataDirectory", + "description": [ + "\nUnpacks a tar or zip file containing the data directory for an ES cluster." + ], + "signature": [ + "(installPath: string, archivePath: string, extractDirName?: string) => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.extractDataDirectory.$1", + "type": "string", + "tags": [], + "label": "installPath", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.extractDataDirectory.$2", + "type": "string", + "tags": [], + "label": "archivePath", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.extractDataDirectory.$3", + "type": "string", + "tags": [], + "label": "extractDirName", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installPlugins", + "type": "Function", + "tags": [], + "label": "installPlugins", + "description": [ + "\nInstalls comma separated list of ES plugins to the specified path" + ], + "signature": [ + "(installPath: string, plugins: string, esJavaOpts?: string | undefined) => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installPlugins.$1", + "type": "string", + "tags": [], + "label": "installPath", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installPlugins.$2", + "type": "string", + "tags": [], + "label": "plugins", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.installPlugins.$3", + "type": "string", + "tags": [], + "label": "esJavaOpts", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.configureKeystoreWithSecureSettingsFiles", + "type": "Function", + "tags": [], + "label": "configureKeystoreWithSecureSettingsFiles", + "description": [], + "signature": [ + "(installPath: string, secureSettingsFiles: string[][]) => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.configureKeystoreWithSecureSettingsFiles.$1", + "type": "string", + "tags": [], + "label": "installPath", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.configureKeystoreWithSecureSettingsFiles.$2", + "type": "Array", + "tags": [], + "label": "secureSettingsFiles", + "description": [], + "signature": [ + "string[][]" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.start", + "type": "Function", + "tags": [], + "label": "start", + "description": [ + "\nStarts ES and returns resolved promise once started" + ], + "signature": [ + "(installPath: string, options: ", + "EsClusterExecOptions", + ") => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.start.$1", + "type": "string", + "tags": [], + "label": "installPath", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.start.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "EsClusterExecOptions" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.run", + "type": "Function", + "tags": [], + "label": "run", + "description": [ + "\nStarts Elasticsearch and waits for Elasticsearch to exit" + ], + "signature": [ + "(installPath: string, options: ", + "EsClusterExecOptions", + ") => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.run.$1", + "type": "string", + "tags": [], + "label": "installPath", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.run.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "EsClusterExecOptions" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.stop", + "type": "Function", + "tags": [], + "label": "stop", + "description": [ + "\nStops ES process, if it's running" + ], + "signature": [ + "() => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.kill", + "type": "Function", + "tags": [], + "label": "kill", + "description": [ + "\nStops ES process without waiting for it to shutdown gracefully" + ], + "signature": [ + "() => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.runServerless", + "type": "Function", + "tags": [], + "label": "runServerless", + "description": [ + "\nRuns an Elasticsearch Serverless Docker cluster and returns node names" + ], + "signature": [ + "(options: ", + "ServerlessOptions", + ") => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.runServerless.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "ServerlessOptions" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.runDocker", + "type": "Function", + "tags": [], + "label": "runDocker", + "description": [ + "\nRun an Elasticsearch Docker container" + ], + "signature": [ + "(options: ", + "DockerOptions", + ") => Promise" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.Cluster.runDocker.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "DockerOptions" + ], + "path": "packages/kbn-es/src/cluster.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "functions": [ { "parentPluginId": "@kbn/es", @@ -91,6 +691,21 @@ "interfaces": [], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/es", + "id": "def-common.ArtifactLicense", + "type": "Type", + "tags": [], + "label": "ArtifactLicense", + "description": [], + "signature": [ + "\"basic\" | \"trial\" | \"oss\"" + ], + "path": "packages/kbn-es/src/artifact.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es", "id": "def-common.ELASTIC_SERVERLESS_SUPERUSER", @@ -134,29 +749,6 @@ "initialIsOpen": false } ], - "objects": [ - { - "parentPluginId": "@kbn/es", - "id": "def-common.Cluster", - "type": "Object", - "tags": [], - "label": "Cluster", - "description": [], - "signature": [ - "typeof ", - { - "pluginId": "@kbn/es", - "scope": "common", - "docId": "kibKbnEsPluginApi", - "section": "def-common.Cluster", - "text": "Cluster" - } - ], - "path": "packages/kbn-es/src/cluster.js", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ] + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 42ff9a3b20562..08ba49d3ab575 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; @@ -21,16 +21,16 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 8 | 0 | 7 | 0 | +| 43 | 0 | 30 | 7 | ## Common -### Objects - - ### Functions +### Classes + + ### Consts, variables and types diff --git a/api_docs/kbn_es_archiver.devdocs.json b/api_docs/kbn_es_archiver.devdocs.json index 65257a15f067a..61d819e2b15f9 100644 --- a/api_docs/kbn_es_archiver.devdocs.json +++ b/api_docs/kbn_es_archiver.devdocs.json @@ -439,7 +439,7 @@ "tags": [], "label": "emptyKibanaIndex", "description": [ - "\nDelete any Kibana indices, and initialize the Kibana index as Kibana would do\non startup." + "\nCleanup saved object indices, preserving the space:default saved object." ], "signature": [ "() => Promise never" ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2592,7 +2592,7 @@ "text": "AggregateQuery" } ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2683,6 +2683,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.getIndexPatternFromESQLQuery", + "type": "Function", + "tags": [], + "label": "getIndexPatternFromESQLQuery", + "description": [], + "signature": [ + "(esql: string | undefined) => string" + ], + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.getIndexPatternFromESQLQuery.$1", + "type": "string", + "tags": [], + "label": "esql", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es-query", "id": "def-common.getIndexPatternFromSQLQuery", @@ -2693,7 +2726,7 @@ "signature": [ "(sqlQuery: string | undefined) => string" ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2707,7 +2740,7 @@ "signature": [ "string | undefined" ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -2716,6 +2749,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.getLanguageDisplayName", + "type": "Function", + "tags": [], + "label": "getLanguageDisplayName", + "description": [], + "signature": [ + "(language: string) => string" + ], + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.getLanguageDisplayName.$1", + "type": "string", + "tags": [], + "label": "language", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es-query", "id": "def-common.isCombinedFilter", @@ -3152,7 +3218,7 @@ }, " | { [key: string]: any; }) => boolean" ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -3181,7 +3247,7 @@ }, " | { [key: string]: any; }" ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -3216,7 +3282,7 @@ }, " | undefined) => boolean" ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -3245,7 +3311,7 @@ }, " | undefined" ], - "path": "packages/kbn-es-query/src/es_query/es_query_sql.ts", + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", "deprecated": false, "trackAdoption": false, "isRequired": false diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 1f326e8a9da21..094e7c556fbb4 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 255 | 1 | 195 | 15 | +| 259 | 1 | 199 | 15 | ## Common diff --git a/api_docs/kbn_es_types.devdocs.json b/api_docs/kbn_es_types.devdocs.json index 7b364b08e03a3..37cab03f85e11 100644 --- a/api_docs/kbn_es_types.devdocs.json +++ b/api_docs/kbn_es_types.devdocs.json @@ -20,6 +20,100 @@ "classes": [], "functions": [], "interfaces": [ + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ClusterDetails", + "type": "Interface", + "tags": [], + "label": "ClusterDetails", + "description": [], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ClusterDetails.status", + "type": "CompoundType", + "tags": [], + "label": "status", + "description": [], + "signature": [ + "\"running\" | \"failed\" | \"partial\" | \"skipped\" | \"successful\"" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ClusterDetails.indices", + "type": "string", + "tags": [], + "label": "indices", + "description": [], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ClusterDetails.took", + "type": "number", + "tags": [], + "label": "took", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ClusterDetails.timed_out", + "type": "boolean", + "tags": [], + "label": "timed_out", + "description": [], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ClusterDetails._shards", + "type": "Object", + "tags": [], + "label": "_shards", + "description": [], + "signature": [ + "ShardStatistics", + " | undefined" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ClusterDetails.failures", + "type": "Array", + "tags": [], + "label": "failures", + "description": [], + "signature": [ + "ShardFailure", + "[] | undefined" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es-types", "id": "def-common.ESSearchOptions", diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 85bea4b3bf4a9..5b3a394b8287b 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 12 | 0 | 12 | 0 | +| 19 | 0 | 19 | 0 | ## Common diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 0e4f605a555fd..0414e9e347f15 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 36da66848d651..a055aa38dd55d 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.devdocs.json b/api_docs/kbn_event_annotation_components.devdocs.json index 64b85baf88e09..b17e02bb66ce2 100644 --- a/api_docs/kbn_event_annotation_components.devdocs.json +++ b/api_docs/kbn_event_annotation_components.devdocs.json @@ -123,336 +123,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList", - "type": "Function", - "tags": [], - "label": "EventAnnotationGroupTableList", - "description": [], - "signature": [ - "({ uiSettings, eventAnnotationService, visualizeCapabilities, savedObjectsTagging, parentProps, dataViews, createDataView, queryInputServices, toasts, navigateToLens, }: { uiSettings: ", - { - "pluginId": "@kbn/core-ui-settings-browser", - "scope": "common", - "docId": "kibKbnCoreUiSettingsBrowserPluginApi", - "section": "def-common.IUiSettingsClient", - "text": "IUiSettingsClient" - }, - "; eventAnnotationService: ", - { - "pluginId": "@kbn/event-annotation-components", - "scope": "public", - "docId": "kibKbnEventAnnotationComponentsPluginApi", - "section": "def-public.EventAnnotationServiceType", - "text": "EventAnnotationServiceType" - }, - "; visualizeCapabilities: Record>; savedObjectsTagging: ", - { - "pluginId": "savedObjectsTaggingOss", - "scope": "public", - "docId": "kibSavedObjectsTaggingOssPluginApi", - "section": "def-public.SavedObjectsTaggingApi", - "text": "SavedObjectsTaggingApi" - }, - "; parentProps: ", - { - "pluginId": "@kbn/content-management-tabbed-table-list-view", - "scope": "common", - "docId": "kibKbnContentManagementTabbedTableListViewPluginApi", - "section": "def-common.TableListTabParentProps", - "text": "TableListTabParentProps" - }, - "<", - { - "pluginId": "@kbn/content-management-table-list-view-table", - "scope": "common", - "docId": "kibKbnContentManagementTableListViewTablePluginApi", - "section": "def-common.UserContentCommonSchema", - "text": "UserContentCommonSchema" - }, - ">; dataViews: ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - "[]; createDataView: (spec: ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" - }, - ") => Promise<", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - ">; queryInputServices: ", - { - "pluginId": "@kbn/visualization-ui-components", - "scope": "public", - "docId": "kibKbnVisualizationUiComponentsPluginApi", - "section": "def-public.QueryInputServices", - "text": "QueryInputServices" - }, - "; toasts: ", - { - "pluginId": "@kbn/core-notifications-browser", - "scope": "common", - "docId": "kibKbnCoreNotificationsBrowserPluginApi", - "section": "def-common.IToasts", - "text": "IToasts" - }, - "; navigateToLens: () => void; }) => JSX.Element" - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1", - "type": "Object", - "tags": [], - "label": "{\n uiSettings,\n eventAnnotationService,\n visualizeCapabilities,\n savedObjectsTagging,\n parentProps,\n dataViews,\n createDataView,\n queryInputServices,\n toasts,\n navigateToLens,\n}", - "description": [], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.uiSettings", - "type": "Object", - "tags": [], - "label": "uiSettings", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-ui-settings-browser", - "scope": "common", - "docId": "kibKbnCoreUiSettingsBrowserPluginApi", - "section": "def-common.IUiSettingsClient", - "text": "IUiSettingsClient" - } - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.eventAnnotationService", - "type": "Object", - "tags": [], - "label": "eventAnnotationService", - "description": [], - "signature": [ - { - "pluginId": "@kbn/event-annotation-components", - "scope": "public", - "docId": "kibKbnEventAnnotationComponentsPluginApi", - "section": "def-public.EventAnnotationServiceType", - "text": "EventAnnotationServiceType" - } - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.visualizeCapabilities", - "type": "Object", - "tags": [], - "label": "visualizeCapabilities", - "description": [], - "signature": [ - "{ [x: string]: boolean | Record; }" - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.savedObjectsTagging", - "type": "Object", - "tags": [], - "label": "savedObjectsTagging", - "description": [], - "signature": [ - { - "pluginId": "savedObjectsTaggingOss", - "scope": "public", - "docId": "kibSavedObjectsTaggingOssPluginApi", - "section": "def-public.SavedObjectsTaggingApi", - "text": "SavedObjectsTaggingApi" - } - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.parentProps", - "type": "Object", - "tags": [], - "label": "parentProps", - "description": [], - "signature": [ - "{ onFetchSuccess: () => void; setPageDataTestSubject: (subject: string) => void; }" - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.dataViews", - "type": "Array", - "tags": [], - "label": "dataViews", - "description": [], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - "[]" - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.createDataView", - "type": "Function", - "tags": [], - "label": "createDataView", - "description": [], - "signature": [ - "(spec: ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" - }, - ") => Promise<", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - ">" - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.createDataView.$1", - "type": "Object", - "tags": [], - "label": "spec", - "description": [], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" - } - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.queryInputServices", - "type": "Object", - "tags": [], - "label": "queryInputServices", - "description": [], - "signature": [ - { - "pluginId": "@kbn/visualization-ui-components", - "scope": "public", - "docId": "kibKbnVisualizationUiComponentsPluginApi", - "section": "def-public.QueryInputServices", - "text": "QueryInputServices" - } - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.toasts", - "type": "Object", - "tags": [], - "label": "toasts", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-notifications-browser", - "scope": "common", - "docId": "kibKbnCoreNotificationsBrowserPluginApi", - "section": "def-common.IToasts", - "text": "IToasts" - } - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/event-annotation-components", - "id": "def-public.EventAnnotationGroupTableList.$1.navigateToLens", - "type": "Function", - "tags": [], - "label": "navigateToLens", - "description": [], - "signature": [ - "() => void" - ], - "path": "packages/kbn-event-annotation-components/components/table_list.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - } - ] - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/event-annotation-components", "id": "def-public.getAnnotationAccessor", diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index ac5fecbc330f8..4fbdd8c2489f8 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 65 | 0 | 65 | 1 | +| 52 | 0 | 52 | 1 | ## Client diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index 5925a7c86030f..c417be01991ec 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 8715c09f3030d..3113bcf9f9e09 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 77685c076a27c..5ed893d1a1902 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index c18507ab036e4..22d53f9916240 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index dcb0610551cad..fd135ab66b04d 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index 6cab2a17b1eb7..0ac40d252c7ab 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index a2ca8d6fea0ef..52de8fab97857 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_generate_csv_types.mdx b/api_docs/kbn_generate_csv_types.mdx index 3c096a07c337d..3f86b4bd5345b 100644 --- a/api_docs/kbn_generate_csv_types.mdx +++ b/api_docs/kbn_generate_csv_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv-types title: "@kbn/generate-csv-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv-types'] --- import kbnGenerateCsvTypesObj from './kbn_generate_csv_types.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index fe27879215b6e..edaa5d9c898ce 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 6cfb08ebfb7d4..ee3393d592ede 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 9b2c6d475600d..071f46f6131f8 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 8f7289bf003ff..f201e7104b40c 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index adb7115e75c4c..77dd7f2fe8d3d 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index bde6ed48305c2..d509beb85d7fa 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 530c0c4e3b47a..58f3b54992f7c 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index ec5bc472f9892..923c2692cb7b1 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 592304aabd880..74c98b3167a92 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 2dc027e61305d..93699845957da 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 76fc39b19ce4c..532dc7e0147ee 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 354775f584bb9..a840b1390e377 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 6cf49dafb657e..31cfb13988813 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 389f0677f32c7..5dd4b16f7971b 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 5545d13c4d266..48b73c2c67c9f 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index fec54f8ec0558..b9d9e9c8b7e94 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 3272017185207..ce9acc5d1f037 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.devdocs.json b/api_docs/kbn_lens_embeddable_utils.devdocs.json index 18adb1d1f7a03..3bc9508b445d6 100644 --- a/api_docs/kbn_lens_embeddable_utils.devdocs.json +++ b/api_docs/kbn_lens_embeddable_utils.devdocs.json @@ -2835,7 +2835,7 @@ }, " | undefined; reducedTimeRange?: string | undefined; timeScale?: ", "TimeScaleUnit", - " | undefined; format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; }, \"formula\"> & { color?: string | undefined; value: string; }" + " | undefined; format?: { id: string; params?: { decimals: number; compact?: boolean | undefined; } | undefined; } | undefined; }, \"formula\"> & { color?: string | undefined; value: string; }" ], "path": "packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/layers/metric_layer.ts", "deprecated": false, @@ -3458,7 +3458,7 @@ }, " | undefined; reducedTimeRange?: string | undefined; timeScale?: ", "TimeScaleUnit", - " | undefined; format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; }, \"formula\"> & { color?: string | undefined; value: string; }" + " | undefined; format?: { id: string; params?: { decimals: number; compact?: boolean | undefined; } | undefined; } | undefined; }, \"formula\"> & { color?: string | undefined; value: string; }" ], "path": "packages/kbn-lens-embeddable-utils/attribute_builder/types.ts", "deprecated": false, @@ -3606,7 +3606,7 @@ }, " | undefined; reducedTimeRange?: string | undefined; timeScale?: ", "TimeScaleUnit", - " | undefined; format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; }, \"formula\"> & { color?: string | undefined; value: string; }" + " | undefined; format?: { id: string; params?: { decimals: number; compact?: boolean | undefined; } | undefined; } | undefined; }, \"formula\"> & { color?: string | undefined; value: string; }" ], "path": "packages/kbn-lens-embeddable-utils/attribute_builder/types.ts", "deprecated": false, diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index d8c72b3999eac..f0ae5b4e7dcbb 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 3e2db0a4fb049..59e79931862e0 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 14cd96df85166..8487009e22acd 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 70c9874174eef..4792c412dae5a 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 2027a09d74372..8a0f94f6cb939 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.devdocs.json b/api_docs/kbn_management_settings_components_field_input.devdocs.json new file mode 100644 index 0000000000000..3209874b93880 --- /dev/null +++ b/api_docs/kbn_management_settings_components_field_input.devdocs.json @@ -0,0 +1,467 @@ +{ + "id": "@kbn/management-settings-components-field-input", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInput", + "type": "Function", + "tags": [], + "label": "FieldInput", + "description": [ + "\nAn input that allows one to change a setting in Kibana.\n" + ], + "signature": [ + "React.ForwardRefExoticComponent<", + { + "pluginId": "@kbn/management-settings-components-field-input", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldInputPluginApi", + "section": "def-common.FieldInputProps", + "text": "FieldInputProps" + }, + "<", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsType", + "text": "UiSettingsType" + }, + "> & React.RefAttributes<", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ResetInputRef", + "text": "ResetInputRef" + }, + ">>" + ], + "path": "packages/kbn-management/settings/components/field_input/field_input.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInput.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [ + "The props for the {@link FieldInput } component." + ], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputKibanaDependencies", + "type": "Interface", + "tags": [], + "label": "FieldInputKibanaDependencies", + "description": [ + "\nAn interface containing a collection of Kibana plugins and services required to\nrender this component." + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputKibanaDependencies.toasts", + "type": "Object", + "tags": [], + "label": "toasts", + "description": [ + "The portion of the {@link ToastsStart} contract used by this component." + ], + "signature": [ + "{ addDanger: (toastOrTitle: ", + { + "pluginId": "@kbn/core-notifications-browser", + "scope": "common", + "docId": "kibKbnCoreNotificationsBrowserPluginApi", + "section": "def-common.ToastInput", + "text": "ToastInput" + }, + ", options?: any) => ", + { + "pluginId": "@kbn/core-notifications-browser", + "scope": "common", + "docId": "kibKbnCoreNotificationsBrowserPluginApi", + "section": "def-common.Toast", + "text": "Toast" + }, + "; }" + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputProps", + "type": "Interface", + "tags": [], + "label": "FieldInputProps", + "description": [ + "\nThe props that are passed to the {@link FieldInput} component." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-components-field-input", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldInputPluginApi", + "section": "def-common.FieldInputProps", + "text": "FieldInputProps" + }, + "" + ], + "path": "packages/kbn-management/settings/components/field_input/field_input.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputProps.field", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The {@link FieldDefinition} for the component." + ], + "signature": [ + "{ type: T; id: string; name: string; ariaAttributes: { ariaLabel: string; ariaDescribedBy?: string | undefined; }; }" + ], + "path": "packages/kbn-management/settings/components/field_input/field_input.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputProps.unsavedChange", + "type": "Object", + "tags": [], + "label": "unsavedChange", + "description": [ + "An {@link UnsavedFieldChange} for the component, if any." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/components/field_input/field_input.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputProps.onChange", + "type": "Function", + "tags": [], + "label": "onChange", + "description": [ + "The `onChange` handler for the input." + ], + "signature": [ + "(change?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined) => void" + ], + "path": "packages/kbn-management/settings/components/field_input/field_input.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputProps.onChange.$1", + "type": "Object", + "tags": [], + "label": "change", + "description": [], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputProps.isSavingEnabled", + "type": "boolean", + "tags": [], + "label": "isSavingEnabled", + "description": [ + "True if the input can be saved, false otherwise." + ], + "path": "packages/kbn-management/settings/components/field_input/field_input.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputProps.isInvalid", + "type": "CompoundType", + "tags": [], + "label": "isInvalid", + "description": [ + "True if the value within the input is invalid, false otherwise." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-management/settings/components/field_input/field_input.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputServices", + "type": "Interface", + "tags": [], + "label": "FieldInputServices", + "description": [ + "\nContextual services used by a {@link FieldInput} component." + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputServices.showDanger", + "type": "Function", + "tags": [], + "label": "showDanger", + "description": [ + "\nDisplays a danger toast message." + ], + "signature": [ + "(value: string) => void" + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.FieldInputServices.showDanger.$1", + "type": "string", + "tags": [], + "label": "value", + "description": [ + "The message to display." + ], + "signature": [ + "string" + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.InputProps", + "type": "Interface", + "tags": [], + "label": "InputProps", + "description": [ + "\nProps passed to a {@link FieldInput} component." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-components-field-input", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldInputPluginApi", + "section": "def-common.InputProps", + "text": "InputProps" + }, + "" + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.InputProps.field", + "type": "Object", + "tags": [], + "label": "field", + "description": [], + "signature": [ + "{ type: T; id: string; defaultValue?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.KnownTypeToValue", + "text": "KnownTypeToValue" + }, + " | null | undefined; name: string; isOverridden: boolean; ariaAttributes: { ariaLabel: string; ariaDescribedBy?: string | undefined; }; savedValue?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.KnownTypeToValue", + "text": "KnownTypeToValue" + }, + " | null | undefined; }" + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.InputProps.unsavedChange", + "type": "Object", + "tags": [], + "label": "unsavedChange", + "description": [], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.InputProps.isSavingEnabled", + "type": "boolean", + "tags": [], + "label": "isSavingEnabled", + "description": [], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.InputProps.onChange", + "type": "Function", + "tags": [], + "label": "onChange", + "description": [ + "The `onChange` handler." + ], + "signature": [ + "(change?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined) => void" + ], + "path": "packages/kbn-management/settings/components/field_input/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-input", + "id": "def-common.InputProps.onChange.$1", + "type": "Object", + "tags": [], + "label": "change", + "description": [], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx new file mode 100644 index 0000000000000..def6f35ed1504 --- /dev/null +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnManagementSettingsComponentsFieldInputPluginApi +slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input +title: "@kbn/management-settings-components-field-input" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/management-settings-components-field-input plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] +--- +import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; + + + +Contact [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 20 | 0 | 5 | 0 | + +## Common + +### Functions + + +### Interfaces + + diff --git a/api_docs/kbn_management_settings_components_field_row.devdocs.json b/api_docs/kbn_management_settings_components_field_row.devdocs.json new file mode 100644 index 0000000000000..c1f7c49ed5c67 --- /dev/null +++ b/api_docs/kbn_management_settings_components_field_row.devdocs.json @@ -0,0 +1,601 @@ +{ + "id": "@kbn/management-settings-components-field-row", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRow", + "type": "Function", + "tags": [], + "label": "FieldRow", + "description": [ + "\nComponent for displaying a {@link FieldDefinition} in a form row, using a {@link FieldInput}." + ], + "signature": [ + "(props: ", + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowProps", + "text": "FieldRowProps" + }, + ") => JSX.Element" + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRow.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [ + "The {@link FieldRowProps } for the {@link FieldRow } component." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowProps", + "text": "FieldRowProps" + } + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowKibanaProvider", + "type": "Function", + "tags": [], + "label": "FieldRowKibanaProvider", + "description": [ + "\nKibana-specific Provider that maps Kibana plugins and services to a {@link FieldRowProvider}." + ], + "signature": [ + "({ children, docLinks, toasts, }: React.PropsWithChildren<", + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowKibanaDependencies", + "text": "FieldRowKibanaDependencies" + }, + ">) => JSX.Element" + ], + "path": "packages/kbn-management/settings/components/field_row/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowKibanaProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n children,\n docLinks,\n toasts,\n}", + "description": [], + "signature": [ + "React.PropsWithChildren<", + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowKibanaDependencies", + "text": "FieldRowKibanaDependencies" + }, + ">" + ], + "path": "packages/kbn-management/settings/components/field_row/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProvider", + "type": "Function", + "tags": [], + "label": "FieldRowProvider", + "description": [ + "\nReact Provider that provides services to a {@link FieldRow} component and its dependents.\\" + ], + "signature": [ + "({ children, ...services }: ", + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowProviderProps", + "text": "FieldRowProviderProps" + }, + ") => JSX.Element" + ], + "path": "packages/kbn-management/settings/components/field_row/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProvider.$1", + "type": "Object", + "tags": [], + "label": "{ children, ...services }", + "description": [], + "signature": [ + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowProviderProps", + "text": "FieldRowProviderProps" + } + ], + "path": "packages/kbn-management/settings/components/field_row/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps", + "type": "Interface", + "tags": [], + "label": "FieldRowProps", + "description": [ + "\nProps for a {@link FieldRow} component." + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.field", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The {@link FieldDefinition} corresponding the setting." + ], + "signature": [ + "{ type: ", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsType", + "text": "UiSettingsType" + }, + "; id: string; defaultValue?: string | number | boolean | (string | number)[] | null | undefined; name: string; groupId: string; displayName: string; isCustom: boolean; isOverridden: boolean; ariaAttributes: { ariaLabel: string; ariaDescribedBy?: string | undefined; }; savedValue?: string | number | boolean | (string | number)[] | null | undefined; defaultValueDisplay: string; isDefaultValue: boolean; unsavedFieldId: string; }" + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.isSavingEnabled", + "type": "boolean", + "tags": [], + "label": "isSavingEnabled", + "description": [ + "True if saving settings is enabled, false otherwise." + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.onChange", + "type": "Function", + "tags": [], + "label": "onChange", + "description": [ + "The {@link OnChangeFn} handler." + ], + "signature": [ + "(id: string, change?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsType", + "text": "UiSettingsType" + }, + "> | undefined) => void" + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.onChange.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.onChange.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.onClear", + "type": "Function", + "tags": [], + "label": "onClear", + "description": [ + "\nThe onClear handler, if a value is cleared to an empty or default state." + ], + "signature": [ + "((id: string) => void) | undefined" + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.onClear.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "The id relating to the field to clear." + ], + "signature": [ + "string" + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProps.unsavedChange", + "type": "Object", + "tags": [], + "label": "unsavedChange", + "description": [ + "The {@link UnsavedFieldChange} corresponding to any unsaved change to the field." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsType", + "text": "UiSettingsType" + }, + "> | undefined" + ], + "path": "packages/kbn-management/settings/components/field_row/field_row.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProviderProps", + "type": "Interface", + "tags": [], + "label": "FieldRowProviderProps", + "description": [ + "\nProps for {@link FieldRowProvider}." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowProviderProps", + "text": "FieldRowProviderProps" + }, + " extends ", + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.FieldRowServices", + "text": "FieldRowServices" + } + ], + "path": "packages/kbn-management/settings/components/field_row/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowProviderProps.children", + "type": "CompoundType", + "tags": [], + "label": "children", + "description": [], + "signature": [ + "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" + ], + "path": "packages/kbn-management/settings/components/field_row/services.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.KibanaDependencies", + "type": "Interface", + "tags": [], + "label": "KibanaDependencies", + "description": [ + "\nAn interface containing a collection of Kibana plugins and services required to\nrender a {@link FieldRow} component." + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.KibanaDependencies.docLinks", + "type": "Object", + "tags": [], + "label": "docLinks", + "description": [], + "signature": [ + "{ links: { management: Record; }; }" + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.Services", + "type": "Interface", + "tags": [], + "label": "Services", + "description": [ + "\nContextual services used by a {@link FieldRow} component." + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.Services.links", + "type": "Object", + "tags": [], + "label": "links", + "description": [], + "signature": [ + "{ [key: string]: string; }" + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowKibanaDependencies", + "type": "Type", + "tags": [], + "label": "FieldRowKibanaDependencies", + "description": [ + "\nAn interface containing a collection of Kibana plugins and services required to\nrender a {@link FieldRow} component and its dependents." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.KibanaDependencies", + "text": "KibanaDependencies" + }, + " & ", + { + "pluginId": "@kbn/management-settings-components-field-input", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldInputPluginApi", + "section": "def-common.FieldInputKibanaDependencies", + "text": "FieldInputKibanaDependencies" + } + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.FieldRowServices", + "type": "Type", + "tags": [], + "label": "FieldRowServices", + "description": [ + "\nContextual services used by a {@link FieldRow} component and its dependents." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-components-field-input", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldInputPluginApi", + "section": "def-common.FieldInputServices", + "text": "FieldInputServices" + }, + " & ", + { + "pluginId": "@kbn/management-settings-components-field-row", + "scope": "common", + "docId": "kibKbnManagementSettingsComponentsFieldRowPluginApi", + "section": "def-common.Services", + "text": "Services" + } + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.RowOnChangeFn", + "type": "Type", + "tags": [], + "label": "RowOnChangeFn", + "description": [ + "\nAn `onChange` handler for a {@link FieldRow} component." + ], + "signature": [ + "(id: string, change?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined) => void" + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.RowOnChangeFn.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "A unique id corresponding to the particular setting being changed." + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-components-field-row", + "id": "def-common.RowOnChangeFn.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The {@link UnsavedFieldChange } corresponding to any unsaved change to the field." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/components/field_row/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx new file mode 100644 index 0000000000000..6f1d36d6db926 --- /dev/null +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnManagementSettingsComponentsFieldRowPluginApi +slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row +title: "@kbn/management-settings-components-field-row" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/management-settings-components-field-row plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] +--- +import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; + + + +Contact [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 26 | 0 | 7 | 0 | + +## Common + +### Functions + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_management_settings_field_definition.devdocs.json b/api_docs/kbn_management_settings_field_definition.devdocs.json new file mode 100644 index 0000000000000..d51791adc595a --- /dev/null +++ b/api_docs/kbn_management_settings_field_definition.devdocs.json @@ -0,0 +1,1087 @@ +{ + "id": "@kbn/management-settings-field-definition", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.getFieldDefinition", + "type": "Function", + "tags": [], + "label": "getFieldDefinition", + "description": [ + "\nCreate a {@link FieldDefinition} from a {@link UiSettingMetadata} object for use\nin the UI.\n" + ], + "signature": [ + "(parameters: GetDefinitionParams) => ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + " | null>" + ], + "path": "packages/kbn-management/settings/field_definition/get_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.getFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "parameters", + "description": [ + "The {@link GetDefinitionParams } for creating the {@link FieldDefinition }." + ], + "signature": [ + "GetDefinitionParams" + ], + "path": "packages/kbn-management/settings/field_definition/get_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.getFieldDefinitions", + "type": "Function", + "tags": [], + "label": "getFieldDefinitions", + "description": [ + "\nConvenience function to convert settings taken from a UiSettingsClient into\n{@link FieldDefinition} objects.\n" + ], + "signature": [ + "(settings: Record>, client: ", + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" + }, + ") => ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsType", + "text": "UiSettingsType" + }, + ", string | number | boolean | (string | number)[] | null | undefined>[]" + ], + "path": "packages/kbn-management/settings/field_definition/get_definitions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.getFieldDefinitions.$1", + "type": "Object", + "tags": [], + "label": "settings", + "description": [ + "The settings retreived from the UiSettingsClient." + ], + "signature": [ + "Record>" + ], + "path": "packages/kbn-management/settings/field_definition/get_definitions.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.getFieldDefinitions.$2", + "type": "Object", + "tags": [], + "label": "client", + "description": [ + "The client itself, used to determine if a setting is custom or overridden." + ], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" + } + ], + "path": "packages/kbn-management/settings/field_definition/get_definitions.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "An array of {@link FieldDefinition } objects." + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isArrayFieldDefinition", + "type": "Function", + "tags": [], + "label": "isArrayFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link ArrayFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ArrayFieldDefinition", + "text": "ArrayFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isArrayFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isArrayFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isArrayFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link ArrayUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ArrayUnsavedFieldChange", + "text": "ArrayUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isArrayFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isBooleanFieldDefinition", + "type": "Function", + "tags": [], + "label": "isBooleanFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link BooleanFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.BooleanFieldDefinition", + "text": "BooleanFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isBooleanFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isBooleanFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isBooleanFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link BooleanUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.BooleanUnsavedFieldChange", + "text": "BooleanUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isBooleanFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isColorFieldDefinition", + "type": "Function", + "tags": [], + "label": "isColorFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link ColorFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ColorFieldDefinition", + "text": "ColorFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isColorFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isColorFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isColorFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link ColorUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ColorUnsavedFieldChange", + "text": "ColorUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isColorFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isImageFieldDefinition", + "type": "Function", + "tags": [], + "label": "isImageFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link ImageFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ImageFieldDefinition", + "text": "ImageFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isImageFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isImageFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isImageFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link ImageUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ImageUnsavedFieldChange", + "text": "ImageUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isImageFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isJsonFieldDefinition", + "type": "Function", + "tags": [], + "label": "isJsonFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link JsonFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.JsonFieldDefinition", + "text": "JsonFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isJsonFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isJsonFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isJsonFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link JsonUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.JsonUnsavedFieldChange", + "text": "JsonUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isJsonFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isMarkdownFieldDefinition", + "type": "Function", + "tags": [], + "label": "isMarkdownFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link MarkdownFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.MarkdownFieldDefinition", + "text": "MarkdownFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isMarkdownFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isMarkdownFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isMarkdownFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link MarkdownUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.MarkdownUnsavedFieldChange", + "text": "MarkdownUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isMarkdownFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isNumberFieldDefinition", + "type": "Function", + "tags": [], + "label": "isNumberFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link NumberFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.NumberFieldDefinition", + "text": "NumberFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isNumberFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isNumberFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isNumberFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link NumberUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.NumberUnsavedFieldChange", + "text": "NumberUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isNumberFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isSelectFieldDefinition", + "type": "Function", + "tags": [], + "label": "isSelectFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link SelectFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.SelectFieldDefinition", + "text": "SelectFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isSelectFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isSelectFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isSelectFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link SelectUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.SelectUnsavedFieldChange", + "text": "SelectUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isSelectFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isStringFieldDefinition", + "type": "Function", + "tags": [], + "label": "isStringFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link StringFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.StringFieldDefinition", + "text": "StringFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isStringFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isStringFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isStringFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link StringUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.StringUnsavedFieldChange", + "text": "StringUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isStringFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isUndefinedFieldDefinition", + "type": "Function", + "tags": [], + "label": "isUndefinedFieldDefinition", + "description": [ + "\nReturns `true` if the given {@link FieldDefinition} is an {@link UndefinedFieldDefinition},\n`false` otherwise." + ], + "signature": [ + "(d: Definition) => d is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UndefinedFieldDefinition", + "text": "UndefinedFieldDefinition" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isUndefinedFieldDefinition.$1", + "type": "Object", + "tags": [], + "label": "d", + "description": [ + "The {@link FieldDefinition } to check." + ], + "signature": [ + "Definition" + ], + "path": "packages/kbn-management/settings/field_definition/is/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isUndefinedFieldUnsavedChange", + "type": "Function", + "tags": [], + "label": "isUndefinedFieldUnsavedChange", + "description": [ + "\nReturns `true` if the given {@link FieldUnsavedChange} is an {@link UndefinedUnsavedFieldChange},\n`false` otherwise." + ], + "signature": [ + "(c?: Change | undefined) => c is ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UndefinedUnsavedFieldChange", + "text": "UndefinedUnsavedFieldChange" + } + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-field-definition", + "id": "def-common.isUndefinedFieldUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "c", + "description": [ + "The {@link FieldUnsavedChange } to check." + ], + "signature": [ + "Change | undefined" + ], + "path": "packages/kbn-management/settings/field_definition/is/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx new file mode 100644 index 0000000000000..9e3c63985e3fe --- /dev/null +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnManagementSettingsFieldDefinitionPluginApi +slug: /kibana-dev-docs/api/kbn-management-settings-field-definition +title: "@kbn/management-settings-field-definition" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/management-settings-field-definition plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] +--- +import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; + + + +Contact [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 45 | 0 | 0 | 0 | + +## Common + +### Functions + + diff --git a/api_docs/kbn_management_settings_ids.devdocs.json b/api_docs/kbn_management_settings_ids.devdocs.json new file mode 100644 index 0000000000000..65f9349533995 --- /dev/null +++ b/api_docs/kbn_management_settings_ids.devdocs.json @@ -0,0 +1,1933 @@ +{ + "id": "@kbn/management-settings-ids", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.ACCESSIBILITY_DISABLE_ANIMATIONS_ID", + "type": "string", + "tags": [], + "label": "ACCESSIBILITY_DISABLE_ANIMATIONS_ID", + "description": [], + "signature": [ + "\"accessibility:disableAnimations\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.AUTOCOMPLETE_USE_TIME_RANGE_ID", + "type": "string", + "tags": [], + "label": "AUTOCOMPLETE_USE_TIME_RANGE_ID", + "description": [], + "signature": [ + "\"autocomplete:useTimeRange\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD_ID", + "type": "string", + "tags": [], + "label": "AUTOCOMPLETE_VALUE_SUGGESTION_METHOD_ID", + "description": [], + "signature": [ + "\"autocomplete:valueSuggestionMethod\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.BANNERS_BACKGROUND_COLOR_ID", + "type": "string", + "tags": [], + "label": "BANNERS_BACKGROUND_COLOR_ID", + "description": [], + "signature": [ + "\"banners:backgroundColor\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.BANNERS_PLACEMENT_ID", + "type": "string", + "tags": [], + "label": "BANNERS_PLACEMENT_ID", + "description": [], + "signature": [ + "\"banners:placement\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.BANNERS_TEXT_COLOR_ID", + "type": "string", + "tags": [], + "label": "BANNERS_TEXT_COLOR_ID", + "description": [], + "signature": [ + "\"banners:textColor\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.BANNERS_TEXT_CONTENT_ID", + "type": "string", + "tags": [], + "label": "BANNERS_TEXT_CONTENT_ID", + "description": [], + "signature": [ + "\"banners:textContent\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.CONTEXT_DEFAULT_SIZE_ID", + "type": "string", + "tags": [], + "label": "CONTEXT_DEFAULT_SIZE_ID", + "description": [], + "signature": [ + "\"context:defaultSize\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.CONTEXT_STEP_ID", + "type": "string", + "tags": [], + "label": "CONTEXT_STEP_ID", + "description": [], + "signature": [ + "\"context:step\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.CONTEXT_TIE_BREAKER_FIELDS_ID", + "type": "string", + "tags": [], + "label": "CONTEXT_TIE_BREAKER_FIELDS_ID", + "description": [], + "signature": [ + "\"context:tieBreakerFields\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.COURIER_CUSTOM_REQUEST_PREFERENCE_ID", + "type": "string", + "tags": [], + "label": "COURIER_CUSTOM_REQUEST_PREFERENCE_ID", + "description": [], + "signature": [ + "\"courier:customRequestPreference\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX_ID", + "type": "string", + "tags": [], + "label": "COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX_ID", + "description": [], + "signature": [ + "\"courier:ignoreFilterIfFieldNotInIndex\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.COURIER_MAX_CONCURRENT_SHARD_REQUEST_ID", + "type": "string", + "tags": [], + "label": "COURIER_MAX_CONCURRENT_SHARD_REQUEST_ID", + "description": [], + "signature": [ + "\"courier:maxConcurrentShardRequests\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.COURIER_SET_REQUEST_PREFERENCE_ID", + "type": "string", + "tags": [], + "label": "COURIER_SET_REQUEST_PREFERENCE_ID", + "description": [], + "signature": [ + "\"courier:setRequestPreference\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.CSV_QUOTE_VALUES_ID", + "type": "string", + "tags": [], + "label": "CSV_QUOTE_VALUES_ID", + "description": [], + "signature": [ + "\"csv:quoteValues\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.CSV_SEPARATOR_ID", + "type": "string", + "tags": [], + "label": "CSV_SEPARATOR_ID", + "description": [], + "signature": [ + "\"csv:separator\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DATE_FORMAT_DOW_ID", + "type": "string", + "tags": [], + "label": "DATE_FORMAT_DOW_ID", + "description": [], + "signature": [ + "\"dateFormat:dow\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DATE_FORMAT_ID", + "type": "string", + "tags": [], + "label": "DATE_FORMAT_ID", + "description": [], + "signature": [ + "\"dateFormat\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DATE_FORMAT_NANOS_ID", + "type": "string", + "tags": [], + "label": "DATE_FORMAT_NANOS_ID", + "description": [], + "signature": [ + "\"dateNanosFormat\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DATE_FORMAT_SCALED_ID", + "type": "string", + "tags": [], + "label": "DATE_FORMAT_SCALED_ID", + "description": [], + "signature": [ + "\"dateFormat:scaled\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DATE_FORMAT_TZ_ID", + "type": "string", + "tags": [], + "label": "DATE_FORMAT_TZ_ID", + "description": [], + "signature": [ + "\"dateFormat:tz\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DEFAULT_COLUMNS_ID", + "type": "string", + "tags": [], + "label": "DEFAULT_COLUMNS_ID", + "description": [], + "signature": [ + "\"defaultColumns\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DEFAULT_INDEX_ID", + "type": "string", + "tags": [], + "label": "DEFAULT_INDEX_ID", + "description": [], + "signature": [ + "\"defaultIndex\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DEFAULT_ROUTE_ID", + "type": "string", + "tags": [], + "label": "DEFAULT_ROUTE_ID", + "description": [], + "signature": [ + "\"defaultRoute\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISABLE_BATCH_COMPRESSION_ID", + "type": "string", + "tags": [], + "label": "DISABLE_BATCH_COMPRESSION_ID", + "description": [], + "signature": [ + "\"bfetch:disableCompression\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISABLE_REQUEST_BATCHING_ID", + "type": "string", + "tags": [], + "label": "DISABLE_REQUEST_BATCHING_ID", + "description": [], + "signature": [ + "\"bfetch:disable\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_ENABLE_SQL_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_ENABLE_SQL_ID", + "description": [], + "signature": [ + "\"discover:enableSql\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_MAX_DOC_FIELDS_DISPLAYED_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_MAX_DOC_FIELDS_DISPLAYED_ID", + "description": [], + "signature": [ + "\"discover:maxDocFieldsDisplayed\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_MODIFY_COLUMNS_ON_SWITCH_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_MODIFY_COLUMNS_ON_SWITCH_ID", + "description": [], + "signature": [ + "\"discover:modifyColumnsOnSwitch\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_ROW_HEIGHT_OPTION_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_ROW_HEIGHT_OPTION_ID", + "description": [], + "signature": [ + "\"discover:rowHeightOption\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SAMPLE_ROWS_PER_PAGE_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SAMPLE_ROWS_PER_PAGE_ID", + "description": [], + "signature": [ + "\"discover:sampleRowsPerPage\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SAMPLE_SIZE_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SAMPLE_SIZE_ID", + "description": [], + "signature": [ + "\"discover:sampleSize\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SEARCH_FIELDS_FROM_SOURCE_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SEARCH_FIELDS_FROM_SOURCE_ID", + "description": [], + "signature": [ + "\"discover:searchFieldsFromSource\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SEARCH_ON_PAGE_LOAD_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SEARCH_ON_PAGE_LOAD_ID", + "description": [], + "signature": [ + "\"discover:searchOnPageLoad\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SHOW_FIELD_STATISTICS_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SHOW_FIELD_STATISTICS_ID", + "description": [], + "signature": [ + "\"discover:showFieldStatistics\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SHOW_LEGACY_FIELD_TOP_VALUES_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SHOW_LEGACY_FIELD_TOP_VALUES_ID", + "description": [], + "signature": [ + "\"discover:showLegacyFieldTopValues\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SHOW_MULTI_FIELDS_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SHOW_MULTI_FIELDS_ID", + "description": [], + "signature": [ + "\"discover:showMultiFields\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DISCOVER_SORT_DEFAULT_ORDER_ID", + "type": "string", + "tags": [], + "label": "DISCOVER_SORT_DEFAULT_ORDER_ID", + "description": [], + "signature": [ + "\"discover:sort:defaultOrder\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DOC_TABLE_HIDE_TIME_COLUMNS_ID", + "type": "string", + "tags": [], + "label": "DOC_TABLE_HIDE_TIME_COLUMNS_ID", + "description": [], + "signature": [ + "\"doc_table:hideTimeColumn\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DOC_TABLE_HIGHLIGHT_ID", + "type": "string", + "tags": [], + "label": "DOC_TABLE_HIGHLIGHT_ID", + "description": [], + "signature": [ + "\"doc_table:highlight\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.DOC_TABLE_LEGACY_ID", + "type": "string", + "tags": [], + "label": "DOC_TABLE_LEGACY_ID", + "description": [], + "signature": [ + "\"doc_table:legacy\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FIELDS_POPULAR_LIMIT_ID", + "type": "string", + "tags": [], + "label": "FIELDS_POPULAR_LIMIT_ID", + "description": [], + "signature": [ + "\"fields:popularLimit\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FILE_UPLOAD_MAX_SIZE_ID", + "type": "string", + "tags": [], + "label": "FILE_UPLOAD_MAX_SIZE_ID", + "description": [], + "signature": [ + "\"fileUpload:maxFileSize\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FILTER_EDITOR_SUGGEST_VALUES_ID", + "type": "string", + "tags": [], + "label": "FILTER_EDITOR_SUGGEST_VALUES_ID", + "description": [], + "signature": [ + "\"filterEditor:suggestValues\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FILTERS_PINNED_BY_DEFAULT_ID", + "type": "string", + "tags": [], + "label": "FILTERS_PINNED_BY_DEFAULT_ID", + "description": [], + "signature": [ + "\"filters:pinnedByDefault\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FORMAT_BYTES_DEFAULT_PATTERN_ID", + "type": "string", + "tags": [], + "label": "FORMAT_BYTES_DEFAULT_PATTERN_ID", + "description": [], + "signature": [ + "\"format:bytes:defaultPattern\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FORMAT_CURRENCY_DEFAULT_PATTERN_ID", + "type": "string", + "tags": [], + "label": "FORMAT_CURRENCY_DEFAULT_PATTERN_ID", + "description": [], + "signature": [ + "\"format:currency:defaultPattern\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FORMAT_DEFAULT_TYPE_MAP_ID", + "type": "string", + "tags": [], + "label": "FORMAT_DEFAULT_TYPE_MAP_ID", + "description": [], + "signature": [ + "\"format:defaultTypeMap\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FORMAT_NUMBER_DEFAULT_LOCALE_ID", + "type": "string", + "tags": [], + "label": "FORMAT_NUMBER_DEFAULT_LOCALE_ID", + "description": [], + "signature": [ + "\"format:number:defaultLocale\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FORMAT_NUMBER_DEFAULT_PATTERN_ID", + "type": "string", + "tags": [], + "label": "FORMAT_NUMBER_DEFAULT_PATTERN_ID", + "description": [], + "signature": [ + "\"format:number:defaultPattern\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.FORMAT_PERCENT_DEFAULT_PATTERN_ID", + "type": "string", + "tags": [], + "label": "FORMAT_PERCENT_DEFAULT_PATTERN_ID", + "description": [], + "signature": [ + "\"format:percent:defaultPattern\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.HIDE_ANNOUNCEMENTS_ID", + "type": "string", + "tags": [], + "label": "HIDE_ANNOUNCEMENTS_ID", + "description": [], + "signature": [ + "\"hideAnnouncements\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.HISTOGRAM_BAR_TARGET_ID", + "type": "string", + "tags": [], + "label": "HISTOGRAM_BAR_TARGET_ID", + "description": [], + "signature": [ + "\"histogram:barTarget\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.HISTOGRAM_MAX_BARS_ID", + "type": "string", + "tags": [], + "label": "HISTOGRAM_MAX_BARS_ID", + "description": [], + "signature": [ + "\"histogram:maxBars\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.HISTORY_LIMIT_ID", + "type": "string", + "tags": [], + "label": "HISTORY_LIMIT_ID", + "description": [], + "signature": [ + "\"history:limit\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.LABS_CANVAS_BY_VALUE_EMBEDDABLE_ID", + "type": "string", + "tags": [], + "label": "LABS_CANVAS_BY_VALUE_EMBEDDABLE_ID", + "description": [], + "signature": [ + "\"labs:canvas:byValueEmbeddable\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.LABS_CANVAS_ENABLE_UI_ID", + "type": "string", + "tags": [], + "label": "LABS_CANVAS_ENABLE_UI_ID", + "description": [], + "signature": [ + "\"labs:canvas:enable_ui\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.LABS_DASHBOARD_CONTROLS_ID", + "type": "string", + "tags": [], + "label": "LABS_DASHBOARD_CONTROLS_ID", + "description": [], + "signature": [ + "\"labs:dashboard:dashboardControls\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.LABS_DASHBOARD_DEFER_BELOW_FOLD_ID", + "type": "string", + "tags": [], + "label": "LABS_DASHBOARD_DEFER_BELOW_FOLD_ID", + "description": [], + "signature": [ + "\"labs:dashboard:deferBelowFold\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.LABS_DASHBOARDS_ENABLE_UI_ID", + "type": "string", + "tags": [], + "label": "LABS_DASHBOARDS_ENABLE_UI_ID", + "description": [], + "signature": [ + "\"labs:dashboard:enable_ui\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.META_FIELDS_ID", + "type": "string", + "tags": [], + "label": "META_FIELDS_ID", + "description": [], + "signature": [ + "\"metaFields\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.METRICS_ALLOW_CHECKING_FOR_FAILED_SHARDS_ID", + "type": "string", + "tags": [], + "label": "METRICS_ALLOW_CHECKING_FOR_FAILED_SHARDS_ID", + "description": [], + "signature": [ + "\"metrics:allowCheckingForFailedShards\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.METRICS_ALLOW_STRING_INDICES_ID", + "type": "string", + "tags": [], + "label": "METRICS_ALLOW_STRING_INDICES_ID", + "description": [], + "signature": [ + "\"metrics:allowStringIndices\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.METRICS_MAX_BUCKETS_ID", + "type": "string", + "tags": [], + "label": "METRICS_MAX_BUCKETS_ID", + "description": [], + "signature": [ + "\"metrics:max_buckets\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.ML_ANOMALY_DETECTION_RESULTS_ENABLE_TIME_DEFAULTS_ID", + "type": "string", + "tags": [], + "label": "ML_ANOMALY_DETECTION_RESULTS_ENABLE_TIME_DEFAULTS_ID", + "description": [], + "signature": [ + "\"ml:anomalyDetection:results:enableTimeDefaults\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.ML_ANOMALY_DETECTION_RESULTS_TIME_DEFAULTS_ID", + "type": "string", + "tags": [], + "label": "ML_ANOMALY_DETECTION_RESULTS_TIME_DEFAULTS_ID", + "description": [], + "signature": [ + "\"ml:anomalyDetection:results:timeDefaults\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.NOTIFICATIONS_BANNER_ID", + "type": "string", + "tags": [], + "label": "NOTIFICATIONS_BANNER_ID", + "description": [], + "signature": [ + "\"notifications:banner\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.NOTIFICATIONS_LIFETIME_BANNER_ID", + "type": "string", + "tags": [], + "label": "NOTIFICATIONS_LIFETIME_BANNER_ID", + "description": [], + "signature": [ + "\"notifications:lifetime:banner\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.NOTIFICATIONS_LIFETIME_ERROR_ID", + "type": "string", + "tags": [], + "label": "NOTIFICATIONS_LIFETIME_ERROR_ID", + "description": [], + "signature": [ + "\"notifications:lifetime:error\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.NOTIFICATIONS_LIFETIME_INFO_ID", + "type": "string", + "tags": [], + "label": "NOTIFICATIONS_LIFETIME_INFO_ID", + "description": [], + "signature": [ + "\"notifications:lifetime:info\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.NOTIFICATIONS_LIFETIME_WARNING_ID", + "type": "string", + "tags": [], + "label": "NOTIFICATIONS_LIFETIME_WARNING_ID", + "description": [], + "signature": [ + "\"notifications:lifetime:warning\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_AGENT_EXPLORER_VIEW_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_AGENT_EXPLORER_VIEW_ID", + "description": [], + "signature": [ + "\"observability:apmAgentExplorerView\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_AWS_LAMBDA_PRICE_FACTOR_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_AWS_LAMBDA_PRICE_FACTOR_ID", + "description": [], + "signature": [ + "\"observability:apmAWSLambdaPriceFactor\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_AWS_LAMBDA_REQUEST_COST_PER_MILLION_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_AWS_LAMBDA_REQUEST_COST_PER_MILLION_ID", + "description": [], + "signature": [ + "\"observability:apmAWSLambdaRequestCostPerMillion\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_DEFAULT_SERVICE_ENVIRONMENT_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_DEFAULT_SERVICE_ENVIRONMENT_ID", + "description": [], + "signature": [ + "\"observability:apmDefaultServiceEnvironment\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_ENABLE_CRITICAL_PATH_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_ENABLE_CRITICAL_PATH_ID", + "description": [], + "signature": [ + "\"observability:apmEnableCriticalPath\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_LABS_BUTTON_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_LABS_BUTTON_ID", + "description": [], + "signature": [ + "\"observability:apmLabsButton\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_PROGRESSIVE_LOADING_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_PROGRESSIVE_LOADING_ID", + "description": [], + "signature": [ + "\"observability:apmProgressiveLoading\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_SERVICE_GROUP_MAX_NUMBER_OF_SERVCIE_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_SERVICE_GROUP_MAX_NUMBER_OF_SERVCIE_ID", + "description": [], + "signature": [ + "\"observability:apmServiceGroupMaxNumberOfServices\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_SERVICE_INVENTORY_OPTIMIZED_SORTING_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_SERVICE_INVENTORY_OPTIMIZED_SORTING_ID", + "description": [], + "signature": [ + "\"observability:apmServiceInventoryOptimizedSorting\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_APM_TRACE_EXPLORER_TAB_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_APM_TRACE_EXPLORER_TAB_ID", + "description": [], + "signature": [ + "\"observability:apmTraceExplorerTab\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_ENABLE_AWS_LAMBDA_METRICS_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_ENABLE_AWS_LAMBDA_METRICS_ID", + "description": [], + "signature": [ + "\"observability:enableAwsLambdaMetrics\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_ENABLE_COMPARISON_BY_DEFAULT_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_ENABLE_COMPARISON_BY_DEFAULT_ID", + "description": [], + "signature": [ + "\"observability:enableComparisonByDefault\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_VIEW_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_VIEW_ID", + "description": [], + "signature": [ + "\"observability:enableInfrastructureHostsView\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_ENABLE_INSPECT_ES_QUERIES_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_ENABLE_INSPECT_ES_QUERIES_ID", + "description": [], + "signature": [ + "\"observability:enableInspectEsQueries\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_MAX_SUGGESTIONS_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_MAX_SUGGESTIONS_ID", + "description": [], + "signature": [ + "\"observability:maxSuggestions\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_PROFILING_ELASTICSEARCH_PLUGIN_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_PROFILING_ELASTICSEARCH_PLUGIN_ID", + "description": [], + "signature": [ + "\"observability:profilingElasticsearchPlugin\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.QUERY_ALLOW_LEADING_WILDCARDS_ID", + "type": "string", + "tags": [], + "label": "QUERY_ALLOW_LEADING_WILDCARDS_ID", + "description": [], + "signature": [ + "\"query:allowLeadingWildcards\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.QUERY_STRING_OPTIONS_ID", + "type": "string", + "tags": [], + "label": "QUERY_STRING_OPTIONS_ID", + "description": [], + "signature": [ + "\"query:queryString:options\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.ROLLUPS_ENABLE_INDEX_PATTERNS_ID", + "type": "string", + "tags": [], + "label": "ROLLUPS_ENABLE_INDEX_PATTERNS_ID", + "description": [], + "signature": [ + "\"rollups.enableIndexPatterns\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SAVED_OBJECTS_LISTING_LIMIT_ID", + "type": "string", + "tags": [], + "label": "SAVED_OBJECTS_LISTING_LIMIT_ID", + "description": [], + "signature": [ + "\"savedObjects:listingLimit\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SAVED_OBJECTS_PER_PAGE_ID", + "type": "string", + "tags": [], + "label": "SAVED_OBJECTS_PER_PAGE_ID", + "description": [], + "signature": [ + "\"savedObjects:perPage\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SEARCH_INCLUDE_FROZEN_ID", + "type": "string", + "tags": [], + "label": "SEARCH_INCLUDE_FROZEN_ID", + "description": [], + "signature": [ + "\"search:includeFrozen\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SEARCH_QUERY_LANGUAGE_ID", + "type": "string", + "tags": [], + "label": "SEARCH_QUERY_LANGUAGE_ID", + "description": [], + "signature": [ + "\"search:queryLanguage\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SEARCH_TIMEOUT_ID", + "type": "string", + "tags": [], + "label": "SEARCH_TIMEOUT_ID", + "description": [], + "signature": [ + "\"search:timeout\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_DEFAULT_ANOMALY_SCORE_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_DEFAULT_ANOMALY_SCORE_ID", + "description": [], + "signature": [ + "\"securitySolution:defaultAnomalyScore\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_DEFAULT_INDEX_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_DEFAULT_INDEX_ID", + "description": [], + "signature": [ + "\"securitySolution:defaultIndex\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_DEFAULT_THREAT_INDEX_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_DEFAULT_THREAT_INDEX_ID", + "description": [], + "signature": [ + "\"securitySolution:defaultThreatIndex\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_ENABLE_CCS_WARNING_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_ENABLE_CCS_WARNING_ID", + "description": [], + "signature": [ + "\"securitySolution:enableCcsWarning\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_ENABLE_GROUPED_NAV_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_ENABLE_GROUPED_NAV_ID", + "description": [], + "signature": [ + "\"securitySolution:enableGroupedNav\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_ENABLE_NEWS_FEED_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_ENABLE_NEWS_FEED_ID", + "description": [], + "signature": [ + "\"securitySolution:enableNewsFeed\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_IP_REPUTATION_LINKS_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_IP_REPUTATION_LINKS_ID", + "description": [], + "signature": [ + "\"securitySolution:ipReputationLinks\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_NEWS_FEED_URL_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_NEWS_FEED_URL_ID", + "description": [], + "signature": [ + "\"securitySolution:newsFeedUrl\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_REFRESH_INTERVAL_DEFAULTS_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_REFRESH_INTERVAL_DEFAULTS_ID", + "description": [], + "signature": [ + "\"securitySolution:refreshIntervalDefaults\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_RULES_TABLE_REFRESH_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_RULES_TABLE_REFRESH_ID", + "description": [], + "signature": [ + "\"securitySolution:rulesTableRefresh\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID", + "description": [], + "signature": [ + "\"securitySolution:showRelatedIntegrations\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SECURITY_SOLUTION_TIME_DEFAULTS_ID", + "type": "string", + "tags": [], + "label": "SECURITY_SOLUTION_TIME_DEFAULTS_ID", + "description": [], + "signature": [ + "\"securitySolution:timeDefaults\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SHORT_DOTS_ENABLE_ID", + "type": "string", + "tags": [], + "label": "SHORT_DOTS_ENABLE_ID", + "description": [], + "signature": [ + "\"shortDots:enable\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.SORT_OPTIONS_ID", + "type": "string", + "tags": [], + "label": "SORT_OPTIONS_ID", + "description": [], + "signature": [ + "\"sort:options\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.STATE_STORE_IN_SESSION_STORAGE_ID", + "type": "string", + "tags": [], + "label": "STATE_STORE_IN_SESSION_STORAGE_ID", + "description": [], + "signature": [ + "\"state:storeInSessionStorage\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.THEME_DARK_MODE_ID", + "type": "string", + "tags": [], + "label": "THEME_DARK_MODE_ID", + "description": [], + "signature": [ + "\"theme:darkMode\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMELION_ES_DEFAULT_INDEX_ID", + "type": "string", + "tags": [], + "label": "TIMELION_ES_DEFAULT_INDEX_ID", + "description": [], + "signature": [ + "\"timelion:es.default_index\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMELION_ES_TIME_FIELD_ID", + "type": "string", + "tags": [], + "label": "TIMELION_ES_TIME_FIELD_ID", + "description": [], + "signature": [ + "\"timelion:es.timefield\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMELION_MAX_BUCKETS_ID", + "type": "string", + "tags": [], + "label": "TIMELION_MAX_BUCKETS_ID", + "description": [], + "signature": [ + "\"timelion:max_buckets\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMELION_MIN_INTERVAL_ID", + "type": "string", + "tags": [], + "label": "TIMELION_MIN_INTERVAL_ID", + "description": [], + "signature": [ + "\"timelion:min_interval\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMELION_TARGET_BUCKETS_ID", + "type": "string", + "tags": [], + "label": "TIMELION_TARGET_BUCKETS_ID", + "description": [], + "signature": [ + "\"timelion:target_buckets\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMEPICKER_QUICK_RANGES_ID", + "type": "string", + "tags": [], + "label": "TIMEPICKER_QUICK_RANGES_ID", + "description": [], + "signature": [ + "\"timepicker:quickRanges\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS_ID", + "type": "string", + "tags": [], + "label": "TIMEPICKER_REFRESH_INTERVAL_DEFAULTS_ID", + "description": [], + "signature": [ + "\"timepicker:refreshIntervalDefaults\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TIMEPICKER_TIME_DEFAULTS_ID", + "type": "string", + "tags": [], + "label": "TIMEPICKER_TIME_DEFAULTS_ID", + "description": [], + "signature": [ + "\"timepicker:timeDefaults\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.TRUNCATE_MAX_HEIGHT_ID", + "type": "string", + "tags": [], + "label": "TRUNCATE_MAX_HEIGHT_ID", + "description": [], + "signature": [ + "\"truncate:maxHeight\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.VISUALIZATION_COLOR_MAPPING_ID", + "type": "string", + "tags": [], + "label": "VISUALIZATION_COLOR_MAPPING_ID", + "description": [], + "signature": [ + "\"visualization:colorMapping\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.VISUALIZATION_ENABLE_LABS_ID", + "type": "string", + "tags": [], + "label": "VISUALIZATION_ENABLE_LABS_ID", + "description": [], + "signature": [ + "\"visualize:enableLabs\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.VISUALIZATION_HEATMAP_MAX_BUCKETS_ID", + "type": "string", + "tags": [], + "label": "VISUALIZATION_HEATMAP_MAX_BUCKETS_ID", + "description": [], + "signature": [ + "\"visualization:heatmap:maxBuckets\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.VISUALIZATION_LEGACY_GAUGE_CHARTS_LIBRARY_ID", + "type": "string", + "tags": [], + "label": "VISUALIZATION_LEGACY_GAUGE_CHARTS_LIBRARY_ID", + "description": [], + "signature": [ + "\"visualization:visualize:legacyGaugeChartsLibrary\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.VISUALIZATION_LEGACY_HEATMAP_CHARTS_LIBRARY_ID", + "type": "string", + "tags": [], + "label": "VISUALIZATION_LEGACY_HEATMAP_CHARTS_LIBRARY_ID", + "description": [], + "signature": [ + "\"visualization:visualize:legacyHeatmapChartsLibrary\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.VISUALIZATION_USE_LEGACY_TIME_AXIS_ID", + "type": "string", + "tags": [], + "label": "VISUALIZATION_USE_LEGACY_TIME_AXIS_ID", + "description": [], + "signature": [ + "\"visualization:useLegacyTimeAxis\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.XPACK_REPORTING_CUSTOM_PDF_LOGO_ID", + "type": "string", + "tags": [], + "label": "XPACK_REPORTING_CUSTOM_PDF_LOGO_ID", + "description": [], + "signature": [ + "\"xpackReporting:customPdfLogo\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx new file mode 100644 index 0000000000000..485a06a4fe724 --- /dev/null +++ b/api_docs/kbn_management_settings_ids.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnManagementSettingsIdsPluginApi +slug: /kibana-dev-docs/api/kbn-management-settings-ids +title: "@kbn/management-settings-ids" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/management-settings-ids plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] +--- +import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; + + + +Contact [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 127 | 0 | 127 | 0 | + +## Common + +### Consts, variables and types + + diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 513f1eb0dfe90..11b13f11b9125 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; diff --git a/api_docs/kbn_management_settings_types.devdocs.json b/api_docs/kbn_management_settings_types.devdocs.json new file mode 100644 index 0000000000000..1cb92153d5159 --- /dev/null +++ b/api_docs/kbn_management_settings_types.devdocs.json @@ -0,0 +1,1631 @@ +{ + "id": "@kbn/management-settings-types", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition", + "type": "Interface", + "tags": [], + "label": "FieldDefinition", + "description": [ + "\nA {@link FieldDefinition} adapts a {@link UiSettingMetadata} object to be more\neasily consumed by the UI. It contains additional information about the field\nthat is determined from a given UiSettingMetadata object, (which is a type\nrepresenting a UiSetting)." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.ariaAttributes", + "type": "Object", + "tags": [], + "label": "ariaAttributes", + "description": [ + "UX ARIA attributes derived from the setting." + ], + "signature": [ + "{ ariaLabel: string; ariaDescribedBy?: string | undefined; }" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.categories", + "type": "Array", + "tags": [], + "label": "categories", + "description": [ + "A list of categories related to the field." + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.defaultValue", + "type": "Uncategorized", + "tags": [], + "label": "defaultValue", + "description": [ + "The default value of the field from Kibana." + ], + "signature": [ + "V | undefined" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.defaultValueDisplay", + "type": "string", + "tags": [], + "label": "defaultValueDisplay", + "description": [ + "The text-based display of the default value, for use in the UI." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.deprecation", + "type": "Object", + "tags": [ + "see" + ], + "label": "deprecation", + "description": [ + "\nDeprecation information for the field" + ], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.DeprecationSettings", + "text": "DeprecationSettings" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.description", + "type": "CompoundType", + "tags": [], + "label": "description", + "description": [ + "A description of the field." + ], + "signature": [ + "string | React.ReactElement> | undefined" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.displayName", + "type": "string", + "tags": [], + "label": "displayName", + "description": [ + "The name of the field suitable for display in the UX." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.groupId", + "type": "string", + "tags": [], + "label": "groupId", + "description": [ + "The grouping identifier for the field." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "The unique identifier of the field, typically separated by `:`" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.isCustom", + "type": "boolean", + "tags": [], + "label": "isCustom", + "description": [ + "True if the field is a custom setting, false otherwise." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.isDefaultValue", + "type": "boolean", + "tags": [], + "label": "isDefaultValue", + "description": [ + "True if the current saved setting matches the default setting." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.isOverridden", + "type": "boolean", + "tags": [], + "label": "isOverridden", + "description": [ + "True if the setting is overridden in Kibana, false otherwise." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.isReadOnly", + "type": "boolean", + "tags": [], + "label": "isReadOnly", + "description": [ + "True if the setting is read-only, false otherwise." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.metric", + "type": "Object", + "tags": [], + "label": "metric", + "description": [ + "Metric information when one interacts with the field." + ], + "signature": [ + "{ name?: string | undefined; type?: string | undefined; } | undefined" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.name", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "The name of the field suitable for use in the UX." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.options", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "Option information if the field represents a `select` setting." + ], + "signature": [ + "{ values: string[] | number[]; labels: Record; } | undefined" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.order", + "type": "number", + "tags": [], + "label": "order", + "description": [ + "A rank order for the field relative to other fields." + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [ + "True if the browser must be reloaded for the setting to take effect, false otherwise." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.savedValue", + "type": "Uncategorized", + "tags": [], + "label": "savedValue", + "description": [ + "The current saved value of the setting." + ], + "signature": [ + "V | undefined" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.type", + "type": "Uncategorized", + "tags": [ + "see" + ], + "label": "type", + "description": [ + "\nThe type of setting the field represents." + ], + "signature": [ + "T" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.FieldDefinition.unsavedFieldId", + "type": "string", + "tags": [], + "label": "unsavedFieldId", + "description": [ + "An identifier of the field when it has an unsaved change." + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.SelectFieldDefinition", + "type": "Interface", + "tags": [], + "label": "SelectFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `select` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.SelectFieldDefinition", + "text": "SelectFieldDefinition" + }, + " extends ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"select\", string | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.SelectFieldDefinition.options", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "Options are required when this definition is used." + ], + "signature": [ + "{ values: string[] | number[]; labels: Record; }" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UiSettingMetadata", + "type": "Interface", + "tags": [], + "label": "UiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} type, whose fields\nare not only optional, but also not strongly typed to\n{@link @kbn/core-ui-settings-common#UiSettingsType}.\n" + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + " extends ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSetting", + "text": "UiSetting" + }, + "" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UiSettingMetadata.type", + "type": "Uncategorized", + "tags": [ + "see" + ], + "label": "type", + "description": [ + "\nThe type of setting being represented." + ], + "signature": [ + "T" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UiSettingMetadata.value", + "type": "Uncategorized", + "tags": [], + "label": "value", + "description": [ + "The default value in Kibana for the setting." + ], + "signature": [ + "V | undefined" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UiSettingMetadata.userValue", + "type": "Uncategorized", + "tags": [], + "label": "userValue", + "description": [ + "The value saved by the user." + ], + "signature": [ + "V | undefined" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UnsavedFieldChange", + "type": "Interface", + "tags": [], + "label": "UnsavedFieldChange", + "description": [ + "\nA {@link UnsavedFieldChange} represents local changes to a field that have not\nyet been saved." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UnsavedFieldChange.type", + "type": "Uncategorized", + "tags": [ + "see" + ], + "label": "type", + "description": [ + "\nThe type of setting." + ], + "signature": [ + "T" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UnsavedFieldChange.error", + "type": "CompoundType", + "tags": [], + "label": "error", + "description": [ + "An error message, if any, from the change." + ], + "signature": [ + "string | null | undefined" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UnsavedFieldChange.isInvalid", + "type": "CompoundType", + "tags": [], + "label": "isInvalid", + "description": [ + "True if the change is invalid for the field, false otherwise." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UnsavedFieldChange.unsavedValue", + "type": "CompoundType", + "tags": [], + "label": "unsavedValue", + "description": [ + "The current unsaved value stored in the field." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.KnownTypeToValue", + "text": "KnownTypeToValue" + }, + " | null | undefined" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ArrayFieldDefinition", + "type": "Type", + "tags": [], + "label": "ArrayFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `array` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"array\", (string | number)[] | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ArraySettingType", + "type": "Type", + "tags": [], + "label": "ArraySettingType", + "description": [ + "\nA narrowing type representing all {@link SettingType} values that correspond\nto an `array` primitive type value." + ], + "signature": [ + "\"array\"" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ArrayUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "ArrayUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `array` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"array\", (string | number)[] | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ArrayUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "ArrayUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `number` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"array\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.BooleanFieldDefinition", + "type": "Type", + "tags": [], + "label": "BooleanFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `boolean` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"boolean\", boolean | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.BooleanSettingType", + "type": "Type", + "tags": [], + "label": "BooleanSettingType", + "description": [ + "\nA narrowing type representing all {@link SettingType} values that correspond\nto an `boolean` primitive type value." + ], + "signature": [ + "\"boolean\"" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.BooleanUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "BooleanUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `boolean` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"boolean\", boolean | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.BooleanUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "BooleanUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `boolean` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"boolean\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ColorFieldDefinition", + "type": "Type", + "tags": [], + "label": "ColorFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `color` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"color\", string | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ColorUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "ColorUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `color` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"color\", string | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ColorUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "ColorUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `color` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"color\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ImageFieldDefinition", + "type": "Type", + "tags": [], + "label": "ImageFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `image` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"image\", string | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ImageUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "ImageUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `image` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"image\", string | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ImageUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "ImageUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `image` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"image\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.JsonFieldDefinition", + "type": "Type", + "tags": [], + "label": "JsonFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `json` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"json\", string | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.JsonUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "JsonUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `json` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"json\", string | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.JsonUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "JsonUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `json` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"json\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.KnownTypeToMetadata", + "type": "Type", + "tags": [], + "label": "KnownTypeToMetadata", + "description": [ + "\nThis is a narrowing type, which finds the correct {@link UiSettingMetadata}\ntype based on a given {@link SettingType}." + ], + "signature": [ + "T extends \"array\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ArrayUiSettingMetadata", + "text": "ArrayUiSettingMetadata" + }, + " : T extends \"boolean\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.BooleanUiSettingMetadata", + "text": "BooleanUiSettingMetadata" + }, + " : T extends \"color\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ColorUiSettingMetadata", + "text": "ColorUiSettingMetadata" + }, + " : T extends \"image\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.ImageUiSettingMetadata", + "text": "ImageUiSettingMetadata" + }, + " : T extends \"json\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.JsonUiSettingMetadata", + "text": "JsonUiSettingMetadata" + }, + " : T extends \"markdown\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.MarkdownUiSettingMetadata", + "text": "MarkdownUiSettingMetadata" + }, + " : T extends \"number\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.NumberUiSettingMetadata", + "text": "NumberUiSettingMetadata" + }, + " : T extends \"select\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.SelectUiSettingMetadata", + "text": "SelectUiSettingMetadata" + }, + " : T extends \"string\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.StringUiSettingMetadata", + "text": "StringUiSettingMetadata" + }, + " : T extends \"undefined\" ? ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UndefinedUiSettingMetadata", + "text": "UndefinedUiSettingMetadata" + }, + " : never" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.KnownTypeToValue", + "type": "Type", + "tags": [], + "label": "KnownTypeToValue", + "description": [ + "\nThis is a narrowing type, which finds the correct primitive type based on a\ngiven {@link SettingType}." + ], + "signature": [ + "T extends \"string\" | \"color\" | \"image\" | \"select\" | \"json\" | \"markdown\" ? string : T extends \"boolean\" ? boolean : T extends \"number\" | \"bigint\" ? number : T extends \"array\" ? (string | number)[] : T extends \"undefined\" ? undefined : never" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.MarkdownFieldDefinition", + "type": "Type", + "tags": [], + "label": "MarkdownFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `markdown` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"markdown\", string | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.MarkdownUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "MarkdownUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `markdown` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"markdown\", string | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.MarkdownUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "MarkdownUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `markdown` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"markdown\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.NumberFieldDefinition", + "type": "Type", + "tags": [], + "label": "NumberFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `number` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"number\", number | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.NumberSettingType", + "type": "Type", + "tags": [], + "label": "NumberSettingType", + "description": [ + "\nA narrowing type representing all {@link SettingType} values that correspond\nto an `number` primitive type value." + ], + "signature": [ + "\"number\"" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.NumberUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "NumberUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `number` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"number\", number | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.NumberUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "NumberUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `number` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"number\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.OnChangeFn", + "type": "Type", + "tags": [], + "label": "OnChangeFn", + "description": [ + "\nA function that is called when the value of a {@link FieldInput} changes." + ], + "signature": [ + "(change?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined) => void" + ], + "path": "packages/kbn-management/settings/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.OnChangeFn.$1", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The {@link UnsavedFieldChange } passed to the handler." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.ResetInputRef", + "type": "Type", + "tags": [], + "label": "ResetInputRef", + "description": [ + "\nA React `ref` that indicates an input can be reset using an\nimperative handle." + ], + "signature": [ + "{ reset: () => void; } | null" + ], + "path": "packages/kbn-management/settings/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.SelectUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "SelectUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `select` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"select\", string | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.SelectUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "SelectUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `select` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"select\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.SettingType", + "type": "Type", + "tags": [], + "label": "SettingType", + "description": [ + "\nThis is a local type equivalent to {@link UiSettingsType} for flexibility." + ], + "signature": [ + "\"string\" | \"number\" | \"boolean\" | \"undefined\" | \"color\" | \"image\" | \"select\" | \"json\" | \"markdown\" | \"array\"" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.StringFieldDefinition", + "type": "Type", + "tags": [], + "label": "StringFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `string` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"string\", string | null>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.StringSettingType", + "type": "Type", + "tags": [], + "label": "StringSettingType", + "description": [ + "\nA narrowing type representing all {@link SettingType} values that correspond\nto an `string` primitive type value." + ], + "signature": [ + "\"string\" | \"color\" | \"image\" | \"select\" | \"json\" | \"markdown\"" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.StringUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "StringUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `string` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"string\", string | null>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.StringUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "StringUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `string` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"string\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UiSetting", + "type": "Type", + "tags": [], + "label": "UiSetting", + "description": [ + "\nCreating this type based on {@link UiSettingsClientCommon} and exporting for ease." + ], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.PublicUiSettingsParams", + "text": "PublicUiSettingsParams" + }, + " & ", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UserProvidedValues", + "text": "UserProvidedValues" + }, + "" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UndefinedFieldDefinition", + "type": "Type", + "tags": [], + "label": "UndefinedFieldDefinition", + "description": [ + "\nThis is a {@link FieldDefinition} representing {@link UiSetting} `undefined` type\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + "<\"undefined\", null | undefined>" + ], + "path": "packages/kbn-management/settings/types/field_definition.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UndefinedSettingType", + "type": "Type", + "tags": [], + "label": "UndefinedSettingType", + "description": [ + "\nA narrowing type representing all {@link SettingType} values that correspond\nto an `undefined` type value." + ], + "signature": [ + "\"undefined\"" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UndefinedUiSettingMetadata", + "type": "Type", + "tags": [], + "label": "UndefinedUiSettingMetadata", + "description": [ + "\nThis is an type-safe abstraction over the {@link UiSetting} `undefined` type." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<\"undefined\", null | undefined>" + ], + "path": "packages/kbn-management/settings/types/metadata.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.UndefinedUnsavedFieldChange", + "type": "Type", + "tags": [], + "label": "UndefinedUnsavedFieldChange", + "description": [ + "\nThis is a {@link UnsavedFieldChange} representing an unsaved change to a\n{@link FieldDefinition} which has a {@link UiSetting} `undefined` value\nfor use in the UI." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + "<\"undefined\">" + ], + "path": "packages/kbn-management/settings/types/unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-types", + "id": "def-common.Value", + "type": "Type", + "tags": [], + "label": "Value", + "description": [ + "\nA type representing all possible values corresponding to a given {@link SettingType}." + ], + "signature": [ + "string | number | boolean | (string | number)[] | null | undefined" + ], + "path": "packages/kbn-management/settings/types/setting_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx new file mode 100644 index 0000000000000..f8e1ad30cced8 --- /dev/null +++ b/api_docs/kbn_management_settings_types.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnManagementSettingsTypesPluginApi +slug: /kibana-dev-docs/api/kbn-management-settings-types +title: "@kbn/management-settings-types" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/management-settings-types plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] +--- +import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; + + + +Contact [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 75 | 0 | 0 | 0 | + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_management_settings_utilities.devdocs.json b/api_docs/kbn_management_settings_utilities.devdocs.json new file mode 100644 index 0000000000000..5412a77029610 --- /dev/null +++ b/api_docs/kbn_management_settings_utilities.devdocs.json @@ -0,0 +1,1125 @@ +{ + "id": "@kbn/management-settings-utilities", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `array` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"array\">, change: C<\"array\"> | undefined) => [string[], boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `array` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"array\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `array` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"array\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `color` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"color\">, change: C<\"color\"> | undefined) => [string, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `color` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"color\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `color` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"color\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `boolean` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"boolean\">, change: C<\"boolean\"> | undefined) => [boolean, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `boolean` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"boolean\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `boolean` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"boolean\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `image` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"image\">, change: C<\"image\"> | undefined) => [string, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `image` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"image\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `image` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"image\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `json` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"json\">, change: C<\"json\"> | undefined) => [string, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `json` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"json\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `json` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"json\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `markdown` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"markdown\">, change: C<\"markdown\"> | undefined) => [string, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `markdown` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"markdown\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `markdown` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"markdown\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `number` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"number\">, change: C<\"number\"> | undefined) => [number, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `number` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"number\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `number` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"number\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `select` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"select\">, change: C<\"select\"> | undefined) => [string, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `select` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"select\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `select` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"select\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `string` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"string\">, change: C<\"string\"> | undefined) => [string, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `string` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"string\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `string` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"string\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function to compare an `undefined` {@link FieldDefinition} and its {@link UnsavedFieldChange},\n" + ], + "signature": [ + "(field: F<\"undefined\">, change: C<\"undefined\"> | undefined) => [string | null | undefined, boolean]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The `undefined` {@link FieldDefinition } to compare." + ], + "signature": [ + "F<\"undefined\">" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The `undefined` {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C<\"undefined\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue", + "type": "Function", + "tags": [], + "label": "getFieldInputValue", + "description": [ + "\nConvenience function that, given a {@link FieldDefinition} and an {@link UnsavedFieldChange},\nreturns the value to be displayed in the input field, and a boolean indicating whether the\nvalue is an unsaved value.\n" + ], + "signature": [ + "(field: F, change: C | undefined) => (boolean | ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.KnownTypeToValue", + "text": "KnownTypeToValue" + }, + " | null | undefined)[]" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The {@link FieldDefinition } to compare." + ], + "signature": [ + "F" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.getFieldInputValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The {@link UnsavedFieldChange } to compare." + ], + "signature": [ + "C | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/get_input_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.hasUnsavedChange", + "type": "Function", + "tags": [], + "label": "hasUnsavedChange", + "description": [ + "\nCompares a given {@link FieldDefinition} to an {@link UnsavedFieldChange} to determine\nif the field has an unsaved change in the UI.\n" + ], + "signature": [ + "(field: Pick<", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + " | null>, \"defaultValue\" | \"savedValue\">, unsavedChange?: Pick<", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + ", \"unsavedValue\"> | undefined) => boolean" + ], + "path": "packages/kbn-management/settings/utilities/field/has_unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.hasUnsavedChange.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The field to compare." + ], + "signature": [ + "Pick<", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.FieldDefinition", + "text": "FieldDefinition" + }, + " | null>, \"defaultValue\" | \"savedValue\">" + ], + "path": "packages/kbn-management/settings/utilities/field/has_unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.hasUnsavedChange.$2", + "type": "Object", + "tags": [], + "label": "unsavedChange", + "description": [ + "The unsaved change to compare." + ], + "signature": [ + "Pick<", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + ", \"unsavedValue\"> | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/has_unsaved_change.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.isFieldDefaultValue", + "type": "Function", + "tags": [], + "label": "isFieldDefaultValue", + "description": [ + "\nUtility function to determine if a given value is equal to the default value of\na {@link FieldDefinition}.\n" + ], + "signature": [ + "(field: F, change: C | undefined) => boolean" + ], + "path": "packages/kbn-management/settings/utilities/field/is_default_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.isFieldDefaultValue.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The field to compare." + ], + "signature": [ + "F" + ], + "path": "packages/kbn-management/settings/utilities/field/is_default_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.isFieldDefaultValue.$2", + "type": "Object", + "tags": [], + "label": "change", + "description": [ + "The unsaved change to compare." + ], + "signature": [ + "C | undefined" + ], + "path": "packages/kbn-management/settings/utilities/field/is_default_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.isSettingDefaultValue", + "type": "Function", + "tags": [], + "label": "isSettingDefaultValue", + "description": [ + "\nUtility function to compare a value to the default value of a {@link UiSettingMetadata}." + ], + "signature": [ + "(setting: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsType", + "text": "UiSettingsType" + }, + ", string | number | boolean | (string | number)[] | null | undefined>, userValue?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.Value", + "text": "Value" + }, + ") => boolean" + ], + "path": "packages/kbn-management/settings/utilities/setting/is_default_value.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.isSettingDefaultValue.$1", + "type": "Object", + "tags": [], + "label": "setting", + "description": [ + "The source {@link UiSettingMetadata } object." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UiSettingMetadata", + "text": "UiSettingMetadata" + }, + "<", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsType", + "text": "UiSettingsType" + }, + ", string | number | boolean | (string | number)[] | null | undefined>" + ], + "path": "packages/kbn-management/settings/utilities/setting/is_default_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.isSettingDefaultValue.$2", + "type": "CompoundType", + "tags": [], + "label": "userValue", + "description": [ + "The value to compare to the setting's default value. Default is the\n{@link UiSettingMetadata }'s user value." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.Value", + "text": "Value" + } + ], + "path": "packages/kbn-management/settings/utilities/setting/is_default_value.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [ + "True if the provided value is equal to the setting's default value, false otherwise." + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.normalizeSettings", + "type": "Function", + "tags": [], + "label": "normalizeSettings", + "description": [ + "\nUiSettings have an extremely permissive set of types, which makes it difficult to code\nagainst them. The `type` and `value` properties are inherently related, and important,\nbut in some cases one or both are missing. This function attempts to normalize the\nsettings to a strongly-typed format, {@link UiSettingMetadata} based on the information\nin the setting at runtime.\n" + ], + "signature": [ + "(rawSettings: RawSettings) => Record>" + ], + "path": "packages/kbn-management/settings/utilities/setting/normalize_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.normalizeSettings.$1", + "type": "Object", + "tags": [], + "label": "rawSettings", + "description": [ + "The raw settings retrieved from the {@link IUiSettingsClient }, which\nmay be missing the `type` or `value` properties." + ], + "signature": [ + "RawSettings" + ], + "path": "packages/kbn-management/settings/utilities/setting/normalize_settings.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "A mapped collection of normalized {@link UiSetting } objects." + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.useUpdate", + "type": "Function", + "tags": [], + "label": "useUpdate", + "description": [ + "\nHook to provide a standard {@link OnChangeFn} that will send an update to the\nfield.\n" + ], + "signature": [ + "(params: ", + { + "pluginId": "@kbn/management-settings-utilities", + "scope": "common", + "docId": "kibKbnManagementSettingsUtilitiesPluginApi", + "section": "def-common.UseUpdateParameters", + "text": "UseUpdateParameters" + }, + ") => ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.OnChangeFn", + "text": "OnChangeFn" + }, + "" + ], + "path": "packages/kbn-management/settings/utilities/field/use_update.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.useUpdate.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [ + "The {@link UseUpdateParameters } to use." + ], + "signature": [ + { + "pluginId": "@kbn/management-settings-utilities", + "scope": "common", + "docId": "kibKbnManagementSettingsUtilitiesPluginApi", + "section": "def-common.UseUpdateParameters", + "text": "UseUpdateParameters" + }, + "" + ], + "path": "packages/kbn-management/settings/utilities/field/use_update.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "An {@link OnChangeFn } that will send an update to the field." + ], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.UseUpdateParameters", + "type": "Interface", + "tags": [], + "label": "UseUpdateParameters", + "description": [], + "signature": [ + { + "pluginId": "@kbn/management-settings-utilities", + "scope": "common", + "docId": "kibKbnManagementSettingsUtilitiesPluginApi", + "section": "def-common.UseUpdateParameters", + "text": "UseUpdateParameters" + }, + "" + ], + "path": "packages/kbn-management/settings/utilities/field/use_update.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.UseUpdateParameters.onChange", + "type": "Function", + "tags": [], + "label": "onChange", + "description": [ + "The {@link OnChangeFn} to invoke." + ], + "signature": [ + "(change?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined) => void" + ], + "path": "packages/kbn-management/settings/utilities/field/use_update.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.UseUpdateParameters.onChange.$1", + "type": "Object", + "tags": [], + "label": "change", + "description": [], + "signature": [ + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.UnsavedFieldChange", + "text": "UnsavedFieldChange" + }, + " | undefined" + ], + "path": "packages/kbn-management/settings/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/management-settings-utilities", + "id": "def-common.UseUpdateParameters.field", + "type": "Object", + "tags": [], + "label": "field", + "description": [ + "The {@link FieldDefinition} to use to create an update." + ], + "signature": [ + "{ defaultValue?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.KnownTypeToValue", + "text": "KnownTypeToValue" + }, + " | null | undefined; savedValue?: ", + { + "pluginId": "@kbn/management-settings-types", + "scope": "common", + "docId": "kibKbnManagementSettingsTypesPluginApi", + "section": "def-common.KnownTypeToValue", + "text": "KnownTypeToValue" + }, + " | null | undefined; }" + ], + "path": "packages/kbn-management/settings/utilities/field/use_update.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx new file mode 100644 index 0000000000000..851ae1ee1e9b2 --- /dev/null +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnManagementSettingsUtilitiesPluginApi +slug: /kibana-dev-docs/api/kbn-management-settings-utilities +title: "@kbn/management-settings-utilities" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/management-settings-utilities plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] +--- +import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; + + + +Contact [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 50 | 0 | 2 | 0 | + +## Common + +### Functions + + +### Interfaces + + diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index 2fdbcde76e91d..fa65c39a1ab62 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 77126dd798ccf..16a88d4037be0 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index a60c1a10cbc50..b31a054b18e37 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 94dd7e499a226..451164d8339bd 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index e018ab763ea4c..bf0a830ceb5b7 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index 80228426e7c87..51491053aeca2 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index adb8f5ac9f634..a3bd233547bed 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index ada1ea99901d6..79463d5277c54 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.devdocs.json b/api_docs/kbn_ml_date_picker.devdocs.json index 69aa0e7997284..77ed2b13c1320 100644 --- a/api_docs/kbn_ml_date_picker.devdocs.json +++ b/api_docs/kbn_ml_date_picker.devdocs.json @@ -543,6 +543,22 @@ "path": "x-pack/packages/ml/date_picker/src/hooks/use_date_picker_context.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ml-date-picker", + "id": "def-common.DatePickerDependencies.isServerless", + "type": "CompoundType", + "tags": [], + "label": "isServerless", + "description": [ + "\nOptional flag to indicate whether kibana is running in serverless" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/packages/ml/date_picker/src/hooks/use_date_picker_context.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -935,6 +951,22 @@ "path": "x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ml-date-picker", + "id": "def-common.FullTimeRangeSelectorProps.hideFrozenDataTierChoice", + "type": "CompoundType", + "tags": [], + "label": "hideFrozenDataTierChoice", + "description": [ + "\nOptional flag to disable the frozen data tier choice." + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 40bf1da8636a4..2b8a64dada7f6 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 47 | 0 | 0 | 0 | +| 49 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index c69c9f5d1165a..d98622a6151d0 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index dca38829d1f8a..27233f6afd799 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index a0df930cf267e..8199b34b2ee8c 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 9f484184a1ee6..0a8522db4cf25 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index b0878df182bb8..cd4488ed0fc75 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index 7bca4caf14b01..7fb218fa9df4c 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 43ad3a96481c4..67696d70e4b49 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 4ba43a3a50a33..1637bb731345e 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index 47221104da4b7..dd2d0f0bad8ca 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index b7479a7488984..6da71251acdca 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 550fda3a86c3e..8ca8cf2e43929 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 59a9fd5e0be72..28eaafc45de5e 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index 22275a6390f02..1deb1fe30d6f8 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 61d20853caa8b..9e046eea9e486 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 0b2874d805878..2d48c08a54e28 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index dbbfa6b0fab39..6b0c6049fe412 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.devdocs.json b/api_docs/kbn_monaco.devdocs.json index 364fa6046fc99..c7d87a27c0872 100644 --- a/api_docs/kbn_monaco.devdocs.json +++ b/api_docs/kbn_monaco.devdocs.json @@ -373,7 +373,7 @@ "label": "getSourceIdentifiers", "description": [], "signature": [ - "CallbackFn | undefined" + "CallbackFn | undefined" ], "path": "packages/kbn-monaco/src/esql/lib/autocomplete/types.ts", "deprecated": false, @@ -387,7 +387,49 @@ "label": "getFieldsIdentifiers", "description": [], "signature": [ - "CallbackFn | undefined" + "CallbackFn | undefined" + ], + "path": "packages/kbn-monaco/src/esql/lib/autocomplete/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQLCustomAutocompleteCallbacks.getPoliciesIdentifiers", + "type": "Function", + "tags": [], + "label": "getPoliciesIdentifiers", + "description": [], + "signature": [ + "CallbackFn<{ name: string; indices: string[]; }> | undefined" + ], + "path": "packages/kbn-monaco/src/esql/lib/autocomplete/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQLCustomAutocompleteCallbacks.getPolicyFieldsIdentifiers", + "type": "Function", + "tags": [], + "label": "getPolicyFieldsIdentifiers", + "description": [], + "signature": [ + "CallbackFn | undefined" + ], + "path": "packages/kbn-monaco/src/esql/lib/autocomplete/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQLCustomAutocompleteCallbacks.getPolicyMatchingFieldIdentifiers", + "type": "Function", + "tags": [], + "label": "getPolicyMatchingFieldIdentifiers", + "description": [], + "signature": [ + "CallbackFn | undefined" ], "path": "packages/kbn-monaco/src/esql/lib/autocomplete/types.ts", "deprecated": false, @@ -850,6 +892,61 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQLLang.languageConfiguration", + "type": "Object", + "tags": [], + "label": "languageConfiguration", + "description": [], + "path": "packages/kbn-monaco/src/esql/language.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQLLang.languageConfiguration.brackets", + "type": "Array", + "tags": [], + "label": "brackets", + "description": [], + "signature": [ + "[string, string][]" + ], + "path": "packages/kbn-monaco/src/esql/language.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQLLang.languageConfiguration.autoClosingPairs", + "type": "Array", + "tags": [], + "label": "autoClosingPairs", + "description": [], + "signature": [ + "{ open: string; close: string; }[]" + ], + "path": "packages/kbn-monaco/src/esql/language.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQLLang.languageConfiguration.surroundingPairs", + "type": "Array", + "tags": [], + "label": "surroundingPairs", + "description": [], + "signature": [ + "{ open: string; close: string; }[]" + ], + "path": "packages/kbn-monaco/src/esql/language.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, { "parentPluginId": "@kbn/monaco", "id": "def-common.ESQLLang.getSuggestionProvider", diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index c23d7b80c4e0a..b40894f98bcfb 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 71 | 0 | 69 | 3 | +| 78 | 0 | 76 | 3 | ## Common diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index bda7ee374437f..06a456230ef61 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index aef43868ae482..d7d30209972a6 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 5da9555bbfcae..6dd4f10d21d9f 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 6e06115ceebe4..4f97c8f4cd0cf 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 1fa274bc6daa0..9080a2ae4f73a 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 9041966720191..476ea619fd815 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index bda21ef6f5423..7e4538c379e1b 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 44413e84c712c..6ee75d3f4671d 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.devdocs.json b/api_docs/kbn_profiling_utils.devdocs.json new file mode 100644 index 0000000000000..3a66fbbf97667 --- /dev/null +++ b/api_docs/kbn_profiling_utils.devdocs.json @@ -0,0 +1,2964 @@ +{ + "id": "@kbn/profiling-utils", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createBaseFlameGraph", + "type": "Function", + "tags": [], + "label": "createBaseFlameGraph", + "description": [ + "\ncreateBaseFlameGraph encapsulates the tree representation into a serialized form." + ], + "signature": [ + "(tree: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.CalleeTree", + "text": "CalleeTree" + }, + ", samplingRate: number, totalSeconds: number) => ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.BaseFlameGraph", + "text": "BaseFlameGraph" + } + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createBaseFlameGraph.$1", + "type": "Object", + "tags": [], + "label": "tree", + "description": [ + "CalleeTree" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.CalleeTree", + "text": "CalleeTree" + } + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createBaseFlameGraph.$2", + "type": "number", + "tags": [], + "label": "samplingRate", + "description": [ + "number" + ], + "signature": [ + "number" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createBaseFlameGraph.$3", + "type": "number", + "tags": [], + "label": "totalSeconds", + "description": [ + "number" + ], + "signature": [ + "number" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "BaseFlameGraph" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createCalleeTree", + "type": "Function", + "tags": [], + "label": "createCalleeTree", + "description": [ + "\nCreate a callee tree" + ], + "signature": [ + "(events: Map, stackTraces: Map, stackFrames: Map, executables: Map, totalFrames: number, samplingRate: number) => ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.CalleeTree", + "text": "CalleeTree" + } + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createCalleeTree.$1", + "type": "Object", + "tags": [], + "label": "events", + "description": [ + "Map" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createCalleeTree.$2", + "type": "Object", + "tags": [], + "label": "stackTraces", + "description": [ + "Map" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createCalleeTree.$3", + "type": "Object", + "tags": [], + "label": "stackFrames", + "description": [ + "Map" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createCalleeTree.$4", + "type": "Object", + "tags": [], + "label": "executables", + "description": [ + "Map" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createCalleeTree.$5", + "type": "number", + "tags": [], + "label": "totalFrames", + "description": [ + "number" + ], + "signature": [ + "number" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createCalleeTree.$6", + "type": "number", + "tags": [], + "label": "samplingRate", + "description": [ + "number" + ], + "signature": [ + "number" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFlameGraph", + "type": "Function", + "tags": [], + "label": "createFlameGraph", + "description": [ + "\n\ncreateFlameGraph combines the base flamegraph with CPU-intensive values.\nThis allows us to create a flamegraph in two steps (e.g. first on the server\nand finally in the browser)." + ], + "signature": [ + "(base: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.BaseFlameGraph", + "text": "BaseFlameGraph" + }, + ") => ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.ElasticFlameGraph", + "text": "ElasticFlameGraph" + } + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFlameGraph.$1", + "type": "Object", + "tags": [], + "label": "base", + "description": [ + "BaseFlameGraph" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.BaseFlameGraph", + "text": "BaseFlameGraph" + } + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "ElasticFlameGraph" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFrameGroupID", + "type": "Function", + "tags": [], + "label": "createFrameGroupID", + "description": [ + "\n\ncreateFrameGroupID is the \"standard\" way of grouping frames, by commonly shared group identifiers.\nFor ELF-symbolized frames, group by FunctionName, ExeFileName and FileID.\nFor non-symbolized frames, group by FileID and AddressOrLine.\notherwise group by ExeFileName, SourceFilename and FunctionName." + ], + "signature": [ + "(fileID: string, addressOrLine: number, exeFilename: string, sourceFilename: string, functionName: string) => string" + ], + "path": "packages/kbn-profiling-utils/common/frame_group.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFrameGroupID.$1", + "type": "string", + "tags": [], + "label": "fileID", + "description": [ + "string" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/frame_group.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFrameGroupID.$2", + "type": "number", + "tags": [], + "label": "addressOrLine", + "description": [ + "string" + ], + "signature": [ + "number" + ], + "path": "packages/kbn-profiling-utils/common/frame_group.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFrameGroupID.$3", + "type": "string", + "tags": [], + "label": "exeFilename", + "description": [ + "string" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/frame_group.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFrameGroupID.$4", + "type": "string", + "tags": [], + "label": "sourceFilename", + "description": [ + "string" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/frame_group.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createFrameGroupID.$5", + "type": "string", + "tags": [], + "label": "functionName", + "description": [ + "string" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/frame_group.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "FrameGroupID" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createStackFrameMetadata", + "type": "Function", + "tags": [], + "label": "createStackFrameMetadata", + "description": [ + "\ncreate stackframe metadata" + ], + "signature": [ + "(options: Partial<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackFrameMetadata", + "text": "StackFrameMetadata" + }, + ">) => ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackFrameMetadata", + "text": "StackFrameMetadata" + } + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createStackFrameMetadata.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "Partial" + ], + "signature": [ + "Partial<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackFrameMetadata", + "text": "StackFrameMetadata" + }, + ">" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "StackFrameMetadata" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions", + "type": "Function", + "tags": [], + "label": "createTopNFunctions", + "description": [], + "signature": [ + "({\n endIndex,\n events,\n executables,\n samplingRate,\n stackFrames,\n stackTraces,\n startIndex,\n}: { endIndex: number; events: Map; executables: Map; samplingRate: number; stackFrames: Map; stackTraces: Map; startIndex: number; }) => ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctions", + "text": "TopNFunctions" + } + ], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1", + "type": "Object", + "tags": [], + "label": "{\n endIndex,\n events,\n executables,\n samplingRate,\n stackFrames,\n stackTraces,\n startIndex,\n}", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1.endIndex", + "type": "number", + "tags": [], + "label": "endIndex", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1.events", + "type": "Object", + "tags": [], + "label": "events", + "description": [], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1.executables", + "type": "Object", + "tags": [], + "label": "executables", + "description": [], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1.samplingRate", + "type": "number", + "tags": [], + "label": "samplingRate", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1.stackFrames", + "type": "Object", + "tags": [], + "label": "stackFrames", + "description": [], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1.stackTraces", + "type": "Object", + "tags": [], + "label": "stackTraces", + "description": [], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.createTopNFunctions.$1.startIndex", + "type": "number", + "tags": [], + "label": "startIndex", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.decodeStackTraceResponse", + "type": "Function", + "tags": [], + "label": "decodeStackTraceResponse", + "description": [ + "\nDecodes stack trace response" + ], + "signature": [ + "(response: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackTraceResponse", + "text": "StackTraceResponse" + }, + ") => ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.DecodedStackTraceResponse", + "text": "DecodedStackTraceResponse" + } + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.decodeStackTraceResponse.$1", + "type": "Object", + "tags": [], + "label": "response", + "description": [ + "StackTraceResponse" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackTraceResponse", + "text": "StackTraceResponse" + } + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "DecodedStackTraceResponse" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.describeFrameType", + "type": "Function", + "tags": [], + "label": "describeFrameType", + "description": [ + "\nget frame type name" + ], + "signature": [ + "(ft: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.FrameType", + "text": "FrameType" + }, + ") => string" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.describeFrameType.$1", + "type": "Enum", + "tags": [], + "label": "ft", + "description": [ + "FrameType" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.FrameType", + "text": "FrameType" + } + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "string" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getCalleeFunction", + "type": "Function", + "tags": [], + "label": "getCalleeFunction", + "description": [ + "\nGet callee function name" + ], + "signature": [ + "(frame: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackFrameMetadata", + "text": "StackFrameMetadata" + }, + ") => string" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getCalleeFunction.$1", + "type": "Object", + "tags": [], + "label": "frame", + "description": [ + "StackFrameMetadata" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackFrameMetadata", + "text": "StackFrameMetadata" + } + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "string" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getCalleeSource", + "type": "Function", + "tags": [], + "label": "getCalleeSource", + "description": [ + "\nGet callee source information.\nIf we don't have the executable filename, display \nIf no source line or filename available, display the executable offset" + ], + "signature": [ + "(frame: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackFrameMetadata", + "text": "StackFrameMetadata" + }, + ") => string" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getCalleeSource.$1", + "type": "Object", + "tags": [], + "label": "frame", + "description": [ + "StackFrameMetadata" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.StackFrameMetadata", + "text": "StackFrameMetadata" + } + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "string" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getFieldNameForTopNType", + "type": "Function", + "tags": [], + "label": "getFieldNameForTopNType", + "description": [ + "\nGet Profiling ES field based on TopN Type" + ], + "signature": [ + "(type: ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNType", + "text": "TopNType" + }, + ") => string" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getFieldNameForTopNType.$1", + "type": "Enum", + "tags": [], + "label": "type", + "description": [ + "TopNType" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNType", + "text": "TopNType" + } + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "string" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getFrameSymbolStatus", + "type": "Function", + "tags": [], + "label": "getFrameSymbolStatus", + "description": [ + "\nGet frame symbol status" + ], + "signature": [ + "(param: FrameSymbolStatusParams) => ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.FrameSymbolStatus", + "text": "FrameSymbolStatus" + } + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getFrameSymbolStatus.$1", + "type": "Object", + "tags": [], + "label": "param", + "description": [ + "FrameSymbolStatusParams" + ], + "signature": [ + "FrameSymbolStatusParams" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "FrameSymbolStatus" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getLanguageType", + "type": "Function", + "tags": [], + "label": "getLanguageType", + "description": [ + "\nGet language type" + ], + "signature": [ + "(param: LanguageTypeParams) => \"NATIVE\" | \"INTERPRETED\"" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.getLanguageType.$1", + "type": "Object", + "tags": [], + "label": "param", + "description": [ + "LanguageTypeParams" + ], + "signature": [ + "LanguageTypeParams" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "string" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.groupStackFrameMetadataByStackTrace", + "type": "Function", + "tags": [], + "label": "groupStackFrameMetadataByStackTrace", + "description": [ + "\nGroup stackframe by stack trace" + ], + "signature": [ + "(stackTraces: Map, stackFrames: Map, executables: Map) => Record" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.groupStackFrameMetadataByStackTrace.$1", + "type": "Object", + "tags": [], + "label": "stackTraces", + "description": [ + "Map" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.groupStackFrameMetadataByStackTrace.$2", + "type": "Object", + "tags": [], + "label": "stackFrames", + "description": [ + "Map" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.groupStackFrameMetadataByStackTrace.$3", + "type": "Object", + "tags": [], + "label": "executables", + "description": [ + "Map" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "Record" + ], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph", + "type": "Interface", + "tags": [], + "label": "BaseFlameGraph", + "description": [ + "\nBase Flamegraph" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.Size", + "type": "number", + "tags": [], + "label": "Size", + "description": [ + "size" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.Edges", + "type": "Array", + "tags": [], + "label": "Edges", + "description": [ + "edges" + ], + "signature": [ + "number[][]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.FileID", + "type": "Array", + "tags": [], + "label": "FileID", + "description": [ + "file ids" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.FrameType", + "type": "Array", + "tags": [], + "label": "FrameType", + "description": [ + "frame types" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.Inline", + "type": "Array", + "tags": [], + "label": "Inline", + "description": [ + "inlines" + ], + "signature": [ + "boolean[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.ExeFilename", + "type": "Array", + "tags": [], + "label": "ExeFilename", + "description": [ + "executable file names" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.AddressOrLine", + "type": "Array", + "tags": [], + "label": "AddressOrLine", + "description": [ + "address or line" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.FunctionName", + "type": "Array", + "tags": [], + "label": "FunctionName", + "description": [ + "function names" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.FunctionOffset", + "type": "Array", + "tags": [], + "label": "FunctionOffset", + "description": [ + "function offsets" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.SourceFilename", + "type": "Array", + "tags": [], + "label": "SourceFilename", + "description": [ + "source file names" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.SourceLine", + "type": "Array", + "tags": [], + "label": "SourceLine", + "description": [ + "source lines" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.CountInclusive", + "type": "Array", + "tags": [], + "label": "CountInclusive", + "description": [ + "total cpu" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.CountExclusive", + "type": "Array", + "tags": [], + "label": "CountExclusive", + "description": [ + "self cpu" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.TotalSeconds", + "type": "number", + "tags": [], + "label": "TotalSeconds", + "description": [ + "total seconds" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.BaseFlameGraph.SamplingRate", + "type": "number", + "tags": [], + "label": "SamplingRate", + "description": [ + "sampling rate" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree", + "type": "Interface", + "tags": [], + "label": "CalleeTree", + "description": [ + "\nCallee tree" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.Size", + "type": "number", + "tags": [], + "label": "Size", + "description": [ + "size" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.Edges", + "type": "Array", + "tags": [], + "label": "Edges", + "description": [ + "edges" + ], + "signature": [ + "Map[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.FileID", + "type": "Array", + "tags": [], + "label": "FileID", + "description": [ + "file ids" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.FrameType", + "type": "Array", + "tags": [], + "label": "FrameType", + "description": [ + "frame types" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.Inline", + "type": "Array", + "tags": [], + "label": "Inline", + "description": [ + "inlines" + ], + "signature": [ + "boolean[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.ExeFilename", + "type": "Array", + "tags": [], + "label": "ExeFilename", + "description": [ + "executable file names" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.AddressOrLine", + "type": "Array", + "tags": [], + "label": "AddressOrLine", + "description": [ + "address or lines" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.FunctionName", + "type": "Array", + "tags": [], + "label": "FunctionName", + "description": [ + "function names" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.FunctionOffset", + "type": "Array", + "tags": [], + "label": "FunctionOffset", + "description": [ + "function offsets" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.SourceFilename", + "type": "Array", + "tags": [], + "label": "SourceFilename", + "description": [ + "source file names" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.SourceLine", + "type": "Array", + "tags": [], + "label": "SourceLine", + "description": [ + "source lines" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.CountInclusive", + "type": "Array", + "tags": [], + "label": "CountInclusive", + "description": [ + "total cpu" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.CalleeTree.CountExclusive", + "type": "Array", + "tags": [], + "label": "CountExclusive", + "description": [ + "self cpu" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/callee.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.DecodedStackTraceResponse", + "type": "Interface", + "tags": [], + "label": "DecodedStackTraceResponse", + "description": [ + "Decoded stack trace response" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.DecodedStackTraceResponse.events", + "type": "Object", + "tags": [], + "label": "events", + "description": [ + "Map of Stacktrace ID and event" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.DecodedStackTraceResponse.stackTraces", + "type": "Object", + "tags": [], + "label": "stackTraces", + "description": [ + "Map of stacktrace ID and stacktrace" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.DecodedStackTraceResponse.stackFrames", + "type": "Object", + "tags": [], + "label": "stackFrames", + "description": [ + "Map of stackframe ID and stackframe" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.DecodedStackTraceResponse.executables", + "type": "Object", + "tags": [], + "label": "executables", + "description": [ + "Map of file ID and Executables" + ], + "signature": [ + "Map" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.DecodedStackTraceResponse.totalFrames", + "type": "number", + "tags": [], + "label": "totalFrames", + "description": [ + "Total number of frames" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.DecodedStackTraceResponse.samplingRate", + "type": "number", + "tags": [], + "label": "samplingRate", + "description": [ + "sampling rate" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ElasticFlameGraph", + "type": "Interface", + "tags": [], + "label": "ElasticFlameGraph", + "description": [ + "Elasticsearch flamegraph" + ], + "signature": [ + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.ElasticFlameGraph", + "text": "ElasticFlameGraph" + }, + " extends ", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.BaseFlameGraph", + "text": "BaseFlameGraph" + } + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ElasticFlameGraph.ID", + "type": "Array", + "tags": [], + "label": "ID", + "description": [ + "ID" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ElasticFlameGraph.Label", + "type": "Array", + "tags": [], + "label": "Label", + "description": [ + "Label" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/flamegraph.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.Executable", + "type": "Interface", + "tags": [], + "label": "Executable", + "description": [ + "Executable" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.Executable.FileName", + "type": "string", + "tags": [], + "label": "FileName", + "description": [ + "file name" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ProfilingStatusResponse", + "type": "Interface", + "tags": [], + "label": "ProfilingStatusResponse", + "description": [ + "Profiling status response" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ProfilingStatusResponse.profiling", + "type": "Object", + "tags": [], + "label": "profiling", + "description": [ + "profiling enabled" + ], + "signature": [ + "{ enabled: boolean; }" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ProfilingStatusResponse.resource_management", + "type": "Object", + "tags": [], + "label": "resource_management", + "description": [ + "resource management status" + ], + "signature": [ + "{ enabled: boolean; }" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ProfilingStatusResponse.resources", + "type": "Object", + "tags": [], + "label": "resources", + "description": [ + "Indices creates / pre 8.9.1 data still available" + ], + "signature": [ + "{ created: boolean; pre_8_9_1_data: boolean; }" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrame", + "type": "Interface", + "tags": [], + "label": "StackFrame", + "description": [ + "Stack frame" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrame.FileName", + "type": "string", + "tags": [], + "label": "FileName", + "description": [ + "file name" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrame.FunctionName", + "type": "string", + "tags": [], + "label": "FunctionName", + "description": [ + "function name" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrame.FunctionOffset", + "type": "number", + "tags": [], + "label": "FunctionOffset", + "description": [ + "function offset" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrame.LineNumber", + "type": "number", + "tags": [], + "label": "LineNumber", + "description": [ + "line number" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrame.Inline", + "type": "boolean", + "tags": [], + "label": "Inline", + "description": [ + "inline" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata", + "type": "Interface", + "tags": [], + "label": "StackFrameMetadata", + "description": [ + "Stack frame metadata" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.FrameID", + "type": "string", + "tags": [], + "label": "FrameID", + "description": [ + "StackTrace.FrameID" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.FileID", + "type": "string", + "tags": [], + "label": "FileID", + "description": [ + "StackTrace.FileID" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.FrameType", + "type": "Enum", + "tags": [], + "label": "FrameType", + "description": [ + "StackTrace.Type" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.Inline", + "type": "boolean", + "tags": [], + "label": "Inline", + "description": [ + "StackFrame.Inline" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.AddressOrLine", + "type": "number", + "tags": [], + "label": "AddressOrLine", + "description": [ + "StackTrace.AddressOrLine" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.FunctionName", + "type": "string", + "tags": [], + "label": "FunctionName", + "description": [ + "StackFrame.FunctionName" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.FunctionOffset", + "type": "number", + "tags": [], + "label": "FunctionOffset", + "description": [ + "StackFrame.FunctionOffset" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.SourceID", + "type": "string", + "tags": [], + "label": "SourceID", + "description": [ + "should this be StackFrame.SourceID?" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.SourceFilename", + "type": "string", + "tags": [], + "label": "SourceFilename", + "description": [ + "StackFrame.Filename" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.SourceLine", + "type": "number", + "tags": [], + "label": "SourceLine", + "description": [ + "StackFrame.LineNumber" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.FunctionSourceLine", + "type": "number", + "tags": [], + "label": "FunctionSourceLine", + "description": [ + "auto-generated - see createStackFrameMetadata" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.ExeFileName", + "type": "string", + "tags": [], + "label": "ExeFileName", + "description": [ + "Executable.FileName" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.CommitHash", + "type": "string", + "tags": [], + "label": "CommitHash", + "description": [ + "unused atm due to lack of symbolization metadata" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.SourceCodeURL", + "type": "string", + "tags": [], + "label": "SourceCodeURL", + "description": [ + "unused atm due to lack of symbolization metadata" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.SourcePackageHash", + "type": "string", + "tags": [], + "label": "SourcePackageHash", + "description": [ + "unused atm due to lack of symbolization metadata" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.SourcePackageURL", + "type": "string", + "tags": [], + "label": "SourcePackageURL", + "description": [ + "unused atm due to lack of symbolization metadata" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameMetadata.SamplingRate", + "type": "number", + "tags": [], + "label": "SamplingRate", + "description": [ + "unused atm due to lack of symbolization metadata" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTrace", + "type": "Interface", + "tags": [], + "label": "StackTrace", + "description": [ + "Stack trace" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTrace.FrameIDs", + "type": "Array", + "tags": [], + "label": "FrameIDs", + "description": [ + "frame ids" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTrace.FileIDs", + "type": "Array", + "tags": [], + "label": "FileIDs", + "description": [ + "file ids" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTrace.AddressOrLines", + "type": "Array", + "tags": [], + "label": "AddressOrLines", + "description": [ + "address or lines" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTrace.Types", + "type": "Array", + "tags": [], + "label": "Types", + "description": [ + "types" + ], + "signature": [ + "number[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceResponse", + "type": "Interface", + "tags": [], + "label": "StackTraceResponse", + "description": [ + "Profiling stacktrace" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceResponse.stack_trace_events", + "type": "Object", + "tags": [], + "label": "['stack_trace_events']", + "description": [ + "stack trace events" + ], + "signature": [ + "ProfilingEvents | undefined" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceResponse.stack_traces", + "type": "Object", + "tags": [], + "label": "['stack_traces']", + "description": [ + "stack traces" + ], + "signature": [ + "ProfilingStackTraces | undefined" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceResponse.stack_frames", + "type": "Object", + "tags": [], + "label": "['stack_frames']", + "description": [ + "stack frames" + ], + "signature": [ + "ProfilingStackFrames | undefined" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceResponse.executables", + "type": "Object", + "tags": [], + "label": "['executables']", + "description": [ + "executables" + ], + "signature": [ + "ProfilingExecutables | undefined" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceResponse.total_frames", + "type": "number", + "tags": [], + "label": "['total_frames']", + "description": [ + "total frames" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceResponse.sampling_rate", + "type": "number", + "tags": [], + "label": "['sampling_rate']", + "description": [ + "sampling rate" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNFunctions", + "type": "Interface", + "tags": [], + "label": "TopNFunctions", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNFunctions.TotalCount", + "type": "number", + "tags": [], + "label": "TotalCount", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNFunctions.TopN", + "type": "Array", + "tags": [], + "label": "TopN", + "description": [], + "signature": [ + "TopNFunction[]" + ], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNFunctions.SamplingRate", + "type": "number", + "tags": [], + "label": "SamplingRate", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNFunctions.selfCPU", + "type": "number", + "tags": [], + "label": "selfCPU", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNFunctions.totalCPU", + "type": "number", + "tags": [], + "label": "totalCPU", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.FrameSymbolStatus", + "type": "Enum", + "tags": [], + "label": "FrameSymbolStatus", + "description": [ + "\nFrame symbol status" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.FrameType", + "type": "Enum", + "tags": [], + "label": "FrameType", + "description": [ + "\nFrame type" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.ProfilingESField", + "type": "Enum", + "tags": [], + "label": "ProfilingESField", + "description": [ + "\nProfiling Elasticsearch fields" + ], + "path": "packages/kbn-profiling-utils/common/elasticsearch.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTracesDisplayOption", + "type": "Enum", + "tags": [], + "label": "StackTracesDisplayOption", + "description": [ + "\nStacktraces options" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNFunctionSortField", + "type": "Enum", + "tags": [], + "label": "TopNFunctionSortField", + "description": [], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.TopNType", + "type": "Enum", + "tags": [], + "label": "TopNType", + "description": [ + "\nFunctions TopN types definition" + ], + "path": "packages/kbn-profiling-utils/common/stack_traces.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.FileID", + "type": "Type", + "tags": [], + "label": "FileID", + "description": [ + "\nFile ID" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.FrameGroupID", + "type": "Type", + "tags": [], + "label": "FrameGroupID", + "description": [ + "Frame group ID" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/frame_group.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackFrameID", + "type": "Type", + "tags": [], + "label": "StackFrameID", + "description": [ + "\nStackFrame ID" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.StackTraceID", + "type": "Type", + "tags": [], + "label": "StackTraceID", + "description": [ + "\nStacktrace ID" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyExecutable", + "type": "Object", + "tags": [], + "label": "emptyExecutable", + "description": [ + "\nEmpty exectutable" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyExecutable.FileName", + "type": "string", + "tags": [], + "label": "FileName", + "description": [ + "/** file name */" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackFrame", + "type": "Object", + "tags": [], + "label": "emptyStackFrame", + "description": [ + "\nEmpty stack frame" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackFrame.FileName", + "type": "string", + "tags": [], + "label": "FileName", + "description": [ + "/** File name */" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackFrame.FunctionName", + "type": "string", + "tags": [], + "label": "FunctionName", + "description": [ + "/** Function name */" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackFrame.FunctionOffset", + "type": "number", + "tags": [], + "label": "FunctionOffset", + "description": [ + "/** Function offset */" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackFrame.LineNumber", + "type": "number", + "tags": [], + "label": "LineNumber", + "description": [ + "/** Line number */" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackFrame.Inline", + "type": "boolean", + "tags": [], + "label": "Inline", + "description": [ + "/** Inline */" + ], + "signature": [ + "false" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackTrace", + "type": "Object", + "tags": [], + "label": "emptyStackTrace", + "description": [ + "\nEmpty stack trace" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackTrace.FrameIDs", + "type": "Array", + "tags": [], + "label": "FrameIDs", + "description": [ + "/** Frame IDs */" + ], + "signature": [ + "never[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackTrace.FileIDs", + "type": "Array", + "tags": [], + "label": "FileIDs", + "description": [ + "/** File IDs */" + ], + "signature": [ + "never[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackTrace.AddressOrLines", + "type": "Array", + "tags": [], + "label": "AddressOrLines", + "description": [ + "/** Address or lines */" + ], + "signature": [ + "never[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.emptyStackTrace.Types", + "type": "Array", + "tags": [], + "label": "Types", + "description": [ + "/** Types */" + ], + "signature": [ + "never[]" + ], + "path": "packages/kbn-profiling-utils/common/profiling.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/profiling-utils", + "id": "def-common.topNFunctionSortFieldRt", + "type": "Object", + "tags": [], + "label": "topNFunctionSortFieldRt", + "description": [], + "signature": [ + "UnionC", + "<[", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".Rank>, ", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".Frame>, ", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".Samples>, ", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".SelfCPU>, ", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".TotalCPU>, ", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".Diff>, ", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".AnnualizedCo2>, ", + "LiteralC", + "<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctionSortField", + "text": "TopNFunctionSortField" + }, + ".AnnualizedDollarCost>]>" + ], + "path": "packages/kbn-profiling-utils/common/functions.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx new file mode 100644 index 0000000000000..57ac8b592246a --- /dev/null +++ b/api_docs/kbn_profiling_utils.mdx @@ -0,0 +1,42 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnProfilingUtilsPluginApi +slug: /kibana-dev-docs/api/kbn-profiling-utils +title: "@kbn/profiling-utils" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/profiling-utils plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] +--- +import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; + + + +Contact [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 160 | 0 | 17 | 0 | + +## Common + +### Objects + + +### Functions + + +### Interfaces + + +### Enums + + +### Consts, variables and types + + diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index 634f2b2aab539..efb549c1d7828 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index d552adba2ac5c..7e21c28b13dfd 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 89e0b6f0caa65..0f538e68f7881 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index 7e323d2cd4cd5..12a8a4483cb2a 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index dacc15f4d5b10..567b4153e4d2f 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index baa6ce77ac522..4629cb9e45291 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index 6d28c1ee43eb1..f0a6f159f3b7a 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index 539bf7cc83c85..ed6a8e1981a18 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index 383bdc0e2d5ec..8b4e44933a8a5 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index c13ce02fa0421..ecd20af2807ef 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index a6728dbd76035..f2b391bca66d2 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 6a0df88f9db3c..46899d23edd68 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index 4ce4e32d33757..588fd1163811b 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 2414241553b57..d4dacc5e71fcd 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 366ed43c51a25..746db6f3cd015 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 8d5de8947312f..e922af36c9495 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index 6eb32ab8e700d..1f4f84be69f1c 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.devdocs.json b/api_docs/kbn_search_api_panels.devdocs.json index 8c1ce9f259ef6..52bb065a5c52b 100644 --- a/api_docs/kbn_search_api_panels.devdocs.json +++ b/api_docs/kbn_search_api_panels.devdocs.json @@ -68,7 +68,15 @@ "section": "def-common.LanguageDefinition", "text": "LanguageDefinition" }, - ") => string | undefined" + ", args?: ", + { + "pluginId": "@kbn/search-api-panels", + "scope": "common", + "docId": "kibKbnSearchApiPanelsPluginApi", + "section": "def-common.LanguageDefinitionSnippetArguments", + "text": "LanguageDefinitionSnippetArguments" + }, + " | undefined) => string | undefined" ], "path": "packages/kbn-search-api-panels/utils.ts", "deprecated": false, @@ -95,6 +103,28 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/search-api-panels", + "id": "def-common.getConsoleRequest.$2", + "type": "Object", + "tags": [], + "label": "args", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-api-panels", + "scope": "common", + "docId": "kibKbnSearchApiPanelsPluginApi", + "section": "def-common.LanguageDefinitionSnippetArguments", + "text": "LanguageDefinitionSnippetArguments" + }, + " | undefined" + ], + "path": "packages/kbn-search-api-panels/utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [], @@ -526,7 +556,7 @@ "label": "WelcomeBanner", "description": [], "signature": [ - "({ userProfile, assetBasePath, image, showDescription, }: React.PropsWithChildren<", + "({ user, assetBasePath, image, showDescription, }: React.PropsWithChildren<", { "pluginId": "@kbn/search-api-panels", "scope": "common", @@ -545,7 +575,7 @@ "id": "def-common.WelcomeBanner.$1", "type": "CompoundType", "tags": [], - "label": "{\n userProfile,\n assetBasePath,\n image,\n showDescription = true,\n}", + "label": "{\n user,\n assetBasePath,\n image,\n showDescription = true,\n}", "description": [], "signature": [ "React.PropsWithChildren<", @@ -621,27 +651,30 @@ "children": [ { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.advancedConfig", + "id": "def-common.LanguageDefinition.name", "type": "string", "tags": [], - "label": "advancedConfig", + "label": "name", "description": [], - "signature": [ - "string | undefined" - ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.apiReference", - "type": "string", + "id": "def-common.LanguageDefinition.id", + "type": "Enum", "tags": [], - "label": "apiReference", + "label": "id", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "@kbn/search-api-panels", + "scope": "common", + "docId": "kibKbnSearchApiPanelsPluginApi", + "section": "def-common.Languages", + "text": "Languages" + } ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -649,35 +682,24 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.basicConfig", + "id": "def-common.LanguageDefinition.iconType", "type": "string", "tags": [], - "label": "basicConfig", + "label": "iconType", "description": [], - "signature": [ - "string | undefined" - ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.configureClient", - "type": "CompoundType", + "id": "def-common.LanguageDefinition.docLink", + "type": "string", "tags": [], - "label": "configureClient", + "label": "docLink", "description": [], "signature": [ - "string | ((args: ", - { - "pluginId": "@kbn/search-api-panels", - "scope": "common", - "docId": "kibKbnSearchApiPanelsPluginApi", - "section": "def-common.LanguageDefinitionSnippetArguments", - "text": "LanguageDefinitionSnippetArguments" - }, - ") => string)" + "string | undefined" ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -685,24 +707,27 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.docLink", - "type": "string", + "id": "def-common.LanguageDefinition.configureClient", + "type": "CompoundType", "tags": [], - "label": "docLink", + "label": "configureClient", "description": [], + "signature": [ + "CodeSnippet | undefined" + ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.github", - "type": "Object", + "id": "def-common.LanguageDefinition.ingestData", + "type": "CompoundType", "tags": [], - "label": "github", + "label": "ingestData", "description": [], "signature": [ - "{ link: string; label: string; } | undefined" + "CodeSnippet | undefined" ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -710,30 +735,27 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.iconType", - "type": "string", + "id": "def-common.LanguageDefinition.ingestDataIndex", + "type": "CompoundType", "tags": [], - "label": "iconType", + "label": "ingestDataIndex", "description": [], + "signature": [ + "CodeSnippet | undefined" + ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.id", - "type": "Enum", + "id": "def-common.LanguageDefinition.installClient", + "type": "string", "tags": [], - "label": "id", + "label": "installClient", "description": [], "signature": [ - { - "pluginId": "@kbn/search-api-panels", - "scope": "common", - "docId": "kibKbnSearchApiPanelsPluginApi", - "section": "def-common.Languages", - "text": "Languages" - } + "string | undefined" ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -741,21 +763,13 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.ingestData", + "id": "def-common.LanguageDefinition.buildSearchQuery", "type": "CompoundType", "tags": [], - "label": "ingestData", + "label": "buildSearchQuery", "description": [], "signature": [ - "string | ((args: ", - { - "pluginId": "@kbn/search-api-panels", - "scope": "common", - "docId": "kibKbnSearchApiPanelsPluginApi", - "section": "def-common.LanguageDefinitionSnippetArguments", - "text": "LanguageDefinitionSnippetArguments" - }, - ") => string)" + "CodeSnippet | undefined" ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -763,21 +777,13 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.ingestDataIndex", + "id": "def-common.LanguageDefinition.testConnection", "type": "CompoundType", "tags": [], - "label": "ingestDataIndex", + "label": "testConnection", "description": [], "signature": [ - "string | ((args: ", - { - "pluginId": "@kbn/search-api-panels", - "scope": "common", - "docId": "kibKbnSearchApiPanelsPluginApi", - "section": "def-common.LanguageDefinitionSnippetArguments", - "text": "LanguageDefinitionSnippetArguments" - }, - ") => string)" + "CodeSnippet | undefined" ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -785,21 +791,24 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.installClient", + "id": "def-common.LanguageDefinition.advancedConfig", "type": "string", "tags": [], - "label": "installClient", + "label": "advancedConfig", "description": [], + "signature": [ + "string | undefined" + ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.languageStyling", + "id": "def-common.LanguageDefinition.apiReference", "type": "string", "tags": [], - "label": "languageStyling", + "label": "apiReference", "description": [], "signature": [ "string | undefined" @@ -810,32 +819,27 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.name", + "id": "def-common.LanguageDefinition.basicConfig", "type": "string", "tags": [], - "label": "name", + "label": "basicConfig", "description": [], + "signature": [ + "string | undefined" + ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.buildSearchQuery", - "type": "CompoundType", + "id": "def-common.LanguageDefinition.github", + "type": "Object", "tags": [], - "label": "buildSearchQuery", + "label": "github", "description": [], "signature": [ - "string | ((args: ", - { - "pluginId": "@kbn/search-api-panels", - "scope": "common", - "docId": "kibKbnSearchApiPanelsPluginApi", - "section": "def-common.LanguageDefinitionSnippetArguments", - "text": "LanguageDefinitionSnippetArguments" - }, - ") => string)" + "{ link: string; label: string; } | undefined" ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -843,21 +847,13 @@ }, { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.LanguageDefinition.testConnection", - "type": "CompoundType", + "id": "def-common.LanguageDefinition.languageStyling", + "type": "string", "tags": [], - "label": "testConnection", + "label": "languageStyling", "description": [], "signature": [ - "string | ((args: ", - { - "pluginId": "@kbn/search-api-panels", - "scope": "common", - "docId": "kibKbnSearchApiPanelsPluginApi", - "section": "def-common.LanguageDefinitionSnippetArguments", - "text": "LanguageDefinitionSnippetArguments" - }, - ") => string)" + "string | undefined" ], "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, @@ -926,6 +922,34 @@ "path": "packages/kbn-search-api-panels/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-api-panels", + "id": "def-common.LanguageDefinitionSnippetArguments.ingestPipeline", + "type": "string", + "tags": [], + "label": "ingestPipeline", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-search-api-panels/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-api-panels", + "id": "def-common.LanguageDefinitionSnippetArguments.extraIngestDocumentValues", + "type": "Object", + "tags": [], + "label": "extraIngestDocumentValues", + "description": [], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-search-api-panels/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1121,13 +1145,20 @@ "children": [ { "parentPluginId": "@kbn/search-api-panels", - "id": "def-common.WelcomeBannerProps.userProfile", + "id": "def-common.WelcomeBannerProps.user", "type": "Object", "tags": [], - "label": "userProfile", + "label": "user", "description": [], "signature": [ - "{ user: { full_name?: string | undefined; username?: string | undefined; }; }" + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + " | undefined" ], "path": "packages/kbn-search-api-panels/index.tsx", "deprecated": false, diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index f791b92030167..5ec676d6e0e34 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 65 | 0 | 65 | 0 | +| 68 | 0 | 68 | 0 | ## Common diff --git a/api_docs/kbn_search_connectors.devdocs.json b/api_docs/kbn_search_connectors.devdocs.json new file mode 100644 index 0000000000000..d008846114104 --- /dev/null +++ b/api_docs/kbn_search_connectors.devdocs.json @@ -0,0 +1,24537 @@ +{ + "id": "@kbn/search-connectors", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.cancelSyncs", + "type": "Function", + "tags": [], + "label": "cancelSyncs", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string) => Promise" + ], + "path": "packages/kbn-search-connectors/lib/cancel_syncs.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.cancelSyncs.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/cancel_syncs.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.cancelSyncs.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/cancel_syncs.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector", + "type": "Function", + "tags": [], + "label": "createConnector", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", input: { configuration?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" + }, + " | undefined; features?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorFeatures", + "text": "ConnectorFeatures" + }, + " | undefined; indexName: string | null; isNative: boolean; language: string | null; name?: string | undefined; pipeline: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + }, + "; serviceType?: string | null | undefined; }) => Promise<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Connector", + "text": "Connector" + }, + ">" + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.configuration", + "type": "CompoundType", + "tags": [], + "label": "configuration", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" + }, + " | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.features", + "type": "CompoundType", + "tags": [], + "label": "features", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorFeatures", + "text": "ConnectorFeatures" + }, + " | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.indexName", + "type": "CompoundType", + "tags": [], + "label": "indexName", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.isNative", + "type": "boolean", + "tags": [], + "label": "isNative", + "description": [], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.language", + "type": "CompoundType", + "tags": [], + "label": "language", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.pipeline", + "type": "Object", + "tags": [], + "label": "pipeline", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + } + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnector.$2.serviceType", + "type": "CompoundType", + "tags": [], + "label": "serviceType", + "description": [], + "signature": [ + "string | null | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument", + "type": "Function", + "tags": [], + "label": "createConnectorDocument", + "description": [], + "signature": [ + "({\n configuration,\n features,\n indexName,\n isNative,\n name,\n pipeline,\n serviceType,\n language,\n}: { configuration?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" + }, + " | undefined; features?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorFeatures", + "text": "ConnectorFeatures" + }, + " | undefined; indexName: string | null; isNative: boolean; language: string | null; name?: string | undefined; pipeline?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + }, + " | null | undefined; serviceType: string | null; }) => ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorDocument", + "text": "ConnectorDocument" + } + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1", + "type": "Object", + "tags": [], + "label": "{\n configuration,\n features,\n indexName,\n isNative,\n name,\n pipeline,\n serviceType,\n language,\n}", + "description": [], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.configuration", + "type": "CompoundType", + "tags": [], + "label": "configuration", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" + }, + " | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.features", + "type": "CompoundType", + "tags": [], + "label": "features", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorFeatures", + "text": "ConnectorFeatures" + }, + " | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.indexName", + "type": "CompoundType", + "tags": [], + "label": "indexName", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.isNative", + "type": "boolean", + "tags": [], + "label": "isNative", + "description": [], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.language", + "type": "CompoundType", + "tags": [], + "label": "language", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.pipeline", + "type": "CompoundType", + "tags": [], + "label": "pipeline", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + }, + " | null | undefined" + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.createConnectorDocument.$1.serviceType", + "type": "CompoundType", + "tags": [], + "label": "serviceType", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/lib/create_connector_document.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.deleteConnectorById", + "type": "Function", + "tags": [], + "label": "deleteConnectorById", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", id: string) => Promise<", + "WriteResponseBase", + ">" + ], + "path": "packages/kbn-search-connectors/lib/delete_connector.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.deleteConnectorById.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/delete_connector.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.deleteConnectorById.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/delete_connector.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectorById", + "type": "Function", + "tags": [], + "label": "fetchConnectorById", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string) => Promise<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.OptimisticConcurrency", + "text": "OptimisticConcurrency" + }, + "<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Connector", + "text": "Connector" + }, + "> | undefined>" + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectorById.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectorById.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectorByIndexName", + "type": "Function", + "tags": [], + "label": "fetchConnectorByIndexName", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", indexName: string) => Promise<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Connector", + "text": "Connector" + }, + " | undefined>" + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectorByIndexName.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectorByIndexName.$2", + "type": "string", + "tags": [], + "label": "indexName", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectors", + "type": "Function", + "tags": [], + "label": "fetchConnectors", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", indexNames?: string[] | undefined) => Promise<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Connector", + "text": "Connector" + }, + "[]>" + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectors.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchConnectors.$2", + "type": "Array", + "tags": [], + "label": "indexNames", + "description": [], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-search-connectors/lib/fetch_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchSyncJobsByConnectorId", + "type": "Function", + "tags": [], + "label": "fetchSyncJobsByConnectorId", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, from: number, size: number, syncJobType?: \"all\" | \"content\" | \"access_control\") => Promise<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Paginate", + "text": "Paginate" + }, + "<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorSyncJob", + "text": "ConnectorSyncJob" + }, + ">>" + ], + "path": "packages/kbn-search-connectors/lib/fetch_sync_jobs.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchSyncJobsByConnectorId.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/fetch_sync_jobs.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchSyncJobsByConnectorId.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/fetch_sync_jobs.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchSyncJobsByConnectorId.$3", + "type": "number", + "tags": [], + "label": "from", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-search-connectors/lib/fetch_sync_jobs.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchSyncJobsByConnectorId.$4", + "type": "number", + "tags": [], + "label": "size", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-search-connectors/lib/fetch_sync_jobs.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.fetchSyncJobsByConnectorId.$5", + "type": "CompoundType", + "tags": [], + "label": "syncJobType", + "description": [], + "signature": [ + "\"all\" | \"content\" | \"access_control\"" + ], + "path": "packages/kbn-search-connectors/lib/fetch_sync_jobs.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.isCategoryEntry", + "type": "Function", + "tags": [], + "label": "isCategoryEntry", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigCategoryProperties", + "text": "ConnectorConfigCategoryProperties" + }, + " | { label: string; value: boolean; } | null) => boolean" + ], + "path": "packages/kbn-search-connectors/utils/is_category_entry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.isCategoryEntry.$1", + "type": "CompoundType", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigCategoryProperties", + "text": "ConnectorConfigCategoryProperties" + }, + " | { label: string; value: boolean; } | null" + ], + "path": "packages/kbn-search-connectors/utils/is_category_entry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.isConfigEntry", + "type": "Function", + "tags": [], + "label": "isConfigEntry", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigCategoryProperties", + "text": "ConnectorConfigCategoryProperties" + }, + " | { label: string; value: boolean; } | null) => boolean" + ], + "path": "packages/kbn-search-connectors/utils/is_category_entry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.isConfigEntry.$1", + "type": "CompoundType", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigCategoryProperties", + "text": "ConnectorConfigCategoryProperties" + }, + " | { label: string; value: boolean; } | null" + ], + "path": "packages/kbn-search-connectors/utils/is_category_entry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.putUpdateNative", + "type": "Function", + "tags": [], + "label": "putUpdateNative", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, isNative: boolean) => Promise<", + "UpdateResponse", + ">" + ], + "path": "packages/kbn-search-connectors/lib/update_native.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.putUpdateNative.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_native.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.putUpdateNative.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_native.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.putUpdateNative.$3", + "type": "boolean", + "tags": [], + "label": "isNative", + "description": [], + "signature": [ + "boolean" + ], + "path": "packages/kbn-search-connectors/lib/update_native.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.startConnectorSync", + "type": "Function", + "tags": [], + "label": "startConnectorSync", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", { connectorId, jobType, targetIndexName, }: { connectorId: string; jobType?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncJobType", + "text": "SyncJobType" + }, + " | undefined; targetIndexName?: string | undefined; }) => Promise<", + "WriteResponseBase", + ">" + ], + "path": "packages/kbn-search-connectors/lib/start_sync.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.startConnectorSync.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/start_sync.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.startConnectorSync.$2", + "type": "Object", + "tags": [], + "label": "{\n connectorId,\n jobType,\n targetIndexName,\n }", + "description": [], + "path": "packages/kbn-search-connectors/lib/start_sync.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.startConnectorSync.$2.connectorId", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "path": "packages/kbn-search-connectors/lib/start_sync.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.startConnectorSync.$2.jobType", + "type": "CompoundType", + "tags": [], + "label": "jobType", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncJobType", + "text": "SyncJobType" + }, + " | undefined" + ], + "path": "packages/kbn-search-connectors/lib/start_sync.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.startConnectorSync.$2.targetIndexName", + "type": "string", + "tags": [], + "label": "targetIndexName", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-search-connectors/lib/start_sync.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorConfiguration", + "type": "Function", + "tags": [], + "label": "updateConnectorConfiguration", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, configuration: Record) => Promise<{ [x: string]: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigCategoryProperties", + "text": "ConnectorConfigCategoryProperties" + }, + " | { value: string | number | boolean; category?: string | undefined; default_value: string | number | boolean | null; depends_on: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Dependency", + "text": "Dependency" + }, + "[]; display: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + "; label: string; options: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SelectOption", + "text": "SelectOption" + }, + "[]; order?: number | null | undefined; placeholder?: string | undefined; required: boolean; sensitive: boolean; tooltip: string | null; type: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + "; ui_restrictions: string[]; validations: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Validation", + "text": "Validation" + }, + "[]; } | null; extract_full_html?: { label: string; value: boolean; } | undefined; use_document_level_security?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; use_text_extraction_service?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; }>" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_configuration.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorConfiguration.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_connector_configuration.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorConfiguration.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_configuration.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorConfiguration.$3", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "signature": [ + "Record" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_configuration.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorNameAndDescription", + "type": "Function", + "tags": [], + "label": "updateConnectorNameAndDescription", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, connectorUpdates: Partial>) => Promise<", + "WriteResponseBase", + ">" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_name_and_description.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorNameAndDescription.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_connector_name_and_description.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorNameAndDescription.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_name_and_description.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorNameAndDescription.$3", + "type": "Object", + "tags": [], + "label": "connectorUpdates", + "description": [], + "signature": [ + "Partial>" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_name_and_description.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorScheduling", + "type": "Function", + "tags": [], + "label": "updateConnectorScheduling", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, scheduling: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SchedulingConfiguraton", + "text": "SchedulingConfiguraton" + }, + ") => Promise<", + "WriteResponseBase", + ">" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_scheduling.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorScheduling.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_connector_scheduling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorScheduling.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_scheduling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorScheduling.$3", + "type": "Object", + "tags": [], + "label": "scheduling", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SchedulingConfiguraton", + "text": "SchedulingConfiguraton" + } + ], + "path": "packages/kbn-search-connectors/lib/update_connector_scheduling.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorServiceType", + "type": "Function", + "tags": [], + "label": "updateConnectorServiceType", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, serviceType: string) => Promise<", + "WriteResponseBase", + ">" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_service_type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorServiceType.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_connector_service_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorServiceType.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_service_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorServiceType.$3", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_service_type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorStatus", + "type": "Function", + "tags": [], + "label": "updateConnectorStatus", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, status: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorStatus", + "text": "ConnectorStatus" + }, + ") => Promise<", + "WriteResponseBase", + ">" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_status.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorStatus.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_connector_status.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorStatus.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_connector_status.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateConnectorStatus.$3", + "type": "Enum", + "tags": [], + "label": "status", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorStatus", + "text": "ConnectorStatus" + } + ], + "path": "packages/kbn-search-connectors/lib/update_connector_status.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFiltering", + "type": "Function", + "tags": [], + "label": "updateFiltering", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, { advancedSnippet, filteringRules, }: { advancedSnippet: string; filteringRules: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRule", + "text": "FilteringRule" + }, + "[]; }) => Promise<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + }, + " | undefined>" + ], + "path": "packages/kbn-search-connectors/lib/update_filtering.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFiltering.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_filtering.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFiltering.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_filtering.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFiltering.$3", + "type": "Object", + "tags": [], + "label": "{\n advancedSnippet,\n filteringRules,\n }", + "description": [], + "path": "packages/kbn-search-connectors/lib/update_filtering.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFiltering.$3.advancedSnippet", + "type": "string", + "tags": [], + "label": "advancedSnippet", + "description": [], + "path": "packages/kbn-search-connectors/lib/update_filtering.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFiltering.$3.filteringRules", + "type": "Array", + "tags": [], + "label": "filteringRules", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRule", + "text": "FilteringRule" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/lib/update_filtering.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFilteringDraft", + "type": "Function", + "tags": [], + "label": "updateFilteringDraft", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", connectorId: string, { advancedSnippet, filteringRules, }: { advancedSnippet: string; filteringRules: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRule", + "text": "FilteringRule" + }, + "[]; }) => Promise<", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + }, + " | undefined>" + ], + "path": "packages/kbn-search-connectors/lib/update_filtering_draft.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFilteringDraft.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/kbn-search-connectors/lib/update_filtering_draft.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFilteringDraft.$2", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-search-connectors/lib/update_filtering_draft.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFilteringDraft.$3", + "type": "Object", + "tags": [], + "label": "{\n advancedSnippet,\n filteringRules,\n }", + "description": [], + "path": "packages/kbn-search-connectors/lib/update_filtering_draft.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFilteringDraft.$3.advancedSnippet", + "type": "string", + "tags": [], + "label": "advancedSnippet", + "description": [], + "path": "packages/kbn-search-connectors/lib/update_filtering_draft.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.updateFilteringDraft.$3.filteringRules", + "type": "Array", + "tags": [], + "label": "filteringRules", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRule", + "text": "FilteringRule" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/lib/update_filtering_draft.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector", + "type": "Interface", + "tags": [], + "label": "Connector", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.api_key_id", + "type": "CompoundType", + "tags": [], + "label": "api_key_id", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.configuration", + "type": "CompoundType", + "tags": [], + "label": "configuration", + "description": [], + "signature": [ + "Record & { extract_full_html?: { label: string; value: boolean; } | undefined; use_document_level_security?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; use_text_extraction_service?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.custom_scheduling", + "type": "Object", + "tags": [], + "label": "custom_scheduling", + "description": [], + "signature": [ + "{ [x: string]: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.CustomScheduling", + "text": "CustomScheduling" + }, + " | null; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.description", + "type": "CompoundType", + "tags": [], + "label": "description", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.error", + "type": "CompoundType", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.features", + "type": "CompoundType", + "tags": [], + "label": "features", + "description": [], + "signature": [ + "Partial<{ document_level_security: { enabled: boolean; }; filtering_advanced_config: boolean; filtering_rules: boolean; incremental_sync: { enabled: boolean; }; sync_rules: { advanced?: { enabled: boolean; } | undefined; basic?: { enabled: boolean; } | undefined; }; }> | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.filtering", + "type": "Array", + "tags": [], + "label": "filtering", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringConfig", + "text": "FilteringConfig" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.index_name", + "type": "CompoundType", + "tags": [], + "label": "index_name", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.is_native", + "type": "boolean", + "tags": [], + "label": "is_native", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.language", + "type": "CompoundType", + "tags": [], + "label": "language", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_access_control_sync_error", + "type": "CompoundType", + "tags": [], + "label": "last_access_control_sync_error", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_access_control_sync_scheduled_at", + "type": "CompoundType", + "tags": [], + "label": "last_access_control_sync_scheduled_at", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_access_control_sync_status", + "type": "CompoundType", + "tags": [], + "label": "last_access_control_sync_status", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncStatus", + "text": "SyncStatus" + }, + " | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_incremental_sync_scheduled_at", + "type": "CompoundType", + "tags": [], + "label": "last_incremental_sync_scheduled_at", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_seen", + "type": "CompoundType", + "tags": [], + "label": "last_seen", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_sync_error", + "type": "CompoundType", + "tags": [], + "label": "last_sync_error", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_sync_scheduled_at", + "type": "CompoundType", + "tags": [], + "label": "last_sync_scheduled_at", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_sync_status", + "type": "CompoundType", + "tags": [], + "label": "last_sync_status", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncStatus", + "text": "SyncStatus" + }, + " | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.last_synced", + "type": "CompoundType", + "tags": [], + "label": "last_synced", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.pipeline", + "type": "CompoundType", + "tags": [], + "label": "pipeline", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + }, + " | null | undefined" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.scheduling", + "type": "Object", + "tags": [], + "label": "scheduling", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SchedulingConfiguraton", + "text": "SchedulingConfiguraton" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.service_type", + "type": "CompoundType", + "tags": [], + "label": "service_type", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.status", + "type": "Enum", + "tags": [], + "label": "status", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorStatus", + "text": "ConnectorStatus" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Connector.sync_now", + "type": "boolean", + "tags": [], + "label": "sync_now", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigCategoryProperties", + "type": "Interface", + "tags": [], + "label": "ConnectorConfigCategoryProperties", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigCategoryProperties.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigCategoryProperties.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigCategoryProperties.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"category\"" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties", + "type": "Interface", + "tags": [], + "label": "ConnectorConfigProperties", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.category", + "type": "string", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.default_value", + "type": "CompoundType", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "string | number | boolean | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Dependency", + "text": "Dependency" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.display", + "type": "Enum", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SelectOption", + "text": "SelectOption" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.order", + "type": "CompoundType", + "tags": [], + "label": "order", + "description": [], + "signature": [ + "number | null | undefined" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.tooltip", + "type": "CompoundType", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.type", + "type": "Enum", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Validation", + "text": "Validation" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfigProperties.value", + "type": "CompoundType", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "string | number | boolean | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorScheduling", + "type": "Interface", + "tags": [], + "label": "ConnectorScheduling", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorScheduling.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorScheduling.interval", + "type": "string", + "tags": [], + "label": "interval", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition", + "type": "Interface", + "tags": [], + "label": "ConnectorServerSideDefinition", + "description": [], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition.iconPath", + "type": "string", + "tags": [], + "label": "iconPath", + "description": [], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition.isBeta", + "type": "boolean", + "tags": [], + "label": "isBeta", + "description": [], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition.isNative", + "type": "boolean", + "tags": [], + "label": "isNative", + "description": [], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition.isTechPreview", + "type": "CompoundType", + "tags": [], + "label": "isTechPreview", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition.keywords", + "type": "Array", + "tags": [], + "label": "keywords", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorServerSideDefinition.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob", + "type": "Interface", + "tags": [], + "label": "ConnectorSyncJob", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.cancelation_requested_at", + "type": "CompoundType", + "tags": [], + "label": "cancelation_requested_at", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.canceled_at", + "type": "CompoundType", + "tags": [], + "label": "canceled_at", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.completed_at", + "type": "CompoundType", + "tags": [], + "label": "completed_at", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.connector", + "type": "Object", + "tags": [], + "label": "connector", + "description": [], + "signature": [ + "{ configuration: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" + }, + "; filtering: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + }, + " | ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + }, + "[] | null; id: string; index_name: string; language: string | null; pipeline: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + }, + " | null; service_type: string | null; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.deleted_document_count", + "type": "number", + "tags": [], + "label": "deleted_document_count", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.error", + "type": "CompoundType", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.indexed_document_count", + "type": "number", + "tags": [], + "label": "indexed_document_count", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.indexed_document_volume", + "type": "number", + "tags": [], + "label": "indexed_document_volume", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.job_type", + "type": "Enum", + "tags": [], + "label": "job_type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncJobType", + "text": "SyncJobType" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.last_seen", + "type": "CompoundType", + "tags": [], + "label": "last_seen", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.metadata", + "type": "Object", + "tags": [], + "label": "metadata", + "description": [], + "signature": [ + "{ [x: string]: unknown; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.started_at", + "type": "CompoundType", + "tags": [], + "label": "started_at", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.status", + "type": "Enum", + "tags": [], + "label": "status", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncStatus", + "text": "SyncStatus" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.total_document_count", + "type": "CompoundType", + "tags": [], + "label": "total_document_count", + "description": [], + "signature": [ + "number | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.trigger_method", + "type": "Enum", + "tags": [], + "label": "trigger_method", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.TriggerMethod", + "text": "TriggerMethod" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJob.worker_hostname", + "type": "CompoundType", + "tags": [], + "label": "worker_hostname", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CustomScheduling", + "type": "Interface", + "tags": [], + "label": "CustomScheduling", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CustomScheduling.configuration_overrides", + "type": "Object", + "tags": [], + "label": "configuration_overrides", + "description": [], + "signature": [ + "{ [x: string]: unknown; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CustomScheduling.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CustomScheduling.interval", + "type": "string", + "tags": [], + "label": "interval", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CustomScheduling.last_synced", + "type": "CompoundType", + "tags": [], + "label": "last_synced", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CustomScheduling.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Dependency", + "type": "Interface", + "tags": [], + "label": "Dependency", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Dependency.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Dependency.value", + "type": "CompoundType", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "string | number | boolean | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringConfig", + "type": "Interface", + "tags": [], + "label": "FilteringConfig", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringConfig.active", + "type": "Object", + "tags": [], + "label": "active", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringConfig.domain", + "type": "string", + "tags": [], + "label": "domain", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringConfig.draft", + "type": "Object", + "tags": [], + "label": "draft", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule", + "type": "Interface", + "tags": [], + "label": "FilteringRule", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.policy", + "type": "Enum", + "tags": [], + "label": "policy", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringPolicy", + "text": "FilteringPolicy" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.rule", + "type": "Enum", + "tags": [], + "label": "rule", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRuleRule", + "text": "FilteringRuleRule" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.updated_at", + "type": "string", + "tags": [], + "label": "updated_at", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRule.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRules", + "type": "Interface", + "tags": [], + "label": "FilteringRules", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRules.advanced_snippet", + "type": "Object", + "tags": [], + "label": "advanced_snippet", + "description": [], + "signature": [ + "{ created_at: string; updated_at: string; value: Record; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRules.rules", + "type": "Array", + "tags": [], + "label": "rules", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRule", + "text": "FilteringRule" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRules.validation", + "type": "Object", + "tags": [], + "label": "validation", + "description": [], + "signature": [ + "{ errors: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringValidation", + "text": "FilteringValidation" + }, + "[]; state: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringValidationState", + "text": "FilteringValidationState" + }, + "; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringValidation", + "type": "Interface", + "tags": [], + "label": "FilteringValidation", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringValidation.ids", + "type": "Array", + "tags": [], + "label": "ids", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringValidation.messages", + "type": "Array", + "tags": [], + "label": "messages", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.IngestPipelineParams", + "type": "Interface", + "tags": [], + "label": "IngestPipelineParams", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.IngestPipelineParams.extract_binary_content", + "type": "boolean", + "tags": [], + "label": "extract_binary_content", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.IngestPipelineParams.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.IngestPipelineParams.reduce_whitespace", + "type": "boolean", + "tags": [], + "label": "reduce_whitespace", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.IngestPipelineParams.run_ml_inference", + "type": "boolean", + "tags": [], + "label": "run_ml_inference", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Meta", + "type": "Interface", + "tags": [], + "label": "Meta", + "description": [], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Meta.page", + "type": "Object", + "tags": [], + "label": "page", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Page", + "text": "Page" + } + ], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NativeConnector", + "type": "Interface", + "tags": [], + "label": "NativeConnector", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NativeConnector.configuration", + "type": "CompoundType", + "tags": [], + "label": "configuration", + "description": [], + "signature": [ + "Record & { extract_full_html?: { label: string; value: boolean; } | undefined; use_document_level_security?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; use_text_extraction_service?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NativeConnector.features", + "type": "CompoundType", + "tags": [], + "label": "features", + "description": [], + "signature": [ + "Partial<{ document_level_security: { enabled: boolean; }; filtering_advanced_config: boolean; filtering_rules: boolean; incremental_sync: { enabled: boolean; }; sync_rules: { advanced?: { enabled: boolean; } | undefined; basic?: { enabled: boolean; } | undefined; }; }> | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NativeConnector.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NativeConnector.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.OptimisticConcurrency", + "type": "Interface", + "tags": [], + "label": "OptimisticConcurrency", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.OptimisticConcurrency", + "text": "OptimisticConcurrency" + }, + "" + ], + "path": "packages/kbn-search-connectors/types/optimistic_concurrency.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.OptimisticConcurrency.primaryTerm", + "type": "number", + "tags": [], + "label": "primaryTerm", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-search-connectors/types/optimistic_concurrency.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.OptimisticConcurrency.seqNo", + "type": "number", + "tags": [], + "label": "seqNo", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-search-connectors/types/optimistic_concurrency.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.OptimisticConcurrency.value", + "type": "Uncategorized", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "T" + ], + "path": "packages/kbn-search-connectors/types/optimistic_concurrency.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Page", + "type": "Interface", + "tags": [], + "label": "Page", + "description": [], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Page.from", + "type": "number", + "tags": [], + "label": "from", + "description": [], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Page.has_more_hits_than_total", + "type": "CompoundType", + "tags": [], + "label": "has_more_hits_than_total", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Page.size", + "type": "number", + "tags": [], + "label": "size", + "description": [], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Page.total", + "type": "number", + "tags": [], + "label": "total", + "description": [], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Paginate", + "type": "Interface", + "tags": [], + "label": "Paginate", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Paginate", + "text": "Paginate" + }, + "" + ], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Paginate._meta", + "type": "Object", + "tags": [], + "label": "_meta", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.Meta", + "text": "Meta" + } + ], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Paginate.data", + "type": "Array", + "tags": [], + "label": "data", + "description": [], + "signature": [ + "T[]" + ], + "path": "packages/kbn-search-connectors/types/pagination.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SchedulingConfiguraton", + "type": "Interface", + "tags": [], + "label": "SchedulingConfiguraton", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SchedulingConfiguraton.access_control", + "type": "Object", + "tags": [], + "label": "access_control", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorScheduling", + "text": "ConnectorScheduling" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SchedulingConfiguraton.full", + "type": "Object", + "tags": [], + "label": "full", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorScheduling", + "text": "ConnectorScheduling" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SchedulingConfiguraton.incremental", + "type": "Object", + "tags": [], + "label": "incremental", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorScheduling", + "text": "ConnectorScheduling" + } + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SelectOption", + "type": "Interface", + "tags": [], + "label": "SelectOption", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SelectOption.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SelectOption.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Validation", + "type": "Interface", + "tags": [], + "label": "Validation", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Validation.constraint", + "type": "CompoundType", + "tags": [], + "label": "constraint", + "description": [], + "signature": [ + "string | number" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.Validation.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorStatus", + "type": "Enum", + "tags": [], + "label": "ConnectorStatus", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.DisplayType", + "type": "Enum", + "tags": [], + "label": "DisplayType", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FeatureName", + "type": "Enum", + "tags": [], + "label": "FeatureName", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FieldType", + "type": "Enum", + "tags": [], + "label": "FieldType", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringPolicy", + "type": "Enum", + "tags": [], + "label": "FilteringPolicy", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringRuleRule", + "type": "Enum", + "tags": [], + "label": "FilteringRuleRule", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.FilteringValidationState", + "type": "Enum", + "tags": [], + "label": "FilteringValidationState", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SyncJobType", + "type": "Enum", + "tags": [], + "label": "SyncJobType", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.SyncStatus", + "type": "Enum", + "tags": [], + "label": "SyncStatus", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.TriggerMethod", + "type": "Enum", + "tags": [], + "label": "TriggerMethod", + "description": [], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CONNECTOR_DEFINITIONS", + "type": "Array", + "tags": [], + "label": "CONNECTOR_DEFINITIONS", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorServerSideDefinition", + "text": "ConnectorServerSideDefinition" + }, + "[]" + ], + "path": "packages/kbn-search-connectors/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorConfiguration", + "type": "Type", + "tags": [], + "label": "ConnectorConfiguration", + "description": [], + "signature": [ + "Record & { extract_full_html?: { label: string; value: boolean; } | undefined; use_document_level_security?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; use_text_extraction_service?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfigProperties", + "text": "ConnectorConfigProperties" + }, + " | undefined; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorCustomScheduling", + "type": "Type", + "tags": [], + "label": "ConnectorCustomScheduling", + "description": [], + "signature": [ + "{ [x: string]: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.CustomScheduling", + "text": "CustomScheduling" + }, + " | null; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorDocument", + "type": "Type", + "tags": [], + "label": "ConnectorDocument", + "description": [], + "signature": [ + "{ error: string | null; name: string; features: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorFeatures", + "text": "ConnectorFeatures" + }, + "; description: string | null; status: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorStatus", + "text": "ConnectorStatus" + }, + "; language: string | null; index_name: string | null; configuration: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" + }, + "; last_seen: string | null; pipeline?: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + }, + " | null | undefined; api_key_id: string | null; custom_scheduling: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorCustomScheduling", + "text": "ConnectorCustomScheduling" + }, + "; filtering: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringConfig", + "text": "FilteringConfig" + }, + "[]; is_native: boolean; last_access_control_sync_error: string | null; last_access_control_sync_scheduled_at: string | null; last_access_control_sync_status: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncStatus", + "text": "SyncStatus" + }, + " | null; last_incremental_sync_scheduled_at: string | null; last_sync_error: string | null; last_sync_scheduled_at: string | null; last_sync_status: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncStatus", + "text": "SyncStatus" + }, + " | null; last_synced: string | null; scheduling: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SchedulingConfiguraton", + "text": "SchedulingConfiguraton" + }, + "; service_type: string | null; sync_now: boolean; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorFeatures", + "type": "Type", + "tags": [], + "label": "ConnectorFeatures", + "description": [], + "signature": [ + "Partial<{ document_level_security: { enabled: boolean; }; filtering_advanced_config: boolean; filtering_rules: boolean; incremental_sync: { enabled: boolean; }; sync_rules: { advanced?: { enabled: boolean; } | undefined; basic?: { enabled: boolean; } | undefined; }; }> | null" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX", + "type": "string", + "tags": [], + "label": "CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX", + "description": [], + "signature": [ + "\".search-acl-filter-\"" + ], + "path": "packages/kbn-search-connectors/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CONNECTORS_INDEX", + "type": "string", + "tags": [], + "label": "CONNECTORS_INDEX", + "description": [], + "signature": [ + "\".elastic-connectors\"" + ], + "path": "packages/kbn-search-connectors/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CONNECTORS_JOBS_INDEX", + "type": "string", + "tags": [], + "label": "CONNECTORS_JOBS_INDEX", + "description": [], + "signature": [ + "\".elastic-connectors-sync-jobs\"" + ], + "path": "packages/kbn-search-connectors/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CONNECTORS_VERSION", + "type": "number", + "tags": [], + "label": "CONNECTORS_VERSION", + "description": [], + "signature": [ + "1" + ], + "path": "packages/kbn-search-connectors/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.ConnectorSyncJobDocument", + "type": "Type", + "tags": [], + "label": "ConnectorSyncJobDocument", + "description": [], + "signature": [ + "{ error: string | null; connector: { configuration: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" + }, + "; filtering: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + }, + " | ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FilteringRules", + "text": "FilteringRules" + }, + "[] | null; id: string; index_name: string; language: string | null; pipeline: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" + }, + " | null; service_type: string | null; }; status: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncStatus", + "text": "SyncStatus" + }, + "; created_at: string; metadata: Record; job_type: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.SyncJobType", + "text": "SyncJobType" + }, + "; last_seen: string | null; cancelation_requested_at: string | null; canceled_at: string | null; completed_at: string | null; deleted_document_count: number; indexed_document_count: number; indexed_document_volume: number; started_at: string | null; total_document_count: number | null; trigger_method: ", + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.TriggerMethod", + "text": "TriggerMethod" + }, + "; worker_hostname: string | null; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CURRENT_CONNECTORS_INDEX", + "type": "string", + "tags": [], + "label": "CURRENT_CONNECTORS_INDEX", + "description": [], + "signature": [ + "\".elastic-connectors-v1\"" + ], + "path": "packages/kbn-search-connectors/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.CURRENT_CONNECTORS_JOB_INDEX", + "type": "string", + "tags": [], + "label": "CURRENT_CONNECTORS_JOB_INDEX", + "description": [], + "signature": [ + "\".elastic-connectors-sync-jobs-v1\"" + ], + "path": "packages/kbn-search-connectors/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.DependencyLookup", + "type": "Type", + "tags": [], + "label": "DependencyLookup", + "description": [], + "signature": [ + "{ [x: string]: string | number | boolean | null; }" + ], + "path": "packages/kbn-search-connectors/types/connectors.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS", + "type": "Object", + "tags": [], + "label": "NATIVE_CONNECTOR_DEFINITIONS", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage", + "type": "Object", + "tags": [], + "label": "azure_blob_storage", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name", + "type": "Object", + "tags": [], + "label": "account_name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_name.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key", + "type": "Object", + "tags": [], + "label": "account_key", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.account_key.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint", + "type": "Object", + "tags": [], + "label": "blob_endpoint", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.blob_endpoint.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads", + "type": "Object", + "tags": [], + "label": "concurrent_downloads", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "{ type: string; constraint: number; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.configuration.concurrent_downloads.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.azure_blob_storage.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence", + "type": "Object", + "tags": [], + "label": "confluence", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source", + "type": "Object", + "tags": [], + "label": "data_source", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".DROPDOWN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ label: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.data_source.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username", + "type": "Object", + "tags": [], + "label": "username", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.username.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email", + "type": "Object", + "tags": [], + "label": "account_email", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.account_email.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token", + "type": "Object", + "tags": [], + "label": "api_token", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.api_token.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url", + "type": "Object", + "tags": [], + "label": "confluence_url", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.confluence_url.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces", + "type": "Object", + "tags": [], + "label": "spaces", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".LIST" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.spaces.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled", + "type": "Object", + "tags": [], + "label": "ssl_enabled", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_enabled.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca", + "type": "Object", + "tags": [], + "label": "ssl_ca", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.ssl_ca.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads", + "type": "Object", + "tags": [], + "label": "concurrent_downloads", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "{ constraint: number; type: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.configuration.concurrent_downloads.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.confluence.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox", + "type": "Object", + "tags": [], + "label": "dropbox", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path", + "type": "Object", + "tags": [], + "label": "path", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.path.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key", + "type": "Object", + "tags": [], + "label": "app_key", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_key.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret", + "type": "Object", + "tags": [], + "label": "app_secret", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.app_secret.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token", + "type": "Object", + "tags": [], + "label": "refresh_token", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.refresh_token.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads", + "type": "Object", + "tags": [], + "label": "concurrent_downloads", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.concurrent_downloads.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira", + "type": "Object", + "tags": [], + "label": "jira", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source", + "type": "Object", + "tags": [], + "label": "data_source", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".DROPDOWN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ label: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.data_source.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username", + "type": "Object", + "tags": [], + "label": "username", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.username.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email", + "type": "Object", + "tags": [], + "label": "account_email", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.account_email.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token", + "type": "Object", + "tags": [], + "label": "api_token", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: string; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.api_token.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url", + "type": "Object", + "tags": [], + "label": "jira_url", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.jira_url.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects", + "type": "Object", + "tags": [], + "label": "projects", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".LIST" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.projects.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled", + "type": "Object", + "tags": [], + "label": "ssl_enabled", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_enabled.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca", + "type": "Object", + "tags": [], + "label": "ssl_ca", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.ssl_ca.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads", + "type": "Object", + "tags": [], + "label": "concurrent_downloads", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "{ type: string; constraint: number; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.configuration.concurrent_downloads.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.jira.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb", + "type": "Object", + "tags": [], + "label": "mongodb", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host", + "type": "Object", + "tags": [], + "label": "host", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.host.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.user.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database", + "type": "Object", + "tags": [], + "label": "database", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.database.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection", + "type": "Object", + "tags": [], + "label": "collection", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.collection.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection", + "type": "Object", + "tags": [], + "label": "direct_connection", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.configuration.direct_connection.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features.FeatureName.FILTERING_ADVANCED_CONFIG", + "type": "boolean", + "tags": [], + "label": "[FeatureName.FILTERING_ADVANCED_CONFIG]", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features.FeatureName.FILTERING_RULES", + "type": "boolean", + "tags": [], + "label": "[FeatureName.FILTERING_RULES]", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mongodb.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql", + "type": "Object", + "tags": [], + "label": "mssql", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host", + "type": "Object", + "tags": [], + "label": "host", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.host.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port", + "type": "Object", + "tags": [], + "label": "port", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.port.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username", + "type": "Object", + "tags": [], + "label": "username", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.username.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database", + "type": "Object", + "tags": [], + "label": "database", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.database.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables", + "type": "Object", + "tags": [], + "label": "tables", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".LIST" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.tables.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled", + "type": "Object", + "tags": [], + "label": "ssl_enabled", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_enabled.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca", + "type": "Object", + "tags": [], + "label": "ssl_ca", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.ssl_ca.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.schema.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size", + "type": "Object", + "tags": [], + "label": "fetch_size", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.fetch_size.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host", + "type": "Object", + "tags": [], + "label": "validate_host", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.configuration.validate_host.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mssql.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql", + "type": "Object", + "tags": [], + "label": "mysql", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host", + "type": "Object", + "tags": [], + "label": "host", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.host.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port", + "type": "Object", + "tags": [], + "label": "port", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.port.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.user.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database", + "type": "Object", + "tags": [], + "label": "database", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.database.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables", + "type": "Object", + "tags": [], + "label": "tables", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".LIST" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.tables.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled", + "type": "Object", + "tags": [], + "label": "ssl_enabled", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_enabled.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca", + "type": "Object", + "tags": [], + "label": "ssl_ca", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.ssl_ca.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size", + "type": "Object", + "tags": [], + "label": "fetch_size", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.fetch_size.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.mysql.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive", + "type": "Object", + "tags": [], + "label": "network_drive", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username", + "type": "Object", + "tags": [], + "label": "username", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.username.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip", + "type": "Object", + "tags": [], + "label": "server_ip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_ip.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port", + "type": "Object", + "tags": [], + "label": "server_port", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.server_port.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path", + "type": "Object", + "tags": [], + "label": "drive_path", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.configuration.drive_path.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.network_drive.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql", + "type": "Object", + "tags": [], + "label": "postgresql", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host", + "type": "Object", + "tags": [], + "label": "host", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.host.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port", + "type": "Object", + "tags": [], + "label": "port", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.port.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username", + "type": "Object", + "tags": [], + "label": "username", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.username.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database", + "type": "Object", + "tags": [], + "label": "database", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.database.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables", + "type": "Object", + "tags": [], + "label": "tables", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".LIST" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.tables.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled", + "type": "Object", + "tags": [], + "label": "ssl_enabled", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_enabled.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca", + "type": "Object", + "tags": [], + "label": "ssl_ca", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.default_value", + "type": "string", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.ssl_ca.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size", + "type": "Object", + "tags": [], + "label": "fetch_size", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.fetch_size.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.postgresql.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow", + "type": "Object", + "tags": [], + "label": "servicenow", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url", + "type": "Object", + "tags": [], + "label": "url", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.url.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username", + "type": "Object", + "tags": [], + "label": "username", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.username.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password", + "type": "Object", + "tags": [], + "label": "password", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.password.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services", + "type": "Object", + "tags": [], + "label": "services", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".LIST" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.services.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count", + "type": "Object", + "tags": [], + "label": "retry_count", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.retry_count.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads", + "type": "Object", + "tags": [], + "label": "concurrent_downloads", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.default_value", + "type": "number", + "tags": [], + "label": "default_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".NUMERIC" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.tooltip", + "type": "Uncategorized", + "tags": [], + "label": "tooltip", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".INTEGER" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.configuration.concurrent_downloads.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.servicenow.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online", + "type": "Object", + "tags": [], + "label": "sharepoint_online", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration", + "type": "Object", + "tags": [], + "label": "configuration", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id", + "type": "Object", + "tags": [], + "label": "tenant_id", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_id.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name", + "type": "Object", + "tags": [], + "label": "tenant_name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.tenant_name.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id", + "type": "Object", + "tags": [], + "label": "client_id", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.client_id.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value", + "type": "Object", + "tags": [], + "label": "secret_value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTBOX" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".STRING" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.secret_value.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections", + "type": "Object", + "tags": [], + "label": "site_collections", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TEXTAREA" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".LIST" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.site_collections.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service", + "type": "Object", + "tags": [], + "label": "use_text_extraction_service", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_text_extraction_service.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security", + "type": "Object", + "tags": [], + "label": "use_document_level_security", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.use_document_level_security.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions", + "type": "Object", + "tags": [], + "label": "fetch_drive_item_permissions", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_drive_item_permissions.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions", + "type": "Object", + "tags": [], + "label": "fetch_unique_page_permissions", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_page_permissions.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions", + "type": "Object", + "tags": [], + "label": "fetch_unique_list_permissions", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_permissions.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions", + "type": "Object", + "tags": [], + "label": "fetch_unique_list_item_permissions", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.default_value", + "type": "boolean", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.configuration.fetch_unique_list_item_permissions.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features", + "type": "Object", + "tags": [], + "label": "features", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.SYNC_RULES", + "type": "Object", + "tags": [], + "label": "[FeatureName.SYNC_RULES]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.SYNC_RULES.advanced", + "type": "Object", + "tags": [], + "label": "advanced", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.SYNC_RULES.advanced.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.SYNC_RULES.basic", + "type": "Object", + "tags": [], + "label": "basic", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.SYNC_RULES.basic.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.DOCUMENT_LEVEL_SECURITY", + "type": "Object", + "tags": [], + "label": "[FeatureName.DOCUMENT_LEVEL_SECURITY]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.DOCUMENT_LEVEL_SECURITY.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.INCREMENTAL_SYNC", + "type": "Object", + "tags": [], + "label": "[FeatureName.INCREMENTAL_SYNC]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.features.FeatureName.INCREMENTAL_SYNC.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_online.serviceType", + "type": "string", + "tags": [], + "label": "serviceType", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx new file mode 100644 index 0000000000000..f3bacf70c7e70 --- /dev/null +++ b/api_docs/kbn_search_connectors.mdx @@ -0,0 +1,42 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnSearchConnectorsPluginApi +slug: /kibana-dev-docs/api/kbn-search-connectors +title: "@kbn/search-connectors" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/search-connectors plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] +--- +import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; + + + +Contact [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1678 | 0 | 1678 | 0 | + +## Common + +### Objects + + +### Functions + + +### Interfaces + + +### Enums + + +### Consts, variables and types + + diff --git a/api_docs/kbn_search_response_warnings.devdocs.json b/api_docs/kbn_search_response_warnings.devdocs.json index aa22ec9dc5bdb..1ffcdaf144fb1 100644 --- a/api_docs/kbn_search_response_warnings.devdocs.json +++ b/api_docs/kbn_search_response_warnings.devdocs.json @@ -29,7 +29,7 @@ "\nIntercepts warnings for a search source request" ], "signature": [ - "({ services, adapter, options, }: { services: { data: ", + "({ services, adapter, }: { services: { data: ", { "pluginId": "data", "scope": "public", @@ -53,7 +53,7 @@ "section": "def-common.RequestAdapter", "text": "RequestAdapter" }, - "; options?: { disableShardFailureWarning?: boolean | undefined; } | undefined; }) => ", + "; }) => ", { "pluginId": "@kbn/search-response-warnings", "scope": "common", @@ -61,7 +61,7 @@ "section": "def-common.SearchResponseInterceptedWarning", "text": "SearchResponseInterceptedWarning" }, - "[] | undefined" + "[]" ], "path": "packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx", "deprecated": false, @@ -72,7 +72,7 @@ "id": "def-common.getSearchResponseInterceptedWarnings.$1", "type": "Object", "tags": [], - "label": "{\n services,\n adapter,\n options,\n}", + "label": "{\n services,\n adapter,\n}", "description": [], "path": "packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx", "deprecated": false, @@ -127,20 +127,6 @@ "path": "packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "@kbn/search-response-warnings", - "id": "def-common.getSearchResponseInterceptedWarnings.$1.options", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "{ disableShardFailureWarning?: boolean | undefined; } | undefined" - ], - "path": "packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx", - "deprecated": false, - "trackAdoption": false } ] } @@ -150,57 +136,46 @@ }, { "parentPluginId": "@kbn/search-response-warnings", - "id": "def-common.removeInterceptedWarningDuplicates", + "id": "def-common.hasUnsupportedDownsampledAggregationFailure", "type": "Function", "tags": [], - "label": "removeInterceptedWarningDuplicates", - "description": [ - "\nRemoves duplicated warnings" - ], + "label": "hasUnsupportedDownsampledAggregationFailure", + "description": [], "signature": [ - "(interceptedWarnings: ", + "(warning: ", { - "pluginId": "@kbn/search-response-warnings", - "scope": "common", - "docId": "kibKbnSearchResponseWarningsPluginApi", - "section": "def-common.SearchResponseInterceptedWarning", - "text": "SearchResponseInterceptedWarning" - }, - "[] | undefined) => ", - { - "pluginId": "@kbn/search-response-warnings", - "scope": "common", - "docId": "kibKbnSearchResponseWarningsPluginApi", - "section": "def-common.SearchResponseInterceptedWarning", - "text": "SearchResponseInterceptedWarning" + "pluginId": "data", + "scope": "public", + "docId": "kibDataSearchPluginApi", + "section": "def-public.SearchResponseIncompleteWarning", + "text": "SearchResponseIncompleteWarning" }, - "[] | undefined" + ") => boolean" ], - "path": "packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx", + "path": "packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/search-response-warnings", - "id": "def-common.removeInterceptedWarningDuplicates.$1", - "type": "Array", + "id": "def-common.hasUnsupportedDownsampledAggregationFailure.$1", + "type": "Object", "tags": [], - "label": "interceptedWarnings", + "label": "warning", "description": [], "signature": [ { - "pluginId": "@kbn/search-response-warnings", - "scope": "common", - "docId": "kibKbnSearchResponseWarningsPluginApi", - "section": "def-common.SearchResponseInterceptedWarning", - "text": "SearchResponseInterceptedWarning" - }, - "[] | undefined" + "pluginId": "data", + "scope": "public", + "docId": "kibDataSearchPluginApi", + "section": "def-public.SearchResponseIncompleteWarning", + "text": "SearchResponseIncompleteWarning" + } ], - "path": "packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx", + "path": "packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.ts", "deprecated": false, "trackAdoption": false, - "isRequired": false + "isRequired": true } ], "returnComment": [], @@ -275,14 +250,18 @@ { "parentPluginId": "@kbn/search-response-warnings", "id": "def-common.SearchResponseInterceptedWarning.originalWarning", - "type": "CompoundType", + "type": "Object", "tags": [], "label": "originalWarning", "description": [], "signature": [ - "SearchResponseTimeoutWarning", - " | ", - "SearchResponseShardFailureWarning" + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataSearchPluginApi", + "section": "def-public.SearchResponseIncompleteWarning", + "text": "SearchResponseIncompleteWarning" + } ], "path": "packages/kbn-search-response-warnings/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 531afa21b44de..a4b05b73ed3cc 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 16 | 0 | 8 | 0 | +| 15 | 0 | 8 | 0 | ## Common diff --git a/api_docs/kbn_security_solution_features.devdocs.json b/api_docs/kbn_security_solution_features.devdocs.json new file mode 100644 index 0000000000000..efeb01d3237a1 --- /dev/null +++ b/api_docs/kbn_security_solution_features.devdocs.json @@ -0,0 +1,487 @@ +{ + "id": "@kbn/security-solution-features", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeatureParams", + "type": "Interface", + "tags": [], + "label": "AppFeatureParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.AppFeatureParams", + "text": "AppFeatureParams" + }, + "" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeatureParams.baseKibanaFeature", + "type": "Object", + "tags": [], + "label": "baseKibanaFeature", + "description": [], + "signature": [ + "{ id: string; order?: number | undefined; name: string; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; description?: string | undefined; category: ", + { + "pluginId": "@kbn/core-application-common", + "scope": "common", + "docId": "kibKbnCoreApplicationCommonPluginApi", + "section": "def-common.AppCategory", + "text": "AppCategory" + }, + "; management?: { [sectionId: string]: readonly string[]; } | undefined; privileges: { all: ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivileges", + "text": "FeatureKibanaPrivileges" + }, + "; read: ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivileges", + "text": "FeatureKibanaPrivileges" + }, + "; } | null; catalogue?: readonly string[] | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; privilegesTooltip?: string | undefined; reserved?: { description: string; privileges: readonly ", + "ReservedKibanaPrivilege", + "[]; } | undefined; }" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeatureParams.baseKibanaSubFeatureIds", + "type": "Array", + "tags": [], + "label": "baseKibanaSubFeatureIds", + "description": [], + "signature": [ + "T[]" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeatureParams.subFeaturesMap", + "type": "Object", + "tags": [], + "label": "subFeaturesMap", + "description": [], + "signature": [ + "Map" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeatureKeys", + "type": "Type", + "tags": [], + "label": "AppFeatureKeys", + "description": [], + "signature": [ + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.AppFeatureKeyType", + "text": "AppFeatureKeyType" + }, + "[]" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeatureKeyType", + "type": "Type", + "tags": [], + "label": "AppFeatureKeyType", + "description": [], + "signature": [ + "AppFeatureSecurityKey", + " | ", + "AppFeatureCasesKey", + " | ", + "AppFeatureAssistantKey" + ], + "path": "x-pack/packages/security-solution/features/src/app_features_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeatureKibanaConfig", + "type": "Type", + "tags": [], + "label": "AppFeatureKibanaConfig", + "description": [], + "signature": [ + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "<", + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.BaseKibanaFeatureConfig", + "text": "BaseKibanaFeatureConfig" + }, + "> & { subFeatureIds?: T[] | undefined; subFeaturesPrivileges?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "<", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.SubFeaturePrivilegeConfig", + "text": "SubFeaturePrivilegeConfig" + }, + ">[] | undefined; }" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeaturesAssistantConfig", + "type": "Type", + "tags": [], + "label": "AppFeaturesAssistantConfig", + "description": [], + "signature": [ + "Map<", + "AppFeatureAssistantKey", + ", ", + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.AppFeatureKibanaConfig", + "text": "AppFeatureKibanaConfig" + }, + "<", + "AssistantSubFeatureId", + ">>" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeaturesCasesConfig", + "type": "Type", + "tags": [], + "label": "AppFeaturesCasesConfig", + "description": [], + "signature": [ + "Map<", + "AppFeatureCasesKey", + ", ", + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.AppFeatureKibanaConfig", + "text": "AppFeatureKibanaConfig" + }, + "<", + "CasesSubFeatureId", + ">>" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeaturesConfig", + "type": "Type", + "tags": [], + "label": "AppFeaturesConfig", + "description": [], + "signature": [ + "Map<", + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.AppFeatureKeyType", + "text": "AppFeatureKeyType" + }, + ", ", + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.AppFeatureKibanaConfig", + "text": "AppFeatureKibanaConfig" + }, + ">" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppFeaturesSecurityConfig", + "type": "Type", + "tags": [], + "label": "AppFeaturesSecurityConfig", + "description": [], + "signature": [ + "Map<", + "AppFeatureSecurityKey", + ", ", + { + "pluginId": "@kbn/security-solution-features", + "scope": "common", + "docId": "kibKbnSecuritySolutionFeaturesPluginApi", + "section": "def-common.AppFeatureKibanaConfig", + "text": "AppFeatureKibanaConfig" + }, + "<", + "SecuritySubFeatureId", + ">>" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.AppSubFeaturesMap", + "type": "Type", + "tags": [], + "label": "AppSubFeaturesMap", + "description": [], + "signature": [ + "Map" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.BaseKibanaFeatureConfig", + "type": "Type", + "tags": [], + "label": "BaseKibanaFeatureConfig", + "description": [], + "signature": [ + "{ id: string; order?: number | undefined; name: string; alerting?: readonly string[] | undefined; cases?: readonly string[] | undefined; description?: string | undefined; category: ", + { + "pluginId": "@kbn/core-application-common", + "scope": "common", + "docId": "kibKbnCoreApplicationCommonPluginApi", + "section": "def-common.AppCategory", + "text": "AppCategory" + }, + "; management?: { [sectionId: string]: readonly string[]; } | undefined; privileges: { all: ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivileges", + "text": "FeatureKibanaPrivileges" + }, + "; read: ", + { + "pluginId": "features", + "scope": "common", + "docId": "kibFeaturesPluginApi", + "section": "def-common.FeatureKibanaPrivileges", + "text": "FeatureKibanaPrivileges" + }, + "; } | null; catalogue?: readonly string[] | undefined; excludeFromBasePrivileges?: boolean | undefined; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; app: readonly string[]; privilegesTooltip?: string | undefined; reserved?: { description: string; privileges: readonly ", + "ReservedKibanaPrivilege", + "[]; } | undefined; }" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/security-solution-features", + "id": "def-common.SubFeaturesPrivileges", + "type": "Type", + "tags": [], + "label": "SubFeaturesPrivileges", + "description": [], + "signature": [ + "{ id?: string | undefined; name?: string | undefined; includeIn?: \"none\" | \"read\" | \"all\" | undefined; minimumLicense?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "<\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined>; alerting?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "<{ rule?: { all?: readonly string[] | undefined; read?: readonly string[] | undefined; } | undefined; alert?: { all?: readonly string[] | undefined; read?: readonly string[] | undefined; } | undefined; } | undefined>; cases?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; } | undefined>; disabled?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "; management?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "<{ [sectionId: string]: readonly string[]; } | undefined>; ui?: readonly string[] | undefined; catalogue?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "; app?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "; requireAllSpaces?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "; api?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "; savedObject?: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "<{ all: readonly string[]; read: readonly string[]; }> | undefined; }" + ], + "path": "x-pack/packages/security-solution/features/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx new file mode 100644 index 0000000000000..320445b6eb584 --- /dev/null +++ b/api_docs/kbn_security_solution_features.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnSecuritySolutionFeaturesPluginApi +slug: /kibana-dev-docs/api/kbn-security-solution-features +title: "@kbn/security-solution-features" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/security-solution-features plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] +--- +import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; + + + +Contact [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 14 | 0 | 14 | 6 | + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_security_solution_navigation.devdocs.json b/api_docs/kbn_security_solution_navigation.devdocs.json index bf9e90cb7ae75..9bfa19202578c 100644 --- a/api_docs/kbn_security_solution_navigation.devdocs.json +++ b/api_docs/kbn_security_solution_navigation.devdocs.json @@ -991,7 +991,7 @@ "label": "NavigateTo", "description": [], "signature": [ - "(param: { url?: string | undefined; appId?: string | undefined; } & ", + "(param: { url?: string | undefined; appId?: string | undefined; restoreScroll?: boolean | undefined; } & ", { "pluginId": "@kbn/core-application-browser", "scope": "common", @@ -1014,7 +1014,7 @@ "label": "param", "description": [], "signature": [ - "{ url?: string | undefined; appId?: string | undefined; } & ", + "{ url?: string | undefined; appId?: string | undefined; restoreScroll?: boolean | undefined; } & ", { "pluginId": "@kbn/core-application-browser", "scope": "common", diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 7ba0bfa26420b..59d743d13f6f5 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 83879ef322a10..5e21c7adb548b 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index 068006b37a637..bc8763c6cd5b0 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index dffbe8973049b..385427048c604 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index 7bc25bbefdd9a..161cf513064ec 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 574af8bded64c..9ad3744478ca9 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 2c2e112945173..ce999d143a990 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 99c81007fab71..32dffaa7c501e 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index 4ce23e47fba8f..64eb50731e5f6 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index ae9a0a7e9b537..d5c353884be49 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 1cf8eca2166d8..809d66fcf0ce1 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 464c6e10d8866..16a083a8dcaec 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 07e8aa5b5556c..cb2ce46d0a79c 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 974020ce0ade0..6e5f0b52df3ed 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 260431910b459..6b5109d5dda27 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index fb8c299509112..0cf501e8c64e4 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 056b62569ad6b..c04457696052c 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 43c0751f6bdfc..b263459bda9c7 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index b6abee2a70f3a..aafa96c6b8b08 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index ab93744b3e11f..01cb9df8560ba 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 6a865e0ae4142..642ee9e5049b5 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 31d4820763432..cb9b8368e7fbe 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index ec9839bb4db68..fce6de22dccae 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.devdocs.json b/api_docs/kbn_serverless_common_settings.devdocs.json new file mode 100644 index 0000000000000..7a9606cbc24d0 --- /dev/null +++ b/api_docs/kbn_serverless_common_settings.devdocs.json @@ -0,0 +1,43 @@ +{ + "id": "@kbn/serverless-common-settings", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/serverless-common-settings", + "id": "def-common.ALL_COMMON_SETTINGS", + "type": "Array", + "tags": [], + "label": "ALL_COMMON_SETTINGS", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/serverless/settings/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx new file mode 100644 index 0000000000000..6d4ddc26935e2 --- /dev/null +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnServerlessCommonSettingsPluginApi +slug: /kibana-dev-docs/api/kbn-serverless-common-settings +title: "@kbn/serverless-common-settings" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/serverless-common-settings plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] +--- +import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; + + + +Contact [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1 | 0 | 1 | 0 | + +## Common + +### Consts, variables and types + + diff --git a/api_docs/kbn_serverless_observability_settings.devdocs.json b/api_docs/kbn_serverless_observability_settings.devdocs.json new file mode 100644 index 0000000000000..35b798a42f024 --- /dev/null +++ b/api_docs/kbn_serverless_observability_settings.devdocs.json @@ -0,0 +1,43 @@ +{ + "id": "@kbn/serverless-observability-settings", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/serverless-observability-settings", + "id": "def-common.OBSERVABILITY_PROJECT_SETTINGS", + "type": "Array", + "tags": [], + "label": "OBSERVABILITY_PROJECT_SETTINGS", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/serverless/settings/observability_project/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx new file mode 100644 index 0000000000000..7628d3a9943f4 --- /dev/null +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnServerlessObservabilitySettingsPluginApi +slug: /kibana-dev-docs/api/kbn-serverless-observability-settings +title: "@kbn/serverless-observability-settings" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/serverless-observability-settings plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] +--- +import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; + + + +Contact [@elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1 | 0 | 1 | 0 | + +## Common + +### Consts, variables and types + + diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index c40cef6f7927c..13960b4c4fe5f 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.devdocs.json b/api_docs/kbn_serverless_search_settings.devdocs.json new file mode 100644 index 0000000000000..772c029aea954 --- /dev/null +++ b/api_docs/kbn_serverless_search_settings.devdocs.json @@ -0,0 +1,43 @@ +{ + "id": "@kbn/serverless-search-settings", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/serverless-search-settings", + "id": "def-common.SEARCH_PROJECT_SETTINGS", + "type": "Array", + "tags": [], + "label": "SEARCH_PROJECT_SETTINGS", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/serverless/settings/search_project/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx new file mode 100644 index 0000000000000..d3aeb49e79cbc --- /dev/null +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnServerlessSearchSettingsPluginApi +slug: /kibana-dev-docs/api/kbn-serverless-search-settings +title: "@kbn/serverless-search-settings" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/serverless-search-settings plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] +--- +import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; + + + +Contact [@elastic/enterprise-search-frontend @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/enterprise-search-frontend ) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1 | 0 | 1 | 0 | + +## Common + +### Consts, variables and types + + diff --git a/api_docs/kbn_serverless_security_settings.devdocs.json b/api_docs/kbn_serverless_security_settings.devdocs.json new file mode 100644 index 0000000000000..fd54e257a28c8 --- /dev/null +++ b/api_docs/kbn_serverless_security_settings.devdocs.json @@ -0,0 +1,43 @@ +{ + "id": "@kbn/serverless-security-settings", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/serverless-security-settings", + "id": "def-common.SECURITY_PROJECT_SETTINGS", + "type": "Array", + "tags": [], + "label": "SECURITY_PROJECT_SETTINGS", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/serverless/settings/security_project/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx new file mode 100644 index 0000000000000..6396bfa0e92c3 --- /dev/null +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnServerlessSecuritySettingsPluginApi +slug: /kibana-dev-docs/api/kbn-serverless-security-settings +title: "@kbn/serverless-security-settings" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/serverless-security-settings plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] +--- +import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; + + + +Contact [@elastic/security-solution @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/security-solution ) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1 | 0 | 1 | 0 | + +## Common + +### Consts, variables and types + + diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index e1084b2209f5d..b3883548ccbd2 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index e829ca52fb8f6..eab2f3de798ab 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 5db48e2f132a6..771e5888ce2f3 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 937164ab6caed..3273faa46daa3 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index 170a79043d17d..3f508f6e1d409 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 28c333bf658f1..d1f2a017ea674 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 6efe1857d2738..80b4b93236fe0 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index c467c44c5bf9f..5d006ce596e10 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index a037514c01402..a63817086c006 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index da0742e78a0c4..67b20a5253fd3 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index cda0e6cc766a9..7e61244c46070 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 503b49e6a26a2..6430b27b37519 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index 647be5d88ced7..e2a620a2bc2ce 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 14e968508ea23..607c20e00fa4d 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index 9c6d7a66453a9..d61b6b9f89c6a 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 45b1713daaee5..4bee26ebb68e9 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 4d7167c79a271..2b195ca4faa88 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index fadd69b743bf2..23581809543d9 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index 5f5003dce8576..f37d7cf2f763d 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index c736017970b4d..4669fbfc93b5c 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 2da9f06ac47fb..46c1e18698671 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index e008dbcaac161..fa9a85303f4ea 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index de564080f6682..347681ff89d0a 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 4fd42bb48e46b..bee3ced958783 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index c54b630b535a3..c9ba826f4d73b 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 2acd11544fc63..1e439c4f80c57 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 7ca17c09b82a6..b0d941f8bb347 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 5f30612551e76..70a8d9928615f 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index b9592179ecc9b..d57b253320c10 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index f6dde6c99f1e0..e0b5c0b4aceb6 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 573d72fbd8caa..aab4d7b6408b6 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index bed39fe2108b1..a94e03c0c5b0a 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index e63bb6bca3c2e..6df2a945d3358 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index bbfdab153d988..dcd6a6ed05910 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index c383c36cad2e0..80c3e65393166 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index adb787dc89fe5..b647aff96ee45 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 35acb7814d426..890cf8ffb6eaa 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 575dec52b2730..7f2b66bc34fdb 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index d206006f296c6..4d3ff6f01dc8d 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 6d9e3e4477ffe..7a29b62590976 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index ae276d53a3cda..17184a72d01b2 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.devdocs.json b/api_docs/kbn_slo_schema.devdocs.json index 02eec6e3a39cb..0666cd886ffa9 100644 --- a/api_docs/kbn_slo_schema.devdocs.json +++ b/api_docs/kbn_slo_schema.devdocs.json @@ -628,6 +628,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.DeleteSLOInstancesInput", + "type": "Type", + "tags": [], + "label": "DeleteSLOInstancesInput", + "description": [], + "signature": [ + "{ list: { sloId: string; instanceId: string; }[]; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.DeleteSLOInstancesParams", + "type": "Type", + "tags": [], + "label": "DeleteSLOInstancesParams", + "description": [], + "signature": [ + "{ list: { sloId: string; instanceId: string; }[]; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.FetchHistoricalSummaryParams", @@ -2079,6 +2109,32 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.deleteSLOInstancesParamsSchema", + "type": "Object", + "tags": [], + "label": "deleteSLOInstancesParamsSchema", + "description": [], + "signature": [ + "TypeC", + "<{ body: ", + "TypeC", + "<{ list: ", + "ArrayC", + "<", + "TypeC", + "<{ sloId: ", + "StringC", + "; instanceId: ", + "StringC", + "; }>>; }>; }>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.deleteSLOParamsSchema", diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index a2ce1f57cb673..f5c3185522ec2 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/team | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 132 | 0 | 129 | 0 | +| 135 | 0 | 132 | 0 | ## Common diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index ea565b6ba58b6..7da7ee72aef43 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index f34f0d248cb0f..dbb0e02750420 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index e7ab1af01cb48..4a85b93aac3ac 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 309966fc0a9db..0c27909a3af32 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_subscription_tracking.devdocs.json b/api_docs/kbn_subscription_tracking.devdocs.json new file mode 100644 index 0000000000000..ea9cdb7f07167 --- /dev/null +++ b/api_docs/kbn_subscription_tracking.devdocs.json @@ -0,0 +1,519 @@ +{ + "id": "@kbn/subscription-tracking", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.registerEvents", + "type": "Function", + "tags": [], + "label": "registerEvents", + "description": [ + "\nRegisters the subscription-specific event types" + ], + "signature": [ + "(analyticsClient: Pick<", + { + "pluginId": "@kbn/analytics-client", + "scope": "common", + "docId": "kibKbnAnalyticsClientPluginApi", + "section": "def-common.IAnalyticsClient", + "text": "IAnalyticsClient" + }, + ", \"registerEventType\">) => void" + ], + "path": "packages/kbn-subscription-tracking/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.registerEvents.$1", + "type": "Object", + "tags": [], + "label": "analyticsClient", + "description": [], + "signature": [ + "Pick<", + { + "pluginId": "@kbn/analytics-client", + "scope": "common", + "docId": "kibKbnAnalyticsClientPluginApi", + "section": "def-common.IAnalyticsClient", + "text": "IAnalyticsClient" + }, + ", \"registerEventType\">" + ], + "path": "packages/kbn-subscription-tracking/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionButton", + "type": "Function", + "tags": [], + "label": "SubscriptionButton", + "description": [ + "\nWrapper around `EuiButton` that provides subscription events" + ], + "signature": [ + "({\n subscriptionContext,\n children,\n ...restProps\n}: ", + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.SubscriptionButtonProps", + "text": "SubscriptionButtonProps" + }, + ") => JSX.Element" + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionButton.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n subscriptionContext,\n children,\n ...restProps\n}", + "description": [], + "signature": [ + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.SubscriptionButtonProps", + "text": "SubscriptionButtonProps" + } + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionButtonEmpty", + "type": "Function", + "tags": [], + "label": "SubscriptionButtonEmpty", + "description": [ + "\nWrapper around `EuiButtonEmpty` that provides subscription events" + ], + "signature": [ + "({\n subscriptionContext,\n children,\n ...restProps\n}: ", + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.SubscriptionButtonEmptyProps", + "text": "SubscriptionButtonEmptyProps" + }, + ") => JSX.Element" + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionButtonEmpty.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n subscriptionContext,\n children,\n ...restProps\n}", + "description": [], + "signature": [ + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.SubscriptionButtonEmptyProps", + "text": "SubscriptionButtonEmptyProps" + } + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionLink", + "type": "Function", + "tags": [], + "label": "SubscriptionLink", + "description": [ + "\nWrapper around `EuiLink` that provides subscription events" + ], + "signature": [ + "({\n subscriptionContext,\n children,\n ...restProps\n}: ", + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.SubscriptionLinkProps", + "text": "SubscriptionLinkProps" + }, + ") => JSX.Element" + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionLink.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n subscriptionContext,\n children,\n ...restProps\n}", + "description": [], + "signature": [ + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.SubscriptionLinkProps", + "text": "SubscriptionLinkProps" + } + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionTrackingProvider", + "type": "Function", + "tags": [], + "label": "SubscriptionTrackingProvider", + "description": [ + "\nExternal services provider" + ], + "signature": [ + "({ children, ...services }: React.PropsWithChildren<", + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.Services", + "text": "Services" + }, + ">) => JSX.Element" + ], + "path": "packages/kbn-subscription-tracking/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionTrackingProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "{ children, ...services }", + "description": [], + "signature": [ + "React.PropsWithChildren<", + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.Services", + "text": "Services" + }, + ">" + ], + "path": "packages/kbn-subscription-tracking/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.Services", + "type": "Interface", + "tags": [], + "label": "Services", + "description": [], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.Services.navigateToApp", + "type": "Function", + "tags": [], + "label": "navigateToApp", + "description": [], + "signature": [ + "(app: string, options: { path: string; }) => void" + ], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.Services.navigateToApp.$1", + "type": "string", + "tags": [], + "label": "app", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.Services.navigateToApp.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.Services.navigateToApp.$2.path", + "type": "string", + "tags": [], + "label": "path", + "description": [], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.Services.analyticsClient", + "type": "Object", + "tags": [], + "label": "analyticsClient", + "description": [], + "signature": [ + "{ reportEvent: (eventType: string, eventData: EventTypeData) => void; }" + ], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionContextData", + "type": "Interface", + "tags": [], + "label": "SubscriptionContextData", + "description": [ + "\nA piece of metadata which consists of an identifier of the advertised feature and\nthe `source` (e.g. location) of the subscription element." + ], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionContextData.source", + "type": "CompoundType", + "tags": [], + "label": "source", + "description": [ + "\nA human-readable identifier describing the location of the beginning of the\nsubscription flow.\nLocation identifiers are prefixed with a solution identifier, e.g. `security__`\n" + ], + "signature": [ + "`observability__${string}` | `security__${string}`" + ], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionContextData.feature", + "type": "string", + "tags": [], + "label": "feature", + "description": [ + "\nA human-readable identifier describing the feature that is being promoted.\n" + ], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.EVENT_NAMES", + "type": "Enum", + "tags": [], + "label": "EVENT_NAMES", + "description": [], + "path": "packages/kbn-subscription-tracking/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionButtonEmptyProps", + "type": "Type", + "tags": [], + "label": "SubscriptionButtonEmptyProps", + "description": [], + "signature": [ + "((", + "DisambiguateSet", + " & ", + "CommonEuiButtonEmptyProps", + " & { onClick?: React.MouseEventHandler | undefined; } & React.ButtonHTMLAttributes) | (", + "DisambiguateSet", + "<", + "EuiButtonEmptyPropsForButton", + ", EuiButtonEmptyPropsForAnchor> & ", + "CommonEuiButtonEmptyProps", + " & { href?: string | undefined; onClick?: React.MouseEventHandler | undefined; } & React.AnchorHTMLAttributes)) & CommonProps" + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionButtonProps", + "type": "Type", + "tags": [], + "label": "SubscriptionButtonProps", + "description": [], + "signature": [ + "EuiButtonProps", + " & CommonProps" + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionLinkProps", + "type": "Type", + "tags": [], + "label": "SubscriptionLinkProps", + "description": [], + "signature": [ + "((", + "DisambiguateSet", + "<", + "EuiLinkButtonProps", + ", ", + "EuiLinkAnchorProps", + "> & ", + "EuiLinkAnchorProps", + ") | (", + "DisambiguateSet", + "<", + "EuiLinkAnchorProps", + ", ", + "EuiLinkButtonProps", + "> & ", + "EuiLinkButtonProps", + ")) & CommonProps" + ], + "path": "packages/kbn-subscription-tracking/src/subscription_elements.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/subscription-tracking", + "id": "def-common.SubscriptionTrackingContext", + "type": "Object", + "tags": [], + "label": "SubscriptionTrackingContext", + "description": [], + "signature": [ + "React.Context<", + { + "pluginId": "@kbn/subscription-tracking", + "scope": "common", + "docId": "kibKbnSubscriptionTrackingPluginApi", + "section": "def-common.Services", + "text": "Services" + }, + " | null>" + ], + "path": "packages/kbn-subscription-tracking/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/kbn_subscription_tracking.mdx b/api_docs/kbn_subscription_tracking.mdx new file mode 100644 index 0000000000000..8efe2a60221d5 --- /dev/null +++ b/api_docs/kbn_subscription_tracking.mdx @@ -0,0 +1,42 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnSubscriptionTrackingPluginApi +slug: /kibana-dev-docs/api/kbn-subscription-tracking +title: "@kbn/subscription-tracking" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/subscription-tracking plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/subscription-tracking'] +--- +import kbnSubscriptionTrackingObj from './kbn_subscription_tracking.devdocs.json'; + + + +Contact [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 24 | 0 | 16 | 0 | + +## Common + +### Objects + + +### Functions + + +### Interfaces + + +### Enums + + +### Consts, variables and types + + diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 4312a7942360c..ce557e38cc184 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.devdocs.json b/api_docs/kbn_test.devdocs.json index 86f6572ae1162..222a0d35f68c6 100644 --- a/api_docs/kbn_test.devdocs.json +++ b/api_docs/kbn_test.devdocs.json @@ -3136,7 +3136,14 @@ "\nLicense to run your cluster under. Keep in mind that a `trial` license\nhas an expiration date. If you are using a `dataArchive` with your tests,\nyou'll likely need to use `basic` or `gold` to prevent the test from failing\nwhen the license expires." ], "signature": [ - "\"basic\" | \"gold\" | \"trial\" | undefined" + { + "pluginId": "@kbn/es", + "scope": "common", + "docId": "kibKbnEsPluginApi", + "section": "def-common.ArtifactLicense", + "text": "ArtifactLicense" + }, + " | undefined" ], "path": "packages/kbn-test/src/es/test_es_cluster.ts", "deprecated": false, diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 93b224851b4cf..bd64d32ef49ae 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index da0ee7250ea6c..15b79c3c0e184 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 984266764db13..1b68ebd21346c 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_text_based_editor.devdocs.json b/api_docs/kbn_text_based_editor.devdocs.json index 1eb1abc6f8275..252a6537ae639 100644 --- a/api_docs/kbn_text_based_editor.devdocs.json +++ b/api_docs/kbn_text_based_editor.devdocs.json @@ -3,6 +3,136 @@ "client": { "classes": [], "functions": [ + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.fetchFieldsFromESQL", + "type": "Function", + "tags": [], + "label": "fetchFieldsFromESQL", + "description": [], + "signature": [ + "(query: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + ", expressions: ", + { + "pluginId": "expressions", + "scope": "public", + "docId": "kibExpressionsPluginApi", + "section": "def-public.ExpressionsStart", + "text": "ExpressionsStart" + }, + ", time: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined) => Promise<", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + " | undefined>" + ], + "path": "packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.fetchFieldsFromESQL.$1", + "type": "CompoundType", + "tags": [], + "label": "query", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + } + ], + "path": "packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.fetchFieldsFromESQL.$2", + "type": "Object", + "tags": [], + "label": "expressions", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "public", + "docId": "kibExpressionsPluginApi", + "section": "def-public.ExpressionsStart", + "text": "ExpressionsStart" + } + ], + "path": "packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.fetchFieldsFromESQL.$3", + "type": "Object", + "tags": [], + "label": "time", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined" + ], + "path": "packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/text-based-editor", "id": "def-public.TextBasedLanguagesEditor", @@ -203,6 +333,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.TextBasedLanguagesEditorProps.warning", + "type": "string", + "tags": [], + "label": "warning", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/text-based-editor", "id": "def-public.TextBasedLanguagesEditorProps.isDisabled", @@ -244,6 +388,34 @@ "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.TextBasedLanguagesEditorProps.hideMinimizeButton", + "type": "CompoundType", + "tags": [], + "label": "hideMinimizeButton", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.TextBasedLanguagesEditorProps.hideRunQueryText", + "type": "CompoundType", + "tags": [], + "label": "hideRunQueryText", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index 3b5916bc801f9..e298cc07d56ce 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 15 | 0 | 14 | 0 | +| 22 | 0 | 21 | 0 | ## Client diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 1e09e6abda1dd..102b18fd05e4a 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 7693bbfa5144d..5d8254e5595e9 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 399abae571184..b1e232927c1ac 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index ad123dd8f536c..fd4267c4545e0 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.devdocs.json b/api_docs/kbn_ui_shared_deps_src.devdocs.json index 2e7894453849b..6dc5639e2e27a 100644 --- a/api_docs/kbn_ui_shared_deps_src.devdocs.json +++ b/api_docs/kbn_ui_shared_deps_src.devdocs.json @@ -287,6 +287,61 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/ui-shared-deps-src", + "id": "def-common.externals.reduxjstoolkit", + "type": "string", + "tags": [], + "label": "'@reduxjs/toolkit'", + "description": [], + "path": "packages/kbn-ui-shared-deps-src/src/definitions.js", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ui-shared-deps-src", + "id": "def-common.externals.reactredux", + "type": "string", + "tags": [], + "label": "'react-redux'", + "description": [], + "path": "packages/kbn-ui-shared-deps-src/src/definitions.js", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ui-shared-deps-src", + "id": "def-common.externals.redux", + "type": "string", + "tags": [], + "label": "redux", + "description": [], + "path": "packages/kbn-ui-shared-deps-src/src/definitions.js", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ui-shared-deps-src", + "id": "def-common.externals.immer", + "type": "string", + "tags": [], + "label": "immer", + "description": [], + "path": "packages/kbn-ui-shared-deps-src/src/definitions.js", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ui-shared-deps-src", + "id": "def-common.externals.reselect", + "type": "string", + "tags": [], + "label": "reselect", + "description": [], + "path": "packages/kbn-ui-shared-deps-src/src/definitions.js", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/ui-shared-deps-src", "id": "def-common.externals.rxjs", diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 81bb9ce483ff1..8fb996ed962c6 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 47 | 0 | 38 | 0 | +| 52 | 0 | 43 | 0 | ## Common diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 6ace28c7d04f4..37472fbdc5a84 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.devdocs.json b/api_docs/kbn_unified_data_table.devdocs.json new file mode 100644 index 0000000000000..66f136070b569 --- /dev/null +++ b/api_docs/kbn_unified_data_table.devdocs.json @@ -0,0 +1,1634 @@ +{ + "id": "@kbn/unified-data-table", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.getDisplayedColumns", + "type": "Function", + "tags": [], + "label": "getDisplayedColumns", + "description": [ + "\nFunction to provide fallback when\n1) no columns are given\n2) Just one column is given, which is the configured timefields" + ], + "signature": [ + "(stateColumns: string[], dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ") => string[]" + ], + "path": "packages/kbn-unified-data-table/src/utils/columns.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.getDisplayedColumns.$1", + "type": "Array", + "tags": [], + "label": "stateColumns", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-unified-data-table/src/utils/columns.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.getDisplayedColumns.$2", + "type": "Object", + "tags": [], + "label": "dataView", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + } + ], + "path": "packages/kbn-unified-data-table/src/utils/columns.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.getRowsPerPageOptions", + "type": "Function", + "tags": [], + "label": "getRowsPerPageOptions", + "description": [], + "signature": [ + "(currentRowsPerPage?: number | undefined) => number[]" + ], + "path": "packages/kbn-unified-data-table/src/utils/rows_per_page.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.getRowsPerPageOptions.$1", + "type": "number", + "tags": [], + "label": "currentRowsPerPage", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/utils/rows_per_page.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.JSONCodeEditorCommonMemoized", + "type": "Function", + "tags": [], + "label": "JSONCodeEditorCommonMemoized", + "description": [], + "signature": [ + "React.NamedExoticComponent & { readonly type: (props: JsonCodeEditorCommonProps) => JSX.Element; }" + ], + "path": "packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.JSONCodeEditorCommonMemoized.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.popularizeField", + "type": "Function", + "tags": [], + "label": "popularizeField", + "description": [], + "signature": [ + "(dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ", fieldName: string, DataViewsService: ", + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" + }, + ", capabilities: ", + { + "pluginId": "@kbn/core-capabilities-common", + "scope": "common", + "docId": "kibKbnCoreCapabilitiesCommonPluginApi", + "section": "def-common.Capabilities", + "text": "Capabilities" + }, + ") => Promise" + ], + "path": "packages/kbn-unified-data-table/src/utils/popularize_field.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.popularizeField.$1", + "type": "Object", + "tags": [], + "label": "dataView", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + } + ], + "path": "packages/kbn-unified-data-table/src/utils/popularize_field.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.popularizeField.$2", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-unified-data-table/src/utils/popularize_field.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.popularizeField.$3", + "type": "Object", + "tags": [], + "label": "DataViewsService", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" + } + ], + "path": "packages/kbn-unified-data-table/src/utils/popularize_field.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.popularizeField.$4", + "type": "Object", + "tags": [], + "label": "capabilities", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-capabilities-common", + "scope": "common", + "docId": "kibKbnCoreCapabilitiesCommonPluginApi", + "section": "def-common.Capabilities", + "text": "Capabilities" + } + ], + "path": "packages/kbn-unified-data-table/src/utils/popularize_field.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTable", + "type": "Function", + "tags": [], + "label": "UnifiedDataTable", + "description": [], + "signature": [ + "({ ariaLabelledBy, columns, controlColumnIds, dataView, loadingState, onFilter, onResize, onSetColumns, onSort, rows, sampleSize, searchDescription, searchTitle, settings, showTimeCol, showFullScreenButton, sort, useNewFieldsApi, isSortEnabled, isPaginationEnabled, cellActionsTriggerId, className, rowHeightState, onUpdateRowHeight, isPlainRecord, rowsPerPageState, onUpdateRowsPerPage, onFieldEdited, services, renderCustomGridBody, trailingControlColumns, totalHits, onFetchMoreRecords, renderDocumentView, setExpandedDoc, expandedDoc, configRowHeight, showMultiFields, maxDocFieldsDisplayed, externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, externalCustomRenderers, consumer, componentsTourSteps, }: ", + { + "pluginId": "@kbn/unified-data-table", + "scope": "common", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-common.UnifiedDataTableProps", + "text": "UnifiedDataTableProps" + }, + ") => JSX.Element" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTable.$1", + "type": "Object", + "tags": [], + "label": "{\n ariaLabelledBy,\n columns,\n controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,\n dataView,\n loadingState,\n onFilter,\n onResize,\n onSetColumns,\n onSort,\n rows,\n sampleSize,\n searchDescription,\n searchTitle,\n settings,\n showTimeCol,\n showFullScreenButton = true,\n sort,\n useNewFieldsApi,\n isSortEnabled = true,\n isPaginationEnabled = true,\n cellActionsTriggerId,\n className,\n rowHeightState,\n onUpdateRowHeight,\n isPlainRecord = false,\n rowsPerPageState,\n onUpdateRowsPerPage,\n onFieldEdited,\n services,\n renderCustomGridBody,\n trailingControlColumns,\n totalHits,\n onFetchMoreRecords,\n renderDocumentView,\n setExpandedDoc,\n expandedDoc,\n configRowHeight,\n showMultiFields = true,\n maxDocFieldsDisplayed = 50,\n externalControlColumns,\n externalAdditionalControls,\n rowsPerPageOptions,\n visibleCellActions,\n externalCustomRenderers,\n consumer = 'discover',\n componentsTourSteps,\n}", + "description": [], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "common", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-common.UnifiedDataTableProps", + "text": "UnifiedDataTableProps" + } + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.useColumns", + "type": "Function", + "tags": [], + "label": "useColumns", + "description": [], + "signature": [ + "({ capabilities, dataView, dataViews, setAppState, useNewFieldsApi, columns, sort, defaultOrder, }: UseColumnsProps) => { columns: string[]; onAddColumn: (columnName: string) => void; onRemoveColumn: (columnName: string) => void; onMoveColumn: (columnName: string, newIndex: number) => void; onSetColumns: (nextColumns: string[], hideTimeColumn: boolean) => void; }" + ], + "path": "packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.useColumns.$1", + "type": "Object", + "tags": [], + "label": "{\n capabilities,\n dataView,\n dataViews,\n setAppState,\n useNewFieldsApi,\n columns,\n sort,\n defaultOrder = 'desc',\n}", + "description": [], + "signature": [ + "UseColumnsProps" + ], + "path": "packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps", + "type": "Interface", + "tags": [], + "label": "UnifiedDataTableProps", + "description": [], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.ariaLabelledBy", + "type": "string", + "tags": [], + "label": "ariaLabelledBy", + "description": [ + "\nDetermines which element labels the grid for ARIA" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.className", + "type": "string", + "tags": [], + "label": "className", + "description": [ + "\nOptional class name to apply" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.columns", + "type": "Array", + "tags": [], + "label": "columns", + "description": [ + "\nDetermines ids of the columns which are displayed" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.expandedDoc", + "type": "Object", + "tags": [], + "label": "expandedDoc", + "description": [ + "\nIf set, the given document is displayed in a flyout" + ], + "signature": [ + "DataTableRecord", + " | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.dataView", + "type": "Object", + "tags": [], + "label": "dataView", + "description": [ + "\nThe used data view" + ], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + } + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.loadingState", + "type": "Enum", + "tags": [], + "label": "loadingState", + "description": [ + "\nDetermines if data is currently loaded" + ], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "common", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-common.DataLoadingState", + "text": "DataLoadingState" + } + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onFilter", + "type": "Function", + "tags": [], + "label": "onFilter", + "description": [ + "\nFunction to add a filter in the grid cell or document flyout" + ], + "signature": [ + "(mapping: string | ", + "FieldMapping", + " | undefined, value: unknown, mode: \"+\" | \"-\") => void" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onFilter.$1", + "type": "CompoundType", + "tags": [], + "label": "mapping", + "description": [], + "signature": [ + "string | ", + "FieldMapping", + " | undefined" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onFilter.$2", + "type": "Unknown", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "unknown" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onFilter.$3", + "type": "CompoundType", + "tags": [], + "label": "mode", + "description": [], + "signature": [ + "\"+\" | \"-\"" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onResize", + "type": "Function", + "tags": [], + "label": "onResize", + "description": [ + "\nFunction triggered when a column is resized by the user" + ], + "signature": [ + "((colSettings: { columnId: string; width: number; }) => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onResize.$1", + "type": "Object", + "tags": [], + "label": "colSettings", + "description": [], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onResize.$1.columnId", + "type": "string", + "tags": [], + "label": "columnId", + "description": [], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onResize.$1.width", + "type": "number", + "tags": [], + "label": "width", + "description": [], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onSetColumns", + "type": "Function", + "tags": [], + "label": "onSetColumns", + "description": [ + "\nFunction to set all columns" + ], + "signature": [ + "(columns: string[], hideTimeColumn: boolean) => void" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onSetColumns.$1", + "type": "Array", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onSetColumns.$2", + "type": "boolean", + "tags": [], + "label": "hideTimeColumn", + "description": [], + "signature": [ + "boolean" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onSort", + "type": "Function", + "tags": [], + "label": "onSort", + "description": [ + "\nfunction to change sorting of the documents, skipped when isSortEnabled is set to false" + ], + "signature": [ + "((sort: string[][]) => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onSort.$1", + "type": "Array", + "tags": [], + "label": "sort", + "description": [], + "signature": [ + "string[][]" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.rows", + "type": "Array", + "tags": [], + "label": "rows", + "description": [ + "\nArray of documents provided by Elasticsearch" + ], + "signature": [ + "DataTableRecord", + "[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.sampleSize", + "type": "number", + "tags": [], + "label": "sampleSize", + "description": [ + "\nThe max size of the documents returned by Elasticsearch" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.setExpandedDoc", + "type": "Function", + "tags": [], + "label": "setExpandedDoc", + "description": [ + "\nFunction to set the expanded document, which is displayed in a flyout" + ], + "signature": [ + "((doc?: ", + "DataTableRecord", + " | undefined) => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.setExpandedDoc.$1", + "type": "Object", + "tags": [], + "label": "doc", + "description": [], + "signature": [ + "DataTableRecord", + " | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.settings", + "type": "Object", + "tags": [], + "label": "settings", + "description": [ + "\nGrid display settings persisted in Elasticsearch (e.g. column width)" + ], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "common", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-common.UnifiedDataTableSettings", + "text": "UnifiedDataTableSettings" + }, + " | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.searchDescription", + "type": "string", + "tags": [], + "label": "searchDescription", + "description": [ + "\nSearch description" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.searchTitle", + "type": "string", + "tags": [], + "label": "searchTitle", + "description": [ + "\nSearch title" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.showTimeCol", + "type": "boolean", + "tags": [], + "label": "showTimeCol", + "description": [ + "\nDetermines whether the time columns should be displayed (legacy settings)" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.showFullScreenButton", + "type": "CompoundType", + "tags": [], + "label": "showFullScreenButton", + "description": [ + "\nDetermines whether the full screen button should be displayed" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.isSortEnabled", + "type": "CompoundType", + "tags": [], + "label": "isSortEnabled", + "description": [ + "\nManage user sorting control" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.sort", + "type": "Array", + "tags": [], + "label": "sort", + "description": [ + "\nCurrent sort setting" + ], + "signature": [ + "SortOrder", + "[]" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.useNewFieldsApi", + "type": "boolean", + "tags": [], + "label": "useNewFieldsApi", + "description": [ + "\nHow the data is fetched" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.isPaginationEnabled", + "type": "CompoundType", + "tags": [], + "label": "isPaginationEnabled", + "description": [ + "\nManage pagination control" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.controlColumnIds", + "type": "Array", + "tags": [], + "label": "controlColumnIds", + "description": [ + "\nList of used control columns (available: 'openDetails', 'select')" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.rowHeightState", + "type": "number", + "tags": [], + "label": "rowHeightState", + "description": [ + "\nRow height from state" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onUpdateRowHeight", + "type": "Function", + "tags": [], + "label": "onUpdateRowHeight", + "description": [ + "\nUpdate row height state" + ], + "signature": [ + "((rowHeight: number) => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onUpdateRowHeight.$1", + "type": "number", + "tags": [], + "label": "rowHeight", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.isPlainRecord", + "type": "CompoundType", + "tags": [], + "label": "isPlainRecord", + "description": [ + "\nIs text base lang mode enabled" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.rowsPerPageState", + "type": "number", + "tags": [], + "label": "rowsPerPageState", + "description": [ + "\nCurrent state value for rowsPerPage" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onUpdateRowsPerPage", + "type": "Function", + "tags": [], + "label": "onUpdateRowsPerPage", + "description": [ + "\nUpdate rows per page state" + ], + "signature": [ + "((rowsPerPage: number) => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onUpdateRowsPerPage.$1", + "type": "number", + "tags": [], + "label": "rowsPerPage", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onFieldEdited", + "type": "Function", + "tags": [], + "label": "onFieldEdited", + "description": [ + "\nCallback to execute on edit runtime field" + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.cellActionsTriggerId", + "type": "string", + "tags": [], + "label": "cellActionsTriggerId", + "description": [ + "\nOptional triggerId to retrieve the column cell actions that will override the default ones" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.services", + "type": "Object", + "tags": [], + "label": "services", + "description": [ + "\nService dependencies" + ], + "signature": [ + "{ theme: ", + { + "pluginId": "@kbn/react-kibana-context-common", + "scope": "common", + "docId": "kibKbnReactKibanaContextCommonPluginApi", + "section": "def-common.ThemeServiceStart", + "text": "ThemeServiceStart" + }, + "; fieldFormats: ", + { + "pluginId": "fieldFormats", + "scope": "public", + "docId": "kibFieldFormatsPluginApi", + "section": "def-public.FieldFormatsStart", + "text": "FieldFormatsStart" + }, + "; uiSettings: ", + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" + }, + "; dataViewFieldEditor: ", + { + "pluginId": "dataViewFieldEditor", + "scope": "public", + "docId": "kibDataViewFieldEditorPluginApi", + "section": "def-public.PluginStart", + "text": "PluginStart" + }, + "; toastNotifications: ", + { + "pluginId": "@kbn/core-notifications-browser", + "scope": "common", + "docId": "kibKbnCoreNotificationsBrowserPluginApi", + "section": "def-common.IToasts", + "text": "IToasts" + }, + "; storage: ", + { + "pluginId": "kibanaUtils", + "scope": "public", + "docId": "kibKibanaUtilsPluginApi", + "section": "def-public.Storage", + "text": "Storage" + }, + "; data: ", + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataPluginApi", + "section": "def-public.DataPublicPluginStart", + "text": "DataPublicPluginStart" + }, + "; }" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.renderDocumentView", + "type": "Function", + "tags": [], + "label": "renderDocumentView", + "description": [ + "\nCallback to render DocumentView when the document is expanded" + ], + "signature": [ + "((hit: ", + "DataTableRecord", + ", displayedRows: ", + "DataTableRecord", + "[], displayedColumns: string[]) => JSX.Element | undefined) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.renderDocumentView.$1", + "type": "Object", + "tags": [], + "label": "hit", + "description": [], + "signature": [ + "DataTableRecord" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.renderDocumentView.$2", + "type": "Array", + "tags": [], + "label": "displayedRows", + "description": [], + "signature": [ + "DataTableRecord", + "[]" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.renderDocumentView.$3", + "type": "Array", + "tags": [], + "label": "displayedColumns", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.configRowHeight", + "type": "number", + "tags": [], + "label": "configRowHeight", + "description": [ + "\nOptional value for providing configuration setting for UnifiedDataTable rows height" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.showMultiFields", + "type": "CompoundType", + "tags": [], + "label": "showMultiFields", + "description": [ + "\nOptional value for providing configuration setting for enabling to display the complex fields in the table. Default is true." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.maxDocFieldsDisplayed", + "type": "number", + "tags": [], + "label": "maxDocFieldsDisplayed", + "description": [ + "\nOptional value for providing configuration setting for maximum number of document fields to display in the table. Default is 50." + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.externalControlColumns", + "type": "Array", + "tags": [], + "label": "externalControlColumns", + "description": [ + "\nOptional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select." + ], + "signature": [ + "EuiDataGridControlColumn", + "[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.totalHits", + "type": "number", + "tags": [], + "label": "totalHits", + "description": [ + "\nNumber total hits from ES" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onFetchMoreRecords", + "type": "Function", + "tags": [], + "label": "onFetchMoreRecords", + "description": [ + "\nTo fetch more" + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.externalAdditionalControls", + "type": "CompoundType", + "tags": [], + "label": "externalAdditionalControls", + "description": [ + "\nOptional value for providing the additional controls available in the UnifiedDataTable toolbar to manage it's records or state. UnifiedDataTable includes Columns, Sorting and Bulk Actions." + ], + "signature": [ + "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.rowsPerPageOptions", + "type": "Array", + "tags": [], + "label": "rowsPerPageOptions", + "description": [ + "\nOptional list of number type values to set custom UnifiedDataTable paging options to display the records per page." + ], + "signature": [ + "number[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.renderCustomGridBody", + "type": "Function", + "tags": [], + "label": "renderCustomGridBody", + "description": [ + "\nAn optional function called to completely customize and control the rendering of\nEuiDataGrid's body and cell placement. This can be used to, e.g. remove EuiDataGrid's\nvirtualization library, or roll your own.\n\nThis component is **only** meant as an escape hatch for extremely custom use cases.\n\nBehind the scenes, this function is treated as a React component,\nallowing hooks, context, and other React concepts to be used.\nIt receives #EuiDataGridCustomBodyProps as its only argument." + ], + "signature": [ + "((args: ", + "EuiDataGridCustomBodyProps", + ") => React.ReactNode) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.renderCustomGridBody.$1", + "type": "Object", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "EuiDataGridCustomBodyProps" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.trailingControlColumns", + "type": "Array", + "tags": [], + "label": "trailingControlColumns", + "description": [ + "\nAn optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid." + ], + "signature": [ + "EuiDataGridControlColumn", + "[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.visibleCellActions", + "type": "number", + "tags": [], + "label": "visibleCellActions", + "description": [ + "\nAn optional value for a custom number of the visible cell actions in the table. By default is up to 3." + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.externalCustomRenderers", + "type": "Object", + "tags": [], + "label": "externalCustomRenderers", + "description": [ + "\nAn optional settings for a specified fields rendering like links. Applied only for the listed fields rendering." + ], + "signature": [ + "Record React.ReactNode> | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.consumer", + "type": "string", + "tags": [], + "label": "consumer", + "description": [ + "\nName of the UnifiedDataTable consumer component or application" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.componentsTourSteps", + "type": "Object", + "tags": [], + "label": "componentsTourSteps", + "description": [ + "\nOptional key/value pairs to set guided onboarding steps ids for a data table components included to guided tour." + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableSettings", + "type": "Interface", + "tags": [], + "label": "UnifiedDataTableSettings", + "description": [ + "\nUser configurable state of data grid, persisted in saved search" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableSettings.columns", + "type": "Object", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableSettingsColumn", + "type": "Interface", + "tags": [], + "label": "UnifiedDataTableSettingsColumn", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableSettingsColumn.width", + "type": "number", + "tags": [], + "label": "width", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.DataLoadingState", + "type": "Enum", + "tags": [], + "label": "DataLoadingState", + "description": [], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ValueToStringConverter", + "type": "Type", + "tags": [], + "label": "ValueToStringConverter", + "description": [], + "signature": [ + "(rowIndex: number, columnId: string, options?: { compatibleWithCSV?: boolean | undefined; } | undefined) => { formattedString: string; withFormula: boolean; }" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ValueToStringConverter.$1", + "type": "number", + "tags": [], + "label": "rowIndex", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ValueToStringConverter.$2", + "type": "string", + "tags": [], + "label": "columnId", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ValueToStringConverter.$3", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ compatibleWithCSV?: boolean | undefined; } | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ROWS_HEIGHT_OPTIONS", + "type": "Object", + "tags": [], + "label": "ROWS_HEIGHT_OPTIONS", + "description": [ + "\nRow height might be a value from -1 to 20\nA value of -1 automatically adjusts the row height to fit the contents.\nA value of 0 displays the content in a single line.\nA value from 1 to 20 represents number of lines of Document explorer row to display." + ], + "path": "packages/kbn-unified-data-table/src/constants.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ROWS_HEIGHT_OPTIONS.auto", + "type": "number", + "tags": [], + "label": "auto", + "description": [], + "path": "packages/kbn-unified-data-table/src/constants.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ROWS_HEIGHT_OPTIONS.single", + "type": "number", + "tags": [], + "label": "single", + "description": [], + "path": "packages/kbn-unified-data-table/src/constants.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.ROWS_HEIGHT_OPTIONS.default", + "type": "number", + "tags": [], + "label": "default", + "description": [], + "path": "packages/kbn-unified-data-table/src/constants.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx new file mode 100644 index 0000000000000..52c816d9859d7 --- /dev/null +++ b/api_docs/kbn_unified_data_table.mdx @@ -0,0 +1,42 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnUnifiedDataTablePluginApi +slug: /kibana-dev-docs/api/kbn-unified-data-table +title: "@kbn/unified-data-table" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/unified-data-table plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] +--- +import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; + +Contains functionality for the unified data table which can be integrated into apps + +Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 92 | 0 | 42 | 1 | + +## Common + +### Objects + + +### Functions + + +### Interfaces + + +### Enums + + +### Consts, variables and types + + diff --git a/api_docs/kbn_unified_doc_viewer.devdocs.json b/api_docs/kbn_unified_doc_viewer.devdocs.json new file mode 100644 index 0000000000000..da1c2b438bd70 --- /dev/null +++ b/api_docs/kbn_unified_doc_viewer.devdocs.json @@ -0,0 +1,204 @@ +{ + "id": "@kbn/unified-doc-viewer", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.DocViewsRegistry", + "type": "Class", + "tags": [], + "label": "DocViewsRegistry", + "description": [], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.DocViewsRegistry.addDocView", + "type": "Function", + "tags": [], + "label": "addDocView", + "description": [ + "\nExtends and adds the given doc view to the registry array" + ], + "signature": [ + "(docViewRaw: ", + "DocViewInput", + " | ", + "DocViewInputFn", + ") => void" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.DocViewsRegistry.addDocView.$1", + "type": "CompoundType", + "tags": [], + "label": "docViewRaw", + "description": [], + "signature": [ + "DocViewInput", + " | ", + "DocViewInputFn" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.DocViewsRegistry.getDocViewsSorted", + "type": "Function", + "tags": [], + "label": "getDocViewsSorted", + "description": [ + "\nReturns a sorted array of doc_views for rendering tabs" + ], + "signature": [ + "(hit: ", + "DataTableRecord", + ") => ", + "DocView", + "[]" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.DocViewsRegistry.getDocViewsSorted.$1", + "type": "Object", + "tags": [], + "label": "hit", + "description": [], + "signature": [ + "DataTableRecord" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.DocViewer", + "type": "Function", + "tags": [], + "label": "DocViewer", + "description": [ + "\nRendering tabs with different views of 1 Elasticsearch hit in Discover.\nThe tabs are provided by the `docs_views` registry.\nA view can contain a React `component`, or any JS framework by using\na `render` function." + ], + "signature": [ + "({ docViews, ...renderProps }: ", + "DocViewerProps", + ") => JSX.Element | null" + ], + "path": "packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.DocViewer.$1", + "type": "Object", + "tags": [], + "label": "{ docViews, ...renderProps }", + "description": [], + "signature": [ + "DocViewerProps" + ], + "path": "packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.FieldName", + "type": "Function", + "tags": [], + "label": "FieldName", + "description": [], + "signature": [ + "({\n fieldName,\n fieldMapping,\n fieldType,\n fieldIconProps,\n scripted = false,\n highlight = '',\n}: Props) => JSX.Element" + ], + "path": "packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.FieldName.$1", + "type": "Object", + "tags": [], + "label": "{\n fieldName,\n fieldMapping,\n fieldType,\n fieldIconProps,\n scripted = false,\n highlight = '',\n}", + "description": [], + "signature": [ + "Props" + ], + "path": "packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [ + { + "parentPluginId": "@kbn/unified-doc-viewer", + "id": "def-common.ElasticRequestState", + "type": "Enum", + "tags": [], + "label": "ElasticRequestState", + "description": [], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx new file mode 100644 index 0000000000000..8be1d0025d7e3 --- /dev/null +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnUnifiedDocViewerPluginApi +slug: /kibana-dev-docs/api/kbn-unified-doc-viewer +title: "@kbn/unified-doc-viewer" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/unified-doc-viewer plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] +--- +import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; + + + +Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 10 | 0 | 7 | 6 | + +## Common + +### Functions + + +### Classes + + +### Enums + + diff --git a/api_docs/kbn_unified_field_list.devdocs.json b/api_docs/kbn_unified_field_list.devdocs.json index 88f3b347e3f0b..d8aeafc25ecbe 100644 --- a/api_docs/kbn_unified_field_list.devdocs.json +++ b/api_docs/kbn_unified_field_list.devdocs.json @@ -796,41 +796,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/unified-field-list", - "id": "def-common.getFieldTypeName", - "type": "Function", - "tags": [], - "label": "getFieldTypeName", - "description": [ - "\nReturns a user-friendly name of a field type" - ], - "signature": [ - "(type: string | undefined) => string" - ], - "path": "packages/kbn-unified-field-list/src/utils/field_types/get_field_type_name.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/unified-field-list", - "id": "def-common.getFieldTypeName.$1", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-unified-field-list/src/utils/field_types/get_field_type_name.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/unified-field-list", "id": "def-common.getSearchMode", @@ -1555,7 +1520,7 @@ }, " | undefined; }; getCreationOptions: () => ", "UnifiedFieldListSidebarContainerCreationOptions", - "; isSidebarCollapsed?: boolean | undefined; prependInFlyout?: (() => React.ReactNode) | undefined; variant?: \"responsive\" | \"button-and-flyout-always\" | \"list-always\" | undefined; onSelectedFieldFilter?: ((field: ", + "; prependInFlyout?: (() => React.ReactNode) | undefined; variant?: \"responsive\" | \"button-and-flyout-always\" | \"list-always\" | undefined; onSelectedFieldFilter?: ((field: ", { "pluginId": "dataViews", "scope": "common", @@ -3020,6 +2985,20 @@ } ] }, + { + "parentPluginId": "@kbn/unified-field-list", + "id": "def-common.FieldListFiltersProps.compressed", + "type": "CompoundType", + "tags": [], + "label": "compressed", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-field-list/src/components/field_list_filters/field_list_filters.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/unified-field-list", "id": "def-common.FieldListFiltersProps.nameFilter", @@ -5518,20 +5497,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/unified-field-list", - "id": "def-common.KNOWN_FIELD_TYPES", - "type": "Enum", - "tags": [], - "label": "KNOWN_FIELD_TYPES", - "description": [ - "\nField types for which name and description are defined" - ], - "path": "packages/kbn-unified-field-list/src/utils/field_types/field_types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false } ], "misc": [ @@ -5691,21 +5656,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/unified-field-list", - "id": "def-common.FieldTypeKnown", - "type": "Type", - "tags": [], - "label": "FieldTypeKnown", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-unified-field-list/src/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/unified-field-list", "id": "def-common.GetCustomFieldType", @@ -5797,7 +5747,7 @@ }, " | undefined; }; getCreationOptions: () => ", "UnifiedFieldListSidebarContainerCreationOptions", - "; isSidebarCollapsed?: boolean | undefined; prependInFlyout?: (() => React.ReactNode) | undefined; variant?: \"responsive\" | \"button-and-flyout-always\" | \"list-always\" | undefined; onSelectedFieldFilter?: ((field: ", + "; prependInFlyout?: (() => React.ReactNode) | undefined; variant?: \"responsive\" | \"button-and-flyout-always\" | \"list-always\" | undefined; onSelectedFieldFilter?: ((field: ", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index 967ffdd332e73..903fee5faa4ed 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 306 | 0 | 277 | 9 | +| 303 | 0 | 276 | 9 | ## Common diff --git a/api_docs/kbn_url_state.mdx b/api_docs/kbn_url_state.mdx index 20289bb13bc82..a975b538a9dbb 100644 --- a/api_docs/kbn_url_state.mdx +++ b/api_docs/kbn_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-url-state title: "@kbn/url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/url-state plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/url-state'] --- import kbnUrlStateObj from './kbn_url_state.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 8d7ec6eac980f..b35dd16cc81a2 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 522ad78bd0435..3e9599dfccfe2 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.devdocs.json b/api_docs/kbn_utility_types.devdocs.json index 821e2d7a40523..6d177a2aa2cf2 100644 --- a/api_docs/kbn_utility_types.devdocs.json +++ b/api_docs/kbn_utility_types.devdocs.json @@ -627,6 +627,61 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/utility-types", + "id": "def-common.RecursivePartial", + "type": "Type", + "tags": [], + "label": "RecursivePartial", + "description": [], + "signature": [ + "{ [P in keyof T]?: (T[P] extends NonAny[] ? T[P] : T[P] extends readonly NonAny[] ? T[P] : T[P] extends (infer U)[] ? ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "[] : T[P] extends readonly (infer U)[] ? readonly ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "[] : T[P] extends Set ? Set<", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + "> : T[P] extends Map ? Map> : T[P] extends NonAny ? T[P] : ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.RecursivePartial", + "text": "RecursivePartial" + }, + ") | undefined; }" + ], + "path": "packages/kbn-utility-types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/utility-types", "id": "def-common.RecursiveReadonly", diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index a81392db521d3..f8ec982bde618 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 36 | 0 | 15 | 1 | +| 37 | 0 | 16 | 1 | ## Common diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index f64e21c7473c3..7b9d751a350c5 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 144543ea37762..2e3cf322c2cd3 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index bd0c650311ba9..e7f99fbed1868 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.devdocs.json b/api_docs/kbn_xstate_utils.devdocs.json new file mode 100644 index 0000000000000..b67854e217961 --- /dev/null +++ b/api_docs/kbn_xstate_utils.devdocs.json @@ -0,0 +1,274 @@ +{ + "id": "@kbn/xstate-utils", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.createNotificationChannel", + "type": "Function", + "tags": [], + "label": "createNotificationChannel", + "description": [], + "signature": [ + "(shouldReplayLastEvent?: boolean) => ", + { + "pluginId": "@kbn/xstate-utils", + "scope": "common", + "docId": "kibKbnXstateUtilsPluginApi", + "section": "def-common.NotificationChannel", + "text": "NotificationChannel" + }, + "" + ], + "path": "packages/kbn-xstate-utils/src/notification_channel.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.createNotificationChannel.$1", + "type": "boolean", + "tags": [], + "label": "shouldReplayLastEvent", + "description": [], + "signature": [ + "boolean" + ], + "path": "packages/kbn-xstate-utils/src/notification_channel.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.isDevMode", + "type": "Function", + "tags": [], + "label": "isDevMode", + "description": [], + "signature": [ + "() => boolean" + ], + "path": "packages/kbn-xstate-utils/src/dev_tools.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.sendIfDefined", + "type": "Function", + "tags": [], + "label": "sendIfDefined", + "description": [], + "signature": [ + "(target: string | ", + "ActorRef", + ") => (eventExpr: ", + "Expr", + ", options?: ", + "SendActionOptions", + " | undefined) => ", + "PureAction", + "" + ], + "path": "packages/kbn-xstate-utils/src/actions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.sendIfDefined.$1", + "type": "CompoundType", + "tags": [], + "label": "target", + "description": [], + "signature": [ + "string | ", + "ActorRef", + "" + ], + "path": "packages/kbn-xstate-utils/src/actions.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.NotificationChannel", + "type": "Interface", + "tags": [], + "label": "NotificationChannel", + "description": [], + "signature": [ + { + "pluginId": "@kbn/xstate-utils", + "scope": "common", + "docId": "kibKbnXstateUtilsPluginApi", + "section": "def-common.NotificationChannel", + "text": "NotificationChannel" + }, + "" + ], + "path": "packages/kbn-xstate-utils/src/notification_channel.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.NotificationChannel.createService", + "type": "Function", + "tags": [], + "label": "createService", + "description": [], + "signature": [ + "() => ", + "Subscribable", + "" + ], + "path": "packages/kbn-xstate-utils/src/notification_channel.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.NotificationChannel.notify", + "type": "Function", + "tags": [], + "label": "notify", + "description": [], + "signature": [ + "(eventExpr: ", + "Expr", + ") => (context: TContext, event: TEvent, meta: ", + "ActionMeta", + ") => void" + ], + "path": "packages/kbn-xstate-utils/src/notification_channel.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.NotificationChannel.notify.$1", + "type": "Function", + "tags": [], + "label": "eventExpr", + "description": [], + "signature": [ + "Expr", + "" + ], + "path": "packages/kbn-xstate-utils/src/notification_channel.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.MatchedState", + "type": "Type", + "tags": [], + "label": "MatchedState", + "description": [], + "signature": [ + "TState extends ", + "State", + " ? ", + "State", + "<(TTypestate extends any ? { value: TStateValue; context: any; } extends TTypestate ? TTypestate : never : never)[\"context\"], TEvent, TStateSchema, TTypestate, TResolvedTypesMeta> & { value: TStateValue; } : never" + ], + "path": "packages/kbn-xstate-utils/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.MatchedStateFromActor", + "type": "Type", + "tags": [], + "label": "MatchedStateFromActor", + "description": [], + "signature": [ + "EmittedFrom", + " extends ", + "State", + " ? ", + "State", + "<(TTypestate extends any ? { value: TStateValue; context: any; } extends TTypestate ? TTypestate : never : never)[\"context\"], TEvent, TStateSchema, TTypestate, TResolvedTypesMeta> & { value: TStateValue; } : never" + ], + "path": "packages/kbn-xstate-utils/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/xstate-utils", + "id": "def-common.OmitDeprecatedState", + "type": "Type", + "tags": [], + "label": "OmitDeprecatedState", + "description": [], + "signature": [ + "{ [P in Exclude]: T[P]; }" + ], + "path": "packages/kbn-xstate-utils/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx new file mode 100644 index 0000000000000..d918603b4dd1b --- /dev/null +++ b/api_docs/kbn_xstate_utils.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnXstateUtilsPluginApi +slug: /kibana-dev-docs/api/kbn-xstate-utils +title: "@kbn/xstate-utils" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/xstate-utils plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] +--- +import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; + + + +Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 12 | 0 | 12 | 0 | + +## Common + +### Functions + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 99d72a7aa596d..47958bd7be0b2 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index e7ae39a2ce8f4..693786c5361ed 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.devdocs.json b/api_docs/kibana_react.devdocs.json index 04518800ede3e..211c5113537ad 100644 --- a/api_docs/kibana_react.devdocs.json +++ b/api_docs/kibana_react.devdocs.json @@ -728,6 +728,18 @@ "plugin": "home", "path": "src/plugins/home/public/application/application.tsx" }, + { + "plugin": "serverless", + "path": "x-pack/plugins/serverless/public/plugin.tsx" + }, + { + "plugin": "serverless", + "path": "x-pack/plugins/serverless/public/plugin.tsx" + }, + { + "plugin": "serverless", + "path": "x-pack/plugins/serverless/public/plugin.tsx" + }, { "plugin": "management", "path": "src/plugins/management/public/components/management_app/management_app.tsx" @@ -909,16 +921,20 @@ "path": "src/plugins/saved_objects/public/save_modal/show_saved_object_save_modal.tsx" }, { - "plugin": "serverless", - "path": "x-pack/plugins/serverless/public/plugin.tsx" + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/public/shared_imports.ts" }, { - "plugin": "serverless", - "path": "x-pack/plugins/serverless/public/plugin.tsx" + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/public/application/index.tsx" }, { - "plugin": "serverless", - "path": "x-pack/plugins/serverless/public/plugin.tsx" + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/public/application/index.tsx" + }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/public/application/index.tsx" }, { "plugin": "visualizations", @@ -1576,22 +1592,6 @@ "plugin": "expressionShape", "path": "src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx" }, - { - "plugin": "indexManagement", - "path": "x-pack/plugins/index_management/public/shared_imports.ts" - }, - { - "plugin": "indexManagement", - "path": "x-pack/plugins/index_management/public/application/index.tsx" - }, - { - "plugin": "indexManagement", - "path": "x-pack/plugins/index_management/public/application/index.tsx" - }, - { - "plugin": "indexManagement", - "path": "x-pack/plugins/index_management/public/application/index.tsx" - }, { "plugin": "crossClusterReplication", "path": "x-pack/plugins/cross_cluster_replication/public/shared_imports.ts" @@ -3080,11 +3080,11 @@ }, { "plugin": "data", - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.tsx" + "path": "src/plugins/data/public/incomplete_results_modal/open_incomplete_results_modal_button.tsx" }, { "plugin": "data", - "path": "src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.tsx" + "path": "src/plugins/data/public/incomplete_results_modal/open_incomplete_results_modal_button.tsx" }, { "plugin": "data", @@ -3166,6 +3166,26 @@ "plugin": "savedObjects", "path": "src/plugins/saved_objects/public/saved_object/helpers/confirm_modal_promise.tsx" }, + { + "plugin": "runtimeFields", + "path": "x-pack/plugins/runtime_fields/public/shared_imports.ts" + }, + { + "plugin": "runtimeFields", + "path": "x-pack/plugins/runtime_fields/public/load_editor.tsx" + }, + { + "plugin": "runtimeFields", + "path": "x-pack/plugins/runtime_fields/public/load_editor.tsx" + }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx" + }, + { + "plugin": "indexManagement", + "path": "x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx" + }, { "plugin": "dataViewEditor", "path": "src/plugins/data_view_editor/public/shared_imports.ts" @@ -3326,14 +3346,6 @@ "plugin": "savedObjectsTagging", "path": "x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/open_assign_flyout.tsx" }, - { - "plugin": "eventAnnotation", - "path": "src/plugins/event_annotation/public/get_table_list.tsx" - }, - { - "plugin": "eventAnnotation", - "path": "src/plugins/event_annotation/public/get_table_list.tsx" - }, { "plugin": "dataViewFieldEditor", "path": "src/plugins/data_view_field_editor/public/shared_imports.ts" @@ -3674,26 +3686,6 @@ "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/public/components/take_action.tsx" }, - { - "plugin": "runtimeFields", - "path": "x-pack/plugins/runtime_fields/public/shared_imports.ts" - }, - { - "plugin": "runtimeFields", - "path": "x-pack/plugins/runtime_fields/public/load_editor.tsx" - }, - { - "plugin": "runtimeFields", - "path": "x-pack/plugins/runtime_fields/public/load_editor.tsx" - }, - { - "plugin": "indexManagement", - "path": "x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx" - }, - { - "plugin": "indexManagement", - "path": "x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx" - }, { "plugin": "dashboardEnhanced", "path": "x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx" @@ -3726,14 +3718,6 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/helpers/saved_objects_utils/confirm_modal_promise.tsx" }, - { - "plugin": "graph", - "path": "x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx" - }, - { - "plugin": "graph", - "path": "x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx" - }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/application.tsx" @@ -3852,11 +3836,11 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx" }, { "plugin": "synthetics", @@ -3864,11 +3848,11 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx" }, { "plugin": "synthetics", @@ -3906,122 +3890,6 @@ "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/simple_test_results.tsx" }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/components/toast_notification_text.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/components/toast_notification_text.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx" - }, - { - "plugin": "transform", - "path": "x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx" - }, { "plugin": "uptime", "path": "x-pack/plugins/uptime/public/legacy_uptime/lib/alert_types/alert_messages.tsx" @@ -4078,6 +3946,14 @@ "plugin": "dataViewManagement", "path": "src/plugins/data_view_management/public/components/edit_index_pattern/remove_data_view.tsx" }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/get_table_list.tsx" + }, + { + "plugin": "eventAnnotationListing", + "path": "src/plugins/event_annotation_listing/public/get_table_list.tsx" + }, { "plugin": "filesManagement", "path": "src/plugins/files_management/public/mount_management_section.tsx" diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index c2899bab77685..37a8f1c39955a 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index c7043efea0322..8a1a411fd1139 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 71bacaad6ebca..50257b59cfc42 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 331601a978bd5..093458c12f8fa 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -2451,7 +2451,7 @@ }, " | undefined; reducedTimeRange?: string | undefined; timeScale?: ", "TimeScaleUnit", - " | undefined; format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; }, layer: ", + " | undefined; format?: { id: string; params?: { decimals: number; compact?: boolean | undefined; } | undefined; } | undefined; }, layer: ", { "pluginId": "lens", "scope": "public", @@ -2594,7 +2594,7 @@ "label": "format", "description": [], "signature": [ - "{ id: string; params?: { decimals: number; } | undefined; } | undefined" + "{ id: string; params?: { decimals: number; compact?: boolean | undefined; } | undefined; } | undefined" ], "path": "x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts", "deprecated": false, @@ -9651,6 +9651,37 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "lens", + "id": "def-public.EmbeddableComponent", + "type": "Type", + "tags": [], + "label": "EmbeddableComponent", + "description": [], + "signature": [ + "React.ComponentClass<", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.EmbeddableComponentProps", + "text": "EmbeddableComponentProps" + }, + ", any> | React.FunctionComponent<", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.EmbeddableComponentProps", + "text": "EmbeddableComponentProps" + }, + ">" + ], + "path": "x-pack/plugins/lens/public/embeddable/embeddable_component.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "lens", "id": "def-public.EmbeddableComponentProps", diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index ff23c940b0bc8..62f688564bbdf 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 616 | 0 | 519 | 61 | +| 617 | 0 | 520 | 61 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 86f910ca4901e..2b6aa855cb4fb 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 9a5858fcaee14..5aa6ab7857a48 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index bfa40e142ca1f..fa3ad2358899b 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 78a4d8f31609d..77336f712b2e4 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/log_explorer.devdocs.json b/api_docs/log_explorer.devdocs.json new file mode 100644 index 0000000000000..238163290a823 --- /dev/null +++ b/api_docs/log_explorer.devdocs.json @@ -0,0 +1,437 @@ +{ + "id": "logExplorer", + "client": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerStateContainer", + "type": "Interface", + "tags": [], + "label": "LogExplorerStateContainer", + "description": [], + "path": "x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerStateContainer.appState", + "type": "Object", + "tags": [], + "label": "appState", + "description": [], + "signature": [ + { + "pluginId": "discover", + "scope": "public", + "docId": "kibDiscoverPluginApi", + "section": "def-public.DiscoverAppState", + "text": "DiscoverAppState" + }, + " | undefined" + ], + "path": "x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerStateContainer.logExplorerState", + "type": "CompoundType", + "tags": [], + "label": "logExplorerState", + "description": [], + "signature": [ + "Partial<", + "WithDatasetSelection", + " | (", + "WithDatasetSelection", + " & ", + "WithControlPanels", + ") | (", + "WithDatasetSelection", + " & ", + "WithControlPanelGroupAPI", + " & ", + "WithControlPanels", + ")> | undefined" + ], + "path": "x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerPluginSetup", + "type": "Interface", + "tags": [], + "label": "LogExplorerPluginSetup", + "description": [], + "path": "x-pack/plugins/log_explorer/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerPluginSetup.locators", + "type": "Object", + "tags": [], + "label": "locators", + "description": [], + "signature": [ + "LogExplorerLocators" + ], + "path": "x-pack/plugins/log_explorer/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerPluginStart", + "type": "Interface", + "tags": [], + "label": "LogExplorerPluginStart", + "description": [], + "path": "x-pack/plugins/log_explorer/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerPluginStart.LogExplorer", + "type": "CompoundType", + "tags": [], + "label": "LogExplorer", + "description": [], + "signature": [ + "React.ComponentClass<", + "LogExplorerProps", + ", any> | React.FunctionComponent<", + "LogExplorerProps", + ">" + ], + "path": "x-pack/plugins/log_explorer/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [ + { + "parentPluginId": "logExplorer", + "id": "def-common.AllDatasetSelection", + "type": "Class", + "tags": [], + "label": "AllDatasetSelection", + "description": [], + "signature": [ + { + "pluginId": "logExplorer", + "scope": "common", + "docId": "kibLogExplorerPluginApi", + "section": "def-common.AllDatasetSelection", + "text": "AllDatasetSelection" + }, + " implements ", + "DatasetSelectionStrategy" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-common.AllDatasetSelection.selectionType", + "type": "string", + "tags": [], + "label": "selectionType", + "description": [], + "signature": [ + "\"all\"" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.AllDatasetSelection.selection", + "type": "Object", + "tags": [], + "label": "selection", + "description": [], + "signature": [ + "{ dataset: ", + "Dataset", + "; }" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.AllDatasetSelection.toDataviewSpec", + "type": "Function", + "tags": [], + "label": "toDataviewSpec", + "description": [], + "signature": [ + "() => { id: string; name: string | undefined; title: string | undefined; }" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.AllDatasetSelection.toURLSelectionId", + "type": "Function", + "tags": [], + "label": "toURLSelectionId", + "description": [], + "signature": [ + "() => string" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.AllDatasetSelection.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "logExplorer", + "scope": "common", + "docId": "kibLogExplorerPluginApi", + "section": "def-common.AllDatasetSelection", + "text": "AllDatasetSelection" + } + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection", + "type": "Class", + "tags": [], + "label": "UnresolvedDatasetSelection", + "description": [], + "signature": [ + { + "pluginId": "logExplorer", + "scope": "common", + "docId": "kibLogExplorerPluginApi", + "section": "def-common.UnresolvedDatasetSelection", + "text": "UnresolvedDatasetSelection" + }, + " implements ", + "DatasetSelectionStrategy" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.selectionType", + "type": "string", + "tags": [], + "label": "selectionType", + "description": [], + "signature": [ + "\"unresolved\"" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.selection", + "type": "Object", + "tags": [], + "label": "selection", + "description": [], + "signature": [ + "{ name?: string | undefined; dataset: ", + "Dataset", + "; }" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.toDataviewSpec", + "type": "Function", + "tags": [], + "label": "toDataviewSpec", + "description": [], + "signature": [ + "() => { id: string; name: string | undefined; title: string | undefined; }" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.toURLSelectionId", + "type": "Function", + "tags": [], + "label": "toURLSelectionId", + "description": [], + "signature": [ + "() => string" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.fromSelection", + "type": "Function", + "tags": [], + "label": "fromSelection", + "description": [], + "signature": [ + "(selection: { name?: string | undefined; } & { dataset: { name: ", + "Branded", + "; } & { title?: string | undefined; }; }) => ", + { + "pluginId": "logExplorer", + "scope": "common", + "docId": "kibLogExplorerPluginApi", + "section": "def-common.UnresolvedDatasetSelection", + "text": "UnresolvedDatasetSelection" + } + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.fromSelection.$1", + "type": "CompoundType", + "tags": [], + "label": "selection", + "description": [], + "signature": [ + "{ name?: string | undefined; } & { dataset: { name: ", + "Branded", + "; } & { title?: string | undefined; }; }" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "(dataset: ", + "Dataset", + ") => ", + { + "pluginId": "logExplorer", + "scope": "common", + "docId": "kibLogExplorerPluginApi", + "section": "def-common.UnresolvedDatasetSelection", + "text": "UnresolvedDatasetSelection" + } + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-common.UnresolvedDatasetSelection.create.$1", + "type": "Object", + "tags": [], + "label": "dataset", + "description": [], + "signature": [ + "Dataset" + ], + "path": "x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/log_explorer.mdx b/api_docs/log_explorer.mdx new file mode 100644 index 0000000000000..140c7e9406e96 --- /dev/null +++ b/api_docs/log_explorer.mdx @@ -0,0 +1,41 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibLogExplorerPluginApi +slug: /kibana-dev-docs/api/logExplorer +title: "logExplorer" +image: https://source.unsplash.com/400x175/?github +description: API docs for the logExplorer plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logExplorer'] +--- +import logExplorerObj from './log_explorer.devdocs.json'; + +This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. + +Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 22 | 0 | 22 | 7 | + +## Client + +### Setup + + +### Start + + +### Interfaces + + +## Common + +### Classes + + diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index d21c8678bdbd7..b1f29f1957d2b 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 62bf42794f51e..7ea12f4dc5f0c 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index e732667d42150..2912fb874ff47 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -3095,54 +3095,6 @@ ], "returnComment": [] }, - { - "parentPluginId": "maps", - "id": "def-public.IVectorSource.createField", - "type": "Function", - "tags": [], - "label": "createField", - "description": [], - "signature": [ - "({ fieldName }: { fieldName: string; }) => ", - { - "pluginId": "maps", - "scope": "public", - "docId": "kibMapsPluginApi", - "section": "def-public.IField", - "text": "IField" - } - ], - "path": "x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.IVectorSource.createField.$1", - "type": "Object", - "tags": [], - "label": "{ fieldName }", - "description": [], - "path": "x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.IVectorSource.createField.$1.fieldName", - "type": "string", - "tags": [], - "label": "fieldName", - "description": [], - "path": "x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [] - }, { "parentPluginId": "maps", "id": "def-public.IVectorSource.hasTooltipProperties", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 753e36934248f..c0fe15718b0b0 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 262 | 0 | 261 | 28 | +| 259 | 0 | 258 | 28 | ## Client diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index f2ee21c0654f0..e64231326653a 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.devdocs.json b/api_docs/metrics_data_access.devdocs.json new file mode 100644 index 0000000000000..ed498fd981e05 --- /dev/null +++ b/api_docs/metrics_data_access.devdocs.json @@ -0,0 +1,319 @@ +{ + "id": "metricsDataAccess", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataClient", + "type": "Class", + "tags": [], + "label": "MetricsDataClient", + "description": [], + "path": "x-pack/plugins/metrics_data_access/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataClient.getMetricIndices", + "type": "Function", + "tags": [], + "label": "getMetricIndices", + "description": [], + "signature": [ + "(options: ", + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.GetMetricIndicesOptions", + "text": "GetMetricIndicesOptions" + }, + ") => Promise" + ], + "path": "x-pack/plugins/metrics_data_access/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataClient.getMetricIndices.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.GetMetricIndicesOptions", + "text": "GetMetricIndicesOptions" + } + ], + "path": "x-pack/plugins/metrics_data_access/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataClient.updateMetricIndices", + "type": "Function", + "tags": [], + "label": "updateMetricIndices", + "description": [], + "signature": [ + "(options: ", + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.UpdateMetricIndicesOptions", + "text": "UpdateMetricIndicesOptions" + }, + ") => Promise<", + { + "pluginId": "@kbn/core-saved-objects-common", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsCommonPluginApi", + "section": "def-common.SavedObject", + "text": "SavedObject" + }, + "<{ metricIndices: string; }>>" + ], + "path": "x-pack/plugins/metrics_data_access/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataClient.updateMetricIndices.$1", + "type": "CompoundType", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.UpdateMetricIndicesOptions", + "text": "UpdateMetricIndicesOptions" + } + ], + "path": "x-pack/plugins/metrics_data_access/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataClient.setDefaultMetricIndicesHandler", + "type": "Function", + "tags": [], + "label": "setDefaultMetricIndicesHandler", + "description": [], + "signature": [ + "(handler: ", + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.DefaultMetricIndicesHandler", + "text": "DefaultMetricIndicesHandler" + }, + ") => void" + ], + "path": "x-pack/plugins/metrics_data_access/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataClient.setDefaultMetricIndicesHandler.$1", + "type": "CompoundType", + "tags": [], + "label": "handler", + "description": [], + "signature": [ + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.DefaultMetricIndicesHandler", + "text": "DefaultMetricIndicesHandler" + } + ], + "path": "x-pack/plugins/metrics_data_access/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.GetMetricIndicesOptions", + "type": "Interface", + "tags": [], + "label": "GetMetricIndicesOptions", + "description": [], + "path": "x-pack/plugins/metrics_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.GetMetricIndicesOptions.savedObjectsClient", + "type": "Object", + "tags": [], + "label": "savedObjectsClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/metrics_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.DefaultMetricIndicesHandler", + "type": "Type", + "tags": [], + "label": "DefaultMetricIndicesHandler", + "description": [], + "signature": [ + "((options: ", + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.GetMetricIndicesOptions", + "text": "GetMetricIndicesOptions" + }, + ") => Promise) | null" + ], + "path": "x-pack/plugins/metrics_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.metricsDataSourceSavedObjectName", + "type": "string", + "tags": [], + "label": "metricsDataSourceSavedObjectName", + "description": [], + "signature": [ + "\"metrics-data-source\"" + ], + "path": "x-pack/plugins/metrics_data_access/server/saved_objects/metrics_data_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.UpdateMetricIndicesOptions", + "type": "Type", + "tags": [], + "label": "UpdateMetricIndicesOptions", + "description": [], + "signature": [ + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.GetMetricIndicesOptions", + "text": "GetMetricIndicesOptions" + }, + " & { metricIndices: string; }" + ], + "path": "x-pack/plugins/metrics_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [], + "setup": { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataPluginSetup", + "type": "Interface", + "tags": [], + "label": "MetricsDataPluginSetup", + "description": [], + "path": "x-pack/plugins/metrics_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "metricsDataAccess", + "id": "def-server.MetricsDataPluginSetup.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "metricsDataAccess", + "scope": "server", + "docId": "kibMetricsDataAccessPluginApi", + "section": "def-server.MetricsDataClient", + "text": "MetricsDataClient" + } + ], + "path": "x-pack/plugins/metrics_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "setup", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx new file mode 100644 index 0000000000000..c00349e8168fb --- /dev/null +++ b/api_docs/metrics_data_access.mdx @@ -0,0 +1,39 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibMetricsDataAccessPluginApi +slug: /kibana-dev-docs/api/metricsDataAccess +title: "metricsDataAccess" +image: https://source.unsplash.com/400x175/?github +description: API docs for the metricsDataAccess plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] +--- +import metricsDataAccessObj from './metrics_data_access.devdocs.json'; + +Exposes utilities for accessing metrics data + +Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 14 | 0 | 14 | 0 | + +## Server + +### Setup + + +### Classes + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 6104d0c90ee7e..b7c8f8a996d55 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 3bd402b689bde..524604a67cb69 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 2cd8a2e6ec393..48a3e009c3e2b 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 349c843836456..9cf0efdfd7466 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 04e067a9fa249..6831ca9c2d2af 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index 753457d10e5c3..5bc6bddf52d89 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index da5e1a83739fe..0d73b68f209ce 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index e910e74323d61..4af6701526078 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -155,9 +155,7 @@ "label": "AlertSummary", "description": [], "signature": [ - "({ alertSummaryFields }: ", - "AlertSummaryProps", - ") => JSX.Element" + "({ alertSummaryFields }: AlertSummaryProps) => JSX.Element" ], "path": "x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx", "deprecated": false, @@ -418,9 +416,7 @@ "label": "DatePicker", "description": [], "signature": [ - "({\n rangeFrom,\n rangeTo,\n refreshPaused,\n refreshInterval,\n width = 'restricted',\n onTimeRangeRefresh,\n}: ", - "DatePickerProps", - ") => JSX.Element" + "({\n rangeFrom,\n rangeTo,\n refreshPaused,\n refreshInterval,\n width = 'restricted',\n onTimeRangeRefresh,\n}: DatePickerProps) => JSX.Element" ], "path": "x-pack/plugins/observability/public/pages/overview/components/date_picker/date_picker.tsx", "deprecated": false, @@ -788,7 +784,7 @@ }, " | undefined; list: () => string[]; }; selectedAlertId?: string | undefined; } & ", "CommonProps", - " & { as?: \"div\" | undefined; } & _EuiFlyoutProps & Omit, HTMLDivElement>, keyof _EuiFlyoutProps> & Omit, HTMLDivElement>, \"key\" | keyof React.HTMLAttributes | \"css\"> & { ref?: React.RefObject | ((instance: HTMLDivElement | null) => void) | null | undefined; }, \"type\" | \"prefix\" | \"key\" | \"id\" | \"defaultValue\" | \"security\" | \"children\" | \"ref\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"title\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"paddingSize\" | \"data-test-subj\" | \"css\" | \"size\" | \"onClose\" | \"as\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\">, \"type\" | \"prefix\" | \"key\" | \"id\" | \"defaultValue\" | \"security\" | \"alert\" | \"children\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"title\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"paddingSize\" | \"data-test-subj\" | \"css\" | \"alerts\" | \"size\" | \"onClose\" | \"as\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\" | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: React.RefObject | ((instance: HTMLDivElement | null) => void) | null | undefined; }> & { readonly _result: ({ alert, alerts, isInApp, observabilityRuleTypeRegistry, onClose, selectedAlertId, }: AlertsFlyoutProps) => JSX.Element | null; }" + " & { as?: \"div\" | undefined; } & _EuiFlyoutProps & Omit, HTMLDivElement>, keyof _EuiFlyoutProps> & Omit, HTMLDivElement>, \"key\" | keyof React.HTMLAttributes | \"css\"> & { ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject | null | undefined; }, \"type\" | \"prefix\" | \"key\" | \"id\" | \"defaultValue\" | \"security\" | \"children\" | \"ref\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"title\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"paddingSize\" | \"data-test-subj\" | \"css\" | \"size\" | \"onClose\" | \"as\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\">, \"type\" | \"prefix\" | \"key\" | \"id\" | \"defaultValue\" | \"security\" | \"alert\" | \"children\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"title\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"paddingSize\" | \"data-test-subj\" | \"css\" | \"alerts\" | \"size\" | \"onClose\" | \"as\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\" | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject | null | undefined; }> & { readonly _result: ({ alert, alerts, isInApp, observabilityRuleTypeRegistry, onClose, selectedAlertId, }: AlertsFlyoutProps) => JSX.Element | null; }" ], "path": "x-pack/plugins/observability/public/index.ts", "deprecated": false, @@ -2255,6 +2251,26 @@ "path": "x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityFetchDataResponse.universal_profiling", + "type": "Object", + "tags": [], + "label": "universal_profiling", + "description": [], + "signature": [ + { + "pluginId": "observability", + "scope": "public", + "docId": "kibObservabilityPluginApi", + "section": "def-public.FetchDataResponse", + "text": "FetchDataResponse" + } + ], + "path": "x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2369,6 +2385,20 @@ "path": "x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityHasDataResponse.universal_profiling", + "type": "Object", + "tags": [], + "label": "universal_profiling", + "description": [], + "signature": [ + "UniversalProfilingHasDataResponse" + ], + "path": "x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2652,6 +2682,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.contentManagement", + "type": "Object", + "tags": [], + "label": "contentManagement", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentManagementPublicStart", + "text": "ContentManagementPublicStart" + } + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityPublicPluginsStart.data", @@ -3162,6 +3212,20 @@ "path": "x-pack/plugins/observability/public/plugin.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.aiops", + "type": "Object", + "tags": [], + "label": "aiops", + "description": [], + "signature": [ + "AiopsPluginStart" + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -4201,7 +4265,7 @@ "label": "ObservabilityFetchDataPlugins", "description": [], "signature": [ - "\"uptime\" | \"ux\" | \"infra_logs\" | \"infra_metrics\" | \"apm\"" + "\"uptime\" | \"ux\" | \"infra_logs\" | \"infra_metrics\" | \"apm\" | \"universal_profiling\"" ], "path": "x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts", "deprecated": false, @@ -4394,6 +4458,27 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-public.UniversalProfilingDataResponse", + "type": "Type", + "tags": [], + "label": "UniversalProfilingDataResponse", + "description": [], + "signature": [ + { + "pluginId": "observability", + "scope": "public", + "docId": "kibObservabilityPluginApi", + "section": "def-public.FetchDataResponse", + "text": "FetchDataResponse" + } + ], + "path": "x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-public.uptimeOverviewLocatorID", @@ -7770,7 +7855,7 @@ "label": "options", "description": [], "signature": [ - "{ tags: string[]; }" + "{ tags: string[]; access?: \"public\" | \"internal\" | undefined; }" ], "path": "x-pack/plugins/observability/server/routes/types.ts", "deprecated": false, @@ -7898,7 +7983,7 @@ "label": "config", "description": [], "signature": [ - "{ readonly enabled: boolean; readonly unsafe: Readonly<{} & { alertDetails: Readonly<{} & { uptime: Readonly<{} & { enabled: boolean; }>; metrics: Readonly<{} & { enabled: boolean; }>; observability: Readonly<{} & { enabled: boolean; }>; logs: Readonly<{} & { enabled: boolean; }>; }>; thresholdRule: Readonly<{} & { enabled: boolean; }>; }>; readonly annotations: Readonly<{} & { index: string; enabled: boolean; }>; readonly thresholdRule: Readonly<{} & { groupByPageSize: number; }>; readonly compositeSlo: Readonly<{} & { enabled: boolean; }>; }" + "{ readonly enabled: boolean; readonly unsafe: Readonly<{} & { alertDetails: Readonly<{} & { uptime: Readonly<{} & { enabled: boolean; }>; metrics: Readonly<{} & { enabled: boolean; }>; observability: Readonly<{} & { enabled: boolean; }>; logs: Readonly<{} & { enabled: boolean; }>; }>; thresholdRule: Readonly<{} & { enabled: boolean; }>; }>; readonly annotations: Readonly<{} & { index: string; enabled: boolean; }>; readonly customThresholdRule: Readonly<{} & { groupByPageSize: number; }>; readonly compositeSlo: Readonly<{} & { enabled: boolean; }>; }" ], "path": "x-pack/plugins/observability/server/routes/types.ts", "deprecated": false, @@ -9211,6 +9296,34 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, + "; \"POST /api/observability/slos/_delete_instances 2023-10-31\": { endpoint: \"POST /api/observability/slos/_delete_instances 2023-10-31\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ list: ", + "ArrayC", + "<", + "TypeC", + "<{ sloId: ", + "StringC", + "; instanceId: ", + "StringC", + "; }>>; }>; }> | undefined; handler: ({}: ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + " & { params: { body: { list: { sloId: string; instanceId: string; }[]; }; }; }) => Promise; } & ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, "; \"DELETE /api/observability/slos/{id} 2023-10-31\": { endpoint: \"DELETE /api/observability/slos/{id} 2023-10-31\"; params?: ", "TypeC", "<{ path: ", @@ -9666,7 +9779,7 @@ "label": "ObservabilityConfig", "description": [], "signature": [ - "{ readonly enabled: boolean; readonly unsafe: Readonly<{} & { alertDetails: Readonly<{} & { uptime: Readonly<{} & { enabled: boolean; }>; metrics: Readonly<{} & { enabled: boolean; }>; observability: Readonly<{} & { enabled: boolean; }>; logs: Readonly<{} & { enabled: boolean; }>; }>; thresholdRule: Readonly<{} & { enabled: boolean; }>; }>; readonly annotations: Readonly<{} & { index: string; enabled: boolean; }>; readonly thresholdRule: Readonly<{} & { groupByPageSize: number; }>; readonly compositeSlo: Readonly<{} & { enabled: boolean; }>; }" + "{ readonly enabled: boolean; readonly unsafe: Readonly<{} & { alertDetails: Readonly<{} & { uptime: Readonly<{} & { enabled: boolean; }>; metrics: Readonly<{} & { enabled: boolean; }>; observability: Readonly<{} & { enabled: boolean; }>; logs: Readonly<{} & { enabled: boolean; }>; }>; thresholdRule: Readonly<{} & { enabled: boolean; }>; }>; readonly annotations: Readonly<{} & { index: string; enabled: boolean; }>; readonly customThresholdRule: Readonly<{} & { groupByPageSize: number; }>; readonly compositeSlo: Readonly<{} & { enabled: boolean; }>; }" ], "path": "x-pack/plugins/observability/server/index.ts", "deprecated": false, @@ -10917,6 +11030,34 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, + "; \"POST /api/observability/slos/_delete_instances 2023-10-31\": { endpoint: \"POST /api/observability/slos/_delete_instances 2023-10-31\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ list: ", + "ArrayC", + "<", + "TypeC", + "<{ sloId: ", + "StringC", + "; instanceId: ", + "StringC", + "; }>>; }>; }> | undefined; handler: ({}: ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + " & { params: { body: { list: { sloId: string; instanceId: string; }[]; }; }; }) => Promise; } & ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, "; \"DELETE /api/observability/slos/{id} 2023-10-31\": { endpoint: \"DELETE /api/observability/slos/{id} 2023-10-31\"; params?: ", "TypeC", "<{ path: ", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 57b36d34aa168..565c340aa3350 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/team | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 541 | 2 | 532 | 16 | +| 546 | 2 | 537 | 14 | ## Client diff --git a/api_docs/observability_a_i_assistant.devdocs.json b/api_docs/observability_a_i_assistant.devdocs.json index 59f2dcee727e4..73a81e8571d20 100644 --- a/api_docs/observability_a_i_assistant.devdocs.json +++ b/api_docs/observability_a_i_assistant.devdocs.json @@ -399,7 +399,7 @@ "ObservabilityAIAssistantRouteHandlerResources", ") => Promise<{}>; } & ", "ObservabilityAIAssistantRouteCreateOptions", - "; \"POST /internal/observability_ai_assistant/functions/summarise\": { endpoint: \"POST /internal/observability_ai_assistant/functions/summarise\"; params?: ", + "; \"POST /internal/observability_ai_assistant/functions/summarize\": { endpoint: \"POST /internal/observability_ai_assistant/functions/summarize\"; params?: ", "TypeC", "<{ body: ", "TypeC", @@ -764,7 +764,7 @@ "ObservabilityAIAssistantRouteHandlerResources", ") => Promise<{}>; } & ", "ObservabilityAIAssistantRouteCreateOptions", - "; \"POST /internal/observability_ai_assistant/functions/summarise\": { endpoint: \"POST /internal/observability_ai_assistant/functions/summarise\"; params?: ", + "; \"POST /internal/observability_ai_assistant/functions/summarize\": { endpoint: \"POST /internal/observability_ai_assistant/functions/summarize\"; params?: ", "TypeC", "<{ body: ", "TypeC", @@ -1105,7 +1105,7 @@ "label": "ObservabilityAIAssistantAPIEndpoint", "description": [], "signature": [ - "\"POST /internal/observability_ai_assistant/chat\" | \"GET /internal/observability_ai_assistant/conversation/{conversationId}\" | \"POST /internal/observability_ai_assistant/conversations\" | \"POST /internal/observability_ai_assistant/conversation\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}/auto_title\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}/title\" | \"DELETE /internal/observability_ai_assistant/conversation/{conversationId}\" | \"GET /internal/observability_ai_assistant/connectors\" | \"POST /internal/observability_ai_assistant/functions/elasticsearch\" | \"POST /internal/observability_ai_assistant/functions/recall\" | \"POST /internal/observability_ai_assistant/functions/summarise\" | \"POST /internal/observability_ai_assistant/functions/setup_kb\" | \"GET /internal/observability_ai_assistant/functions/kb_status\" | \"POST /internal/observability_ai_assistant/functions/alerts\"" + "\"POST /internal/observability_ai_assistant/chat\" | \"GET /internal/observability_ai_assistant/conversation/{conversationId}\" | \"POST /internal/observability_ai_assistant/conversations\" | \"POST /internal/observability_ai_assistant/conversation\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}/auto_title\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}/title\" | \"DELETE /internal/observability_ai_assistant/conversation/{conversationId}\" | \"GET /internal/observability_ai_assistant/connectors\" | \"POST /internal/observability_ai_assistant/functions/elasticsearch\" | \"POST /internal/observability_ai_assistant/functions/recall\" | \"POST /internal/observability_ai_assistant/functions/summarize\" | \"POST /internal/observability_ai_assistant/functions/setup_kb\" | \"GET /internal/observability_ai_assistant/functions/kb_status\" | \"POST /internal/observability_ai_assistant/functions/alerts\"" ], "path": "x-pack/plugins/observability_ai_assistant/public/api/index.ts", "deprecated": false, @@ -1235,7 +1235,7 @@ "ObservabilityAIAssistantRouteHandlerResources", ") => Promise<{}>; } & ", "ObservabilityAIAssistantRouteCreateOptions", - "; \"POST /internal/observability_ai_assistant/functions/summarise\": { endpoint: \"POST /internal/observability_ai_assistant/functions/summarise\"; params?: ", + "; \"POST /internal/observability_ai_assistant/functions/summarize\": { endpoint: \"POST /internal/observability_ai_assistant/functions/summarize\"; params?: ", "TypeC", "<{ body: ", "TypeC", diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index 8df493b8cd5e0..6d72757ddcabd 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_log_explorer.devdocs.json b/api_docs/observability_log_explorer.devdocs.json new file mode 100644 index 0000000000000..d800417b5e917 --- /dev/null +++ b/api_docs/observability_log_explorer.devdocs.json @@ -0,0 +1,292 @@ +{ + "id": "observabilityLogExplorer", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [ + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.AllDatasetsLocatorDefinition", + "type": "Class", + "tags": [], + "label": "AllDatasetsLocatorDefinition", + "description": [], + "signature": [ + { + "pluginId": "observabilityLogExplorer", + "scope": "common", + "docId": "kibObservabilityLogExplorerPluginApi", + "section": "def-common.AllDatasetsLocatorDefinition", + "text": "AllDatasetsLocatorDefinition" + }, + " implements ", + { + "pluginId": "share", + "scope": "common", + "docId": "kibSharePluginApi", + "section": "def-common.LocatorDefinition", + "text": "LocatorDefinition" + }, + "<", + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + }, + ">" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.AllDatasetsLocatorDefinition.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "\"ALL_DATASETS_LOCATOR\"" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.AllDatasetsLocatorDefinition.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.AllDatasetsLocatorDefinition.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "DatasetLocatorDependencies" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.AllDatasetsLocatorDefinition.getLocation", + "type": "Function", + "tags": [], + "label": "getLocation", + "description": [], + "signature": [ + "(params: ", + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + }, + ") => Promise<{ app: string; path: string; state: {}; }>" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.AllDatasetsLocatorDefinition.getLocation.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.LogExplorerNavigationParams", + "text": "LogExplorerNavigationParams" + } + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.SingleDatasetLocatorDefinition", + "type": "Class", + "tags": [], + "label": "SingleDatasetLocatorDefinition", + "description": [], + "signature": [ + { + "pluginId": "observabilityLogExplorer", + "scope": "common", + "docId": "kibObservabilityLogExplorerPluginApi", + "section": "def-common.SingleDatasetLocatorDefinition", + "text": "SingleDatasetLocatorDefinition" + }, + " implements ", + { + "pluginId": "share", + "scope": "common", + "docId": "kibSharePluginApi", + "section": "def-common.LocatorDefinition", + "text": "LocatorDefinition" + }, + "<", + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.SingleDatasetLocatorParams", + "text": "SingleDatasetLocatorParams" + }, + ">" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.SingleDatasetLocatorDefinition.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "\"SINGLE_DATASET_LOCATOR\"" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.SingleDatasetLocatorDefinition.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.SingleDatasetLocatorDefinition.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "DatasetLocatorDependencies" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.SingleDatasetLocatorDefinition.getLocation", + "type": "Function", + "tags": [], + "label": "getLocation", + "description": [], + "signature": [ + "(params: ", + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.SingleDatasetLocatorParams", + "text": "SingleDatasetLocatorParams" + }, + ") => Promise<{ app: string; path: string; state: {}; }>" + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityLogExplorer", + "id": "def-common.SingleDatasetLocatorDefinition.getLocation.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + { + "pluginId": "@kbn/deeplinks-observability", + "scope": "common", + "docId": "kibKbnDeeplinksObservabilityPluginApi", + "section": "def-common.SingleDatasetLocatorParams", + "text": "SingleDatasetLocatorParams" + } + ], + "path": "x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/observability_log_explorer.mdx b/api_docs/observability_log_explorer.mdx new file mode 100644 index 0000000000000..2d08b65888627 --- /dev/null +++ b/api_docs/observability_log_explorer.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibObservabilityLogExplorerPluginApi +slug: /kibana-dev-docs/api/observabilityLogExplorer +title: "observabilityLogExplorer" +image: https://source.unsplash.com/400x175/?github +description: API docs for the observabilityLogExplorer plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogExplorer'] +--- +import observabilityLogExplorerObj from './observability_log_explorer.devdocs.json'; + +This plugin exposes and registers observability log consumption features. + +Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 12 | 0 | 12 | 1 | + +## Common + +### Classes + + diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 3d19856c2518b..667c6ce0254de 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.devdocs.json b/api_docs/observability_shared.devdocs.json index 2bfd9c96c19c3..cbad322aa12d4 100644 --- a/api_docs/observability_shared.devdocs.json +++ b/api_docs/observability_shared.devdocs.json @@ -270,6 +270,72 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.EmbeddableFlamegraph", + "type": "Function", + "tags": [], + "label": "EmbeddableFlamegraph", + "description": [], + "signature": [ + "(props: Props) => JSX.Element" + ], + "path": "x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityShared", + "id": "def-public.EmbeddableFlamegraph.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "Props" + ], + "path": "x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.EmbeddableFunctions", + "type": "Function", + "tags": [], + "label": "EmbeddableFunctions", + "description": [], + "signature": [ + "(props: Props) => JSX.Element" + ], + "path": "x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityShared", + "id": "def-public.EmbeddableFunctions.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "Props" + ], + "path": "x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "observabilityShared", "id": "def-public.getContextMenuItemsFromActions", @@ -1319,7 +1385,7 @@ "label": "useLinkProps", "description": [], "signature": [ - "({ app, pathname, hash, search }: ", + "({ app, pathname, hash, search, state }: ", { "pluginId": "observabilityShared", "scope": "public", @@ -1347,7 +1413,7 @@ "id": "def-public.useLinkProps.$1", "type": "Object", "tags": [], - "label": "{ app, pathname, hash, search }", + "label": "{ app, pathname, hash, search, state }", "description": [], "signature": [ { @@ -1837,6 +1903,20 @@ "path": "x-pack/plugins/observability_shared/public/hooks/use_link_props.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.LinkDescriptor.state", + "type": "Unknown", + "tags": [], + "label": "state", + "description": [], + "signature": [ + "unknown" + ], + "path": "x-pack/plugins/observability_shared/public/hooks/use_link_props.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2348,6 +2428,40 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.EMBEDDABLE_FLAMEGRAPH", + "type": "string", + "tags": [], + "label": "EMBEDDABLE_FLAMEGRAPH", + "description": [ + "Profiling flamegraph embeddable key" + ], + "signature": [ + "\"EMBEDDABLE_FLAMEGRAPH\"" + ], + "path": "x-pack/plugins/observability_shared/public/components/profiling/embeddables/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.EMBEDDABLE_FUNCTIONS", + "type": "string", + "tags": [], + "label": "EMBEDDABLE_FUNCTIONS", + "description": [ + "Profiling functions embeddable key" + ], + "signature": [ + "\"EMBEDDABLE_FUNCTIONS\"" + ], + "path": "x-pack/plugins/observability_shared/public/components/profiling/embeddables/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observabilityShared", "id": "def-public.LazyObservabilityPageTemplateProps", diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 8851407016745..0cab1278f9eaf 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 277 | 1 | 276 | 11 | +| 284 | 1 | 281 | 11 | ## Client diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 02bb0e4cd8143..746067c836abd 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.devdocs.json b/api_docs/painless_lab.devdocs.json new file mode 100644 index 0000000000000..04e37e95b5801 --- /dev/null +++ b/api_docs/painless_lab.devdocs.json @@ -0,0 +1,74 @@ +{ + "id": "painlessLab", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "painlessLab", + "id": "def-server.ConfigType", + "type": "Type", + "tags": [], + "label": "ConfigType", + "description": [], + "signature": [ + "{ readonly enabled: boolean; }" + ], + "path": "x-pack/plugins/painless_lab/server/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "painlessLab", + "id": "def-server.configSchema", + "type": "Object", + "tags": [], + "label": "configSchema", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "<{ enabled: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ConditionalType", + "text": "ConditionalType" + }, + "; }>" + ], + "path": "x-pack/plugins/painless_lab/server/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx new file mode 100644 index 0000000000000..3c962c6a95ba9 --- /dev/null +++ b/api_docs/painless_lab.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibPainlessLabPluginApi +slug: /kibana-dev-docs/api/painlessLab +title: "painlessLab" +image: https://source.unsplash.com/400x175/?github +description: API docs for the painlessLab plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] +--- +import painlessLabObj from './painless_lab.devdocs.json'; + + + +Contact [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2 | 0 | 2 | 0 | + +## Server + +### Objects + + +### Consts, variables and types + + diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index f1e64b69f8a00..b6fb713634393 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,29 +15,29 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 668 | 558 | 39 | +| 691 | 583 | 42 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 72348 | 226 | 61798 | 1499 | +| 74941 | 223 | 63921 | 1536 | ## Plugin Directory | Plugin name           | Maintaining team | Description | API Cnt | Any Cnt | Missing
comments | Missing
exports | |--------------|----------------|-----------|--------------|----------|---------------|--------| -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 267 | 0 | 261 | 30 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 269 | 0 | 263 | 31 | | | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 17 | 1 | 15 | 2 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 61 | 1 | 3 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 790 | 1 | 759 | 49 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 66 | 1 | 4 | 1 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 767 | 1 | 736 | 50 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 29 | 0 | 29 | 119 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 9 | 0 | 9 | 0 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Asset manager plugin for entity assets (inventory, topology, etc) | 2 | 0 | 2 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 91 | 1 | 75 | 2 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 94 | 0 | 75 | 27 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 104 | 0 | 84 | 27 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 268 | 2 | 253 | 10 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 72 | 0 | 16 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Chat available on Elastic Cloud deployments for quicker assistance. | 3 | 0 | 2 | 0 | @@ -57,24 +57,24 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 268 | 0 | 249 | 1 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 101 | 0 | 98 | 9 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 54 | 0 | 51 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3310 | 33 | 2583 | 26 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3308 | 33 | 2577 | 24 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 16 | 0 | 7 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Reusable data view field editor across Kibana | 72 | 0 | 33 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data view management app | 2 | 0 | 2 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1034 | 0 | 254 | 2 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1037 | 0 | 257 | 2 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 31 | 3 | 25 | 1 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 12 | 0 | 10 | 3 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 99 | 0 | 72 | 17 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 113 | 0 | 72 | 15 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | -| discoverLogExplorer | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin exposes and registers Logs+ features. | 0 | 0 | 0 | 0 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Server APIs for the Elastic AI Assistant | 4 | 0 | 2 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 542 | 1 | 442 | 7 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 534 | 1 | 434 | 7 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 44 | 0 | -| | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 11 | 0 | 11 | 0 | +| | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 6 | 0 | 6 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 115 | 3 | 111 | 3 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 195 | 0 | 195 | 5 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 200 | 0 | 200 | 6 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The listing page for event annotations. | 15 | 0 | 15 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 111 | 0 | 111 | 11 | | | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | - | 132 | 1 | 132 | 14 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | @@ -90,13 +90,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'shape' function and renderer to expressions | 148 | 0 | 146 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart. | 6 | 0 | 6 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart. | 175 | 0 | 165 | 13 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Adds expression runtime to Kibana | 2205 | 17 | 1746 | 5 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Adds expression runtime to Kibana | 2208 | 17 | 1749 | 5 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 235 | 0 | 99 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Index pattern fields and ambiguous values formatters | 292 | 5 | 253 | 3 | -| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | +| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 59 | 0 | 59 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 239 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 0 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1189 | 3 | 1073 | 41 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1199 | 3 | 1082 | 41 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -104,11 +104,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | graph | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 0 | 0 | 0 | 0 | | grokdebugger | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/platform-onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Guided onboarding framework | 58 | 0 | 57 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 147 | 0 | 108 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 149 | 0 | 109 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Image embeddable | 3 | 0 | 3 | 1 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | -| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 191 | 0 | 186 | 4 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 45 | 0 | 42 | 11 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 197 | 0 | 192 | 3 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 42 | 0 | 39 | 11 | | ingestPipelines | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | inputControlVis | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Input Control visualization to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 123 | 2 | 96 | 4 | @@ -118,16 +118,18 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 610 | 3 | 417 | 9 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | - | 5 | 0 | 5 | 1 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 616 | 0 | 519 | 61 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 617 | 0 | 520 | 61 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 8 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 224 | 0 | 96 | 51 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. | 22 | 0 | 22 | 7 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Exposes the shared components and APIs to access and visualize logs. | 269 | 10 | 256 | 27 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 47 | 0 | 47 | 7 | -| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 262 | 0 | 261 | 28 | +| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 259 | 0 | 258 | 28 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 67 | 0 | 67 | 0 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Exposes utilities for accessing metrics data | 14 | 0 | 14 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 150 | 3 | 64 | 32 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 15 | 3 | 13 | 1 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 9 | 0 | 9 | 0 | @@ -135,14 +137,16 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 541 | 2 | 532 | 16 | +| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 546 | 2 | 537 | 14 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 42 | 0 | 39 | 7 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin exposes and registers observability log consumption features. | 12 | 0 | 12 | 1 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 14 | 0 | 14 | 0 | -| | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 277 | 1 | 276 | 11 | +| | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 284 | 1 | 281 | 11 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 24 | 0 | 24 | 7 | -| painlessLab | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 218 | 2 | 164 | 11 | -| | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 19 | 1 | 19 | 3 | +| | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 17 | 1 | 17 | 3 | +| | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 3 | 0 | 3 | 2 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Reporting Services enables applications to feature reports that the user can automate with Watcher and download later. | 42 | 0 | 22 | 5 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 21 | 0 | 21 | 0 | @@ -158,17 +162,17 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-reporting-services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 8 | 5 | | searchprofiler | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 270 | 0 | 87 | 3 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 177 | 3 | 111 | 34 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 172 | 0 | 106 | 32 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | ESS customizations for Security Solution. | 6 | 0 | 6 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Serverless customizations for security. | 6 | 0 | 6 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 17 | 0 | 16 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 19 | 0 | 18 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Serverless customizations for observability. | 6 | 0 | 6 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Serverless customizations for search. | 6 | 0 | 6 | 0 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | - | 134 | 0 | 134 | 8 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds URL Service and sharing capabilities to Kibana | 119 | 0 | 60 | 10 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 22 | 1 | 22 | 1 | -| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories. | 253 | 0 | 65 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 14 | 0 | 14 | 3 | +| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories. | 256 | 0 | 65 | 0 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 24 | 0 | 24 | 3 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 4 | 0 | 4 | 1 | | synthetics | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 96 | 0 | 53 | 6 | @@ -176,14 +180,15 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 31 | 0 | 26 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 1 | 0 | 1 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 0 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 17 | 0 | 17 | 0 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 20 | 0 | 20 | 0 | | | [@elastic/protections-experience](https://github.com/orgs/elastic/teams/protections-experience) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 30 | 0 | 14 | 5 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 257 | 1 | 213 | 22 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [@elastic/kibana-localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 573 | 1 | 547 | 51 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 576 | 1 | 550 | 52 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds UI Actions service to Kibana | 145 | 0 | 103 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Extends UI Actions plugin with more functionality | 206 | 0 | 140 | 9 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains services reliant on the plugin lifecycle for the unified doc viewer component (see @kbn/unified-doc-viewer). | 13 | 0 | 10 | 3 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display. | 53 | 0 | 23 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 148 | 2 | 110 | 22 | | upgradeAssistant | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | @@ -205,7 +210,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the vislib visualizations. These are the classical area/line/bar, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 1 | 0 | 1 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 52 | 0 | 50 | 5 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 823 | 12 | 793 | 19 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 837 | 12 | 807 | 19 | | watcher | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -262,7 +267,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 8 | 0 | 1 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 20 | 0 | 19 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2 | 0 | 2 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 8 | 0 | 8 | 1 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 13 | 0 | 4 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 2 | @@ -356,7 +361,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 15 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 58 | 0 | 26 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 60 | 0 | 26 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 5 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | @@ -365,7 +370,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 25 | 1 | 24 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 111 | 1 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 351 | 1 | 5 | 1 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 352 | 1 | 5 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 89 | 0 | 61 | 10 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | @@ -375,9 +380,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 73 | 0 | 40 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 26 | 0 | 23 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 125 | 0 | 91 | 46 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 125 | 0 | 91 | 47 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 12 | 0 | 12 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 548 | 1 | 122 | 4 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 547 | 1 | 121 | 4 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 69 | 0 | 69 | 4 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 14 | 0 | 14 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 0 | 6 | 0 | @@ -396,8 +401,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 30 | 1 | 18 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 11 | 1 | 11 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 8 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 25 | 0 | 4 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 1 | 16 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 27 | 0 | 4 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 34 | 1 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 18 | 1 | 17 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 6 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 155 | 0 | 144 | 0 | @@ -408,6 +413,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 13 | 0 | 7 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 10 | 0 | 10 | 0 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 19 | 0 | 17 | 6 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 14 | 0 | 9 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 43 | 0 | @@ -415,7 +421,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 5 | 0 | 5 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 3 | 0 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 3 | 0 | 3 | 0 | +| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 24 | 0 | 14 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 4 | 0 | 4 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 7 | 0 | 7 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 7 | 0 | 7 | 0 | @@ -425,22 +431,22 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 101 | 0 | 85 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 15 | 0 | 9 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 29 | 2 | 25 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 59 | 0 | 34 | 3 | -| | [@elastic/docs](https://github.com/orgs/elastic/teams/docs) | - | 73 | 0 | 73 | 2 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 65 | 0 | 38 | 3 | +| | [@elastic/docs](https://github.com/orgs/elastic/teams/docs) | - | 74 | 0 | 74 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 39 | 0 | 26 | 5 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 19 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 35125 | 0 | 34718 | 0 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 13 | 0 | 5 | 0 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 84 | 0 | 64 | 5 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 8 | 0 | 7 | 0 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 94 | 0 | 74 | 6 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 43 | 0 | 30 | 7 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 27 | 0 | 14 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 3 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 255 | 1 | 195 | 15 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 12 | 0 | 12 | 0 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 259 | 1 | 199 | 15 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 19 | 0 | 19 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 39 | 0 | 39 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 65 | 0 | 65 | 1 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 0 | 52 | 1 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 36 | 0 | 14 | 3 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 20 | 0 | 16 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 0 | 0 | @@ -471,7 +477,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 8 | 0 | 8 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 1 | 1 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 10 | 0 | 10 | 1 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 20 | 0 | 5 | 0 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 26 | 0 | 7 | 0 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 45 | 0 | 0 | 0 | +| | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 127 | 0 | 127 | 0 | | | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 20 | 0 | 11 | 0 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 75 | 0 | 0 | 0 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 50 | 0 | 2 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 2 | 0 | 0 | 0 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 582 | 1 | 1 | 0 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 2 | 0 | 2 | 0 | @@ -480,7 +492,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 37 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 152 | 1 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 141 | 0 | 5 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 47 | 0 | 0 | 0 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 49 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 11 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 36 | 4 | 8 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 11 | 0 | 0 | 0 | @@ -497,7 +509,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 2 | 0 | 1 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 8 | 0 | 8 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 31 | 1 | 24 | 1 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 71 | 0 | 69 | 3 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 78 | 0 | 76 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 55 | 1 | 50 | 0 | | | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 13 | 0 | 13 | 3 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 45 | 0 | 45 | 10 | @@ -506,6 +518,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-performance-testing](https://github.com/orgs/elastic/teams/kibana-performance-testing) | - | 3 | 0 | 3 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | +| | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 160 | 0 | 17 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 13 | 0 | 7 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 22 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 2 | 0 | @@ -523,8 +536,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 16 | 0 | 16 | 1 | | | [@elastic/security-detections-response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 107 | 0 | 104 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | -| | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 65 | 0 | 65 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 16 | 0 | 8 | 0 | +| | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 68 | 0 | 68 | 0 | +| | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 1678 | 0 | 1678 | 0 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 15 | 0 | 8 | 0 | +| | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 14 | 0 | 14 | 6 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 50 | 0 | 47 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 29 | 0 | 23 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 2 | 0 | 0 | 0 | @@ -548,7 +563,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 31 | 0 | 29 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 54 | 0 | 51 | 1 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 31 | 0 | 30 | 1 | +| | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 1 | 0 | 1 | 0 | +| | [@elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 7 | 1 | +| | [@elastic/enterprise-search-frontend @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/enterprise-search-frontend ) | - | 1 | 0 | 1 | 0 | +| | [@elastic/security-solution @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/security-solution ) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 0 | 0 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 2 | 0 | 2 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 2 | 2 | @@ -590,29 +609,33 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 15 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 3 | 0 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 132 | 0 | 129 | 0 | +| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 135 | 0 | 132 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 20 | 0 | 12 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 102 | 2 | 65 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 4 | 0 | 2 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 41 | 2 | 21 | 0 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 24 | 0 | 16 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 5 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 289 | 4 | 242 | 12 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 137 | 5 | 105 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 15 | 0 | 14 | 0 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 22 | 0 | 21 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 72 | 0 | 55 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 39 | 0 | 25 | 1 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 86 | 0 | 86 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 49 | 0 | 35 | 0 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 47 | 0 | 38 | 0 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 52 | 0 | 43 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 7 | 0 | 6 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list and field stats which can be integrated into apps | 306 | 0 | 277 | 9 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the unified data table which can be integrated into apps | 92 | 0 | 42 | 1 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 10 | 0 | 7 | 6 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list and field stats which can be integrated into apps | 303 | 0 | 276 | 9 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 4 | 0 | 0 | 0 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 3 | 0 | 2 | 1 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 80 | 0 | 21 | 2 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 0 | 15 | 1 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 37 | 0 | 16 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 24 | 0 | 14 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 155 | 0 | 151 | 3 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 12 | 0 | 12 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 2 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 9f470e31cc507..f97101d749676 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.devdocs.json b/api_docs/profiling.devdocs.json index c6b864a1e228a..1b57fc843050b 100644 --- a/api_docs/profiling.devdocs.json +++ b/api_docs/profiling.devdocs.json @@ -185,39 +185,6 @@ "common": { "classes": [], "functions": [ - { - "parentPluginId": "profiling", - "id": "def-common.fromMapToRecord", - "type": "Function", - "tags": [], - "label": "fromMapToRecord", - "description": [], - "signature": [ - "(m: Map) => Record" - ], - "path": "x-pack/plugins/profiling/common/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "profiling", - "id": "def-common.fromMapToRecord.$1", - "type": "Object", - "tags": [], - "label": "m", - "description": [], - "signature": [ - "Map" - ], - "path": "x-pack/plugins/profiling/common/index.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "profiling", "id": "def-common.getRoutePaths", diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 5509809657a0d..c012de8ea332e 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 19 | 1 | 19 | 3 | +| 17 | 1 | 17 | 3 | ## Client diff --git a/api_docs/profiling_data_access.devdocs.json b/api_docs/profiling_data_access.devdocs.json new file mode 100644 index 0000000000000..f686b78fabe46 --- /dev/null +++ b/api_docs/profiling_data_access.devdocs.json @@ -0,0 +1,94 @@ +{ + "id": "profilingDataAccess", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "profilingDataAccess", + "id": "def-server.ProfilingConfig", + "type": "Type", + "tags": [], + "label": "ProfilingConfig", + "description": [], + "signature": [ + "{ readonly elasticsearch?: Readonly<{} & { username: string; hosts: string; password: string; }> | undefined; }" + ], + "path": "x-pack/plugins/profiling_data_access/server/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "profilingDataAccess", + "id": "def-server.ProfilingDataAccessPluginStart", + "type": "Type", + "tags": [], + "label": "ProfilingDataAccessPluginStart", + "description": [], + "signature": [ + "{ services: { fetchFlamechartData: ({ esClient, rangeFromMs, rangeToMs, kuery }: ", + "FetchFlamechartParams", + ") => Promise<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.BaseFlameGraph", + "text": "BaseFlameGraph" + }, + ">; fetchFunction: ({ esClient, rangeFromMs, rangeToMs, kuery, startIndex, endIndex, }: ", + "FetchFunctionsParams", + ") => Promise<", + { + "pluginId": "@kbn/profiling-utils", + "scope": "common", + "docId": "kibKbnProfilingUtilsPluginApi", + "section": "def-common.TopNFunctions", + "text": "TopNFunctions" + }, + ">; }; }" + ], + "path": "x-pack/plugins/profiling_data_access/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [], + "start": { + "parentPluginId": "profilingDataAccess", + "id": "def-server.ProfilingDataAccessPluginSetup", + "type": "Type", + "tags": [], + "label": "ProfilingDataAccessPluginSetup", + "description": [], + "signature": [ + "void" + ], + "path": "x-pack/plugins/profiling_data_access/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx new file mode 100644 index 0000000000000..6058d4dbec2f3 --- /dev/null +++ b/api_docs/profiling_data_access.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibProfilingDataAccessPluginApi +slug: /kibana-dev-docs/api/profilingDataAccess +title: "profilingDataAccess" +image: https://source.unsplash.com/400x175/?github +description: API docs for the profilingDataAccess plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] +--- +import profilingDataAccessObj from './profiling_data_access.devdocs.json'; + + + +Contact [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3 | 0 | 3 | 2 | + +## Server + +### Start + + +### Consts, variables and types + + diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 0395c6760bf09..ba37ba640c5c7 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index c0edc8e9b160a..4f5522de92782 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 8fa7b2ae18a68..bfada70d2c7e1 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 551f9ac0dac59..cbfb7658e933b 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index ca886020268e1..61d123e0bb163 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 71b50534dd919..48929bf0650a1 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 8e041a0cdeb19..65f13d7725a1e 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 38309fcc1bc66..c25fddc1576ea 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 32575de4b4341..98d48307b5db9 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 502f257494087..ffbde637f7bc3 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 4f1e99857d87a..f07b31928f1df 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 3fd7bf4ac83d4..a19ec711e953d 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index ef15fa1453062..5f0a297b21812 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index cc599286f3517..5d4a064bf1791 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 5ebed17999232..45c4a2685ac7c 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -1941,6 +1941,38 @@ ], "returnComment": [] }, + { + "parentPluginId": "securitySolution", + "id": "def-public.PluginStart.setDashboardsLandingCallout", + "type": "Function", + "tags": [], + "label": "setDashboardsLandingCallout", + "description": [], + "signature": [ + "(dashboardsLandingCallout: React.ComponentType<{}>) => void" + ], + "path": "x-pack/plugins/security_solution/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "securitySolution", + "id": "def-public.PluginStart.setDashboardsLandingCallout.$1", + "type": "CompoundType", + "tags": [], + "label": "dashboardsLandingCallout", + "description": [], + "signature": [ + "React.ComponentType<{}>" + ], + "path": "x-pack/plugins/security_solution/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "securitySolution", "id": "def-public.PluginStart.getBreadcrumbsNav$", @@ -2792,22 +2824,16 @@ "children": [ { "parentPluginId": "securitySolution", - "id": "def-server.SecuritySolutionPluginSetup.setAppFeatures", + "id": "def-server.SecuritySolutionPluginSetup.setAppFeaturesConfigurator", "type": "Function", "tags": [], - "label": "setAppFeatures", + "label": "setAppFeaturesConfigurator", "description": [ - "\nSets the app features that are available to the Security Solution" + "\nSets the configurations for app features that are available to the Security Solution" ], "signature": [ - "(appFeatureKeys: ", - { - "pluginId": "securitySolution", - "scope": "common", - "docId": "kibSecuritySolutionPluginApi", - "section": "def-common.AppFeatureKeys", - "text": "AppFeatureKeys" - }, + "(configurator: ", + "AppFeaturesConfigurator", ") => void" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", @@ -2817,22 +2843,15 @@ "children": [ { "parentPluginId": "securitySolution", - "id": "def-server.SecuritySolutionPluginSetup.setAppFeatures.$1", - "type": "Array", + "id": "def-server.SecuritySolutionPluginSetup.setAppFeaturesConfigurator.$1", + "type": "Object", "tags": [], - "label": "appFeatureKeys", + "label": "configurator", "description": [], "signature": [ - { - "pluginId": "securitySolution", - "scope": "common", - "docId": "kibSecuritySolutionPluginApi", - "section": "def-common.AppFeatureKey", - "text": "AppFeatureKey" - }, - "[]" + "AppFeaturesConfigurator" ], - "path": "x-pack/plugins/security_solution/server/lib/app_features/app_features.ts", + "path": "x-pack/plugins/security_solution/server/lib/app_features_service/app_features_service.ts", "deprecated": false, "trackAdoption": false } @@ -2938,47 +2957,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "securitySolution", - "id": "def-common.AppFeatureKey", - "type": "Type", - "tags": [], - "label": "AppFeatureKey", - "description": [], - "signature": [ - "AppFeatureSecurityKey", - " | ", - "AppFeatureCasesKey", - " | ", - "AppFeatureAssistantKey" - ], - "path": "x-pack/plugins/security_solution/common/types/app_features.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "securitySolution", - "id": "def-common.AppFeatureKeys", - "type": "Type", - "tags": [], - "label": "AppFeatureKeys", - "description": [], - "signature": [ - { - "pluginId": "securitySolution", - "scope": "common", - "docId": "kibSecuritySolutionPluginApi", - "section": "def-common.AppFeatureKey", - "text": "AppFeatureKey" - }, - "[]" - ], - "path": "x-pack/plugins/security_solution/common/types/app_features.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "securitySolution", "id": "def-common.CASES_FEATURE_ID", @@ -3058,27 +3036,6 @@ } ], "objects": [ - { - "parentPluginId": "securitySolution", - "id": "def-common.ALL_APP_FEATURE_KEYS", - "type": "Object", - "tags": [], - "label": "ALL_APP_FEATURE_KEYS", - "description": [], - "signature": [ - "readonly (", - "AppFeatureSecurityKey", - " | ", - "AppFeatureCasesKey", - ".casesConnectors | ", - "AppFeatureAssistantKey", - ".assistant)[]" - ], - "path": "x-pack/plugins/security_solution/common/types/app_features.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "securitySolution", "id": "def-common.allowedExperimentalValues", @@ -3095,62 +3052,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "securitySolution", - "id": "def-common.AppFeatureKey", - "type": "Object", - "tags": [], - "label": "AppFeatureKey", - "description": [], - "path": "x-pack/plugins/security_solution/common/types/app_features.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "securitySolution", - "id": "def-common.AppFeatureKey.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/security_solution/common/types/app_features.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "securitySolution", - "id": "def-common.AppFeatureKey.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/security_solution/common/types/app_features.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "securitySolution", - "id": "def-common.AppFeatureKey.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/security_solution/common/types/app_features.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ] } diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 818d3d6a75049..3af62e5ba250b 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 177 | 3 | 111 | 34 | +| 172 | 0 | 106 | 32 | ## Client diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index a30f8948a2ffd..c9ad7fa08de66 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index 9cccef4b5e0f2..07899c9d9990a 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.devdocs.json b/api_docs/serverless.devdocs.json index 4a9258ba58353..6275b9f9b0688 100644 --- a/api_docs/serverless.devdocs.json +++ b/api_docs/serverless.devdocs.json @@ -274,7 +274,40 @@ "path": "x-pack/plugins/serverless/server/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "serverless", + "id": "def-server.ServerlessPluginSetup.setupProjectSettings", + "type": "Function", + "tags": [], + "label": "setupProjectSettings", + "description": [], + "signature": [ + "(keys: string[]) => void" + ], + "path": "x-pack/plugins/serverless/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "serverless", + "id": "def-server.ServerlessPluginSetup.setupProjectSettings.$1", + "type": "Array", + "tags": [], + "label": "keys", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/serverless/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], "lifecycle": "setup", "initialIsOpen": true }, diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 7b539f150faca..68e30aebcbcea 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 17 | 0 | 16 | 0 | +| 19 | 0 | 18 | 0 | ## Client diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index f32c5b04369ec..fcc85d5205bdf 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index ca81e10f02ea6..f3326207ffc6b 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 8bef3e925ae36..e9d053e28735f 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index b238a21bd576c..4eaa16b6fb0d7 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 2f13d3c89fe0d..9f53c276f7025 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.devdocs.json b/api_docs/spaces.devdocs.json index 8ce91315daa63..2b4ff0b4dd02d 100644 --- a/api_docs/spaces.devdocs.json +++ b/api_docs/spaces.devdocs.json @@ -2832,7 +2832,7 @@ "\nSetup contract for the Spaces plugin." ], "signature": [ - "{}" + "{ hasOnlyDefaultSpace: boolean; }" ], "path": "x-pack/plugins/spaces/public/plugin.tsx", "deprecated": false, @@ -2907,6 +2907,19 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "spaces", + "id": "def-public.SpacesApi.hasOnlyDefaultSpace", + "type": "boolean", + "tags": [], + "label": "hasOnlyDefaultSpace", + "description": [ + "\nDetermines whether Kibana supports multiple spaces or only the default space.\n\nWhen `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden." + ], + "path": "x-pack/plugins/spaces/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "spaces", "id": "def-public.SpacesApi.ui", @@ -4359,6 +4372,23 @@ "path": "x-pack/plugins/spaces/server/plugin.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "spaces", + "id": "def-server.SpacesPluginSetup.hasOnlyDefaultSpace$", + "type": "Object", + "tags": [], + "label": "hasOnlyDefaultSpace$", + "description": [ + "\nDetermines whether Kibana supports multiple spaces or only the default space.\n\nWhen `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden." + ], + "signature": [ + "Observable", + "" + ], + "path": "x-pack/plugins/spaces/server/plugin.ts", + "deprecated": false, + "trackAdoption": false } ], "lifecycle": "setup", @@ -4398,6 +4428,23 @@ "path": "x-pack/plugins/spaces/server/plugin.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "spaces", + "id": "def-server.SpacesPluginStart.hasOnlyDefaultSpace$", + "type": "Object", + "tags": [], + "label": "hasOnlyDefaultSpace$", + "description": [ + "\nDetermines whether Kibana supports multiple spaces or only the default space.\n\nWhen `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden." + ], + "signature": [ + "Observable", + "" + ], + "path": "x-pack/plugins/spaces/server/plugin.ts", + "deprecated": false, + "trackAdoption": false } ], "lifecycle": "start", diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index a21942465ec24..6f456c9160f21 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 253 | 0 | 65 | 0 | +| 256 | 0 | 65 | 0 | ## Client diff --git a/api_docs/stack_alerts.devdocs.json b/api_docs/stack_alerts.devdocs.json index 65ec1191d667f..cced9a721c32e 100644 --- a/api_docs/stack_alerts.devdocs.json +++ b/api_docs/stack_alerts.devdocs.json @@ -210,9 +210,201 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "stackAlerts", + "id": "def-common.rowToDocument", + "type": "Function", + "tags": [], + "label": "rowToDocument", + "description": [], + "signature": [ + "(columns: EsqlResultColumn[], row: EsqlResultRow) => EsqlDocument" + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "stackAlerts", + "id": "def-common.rowToDocument.$1", + "type": "Array", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + "EsqlResultColumn[]" + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "stackAlerts", + "id": "def-common.rowToDocument.$2", + "type": "Array", + "tags": [], + "label": "row", + "description": [], + "signature": [ + "EsqlResultRow" + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "stackAlerts", + "id": "def-common.toEsQueryHits", + "type": "Function", + "tags": [], + "label": "toEsQueryHits", + "description": [], + "signature": [ + "(results: ", + { + "pluginId": "stackAlerts", + "scope": "common", + "docId": "kibStackAlertsPluginApi", + "section": "def-common.EsqlTable", + "text": "EsqlTable" + }, + ") => { hits: EsqlHit[]; total: number; }" + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "stackAlerts", + "id": "def-common.toEsQueryHits.$1", + "type": "Object", + "tags": [], + "label": "results", + "description": [], + "signature": [ + { + "pluginId": "stackAlerts", + "scope": "common", + "docId": "kibStackAlertsPluginApi", + "section": "def-common.EsqlTable", + "text": "EsqlTable" + } + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "stackAlerts", + "id": "def-common.transformDatatableToEsqlTable", + "type": "Function", + "tags": [], + "label": "transformDatatableToEsqlTable", + "description": [], + "signature": [ + "(results: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + ") => ", + { + "pluginId": "stackAlerts", + "scope": "common", + "docId": "kibStackAlertsPluginApi", + "section": "def-common.EsqlTable", + "text": "EsqlTable" + } + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "stackAlerts", + "id": "def-common.transformDatatableToEsqlTable.$1", + "type": "Object", + "tags": [], + "label": "results", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + } + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "stackAlerts", + "id": "def-common.EsqlTable", + "type": "Interface", + "tags": [], + "label": "EsqlTable", + "description": [], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "stackAlerts", + "id": "def-common.EsqlTable.columns", + "type": "Array", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + "EsqlResultColumn[]" + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "stackAlerts", + "id": "def-common.EsqlTable.values", + "type": "Array", + "tags": [], + "label": "values", + "description": [], + "signature": [ + "EsqlResultRow[]" + ], + "path": "x-pack/plugins/stack_alerts/common/esql_query_utils.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], - "interfaces": [], "enums": [], "misc": [ { diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 69ab8f64e9b9e..8287050a59733 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 14 | 0 | 14 | 3 | +| 24 | 0 | 24 | 3 | ## Client @@ -44,6 +44,9 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o ### Functions +### Interfaces + + ### Consts, variables and types diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index ccc3cf1c467a9..69c7ab28e81cf 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index c71b61035fcf5..5ddcda0dd8657 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 9b7fa86c76505..f9aed1362c7ea 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index c5ec4b1709fa2..edb25486c1c06 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 2ff77cf44e049..df129a2ff1a14 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 1a21f3a4fe16a..2facb2867f53d 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.devdocs.json b/api_docs/text_based_languages.devdocs.json index e5f1b90b12a78..c44e1664a3fe1 100644 --- a/api_docs/text_based_languages.devdocs.json +++ b/api_docs/text_based_languages.devdocs.json @@ -210,6 +210,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "textBasedLanguages", + "id": "def-public.TextBasedLanguagesEditorProps.warning", + "type": "string", + "tags": [], + "label": "warning", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "textBasedLanguages", "id": "def-public.TextBasedLanguagesEditorProps.isDisabled", @@ -251,6 +265,34 @@ "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "textBasedLanguages", + "id": "def-public.TextBasedLanguagesEditorProps.hideMinimizeButton", + "type": "CompoundType", + "tags": [], + "label": "hideMinimizeButton", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "textBasedLanguages", + "id": "def-public.TextBasedLanguagesEditorProps.hideRunQueryText", + "type": "CompoundType", + "tags": [], + "label": "hideRunQueryText", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index 7dbf38fb09a3d..546edabec4a1b 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 17 | 0 | 17 | 0 | +| 20 | 0 | 20 | 0 | ## Client diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 813515dde9f41..cd5373398fdc9 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index a6c77777588d2..789d5bfbc93f4 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -4725,6 +4725,14 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/flyout/right/context.tsx" @@ -4749,6 +4757,14 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_browser_fields.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_browser_fields.ts" + }, { "plugin": "@kbn/securitysolution-data-table", "path": "x-pack/packages/security-solution/data_table/mock/mock_source.ts" diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 87e09f29e8900..e045494640abb 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 4e9d1032e3cf0..df9ed8edbc01b 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index 9d84568f429d3..2bbfb1290b896 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -803,6 +803,47 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.getTimeOptions", + "type": "Function", + "tags": [], + "label": "getTimeOptions", + "description": [], + "signature": [ + "(unitSize: number) => { text: string; value: ", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.TIME_UNITS", + "text": "TIME_UNITS" + }, + "; }[]" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/get_time_options.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.getTimeOptions.$1", + "type": "number", + "tags": [], + "label": "unitSize", + "description": [], + "signature": [ + "number" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/get_time_options.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.getTimeUnitLabel", @@ -1176,13 +1217,7 @@ "({\n http,\n searchText,\n typesFilter,\n actionTypesFilter,\n ruleExecutionStatusesFilter,\n ruleStatusesFilter,\n tagsFilter,\n}: ", "LoadRuleAggregationsProps", ") => Promise<", - { - "pluginId": "alerting", - "scope": "common", - "docId": "kibAlertingPluginApi", - "section": "def-common.RuleAggregationFormattedResult", - "text": "RuleAggregationFormattedResult" - }, + "AggregateRulesResponse", ">" ], "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts", @@ -5688,6 +5723,26 @@ "path": "x-pack/plugins/triggers_actions_ui/public/application/app.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUiServices.expressions", + "type": "Object", + "tags": [], + "label": "expressions", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "public", + "docId": "kibExpressionsPluginApi", + "section": "def-public.ExpressionsStart", + "text": "ExpressionsStart" + } + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/app.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -8958,7 +9013,7 @@ "label": "isGroupAggregation", "description": [], "signature": [ - "(termField?: string | undefined) => boolean" + "(termField?: string | string[] | undefined) => boolean" ], "path": "x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts", "deprecated": false, @@ -8967,12 +9022,12 @@ { "parentPluginId": "triggersActionsUi", "id": "def-common.isGroupAggregation.$1", - "type": "string", + "type": "CompoundType", "tags": [], "label": "termField", "description": [], "signature": [ - "string | undefined" + "string | string[] | undefined" ], "path": "x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts", "deprecated": false, @@ -9195,12 +9250,12 @@ { "parentPluginId": "triggersActionsUi", "id": "def-common.BuildAggregationOpts.termField", - "type": "string", + "type": "CompoundType", "tags": [], "label": "termField", "description": [], "signature": [ - "string | undefined" + "string | string[] | undefined" ], "path": "x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts", "deprecated": false, diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 0912e894a9a00..1097cbe608606 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 573 | 1 | 547 | 51 | +| 576 | 1 | 550 | 52 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 842b9891be752..1cbb5fba59fb8 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 8eb65f4aad6e7..d9d66d5e74ee3 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.devdocs.json b/api_docs/unified_doc_viewer.devdocs.json new file mode 100644 index 0000000000000..eb3609aa71e6d --- /dev/null +++ b/api_docs/unified_doc_viewer.devdocs.json @@ -0,0 +1,263 @@ +{ + "id": "unifiedDocViewer", + "client": { + "classes": [], + "functions": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.JsonCodeEditor", + "type": "Function", + "tags": [], + "label": "JsonCodeEditor", + "description": [], + "signature": [ + "React.ForwardRefExoticComponent<", + "JsonCodeEditorProps", + " & React.RefAttributes<{}>>" + ], + "path": "src/plugins/unified_doc_viewer/public/index.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.JsonCodeEditor.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewer", + "type": "Function", + "tags": [], + "label": "UnifiedDocViewer", + "description": [], + "signature": [ + "React.ForwardRefExoticComponent<", + "DocViewRenderProps", + " & React.RefAttributes<{}>>" + ], + "path": "src/plugins/unified_doc_viewer/public/index.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewer.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.useEsDocSearch", + "type": "Function", + "tags": [], + "label": "useEsDocSearch", + "description": [ + "\nCustom react hook for querying a single doc in ElasticSearch" + ], + "signature": [ + "({\n id,\n index,\n dataView,\n requestSource,\n textBasedHits,\n}: ", + "EsDocSearchProps", + ") => [", + { + "pluginId": "@kbn/unified-doc-viewer", + "scope": "common", + "docId": "kibKbnUnifiedDocViewerPluginApi", + "section": "def-common.ElasticRequestState", + "text": "ElasticRequestState" + }, + ", ", + "DataTableRecord", + " | null, () => void]" + ], + "path": "src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.useEsDocSearch.$1", + "type": "Object", + "tags": [], + "label": "{\n id,\n index,\n dataView,\n requestSource,\n textBasedHits,\n}", + "description": [], + "signature": [ + "EsDocSearchProps" + ], + "path": "src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.useUnifiedDocViewerServices", + "type": "Function", + "tags": [], + "label": "useUnifiedDocViewerServices", + "description": [], + "signature": [ + "() => ", + "UnifiedDocViewerServices" + ], + "path": "src/plugins/unified_doc_viewer/public/hooks/use_doc_viewer_services.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewerSetup", + "type": "Interface", + "tags": [], + "label": "UnifiedDocViewerSetup", + "description": [], + "path": "src/plugins/unified_doc_viewer/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewerSetup.addDocView", + "type": "Function", + "tags": [], + "label": "addDocView", + "description": [], + "signature": [ + "(docViewRaw: ", + "DocViewInput", + " | ", + "DocViewInputFn", + ") => void" + ], + "path": "src/plugins/unified_doc_viewer/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewerSetup.addDocView.$1", + "type": "CompoundType", + "tags": [], + "label": "docViewRaw", + "description": [], + "signature": [ + "DocViewInput", + " | ", + "DocViewInputFn" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewerStart", + "type": "Interface", + "tags": [], + "label": "UnifiedDocViewerStart", + "description": [], + "path": "src/plugins/unified_doc_viewer/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewerStart.getDocViews", + "type": "Function", + "tags": [], + "label": "getDocViews", + "description": [], + "signature": [ + "(hit: ", + "DataTableRecord", + ") => ", + "DocView", + "[]" + ], + "path": "src/plugins/unified_doc_viewer/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedDocViewer", + "id": "def-public.UnifiedDocViewerStart.getDocViews.$1", + "type": "Object", + "tags": [], + "label": "hit", + "description": [], + "signature": [ + "DataTableRecord" + ], + "path": "packages/kbn-unified-doc-viewer/src/services/doc_views_registry.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx new file mode 100644 index 0000000000000..9e53e32a5431c --- /dev/null +++ b/api_docs/unified_doc_viewer.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibUnifiedDocViewerPluginApi +slug: /kibana-dev-docs/api/unifiedDocViewer +title: "unifiedDocViewer" +image: https://source.unsplash.com/400x175/?github +description: API docs for the unifiedDocViewer plugin +date: 2023-09-21 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] +--- +import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; + +This plugin contains services reliant on the plugin lifecycle for the unified doc viewer component (see @kbn/unified-doc-viewer). + +Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 13 | 0 | 10 | 3 | + +## Client + +### Setup + + +### Start + + +### Functions + + diff --git a/api_docs/unified_histogram.devdocs.json b/api_docs/unified_histogram.devdocs.json index afa74e73e7990..4288127fabc2b 100644 --- a/api_docs/unified_histogram.devdocs.json +++ b/api_docs/unified_histogram.devdocs.json @@ -466,7 +466,7 @@ "section": "def-common.RequestAdapter", "text": "RequestAdapter" }, - " | undefined; } & Pick<", + " | undefined; isChartLoading?: boolean | undefined; } & Pick<", "UnifiedHistogramLayoutProps", ", \"children\" | \"className\" | \"query\" | \"filters\" | \"columns\" | \"onBrushEnd\" | \"disabledActions\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"resizeRef\" | \"appendHitsCounter\" | \"onFilter\" | \"withDefaultActions\"> & React.RefAttributes<", { @@ -1176,7 +1176,7 @@ "section": "def-common.RequestAdapter", "text": "RequestAdapter" }, - " | undefined; } & Pick<", + " | undefined; isChartLoading?: boolean | undefined; } & Pick<", "UnifiedHistogramLayoutProps", ", \"children\" | \"className\" | \"query\" | \"filters\" | \"columns\" | \"onBrushEnd\" | \"disabledActions\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"resizeRef\" | \"appendHitsCounter\" | \"onFilter\" | \"withDefaultActions\">" ], diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index e3d016be77067..14e7d09cda721 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index a9e828da07709..b02f9ce5f11be 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -603,7 +603,7 @@ }, "[] | undefined; refreshInterval?: number | undefined; iconType?: ", "IconType", - " | undefined; dataTestSubj?: string | undefined; timeHistory?: ", + " | undefined; showQueryInput?: boolean | undefined; dataTestSubj?: string | undefined; timeHistory?: ", { "pluginId": "data", "scope": "public", @@ -611,7 +611,7 @@ "section": "def-public.TimeHistoryContract", "text": "TimeHistoryContract" }, - " | undefined; customSubmitButton?: React.ReactNode; dataViewPickerOverride?: React.ReactNode; screenTitle?: string | undefined; showQueryMenu?: boolean | undefined; showQueryInput?: boolean | undefined; showFilterBar?: boolean | undefined; showDatePicker?: boolean | undefined; showAutoRefreshOnly?: boolean | undefined; filtersForSuggestions?: ", + " | undefined; customSubmitButton?: React.ReactNode; dataViewPickerOverride?: React.ReactNode; screenTitle?: string | undefined; showQueryMenu?: boolean | undefined; showFilterBar?: boolean | undefined; showDatePicker?: boolean | undefined; showAutoRefreshOnly?: boolean | undefined; filtersForSuggestions?: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -701,7 +701,7 @@ "section": "def-public.DataViewPickerProps", "text": "DataViewPickerProps" }, - " | undefined; textBasedLanguageModeErrors?: Error[] | undefined; onTextBasedSavedAndExit?: (({ onSave }: ", + " | undefined; textBasedLanguageModeErrors?: Error[] | undefined; textBasedLanguageModeWarning?: string | undefined; onTextBasedSavedAndExit?: (({ onSave }: ", "OnSaveTextLanguageQueryProps", ") => void) | undefined; showSubmitButton?: boolean | undefined; submitButtonStyle?: \"full\" | \"auto\" | \"iconOnly\" | undefined; suggestionsSize?: ", "SuggestionsListSize", @@ -2262,9 +2262,9 @@ "Omit", ", \"options\" | \"selectedOptions\" | \"onChange\" | \"isLoading\" | \"onSearchChange\">, \"placeholder\"> & Required, \"options\" | \"selectedOptions\" | \"onChange\" | \"onSearchChange\">, \"placeholder\"> & Required, \"options\" | \"selectedOptions\" | \"onChange\" | \"isLoading\" | \"onSearchChange\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; onNoIndexPatterns?: (() => void) | undefined; }" + ", \"options\" | \"selectedOptions\" | \"onChange\" | \"onSearchChange\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; onNoIndexPatterns?: (() => void) | undefined; }" ], "path": "src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx", "deprecated": false, diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 4f7b41bf5d445..7a4d624baa26f 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 5bc459d37a6ec..f6bd35705d3d3 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index b8f3bb8269554..ad87dc1ae461f 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index fafe162dcb3ef..a608afc4f8085 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 09ba51bc1460c..d709c17c5879e 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index c411f945b55b7..6cd6a2755d511 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index ef59d45846e50..d9876b235a598 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index b8a48e2212386..8fc1446a1081c 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index dd26e237355b1..4b2db22360e64 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 361d2c8148583..0ddeff74aafca 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 53e70f8e9260b..defb27e85ef61 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index b079a98811c17..017a8b07f6406 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 7bcb20b8cd8f4..f847aee632b62 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 1fb6545a65053..3cb1db646e81f 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 6db0c25e347eb..1561f33597aa2 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 8cc91f2fd5a9f..043982c89fc8d 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index 02291d78d807b..c2fc23184160b 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -2249,7 +2249,7 @@ "section": "def-common.DatatableRow", "text": "DatatableRow" }, - "[]; }" + "[]; warning?: string | undefined; }" ], "path": "src/plugins/visualizations/common/utils/prepare_log_table.ts", "deprecated": false, @@ -3352,6 +3352,34 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-public.SerializableAttributes", + "type": "Interface", + "tags": [], + "label": "SerializableAttributes", + "description": [], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.SerializableAttributes.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: unknown", + "description": [], + "signature": [ + "[key: string]: unknown" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-public.SerializedVis", @@ -5518,6 +5546,323 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient", + "type": "Interface", + "tags": [], + "label": "VisualizationClient", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "public", + "docId": "kibVisualizationsPluginApi", + "section": "def-public.VisualizationClient", + "text": "VisualizationClient" + }, + "" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [], + "signature": [ + "(id: string) => Promise<{ item: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "; meta: { outcome: \"conflict\" | \"exactMatch\" | \"aliasMatch\"; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; }; }>" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.get.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "(visualization: Omit<", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + ">, \"contentTypeId\">) => Promise<{ item: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "; }>" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.create.$1", + "type": "Object", + "tags": [], + "label": "visualization", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + ">, \"contentTypeId\">" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [], + "signature": [ + "(visualization: Omit<", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + ", Pick<", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SavedObjectUpdateOptions", + "text": "SavedObjectUpdateOptions" + }, + ", \"references\">>, \"contentTypeId\">) => Promise<{ item: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadataPartial", + "text": "SOWithMetadataPartial" + }, + "; }>" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.update.$1", + "type": "Object", + "tags": [], + "label": "visualization", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + ", Pick<", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SavedObjectUpdateOptions", + "text": "SavedObjectUpdateOptions" + }, + ", \"references\">>, \"contentTypeId\">" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteResult", + "text": "DeleteResult" + }, + ">" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.delete.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [], + "signature": [ + "(query: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchQuery", + "text": "SearchQuery" + }, + ", options?: object | undefined) => Promise<{ hits: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "[]; pagination: { total: number; cursor?: string | undefined; }; }>" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.search.$1", + "type": "Object", + "tags": [], + "label": "query", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchQuery", + "text": "SearchQuery" + } + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "visualizations", + "id": "def-public.VisualizationClient.search.$2", + "type": "Uncategorized", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "object | undefined" + ], + "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-public.VisualizationListItem", @@ -6382,15 +6727,7 @@ "section": "def-common.AggregateQuery", "text": "AggregateQuery" }, - " | undefined>; updateInput: (changes: Partial<", - { - "pluginId": "visualizations", - "scope": "public", - "docId": "kibVisualizationsPluginApi", - "section": "def-public.VisualizeInput", - "text": "VisualizeInput" - }, - ">) => void; openInspector: () => ", + " | undefined>; openInspector: () => ", { "pluginId": "@kbn/core-mount-utils-browser", "scope": "common", @@ -6586,7 +6923,15 @@ "section": "def-public.ContainerOutput", "text": "ContainerOutput" }, - ">; updateOutput: (outputChanges: Partial<", + ">; updateInput: (changes: Partial<", + { + "pluginId": "visualizations", + "scope": "public", + "docId": "kibVisualizationsPluginApi", + "section": "def-public.VisualizeInput", + "text": "VisualizeInput" + }, + ">) => void; updateOutput: (outputChanges: Partial<", "VisualizeOutput", ">) => void; }" ], @@ -8133,7 +8478,7 @@ "section": "def-common.DatatableRow", "text": "DatatableRow" }, - "[]; }" + "[]; warning?: string | undefined; }" ], "path": "src/plugins/visualizations/common/utils/prepare_log_table.ts", "deprecated": false, diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 6456967cfc8c7..d3c74ddb82506 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2023-08-31 +date: 2023-09-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 823 | 12 | 793 | 19 | +| 837 | 12 | 807 | 19 | ## Client diff --git a/catalog-info.yaml b/catalog-info.yaml index e2e4402873718..00637fb1a039b 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -151,11 +151,12 @@ spec: build_pull_request_forks: false build_tags: true # https://regex101.com/r/tY52jo/1 - filter_condition: 'build.tag =~ "/^deploy@\d+\$/"' + filter_condition: 'build.tag =~ /^deploy@\d+$/' filter_enabled: true - trigger_mode: none teams: kibana-operations: access_level: MANAGE_BUILD_AND_READ + kibana-tech-leads: + access_level: MANAGE_BUILD_AND_READ everyone: access_level: READ_ONLY diff --git a/config/serverless.es.yml b/config/serverless.es.yml index 7a1da782855bd..7e4e5547efb1f 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -9,12 +9,8 @@ xpack.observability.enabled: false xpack.securitySolution.enabled: false xpack.serverless.observability.enabled: false xpack.uptime.enabled: false -enterpriseSearch.enabled: true -enterpriseSearch.canDeployEntSearch: false -enterpriseSearch.hasConnectors: true -enterpriseSearch.hasNativeConnectors: false -enterpriseSearch.hasWebCrawler: false -enterpriseSearch.ui.enabled: false +xpack.legacy_uptime.enabled: false +enterpriseSearch.enabled: false monitoring.ui.enabled: false xpack.fleet.enabled: false @@ -35,3 +31,11 @@ xpack.actions.enabledActionTypes: ['.email', '.index', '.slack', '.jira', '.webh # Customize empty page state for analytics apps no_data_page.analyticsNoDataPageFlavor: 'serverless_search' + +# Disable Dev tools +xpack.grokdebugger.enabled: false +xpack.painless_lab.enabled: false + +xpack.ml.ad.enabled: false +xpack.ml.dfa.enabled: false +xpack.ml.nlp.enabled: true diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index 5e6b9a58cbc35..d89f5c5e76cd7 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -3,7 +3,10 @@ ## Disable plugins enterpriseSearch.enabled: false xpack.cloudSecurityPosture.enabled: false +xpack.infra.enabled: false xpack.securitySolution.enabled: false +xpack.uptime.enabled: false +xpack.legacy_uptime.enabled: false ## Enable the Serverless Observability plugin xpack.serverless.observability.enabled: true @@ -24,7 +27,11 @@ xpack.fleet.agentIdVerificationEnabled: false xpack.apm.serverlessOnboarding: true # Fleet specific configuration -xpack.fleet.internal.capabilities: ['apm', 'uptime', 'observability'] +xpack.fleet.internal.registry.capabilities: ['apm', 'observability'] +xpack.fleet.internal.registry.spec.max: '3.0' +# Disabled until packages implement the new spec https://github.com/elastic/kibana/issues/166742 +# xpack.fleet.internal.registry.kibanaVersionCheckEnabled: false +# xpack.fleet.internal.registry.spec.min: '3.0' ## Required for force installation of APM Package xpack.fleet.packages: @@ -41,3 +48,7 @@ xpack.apm.featureFlags.storageExplorerAvailable: false # Specify in telemetry the project type telemetry.labels.serverless: observability + +xpack.ml.ad.enabled: true +xpack.ml.dfa.enabled: false +xpack.ml.nlp.enabled: false diff --git a/config/serverless.security.yml b/config/serverless.security.yml index c9df8e9731d55..7a186ac9e70b1 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -3,8 +3,10 @@ ## Disable plugins enterpriseSearch.enabled: false xpack.apm.enabled: false +xpack.infra.enabled: false xpack.observability.enabled: false xpack.uptime.enabled: false +xpack.legacy_uptime.enabled: false ## Enable the Security Solution Serverless plugin xpack.securitySolutionServerless.enabled: true @@ -24,8 +26,16 @@ xpack.serverless.plugin.developer.projectSwitcher.currentType: 'security' telemetry.labels.serverless: security # Fleet specific configuration -xpack.fleet.internal.capabilities: ['security'] +xpack.fleet.internal.registry.capabilities: ['security'] +xpack.fleet.internal.registry.spec.max: '3.0' +# Disabled until packages implement the new spec https://github.com/elastic/kibana/issues/166742 +# xpack.fleet.internal.registry.kibanaVersionCheckEnabled: false +# xpack.fleet.internal.registry.spec.min: '3.0' # Serverless security specific options xpack.securitySolution.enableExperimental: - discoverInTimeline + +xpack.ml.ad.enabled: true +xpack.ml.dfa.enabled: true +xpack.ml.nlp.enabled: false diff --git a/config/serverless.yml b/config/serverless.yml index 31f51eeb59ee5..33eaec2b22b6a 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -6,9 +6,10 @@ xpack.fleet.internal.fleetServerStandalone: true xpack.fleet.internal.disableILMPolicies: true xpack.fleet.internal.disableProxies: true xpack.fleet.internal.activeAgentsSoftLimit: 25000 +xpack.fleet.internal.onlyAllowAgentUpgradeToKnownVersions: true # Cloud links -xpack.cloud.base_url: "https://cloud.elastic.co" +xpack.cloud.base_url: 'https://cloud.elastic.co' # Enable ZDT migration algorithm migrations.algorithm: zdt @@ -85,7 +86,7 @@ xpack.spaces.allowFeatureVisibility: false console.autocompleteDefinitions.endpointsAvailability: serverless # Allow authentication via the Elasticsearch JWT realm with the `shared_secret` client authentication type. -elasticsearch.requestHeadersWhitelist: ["authorization", "es-client-authentication"] +elasticsearch.requestHeadersWhitelist: ['authorization', 'es-client-authentication'] # Limit maxSockets to 800 as we do in ESS, which improves reliability under high loads. elasticsearch.maxSockets: 800 @@ -117,7 +118,14 @@ xpack.alerting.rules.run.ruleTypeOverrides: - id: siem.indicatorRule timeout: 1m xpack.alerting.rules.minimumScheduleInterval.enforce: true +xpack.alerting.rules.maxScheduledPerMinute: 400 xpack.actions.run.maxAttempts: 10 +xpack.actions.queued.max: 10000 + +# Disables ESQL in advanced settings (hides it from the UI) +uiSettings: + overrides: + discover:enableESQL: false # Task Manager xpack.task_manager.allow_reading_invalid_state: false diff --git a/dev_docs/contributing/code_walkthrough.mdx b/dev_docs/contributing/code_walkthrough.mdx index 617b54e519f13..139ac4df93070 100644 --- a/dev_docs/contributing/code_walkthrough.mdx +++ b/dev_docs/contributing/code_walkthrough.mdx @@ -86,7 +86,7 @@ This code primarily belongs to the Core team and contains the plugin infrastruct ### [src/dev](https://github.com/elastic/kibana/tree/main/src/dev) -Maintained by the Operations team, this code contains build and development tooling related code. This folder existed before `packages`, so contains mostly older code that hasn't been migrated to packages. Prefer creating a `package` if possible. Can be ignored for the most part if you are not on the Ops team. Prefer +Maintained by the Operations team, this code contains build and development tooling related code. This folder existed before `packages`, so contains mostly older code that hasn't been migrated to packages. Prefer creating a `package` if possible. Can be ignored for the most part if you are not on the Ops team. ### [src/plugins](https://github.com/elastic/kibana/tree/main/src/plugins) diff --git a/dev_docs/contributing/standards.mdx b/dev_docs/contributing/standards.mdx index aba29e5fab2f2..80df5f4752131 100644 --- a/dev_docs/contributing/standards.mdx +++ b/dev_docs/contributing/standards.mdx @@ -91,11 +91,12 @@ Every public API should have a release tag specified at the top of it’s docume #### Release tags | Type | Description | Documentation | Asciidoc Tag | -| Undocumented | Every public API should be documented, but if it isn’t, we make no guarantees about it. These need to be eliminated and should become internal or documented. | -| Experimental | A public API that may break or be removed at any time. | experimental[] | -| Beta | A public API that we make a best effort not to break or remove. However, there are no guarantees. | beta[] | -| Stable | No breaking changes outside of a Major\* | stable[] | -| Deprecated | Do not use, will be removed. | deprecated[] | +| -----| ------------| ------------- | ------------ | +| Undocumented | Every public API should be documented, but if it isn’t, we make no guarantees about it. These need to be eliminated and should become internal or documented. | | | +| Experimental | A public API that may break or be removed at any time. | experimental[] | | +| Beta | A public API that we make a best effort not to break or remove. However, there are no guarantees. | beta[] | | +| Stable | No breaking changes outside of a Major\* | stable[] | | +| Deprecated | Do not use, will be removed. | deprecated[] | | \*This is likely to change with Make it Minor as we move towards a calendar based rolling deprecation and removal policy. diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 898597eabce5d..492e8c25acfb6 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,10 @@ Review important information about the {kib} 8.x releases. +* <> +* <> +* <> +* <> * <> * <> * <> @@ -46,10 +50,358 @@ Review important information about the {kib} 8.x releases. * <> -- +[[release-notes-8.10.2]] +== {kib} 8.10.2 + +The 8.10.2 release includes the following bug fixes. + +[float] +[[fixes-v8.10.2]] +=== Bug fixes + +Fleet:: +* Fixes force delete package, updated used by agents check ({kibana-pull}166623[#166623]). +Management:: +* Fixes showing `Received partial message` instead of results when there are some remote shard errors in a {ccs} ({kibana-pull}166544[#166544]). + +[[release-notes-8.10.1]] +== {kib} 8.10.1 + +The 8.10.1 release includes the following bug fixes. + +[float] +[[fixes-v8.10.1]] +=== Bug fixes + +Dashboard:: +* Fixes content editor flyout footer ({kibana-pull}165907[#165907]). +Elastic Security:: +For the Elastic Security 8.10.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Fleet:: +* Show snapshot version in agent upgrade modal and allow custom values ({kibana-pull}165978[#165978]). +Observability:: +* Fix(slo): Use comma-separarted list of source index for transform ({kibana-pull}166294[#166294]). +Presentation:: +* Fixes air-gapped enviroment hitting `400` error when loading fonts for layer ({kibana-pull}165986[#165986]). + +[[release-notes-8.10.0]] +== {kib} 8.10.0 + +IMPORTANT: {kib} 8.10.0 has been withdrawn. + +For information about the {kib} 8.10.0 release, review the following information. + +[float] +[[security-updates-v8.10.0]] +=== Security updates + +* An issue was discovered by Elastic whereby sensitive information is recorded +in {kib} logs in the event of an error. The issue impacts only {kib} version +8.10.0 when logging in the JSON layout or when the pattern layout is configured +to log the `%meta` pattern. ++ +The issue is resolved in {kib} 8.10.1. Version 8.10.0 has been removed from our +download sites. ++ +For more information, see our related +https://discuss.elastic.co/t/kibana-8-10-1-security-update/343287[security +announcement]. + +[float] +[[breaking-changes-8.10.0]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.10.0, review the breaking changes, then mitigate the impact to your application. + +[discrete] +[[breaking-162665]] +.New summary search capabilities +[%collapsible] +==== +*Details* + +New summary search capabilities introduce breaking changes in various places, and we have decided to not handle backward compatibility: + +* SLO find API body parameters have changed +* The index mapping used by the rollup data has changed, and we have added a summary index that becomes the new source of truth for search +* The rollup transform have been updated, but existing SLO with their transform won't be updated. + +If some SLOs have been installed in a prior version at 8.10, the user will need to remove them manually from the UI, or by deleting the Saved Object and the associated Transform. +==== + +[discrete] +[[breaking-162506]] +.Get case metrics APIs now internal +[%collapsible] +==== +*Details* + +The get case metrics APIs are now internal. For more information, refer to ({kibana-pull}162506[#162506]). +==== + +[discrete] +[[breaking-162492]] +.Case limits +[%collapsible] +==== +*Details* + +Limits are now imposed on the number of objects cases can process or the amount of data those objects can store. +//// +For example: +* Updating a case comment is now included in the 10000 user actions restriction. ({kibana-pull}163150[#163150]) +* Updating a case now fails if the operation makes it reach more than 10000 user actions. ({kibana-pull}161848[#161848]) +* The total number of characters per comment is limited to 30000. ({kibana-pull}161357[#161357]) +* The getConnectors API now limits the number of supported connectors returned to 1000. ({kibana-pull}161282[#161282]) +* There are new limits and restrictions when retrieving cases. ({kibana-pull}162411[#162411]), ({kibana-pull}162245[#162245]), ({kibana-pull}161111[#161111]), ({kibana-pull}160705[#160705]) +* A case can now only have 100 external references and persistable state(excluding files) attachments combined. ({kibana-pull}162071[#162071]). +* New limits on titles, descriptions, tags and category. ({kibana-pull}160844[#160844]). +* The maximum number of cases that can be updated simultaneously is now 100. The minimum is 1. ({kibana-pull}161076[#161076]). +* The Delete cases API now limits the number of cases to be deleted to 100.({kibana-pull}160846[#160846]). +//// +For the full list, refer to {kib-issue}146945[#146945]. +==== + +[discrete] +[[breaking-159041]] +.`addProcessorDefinition` is removed +[%collapsible] +==== +*Details* + +The function `addProcessorDefinition` is removed from the Console plugin start contract (server side). For more information, refer to ({kibana-pull}159041[#159041]). +==== + +[float] +[[deprecations-8.10.0]] +=== Deprecations + +The following functionality is deprecated in 8.10.0, and will be removed in 9.0.0. +Deprecated functionality does not have an immediate impact on your application, but we strongly recommend +you make the necessary updates after you upgrade to 8.10.0. + +[discrete] +[[deprecation-161136]] +.Action variables in the UI and in tests that were no longer used have been replaced +[%collapsible] +==== +*Details* + +The following rule action variables have been deprecated; use the recommended variables (in parentheses) instead: + +* alertActionGroup (alert.actionGroup) +* alertActionGroupName (alert.actionGroupName) +* alertActionSubgroup (alert.actionSubgroup) +* alertId (rule.id) +* alertInstanceId (alert.id) +* alertName (rule.name) +* params (rule.params) +* spaceId (rule.spaceId) +* tags (rule.tags) + +For more information, refer to ({kibana-pull}161136[#161136]). +==== + +[float] +[[features-8.10.0]] +=== Features +{kib} 8.10.0 adds the following new and notable features. + +Alerting:: +* Adds support for self-signed SSL certificate authentication in webhook connector ({kibana-pull}161894[#161894]). +* Allow runtime fields to be selected for {es} query rule type 'group by' or 'aggregate over' options ({kibana-pull}160319[#160319]). +APM:: +* Adds KQL filtering in APM rules ({kibana-pull}163825[#163825]). +* Make service group saved objects exportable ({kibana-pull}163569[#163569]). +* Added ability to manage Cross-Cluster API keys ({kibana-pull}162363[#162363]). +* Enable Trace Explorer by default ({kibana-pull}162308[#162308]). +* Adds error.grouping_name to group alerts in Error Count rule ({kibana-pull}161810[#161810]). +* Adds query to check for overflow bucket in service groups ({kibana-pull}159990[#159990]). +Elastic Security:: +For the Elastic Security 8.10.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Enterprise Search:: +For the Elastic Enterprise Search 8.10.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. +Fleet:: +* Only enable secret storage once all fleet servers are above 8.10.0 ({kibana-pull}163627[#163627]). +* Kafka integration API ({kibana-pull}159110[#159110]). +Machine Learning:: +* AIOps: Adds/edits change point charts embeddable from the Dashboard app ({kibana-pull}163694[#163694]). +* AIOps: Adds change point detection charts embeddable ({kibana-pull}162796[#162796]). +* Adds ability to deploy trained models for data frame analytics jobs ({kibana-pull}162537[#162537]). +* Adds map view for models in Trained Models and expands support for models in Analytics map ({kibana-pull}162443[#162443]). +* Adds new Data comparison view ({kibana-pull}161365[#161365]). +Management:: +* Added ability to create a remote cluster with the API key based security model ({kibana-pull}161836[#161836]). +* REST endpoint for swapping saved object references ({kibana-pull}157665[#157665]). +Maps:: +* Maps tracks layer now uses group by time series logic ({kibana-pull}159267[#159267]). +Observability:: +* SLO definition and computed values are now summarized periodically into a summary search index, allowing users to search by name, tags, SLO budgeting type or time window, and even by and sort by error budget consumed, error budget remaining, SLI value or status ({kibana-pull}162665[#162665]). +* Adds indicator to support histogram fields ({kibana-pull}161582[#161582]). + +For more information about the features introduced in 8.10.0, refer to <>. + +[discrete] +[[enhancements-and-bug-fixes-v8.10.0-revised]] +=== Enhancements and bug fixes + +For detailed information about the 8.10.0 release, review the enhancements and bug fixes. + +[float] +[[enhancement-v8.10.0]] +=== Enhancements +Alerting:: +* Fixes the event log data stream to use Data stream lifecycle instead of Index Lifecycle Management. If you had previously customized the Kibana event log ILM policy, you should now update the lifecycle of the event log data stream itself. ({kibana-pull}163210[#163210]). +* KQL search bar for Rules page ({kibana-pull}158106[#158106]). +APM:: +* Lens function ({kibana-pull}163872[#163872]). +* Adds several function implementations to the AI Assistant ({kibana-pull}163764[#163764]). +* Feature controls ({kibana-pull}163232[#163232]). +* Added improved JVM runtime metrics dashboard ({kibana-pull}162460[#162460]). +* Move to new plugin, update design and use connectors ({kibana-pull}162243[#162243]). +* Adding endpoint to upload android map files ({kibana-pull}161252[#161252]). +* Navigate from the transaction details view into the Profiling ({kibana-pull}159686[#159686]). +Dashboard:: +* Change badge color in options list ({kibana-pull}161401[#161401]). +* Edit title, description, and tags from dashboard listing page ({kibana-pull}161399[#161399]). +* Editing toolbar update ({kibana-pull}154966[#154966]). +Discover:: +* Inline shard failures warnings ({kibana-pull}161271[#161271]). +* Improve shard error message formatting ({kibana-pull}161098[#161098]). +Elastic Security:: +For the Elastic Security 8.10.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Enterprise Search:: +For the Elastic Enterprise Search 8.10.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. +Fleet:: +* Adds support for runtime fields ({kibana-pull}161129[#161129]). +Lens & Visualizations:: +* Migrate range slider to `EuiDualRange` and make styling consistent across all controls ({kibana-pull}162651[#162651]). +* Introduce new duration formatter in *Lens* ({kibana-pull}162246[#162246]). +* Create a filter with field:value when last value metric is used on a data table in *Lens* ({kibana-pull}160509[#160509]). +* Adds tooltip for partition and heatmap filtering ({kibana-pull}162716[#162716]). +Machine Learning:: +* Hides paging controls in anomaly swim lane if only one page is available ({kibana-pull}163931[#163931]). +* Adds a throughput description in the Trained Models Deployment stats table ({kibana-pull}163481[#163481]). +* Provides hints for empty fields in dropdown options in Anomaly detection & Transform creation wizards, Change point detection view ({kibana-pull}163371[#163371]). +* AIOps: Adds dip support to log rate analysis in ML AIOps Labs ({kibana-pull}163100[#163100]). +* AIOps: Improves table hovering for log rate analysis ({kibana-pull}162941[#162941]). +* AIOps: Adds dip support for log rate analysis in observability alert details page ({kibana-pull}162476[#162476]). +* Adds validation of field selected for log pattern analysis ({kibana-pull}162319[#162319]). +* AIOps: Renames Explain Log Rate Spikes to Log Rate Analysis ({kibana-pull}161764[#161764]). +* Adds new Data comparison view ({kibana-pull}161365[#161365]). +* Adds test UI for text expansion models ({kibana-pull}159150[#159150]). +* Adds update index mappings step to ML pipeline config workflow ({kibana-pull}163723[#163723]). +Management:: +* Adds multiple formats for geo_point fields and make geo conversion tools part of field_format/common/utils ({kibana-pull}147272[#147272]). +Maps:: +* Support time series split for top hits per entity source ({kibana-pull}161799[#161799]). +Observability:: +* Adds support for group by to SLO burn rate rule ({kibana-pull}163434[#163434]). +* Applying consistent design to Histogram and Custom Metric forms ({kibana-pull}162083[#162083]). +* Adds preview chart to custom metric indicator ({kibana-pull}161597[#161597]). +* Support filters for good/total custom metrics ({kibana-pull}161308[#161308]). +Reporting:: +* Increase max size bytes default to 250mb ({kibana-pull}161318[#161318]). +Security:: +* Change the default value of `session.idleTimeout` from 8 hours to 3 days ({kibana-pull}162313[#162313]). +* User roles displayed on the user profile page ({kibana-pull}161375[#161375]). +Uptime:: +* Implement private location run once ({kibana-pull}162582[#162582]). + +[float] +[[fixes-v8.10.0]] +=== Bug fixes +APM:: +* Fixes styling and port issue with new onboarding ({kibana-pull}163922[#163922]). +* Fixes missing alert index issue ({kibana-pull}163600[#163600]). +* Fixes trace waterfall loading logic to handle empty scenarios ({kibana-pull}163397[#163397]). +* Fixes the action menu overlapping the 'Create custom link' flyout ({kibana-pull}162664[#162664]). +* Improve service groups error messages and validations ({kibana-pull}162556[#162556]). +* Fixes throwing appropriate error when user is missing necessary permission ({kibana-pull}162466[#162466]). +* Hide components when there is no support from agents ({kibana-pull}161970[#161970]). +* Fixes link to onboarding page in the Observability Onboarding plugin ({kibana-pull}161847[#161847]). +Dashboard:: +* Disables top navigation actions when cloning or overlay is showing ({kibana-pull}162091[#162091]). +Discover:: +* Fixes document failing to load when the ID contains a slash ({kibana-pull}160239[#160239]). +* Fixes NoData screen in alignment with Dashboard and Lends behavior ({kibana-pull}160747[#160747]). +Elastic Security:: +For the Elastic Security 8.10.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Enterprise Search:: +For the Elastic Enterprise Search 8.10.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. +Fleet:: +* Only show agent dashboard links if there is more than one non-server agent and if the dashboards exist ({kibana-pull}164469[#164469]). +* Exclude Synthetics from per-policy-outputs ({kibana-pull}161949[#161949]). +* Fixing the path for hint templates for auto-discover ({kibana-pull}161075[#161075]). +Lens & Visualizations:: +* Fixes params._interval conversion to Lens formula ({kibana-pull}164150[#164150]). +* Fixes issues with field name that contains the `:` character in it in *Lens* ({kibana-pull}163626[#163626]). +* Fixes other request merge behavior when empty string key is retrieved ({kibana-pull}163187[#163187]). +* Fixes editing of multi values filters when adding a custom item ({kibana-pull}161940[#161940]). +* Allow setting custom colors to be collapsed by slices pie's multiple metrics in *Lens* ({kibana-pull}160592[#160592]). +* Fixes data table sorting for keyword values in *Lens* ({kibana-pull}160163[#160163]). +* Fixes override parameter when creating data views ({kibana-pull}160953[#160953]). +Logs:: +* Amend lazy imports in logs_shared plugin ({kibana-pull}164102[#164102]). +Machine Learning:: +* Fixes Trained models list crashes on browser refresh if not on page 1 ({kibana-pull}164163[#164163]). +* Fixes query bar not switching from KQL to Lucene and vice versa in Anomaly explorer ({kibana-pull}163625[#163625]). +* Data Frame Analytics creation wizard: ensures validation works correctly ({kibana-pull}163446[#163446]). +* Fixes capabilities polling in manage page ({kibana-pull}163399[#163399]). +* Fixes unhandled promise rejection from ML plugin ({kibana-pull}163129[#163129]). +* AIOps: Uses Kibana's http service instead of fetch and fixes throttling ({kibana-pull}162335[#162335]). +* Uses model supplied mask token for testing trained models ({kibana-pull}162168[#162168]). +Management:: +* Fixes how a bulk request body with variables are processed in Console ({kibana-pull}162745[#162745]). +* Improves a way of variable substitution and its documentation in Console ({kibana-pull}162382[#162382]). +* Transforms: Reduces rerenders and multiple fetches of source index on transform wizard load ({kibana-pull}160979[#160979]). +Maps:: +* Fixes Map layer preview blocks adding layer until all tiles are loaded ({kibana-pull}161994[#161994]). +Monitoring:: +* Rewrite CPU usage rule to improve accuracy ({kibana-pull}159351[#159351]). +Observability:: +* Fixes email connector rule URL ({kibana-pull}162954[#162954]). +* Disable the 'View rule details' option from the Alert Details' Actions list when the rule is deleted ({kibana-pull}163183[#163183]). +Reporting:: +* Fixes a bug where Kibana Reporting would not work in Elastic Docker without adding a special setting in kibana.yml to disable the Chromium sandbox. ({kibana-pull}149080[#149080]). +* Fixes an issue where certain international characters would not appear correctly in the contents of a print-optimized PDF report of a dashboard ({kibana-pull}161825[#161825]). +Uptime:: +* Fixes auto-expand feature for failed step detail ({kibana-pull}162747[#162747]). + +[[release-notes-8.9.2]] +== {kib} 8.9.2 + +Review the following information about the {kib} 8.9.2 release. + +[float] +[[enhancement-v8.9.2]] +=== Enhancements + +Fleet:: +* Adds the configuration setting `xpack.fleet.packageVerification.gpgKeyPath` as an environment variable in the {kib} container ({kibana-pull}163783[#163783]). + +[float] +[[fixes-v8.9.2]] +=== Bug fixes + +Dashboard:: +* Fixes missing state on short URLs could be lost on an alias match redirect ({kibana-pull}163658[#163658]). +* Fixes 'Download CSV' returning no data when panel has custom time range outside the time range of the global time picker ({kibana-pull}163887[#163887]). +* Fixes **Dashboard** getting stuck at loading in {kib} when Controls is used and mapping changed from integer to keyword ({kibana-pull}163529[#163529]). +Elastic Security:: +For the Elastic Security 8.9.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Lens & Visualizations:: +* Allow removing temporary data view from event annotation group in *Lens* ({kibana-pull}163976[#163976]). +Machine Learning:: +* Anomaly detection wizard: ensure custom URLs test functionality works as expected ({kibana-pull}165055[#165055]). +* Fixes anomaly detection module manifest queries for {kib} sample data sets, so cold and frozen tiers are not queried ({kibana-pull}164332[#164332]). +Management:: +* Transforms: Fixes privileges check ({kibana-pull}163687[#163687]). +Operations:: +* Fixes an issue where {kib} did not start on CentOS/RHEL 7 ({kibana-pull}165151[#165151]). +Reporting:: +* Allow custom roles to use image reporting in **Dashboard** ({kibana-pull}163873[#163873]). + [[release-notes-8.9.1]] == {kib} 8.9.1 -Review the following information about the {kib} 8.9.1 release. +Review the following information about the {kib} 8.9.1 release. [float] [[breaking-changes-8.9.1]] @@ -64,7 +416,7 @@ To review the breaking changes in the previous release, check {kibana-ref-all}/8 [float] [[fixes-v8.9.1]] -=== Bug Fixes +=== Bug fixes APM:: * Fixes flame graph rendering on the transaction detail page ({kibana-pull}162968[#162968]). * Check if documents are missing `span.name` ({kibana-pull}162899[#162899]). @@ -96,6 +448,28 @@ Discover:: For information about the {kib} 8.9.0 release, review the following information. +[float] +[[known-issues-8.9.0]] +=== Known issues + +// tag::known-issue-160116[] +[discrete] +.Changes to Lens visualizations do not appear in the saved object. +[%collapsible] +==== +*Details* + +Changes to Lens visualizations do not appear in the saved object. + +*Impact* + +When you remove fields from Lens visualizations, then save your changes, the removed fields continue to appear in the Lens visualization saved objects. +For example, when you remove runtime fields from a Lens visualization and {kib}, then inspect the Lens visualization saved object, the runtime fields continue to appear and an error message appears. + +*Workaround* + +In 8.10.0, we are addressing this issue by merging the existing and changed saved object instead of replacing the saved object. + +==== +// end::known-issue-161249[] + [float] [[breaking-changes-8.9.0]] === Breaking changes @@ -112,7 +486,7 @@ Before you upgrade to 8.9.0, review the breaking changes, then mitigate the impa The Uptime app now gets hidden from the interface when it doesn't have any data for more than a week. If you have a standalone Heartbeat pushing data to Elasticsearch, the Uptime app is considered active. You can disable this automatic behavior from the advanced settings in Kibana using the **Always show legacy Uptime app** option. For synthetic monitoring, we now recommend to use the new Synthetics app. For more information, refer to {kibana-pull}159118[#159118] ==== - + [discrete] [[breaking-159012]] .Remove synthetics pattern from Uptime settings @@ -129,7 +503,7 @@ Data from browser monitors and monitors of all types created within the Syntheti The following functionality is deprecated in 8.9.0, and will be removed in 9.0.0. Deprecated functionality does not have an immediate impact on your application, but we strongly recommend you make the necessary updates after you upgrade to 8.9.0. - + [discrete] [[deprecation-156455]] .Hide ability to create legacy input controls @@ -138,7 +512,7 @@ you make the necessary updates after you upgrade to 8.9.0. *Details* + The option to create legacy input controls when creating a new visualization is hidden. For more information, refer to {kibana-pull}156455[#156455] ==== - + [discrete] [[deprecation-155503]] .Remove legacy field stats @@ -147,7 +521,7 @@ The option to create legacy input controls when creating a new visualization is *Details* + Legacy felid stats that were previously shown within a popover have been removed. For more information, refer to {kibana-pull}155503[#155503] ==== - + [float] [[features-8.9.0]] === Features @@ -265,7 +639,7 @@ Uptime:: [float] [[fixes-v8.9.0]] -=== Bug Fixes +=== Bug fixes Alerting:: * Fixes containment boundaries not being re-fetched when a query changes {kibana-pull}157408[#157408] * Fixes the charts on Log threshold breached details page {kibana-pull}160321[#160321] @@ -357,14 +731,14 @@ Review the following information about the {kib} 8.8.2 release. [%collapsible] ==== *Details* + -Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. +Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. This action triggers a lot of queries to the Elastic Package Registry (EPR) to fetch integration packages. As a result, -there is an increase in Kibana's resident memory usage (RSS). +there is an increase in Kibana's resident memory usage (RSS). *Impact* + -Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can +Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can cause Kibana to run out of memory during an upgrade. For example, we have observed 1GB Kibana instances run -out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. +out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. *Workaround* + Two workaround options are available: @@ -379,7 +753,7 @@ In 8.9.0, we are addressing this by changing the default batch size to `2`. [float] [[fixes-v8.8.2]] -=== Bug Fixes +=== Bug fixes APM:: * Fixes the latency graph displaying all service transactions, rather than the selected one, on the transaction detail page {kibana-pull}159085[#159085] @@ -440,14 +814,14 @@ Review the following information about the {kib} 8.8.1 release. [%collapsible] ==== *Details* + -Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. +Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. This action triggers a lot of queries to the Elastic Package Registry (EPR) to fetch integration packages. As a result, -there is an increase in Kibana's resident memory usage (RSS). +there is an increase in Kibana's resident memory usage (RSS). *Impact* + -Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can +Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can cause Kibana to run out of memory during an upgrade. For example, we have observed 1GB Kibana instances run -out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. +out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. *Workaround* + Two workaround options are available: @@ -499,7 +873,7 @@ Fleet:: [float] [[fixes-v8.8.1]] -=== Bug Fixes +=== Bug fixes Alerting:: * Fixes a bug where ML embeddables, OsQuery, and IoCs attachments in a case render the wrong view {kibana-pull}158441[#158441] @@ -558,14 +932,14 @@ Review the following information about the {kib} 8.8.0 release. [%collapsible] ==== *Details* + -Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. +Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. This action triggers a lot of queries to the Elastic Package Registry (EPR) to fetch integration packages. As a result, -there is an increase in Kibana's resident memory usage (RSS). +there is an increase in Kibana's resident memory usage (RSS). *Impact* + -Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can +Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can cause Kibana to run out of memory during an upgrade. For example, we have observed 1GB Kibana instances run -out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. +out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. *Workaround* + Two workaround options are available: @@ -1002,7 +1376,7 @@ Querying & Filtering:: [float] [[fixes-v8.8.0]] -=== Bug Fixes +=== Bug fixes Alerting:: * Fixes Delete Schedule button padding issue {kibana-pull}154503[#154503] * Fixes error message flash and throttle value reset {kibana-pull}154497[#154497] @@ -1504,7 +1878,7 @@ TLS rule allow monitors filtering {kibana-pull}150339[#150339] [float] [[fixes-v8.7.0]] -=== Bug Fixes +=== Bug fixes Alerting:: * Event log failure message {kibana-pull}149355[#149355] * Optimize alerting task runner for persistent (non-lifecycle rule types) {kibana-pull}149043[#149043] @@ -2529,7 +2903,7 @@ Logs a hash of the saved objects encryption key (`xpack.encryptedSavedObjects.en [float] [[fixes-v8.4.2]] -=== Bug Fixes +=== Bug fixes Connectors:: The connectors table now uses "compatibility" rather than "availability" {kibana-pull}139024[#139024] @@ -4006,7 +4380,7 @@ Allow customizing {es} client maxSockets {kibana-pull}126937[#126937] [float] [[fixes-v8.2.0]] -=== Bug Fixes +=== Bug fixes Alerting:: * Fixes bug when providing a single value to the `fields` query parameter of the Cases find API {kibana-pull}128143[#128143] * Fixes the count of alerts in the cases table. Only unique alerts are being counted {kibana-pull}127721[#127721] @@ -4435,7 +4809,7 @@ Security:: [float] [[fixes-v8.1.0]] -=== Bug Fixes +=== Bug fixes Alerting:: * Fixes the pagination results for fetching existing alerts {kibana-pull}122474[#122474] * Running disabled rules are now skipped {kibana-pull}119239[#119239] @@ -4687,7 +5061,7 @@ For the Elastic Security 8.0.0 release information, refer to {security-guide}/re [float] [[fixes-v8.0.0]] -==== Bug Fixes +==== Bug fixes APM:: Restrict aggregated transaction metrics search to date range {kibana-pull}123445[#123445] @@ -4770,7 +5144,7 @@ Make a monitor's steps details page work on mobile resolutions in Uptime {kibana [float] [[fixes-v8.0.0-rc2]] -==== Bug Fixes +==== Bug fixes Alerting:: Fixes PagerDuty timestamp validation {kibana-pull}122321[#122321] Dashboard:: @@ -5018,7 +5392,7 @@ Adds user logout audit events {kibana-pull}121455[#121455] [float] [[fixes-v8.0.0-rc1]] -=== Bug Fixes +=== Bug fixes Canvas:: * Fixes Error overflow {kibana-pull}122158[#122158] * Fixes expression input {kibana-pull}121490[#121490] diff --git a/docs/api-generated/connectors/connector-apis-passthru.asciidoc b/docs/api-generated/connectors/connector-apis-passthru.asciidoc index f5128f88f1b1e..a8b429e0b5ae4 100644 --- a/docs/api-generated/connectors/connector-apis-passthru.asciidoc +++ b/docs/api-generated/connectors/connector-apis-passthru.asciidoc @@ -1002,17 +1002,24 @@ Any modifications made to this file will be overwritten.
  • Update_connector_request_body_properties - Update connector request body properties
  • action_response_properties - Action response properties
  • config_properties_cases_webhook - Connector request properties for Webhook - Case Management connector
  • +
  • config_properties_d3security - Connector request properties for a D3 Security connector
  • +
  • config_properties_email - Connector request properties for an email connector
  • config_properties_genai - Connector request properties for a generative AI connector
  • +
  • config_properties_genai_oneOf -
  • +
  • config_properties_genai_oneOf_1 -
  • config_properties_index - Connector request properties for an index connector
  • config_properties_jira - Connector request properties for a Jira connector
  • config_properties_opsgenie - Connector request properties for an Opsgenie connector
  • +
  • config_properties_pagerduty - Connector request properties for a PagerDuty connector
  • config_properties_resilient - Connector request properties for a IBM Resilient connector
  • config_properties_servicenow - Connector request properties for a ServiceNow ITSM connector
  • config_properties_servicenow_itom - Connector request properties for a ServiceNow ITSM connector
  • config_properties_swimlane - Connector request properties for a Swimlane connector
  • config_properties_webhook - Connector request properties for a Webhook connector
  • +
  • config_properties_xmatters - Connector request properties for an xMatters connector
  • connector_response_properties - Connector response properties
  • connector_response_properties_cases_webhook - Connector request properties for a Webhook - Case Management connector
  • +
  • connector_response_properties_d3security - Connector response properties for a D3 Security connector
  • connector_response_properties_email - Connector response properties for an email connector
  • connector_response_properties_index - Connector response properties for an index connector
  • connector_response_properties_jira - Connector response properties for a Jira connector
  • @@ -1032,6 +1039,7 @@ Any modifications made to this file will be overwritten.
  • connector_response_properties_xmatters - Connector response properties for an xMatters connector
  • connector_types - Connector types
  • create_connector_request_cases_webhook - Create Webhook - Case Managment connector request
  • +
  • create_connector_request_d3security - Create D3 Security connector request
  • create_connector_request_email - Create email connector request
  • create_connector_request_genai - Create generative AI connector request
  • create_connector_request_index - Create index connector request
  • @@ -1086,20 +1094,28 @@ Any modifications made to this file will be overwritten.
  • run_connector_subaction_pushtoservice_subActionParams_incident_malware_url -
  • run_connector_subaction_pushtoservice_subActionParams_incident_source_ip -
  • secrets_properties_cases_webhook - Connector secrets properties for Webhook - Case Management connector
  • +
  • secrets_properties_d3security - Connector secrets properties for a D3 Security connector
  • +
  • secrets_properties_email - Connector secrets properties for an email connector
  • secrets_properties_genai - Connector secrets properties for a generative AI connector
  • secrets_properties_jira - Connector secrets properties for a Jira connector
  • secrets_properties_opsgenie - Connector secrets properties for an Opsgenie connector
  • +
  • secrets_properties_pagerduty - Connector secrets properties for a PagerDuty connector
  • secrets_properties_resilient - Connector secrets properties for IBM Resilient connector
  • secrets_properties_servicenow - Connector secrets properties for ServiceNow ITOM, ServiceNow ITSM, and ServiceNow SecOps connectors
  • secrets_properties_slack_api - Connector secrets properties for a Web API Slack connector
  • secrets_properties_slack_webhook - Connector secrets properties for a Webhook Slack connector
  • secrets_properties_swimlane - Connector secrets properties for a Swimlane connector
  • +
  • secrets_properties_teams - Connector secrets properties for a Microsoft Teams connector
  • secrets_properties_webhook - Connector secrets properties for a Webhook connector
  • +
  • secrets_properties_xmatters - Connector secrets properties for an xMatters connector
  • updateConnector_400_response -
  • update_connector_request_cases_webhook - Update Webhook - Case Managment connector request
  • +
  • update_connector_request_d3security - Update D3 Security connector request
  • +
  • update_connector_request_email - Update email connector request
  • update_connector_request_index - Update index connector request
  • update_connector_request_jira - Update Jira connector request
  • update_connector_request_opsgenie - Update Opsgenie connector request
  • +
  • update_connector_request_pagerduty - Update PagerDuty connector request
  • update_connector_request_resilient - Update IBM Resilient connector request
  • update_connector_request_serverlog - Update server log connector request
  • update_connector_request_servicenow - Update ServiceNow ITSM connector or ServiceNow SecOps request
  • @@ -1107,6 +1123,9 @@ Any modifications made to this file will be overwritten.
  • update_connector_request_slack_api - Update Slack connector request
  • update_connector_request_slack_webhook - Update Slack connector request
  • update_connector_request_swimlane - Update Swimlane connector request
  • +
  • update_connector_request_teams - Update Microsoft Teams connector request
  • +
  • update_connector_request_webhook - Update Webhook connector request
  • +
  • update_connector_request_xmatters - Update xMatters connector request
  • @@ -1176,12 +1195,12 @@ Any modifications made to this file will be overwritten.

    Create_connector_request_body_properties - Create connector request body properties Up

    The properties vary depending on the connector type.
    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .xmatters.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .xmatters
    name
    String The display name for the connector.
    -
    secrets
    map[String, oas_any_type_not_mapped] Defines secrets for connectors when type is .xmatters.
    +
    secrets
    @@ -1332,9 +1351,12 @@ Any modifications made to this file will be overwritten.

    Update_connector_request_body_properties - Update connector request body properties Up

    The properties vary depending on the connector type.
    -
    config
    +
    config
    name
    String The display name for the connector.
    -
    secrets
    +
    secrets
    +
    connector_type_id
    String The type of connector.
    +
    Enum:
    +
    .gen-ai
    @@ -1377,19 +1399,67 @@ Any modifications made to this file will be overwritten.
    viewIncidentUrl
    String The URL to view the case in the external system. You can use variables to add the external system ID or external system title to the URL.
    +
    +

    config_properties_d3security - Connector request properties for a D3 Security connector Up

    +
    Defines properties for connectors when type is .d3security.
    +
    +
    url
    String The D3 Security API request URL. If you are using the xpack.actions.allowedHosts setting, add the hostname to the allowed hosts.
    +
    +
    +
    +

    config_properties_email - Connector request properties for an email connector Up

    +
    Defines properties for connectors when type is .email.
    +
    +
    clientId (optional)
    String The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If service is exchange_server, this property is required.
    +
    from
    String The from address for all emails sent by the connector. It must be specified in user@host-name format.
    +
    hasAuth (optional)
    Boolean Specifies whether a user and password are required inside the secrets configuration.
    +
    host (optional)
    String The host name of the service provider. If the service is elastic_cloud (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If service is other, this property must be defined.
    +
    oauthTokenUrl (optional)
    +
    port (optional)
    Integer The port to connect to on the service provider. If the service is elastic_cloud (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If service is other, this property must be defined.
    +
    secure (optional)
    Boolean Specifies whether the connection to the service provider will use TLS. If the service is elastic_cloud (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored.
    +
    service (optional)
    String The name of the email service.
    +
    Enum:
    +
    elastic_cloud
    exchange_server
    gmail
    other
    outlook365
    ses
    +
    tenantId (optional)
    String The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If service is exchange_server, this property is required.
    +
    +

    config_properties_genai - Connector request properties for a generative AI connector Up

    Defines properties for connectors when type is .gen-ai.
    -
    apiProvider (optional)
    String The OpenAI API provider.
    -
    apiUrl (optional)
    String The OpenAI API endpoint.
    +
    apiProvider
    String The OpenAI API provider.
    +
    Enum:
    +
    OpenAI
    +
    apiUrl
    String The OpenAI API endpoint.
    +
    defaultModel (optional)
    String The default model to use for requests.
    +
    +
    +
    +

    config_properties_genai_oneOf - Up

    +
    +
    +
    apiProvider
    String The OpenAI API provider.
    +
    Enum:
    +
    Azure OpenAI
    +
    apiUrl
    String The OpenAI API endpoint.
    +
    +
    +
    +

    config_properties_genai_oneOf_1 - Up

    +
    +
    +
    apiProvider
    String The OpenAI API provider.
    +
    Enum:
    +
    OpenAI
    +
    apiUrl
    String The OpenAI API endpoint.
    +
    defaultModel (optional)
    String The default model to use for requests.

    config_properties_index - Connector request properties for an index connector Up

    Defines properties for connectors when type is .index.
    -
    executionTimeField (optional)
    String Specifies a field that will contain the time the alert condition was detected.
    +
    executionTimeField (optional)
    String A field that indicates when the document was indexed.
    index
    String The Elasticsearch index to be written to.
    refresh (optional)
    Boolean The refresh policy for the write request, which affects when changes are made visible to search. Refer to the refresh setting for Elasticsearch document APIs.
    @@ -1409,6 +1479,13 @@ Any modifications made to this file will be overwritten.
    apiUrl
    String The Opsgenie URL. For example, https://api.opsgenie.com or https://api.eu.opsgenie.com. If you are using the xpack.actions.allowedHosts setting, add the hostname to the allowed hosts.
    +
    +

    config_properties_pagerduty - Connector request properties for a PagerDuty connector Up

    +
    Defines properties for connectors when type is .pagerduty.
    +
    +
    apiUrl (optional)
    String The PagerDuty event URL.
    +
    +

    config_properties_resilient - Connector request properties for a IBM Resilient connector Up

    Defines properties for connectors when type is .resilient.
    @@ -1474,11 +1551,19 @@ Any modifications made to this file will be overwritten.
    certificate
    full
    none
    +
    +

    config_properties_xmatters - Connector request properties for an xMatters connector Up

    +
    Defines properties for connectors when type is .xmatters.
    +
    +
    configUrl (optional)
    String The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when usesBasic is true.
    +
    usesBasic (optional)
    Boolean Specifies whether the connector uses HTTP basic authentication (true) or URL authentication (false).
    +
    +

    connector_response_properties - Connector response properties Up

    The properties vary depending on the connector type.
    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .xmatters.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .xmatters
    @@ -1503,6 +1588,22 @@ Any modifications made to this file will be overwritten.
    is_missing_secrets (optional)
    Boolean Indicates whether secrets are missing for the connector. Secrets configuration properties vary depending on the connector type.
    is_preconfigured
    Boolean Indicates whether it is a preconfigured connector. If true, the config and is_missing_secrets properties are omitted from the response.
    is_system_action (optional)
    Boolean Indicates whether the connector is used for system actions.
    +
    name
    String The display name for the connector.
    +
    +
    +
    +

    connector_response_properties_d3security - Connector response properties for a D3 Security connector Up

    +
    +
    +
    config
    +
    connector_type_id
    String The type of connector.
    +
    Enum:
    +
    .d3security
    +
    id
    String The identifier for the connector.
    +
    is_deprecated
    Boolean Indicates whether the connector type is deprecated.
    +
    is_missing_secrets (optional)
    Boolean Indicates whether secrets are missing for the connector. Secrets configuration properties vary depending on the connector type.
    +
    is_preconfigured
    Boolean Indicates whether it is a preconfigured connector. If true, the config and is_missing_secrets properties are omitted from the response.
    +
    is_system_action (optional)
    Boolean Indicates whether the connector is used for system actions.
    name
    String The display name for the connector.
    @@ -1510,7 +1611,7 @@ Any modifications made to this file will be overwritten.

    connector_response_properties_email - Connector response properties for an email connector Up

    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .email.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .email
    @@ -1574,7 +1675,7 @@ Any modifications made to this file will be overwritten.

    connector_response_properties_pagerduty - Connector response properties for a PagerDuty connector Up

    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .pagerduty.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .pagerduty
    @@ -1716,7 +1817,8 @@ Any modifications made to this file will be overwritten.

    connector_response_properties_teams - Connector response properties for a Microsoft Teams connector Up

    -
    connector_type_id
    String The type of connector.
    +
    config (optional)
    +
    connector_type_id
    String The type of connector.
    Enum:
    .teams
    id
    String The identifier for the connector.
    @@ -1763,7 +1865,7 @@ Any modifications made to this file will be overwritten.

    connector_response_properties_xmatters - Connector response properties for an xMatters connector Up

    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .xmatters.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .xmatters
    @@ -1793,16 +1895,28 @@ Any modifications made to this file will be overwritten.
    secrets (optional)
    +
    +

    create_connector_request_d3security - Create D3 Security connector request Up

    +
    The connector uses axios to send a POST request to a D3 Security endpoint.
    +
    +
    config
    +
    connector_type_id
    String The type of connector.
    +
    Enum:
    +
    .d3security
    +
    name
    String The display name for the connector.
    +
    secrets
    +
    +

    create_connector_request_email - Create email connector request Up

    The email connector uses the SMTP protocol to send mail messages, using an integration of Nodemailer. An exception is Microsoft Exchange, which uses HTTP protocol for sending emails, Send mail. Email message text is sent as both plain text and html text.
    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .email.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .email
    name
    String The display name for the connector.
    -
    secrets
    map[String, oas_any_type_not_mapped] Defines secrets for connectors when type is .email.
    +
    secrets
    @@ -1856,12 +1970,12 @@ Any modifications made to this file will be overwritten.

    create_connector_request_pagerduty - Create PagerDuty connector request Up

    The PagerDuty connector uses the v2 Events API to trigger, acknowledge, and resolve PagerDuty alerts.
    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .pagerduty.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .pagerduty
    name
    String The display name for the connector.
    -
    secrets
    map[String, oas_any_type_not_mapped] Defines secrets for connectors when type is .pagerduty.
    +
    secrets
    @@ -1964,7 +2078,7 @@ Any modifications made to this file will be overwritten.
    Enum:
    .teams
    name
    String The display name for the connector.
    -
    secrets
    map[String, oas_any_type_not_mapped] Defines secrets for connectors when type is .teams.
    +
    secrets
    @@ -1995,12 +2109,12 @@ Any modifications made to this file will be overwritten.

    create_connector_request_xmatters - Create xMatters connector request Up

    The xMatters connector uses the xMatters Workflow for Elastic to send actionable alerts to on-call xMatters resources.
    -
    config
    map[String, oas_any_type_not_mapped] Defines properties for connectors when type is .xmatters.
    +
    config
    connector_type_id
    String The type of connector.
    Enum:
    .xmatters
    name
    String The display name for the connector.
    -
    secrets
    map[String, oas_any_type_not_mapped] Defines secrets for connectors when type is .xmatters.
    +
    secrets
    @@ -2351,6 +2465,22 @@ Any modifications made to this file will be overwritten.
    password (optional)
    String The password for HTTP basic authentication. If hasAuth is set to true, this property is required.
    +
    user (optional)
    String The username for HTTP basic authentication. If hasAuth is set to true, this property is required.
    +
    +
    +
    +

    secrets_properties_d3security - Connector secrets properties for a D3 Security connector Up

    +
    Defines secrets for connectors when type is .d3security.
    +
    +
    token
    String The D3 Security token.
    +
    +
    +
    +

    secrets_properties_email - Connector secrets properties for an email connector Up

    +
    Defines secrets for connectors when type is .email.
    +
    +
    clientSecret (optional)
    String The Microsoft Exchange Client secret for OAuth 2.0 client credentials authentication. It must be URL-encoded. If service is exchange_server, this property is required.
    +
    password (optional)
    String The password for HTTP basic authentication. If hasAuth is set to true, this property is required.
    user (optional)
    String The username for HTTP basic authentication. If hasAuth is set to true, this property is required.
    @@ -2376,6 +2506,13 @@ Any modifications made to this file will be overwritten.
    apiKey
    String The Opsgenie API authentication key for HTTP Basic authentication.
    +
    +

    secrets_properties_pagerduty - Connector secrets properties for a PagerDuty connector Up

    +
    Defines secrets for connectors when type is .pagerduty.
    +
    +
    routingKey
    String A 32 character PagerDuty Integration Key for an integration on a service.
    +
    +

    secrets_properties_resilient - Connector secrets properties for IBM Resilient connector Up

    Defines secrets for connectors when type is .resilient.
    @@ -2416,6 +2553,13 @@ Any modifications made to this file will be overwritten.
    apiToken (optional)
    String Swimlane API authentication token.
    +
    +

    secrets_properties_teams - Connector secrets properties for a Microsoft Teams connector Up

    +
    Defines secrets for connectors when type is .teams.
    +
    +
    webhookUrl
    String The URL of the incoming webhook. If you are using the xpack.actions.allowedHosts setting, add the hostname to the allowed hosts.
    +
    +

    secrets_properties_webhook - Connector secrets properties for a Webhook connector Up

    Defines secrets for connectors when type is .webhook.
    @@ -2427,6 +2571,15 @@ Any modifications made to this file will be overwritten.
    user (optional)
    String The username for HTTP basic authentication. If hasAuth is set to true and authType is webhook-authentication-basic, this property is required.
    +
    +

    secrets_properties_xmatters - Connector secrets properties for an xMatters connector Up

    +
    Defines secrets for connectors when type is .xmatters.
    +
    +
    password (optional)
    String A user name for HTTP basic authentication. It is applicable only when usesBasic is true.
    +
    secretsUrl (optional)
    String The request URL for the Elastic Alerts trigger in xMatters with the API key included in the URL. It is applicable only when usesBasic is false.
    +
    user (optional)
    String A password for HTTP basic authentication. It is applicable only when usesBasic is true.
    +
    +

    updateConnector_400_response - Up

    @@ -2445,6 +2598,24 @@ Any modifications made to this file will be overwritten.
    secrets (optional)
    + +
    +

    update_connector_request_email - Update email connector request Up

    +
    +
    +
    config
    +
    name
    String The display name for the connector.
    +
    secrets (optional)
    +
    +

    update_connector_request_index - Update index connector request Up

    @@ -2471,6 +2642,15 @@ Any modifications made to this file will be overwritten.
    secrets
    +

    update_connector_request_resilient - Update IBM Resilient connector request Up

    @@ -2530,5 +2710,31 @@ Any modifications made to this file will be overwritten.
    secrets
    + + + ++++ diff --git a/docs/api/osquery-manager/packs/create.asciidoc b/docs/api/osquery-manager/packs/create.asciidoc index 5f9829164b245..2fcfc58d43dba 100644 --- a/docs/api/osquery-manager/packs/create.asciidoc +++ b/docs/api/osquery-manager/packs/create.asciidoc @@ -33,6 +33,8 @@ experimental[] Create packs. `policy_ids`:: (Optional, array) A list of agents policy IDs. +`shards`:: (Required, object) An object with shard configuration for policies included in the pack. For each policy, set the shard configuration to a percentage (1–100) of target hosts. + `queries`:: (Required, object) An object of queries. @@ -56,8 +58,13 @@ $ curl -X POST api/osquery/packs \ "description": "My pack", "enabled": true, "policy_ids": [ - "my_policy_id" + "my_policy_id", + "fleet-server-policy" ], + "shards": { + "my_policy_id": 35, + "fleet-server-policy": 58 + }, "queries": { "my_query": { "query": "SELECT * FROM listening_ports;", @@ -67,7 +74,10 @@ $ curl -X POST api/osquery/packs \ "field": "port" }, "tags": { - "value": ["tag1", "tag2"] + "value": [ + "tag1", + "tag2" + ] } } } diff --git a/docs/api/osquery-manager/packs/update.asciidoc b/docs/api/osquery-manager/packs/update.asciidoc index 0fc2e2684e0e9..d098d2567f1ac 100644 --- a/docs/api/osquery-manager/packs/update.asciidoc +++ b/docs/api/osquery-manager/packs/update.asciidoc @@ -38,6 +38,8 @@ WARNING: You are unable to update a prebuilt pack (`read_only = true`). `policy_ids`:: (Optional, array) A list of agent policy IDs. +`shards`:: (Optional, object) An object with shard configuration for policies included in the pack. For each policy, set the shard configuration to a percentage (1–100) of target hosts. + `queries`:: (Required, object) An object of queries. diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index 9058b29f0cb8a..341e75bbb835c 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -11,7 +11,6 @@ Some APM app features are provided via a REST API: * <> * <> * <> -* <> * <> [float] @@ -504,6 +503,16 @@ The following APIs are available: * <> * <> +[float] +[[limit-sourcemap-api]] +==== Max payload size + +{kib}'s maximum payload size is 1mb. +If you attempt to upload a source map that exceeds the max payload size, you will get a `413` error. + +Before uploading source maps that exceed this default, change the maximum payload size allowed by {kib} +with the <> variable. + [float] [[use-sourcemap-api]] ==== How to use APM APIs @@ -716,219 +725,6 @@ curl -X DELETE "http://localhost:5601/api/apm/sourcemaps/apm:foo-1.0.0-644fd5a9" ******************************************************* //// -[role="xpack"] -[[android-sourcemap-api]] -=== Android source map API - -IMPORTANT: This endpoint is only compatible with the -{apm-guide-ref}/index.html[APM integration for Elastic Agent]. - -An Android source map (generated using Android's https://developer.android.com/build/shrink-code[R8 tool]) -allows obfuscated app stacktraces to be mapped back to original source code -- -allowing you to maintain the size and security of minimized code, without losing the ability to debug your application. - -For best results, uploading source maps should become a part of your deployment procedure, -and not something you only do when you see unhelpful errors. -That’s because uploading source maps after errors happen won’t make old errors magically readable -- -errors must occur again for source mapping to occur. - -The following APIs are available: - -* <> -* <> -* <> - -[float] -[[use-android-sourcemap-api]] -==== How to use APM APIs - -.Expand for required headers, privileges, and usage details -[%collapsible%closed] -====== -include::api.asciidoc[tag=using-the-APIs] -====== - -//// -******************************************************* -//// - -[[android-sourcemap-post]] -==== Create or update an Android source map - -Create or update an Android source map for a specific app and version. - -[[android-sourcemap-post-privs]] -===== Privileges - -The user accessing this endpoint requires `All` Kibana privileges for the {beat_kib_app} feature. -For more information, see <>. - -[[android-sourcemap-post-req]] -===== Request - -`POST /api/apm/androidmaps` - -[role="child_attributes"] -[[android-sourcemap-post-req-body]] -===== Request body - -`service_name`:: -(required, string) The name of the Android app that the map should apply to. - -`service_version`:: -(required, string) The version of the Android app that the map should apply to. - -`map_file`:: -(required, string or file upload) The R8-generated map. - -[[android-sourcemap-post-example]] -===== Examples - -The following example uploads a source map for a app named `foo` and a service version of `1.0.0`: - -[source,curl] --------------------------------------------------- -curl -X POST "http://localhost:5601/api/apm/androidmaps" \ --H 'Content-Type: multipart/form-data' \ --H 'kbn-xsrf: true' \ --H 'Authorization: ApiKey ${YOUR_API_KEY}' \ --F 'service_name="foo"' \ --F 'service_version="1.0.0"' \ --F 'map_file=@"/Path/to/the/file/mapping.txt"' --------------------------------------------------- - -[[android-sourcemap-post-body]] -===== Response body - -[source,js] --------------------------------------------------- -{ - "type": "sourcemap", - "identifier": "foo-1.0.0-android", - "relative_url": "/api/fleet/artifacts/foo-1.0.0-android/644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "body": "eJyFkL1OwzAUhd/Fc+MbYMuCEBIbHRjKgBgc96R16tiWr1OQqr47NwqJxEK3q/PzWccXxchnZ7E1A1SjuhjVZtF2yOxiEPlO17oWox3D3uPFeSRTjmJQARfCPeiAgGx8NTKsYdAc1T3rwaSJGcds8Sp3c1HnhfywUZ3QhMTFFGepZxqMC9oex3CS9tpk1XyozgOlmoVKuJX1DqEQZ0su7PGtLU+V/3JPKc3cL7TJ2FNDRPov4bFta3MDM4f7W69lpJjLO9qdK8bzVPhcJz3HUCQ4LbO/p5hCSC4cZPByrp/wFqOklbpefwAhzpqI", - "created": "2021-07-09T20:47:44.812Z", - "id": "apm:foo-1.0.0-android-644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "compressionAlgorithm": "zlib", - "decodedSha256": "644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "decodedSize": 441, - "encodedSha256": "024c72749c3e3dd411b103f7040ae62633558608f480bce4b108cf5b2275bd24", - "encodedSize": 237, - "encryptionAlgorithm": "none", - "packageName": "apm" -} --------------------------------------------------- - -//// -******************************************************* -//// - -[[android-sourcemap-get]] -==== Get source maps - -Returns an array of Fleet artifacts, including source map uploads. - -[[android-sourcemap-get-privs]] -===== Privileges - -The user accessing this endpoint requires `Read` or `All` Kibana privileges for the {beat_kib_app} feature. -For more information, see <>. - -[[android-sourcemap-get-req]] -===== Request - -`GET /api/apm/sourcemaps` - -[[android-sourcemap-get-example]] -===== Example - -The following example requests all uploaded source maps: - -[source,curl] --------------------------------------------------- -curl -X GET "http://localhost:5601/api/apm/sourcemaps" \ --H 'Content-Type: application/json' \ --H 'kbn-xsrf: true' \ --H 'Authorization: ApiKey ${YOUR_API_KEY}' --------------------------------------------------- - -[[android-sourcemap-get-body]] -===== Response body - -[source,js] --------------------------------------------------- -{ - "artifacts": [ - { - "type": "sourcemap", - "identifier": "foo-1.0.0-android", - "relative_url": "/api/fleet/artifacts/foo-1.0.0-android/644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "body": { - "serviceName": "foo", - "serviceVersion": "1.0.0", - "bundleFilepath": "android", - "sourceMap": "# compiler: R8\n# compiler_version: 3.2.47\n# min_api: 26\n..." - }, - "created": "2021-07-09T20:47:44.812Z", - "id": "apm:foo-1.0.0-android-644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "compressionAlgorithm": "zlib", - "decodedSha256": "644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "decodedSize": 441, - "encodedSha256": "024c72749c3e3dd411b103f7040ae62633558608f480bce4b108cf5b2275bd24", - "encodedSize": 237, - "encryptionAlgorithm": "none", - "packageName": "apm" - } - ] -} --------------------------------------------------- - -//// -******************************************************* -//// - -[[android-sourcemap-delete]] -==== Delete source map - -Delete a previously uploaded source map. - -[[android-sourcemap-delete-privs]] -===== Privileges - -The user accessing this endpoint requires `All` Kibana privileges for the {beat_kib_app} feature. -For more information, see <>. - -[[android-sourcemap-delete-req]] -===== Request - -`DELETE /api/apm/sourcemaps/:id` - -[[android-sourcemap-delete-example]] -===== Example - -The following example deletes a source map with an id of `apm:foo-1.0.0-android-644fd5a9`: - -[source,curl] --------------------------------------------------- -curl -X DELETE "http://localhost:5601/api/apm/sourcemaps/apm:foo-1.0.0-android-644fd5a9" \ --H 'Content-Type: application/json' \ --H 'kbn-xsrf: true' \ --H 'Authorization: ApiKey ${YOUR_API_KEY}' --------------------------------------------------- - -[[android-sourcemap-delete-body]] -===== Response body - -[source,js] --------------------------------------------------- -{} --------------------------------------------------- - -//// -******************************************************* -******************************************************* -//// - [role="xpack"] [[agent-key-api]] === APM agent Key API diff --git a/docs/concepts/data-views.asciidoc b/docs/concepts/data-views.asciidoc index 79aec68151d9a..c72679734b725 100644 --- a/docs/concepts/data-views.asciidoc +++ b/docs/concepts/data-views.asciidoc @@ -153,6 +153,10 @@ To exclude a cluster with the name `cluster_one`: Once you configure a {data-source} to use the {ccs} syntax, all searches and aggregations using that {data-source} in {kib} take advantage of {ccs}. +For more information, refer to +{ref}/modules-cross-cluster-search.html#exclude-problematic-clusters[Excluding +clusters or indicies from cross-cluster search]. + [float] [[delete-data-view]] === Delete a {data-source} diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 69727ef5f40ca..a89766083f2b2 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -102,6 +102,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |The Event Annotation service contains expressions for event annotations +|{kib-repo}blob/{branch}/src/plugins/event_annotation_listing/README.md[eventAnnotationListing] +|This plugin contains the library listing page for event annotation groups. + + |{kib-repo}blob/{branch}/src/plugins/expression_error/README.md[expressionError] |Expression Error plugin adds an error renderer to the expression plugin. The renderer will display the error image. @@ -637,7 +641,7 @@ using the CURL scripts in the scripts folder. |{kib-repo}blob/{branch}/x-pack/plugins/log_explorer/README.md[logExplorer] -|This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. +|This plugin is home to the component and related types. It implements several of the underlying concepts that the Observability Log Explorer app builds upon. |{kib-repo}blob/{branch}/x-pack/plugins/logs_shared/README.md[logsShared] @@ -652,6 +656,10 @@ using the CURL scripts in the scripts folder. |Visualize geo data from Elasticsearch or 3rd party geo-services. +|{kib-repo}blob/{branch}/x-pack/plugins/metrics_data_access/README.md[metricsDataAccess] +|Exposes utilities to access metrics data. + + |{kib-repo}blob/{branch}/x-pack/plugins/ml/readme.md[ml] |This plugin provides access to the machine learning features provided by Elastic. @@ -701,6 +709,10 @@ Elastic. |Universal Profiling provides fleet-wide, whole-system, continuous profiling with zero instrumentation. Get a comprehensive understanding of what lines of code are consuming compute resources throughout your entire fleet by visualizing your data in Kibana using the flamegraph, stacktraces, and top functions views. +|{kib-repo}blob/{branch}/x-pack/plugins/profiling_data_access[profilingDataAccess] +|WARNING: Missing README. + + |{kib-repo}blob/{branch}/x-pack/plugins/remote_clusters/README.md[remoteClusters] |This plugin helps users manage their remote clusters, which enable cross-cluster search and cross-cluster replication. diff --git a/docs/landing-page.asciidoc b/docs/landing-page.asciidoc index 1cb3e188d6121..8d806d574129a 100644 --- a/docs/landing-page.asciidoc +++ b/docs/landing-page.asciidoc @@ -71,8 +71,8 @@

    - What's new - Release notes + What's new + Release notes Install

    @@ -259,7 +259,7 @@
    - +

    diff --git a/docs/management/action-types.asciidoc b/docs/management/action-types.asciidoc index 1e45c9c64fa14..bbaba806386e2 100644 --- a/docs/management/action-types.asciidoc +++ b/docs/management/action-types.asciidoc @@ -27,9 +27,9 @@ a| <> | Send a message to a Microsoft Teams channel. -a| <> +a| <> -| Create or close an alert in Opsgenie. +| Create or close an alert in {opsgenie}. a| <> diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 5873af1ef9cb1..e0ddf9bd3f334 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -288,8 +288,8 @@ in the current data view is used. The columns that appear by default on the *Discover* page. The default is `_source`. -[[discover:enableSql]]`discover:enableSql`:: -experimental[] Allows SQL queries for search. +[[discover:enableESQL]]`discover:enableESQL`:: +experimental[] Allows ES|QL queries for search. [[discover-max-doc-fields-displayed]]`discover:maxDocFieldsDisplayed`:: Specifies the maximum number of fields to show in the document column of the *Discover* table. diff --git a/docs/management/cases/manage-cases.asciidoc b/docs/management/cases/manage-cases.asciidoc index a3f0a40fd6009..e3896423b3f13 100644 --- a/docs/management/cases/manage-cases.asciidoc +++ b/docs/management/cases/manage-cases.asciidoc @@ -105,14 +105,13 @@ For self-managed {kib}: + -- NOTE: At this time, email notifications support only preconfigured connectors, -which are defined in the `kibana.yml` file. For examples, refer to -{kibana-ref}/email-action-type.html#preconfigured-email-configuration[Preconfigured email connector] -and {kibana-ref}/email-action-type.html#configuring-email[Configuring email connectors for well-known services]. +which are defined in the `kibana.yml` file. +For examples, refer to <> and <>. -- . Set the `notifications.connectors.default.email` {kib} setting to the name of your email connector. . If you want the email notifications to contain links back to the case, you -must configure the {kibana-ref}/settings.html#server-publicBaseUrl[server.publicBaseUrl] setting. +must configure the <> setting. When you subsequently add assignees to cases, they receive an email. // end::case-notifications[] diff --git a/docs/management/connectors/action-types/cases-webhook.asciidoc b/docs/management/connectors/action-types/cases-webhook.asciidoc index d2d72fddb3348..537bfffb88daa 100644 --- a/docs/management/connectors/action-types/cases-webhook.asciidoc +++ b/docs/management/connectors/action-types/cases-webhook.asciidoc @@ -1,9 +1,12 @@ -[role="xpack"] [[cases-webhook-action-type]] == {webhook-cm} connector and action ++++ {webhook-cm} ++++ +:frontmatter-description: Add a connector that can send requests to case management web services. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] The {webhook-cm} connector uses https://github.com/axios/axios[axios] to send POST, PUT, and GET requests to a case management RESTful API web service. @@ -105,62 +108,6 @@ Create comment object:: (Optional) A JSON payload sent to the create comment URL + NOTE: Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated in this step. The JSON is validated once the mustache variables have been placed and when REST method runs. We recommend manually ensuring that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. -[float] -[[preconfigured-cases-webhook-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-case-management-webhook: - name: Case Management Webhook Connector - actionTypeId: .cases-webhook - config: - hasAuth: true - headers: - 'content-type': 'application/json' - createIncidentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue' - createIncidentMethod: 'post' - createIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"labels":{{{case.tags}}}' - getIncidentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}}' - getIncidentResponseExternalTitleKey: 'key' - viewIncidentUrl: 'https://testing-jira.atlassian.net/browse/{{{external.system.title}}}' - updateIncidentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}}' - updateIncidentMethod: 'put' - updateIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"labels":{{{case.tags}}}' - createCommentMethod: 'post', - createCommentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}}/comment', - createCommentJson: '{"body": {{{case.comment}}}}', - secrets: - user: testuser - password: passwordvalue --- - -`config`:: Defines information for the connector type. -`hasAuth`::: A boolean that corresponds to *Requires authentication*. If `true`, this connector will require values for `user` and `password` inside the secrets configuration. Defaults to `true`. -`headers`::: A `record` that corresponds to *Headers*. -`createIncidentUrl`::: A URL string that corresponds to *Create Case URL*. -`createIncidentMethod`::: A string that corresponds to *Create Case Method*. -`createIncidentJson`::: A stringified JSON with Mustache variables that corresponds to *Create Case JSON*. -`createIncidentResponseKey`::: A string from the response body of the create case method that corresponds to the *External Service Id*. -`getIncidentUrl`::: A URL string with an *External Service Id* Mustache variable that corresponds to *Get Case URL*. -`getIncidentResponseExternalTitleKey`::: A string from the response body of the get case method that corresponds to the *External Service Title*. -`viewIncidentUrl`::: A URL string with either the *External Service Id* or *External Service Title* Mustache variable that corresponds to *View Case URL*. -`updateIncidentUrl`::: A URL string that corresponds to *Update Case URL*. -`updateIncidentMethod`::: A string that corresponds to *Update Case Method*. -`updateIncidentJson`::: A stringified JSON with Mustache variables that corresponds to *Update Case JSON*. -`createCommentUrl`::: A URL string that corresponds to *Create Comment URL*. -`createCommentMethod`::: A string that corresponds to *Create Comment Method*. -`createCommentJson`::: A stringified JSON with Mustache variables that corresponds to *Create Comment JSON*. - -`secrets`:: Defines sensitive information for the connector type. -`user`::: A string that corresponds to *User*. Required if `hasAuth` is set to `true`. -`password`::: A string that corresponds to *Password*. Required if `hasAuth` is set to `true`. - [float] [[cases-webhook-action-configuration]] === Test connectors diff --git a/docs/management/connectors/action-types/d3security.asciidoc b/docs/management/connectors/action-types/d3security.asciidoc index 095e5f5980328..76152365f76cd 100644 --- a/docs/management/connectors/action-types/d3security.asciidoc +++ b/docs/management/connectors/action-types/d3security.asciidoc @@ -3,21 +3,14 @@ ++++ D3 Security ++++ +:frontmatter-description: Add a connector that can send requests to D3 Security. +:frontmatter-tags-products: [alerting] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] The D3 Security connector uses https://github.com/axios/axios[axios] to send a POST request to a D3 Security endpoint. The connector uses the <> to send the request. You can use the connector for rule actions. -[float] -[[d3security-connector-prerequisites]] -=== Prerequisites - -To use a D3 Security connector, you must first configure a webhook key in your D3 SOAR environment. To generate an API URL and a token in D3 Security: -1. Log in to your D3 SOAR environment. -2. Navigate to Configuration. -3. Navigate to Integration > Search for “Kibana”. Click “Fetch Event”. -4. Select the "Enable Webhook" checkbox. -5. Click Set up Webhook Keys. -6. Under Event Ingestion, Click +. Select the site for the webhook integration, then click Generate. -7. Copy the Request URL and Request Header Value to configure the Kibana connector +To create this connector, you must first configure a webhook key in your D3 SOAR environment. For configuration tips, refer to <>. [float] [[define-d3security-ui]] @@ -36,35 +29,7 @@ D3 Security connectors have the following configuration properties: Name:: The name of the connector. URL:: The D3 Security API request URL. -Token:: The D3 Security token - -[float] -[[preconfigured-d3security-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-d3security: - name: preconfigured-d3security-connector-type - actionTypeId: .d3security - config: - url: https://testurl.com/elasticsearch/VSOC/api/Data/Kibana/Security%20Operations/CreateEvents - secrets: - token: superlongtoken --- - -Config defines information for the connector type. - -`url`:: A URL string that corresponds to the *D3 Security API URL*. - -Secrets defines sensitive information for the connector type. - -`token`:: A string that corresponds to *D3 Security API Token*. +Token:: The D3 Security token. [float] [[d3security-action-configuration]] @@ -88,4 +53,18 @@ this can be any type, it is not validated [[d3security-connector-networking-configuration]] === Connector networking configuration -Use the <> to customize connector networking configurations, such as proxies, certificates, or TLS settings. You can set configurations that apply to all your connectors or use `xpack.actions.customHostSettings` to set per-host configurations. \ No newline at end of file +Use the <> to customize connector networking configurations, such as proxies, certificates, or TLS settings. You can set configurations that apply to all your connectors or use `xpack.actions.customHostSettings` to set per-host configurations. + +[float] +[[configure-d3security]] +=== Configure D3 Security + +To generate an API URL and a token in D3 Security: + +1. Log in to your D3 SOAR environment. +2. Navigate to *Configuration*. +3. Navigate to *Integration*. Search for {kib}. Click *Fetch Event*. +4. Select the *Enable Webhook* checkbox. +5. Click *Set up Webhook Keys*. +6. Under *Event Ingestion*, click the plus sign(+). Select the site for the webhook integration, then click *Generate*. +7. Copy the request URL and request header value to configure the connector. \ No newline at end of file diff --git a/docs/management/connectors/action-types/email.asciidoc b/docs/management/connectors/action-types/email.asciidoc index 0f652821a3661..c7cea7e1dceff 100644 --- a/docs/management/connectors/action-types/email.asciidoc +++ b/docs/management/connectors/action-types/email.asciidoc @@ -97,88 +97,12 @@ Username for login type authentication. Password:: Password for login type authentication. -[float] -[[preconfigured-email-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-email: - name: preconfigured-email-connector-type - actionTypeId: .email - config: - service: other - from: testsender@test.com - host: validhostname - port: 8080 - secure: false - secrets: - user: testuser - password: passwordkeystorevalue --- - -Config defines information for the connector type. - -`service`:: -The name of the email service. If `service` is `elastic_cloud` (for Elastic -Cloud notifications) or one of Nodemailer's well-known email service providers, -the `host`, `port`, and `secure` properties are ignored. If `service` is `other`, -the `host` and `port` properties must be defined. For more information on the -`gmail` service value, refer to -https://nodemailer.com/usage/using-gmail/[Nodemailer Gmail documentation]. If -`service` is `exchange_server`, the `tenantId`, `clientId`, `clientSecret` -properties are required instead of `host` and `port`. - -`from`:: -An email address that corresponds to *Sender*. - -`host`:: -A string that corresponds to *Host*. - -`port`:: -A number that corresponds to *Port*. - -`secure`:: -A boolean that corresponds to *Secure*. - -`hasAuth`:: -A boolean that corresponds to *Requires authentication*. If `true`, this -connector will require values for `user` and `password` inside the secrets -configuration. Defaults to `true`. - -`tenantId`:: -A GUID format value that corresponds to *Tenant ID*, which is a part of OAuth -2.0 Client Credentials Authentication. - -`clientId`:: -A GUID format value that corresponds to *Client ID*, which is a part of OAuth -2.0 Client Credentials Authentication. - -Secrets defines sensitive information for the connector type. - -`user`:: -A string that corresponds to *Username*. Required if `hasAuth` is set to `true`. - -`password`:: -A string that corresponds to *Password*. Should be stored in the -<>. Required if `hasAuth` is set to `true`. - -`clientSecret`:: -A string that corresponds to *Client Secret*. Should be stored in the -<>. Required if `service` is set to -`exchange_server`, which uses OAuth 2.0 Client Credentials Authentication. - [float] [[email-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. +For example: [role="screenshot"] image::management/connectors/images/email-params-test.png[Email params test] @@ -214,14 +138,6 @@ settings. You can set configurations that apply to all your connectors or use The email connector uses an integration of https://nodemailer.com/[Nodemailer] to send email from many popular SMTP email services. For Microsoft Exchange email, it uses the Microsoft Graph API. -To configure the email connector to work with common email systems, refer to: - -* <> -* <> -* <> -* <> -* <> - For other email servers, you can check the list of well-known services that Nodemailer supports in the JSON file https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json[well-known/services.json]. @@ -233,32 +149,17 @@ is considered `false`. Typically, `port: 465` uses `secure: true`, and [float] [[elasticcloud]] -==== Sending email from Elastic Cloud +==== {ecloud} -Use the preconfigured email connector (`Elastic-Cloud-SMTP`) to send emails from -Elastic Cloud. +Use the preconfigured email connector (`Elastic-Cloud-SMTP`) to send emails from {ecloud}. -NOTE: For more information on the preconfigured email connector, see link:{cloud}/ec-watcher.html#ec-cloud-email-service-limits[Elastic Cloud email service limits]. +NOTE: For more information on the preconfigured email connector, see link:{cloud}/ec-watcher.html#ec-cloud-email-service-limits[{ecloud} email service limits]. [float] [[gmail]] -==== Sending email from Gmail - -Use the following email connector configuration to send email from the -https://mail.google.com[Gmail] SMTP service: - -[source,text] --------------------------------------------------- - config: - service: gmail - // `host`, `port` and `secure` have the following default values and do not need to set: - // host: smtp.gmail.com - // port: 465 - // secure: true - secrets: - user: - password: --------------------------------------------------- +==== Gmail + +To create a connector that sends email from the https://mail.google.com[Gmail] SMTP service, set the **Service** to `Gmail`. If you get an authentication error that indicates that you need to continue the sign-in process from a web browser when the action attempts to send email, you @@ -272,23 +173,10 @@ for more information. [float] [[outlook]] -==== Sending email from Outlook.com - -Use the following email connector configuration to send email from the -https://www.outlook.com/[Outlook.com] SMTP service: - -[source,text] --------------------------------------------------- -config: - service: outlook365 - // `host`, `port` and `secure` have the following default values and do not need to set: - // host: smtp.office365.com - // port: 587 - // secure: false -secrets: - user: - password: --------------------------------------------------- +==== Outlook.com + +To create a connector that sends email from the +https://www.outlook.com/[Outlook.com] SMTP service, set the **Service** to `Outlook`. When sending emails, you must provide a `from` address, either as the default in your connector configuration or as part of the email action in the rule. @@ -300,24 +188,10 @@ for more information. [float] [[amazon-ses]] -==== Sending email from Amazon SES (Simple Email Service) - -Use the following email connector configuration to send email from the -http://aws.amazon.com/ses[Amazon Simple Email Service] (SES) SMTP service: - -[source,text] --------------------------------------------------- -config: - service: ses - // `host`, `port` and `secure` have the following default values and do not need to set: - // host: email-smtp.us-east-1.amazonaws.com <1> - // port: 465 - // secure: true -secrets: - user: - password: --------------------------------------------------- -<1> `config.host` varies depending on the region +==== Amazon SES + +To create a connector that sends email from the +http://aws.amazon.com/ses[Amazon Simple Email Service] (SES) SMTP service, set the **Service** to `Amazon SES`. NOTE: You must use your Amazon SES SMTP credentials to send email through Amazon SES. For more information, see @@ -328,38 +202,19 @@ or https://docs.aws.amazon.com/ses/latest/DeveloperGuide/verify-domains.html[your whole domain] at AWS. - [float] [[exchange-basic-auth]] -==== Sending email from Microsoft Exchange with Basic Authentication +==== Microsoft Exchange with basic authentication deprecated:[7.16.0,"This Microsoft Exchange configuration is deprecated and will be removed later because Microsoft is deprecating basic authentication."] -[source,text] --------------------------------------------------- -config: - service: other - host: - port: 465 - secure: true - from: <1> -secrets: - user: <2> - password: --------------------------------------------------- -<1> Some organizations configure Exchange to validate that the `from` field is a - valid local email account. -<2> Many organizations support use of your email address as your username. - Check with your system administrator if you receive - authentication-related failures. - To prepare for the removal of Basic Auth, you must update all existing Microsoft Exchange connectors with the new configuration based on the https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow[OAuth 2.0 Client Credentials Authentication]. [float] [[exchange]] -==== Sending email from Microsoft Exchange with OAuth 2.0 +==== Microsoft Exchange with OAuth 2.0 NOTE: The email connector uses Microsoft Graph REST API v1.0, in particular the https://docs.microsoft.com/en-us/graph/api/user-sendmail[sendMail] endpoint. It supports only the https://learn.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints[Microsoft Graph global service] root endpoint (`https://graph.microsoft.com`). @@ -396,9 +251,9 @@ image::management/connectors/images/exchange-granted.png[MS Exchange grant confi [float] [[exchange-client-secret]] -===== Configure Microsoft Exchange Client secret +===== Configure the Microsoft Exchange Client secret -To configure the Client secret , open *Manage > Certificates & secrets*. +To configure the Microsoft Exchange Client secret, open *Manage > Certificates & secrets*: [role="screenshot"] image::management/connectors/images/exchange-secrets.png[MS Exchange secrets configuration] @@ -408,29 +263,12 @@ the Microsoft Exchange email connector. [float] [[exchange-client-tenant-id]] -===== Configure Microsoft Exchange Client ID and Tenant ID +===== Configure the Microsoft Exchange client and tenant identifiers -To find the application Client ID, open the *Overview* page. +To find the Microsoft Exchange client and tenant IDs, open the *Overview* page: [role="screenshot"] image::management/connectors/images/exchange-client-tenant.png[MS Exchange Client ID and Tenant ID configuration] -Copy and paste this values to the proper fields in the Microsoft Exchange email -connector. - -Use the following email connector configuration to send email from Microsoft -Exchange: - -[source,text] --------------------------------------------------- -config: - service: exchange_server - clientId: <1> - tenantId: - from: <2> -secrets: - clientSecret: --------------------------------------------------- -<1> This application information is on the https://go.microsoft.com/fwlink/?linkid=2083908[Azure portal – App registrations]. -<2> Some organizations configure Exchange to validate that the `from` field is a - valid local email account. +Create a connector and set the **Service** to `MS Exchange Server`. +Copy and paste the values into the proper fields. \ No newline at end of file diff --git a/docs/management/connectors/action-types/gen-ai.asciidoc b/docs/management/connectors/action-types/gen-ai.asciidoc index 749e3dcd2c1e9..5e1b3553309ac 100644 --- a/docs/management/connectors/action-types/gen-ai.asciidoc +++ b/docs/management/connectors/action-types/gen-ai.asciidoc @@ -3,6 +3,11 @@ ++++ Generative AI ++++ +:frontmatter-description: Add a connector that can send requests to an OpenAI provider. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] + The Generative AI connector uses https://github.com/axios/axios[axios] to send a POST request to an OpenAI provider, either OpenAI or Azure OpenAI. The connector uses the <> to send the request. @@ -14,6 +19,7 @@ You can create connectors in *{stack-manage-app} > {connectors-ui}*. For exampl [role="screenshot"] image::management/connectors/images/gen-ai-connector.png[Generative AI connector] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [float] [[gen-ai-connector-configuration]] @@ -26,36 +32,6 @@ API Provider:: The OpenAI API provider, either OpenAI or Azure OpenAI. API URL:: The OpenAI request URL. API Key:: The OpenAI or Azure OpenAI API key for authentication. -[float] -[[preconfigured-gen-ai-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-gen-ai: - name: preconfigured-gen-ai-connector-type - actionTypeId: .gen-ai - config: - apiUrl: https://api.openai.com/v1/chat/completions - apiProvider: 'Azure OpenAI' - secrets: - apiKey: superlongapikey --- - -Config defines information for the connector type. - -`apiProvider`:: A string that corresponds to *OpenAI API Provider*. -`apiUrl`:: A URL string that corresponds to the *OpenAI API URL*. - -Secrets defines sensitive information for the connector type. - -`apiKey`:: A string that corresponds to *OpenAI API Key*. - [float] [[gen-ai-action-configuration]] === Test connectors @@ -65,6 +41,7 @@ as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/gen-ai-params-test.png[Generative AI params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. The Generative AI actions have the following configuration properties. diff --git a/docs/management/connectors/action-types/index.asciidoc b/docs/management/connectors/action-types/index.asciidoc index 87c7494df23ea..5a09ab2d23c5f 100644 --- a/docs/management/connectors/action-types/index.asciidoc +++ b/docs/management/connectors/action-types/index.asciidoc @@ -3,13 +3,13 @@ ++++ Index ++++ +:frontmatter-description: Add a connector that can add documents to {es} indices. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] -An index connector indexes a document into {es}. -You can create index connectors in {kib} or by using the -<>. Alternatively, you can use the <>. -If you are running {kib} on-prem, you can also create more preconfigured index -connectors. +An index connector indexes a document into {es}. [float] [[define-index-ui]] @@ -26,72 +26,14 @@ image::management/connectors/images/index-connector.png[Index connector] [[index-connector-configuration]] ==== Connector configuration -Index connectors must have a name and an {es} index. You can optionally set the time field, which contains the -details about when each alert condition was detected. - -[float] -[[preconfigured-index-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-index: - name: preconfigured-index-connector-type - actionTypeId: .index - config: - index: .kibana - executionTimeField: my-field --- - -For more information, go to <>. - -[float] -[[preconfigured-connector-alert-history]] -==== Preconfigured alert history {es} index connector - -preview::[] - -{kib} offers a preconfigured index connector to facilitate indexing active alert -data into {es}. - -To use this connector, set -<> to `true`. - -When you subsequently create rules, you can use the -`Alert history Elasticsearch index (preconfigured)` connector. - -[role="screenshot"] -image::images/pre-configured-alert-history-connector.png[Select pre-configured alert history connectors] - -Documents are indexed using a preconfigured schema that captures the -<> available for the rule. -By default, these documents are indexed into the `kibana-alert-history-default` -index, but you can specify a different index. Index names must start with -`kibana-alert-history-` to take advantage of the preconfigured alert history -index template. - -[IMPORTANT] -==== -* To write documents to the preconfigured index, you must have `all` or `write` -privileges to the `kibana-alert-history-*` indices. Refer to -<> for more information. -* The `kibana-alert-history-*` indices are not configured to use ILM so they must -be maintained manually. If the index size grows large, consider using the -{ref}/docs-delete-by-query.html[delete by query] API to clean up older documents -in the index. -==== +Index connectors must have a name and an {es} index. +You can optionally choose a field that indicates when the document was indexed. [float] [[index-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/index-params-test.png[Index params test] diff --git a/docs/management/connectors/action-types/jira.asciidoc b/docs/management/connectors/action-types/jira.asciidoc index e57fdd87b432f..9833d2a08ec71 100644 --- a/docs/management/connectors/action-types/jira.asciidoc +++ b/docs/management/connectors/action-types/jira.asciidoc @@ -25,6 +25,7 @@ or as needed when you're creating a rule. For example: [role="screenshot"] image::management/connectors/images/jira-connector.png[Jira connector] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [float] [[jira-connector-configuration]] @@ -38,38 +39,6 @@ Project key:: Jira project key. Email:: The account email for HTTP Basic authentication. API token:: Jira API authentication token for HTTP Basic authentication. -[float] -[[preconfigured-jira-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-jira: - name: preconfigured-jira-connector-type - actionTypeId: .jira - config: - apiUrl: https://elastic.atlassian.net - projectKey: ES - secrets: - email: testuser - apiToken: tokenkeystorevalue --- - -Config defines information for the connector type. - -`apiUrl`:: An address that corresponds to *URL*. -`projectKey`:: A key that corresponds to *Project Key*. - -Secrets defines sensitive information for the connector type. - -`email`:: A string that corresponds to *Email*. -`apiToken`:: A string that corresponds to *API Token*. Should be stored in the <>. - [float] [[jira-action-configuration]] === Test connectors @@ -77,9 +46,9 @@ Secrets defines sensitive information for the connector type. You can test connectors with the <> or as you're creating or editing the connector in {kib}. For example: - [role="screenshot"] image::management/connectors/images/jira-params-test.png[Jira params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. Jira actions have the following configuration properties. diff --git a/docs/management/connectors/action-types/opsgenie.asciidoc b/docs/management/connectors/action-types/opsgenie.asciidoc index 453aa8c00b811..79c03edf964cc 100644 --- a/docs/management/connectors/action-types/opsgenie.asciidoc +++ b/docs/management/connectors/action-types/opsgenie.asciidoc @@ -3,8 +3,16 @@ ++++ Opsgenie ++++ +:frontmatter-description: Add a connector that can create and close alerts in {opsgenie}. +:frontmatter-tags-products: [alerting] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] -The Opsgenie connector uses the https://docs.opsgenie.com/docs/alert-api[Opsgenie alert API]. +An {opsgenie} connector enables you to create and close alerts in {opsgenie}. +In particular, it uses the https://docs.opsgenie.com/docs/alert-api[{opsgenie} alert API]. + +To create this connector, you must have a valid {opsgenie} URL and API key. +For configuration tips, refer to <>. [float] [[define-opsgenie-ui]] @@ -15,6 +23,7 @@ or as needed when you're creating a rule. For example: [role="screenshot"] image::management/connectors/images/opsgenie-connector.png[Opsgenie connector] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [float] [[opsgenie-connector-configuration]] @@ -26,55 +35,28 @@ Name:: The name of the connector. The name is used to identify a connector in th URL:: The Opsgenie URL. For example, https://api.opsgenie.com or https://api.eu.opsgenie.com. + NOTE: If you are using the <> setting, make sure the hostname is added to the allowed hosts. -API Key:: The Opsgenie API authentication key for HTTP Basic authentication. For more details about generating Opsgenie API keys, refer to https://support.atlassian.com/opsgenie/docs/create-a-default-api-integration/[Opsgenie documentation]. - -[float] -[[preconfigured-opsgenie-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-opsgenie: - name: preconfigured-opsgenie-connector-type - actionTypeId: .opsgenie - config: - apiUrl: https://api.opsgenie.com - secrets: - apiKey: apikey --- - -Config defines information for the connector type. - -`apiUrl`:: A string that corresponds to *URL*. - -Secrets defines sensitive information for the connector type. -`apiKey`:: A string that corresponds to *API Key*. +API Key:: The Opsgenie API authentication key for HTTP basic authentication. For more details about generating Opsgenie API keys, refer to https://support.atlassian.com/opsgenie/docs/create-a-default-api-integration/[Opsgenie documentation]. [float] [[opsgenie-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +After you create a connector, use the *Test* tab to test its actions: -[role="screenshot"] -image::management/connectors/images/opsgenie-params-test.png[Opsgenie params test] - -The Opsgenie connector supports two types of actions: Create alert and Close alert. The properties supported for each action are different because Opsgenie defines different properties for each operation. - -When testing the Opsgenie connector, choose the appropriate action from the selector. Each action has different properties that can be configured. - -Action:: Select *Create alert* to configure the actions that occur when a rule's conditions are met. Select *Close alert* to define the recovery actions that occur when a rule's conditions are no longer met. +* <> +* <> [float] [[opsgenie-action-create-alert-configuration]] -==== Configure the create alert action +==== Create alert action + +When you create a rule that uses an {opsgenie} connector, its actions (with the exception of recovery actions) create {opsgenie} alerts. +You can test this type of action when you create or edit your connector: + +[role="screenshot"] +image::management/connectors/images/opsgenie-create-alert-test.png[{opsgenie} create alert action test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. You can configure the create alert action through the form view or using a JSON editor. @@ -141,7 +123,14 @@ Example JSON editor contents [float] [[opsgenie-action-close-alert-configuration]] -==== Close alert configuration +==== Close alert action + +When you create a rule that uses an {opsgenie} connector, its recovery actions close {opsgenie} alerts. +You can test this type of action when you create or edit your connector: + +[role="screenshot"] +image::management/connectors/images/opsgenie-close-alert-test.png[{opsgenie} close alert action test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. The close alert action has the following configuration properties. diff --git a/docs/management/connectors/action-types/pagerduty.asciidoc b/docs/management/connectors/action-types/pagerduty.asciidoc index ed98e4259d0eb..0a7cf2b584d11 100644 --- a/docs/management/connectors/action-types/pagerduty.asciidoc +++ b/docs/management/connectors/action-types/pagerduty.asciidoc @@ -3,8 +3,16 @@ ++++ PagerDuty ++++ +:frontmatter-description: Add a connector that can manage PagerDuty alerts. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] -The PagerDuty connector uses the https://v2.developer.pagerduty.com/docs/events-api-v2[v2 Events API] to trigger, acknowledge, and resolve PagerDuty alerts. +The PagerDuty connector enables you to trigger, acknowledge, and resolve PagerDuty alerts. +In particular, it uses the https://v2.developer.pagerduty.com/docs/events-api-v2[v2 Events API]. + +To create this connector, you must have a valid PagerDuty integration key. +For configuration tips, refer to <> [float] [[define-pagerduty-ui]] @@ -15,6 +23,7 @@ or as needed when you're creating a rule. For example: [role="screenshot"] image::management/connectors/images/pagerduty-connector.png[PagerDuty connector] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [float] [[pagerduty-connector-configuration]] @@ -27,54 +36,61 @@ API URL:: An optional PagerDuty event URL. Defaults to `https://events.pagerdu Integration Key:: A 32 character PagerDuty Integration Key for an integration on a service, also referred to as the routing key. [float] -[[preconfigured-pagerduty-configuration]] -=== Create preconfigured connectors +[[pagerduty-action-configuration]] +=== Test connectors -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: +After you create a connector, use the *Test* tab to test its actions: -[source,text] --- -xpack.actions.preconfigured: - my-pagerduty: - name: preconfigured-pagerduty-connector-type - actionTypeId: .pagerduty - config: - apiUrl: https://test.host - secrets: - routingKey: testroutingkey --- +* <> +* <> +* <> -Config defines information for the connector type. +When you create a rule that uses a PagerDuty connector, you can use any of these types of actions. +Rule recovery actions also support all types. -`apiURL`:: A URL string that corresponds to *API URL*. +[float] +[[pagerduty-action-acknowledge]] +==== Acknowledge action -Secrets defines sensitive information for the connector type. +When you test the acknowlege action, you must provide the de-duplication key for a PagerDuty alert: -`routingKey`:: A string that corresponds to *Integration Key*. +[role="screenshot"] +image::management/connectors/images/pagerduty-acknowledge-test.png[PagerDuty params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [float] -[[pagerduty-action-configuration]] -=== Test connectors +[[pagerduty-action-resolve]] +==== Resolve action -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +Likewise when you test the resolve action, you must provide the de-duplication key: [role="screenshot"] -image::management/connectors/images/pagerduty-params-test.png[PagerDuty params test] +image::management/connectors/images/pagerduty-resolve-test.png[PagerDuty params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -PagerDuty actions have the following properties. +[float] +[[pagerduty-action-trigger]] +==== Trigger action + +When you test the trigger action, you must provide a summary for the PagerDuty alert: + +[role="screenshot"] +image::management/connectors/images/pagerduty-trigger-test.png[PagerDuty params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +This action has the following properties: Severity:: The perceived severity of on the affected system. This can be one of `Critical`, `Error`, `Warning` or `Info`(default). Event action:: One of `Trigger` (default), `Resolve`, or `Acknowledge`. See https://v2.developer.pagerduty.com/docs/events-api-v2#event-action[event action] for more details. -Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is *optional*, and if not set, defaults to `:`. The maximum length is *255* characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. -Timestamp:: An *optional* https://v2.developer.pagerduty.com/v2/docs/types#datetime[ISO-8601 format date-time], indicating the time the event was detected or generated. -Component:: An *optional* value indicating the component of the source machine that is responsible for the event, for example `mysql` or `eth0`. -Group:: An *optional* value indicating the logical grouping of components of a service, for example `app-stack`. -Source:: An *optional* value indicating the affected system, preferably a hostname or fully qualified domain name. Defaults to the {kib} saved object id of the action. -Summary:: An *optional* text summary of the event, defaults to `No summary provided`. The maximum length is 1024 characters. -Class:: An *optional* value indicating the class/type of the event, for example `ping failure` or `cpu load`. +Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is optional, and if not set, defaults to `:`. The maximum length is 255 characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. ++ +By default, when you create rules that use the PagerDuty connector, the de-duplication key is used to create a new PagerDuty incident for each alert and reuse the incident when a recovered alert reactivates. +Timestamp:: An optional https://v2.developer.pagerduty.com/v2/docs/types#datetime[ISO-8601 format date-time], indicating the time the event was detected or generated. +Component:: An optional value indicating the component of the source machine that is responsible for the event, for example `mysql` or `eth0`. +Group:: An optional value indicating the logical grouping of components of a service, for example `app-stack`. +Source:: An optional value indicating the affected system, preferably a hostname or fully qualified domain name. Defaults to the {kib} saved object id of the action. +Summary:: An optional text summary of the event, defaults to `No summary provided`. The maximum length is 1024 characters. +Class:: An optional value indicating the class/type of the event, for example `ping failure` or `cpu load`. For more details on these properties, see https://v2.developer.pagerduty.com/v2/docs/send-an-event-events-api-v2[PagerDuty v2 event parameters]. @@ -84,7 +100,6 @@ For more details on these properties, see https://v2.developer.pagerduty.com/v2/ Use the <> to customize connector networking configurations, such as proxies, certificates, or TLS settings. You can set configurations that apply to all your connectors or use `xpack.actions.customHostSettings` to set per-host configurations. - [float] [[pagerduty-benefits]] === Configure PagerDuty @@ -92,22 +107,11 @@ Use the <> to customize connecto By integrating PagerDuty with rules, you can: * Route your rules to the right PagerDuty responder within your team, based on your structure, escalation policies, and workflows. -* Automatically generate incidents of different types and severity based on each rule’s context. -* Tailor the incident data to match your needs by easily passing the rule context from Kibana to PagerDuty. - -[float] -[[pagerduty-support]] -=== Support -If you need help with this integration, get in touch with the {kib} team by visiting -https://support.elastic.co[support.elastic.co] or by using the *Ask Elastic* option in the {kib} Help menu. -You can also select the {kib} category at https://discuss.elastic.co/[discuss.elastic.co]. - -[float] -[[pagerduty-integration-walkthrough]] -=== Integration with PagerDuty walkthrough +* Automatically generate incidents of different types and severity based on each rule's context. +* Tailor the incident data to match your needs by easily passing the rule context from {kib} to PagerDuty. [[pagerduty-in-pagerduty]] -*In PagerDuty* +To set up PagerDuty: . From the *Configuration* menu, select *Services*. . Add an integration to a service: @@ -118,8 +122,8 @@ Then, select the *Integrations* tab and click the *New Integration* button. * If you are creating a new service for your integration, go to https://support.pagerduty.com/docs/services-and-integrations#section-configuring-services-and-integrations[Configuring Services and Integrations] -and follow the steps outlined in the *Create a New Service* section, selecting *Elastic Alerts* as the *Integration Type* in step 4. -Continue with the <> section once you have finished these steps. +and follow the steps outlined in the *Create a New Service* section, selecting *Elastic Alerts* as the *Integration Type*. +Continue with the connector creation in {kib} after you have finished these steps. . Enter an *Integration Name* in the format Elastic-service-name (for example, Elastic-Alerting or Kibana-APM-Alerting) and select *Elastic Alerts* from the *Integration Type* menu. @@ -130,33 +134,4 @@ You will be redirected to the *Integrations* tab for your service. An Integratio [role="screenshot"] image::images/pagerduty-integration.png[PagerDuty Integrations tab] -. Save this key, as you will use it when you configure the integration with Elastic in the next section. - -[[pagerduty-in-elastic]] -*In Elastic* - -. Create a PagerDuty connector in Kibana. You can: -+ -* Create a connector as part of creating an rule by selecting PagerDuty in the *Actions* -section of the rule configuration and selecting *Add new*. -* Alternatively, create a connector. To create a connector, go to *{stack-manage-app} > {connectors-ui}*, click *Create connector*, then select the PagerDuty option. - -. Configure the connector by giving it a name and entering the Integration Key, optionally entering a custom API URL. -+ -See <> for how to obtain the endpoint and key information from PagerDuty and -<> for more details. - -. Save the connector. - -. To create a rule, go to *{stack-manage-app} > {rules-ui}* or the application of your choice. - -. Set up an action using your PagerDuty connector, by determining: -+ -* The action's type: Trigger, Resolve, or Acknowledge. -* The event's severity: Info, warning, error, or critical. -* An array of different fields, including the timestamp, group, class, component, and your dedup key. By default, the dedup is configured to create a new PagerDuty incident for each alert and reuse the incident when a recovered alert reactivates. -Depending on your custom needs, assign them variables from the rule context. -To see the available context variables, click on the *Add variable* icon next -to each corresponding field. For more details on these parameters, see the -<> and the PagerDuty -https://v2.developer.pagerduty.com/v2/docs/send-an-event-events-api-v2[API v2 documentation]. +. Save this key for use when you configure the connector in {kib}. diff --git a/docs/management/connectors/action-types/resilient.asciidoc b/docs/management/connectors/action-types/resilient.asciidoc index a317adf3bfe3e..04531a71dee62 100644 --- a/docs/management/connectors/action-types/resilient.asciidoc +++ b/docs/management/connectors/action-types/resilient.asciidoc @@ -1,8 +1,12 @@ [[resilient-action-type]] -== IBM Resilient connector and action +== {ibm-r} connector and action ++++ -IBM Resilient +{ibm-r} ++++ +:frontmatter-description: Add a connector that can create {ibm-r} incidents. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] The IBM Resilient connector uses the https://developer.ibm.com/security/resilient/rest/[RESILIENT REST v2] to create IBM Resilient incidents. @@ -28,38 +32,6 @@ Organization ID:: IBM Resilient organization ID. API key ID:: The authentication key ID for HTTP Basic authentication. API key secret:: The authentication key secret for HTTP Basic authentication. -[float] -[[preconfigured-resilient-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-resilient: - name: preconfigured-resilient-connector-type - actionTypeId: .resilient - config: - apiUrl: https://elastic.resilient.net - orgId: ES - secrets: - apiKeyId: testuser - apiKeySecret: tokenkeystorevalue --- - -Config defines information for the connector type. - -`apiUrl`:: An address that corresponds to *URL*. -`orgId`:: An ID that corresponds to *Organization ID*. - -Secrets defines sensitive information for the connector type. - -`apiKeyId`:: A string that corresponds to *API key ID*. -`apiKeySecret`:: A string that corresponds to *API Key secret*. Should be stored in the <>. - [float] [[resilient-action-configuration]] === Test connectors diff --git a/docs/management/connectors/action-types/slack.asciidoc b/docs/management/connectors/action-types/slack.asciidoc index c5db456fc56a6..c4f1ea6799fb8 100644 --- a/docs/management/connectors/action-types/slack.asciidoc +++ b/docs/management/connectors/action-types/slack.asciidoc @@ -3,6 +3,10 @@ ++++ Slack ++++ +:frontmatter-description: Add a connector that can send Slack messages. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] The Slack connector uses incoming webhooks or an API method to send Slack messages. @@ -28,37 +32,6 @@ Thus a connector can be used in multiple rules and actions to communicate with d For Slack setup details, go to <>. -[float] -[[preconfigured-slack-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. - -.Example Slack connector with webhook -[source,text] --- -xpack.actions.preconfigured: - my-slack: - name: preconfigured-slack-webhook-connector-type - actionTypeId: .slack - secrets: - webhookUrl: 'https://hooks.slack.com/services/xxxx/xxxx/xxxx' <1> --- -<1> To obtain this value, go to <>. - -.Example Slack connector with web API -[source,text] --- -xpack.actions.preconfigured: - my-slack: - name: preconfigured-slack-api-connector-type - actionTypeId: .slack_api - secrets: - token: 'xoxb-xxxx-xxxx-xxxx' <1> --- -<1> To obtain this value, go to <>. - [float] [[slack-action-configuration]] === Test connectors diff --git a/docs/management/connectors/action-types/swimlane.asciidoc b/docs/management/connectors/action-types/swimlane.asciidoc index 18e05b04eca51..227846c7539f3 100644 --- a/docs/management/connectors/action-types/swimlane.asciidoc +++ b/docs/management/connectors/action-types/swimlane.asciidoc @@ -3,6 +3,10 @@ ++++ Swimlane ++++ +:frontmatter-description: Add a connector that can create Swimlane records. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] The Swimlane connector uses the https://swimlane.com/knowledge-center/docs/developer-guide/rest-api/[Swimlane REST API] to create Swimlane records. @@ -25,74 +29,7 @@ Swimlane connectors have the following configuration properties: Name:: The name of the connector. URL:: Swimlane instance URL. Application ID:: Swimlane application ID. -API token:: Swimlane API authentication token for HTTP Basic authentication. - - -[float] -[[preconfigured-swimlane-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-swimlane: - name: preconfigured-swimlane-connector-type - actionTypeId: .swimlane - config: - apiUrl: https://elastic.swimlaneurl.us - appId: app-id - mappings: - alertIdConfig: - fieldType: text - id: agp4s - key: alert-id - name: Alert ID - caseIdConfig: - fieldType: text - id: ae1mi - key: case-id - name: Case ID - caseNameConfig: - fieldType: text - id: anxnr - key: case-name - name: Case Name - commentsConfig: - fieldType: comments - id: au18d - key: comments - name: Comments - descriptionConfig: - fieldType: text - id: ae1gd - key: description - name: Description - ruleNameConfig: - fieldType: text - id: avfsl - key: rule-name - name: Rule Name - severityConfig: - fieldType: text - id: a71ik - key: severity - name: severity - secrets: - apiToken: tokenkeystorevalue --- - -Config defines information for the connector type. - -`apiUrl`:: An address that corresponds to *URL*. -`appId`:: A key that corresponds to *Application ID*. - -Secrets defines sensitive information for the connector type. - -`apiToken`:: A string that corresponds to *API Token*. Should be stored in the <>. +API token:: Swimlane API authentication token for HTTP basic authentication. [float] [[swimlane-action-configuration]] diff --git a/docs/management/connectors/action-types/teams.asciidoc b/docs/management/connectors/action-types/teams.asciidoc index fd4798be97e56..174b0173cb08a 100644 --- a/docs/management/connectors/action-types/teams.asciidoc +++ b/docs/management/connectors/action-types/teams.asciidoc @@ -3,6 +3,10 @@ ++++ Microsoft Teams ++++ +:frontmatter-description: Add a connector that can send messages to a Microsoft Teams channel. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] The Microsoft Teams connector uses https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook[Incoming Webhooks]. @@ -25,28 +29,6 @@ Microsoft Teams connectors have the following configuration properties: Name:: The name of the connector. Webhook URL:: The URL of the incoming webhook. See https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook#add-an-incoming-webhook-to-a-teams-channel[Add Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is added to the allowed hosts. -[float] -[[preconfigured-teams-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -[source,text] --- -xpack.actions.preconfigured: - my-teams: - name: preconfigured-teams-connector-type - actionTypeId: .teams - secrets: - webhookUrl: 'https://outlook.office.com/webhook/abcd@0123456/IncomingWebhook/abcdefgh/ijklmnopqrstuvwxyz' --- - -Secrets defines sensitive information for the connector type. - -`webhookUrl`:: A string that corresponds to *Webhook URL*. - [float] [[teams-action-configuration]] === Test connectors diff --git a/docs/management/connectors/action-types/xmatters.asciidoc b/docs/management/connectors/action-types/xmatters.asciidoc index fce6edd35ab8b..ebc230bf0b39b 100644 --- a/docs/management/connectors/action-types/xmatters.asciidoc +++ b/docs/management/connectors/action-types/xmatters.asciidoc @@ -3,6 +3,10 @@ ++++ xMatters ++++ +:frontmatter-description: Add a connector that can send alerts to xMatters. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] The xMatters connector uses the https://help.xmatters.com/integrations/#cshid=Elastic[xMatters Workflow for Elastic] to send actionable alerts to on-call xMatters resources. @@ -15,9 +19,11 @@ or as needed when you're creating a rule. You must choose between basic and URL [role="screenshot"] image::management/connectors/images/xmatters-connector-basic.png[xMatters connector with basic authentication] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [role="screenshot"] image::management/connectors/images/xmatters-connector-url.png[xMatters connector with url authentication] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [float] [[xmatters-connector-configuration]] @@ -27,59 +33,12 @@ xMatters connectors have the following configuration properties: Name:: The name of the connector. Authentication Type:: The type of authentication used in the request made to xMatters. -URL:: The request URL for the Elastic Alerts trigger in xMatters. If you are using the <> setting, make sure the hostname is added to the allowed hosts. -Username:: Username for HTTP Basic Authentication. -Password:: Password for HTTP Basic Authentication. - -[float] -[[preconfigured-xmatters-configuration]] -=== Create preconfigured connectors - -If you are running {kib} on-prem, you can define connectors by -adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. -For example: - -Connector using Basic Authentication -[source,text] --- -xpack.actions.preconfigured: - my-xmatters: - name: preconfigured-xmatters-connector-type - actionTypeId: .xmatters - config: - configUrl: https://test.host - usesBasic: true - secrets: - user: testuser - password: passwordkeystorevalue --- - -Connector using URL Authentication -[source,text] --- -xpack.actions.preconfigured: - my-xmatters: - name: preconfigured-xmatters-connector-type - actionTypeId: .xmatters - config: - usesBasic: false - secrets: - secretsUrl: https://test.host?apiKey=1234-abcd --- - -Config defines information for the connector type: - -`configUrl`:: A URL string that corresponds to *URL*. Only used if `usesBasic` is true. - -`usesBasic`:: A boolean that corresponds to *Authentication Type*. If `true`, this connector will require values for `user` and `password` inside the secrets configuration. Defaults to `true`. - -Secrets defines sensitive information for the connector type: - -`user`:: A string that corresponds to *User*. Required if `usesBasic` is set to `true`. - -`password`:: A string that corresponds to *Password*. Should be stored in the <>. Required if `usesBasic` is set to `true`. - -`secretsUrl`:: A URL string that corresponds to *URL*. Only used if `usesBasic` is false, indicating the API key is included in the URL. +URL:: +The request URL for the Elastic Alerts trigger in xMatters. +If you are using URL authentication, include the API key in the URL. For example, `https://example.com?apiKey=1234-abcd`. +If you are using the <> setting, make sure the hostname is added to the allowed hosts. +Username:: Username for HTTP basic authentication. +Password:: Password for HTTP basic authentication. [float] [[xmatters-action-configuration]] @@ -90,10 +49,11 @@ as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/xmatters-params-test.png[xMatters params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. xMatters rules have the following properties: -Severity:: Severity of the rule. +Severity:: Severity of the rule. Tags:: Comma-separated list of tags for the rule as provided by the user in Elastic. [float] diff --git a/docs/management/connectors/images/gen-ai-connector.png b/docs/management/connectors/images/gen-ai-connector.png index 7306f6b28383c..e84205723d7cd 100644 Binary files a/docs/management/connectors/images/gen-ai-connector.png and b/docs/management/connectors/images/gen-ai-connector.png differ diff --git a/docs/management/connectors/images/gen-ai-params-test.png b/docs/management/connectors/images/gen-ai-params-test.png index 2a48c4f369ef9..551df4836f957 100644 Binary files a/docs/management/connectors/images/gen-ai-params-test.png and b/docs/management/connectors/images/gen-ai-params-test.png differ diff --git a/docs/management/connectors/images/jira-connector.png b/docs/management/connectors/images/jira-connector.png index fc9a8ab31f876..5060364661296 100644 Binary files a/docs/management/connectors/images/jira-connector.png and b/docs/management/connectors/images/jira-connector.png differ diff --git a/docs/management/connectors/images/jira-params-test.png b/docs/management/connectors/images/jira-params-test.png index 78d51e823fb61..5db0bae22d35f 100644 Binary files a/docs/management/connectors/images/jira-params-test.png and b/docs/management/connectors/images/jira-params-test.png differ diff --git a/docs/management/connectors/images/opsgenie-close-alert-test.png b/docs/management/connectors/images/opsgenie-close-alert-test.png new file mode 100644 index 0000000000000..2892674978234 Binary files /dev/null and b/docs/management/connectors/images/opsgenie-close-alert-test.png differ diff --git a/docs/management/connectors/images/opsgenie-connector.png b/docs/management/connectors/images/opsgenie-connector.png index ccb08b27d6934..29b9db891a5d5 100644 Binary files a/docs/management/connectors/images/opsgenie-connector.png and b/docs/management/connectors/images/opsgenie-connector.png differ diff --git a/docs/management/connectors/images/opsgenie-create-alert-test.png b/docs/management/connectors/images/opsgenie-create-alert-test.png new file mode 100644 index 0000000000000..c727579f89cad Binary files /dev/null and b/docs/management/connectors/images/opsgenie-create-alert-test.png differ diff --git a/docs/management/connectors/images/opsgenie-params-test.png b/docs/management/connectors/images/opsgenie-params-test.png deleted file mode 100644 index f23cff704f440..0000000000000 Binary files a/docs/management/connectors/images/opsgenie-params-test.png and /dev/null differ diff --git a/docs/management/connectors/images/pagerduty-acknowledge-test.png b/docs/management/connectors/images/pagerduty-acknowledge-test.png new file mode 100644 index 0000000000000..e47901f2a90f4 Binary files /dev/null and b/docs/management/connectors/images/pagerduty-acknowledge-test.png differ diff --git a/docs/management/connectors/images/pagerduty-connector.png b/docs/management/connectors/images/pagerduty-connector.png index 2e5d240f42c11..6613c1321f6b5 100644 Binary files a/docs/management/connectors/images/pagerduty-connector.png and b/docs/management/connectors/images/pagerduty-connector.png differ diff --git a/docs/management/connectors/images/pagerduty-params-test.png b/docs/management/connectors/images/pagerduty-params-test.png deleted file mode 100644 index 3fb4a9bb5dc82..0000000000000 Binary files a/docs/management/connectors/images/pagerduty-params-test.png and /dev/null differ diff --git a/docs/management/connectors/images/pagerduty-resolve-test.png b/docs/management/connectors/images/pagerduty-resolve-test.png new file mode 100644 index 0000000000000..37a3c2b2ea895 Binary files /dev/null and b/docs/management/connectors/images/pagerduty-resolve-test.png differ diff --git a/docs/management/connectors/images/pagerduty-trigger-test.png b/docs/management/connectors/images/pagerduty-trigger-test.png new file mode 100644 index 0000000000000..b12f599b58bf7 Binary files /dev/null and b/docs/management/connectors/images/pagerduty-trigger-test.png differ diff --git a/docs/management/connectors/images/pre-configured-alert-history-connector.png b/docs/management/connectors/images/pre-configured-alert-history-connector.png index 98cfbd0f39f7b..dd72a96a761c9 100644 Binary files a/docs/management/connectors/images/pre-configured-alert-history-connector.png and b/docs/management/connectors/images/pre-configured-alert-history-connector.png differ diff --git a/docs/management/connectors/images/xmatters-connector-basic.png b/docs/management/connectors/images/xmatters-connector-basic.png index 7e6437cab9e78..95e5bf8a82b0c 100644 Binary files a/docs/management/connectors/images/xmatters-connector-basic.png and b/docs/management/connectors/images/xmatters-connector-basic.png differ diff --git a/docs/management/connectors/images/xmatters-connector-url.png b/docs/management/connectors/images/xmatters-connector-url.png index a916fd7870fe8..185aaa0223af2 100644 Binary files a/docs/management/connectors/images/xmatters-connector-url.png and b/docs/management/connectors/images/xmatters-connector-url.png differ diff --git a/docs/management/connectors/images/xmatters-params-test.png b/docs/management/connectors/images/xmatters-params-test.png index 5f12050ada953..6d7af4d9a5fce 100644 Binary files a/docs/management/connectors/images/xmatters-params-test.png and b/docs/management/connectors/images/xmatters-params-test.png differ diff --git a/docs/management/connectors/pre-configured-connectors.asciidoc b/docs/management/connectors/pre-configured-connectors.asciidoc index 3584207a88364..72f5f78f6e728 100644 --- a/docs/management/connectors/pre-configured-connectors.asciidoc +++ b/docs/management/connectors/pre-configured-connectors.asciidoc @@ -58,33 +58,372 @@ Sensitive properties, such as passwords, can also be stored in the ============================================== [float] -[[build-in-preconfigured-connectors]] +[[managing-preconfigured-connectors]] +=== View preconfigured connectors + +When you open the main menu, click *{stack-manage-app} > {connectors-ui}*. +Preconfigured connectors appear regardless of which space you are in. +They are tagged as “preconfigured”, and you cannot delete them. + +[role="screenshot"] +image::images/preconfigured-connectors-managing.png[Connectors managing tab with pre-configured] + +Clicking a preconfigured connector shows the description, but not the configuration. + +[float] +[[built-in-preconfigured-connectors]] === Built-in preconfigured connectors {kib} provides the following built-in preconfigured connectors: -* <> +* <> * <> [float] -[[managing-pre-configured-connectors]] -=== View preconfigured connectors +[[preconfigured-connector-alert-history]] +==== Preconfigured alert history {es} index connector -When you open the main menu, click *{stack-manage-app} > {connectors-ui}*. -Preconfigured connectors appear regardless of which space you are -in. They are tagged as “preconfigured”, and you cannot delete them. +preview::[] + +{kib} offers a preconfigured index connector to facilitate indexing active alert data into {es}. +To use this connector, set <> to `true`. + +When you subsequently create rules, you can use the `Alert history Elasticsearch index (preconfigured)` connector. [role="screenshot"] -image::images/preconfigured-connectors-managing.png[Connectors managing tab with pre-configured] +image::images/pre-configured-alert-history-connector.png[Creating a rule action that uses the pre-configured alert history connector] + +Documents are indexed using a preconfigured schema that captures the <> available for the rule. +By default, these documents are indexed into the `kibana-alert-history-default` index, but you can specify a different index. +Index names must start with `kibana-alert-history-` to take advantage of the preconfigured alert history index template. -Clicking a preconfigured connector shows the description, but not the -configuration. +[IMPORTANT] +==== +* To write documents to the preconfigured index, you must have `all` or `write` privileges to the `kibana-alert-history-*` indices. +* The `kibana-alert-history-*` indices are not configured to use ILM so they must be maintained manually. If the index size grows large, consider using the {ref}/docs-delete-by-query.html[delete by query] API to clean up older documents in the index. +==== [float] +[[preconfigured-connector-examples]] === Examples +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> * <> +* <> +* <> * <> +* <> +* <> + +[float] +[[preconfigured-d3security-configuration]] +==== D3 Security connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-d3security: + name: preconfigured-d3security-connector-type + actionTypeId: .d3security + config: + url: https://testurl.com/elasticsearch/VSOC/api/Data/Kibana/Security%20Operations/CreateEvents <1> + secrets: + token: superlongtoken <2> +-- +<1> The D3 Security API request URL. +<2> The D3 Security token. + +[float] +[[preconfigured-email-configuration]] +==== Email connectors + +The following example creates an <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-email: + name: preconfigured-email-connector-type + actionTypeId: .email + config: + service: other <1> + from: testsender@test.com <2> + host: validhostname <3> + port: 8080 <4> + secure: false <5> + hasAuth: true <6> + secrets: + user: testuser <7> + password: passwordkeystorevalue <8> +-- + +<1> The name of the email service. If `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, the `host`, `port`, and `secure` properties are ignored. If `service` is `other`, the `host` and `port` properties must be defined. For more information on the `gmail` service value, refer to https://nodemailer.com/usage/using-gmail/[Nodemailer Gmail documentation]. If `service` is `exchange_server`, the `tenantId`, `clientId`, `clientSecret` +properties are required instead of `host` and `port`. +<2> The email address for all emails sent with this connector. It must be specified in `user@host-name` format. +<3> The host name of the service provider. +<4> The port to connect to on the service provider. +<5> If true, the connection will use TLS when connecting to the service provider. +<6> If `true`, this connector will require values for `user` and `password` inside the secrets configuration. Defaults to `true`. +<7> A user name for authentication. Required if `hasAuth` is set to `true`. +<8> A password for authentication. Should be stored in the <>. Required if `hasAuth` is set to `true`. + + +[float] +[[preconfigured-email-configuration-amazon-ses]] +===== Amazon SES (Simple Email Service) + +Use the following email connector configuration to send email from the +http://aws.amazon.com/ses[Amazon Simple Email Service] (SES) SMTP service: + +[source,text] +-------------------------------------------------- +config: + service: ses + // `host`, `port` and `secure` have the following default values and do not need to set: + // host: email-smtp.us-east-1.amazonaws.com <1> + // port: 465 + // secure: true +secrets: + user: + password: +-------------------------------------------------- +<1> `config.host` varies depending on the region + +[float] +[[preconfigured-email-configuration-gmail]] +===== Gmail + +Use the following email connector configuration to send email from the https://mail.google.com[Gmail] SMTP service: + +[source,text] +-------------------------------------------------- + config: + service: gmail + // `host`, `port` and `secure` have the following default values and do not need to set: + // host: smtp.gmail.com + // port: 465 + // secure: true + secrets: + user: + password: +-------------------------------------------------- + +[float] +[[preconfigured-email-configuration-exchange-basic-auth]] +===== Microsoft Exchange with basic authentication + +deprecated:[7.16.0,"This Microsoft Exchange configuration is deprecated and will be removed later because Microsoft is deprecating basic authentication."] + +[source,text] +-------------------------------------------------- +config: + service: other + host: + port: 465 + secure: true + from: <1> +secrets: + user: <2> + password: +-------------------------------------------------- +<1> Some organizations configure Exchange to validate that the `from` field is a valid local email account. +<2> Many organizations support use of your email address as your username. Check with your system administrator if you receive authentication-related failures. + +[float] +[[preconfigured-email-configuration-exchange]] +===== Microsoft Exchange with OAuth 2.0 + +Use the following email connector configuration to send email from Microsoft Exchange: + +[source,text] +-------------------------------------------------- +config: + service: exchange_server + clientId: <1> + tenantId: + from: <2> +secrets: + clientSecret: +-------------------------------------------------- +<1> This application information is on the https://go.microsoft.com/fwlink/?linkid=2083908[Azure portal – App registrations]. +<2> Some organizations configure Exchange to validate that the `from` field is a valid local email account. + +[float] +[[preconfigured-email-configuration-outlook]] +===== Outlook.com + +Use the following email connector configuration to send email from the +https://www.outlook.com/[Outlook.com] SMTP service: + +[source,text] +-------------------------------------------------- +config: + service: outlook365 + // `host`, `port` and `secure` have the following default values and do not need to set: + // host: smtp.office365.com + // port: 587 + // secure: false +secrets: + user: + password: +-------------------------------------------------- + +[float] +[[preconfigured-gen-ai-configuration]] +==== Generative AI connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-gen-ai: + name: preconfigured-gen-ai-connector-type + actionTypeId: .gen-ai + config: + apiUrl: https://api.openai.com/v1/chat/completions <1> + apiProvider: 'OpenAI' <2> + defaultModel: gpt-4 <3> + secrets: + apiKey: superlongapikey <4> +-- +<1> The OpenAI request URL +<2> The OpenAI API provider, either `OpenAI` or `Azure OpenAI`. +<3> The default model to use for requests. This setting is optional and applicable only when `apiProvider` is `OpenAI`. +<4> The OpenAI or Azure OpenAI API key for authentication. + +[float] +[[preconfigured-resilient-configuration]] +==== {ibm-r} connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-resilient: + name: preconfigured-resilient-connector-type + actionTypeId: .resilient + config: + apiUrl: https://elastic.resilient.net <1> + orgId: ES <2> + secrets: + apiKeyId: testuser <3> + apiKeySecret: tokenkeystorevalue <4> +-- +<1> The {ibm-r} instance URL. +<2> The {ibm-r} organization identifier. +<3> The authentication key ID for HTTP basic authentication. +<4> The authentication key secret for HTTP basic authentication. NOTE: This value should be stored in the <>. + +[float] +[[preconfigured-index-configuration]] +==== Index connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-index: + name: preconfigured-index-connector-type + actionTypeId: .index + config: + index: .kibana <1> + executionTimeField: my-field <2> +-- +<1> The {es} index to be written to. +<2> A field that indicates when the document was indexed. + +[float] +[[preconfigured-jira-configuration]] +==== Jira connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-jira: + name: preconfigured-jira-connector-type + actionTypeId: .jira + config: + apiUrl: https://elastic.atlassian.net <1> + projectKey: ES <2> + secrets: + email: testuser <3> + apiToken: tokenkeystorevalue <4> +-- +<1> The Jira instance URL. +<2> The Jira project key. +<3> The account email for HTTP basic authentication. +<4> The API authentication token for HTTP basic authentication. NOTE: This value should be stored in the <>. + +[float] +[[preconfigured-teams-configuration]] +==== Microsoft Teams connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-teams: + name: preconfigured-teams-connector-type + actionTypeId: .teams + secrets: + webhookUrl: 'https://outlook.office.com/webhook/abcd@0123456/IncomingWebhook/abcdefgh/ijklmnopqrstuvwxyz' <1> +-- +<1> The URL of the incoming webhook. + +[float] +[[preconfigured-opsgenie-configuration]] +==== {opsgenie} connectors + +The following example creates an <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-opsgenie: + name: preconfigured-opsgenie-connector-type + actionTypeId: .opsgenie + config: + apiUrl: https://api.opsgenie.com <1> + secrets: + apiKey: apikey <2> +-- +<1> The {opsgenie} URL. +<2> The {opsgenie} API authentication key for HTTP basic authentication. + +[float] +[[preconfigured-pagerduty-configuration]] +==== PagerDuty connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-pagerduty: + name: preconfigured-pagerduty-connector-type + actionTypeId: .pagerduty + config: + apiUrl: https://test.host <1> + secrets: + routingKey: testroutingkey <2> +-- +<1> The PagerDuty event URL. +<2> A 32 character PagerDuty Integration Key for an integration on a service, also referred to as the routing key. [float] [[preconfigured-server-log-configuration]] @@ -100,6 +439,95 @@ xpack.actions.preconfigured: actionTypeId: .server-log -- +[float] +[[preconfigured-slack-configuration]] +==== Slack connectors + +The following example creates a <> with webhook: + +[source,text] +-- +xpack.actions.preconfigured: + my-slack: + name: preconfigured-slack-webhook-connector-type + actionTypeId: .slack + secrets: + webhookUrl: 'https://hooks.slack.com/services/xxxx/xxxx/xxxx' <1> +-- +<1> The Slack webhook URL. + +The following example creates a Slack connector with web API: + +[source,text] +-- +xpack.actions.preconfigured: + my-slack: + name: preconfigured-slack-api-connector-type + actionTypeId: .slack_api + secrets: + token: 'xoxb-xxxx-xxxx-xxxx' <1> +-- +<1> The Slack bot user OAuth token. + +[float] +[[preconfigured-swimlane-configuration]] +==== Swimlane connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-swimlane: + name: preconfigured-swimlane-connector-type + actionTypeId: .swimlane + config: + apiUrl: https://elastic.swimlaneurl.us <1> + appId: app-id <2> + mappings: <3> + alertIdConfig: + fieldType: text + id: agp4s + key: alert-id + name: Alert ID + caseIdConfig: + fieldType: text + id: ae1mi + key: case-id + name: Case ID + caseNameConfig: + fieldType: text + id: anxnr + key: case-name + name: Case Name + commentsConfig: + fieldType: comments + id: au18d + key: comments + name: Comments + descriptionConfig: + fieldType: text + id: ae1gd + key: description + name: Description + ruleNameConfig: + fieldType: text + id: avfsl + key: rule-name + name: Rule Name + severityConfig: + fieldType: text + id: a71ik + key: severity + name: severity + secrets: + apiToken: tokenkeystorevalue <4> +-- +<1> The {swimlane} instance URL. +<2> The {swimlane} application identifier. +<3> Field mappings for properties such as the alert identifer, severity, and rule name. +<4> The API authentication token for HTTP basic authentication. NOTE: This value should be stored in the <>. + [float] [[preconfigured-webhook-configuration]] ==== Webhook connectors @@ -130,4 +558,94 @@ xpack.actions.preconfigured: <5> A valid user name. Required if `hasAuth` is set to `true`. <6> A valid password. Required if `hasAuth` is set to `true`. NOTE: This value should be stored in the <>. -NOTE: SSL authentication is not supported in preconfigured webhook connectors. \ No newline at end of file +NOTE: SSL authentication is not supported in preconfigured webhook connectors. + + +[float] +[[preconfigured-cases-webhook-configuration]] +==== {webhook-cm} connectors + +The following example creates a <>: + +[source,text] +-- +xpack.actions.preconfigured: + my-case-management-webhook: + name: Case Management Webhook Connector + actionTypeId: .cases-webhook + config: + hasAuth: true <1> + headers: <2> + 'content-type': 'application/json' + createIncidentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue' <3> + createIncidentMethod: 'post' <4> + createIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"labels":{{{case.tags}}}' <5> + getIncidentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}}' <6> + getIncidentResponseExternalTitleKey: 'key' <7> + viewIncidentUrl: 'https://testing-jira.atlassian.net/browse/{{{external.system.title}}}' <8> + updateIncidentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}}' <9> + updateIncidentMethod: 'put' <10> + updateIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"labels":{{{case.tags}}}' <11> + createCommentMethod: 'post', <12> + createCommentUrl: 'https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}}/comment', <13> + createCommentJson: '{"body": {{{case.comment}}}}', <14> + secrets: + user: testuser <15> + password: passwordvalue <16> +-- +<1> If `true`, this connector will require values for `user` and `password` inside the secrets configuration. +<2> A set of key-value pairs sent as headers with the request. +<3> A REST API URL string to create a case in the third-party system. +<4> The REST API HTTP request method to create a case in the third-party system. +<5> A stringified JSON payload with Mustache variables that is sent to the create case URL to create a case. +<6> A REST API URL string with an external service ID Mustache variable to get the case from the third-party system. +<7> A string from the response body of the get case method that corresponds to the external service title. +<8> A URL string with either the external service ID or external service title Mustache variable to view a case in the external system. +<9> The REST API URL to update the case by ID in the third-party system. +<10> The REST API HTTP request method to update the case in the third-party system. +<11> A stringified JSON payload with Mustache variables that is sent to the update case URL to update a case. +<12> The REST API HTTP request method to create a case comment in the third-party system. +<13> A REST API URL string to create a case comment by ID in the third-party system. +<14> A stringified JSON payload with Mustache variables that is sent to the create comment URL to create a case comment. +<15> A user name, which is required when `hasAuth` is `true`. +<16> A password, which is required when `hasAuth` is `true`. + +[float] +[[preconfigured-xmatters-configuration]] +==== xMatters connectors + +The following example creates an <> with basic authentication: + +[source,text] +-- +xpack.actions.preconfigured: +my-xmatters: + name: preconfigured-xmatters-connector-type + actionTypeId: .xmatters + config: + configUrl: https://test.host <1> + usesBasic: true <2> + secrets: + user: testuser <3> + password: passwordkeystorevalue <4> +-- +<1> The request URL for the Elastic Alerts trigger in xMatters. +<2> Indicates whether the connector uses HTTP basic authentication. If `true`, you must provide `user` and `password` values. Defaults to `true`. +<3> A user name for authentication, which is required when `usesBasic` is `true`. +<4> A password for authentication, which is required when `usesBasic` is `true`. NOTE: This value should be stored in the <>. + +The following example creates an xMatters connector with URL authentication: + +[source,text] +-- +xpack.actions.preconfigured: + my-xmatters: + name: preconfigured-xmatters-connector-type + actionTypeId: .xmatters + config: + usesBasic: false <1> + secrets: + secretsUrl: https://test.host?apiKey=1234-abcd <2> +-- +<1> Indicates whether the connector uses HTTP basic authentication. Set to `false` to use URL authentication. Defaults to `true`. +<2> The request URL for the Elastic Alerts trigger in xMatters with the API key included in the URL. diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 1928cfb97bdfe..f4ac0f11739b0 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -291,7 +291,7 @@ This page has been moved. Refer to <>. [[development-plugin-localization]] === Localization for plugins -This page has been moved. PRefer to <>. +This page has been moved. Refer to <>. [role="exclude",id="visualize"] == Visualize @@ -417,3 +417,8 @@ This page has been deleted. Refer to <>. == Alerts and Actions This page has been deleted. Refer to <>. + +[role="exclude",id="enhancements-and-bug-fixes-v8.10.0"] +== Enhancements and bug fixes for 8.10.0 + +This content has moved. Refer to <> for 8.10.0. diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index c48483224ec52..c9880bdade4dc 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -227,11 +227,14 @@ xpack.actions.run: maxAttempts: 5 -- +`xpack.actions.queued.max` {ess-icon}:: +Specifies the maximum number of actions that can be queued. Default: 1000000 + [float] [[preconfigured-connector-settings]] === Preconfigured connector settings -These settings vary depending on which type of <> you're adding. +These settings vary depending on which type of preconfigured connector you're adding. For example: [source,yaml] @@ -242,6 +245,8 @@ xpack.actions.preconfigured: actionTypeId: .server-log ---------------------------------------- +For more examples, go to <>. + `xpack.actions.preconfiguredAlertHistoryEsIndex` {ess-icon}:: Enables a preconfigured alert history {es} <> connector. Default: `false`. @@ -252,9 +257,261 @@ Specifies configuration details that are specific to the type of preconfigured c The type of preconfigured connector. For example: `.email`, `.index`, `.opsgenie`, `.server-log`, `.resilient`, `.slack`, and `.webhook`. +`xpack.actions.preconfigured..config`:: +The configuration details, which are specific to the type of preconfigured connector. + +`xpack.actions.preconfigured..config.apiProvider`:: +For a <>, specifies the OpenAI API provider, either `OpenAI` or `Azure OpenAI`. + +`xpack.actions.preconfigured..config.apiUrl`:: +A configuration URL that varies by connector: ++ +-- +* For a <>, specifies the OpenAI request URL. +* For a <>, specifies the {ibm-r} instance URL. +* For a <>, specifies the Jira instance URL. +* For an <>, specifies the {opsgenie} URL. For example, `https://api.opsgenie.com` or `https://api.eu.opsgenie.com`. +* For a <>, specifies the PagerDuty event URL. Defaults to `https://events.pagerduty.com/v2/enqueue`. +* For a <>, specifies the {swimlane} instance URL. + +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure the hostname in the URL is added to the allowed hosts. +-- + +`xpack.actions.preconfigured..config.appId`:: +An application ID that varies by connector: ++ +-- +* For a <>, specifies a {swimlane} application identifier. +-- + +`xpack.actions.preconfigured..config.clientId`:: +For an <>, specifies a GUID format value that corresponds to the client ID, which is a part of OAuth 2.0 client credentials authentication. + +`xpack.actions.preconfigured..config.configUrl`:: +For an <> with basic authentication, specifies the request URL for the Elastic Alerts trigger in xMatters. + +`xpack.actions.preconfigured..config.createCommentJson`:: +For a <>, specifies a stringified JSON payload with Mustache variables that is sent to the create comment URL to create a case comment. The required variable is `case.description`. ++ +NOTE: The JSON is validated after the Mustache variables have been placed when the REST method runs. You should manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. + +`xpack.actions.preconfigured..config.createCommentMethod`:: +For a <>, specifies the REST API HTTP request method to create a case comment in the third-party system. +For example: `post`, `put`(default), or `patch`. + +`xpack.actions.preconfigured..config.createCommentUrl`:: +For a <>, specifies a REST API URL string to create a case comment by ID in the third-party system. ++ +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure the hostname in the URL is added to the allowed hosts. + +`xpack.actions.preconfigured..config.createIncidentJson`:: +For a <>, specifies a stringified JSON payload with Mustache variables that is sent to the create case URL to create a case. Required variables are `case.title` and `case.description`. ++ +NOTE: The JSON is validated after the Mustache variables have been placed when the REST method runs. You should manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. + +`xpack.actions.preconfigured..config.createIncidentMethod`:: +For a <>, specifies the REST API HTTP request method to create a case in the third-party system, either `post`(default), `put`, or `patch`. + +`xpack.actions.preconfigured..config.createIncidentUrl`:: +For a <>, specifies a REST API URL string to create a case in the third-party system. ++ +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure the hostname in the URL is added to the allowed hosts. + +`xpack.actions.preconfigured..config.createIncidentResponseKey`:: +For a <>, specifies a string from the response body of the create case method that corresponds to the external service identifier. + +`xpack.actions.preconfigured..config.defaultModel`:: +For a <>, specifies the default model to use for requests. It is optional and applicable only when `xpack.actions.preconfigured..config.apiProvider` is `OpenAI`. + +`xpack.actions.preconfigured..config.executionTimeField`:: +For an <>, a field that indicates when the document was indexed. + +`xpack.actions.preconfigured..config.from`:: +For an <>, specifies the from address for all emails sent by the connector. +It must be specified in `user@host-name` format. + +`xpack.actions.preconfigured..config.getIncidentResponseExternalTitleKey`:: +For a <>, specifies a string from the response body of the get case method that corresponds to the external service title. + +`xpack.actions.preconfigured..config.getIncidentUrl`:: +For a <>, specifies a REST API URL string with an external service ID Mustache variable to get the case from the third-party system. ++ +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure the hostname in the URL is added to the allowed hosts. + +`xpack.actions.preconfigured..config.hasAuth`:: +For an <>, <>, or <>, specifies whether a user and password are required inside the secrets configuration. Defaults to `true`. + +`xpack.actions.preconfigured..config.headers`:: +For a <> or <>, specifies a set of key-value pairs sent as headers with the request. + +`xpack.actions.preconfigured..config.host`:: +For an <>, specifies the host name of the service provider. + +`xpack.actions.preconfigured..config.index`:: +For an <>, specifies the {es} index. + +`xpack.actions.preconfigured..config.mappings`:: +For a <>, specifies field mappings. + +`xpack.actions.preconfigured..config.mappings.alertIdConfig`:: +For a <>, field mapping for the alert identifier. +You must provide `fieldtype`, `id`, `key`, and `name` values. + +`xpack.actions.preconfigured..config.mappings.caseIdConfig`:: +For a <>, field mapping for the case identifier. +You must provide `fieldtype`, `id`, `key`, and `name` values. + +`xpack.actions.preconfigured..config.mappings.caseNameConfig`:: +For a <>, field mapping for the case name. +You must provide `fieldtype`, `id`, `key`, and `name` values. + +`xpack.actions.preconfigured..config.mappings.commentsConfig`:: +For a <>, field mapping for the case comments. +You must provide `fieldtype`, `id`, `key`, and `name` values. + +`xpack.actions.preconfigured..config.mappings.descriptionConfig`:: +For a <>, field mapping for the case description. +You must provide `fieldtype`, `id`, `key`, and `name` values. + +`xpack.actions.preconfigured..config.mappings.ruleNameConfig`:: +For a <>, field mapping for the rule name. +You must provide `fieldtype`, `id`, `key`, and `name` values. + +`xpack.actions.preconfigured..config.mappings.severityConfig`:: +For a <>, specifies a field mapping for the severity. +You must provide `fieldtype`, `id`, `key`, and `name` values. + +`xpack.actions.preconfigured..config.method`:: +For a <>, specifies the HTTP request method, either `post` or `put`. Defaults to `post`. + +`xpack.actions.preconfigured..config.orgId`:: +For an <>, specifies the {ibm-r} organization identifier. + +`xpack.actions.preconfigured..config.port`:: +For an <>, specifies the port to connect to on the service provider. + +`xpack.actions.preconfigured..config.projectKey`:: +For a <>, specifies the Jira project key. + +`xpack.actions.preconfigured..config.secure`:: +For an <>, specifies whether the connection will use TLS when connecting to the service provider. If not true, the connection will initially connect over TCP then attempt to switch to TLS via the SMTP STARTTLS command. + +`xpack.actions.preconfigured..config.service`:: +For an <>, specifies the name of the email service. For example, `elastic_cloud`, `exchange_server`, `gmail`, `other`, `outlook365`, or `ses`. + +`xpack.actions.preconfigured..config.tenantId`:: +For an <>, specifies a GUID format value that corresponds to a tenant ID, which is a part of OAuth 2.0 client credentials authentication. + +`xpack.actions.preconfigured..config.updateIncidentJson`:: +For a <>, specifies a stringified JSON payload with Mustache variables that is sent to the update case URL to update a case. Required variables are `case.title` and `case.description`. ++ +NOTE: The JSON is validated after the Mustache variables have been placed when the REST method runs. You should manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. + +`xpack.actions.preconfigured..config.updateIncidentMethod`:: +For a <>, specifies the REST API HTTP request method to update the case in the third-party system. +For example: `post`, `put`(default), or `patch`. + +`xpack.actions.preconfigured..config.updateIncidentUrl`:: +For a <>, specifies the REST API URL to update the case by ID in the third-party system. ++ +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure the hostname in the URL is added to the allowed hosts. + +`xpack.actions.preconfigured..config.url`:: +A configuration URL that varies by connector: ++ +-- +* For a <>, specifies the D3 Security API request URL. +* For a <>, specifies the web service request URL. + +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure this hostname is added to the allowed hosts. +-- + +`xpack.actions.preconfigured..config.usesBasic`:: +For an <>, specifies whether it uses HTTP basic authentication. Defaults to `true`. + +`xpack.actions.preconfigured..config.viewIncidentUrl`:: +For a <>, specifies a URL string with either the external service ID or external service title Mustache variable to view a case in the external system. + `xpack.actions.preconfigured..name`:: The name of the preconfigured connector. +`xpack.actions.preconfigured..secrets`:: +Sensitive configuration details, such as username, password, and keys, which are specific to the connector type. ++ +TIP: Sensitive properties, such as passwords, should be stored in the <>. + +`xpack.actions.preconfigured..secrets.apikey`:: +An API key secret that varies by connector: ++ +-- +* For a <>, specifies the OpenAI or Azure OpenAI API key for authentication. +* For an <>, specifies the {opsgenie} API authentication key for HTTP basic authentication. +-- + +`xpack.actions.preconfigured..secrets.apiKeyId`:: +For an <>, specifies the authentication key ID for HTTP basic authentication. + +`xpack.actions.preconfigured..secrets.apiKeySecret`:: +For an <>, specifies the authentication key secret for HTTP basic authentication. + +`xpack.actions.preconfigured..secrets.apiToken`:: +For a <> or <>, specifies the API authentication token for HTTP basic authentication. + +`xpack.actions.preconfigured..secrets.clientSecret`:: +A client secret that varies by connector: ++ +-- +* For an <>, specifies the client secret that you generated for your app in the app registration portal. It is required when the email service is `exchange_server`, which uses OAuth 2.0 client credentials authentication. + +NOTE: The client secret must be URL-encoded. +-- + +`xpack.actions.preconfigured..secrets.email`:: +For a <>, specifies the account email for HTTP basic authentication. + +`xpack.actions.preconfigured..secrets.password`:: +A password secret that varies by connector: ++ +-- +* For an <>, <>, or <>, specifies a password that is required when `xpack.actions.preconfigured..config.hasAuth` is `true`. +* For an <>, specifies a password that is required when `xpack.actions.preconfigured..config.usesBasic` is `true`. +-- + +`xpack.actions.preconfigured..secrets.routingKey`:: +For a <>, specifies the 32 character PagerDuty Integration Key for an integration on a service, also referred to as the routing key. + +`xpack.actions.preconfigured..secrets.secretsUrl`:: +For an <> with URL authentication, specifies the request URL for the Elastic Alerts trigger in xMatters with the API key included in the URL. +It is used only when `xpack.actions.preconfigured..config.usesBasic` is `false`. ++ +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure this hostname is added to the allowed hosts. + +`xpack.actions.preconfigured..secrets.token`:: +A token secret that varies by connector: ++ +-- +* For a <>, specifies the D3 Security token. +* For a <>, specifies the Slack bot user OAuth token. +-- + +`xpack.actions.preconfigured..secrets.user`:: +A user name secret that varies by connector: ++ +-- +* For an <>, <>, or <>, specifies a user name that is required when `xpack.actions.preconfigured..config.hasAuth` is `true`. +* For an <>, specifies a user name that is required when `xpack.actions.preconfigured..config.usesBasic` is `true`. +-- + +`xpack.actions.preconfigured..secrets.webhookUrl`:: +A URL that varies by connector: ++ +-- +* For a <>, specifies the URL of the incoming webhook. +For a <>, specifies the Slack webhook URL. + +NOTE: If you are using the `xpack.actions.allowedHosts` setting, make sure the hostname is added to the allowed hosts. +-- + [float] [[alert-settings]] === Alerting settings @@ -270,6 +527,9 @@ Specifies whether to skip writing alerts and scheduling actions if rule processing was cancelled due to a timeout. Default: `true`. This setting can be overridden by individual rule types. +`xpack.alerting.rules.maxScheduledPerMinute` {ess-icon}:: +Specifies the maximum number of rules to run per minute. Default: 10000 + `xpack.alerting.rules.minimumScheduleInterval.value` {ess-icon}:: Specifies the minimum schedule interval for rules. This minimum is applied to all rules created or updated after you set this value. The time is formatted as a number and a time unit (`s`, `m`, `h`, or `d`). diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index f8ad122d7e865..4dcd80d1bfd66 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -4,10 +4,10 @@ Install with Docker ++++ -:docker-repo: docker.elastic.co/kibana/kibana -:docker-image: {docker-repo}:{version} -:es-docker-repo: docker.elastic.co/elasticsearch/elasticsearch -:es-docker-image: {es-docker-repo}:{version} +:kib-docker-repo: docker.elastic.co/kibana/kibana +:kib-docker-image: {kib-docker-repo}:{version} +:es-docker-repo: docker.elastic.co/elasticsearch/elasticsearch +:es-docker-image: {es-docker-repo}:{version} Docker images for {kib} are available from the Elastic Docker registry. The base image is https://hub.docker.com/_/ubuntu[ubuntu:20.04]. @@ -21,166 +21,153 @@ These images contain both free and subscription features. [discrete] [[run-kibana-on-docker-for-dev]] -=== Run {kib} on Docker for development +=== Run {kib} in Docker for development -. Start an {es} container for development or testing: -+ --- -ifeval::["{release-state}"=="unreleased"] - -NOTE: No Docker images are currently available for {kib} {version}. +Use Docker commands to run {kib} on a single-node {es} cluster for development or +testing. -endif::[] +TIP: This setup doesn't run multiple {es} nodes by default. To create a +multi-node cluster with {kib}, use Docker Compose instead. Refer to +{ref}/docker.html#docker-compose-file[Start a multi-node cluster with Docker +Compose] in the {es} documentation. -ifeval::["{release-state}"!="unreleased"] +. Install Docker. Visit https://docs.docker.com/get-docker/[Get Docker] to +install Docker for your environment. ++ +IMPORTANT: If using Docker Desktop, make sure to allocate at least 4GB of +memory. You can adjust memory usage in Docker Desktop by going to **Settings > +Resources**. -.. Create a new Docker network for {es} and {kib}: +. Create a new Docker network for {es} and {kib}. + [source,sh,subs="attributes"] ---- docker network create elastic ---- -.. Pull the {es} Docker image: +. Pull the {es} Docker image. + +-- +ifeval::["{release-state}"=="unreleased"] +WARNING: Version {version} has not yet been released. +No Docker image is currently available for {es} {version}. +endif::[] + [source,sh,subs="attributes"] ---- docker pull {es-docker-image} ---- +-- -.. Optional: Verify the {es} Docker image signature:: +. Optional: Install +https://docs.sigstore.dev/system_config/installation/[Cosign] for your +environment. Then use Cosign to verify the {es} image's signature. + [source,sh,subs="attributes"] ---- wget https://artifacts.elastic.co/cosign.pub -cosign verify --key cosign.pub {docker-repo}:{version} +cosign verify --key cosign.pub {es-docker-image} ---- + -For details about this step, refer to {ref}/docker.html#docker-verify-signature[Verify the {es} Docker image signature] in the {es} documentation. +The `cosign` command prints the check results and the signature payload in JSON format: ++ +[source,sh,subs="attributes"] +-------------------------------------------- +Verification for {es-docker-image} -- +The following checks were performed on each of these signatures: + - The cosign claims were validated + - Existence of the claims in the transparency log was verified offline + - The signatures were verified against the specified public key +-------------------------------------------- -.. Start {es} in Docker: +. Start an {es} container. + [source,sh,subs="attributes"] ---- -docker run --name es-node01 --net elastic -p 9200:9200 -p 9300:9300 -t {es-docker-image} +docker run --name es01 --net elastic -p 9200:9200 -it -m 1GB {es-docker-image} ---- - - - -endif::[] - --- -+ -When you start {es} for the first time, the following security configuration -occurs automatically: + -* {ref}/configuring-stack-security.html#stack-security-certificates[Certificates and keys] -are generated for the transport and HTTP layers. -* The Transport Layer Security (TLS) configuration settings are written to -`elasticsearch.yml`. -* A password is generated for the `elastic` user. -* An enrollment token is generated for {kib}. +TIP: Use the `-m` flag to set a memory limit for the container. This removes the +need to {ref}/docker.html#docker-set-heap-size[manually set the JVM size]. + -NOTE: You might need to scroll back a bit in the terminal to view the password -and enrollment token. +The command prints the `elastic` user password and an enrollment token for {kib}. -. Copy the generated password and enrollment token and save them in a secure -location. These values are shown only when you start {es} for the first time. -You'll use these to enroll {kib} with your {es} cluster and log in. - -. In a new terminal session, start {kib} and connect it to your {es} container: +. Copy the generated `elastic` password and enrollment token. These credentials +are only shown when you start {es} for the first time. You can regenerate the +credentials using the following commands. + --- -ifeval::["{release-state}"=="unreleased"] - -NOTE: No Docker images are currently available for {kib} {version}. - -endif::[] - -ifeval::["{release-state}"!="unreleased"] - [source,sh,subs="attributes"] ---- -docker pull {docker-image} -docker run --name kib-01 --net elastic -p 5601:5601 {docker-image} +docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic +docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana ---- -.. Pull the {kib} Docker image: +. Pull the {kib} Docker image. + +-- +ifeval::["{release-state}"=="unreleased"] +WARNING: Version {version} has not yet been released. +No Docker image is currently available for {kib} {version}. +endif::[] + [source,sh,subs="attributes"] ---- -docker pull {docker-image} +docker pull {kib-docker-image} ---- +-- -.. Optional: Verify the {kib} Docker image signature:: +. Optional: Verify the {kib} image's signature. + [source,sh,subs="attributes"] ---- wget https://artifacts.elastic.co/cosign.pub -cosign verify --key cosign.pub {docker-repo}:{version} +cosign verify --key cosign.pub {kib-docker-image} ---- -+ -For details about this step, refer to {ref}/docker.html#docker-verify-signature[Verify the {es} Docker image signature] in the {es} documentation. -.. Start {kib} in Docker: +. Start a {kib} container. + [source,sh,subs="attributes"] ---- -docker run --name kib-01 --net elastic -p 5601:5601 {docker-image} +docker run --name kib01 --net elastic -p 5601:5601 {kib-docker-image} ---- +. When {kib} starts, it outputs a unique generated link to the terminal. To +access {kib}, open this link in a web browser. - - - -endif::[] --- +. In your browser, enter the enrollment token that was generated when you started {es}. ++ +To regenerate the token, run: + -When you start {kib}, a unique link is output to your terminal. - -. To access {kib}, click the generated link in your terminal. - - .. In your browser, paste the enrollment token that you copied when starting - {es} and click the button to connect your {kib} instance with {es}. - - .. Log in to {kib} as the `elastic` user with the password that was generated - when you started {es}. - -[[docker-generate]] -[discrete] -=== Generate passwords and enrollment tokens -If you need to reset the password for the `elastic` user or other -built-in users, run the {ref}/reset-password.html[`elasticsearch-reset-password`] -tool. This tool is available in the {es} `bin` directory of the Docker container. - -For example, to reset the password for the `elastic` user: - [source,sh] ---- -docker exec -it es-node01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic +docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana ---- -If you need to generate new enrollment tokens for {kib} or {es} nodes, run the -{ref}/create-enrollment-token.html[`elasticsearch-create-enrollment-token`] tool. -This tool is available in the {es} `bin` directory of the Docker container. - -For example, to generate a new enrollment token for {kib}: - +. Log in to {kib} as the `elastic` user with the password that was generated +when you started {es}. ++ +To regenerate the password, run: ++ [source,sh] ---- -docker exec -it es-node01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana +docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic ---- [discrete] -=== Remove Docker containers +==== Remove Docker containers To remove the containers and their network, run: -[source,sh] +[source,sh,subs="attributes"] ---- +# Remove the Elastic network docker network rm elastic -docker rm es-node01 -docker rm kib-01 + +# Remove the {es} container +docker rm es01 + +# Remove the {kib} container +docker rm kib01 ---- [discrete] @@ -204,7 +191,7 @@ With `docker-compose`, the bind-mount can be specified like this: version: '2' services: kibana: - image: {docker-image} + image: {kib-docker-image} volumes: - ./kibana.yml:/usr/share/kibana/config/kibana.yml -------------------------------------------- @@ -215,8 +202,8 @@ By default, {kib} auto-generates a keystore file for secure settings at startup. ["source","sh",subs="attributes"] ---- -docker run -it --rm -v full_path_to/config:/usr/share/kibana/config -v full_path_to/data:/usr/share/kibana/data {docker-image} bin/kibana-keystore create -docker run -it --rm -v full_path_to/config:/usr/share/kibana/config -v full_path_to/data:/usr/share/kibana/data {docker-image} bin/kibana-keystore add test_keystore_setting +docker run -it --rm -v full_path_to/config:/usr/share/kibana/config -v full_path_to/data:/usr/share/kibana/data {kib-docker-image} bin/kibana-keystore create +docker run -it --rm -v full_path_to/config:/usr/share/kibana/config -v full_path_to/data:/usr/share/kibana/data {kib-docker-image} bin/kibana-keystore add test_keystore_setting ---- [discrete] @@ -254,7 +241,7 @@ These variables can be set with +docker-compose+ like this: version: '2' services: kibana: - image: {docker-image} + image: {kib-docker-image} environment: SERVER_NAME: kibana.example.org ELASTICSEARCH_HOSTS: '["http://es01:9200","http://es02:9200","http://es03:9200"]' diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 31bcc7ffb81fc..c41a1b1cdb260 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -615,7 +615,7 @@ Set this value to false to disable the Upgrade Assistant UI. *Default: true* `i18n.locale` {ess-icon}:: Set this value to change the {kib} interface language. -Valid locales are: `en`, `zh-CN`, `ja-JP`. *Default: `en`* +Valid locales are: `en`, `zh-CN`, `ja-JP`, `fr-FR`. *Default: `en`* include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[leveloffset=+1] include::{kib-repo-dir}/settings/apm-settings.asciidoc[] diff --git a/docs/user/alerting/create-and-manage-rules.asciidoc b/docs/user/alerting/create-and-manage-rules.asciidoc index 31c43346ef308..670e531350d5b 100644 --- a/docs/user/alerting/create-and-manage-rules.asciidoc +++ b/docs/user/alerting/create-and-manage-rules.asciidoc @@ -1,4 +1,3 @@ -[role="xpack"] [[create-and-manage-rules]] == Create and manage rules :frontmatter-description: Set up alerting in the {kib} {stack-manage-app} app and manage your rules. @@ -52,10 +51,11 @@ For more details, refer to the https://registry.terraform.io/providers/elastic/e Depending on the {kib} app and context, you might be prompted to choose the type of rule to create. Some apps will preselect the type of rule for you. -Each rule type provides its own way of defining the conditions to detect, but an expression formed by a series of clauses is a common pattern. For example, in a metric threshold rule, the `WHEN` clause enables you to select an aggregation operation to apply to a numeric field. +Each rule type provides its own way of defining the conditions to detect, but an expression formed by a series of clauses is a common pattern. +For example, in an {es} query rule, you specify an index, a query, and a threshold, which uses a metric aggregation operation (`count`, `average`, `max`, `min`, or `sum`): [role="screenshot"] -image::images/rule-flyout-rule-conditions.png[UI for defining rule conditions on a metric threshold rule,500] +image::images/es-query-rule-conditions.png[UI for defining rule conditions in an {es} query rule,500] // NOTE: This is an autogenerated screenshot. Do not edit it directly. All rules must have a check interval, which defines how often to evaluate the rule conditions. Checks are queued; they run as close to the defined value as capacity allows. @@ -71,15 +71,16 @@ conditions are met and when they are no longer met. Each action uses a connector, which provides connection information for a {kib} service or third party integration, depending on where you want to send the notifications. If no connectors exist, click **Add connector** to create one. -After you select a connector, set the action frequency. If the rule type supports alert summaries, you can choose to create a summary of alerts on each check interval or on a custom interval. For example, if you create a metrics threshold rule, you can send email notifications that summarize the new, ongoing, and recovered alerts each hour: +After you select a connector, set the action frequency. If the rule type supports alert summaries, you can choose to create a summary of alerts on each check interval or on a custom interval. +For example, if you create an {es} query rule, you can send notifications that summarize the new, ongoing, and recovered alerts on a custom interval: [role="screenshot"] -image::images/action-alert-summary.png[UI for defining rule conditions on a metric threshold rule,500] +image::images/es-query-rule-action-summary.png[UI for defining alert summary action in an {es} query rule,500] // NOTE: This is an autogenerated screenshot. Do not edit it directly. [NOTE] ==== -* The rules that support alert summaries, such as this metric threshold rule, enable you to further refine when actions run by adding time frame and query filters. +* Some rules that support alert summaries, such as metric threshold rules, enable you to further refine when actions run by adding time frame and query filters. * If you choose a custom action interval, it cannot be shorter than the rule's check interval. ==== @@ -87,13 +88,14 @@ Alternatively, you can set the action frequency such that the action runs for ea If the rule type does not support alert summaries, this is your only available option. You must choose when the action runs (for example, at each check interval, only when the alert status changes, or at a custom action interval). You must also choose an action group, which affects whether the action runs. Each rule type has a specific set of valid action groups. -For example, you can set *Run when* to `Alert`, `Warning`, `No data`, or `Recovered` for the metric threshold rule: +For example, you can set *Run when* to `Query matched` or `Recovered` for the {es} query rule: [role="screenshot"] -image::images/rule-flyout-action-details.png[UI for defining an email action,500] +image::images/es-query-rule-recovery-action.png[UI for defining a recovery action,500] // NOTE: This is an autogenerated screenshot. Do not edit it directly. -Each connector enables different action properties. For example, an email connector enables you to set the recipients, the subject, and a message body in markdown format. For more information about connectors, refer to <>. +Each connector supports a specific set of actions for each action group and enables different action properties. +For example, you can have actions that create an {opsgenie} alert when rule conditions are met and recovery actions that close the {opsgenie} alert. For more information about connectors, refer to <>. [[alerting-concepts-suppressing-duplicate-notifications]] [TIP] @@ -123,7 +125,7 @@ You can pass rule values to an action at the time a condition is detected. To view the list of variables available for your rule, click the "add rule variable" button: [role="screenshot"] -image::images/rule-flyout-action-variables.png[Passing rule values to an action,500] +image::images/es-query-rule-action-variables.png[Passing rule values to an action,500] // NOTE: This is an autogenerated screenshot. Do not edit it directly. For more information about common action variables, refer to <>. diff --git a/docs/user/alerting/images/action-alert-summary.png b/docs/user/alerting/images/action-alert-summary.png deleted file mode 100644 index 038e346a72725..0000000000000 Binary files a/docs/user/alerting/images/action-alert-summary.png and /dev/null differ diff --git a/docs/user/alerting/images/alert-types-tracking-containment-conditions.png b/docs/user/alerting/images/alert-types-tracking-containment-conditions.png deleted file mode 100644 index b6b5dbf20ff35..0000000000000 Binary files a/docs/user/alerting/images/alert-types-tracking-containment-conditions.png and /dev/null differ diff --git a/docs/user/alerting/images/es-query-rule-action-query-matched.png b/docs/user/alerting/images/es-query-rule-action-query-matched.png new file mode 100644 index 0000000000000..cafa6e82e2ab2 Binary files /dev/null and b/docs/user/alerting/images/es-query-rule-action-query-matched.png differ diff --git a/docs/user/alerting/images/es-query-rule-action-summary.png b/docs/user/alerting/images/es-query-rule-action-summary.png new file mode 100644 index 0000000000000..1e098d77fc5f3 Binary files /dev/null and b/docs/user/alerting/images/es-query-rule-action-summary.png differ diff --git a/docs/user/alerting/images/es-query-rule-action-variables.png b/docs/user/alerting/images/es-query-rule-action-variables.png new file mode 100644 index 0000000000000..685f455b986ab Binary files /dev/null and b/docs/user/alerting/images/es-query-rule-action-variables.png differ diff --git a/docs/user/alerting/images/es-query-rule-conditions.png b/docs/user/alerting/images/es-query-rule-conditions.png new file mode 100644 index 0000000000000..c9572afc3dc26 Binary files /dev/null and b/docs/user/alerting/images/es-query-rule-conditions.png differ diff --git a/docs/user/alerting/images/es-query-rule-recovery-action.png b/docs/user/alerting/images/es-query-rule-recovery-action.png new file mode 100644 index 0000000000000..a7c1243c1d0f8 Binary files /dev/null and b/docs/user/alerting/images/es-query-rule-recovery-action.png differ diff --git a/docs/user/alerting/images/rule-flyout-action-details.png b/docs/user/alerting/images/rule-flyout-action-details.png deleted file mode 100644 index b7d7050dd9cab..0000000000000 Binary files a/docs/user/alerting/images/rule-flyout-action-details.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-flyout-action-variables.png b/docs/user/alerting/images/rule-flyout-action-variables.png deleted file mode 100644 index ef74f4dc179d6..0000000000000 Binary files a/docs/user/alerting/images/rule-flyout-action-variables.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-flyout-rule-conditions.png b/docs/user/alerting/images/rule-flyout-rule-conditions.png deleted file mode 100644 index 07a1587ab8683..0000000000000 Binary files a/docs/user/alerting/images/rule-flyout-rule-conditions.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-es-query-example-action-variable.png b/docs/user/alerting/images/rule-types-es-query-example-action-variable.png deleted file mode 100644 index 8cb5c07543ddc..0000000000000 Binary files a/docs/user/alerting/images/rule-types-es-query-example-action-variable.png and /dev/null differ diff --git a/docs/user/alerting/rule-types/es-query.asciidoc b/docs/user/alerting/rule-types/es-query.asciidoc index f8310eefa790f..029ec2e1eaa46 100644 --- a/docs/user/alerting/rule-types/es-query.asciidoc +++ b/docs/user/alerting/rule-types/es-query.asciidoc @@ -1,7 +1,7 @@ [[rule-type-es-query]] == {es} query -:frontmatter-description: An {es} query rule generates alerts when your query meets a threshold. +:frontmatter-description: Create an {es} query rule, which generates alerts when your query meets a threshold. :frontmatter-tags-products: [kibana,alerting] :frontmatter-tags-content-type: [overview] :frontmatter-tags-user-goals: [analyze] @@ -10,18 +10,15 @@ The {es} query rule type runs a user-configured query, compares the number of matches to a configured threshold, and schedules actions to run when the threshold condition is met. - [float] === Create the rule -Fill in the name and optional tags, then select -*{es} query*. {es} query rule can be defined using KQL/Lucene or Query DSL. +In *{stack-manage-app}* > *{rules-ui}*, click *Create rule*, fill in the name and optional tags, then select *{es} query*. +An {es} query rule can be defined using KQL/Lucene or Query DSL. [float] === Define the conditions -Define properties to detect the condition. - [role="screenshot"] image::user/alerting/images/rule-types-es-query-conditions.png[Define the condition to detect] // NOTE: This is an autogenerated screenshot. Do not edit it directly. @@ -46,13 +43,51 @@ Exclude matches from previous run:: Turn on to avoid alert duplication by excluding documents that have already been detected by the previous rule run. This option is not available when a grouping field is specified. +[float] +=== Add actions + +You can optionally send notifications when the rule conditions are met and when they are no longer met. +In particular, this rule type supports: + +* alert summaries +* actions that run when the query is matched +* recovery actions that run when the rule conditions are no longer met + +For each action, you must choose a connector, which provides connection information for a {kib} service or third party integration. For more information about all the supported connectors, go to <>. + +After you select a connector, you must set the action frequency. +You can choose to create a summary of alerts on each check interval or on a custom interval. +For example, send email notifications that summarize the new, ongoing, and recovered alerts at a custom interval: + +[role="screenshot"] +image::images/es-query-rule-action-summary.png[UI for defining alert summary action in an {es} query rule] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +Alternatively, you can set the action frequency such that actions run for each alert. +Choose how often the action runs (at each check interval, only when the alert status changes, or at a custom action interval). +You must also choose an action group, which indicates whether the action runs when the query is matched or when the alert is recovered. +For example: + +[role="screenshot"] +image::images/es-query-rule-action-query-matched.png[UI for defining a recovery action] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +Each connector supports a specific set of actions for each action group. +For more details, refer to <>. + [float] === Add action variables -<> to run when the rule condition -is met. The following variables are specific to the {es} query rule. You can -also specify -<>. +You can pass rule values to an action to provide contextual details. +To view the list of variables available for each action, click the "add rule variable" button. +For example: + +[role="screenshot"] +image::images/es-query-rule-action-variables.png[Passing rule values to an action] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +The following variables are specific to the {es} query rule. +You can also specify <>. `context.title`:: A preconstructed title for the rule. Example: `rule term match alert query matched`. @@ -76,17 +111,26 @@ Example: `2022-02-03T20:29:27.732Z`. `context.hits`:: The most recent documents that matched the query. Using the https://mustache.github.io/[Mustache] template array syntax, you can iterate -over these hits to get values from the ES documents into your actions. -+ -[role="screenshot"] -image::images/rule-types-es-query-example-action-variable.png[Iterate over hits using Mustache template syntax] +over these hits to get values from the {es} documents into your actions. +For example, the message in an email connector action might contain: + +-- +[source,sh] +-------------------------------------------------- +Elasticsearch query rule '{{rule.name}}' is active: + +{{#context.hits}} +Document with {{_id}} and hostname {{_source.host.name}} has +{{_source.system.memory.actual.free}} bytes of memory free +{{/context.hits}} +-------------------------------------------------- + The documents returned by `context.hits` include the {ref}/mapping-source-field.html[`_source`] field. If the {es} query search API's {ref}/search-fields.html#search-fields-param[`fields`] parameter is used, documents will also return the `fields` field, -which can be used to access any runtime fields defined by the {ref}/runtime-search-request.html[`runtime_mappings`] parameter as the following example shows: -+ --- -[source] +which can be used to access any runtime fields defined by the {ref}/runtime-search-request.html[`runtime_mappings`] parameter. +For example: + +[source,sh] -------------------------------------------------- {{#context.hits}} timestamp: {{_source.@timestamp}} @@ -95,13 +139,12 @@ day of the week: {{fields.day_of_week}} <1> -------------------------------------------------- // NOTCONSOLE <1> The `fields` parameter here is used to access the `day_of_week` runtime field. --- -+ + As the {ref}/search-fields.html#search-fields-response[`fields`] response always returns an array of values for each field, -the https://mustache.github.io/[Mustache] template array syntax is used to iterate over these values in your actions as the following example shows: -+ --- -[source] +the https://mustache.github.io/[Mustache] template array syntax is used to iterate over these values in your actions. +For example: + +[source,sh] -------------------------------------------------- {{#context.hits}} Labels: diff --git a/docs/user/alerting/rule-types/geo-rule-types.asciidoc b/docs/user/alerting/rule-types/geo-rule-types.asciidoc index cfed5152e7657..f8c750acea62c 100644 --- a/docs/user/alerting/rule-types/geo-rule-types.asciidoc +++ b/docs/user/alerting/rule-types/geo-rule-types.asciidoc @@ -2,51 +2,25 @@ [[geo-alerting]] == Tracking containment -<> offers the tracking containment rule type which runs an {es} query over indices to determine whether any -documents are currently contained within any boundaries from the specified boundary index. -In the event that an entity is contained within a boundary, an alert may be generated. +The tracking containment rule alerts when an entity is contained or no longer contained within a boundary. [float] === Requirements To create a tracking containment rule, the following requirements must be present: -- *Tracks index or data view*: An index containing a `geo_point` or `geo_shape` field, `date` field, -and some form of entity identifier. An entity identifier is a `keyword` or `number` -field that consistently identifies the entity to be tracked. The data in this index should be dynamically -updating so that there are entity movements to alert upon. -- *Boundaries index or data view*: An index containing `geo_shape` data, such as boundary data and bounding box data. -This data is presumed to be static (not updating). Shape data matching the query is -harvested once when the rule is created and anytime after when the rule is re-enabled -after disablement. +- *Entities index*: An index containing a `geo_point` or `geo_shape` field, `date` field, and entity identifier. An entity identifier is a `keyword`, `number`, or `ip` field that identifies the entity. Entity data is expected to be updating so that there are entity movements to alert upon. +- *Boundaries index*: An index containing `geo_shape` data. +Boundaries data is expected to be static (not updating). Boundaries are collected once when the rule is created and anytime after when boundary configuration is modified. -By design, current interval entity locations (_current_ is determined by `date` in -the *Tracked index or data view*) are queried to determine if they are contained -within any monitored boundaries. Entity -data should be somewhat "real time", meaning the dates of new documents aren’t older +Entity locations are queried to determine if they are contained within any monitored boundaries. +Entity data should be somewhat "real time", meaning the dates of new documents aren’t older than the current time minus the amount of the interval. If data older than `now - ` is ingested, it won't trigger a rule. -[float] -=== Rule conditions - -Tracking containment rules have three clauses that define the condition to detect, -as well as two Kuery bars used to provide additional filtering context for each of the indices. - -[role="screenshot"] -image::user/alerting/images/alert-types-tracking-containment-conditions.png[Define the condition to detect,width=75%] -// NOTE: This is an autogenerated screenshot. Do not edit it directly. - -Index (entity):: This clause requires an *index or data view*, a *time field* that will be used for the *time window*, and a *`geo_point` or `geo_shape` field* for tracking. -Index (Boundary):: This clause requires an *index or data view*, a *`geo_shape` field* -identifying boundaries, and an optional *Human-readable boundary name* for better alerting -messages. - [float] === Actions -Conditions for how a rule is tracked can be specified uniquely for each individual action. -A rule can be triggered either when a containment condition is met or when an entity -is no longer contained. +A rule can be triggered either when a containment condition is met or when an entity is no longer contained. [role="screenshot"] image::user/alerting/images/alert-types-tracking-containment-action-options.png[Action frequency options for an action,width=75%] diff --git a/docs/user/alerting/rule-types/index-threshold.asciidoc b/docs/user/alerting/rule-types/index-threshold.asciidoc index d6a3217f3276d..69cdd2c3bbbcc 100644 --- a/docs/user/alerting/rule-types/index-threshold.asciidoc +++ b/docs/user/alerting/rule-types/index-threshold.asciidoc @@ -9,7 +9,7 @@ The index threshold rule type runs an {es} query. It aggregates field values from documents, compares them to threshold values, and schedules actions to run when the thresholds are met. [float] -=== Rule conditions +=== Define the conditions [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-conditions.png[Defining index threshold rule conditions in {kib}] @@ -27,11 +27,24 @@ It also defines a time window, which determines how far back to search for docum If data is available and all clauses have been defined, a preview chart will render the threshold value and display a line chart showing the value for the last 30 intervals. This can provide an indication of recent values and their proximity to the threshold, and help you tune the clauses. +[float] +[[actions-index-threshold]] +=== Add actions + +You can <> to your rule to generate notifications. + +Each action uses a connector, which provides connection information for a {kib} service or third party integration, depending on where you want to send the notifications. + +After you choose a connector, you must choose an action group, which affects when the action runs. +The valid action groups for an index threshold rule are: `Threshold met` and `Recovered`. +Each connector supports a specific set of actions for each action group. For more details, refer to <>. + [float] [[action-variables-index-threshold]] -=== Action variables +=== Add action variables -The following action variables are specific to the index threshold rule. You can also specify <>. +The following action variables are specific to the index threshold rule. +You can also specify <>. `context.conditions`:: A description of the threshold condition. Example: `count greater than 4` `context.date`:: The date, in ISO format, that the rule met the threshold condition. Example: `2020-01-01T00:00:00.000Z`. diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc index f71b32fa5b9ba..32e4115fe59dc 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -53,6 +53,8 @@ Calls to the API endpoints require different operations. To interact with the {k * *PUT* - Updates the existing information. +* *PATCH* - Applies partial modifications to the existing information. + * *DELETE* - Removes the information. [float] diff --git a/docs/user/dashboard/images/lens_saveAnnotationLayerButton_8.9.0.png b/docs/user/dashboard/images/lens_saveAnnotationLayerButton_8.9.0.png new file mode 100644 index 0000000000000..902ef7acfa300 Binary files /dev/null and b/docs/user/dashboard/images/lens_saveAnnotationLayerButton_8.9.0.png differ diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc index 38a21cf3e97c8..f97c22c769a4c 100644 --- a/docs/user/dashboard/lens.asciidoc +++ b/docs/user/dashboard/lens.asciidoc @@ -222,9 +222,11 @@ Annotations support two placement types: * *Custom query* — Displays annotations based on custom {es} queries. For detailed information about queries, check <>. -Create the annotation layer. +Any annotation layer can be saved as an annotation group to the *Visualize Library* in order to reuse it in other visualizations. Any changes made to the annotation group will be reflected in all visualizations to which it is added. -. In the layer pane, click *Add layer > Annotations*. +Create a new annotation layer. + +. In the layer pane, click *Add layer > Annotations > New annotation*. . Select the {data-source} for the annotation. @@ -263,6 +265,20 @@ Specify the annotation appearance. . To close, click *X*. +Save the annotation group to the library. + +. In the layer pane, on your annotation layer, click image:images/lens_saveAnnotationLayerButton_8.9.0.png[Save button on annotations layer]. + +. Enter the *Title*, *Description*, and add any applicable <>. + +. Click *Save group*. + +Add a library annotation group to a visualization. + +. In the layer pane, click *Add layer > Annotations > Load from library*. + +. Select the annotation group you want to use. + [float] [[add-reference-lines]] ==== Add reference lines diff --git a/docs/user/security/api-keys/images/api-keys-details.png b/docs/user/security/api-keys/images/api-keys-details.png new file mode 100644 index 0000000000000..c6029d11de2fa Binary files /dev/null and b/docs/user/security/api-keys/images/api-keys-details.png differ diff --git a/docs/user/security/api-keys/images/api-keys.png b/docs/user/security/api-keys/images/api-keys.png index 2a9df066fc3b8..6dec5730a1289 100644 Binary files a/docs/user/security/api-keys/images/api-keys.png and b/docs/user/security/api-keys/images/api-keys.png differ diff --git a/docs/user/security/api-keys/images/create-ccr-api-key.png b/docs/user/security/api-keys/images/create-ccr-api-key.png new file mode 100644 index 0000000000000..00b5cf546b439 Binary files /dev/null and b/docs/user/security/api-keys/images/create-ccr-api-key.png differ diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc index c8b7d72248e5e..eda187ed9096c 100644 --- a/docs/user/security/api-keys/index.asciidoc +++ b/docs/user/security/api-keys/index.asciidoc @@ -3,18 +3,17 @@ === API Keys -API keys enable you to create secondary credentials so that you can send -requests on behalf of a user. Secondary credentials have -the same or lower access rights. +API keys are security mechanisms used to authenticate and authorize access to {es} resources. They ensure that only authorized users or applications interact with {es}. -For example, if you extract data from an {es} cluster on a daily -basis, you might create an API key tied to your credentials, -configure it with minimum access, -and then put the API credentials into a cron job. -Or, you might create API keys to automate ingestion of new data from -remote sources, without a live user interaction. +For example, if you extract data from an {es} cluster on a daily basis, you might create an API key tied to your credentials, configure it with minimum access, and then put the API credentials into a cron job. Or you might create API keys to automate ingestion of new data from remote sources, without a live user interaction. -To manage API keys, open the main menu, then click *Stack Management > API Keys*. +You can use {kib} to manage your different API keys: + +* Personal API key: allows external services to access the Elastic Stack on behalf of a user. +* Cross-Cluster API key: allows remote clusters to connect to your local cluster. +* Managed API key: created and managed by Kibana to correctly run background tasks. + +To manage API keys, open the main menu, then click *Stack Management > Security > API Keys*. [role="screenshot"] image:images/api-keys.png["API Keys UI"] @@ -23,51 +22,45 @@ image:images/api-keys.png["API Keys UI"] [[api-keys-security-privileges]] === Security privileges -You must have the `manage_security`, `manage_api_key`, or the `manage_own_api_key` -cluster privileges to use API keys in {kib}. API keys can also be seen in a readonly view with access to the page and the `read_security` cluster privilege. To manage roles, open the main menu, then click -*Stack Management > Roles*, or use the <>. +* To use API keys in {kib}, you must have the `manage_security`, `manage_api_key`, or the `manage_own_api_key` cluster privileges. +* To delete API keys, you must have the `manage_api_key` or `manage_own_api_key` privileges. +* To create or update a *personal API key*, you must have the `manage_api_key` or the `manage_own_api_key` privilege. +* To create or update a *cross-cluster API key*, you must have the `manage_security` privilege and an Enterprise license. +* To have a read-only view on the API keys, you must have access to the page and the `read_security` cluster privilege. +To manage roles, open the main menu, then click *Stack Management > Security > Roles*, or use the <>. [float] [[create-api-key]] === Create an API key -To create an API key, open the main menu, then click *Stack Management > API Keys > Create API key*. +To create an API key, open the main menu, then click *Stack Management > Security > API Keys > Create API key*. [role="screenshot"] -image:images/create-api-key.png["Create API Key UI"] +image:images/create-ccr-api-key.png["Create API Key UI"] -Once created, you can copy the API key (Base64 encoded) and use it to send requests to {es} on your behalf. For example: -[source,bash] -curl --location --request GET 'http://localhost:5601/api/security/role' \ ---header 'Content-Type: application/json;charset=UTF-8' \ ---header 'kbn-xsrf: true' \ ---header 'Authorization: ApiKey aVZlLUMzSUJuYndxdDJvN0k1bU46aGxlYUpNS2lTa2FKeVZua1FnY1VEdw==' \ +Refer to the {ref}/security-api-create-api-key.html[create API key] documentation to learn more about creating personal API keys. - -[IMPORTANT] -============================================================================ -API keys are intended for programmatic access to {kib} and {es}. Do not use API keys to authenticate access via a web browser. -============================================================================ +Refer to the {ref}/security-api-create-cross-cluster-api-key.html[create cross-cluster API key] documentation to learn more about creating cross-cluster API keys. [float] [[udpate-api-key]] === Update an API key -To update an API key, open the main menu, click *Stack Management > API Keys*, and then click on the name of the key. +To update an API key, open the main menu, click *Stack Management > Security > API Keys*, and then click on the name of the key. You cannot update the name or the type of API key. + +Refer to the {ref}/security-api-update-api-key.html[update API key] documentation to learn more about updating personal API keys. -You can only update the `Restrict privileges` and `metadata` fields. +Refer to the {ref}/security-api-update-cross-cluster-api-key.html[update cross-cluster API key] documentation to learn more about updating cross-cluster API keys. [float] [[view-api-keys]] === View and delete API keys -The *API Keys* feature in Kibana lists your API keys, including the name, date created, and status. If an API key expires, its status changes from `Active` to `Expired`. +The *API Keys* feature in {kib} lists your API keys, including the name, date created, and status. If an API key expires, its status changes from `Active` to `Expired`. -If you have `manage_security` or `manage_api_key` permissions, -you can view the API keys of all users, and see which API key was -created by which user in which realm. +If you have `manage_security` or `manage_api_key` permissions, you can view the API keys of all users, and see which API key was created by which user in which realm. If you have only the `manage_own_api_key` permission, you see only a list of your own keys. -You can delete API keys individually or in bulk. +You can delete API keys individually or in bulk, but you need the `manage_api_keys` or `manage_own_api_key` privileges. diff --git a/examples/search_examples/public/search/app.tsx b/examples/search_examples/public/search/app.tsx index 0fa13df35a4e9..84bc3e1cd79be 100644 --- a/examples/search_examples/public/search/app.tsx +++ b/examples/search_examples/public/search/app.tsx @@ -311,17 +311,15 @@ export const SearchExamplesApp = ({ const result = await lastValueFrom( searchSource.fetch$({ abortSignal: abortController.signal, - disableShardFailureWarning: !showWarningToastNotifications, + disableWarningToasts: !showWarningToastNotifications, inspector, }) ); setRawResponse(result.rawResponse); - /* Here is an example of using showWarnings on the search service, using an optional callback to - * intercept the warnings before notification warnings are shown. - * - * Suppressing the shard failure warning notification from appearing by default requires setting - * { disableShardFailureWarning: true } in the SearchSourceSearchOptions passed to $fetch + /* + * Set disableWarningToasts to true to disable warning toasts and customize warning display. + * Then use showWarnings to customize warning notification. */ if (showWarningToastNotifications) { setWarningContents([]); @@ -498,7 +496,7 @@ export const SearchExamplesApp = ({ {' '} {' '} {' '} {' '} diff --git a/examples/unified_field_list_examples/public/field_list_sidebar.tsx b/examples/unified_field_list_examples/public/field_list_sidebar.tsx index 121132e89b810..9e71071d327ce 100644 --- a/examples/unified_field_list_examples/public/field_list_sidebar.tsx +++ b/examples/unified_field_list_examples/public/field_list_sidebar.tsx @@ -33,6 +33,8 @@ const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOpti originatingApp: PLUGIN_ID, localStorageKeyPrefix: 'examples', timeRangeUpdatesType: 'timefilter', + compressed: true, + showSidebarToggleButton: true, disablePopularFields: true, }; }; diff --git a/fleet_packages.json b/fleet_packages.json index 5ad9b3cff5830..a206560d48d80 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -24,7 +24,7 @@ [ { "name": "apm", - "version": "8.11.0-preview-1693211748", + "version": "8.11.0-preview-1693558513", "forceAlignStackVersion": true, "allowSyncToPrerelease": true }, @@ -54,7 +54,7 @@ }, { "name": "synthetics", - "version": "1.0.4" + "version": "1.0.6" }, { "name": "security_detection_engine", diff --git a/package.json b/package.json index d85ce3c621caa..3e3ee69fe82f4 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "yarn": "^1.22.19" }, "resolutions": { + "**/@hello-pangea/dnd": "16.2.0", "**/@types/node": "18.17.1", "**/@typescript-eslint/utils": "5.62.0", "**/chokidar": "^3.5.3", @@ -100,7 +101,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.9.1-canary.1", "@elastic/ems-client": "8.4.0", - "@elastic/eui": "87.2.0", + "@elastic/eui": "88.3.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", @@ -125,7 +126,7 @@ "@hapi/hoek": "^9.2.1", "@hapi/inert": "^6.0.4", "@hapi/wreck": "^17.1.0", - "@hello-pangea/dnd": "^16.3.0", + "@hello-pangea/dnd": "16.2.0", "@juggle/resize-observer": "^3.4.0", "@kbn/aad-fixtures-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/aad", "@kbn/ace": "link:packages/kbn-ace", @@ -347,6 +348,7 @@ "@kbn/crypto": "link:packages/kbn-crypto", "@kbn/crypto-browser": "link:packages/kbn-crypto-browser", "@kbn/custom-branding-plugin": "link:x-pack/plugins/custom_branding", + "@kbn/custom-integrations": "link:packages/kbn-custom-integrations", "@kbn/custom-integrations-plugin": "link:src/plugins/custom_integrations", "@kbn/dashboard-enhanced-plugin": "link:x-pack/plugins/dashboard_enhanced", "@kbn/dashboard-plugin": "link:src/plugins/dashboard", @@ -400,6 +402,7 @@ "@kbn/eso-plugin": "link:x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin", "@kbn/event-annotation-common": "link:packages/kbn-event-annotation-common", "@kbn/event-annotation-components": "link:packages/kbn-event-annotation-components", + "@kbn/event-annotation-listing-plugin": "link:src/plugins/event_annotation_listing", "@kbn/event-annotation-plugin": "link:src/plugins/event_annotation", "@kbn/event-log-fixture-plugin": "link:x-pack/test/plugin_api_integration/plugins/event_log", "@kbn/event-log-plugin": "link:x-pack/plugins/event_log", @@ -500,13 +503,20 @@ "@kbn/logstash-plugin": "link:x-pack/plugins/logstash", "@kbn/management-cards-navigation": "link:packages/kbn-management/cards_navigation", "@kbn/management-plugin": "link:src/plugins/management", + "@kbn/management-settings-components-field-input": "link:packages/kbn-management/settings/components/field_input", + "@kbn/management-settings-components-field-row": "link:packages/kbn-management/settings/components/field_row", + "@kbn/management-settings-field-definition": "link:packages/kbn-management/settings/field_definition", + "@kbn/management-settings-ids": "link:packages/kbn-management/settings/setting_ids", "@kbn/management-settings-section-registry": "link:packages/kbn-management/settings/section_registry", + "@kbn/management-settings-types": "link:packages/kbn-management/settings/types", + "@kbn/management-settings-utilities": "link:packages/kbn-management/settings/utilities", "@kbn/management-test-plugin": "link:test/plugin_functional/plugins/management_test_plugin", "@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl", "@kbn/maps-custom-raster-source-plugin": "link:x-pack/examples/third_party_maps_source_example", "@kbn/maps-ems-plugin": "link:src/plugins/maps_ems", "@kbn/maps-plugin": "link:x-pack/plugins/maps", "@kbn/maps-vector-tile-utils": "link:x-pack/packages/maps/vector_tile_utils", + "@kbn/metrics-data-access-plugin": "link:x-pack/plugins/metrics_data_access", "@kbn/ml-agg-utils": "link:x-pack/packages/ml/agg_utils", "@kbn/ml-anomaly-utils": "link:x-pack/packages/ml/anomaly_utils", "@kbn/ml-category-validator": "link:x-pack/packages/ml/category_validator", @@ -557,7 +567,9 @@ "@kbn/portable-dashboards-example": "link:examples/portable_dashboards_example", "@kbn/preboot-example-plugin": "link:examples/preboot_example", "@kbn/presentation-util-plugin": "link:src/plugins/presentation_util", + "@kbn/profiling-data-access-plugin": "link:x-pack/plugins/profiling_data_access", "@kbn/profiling-plugin": "link:x-pack/plugins/profiling", + "@kbn/profiling-utils": "link:packages/kbn-profiling-utils", "@kbn/random-sampling": "link:x-pack/packages/kbn-random-sampling", "@kbn/react-field": "link:packages/kbn-react-field", "@kbn/react-kibana-context-common": "link:packages/react/kibana_context/common", @@ -602,6 +614,7 @@ "@kbn/screenshotting-example-plugin": "link:x-pack/examples/screenshotting_example", "@kbn/screenshotting-plugin": "link:x-pack/plugins/screenshotting", "@kbn/search-api-panels": "link:packages/kbn-search-api-panels", + "@kbn/search-connectors": "link:packages/kbn-search-connectors", "@kbn/search-examples-plugin": "link:examples/search_examples", "@kbn/search-response-warnings": "link:packages/kbn-search-response-warnings", "@kbn/searchprofiler-plugin": "link:x-pack/plugins/searchprofiler", @@ -637,9 +650,13 @@ "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", "@kbn/serverless": "link:x-pack/plugins/serverless", + "@kbn/serverless-common-settings": "link:packages/serverless/settings/common", "@kbn/serverless-observability": "link:x-pack/plugins/serverless_observability", + "@kbn/serverless-observability-settings": "link:packages/serverless/settings/observability_project", "@kbn/serverless-project-switcher": "link:packages/serverless/project_switcher", "@kbn/serverless-search": "link:x-pack/plugins/serverless_search", + "@kbn/serverless-search-settings": "link:packages/serverless/settings/search_project", + "@kbn/serverless-security-settings": "link:packages/serverless/settings/security_project", "@kbn/serverless-types": "link:packages/serverless/types", "@kbn/session-notifications-plugin": "link:test/plugin_functional/plugins/session_notifications", "@kbn/session-view-plugin": "link:x-pack/plugins/session_view", @@ -708,6 +725,7 @@ "@kbn/status-plugin-a-plugin": "link:test/server_integration/plugins/status_plugin_a", "@kbn/status-plugin-b-plugin": "link:test/server_integration/plugins/status_plugin_b", "@kbn/std": "link:packages/kbn-std", + "@kbn/subscription-tracking": "link:packages/kbn-subscription-tracking", "@kbn/synthetics-plugin": "link:x-pack/plugins/synthetics", "@kbn/task-manager-fixture-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture", "@kbn/task-manager-performance-plugin": "link:x-pack/test/plugin_api_perf/plugins/task_manager_performance", @@ -743,6 +761,7 @@ "@kbn/ui-shared-deps-npm": "link:packages/kbn-ui-shared-deps-npm", "@kbn/ui-shared-deps-src": "link:packages/kbn-ui-shared-deps-src", "@kbn/ui-theme": "link:packages/kbn-ui-theme", + "@kbn/unified-data-table": "link:packages/kbn-unified-data-table", "@kbn/unified-doc-viewer": "link:packages/kbn-unified-doc-viewer", "@kbn/unified-doc-viewer-examples": "link:examples/unified_doc_viewer", "@kbn/unified-doc-viewer-plugin": "link:src/plugins/unified_doc_viewer", @@ -782,6 +801,7 @@ "@kbn/visualization-ui-components": "link:packages/kbn-visualization-ui-components", "@kbn/visualizations-plugin": "link:src/plugins/visualizations", "@kbn/watcher-plugin": "link:x-pack/plugins/watcher", + "@kbn/xstate-utils": "link:packages/kbn-xstate-utils", "@loaders.gl/core": "^3.4.7", "@loaders.gl/json": "^3.4.7", "@loaders.gl/shapefile": "^3.4.7", @@ -840,6 +860,7 @@ "core-js": "^3.31.0", "cronstrue": "^1.51.0", "css-box-model": "^1.2.1", + "css.escape": "^1.5.1", "cuid": "^2.1.8", "cytoscape": "^3.10.0", "cytoscape-dagre": "^2.2.2", @@ -857,7 +878,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^6.1.0", - "elastic-apm-node": "^3.49.1", + "elastic-apm-node": "^4.0.0", "email-addresses": "^5.0.0", "execa": "^4.0.2", "expiry-js": "0.1.7", @@ -905,7 +926,7 @@ "jsonwebtoken": "^9.0.0", "jsts": "^1.6.2", "kea": "^2.4.2", - "langchain": "^0.0.132", + "langchain": "^0.0.151", "launchdarkly-js-client-sdk": "^2.22.1", "launchdarkly-node-server-sdk": "^6.4.2", "load-json-file": "^6.2.0", @@ -935,7 +956,7 @@ "object-hash": "^1.3.1", "object-path-immutable": "^3.1.1", "openai": "^3.3.0", - "openpgp": "5.3.0", + "openpgp": "5.10.1", "opn": "^5.5.0", "ora": "^4.0.4", "p-limit": "^3.0.1", @@ -1071,15 +1092,17 @@ "@cypress/webpack-preprocessor": "^5.12.2", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/makelogs": "^6.1.1", - "@elastic/synthetics": "^1.3.0", + "@elastic/synthetics": "^1.4.0", "@emotion/babel-preset-css-prop": "^11.11.0", "@emotion/jest": "^11.11.0", + "@frsource/cypress-plugin-visual-regression-diff": "^3.3.10", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", "@jest/console": "^29.6.1", "@jest/reporters": "^29.6.1", "@jest/transform": "^29.6.1", "@jest/types": "^29.6.1", + "@kayahr/text-encoding": "^1.2.0", "@kbn/alerting-api-integration-helpers": "link:x-pack/test/alerting_api_integration/packages/helpers", "@kbn/ambient-common-types": "link:packages/kbn-ambient-common-types", "@kbn/ambient-ftr-types": "link:packages/kbn-ambient-ftr-types", @@ -1416,7 +1439,7 @@ "blob-polyfill": "^7.0.20220408", "callsites": "^3.1.0", "chance": "1.0.18", - "chromedriver": "^115.0.1", + "chromedriver": "^116.0.0", "clean-webpack-plugin": "^3.0.0", "cli-table3": "^0.6.1", "compression-webpack-plugin": "^4.0.0", @@ -1426,7 +1449,7 @@ "cssnano": "^5.1.12", "cssnano-preset-default": "^5.2.12", "csstype": "^3.0.2", - "cypress": "^12.13.0", + "cypress": "^12.17.4", "cypress-axe": "^1.4.0", "cypress-file-upload": "^5.0.8", "cypress-multi-reporters": "^1.6.3", @@ -1527,7 +1550,7 @@ "pirates": "^4.0.1", "piscina": "^3.2.0", "pixelmatch": "^5.3.0", - "playwright": "^1.35.0", + "playwright": "=1.37.0", "pngjs": "^3.4.0", "postcss": "^8.4.14", "postcss-loader": "^4.2.0", diff --git a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts b/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts index 6c1e11c36542d..bcb95ae38f1fb 100644 --- a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts +++ b/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts @@ -32,6 +32,7 @@ const PAGE_VARS_KEYS = [ 'buildNum', // May be useful for Serverless 'cloudId', 'deploymentId', + 'projectId', // projectId and deploymentId are mutually exclusive. They shouldn't be sent in the same offering. 'cluster_name', 'cluster_uuid', 'cluster_version', diff --git a/packages/content-management/content_editor/src/__jest__/tests.helpers.tsx b/packages/content-management/content_editor/src/__jest__/tests.helpers.tsx index b2bc4c9850f49..d71b237010c1c 100644 --- a/packages/content-management/content_editor/src/__jest__/tests.helpers.tsx +++ b/packages/content-management/content_editor/src/__jest__/tests.helpers.tsx @@ -7,14 +7,11 @@ */ import React from 'react'; import type { ComponentType } from 'react'; -import { of } from 'rxjs'; import { TagSelector, TagList } from '../mocks'; import { ContentEditorProvider } from '../services'; import type { Services } from '../services'; -const theme$ = of({ darkMode: false }); - export const getMockServices = (overrides?: Partial) => { const services = { openFlyout: jest.fn(() => ({ @@ -24,7 +21,6 @@ export const getMockServices = (overrides?: Partial) => { TagList, TagSelector, notifyError: () => undefined, - theme$, ...overrides, }; diff --git a/packages/content-management/content_editor/src/components/editor_loader.tsx b/packages/content-management/content_editor/src/components/editor_loader.tsx index ee621949b5ba8..b15009f3b4db1 100644 --- a/packages/content-management/content_editor/src/components/editor_loader.tsx +++ b/packages/content-management/content_editor/src/components/editor_loader.tsx @@ -7,21 +7,11 @@ */ import React, { useState, useCallback, useEffect } from 'react'; -import { EuiFlyoutHeader, EuiFlyoutBody, EuiFlyoutFooter, EuiThemeProvider } from '@elastic/eui'; -import useObservable from 'react-use/lib/useObservable'; -import { Observable, of } from 'rxjs'; - -import { Theme } from '../services'; +import { EuiFlyoutHeader, EuiFlyoutBody, EuiFlyoutFooter } from '@elastic/eui'; import type { Props } from './editor_flyout_content_container'; -const themeDefault = { darkMode: false }; - -export const ContentEditorLoader: React.FC }> = ({ - theme$, - ...rest -}) => { +export const ContentEditorLoader: React.FC = (props) => { const [Editor, setEditor] = useState | null>(null); - const { darkMode } = useObservable(theme$ ?? of(themeDefault), themeDefault); const loadEditor = useCallback(async () => { const { ContentEditorFlyoutContentContainer } = await import( @@ -36,9 +26,7 @@ export const ContentEditorLoader: React.FC }, [loadEditor]); return Editor ? ( - - - + ) : ( <> diff --git a/packages/content-management/content_editor/src/open_content_editor.tsx b/packages/content-management/content_editor/src/open_content_editor.tsx index e2521eaa996c1..37d06be73aeb4 100644 --- a/packages/content-management/content_editor/src/open_content_editor.tsx +++ b/packages/content-management/content_editor/src/open_content_editor.tsx @@ -20,7 +20,7 @@ export type OpenContentEditorParams = Pick< export function useOpenContentEditor() { const services = useServices(); - const { openFlyout, theme$ } = services; + const { openFlyout } = services; const flyout = useRef(null); return useCallback( @@ -35,12 +35,7 @@ export function useOpenContentEditor() { }; flyout.current = openFlyout( - , + , { maxWidth: 600, size: 'm', @@ -51,6 +46,6 @@ export function useOpenContentEditor() { return closeFlyout; }, - [openFlyout, services, theme$] + [openFlyout, services] ); } diff --git a/packages/content-management/content_editor/src/services.tsx b/packages/content-management/content_editor/src/services.tsx index 6b5536a2961e5..9eb75c0cb3d04 100644 --- a/packages/content-management/content_editor/src/services.tsx +++ b/packages/content-management/content_editor/src/services.tsx @@ -38,7 +38,6 @@ export interface Services { notifyError: NotifyFn; TagList?: FC<{ references: SavedObjectsReference[] }>; TagSelector?: React.FC; - theme$: Observable; } const ContentEditorContext = React.createContext(null); @@ -111,6 +110,7 @@ export const ContentEditorKibanaProvider: FC = }) => { const { core, toMountPoint, savedObjectsTagging } = services; const { openFlyout: coreOpenFlyout } = core.overlays; + const { theme$ } = core.theme; const TagList = useMemo(() => { const Comp: Services['TagList'] = ({ references }) => { @@ -126,9 +126,9 @@ export const ContentEditorKibanaProvider: FC = const openFlyout = useCallback( (node: ReactNode, options: OverlayFlyoutOpenOptions) => { - return coreOpenFlyout(toMountPoint(node), options); + return coreOpenFlyout(toMountPoint(node, { theme$ }), options); }, - [toMountPoint, coreOpenFlyout] + [coreOpenFlyout, toMountPoint, theme$] ); return ( @@ -139,7 +139,6 @@ export const ContentEditorKibanaProvider: FC = }} TagList={TagList} TagSelector={savedObjectsTagging?.ui.components.SavedObjectSaveModalTagSelector} - theme$={core.theme.theme$} > {children} diff --git a/packages/content-management/table_list_view/src/table_list_view.tsx b/packages/content-management/table_list_view/src/table_list_view.tsx index 71e9f95bbf581..f5d1a9bc596b9 100644 --- a/packages/content-management/table_list_view/src/table_list_view.tsx +++ b/packages/content-management/table_list_view/src/table_list_view.tsx @@ -36,7 +36,7 @@ export type TableListViewProps & { title: string; description?: string; @@ -73,6 +73,7 @@ export const TableListView = ({ titleColumnName, additionalRightSideActions, withoutPageTemplateWrapper, + itemIsEditable, }: TableListViewProps) => { const PageTemplate = withoutPageTemplateWrapper ? (React.Fragment as unknown as typeof KibanaPageTemplate) @@ -118,6 +119,7 @@ export const TableListView = ({ id={listingId} contentEditor={contentEditor} titleColumnName={titleColumnName} + itemIsEditable={itemIsEditable} withoutPageTemplateWrapper={withoutPageTemplateWrapper} onFetchSuccess={onFetchSuccess} setPageDataTestSubject={setPageDataTestSubject} diff --git a/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx b/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx index a81ba92b07f5a..5fb8b605d9202 100644 --- a/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx +++ b/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; import type { ComponentType } from 'react'; -import { from, of } from 'rxjs'; +import { from } from 'rxjs'; import { ContentEditorProvider } from '@kbn/content-management-content-editor'; import { TagList } from '../mocks'; @@ -31,13 +31,11 @@ export const getMockServices = (overrides?: Partial) => { return services; }; -const theme$ = of({ darkMode: false }); - export function WithServices

    (Comp: ComponentType

    , overrides: Partial = {}) { return (props: P) => { const services = getMockServices(overrides); return ( - undefined} theme$={theme$}> + undefined}> diff --git a/packages/content-management/table_list_view_table/src/components/tag_filter_panel.tsx b/packages/content-management/table_list_view_table/src/components/tag_filter_panel.tsx index 8b7c947fb0c85..844679ebcd971 100644 --- a/packages/content-management/table_list_view_table/src/components/tag_filter_panel.tsx +++ b/packages/content-management/table_list_view_table/src/components/tag_filter_panel.tsx @@ -176,8 +176,8 @@ export const TagFilterPanel: FC = ({ - {i18n.translate('contentManagement.tableList.tagFilterPanel.applyButtonLabel', { - defaultMessage: 'Apply', + {i18n.translate('contentManagement.tableList.tagFilterPanel.doneButtonLabel', { + defaultMessage: 'Done', })} diff --git a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx index 23a0a7fa29ae4..ccf6ef791d8d4 100644 --- a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx +++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx @@ -90,10 +90,12 @@ export interface TableListViewTableProps< * Edit action onClick handler. Edit action not provided when property is not provided */ editItem?(item: T): void; + /** - * Handler to set edit action visiblity per item. + * Handler to set edit action visiblity, and content editor readonly state per item. If not provided all non-managed items are considered editable. Note: Items with the managed property set to true will always be non-editable. */ - showEditActionForItem?(item: T): boolean; + itemIsEditable?(item: T): boolean; + /** * Name for the column containing the "title" value. */ @@ -144,6 +146,7 @@ export interface State({ findItems, createItem, editItem, - showEditActionForItem, + itemIsEditable, deleteItems, getDetailViewLink, onClickTitle, @@ -451,6 +454,15 @@ function TableListViewTableComp({ items, }); + const isEditable = useCallback( + (item: T) => { + // If the So is `managed` it is never editable. + if (item.managed) return false; + return itemIsEditable?.(item) ?? true; + }, + [itemIsEditable] + ); + const inspectItem = useCallback( (item: T) => { const tags = getTagIdsFromReferences(item.references).map((_id) => { @@ -466,6 +478,7 @@ function TableListViewTableComp({ }, entityName, ...contentEditor, + isReadonly: contentEditor.isReadonly || !isEditable(item), onSave: contentEditor.onSave && (async (args) => { @@ -476,7 +489,7 @@ function TableListViewTableComp({ }), }); }, - [getTagIdsFromReferences, openContentEditor, entityName, contentEditor, fetchItems] + [getTagIdsFromReferences, openContentEditor, entityName, contentEditor, isEditable, fetchItems] ); const tableColumns = useMemo(() => { @@ -550,7 +563,7 @@ function TableListViewTableComp({ ), icon: 'pencil', type: 'icon', - available: (v) => (showEditActionForItem ? showEditActionForItem(v) : true), + available: (item) => isEditable(item), enabled: (v) => !(v as unknown as { error: string })?.error, onClick: editItem, 'data-test-subj': `edit-action`, @@ -598,16 +611,16 @@ function TableListViewTableComp({ customTableColumn, hasUpdatedAtMetadata, editItem, + contentEditor.enabled, listingId, getDetailViewLink, onClickTitle, searchQuery.text, - addOrRemoveIncludeTagFilter, addOrRemoveExcludeTagFilter, + addOrRemoveIncludeTagFilter, DateFormatterComp, - contentEditor, + isEditable, inspectItem, - showEditActionForItem, ]); const itemsById = useMemo(() => { @@ -946,7 +959,7 @@ function TableListViewTableComp({ if (!showFetchError && hasNoItems) { return ( - + diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts index f2ba7c25de768..d140a6c99e7b0 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts @@ -9,8 +9,17 @@ import { fromEvent } from 'rxjs'; import type { AnalyticsClient } from '@kbn/analytics-client'; -/** HTML attributes that should be skipped from reporting because they might contain user data */ -const POTENTIAL_PII_HTML_ATTRIBUTES = ['value']; +/** HTML attributes that should be skipped from reporting because they might contain data we do not wish to collect */ +const HTML_ATTRIBUTES_TO_REMOVE = [ + 'data-href', + 'data-ech-series-name', + 'data-provider-id', + 'data-rfd-drag-handle-draggable-id', + 'data-rfd-droppable-id', + 'data-rfd-draggable-id', + 'href', + 'value', +]; /** * Registers the event type "click" in the analytics client. @@ -71,7 +80,7 @@ function getTargetDefinition(target: HTMLElement): string[] { ...(target.parentElement ? getTargetDefinition(target.parentElement) : []), target.tagName, ...[...target.attributes] - .filter((attr) => !POTENTIAL_PII_HTML_ATTRIBUTES.includes(attr.name)) + .filter((attr) => !HTML_ATTRIBUTES_TO_REMOVE.includes(attr.name)) .map((attr) => `${attr.name}=${attr.value}`), ]; } diff --git a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json index 4c2daa18d079d..c6efe4287effc 100644 --- a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json @@ -2,14 +2,9 @@ "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", - "types": [ - "jest", - "node" - ] + "types": ["jest", "node"] }, - "include": [ - "**/*.ts" - ], + "include": ["**/*.ts"], "kbn_references": [ "@kbn/logging", "@kbn/analytics-client", @@ -20,7 +15,5 @@ "@kbn/core-base-browser-mocks", "@kbn/core-injected-metadata-browser-mocks" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx index ad7c6d8fc52a5..6109a1bd0688c 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx @@ -147,16 +147,15 @@ describe('start', () => { const promise = chrome.getBodyClasses$().pipe(toArray()).toPromise(); service.stop(); await expect(promise).resolves.toMatchInlineSnapshot(` - Array [ - Array [ - "kbnBody", - "kbnBody--classicLayout", - "kbnBody--noHeaderBanner", - "kbnBody--chromeHidden", - "kbnVersion-1-2-3", - ], - ] - `); + Array [ + Array [ + "kbnBody", + "kbnBody--noHeaderBanner", + "kbnBody--chromeHidden", + "kbnVersion-1-2-3", + ], + ] + `); }); it('strips off "snapshot" from the kibana version if present', async () => { @@ -166,16 +165,15 @@ describe('start', () => { const promise = chrome.getBodyClasses$().pipe(toArray()).toPromise(); service.stop(); await expect(promise).resolves.toMatchInlineSnapshot(` - Array [ - Array [ - "kbnBody", - "kbnBody--classicLayout", - "kbnBody--noHeaderBanner", - "kbnBody--chromeHidden", - "kbnVersion-8-0-0", - ], - ] - `); + Array [ + Array [ + "kbnBody", + "kbnBody--noHeaderBanner", + "kbnBody--chromeHidden", + "kbnVersion-8-0-0", + ], + ] + `); }); it('does not add legacy browser warning if browser supports CSP', async () => { diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 76fef465d823c..47abc6c5646fe 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -207,7 +207,6 @@ export class ChromeService { map(([headerBanner, isVisible, chromeStyle]) => { return [ 'kbnBody', - chromeStyle === 'project' ? 'kbnBody--projectLayout' : 'kbnBody--classicLayout', headerBanner ? 'kbnBody--hasHeaderBanner' : 'kbnBody--noHeaderBanner', isVisible ? 'kbnBody--chromeVisible' : 'kbnBody--chromeHidden', getKbnVersionClass(), @@ -279,6 +278,11 @@ export class ChromeService { projectNavigation.setProjectsUrl(projectsUrl); }; + const setProjectName = (projectName: string) => { + validateChromeStyle(); + projectNavigation.setProjectName(projectName); + }; + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -372,6 +376,7 @@ export class ChromeService { headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))} homeHref$={projectNavigation.getProjectHome$()} projectsUrl$={projectNavigation.getProjectsUrl$()} + projectName$={projectNavigation.getProjectName$()} docLinks={docLinks} kibanaVersion={injectedMetadata.getKibanaVersion()} prependBasePath={http.basePath.prepend} @@ -500,6 +505,7 @@ export class ChromeService { project: { setHome: setProjectHome, setProjectsUrl, + setProjectName, setNavigation: setProjectNavigation, setSideNavComponent: setProjectSideNavComponent, setBreadcrumbs: setProjectBreadcrumbs, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts index 90be8ff754053..65d7fcc1bf559 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts @@ -46,6 +46,7 @@ export class ProjectNavigationService { }>({ current: null }); private projectHome$ = new BehaviorSubject(undefined); private projectsUrl$ = new BehaviorSubject(undefined); + private projectName$ = new BehaviorSubject(undefined); private projectNavigation$ = new BehaviorSubject(undefined); private activeNodes$ = new BehaviorSubject([]); private projectNavigationNavTreeFlattened: Record = {}; @@ -98,6 +99,12 @@ export class ProjectNavigationService { getProjectsUrl$: () => { return this.projectsUrl$.asObservable(); }, + setProjectName: (projectName: string) => { + this.projectName$.next(projectName); + }, + getProjectName$: () => { + return this.projectName$.asObservable(); + }, setProjectNavigation: (projectNavigation: ChromeProjectNavigation) => { this.projectNavigation$.next(projectNavigation); this.projectNavigationNavTreeFlattened = flattenNav(projectNavigation.navigationTree); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/types.ts b/packages/core/chrome/core-chrome-browser-internal/src/types.ts index 1eea86ad4090d..009aa57ee6b3b 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/types.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/types.ts @@ -50,6 +50,12 @@ export interface InternalChromeStart extends ChromeStart { */ setProjectsUrl(projectsUrl: string): void; + /** + * Sets the project name. + * @param projectName + */ + setProjectName(projectName: string): void; + /** * Sets the project navigation config to be used for rendering project navigation. * It is used for default project sidenav, project breadcrumbs, tracking active deep link. diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 476776a40a155..ec57877a9744e 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -105,11 +105,11 @@ Array [

    + + + ); + const dragDrop = component.find('[data-test-subj="testDragDrop"]').at(0); + + expect(dragDrop.getDOMNode().querySelector('.dragTest')).toBeNull(); + dragDrop.simulate('dragstart', { dataTransfer }); + expect(dragDrop.getDOMNode().querySelector('.dragTest')).toBeDefined(); + + act(() => { + jest.runAllTimers(); + }); + + expect(dragDrop.getDOMNode().querySelector('.dragTest')).toBeNull(); + }); + test('drop resets all the things', async () => { const preventDefault = jest.fn(); const stopPropagation = jest.fn(); diff --git a/packages/kbn-dom-drag-drop/src/drag_drop.tsx b/packages/kbn-dom-drag-drop/src/drag_drop.tsx index ab4158ad31543..b20570ee6969c 100644 --- a/packages/kbn-dom-drag-drop/src/drag_drop.tsx +++ b/packages/kbn-dom-drag-drop/src/drag_drop.tsx @@ -42,6 +42,10 @@ interface BaseProps { * The CSS class(es) for the root element. */ className?: string; + /** + * CSS class to apply when the item is being dragged + */ + dragClassName?: string; /** * The event handler that fires when an item @@ -212,6 +216,7 @@ const removeSelection = () => { const DragInner = memo(function DragInner({ dataTestSubj, className, + dragClassName, value, children, dndDispatch, @@ -305,6 +310,18 @@ const DragInner = memo(function DragInner({ // so we know we have DraggableProps if we reach this code. if (e && 'dataTransfer' in e) { e.dataTransfer.setData('text', value.humanData.label); + + // Apply an optional class to the element being dragged so the ghost + // can be styled. We must add it to the actual element for a single + // frame before removing it so the ghost picks up the styling. + const current = e.currentTarget; + + if (dragClassName && !current.classList.contains(dragClassName)) { + current.classList.add(dragClassName); + requestAnimationFrame(() => { + current.classList.remove(dragClassName); + }); + } } // Chrome causes issues if you try to render from within a diff --git a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts index 2c23eec3ca2aa..42df7340c1853 100644 --- a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts +++ b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts @@ -8,25 +8,15 @@ import type { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; -import { KbnClient } from '@kbn/test'; import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; -import { migrateSavedObjectIndices, createStats, cleanSavedObjectIndices } from '../lib'; +import { createStats, cleanSavedObjectIndices } from '../lib'; -export async function emptyKibanaIndexAction({ - client, - log, - kbnClient, -}: { - client: Client; - log: ToolingLog; - kbnClient: KbnClient; -}) { +export async function emptyKibanaIndexAction({ client, log }: { client: Client; log: ToolingLog }) { const stats = createStats('emptyKibanaIndex', log); await cleanSavedObjectIndices({ client, stats, log }); - await migrateSavedObjectIndices(kbnClient); await client.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); - ALL_SAVED_OBJECT_INDICES.forEach((indexPattern) => stats.createdIndex(indexPattern)); + return stats.toJSON(); } diff --git a/packages/kbn-es-archiver/src/es_archiver.ts b/packages/kbn-es-archiver/src/es_archiver.ts index 65de41148d8db..d5c989f080486 100644 --- a/packages/kbn-es-archiver/src/es_archiver.ts +++ b/packages/kbn-es-archiver/src/es_archiver.ts @@ -154,14 +154,12 @@ export class EsArchiver { } /** - * Delete any Kibana indices, and initialize the Kibana index as Kibana would do - * on startup. + * Cleanup saved object indices, preserving the space:default saved object. */ async emptyKibanaIndex() { return await emptyKibanaIndexAction({ client: this.client, log: this.log, - kbnClient: this.kbnClient, }); } diff --git a/packages/kbn-es-query/index.ts b/packages/kbn-es-query/index.ts index 1e0d9092adefb..a14724fc3a748 100644 --- a/packages/kbn-es-query/index.ts +++ b/packages/kbn-es-query/index.ts @@ -54,6 +54,8 @@ export { isOfAggregateQueryType, getAggregateQueryMode, getIndexPatternFromSQLQuery, + getIndexPatternFromESQLQuery, + getLanguageDisplayName, } from './src/es_query'; export { diff --git a/packages/kbn-es-query/src/es_query/build_es_query.ts b/packages/kbn-es-query/src/es_query/build_es_query.ts index 9d1b0b1c6b139..ff9908bcb0cc3 100644 --- a/packages/kbn-es-query/src/es_query/build_es_query.ts +++ b/packages/kbn-es-query/src/es_query/build_es_query.ts @@ -12,7 +12,7 @@ import { buildQueryFromKuery } from './from_kuery'; import { buildQueryFromFilters } from './from_filters'; import { buildQueryFromLucene } from './from_lucene'; import { Filter, Query, AggregateQuery } from '../filters'; -import { isOfQueryType } from './es_query_sql'; +import { isOfQueryType } from './es_aggregate_query'; import { BoolQuery, DataViewBase } from './types'; import type { KueryQueryOptions } from '../kuery'; import type { EsQueryFiltersConfig } from './from_filters'; diff --git a/packages/kbn-es-query/src/es_query/es_aggregate_query.test.ts b/packages/kbn-es-query/src/es_query/es_aggregate_query.test.ts new file mode 100644 index 0000000000000..2ab161e0f7517 --- /dev/null +++ b/packages/kbn-es-query/src/es_query/es_aggregate_query.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + isOfQueryType, + isOfAggregateQueryType, + getAggregateQueryMode, + getIndexPatternFromSQLQuery, + getIndexPatternFromESQLQuery, +} from './es_aggregate_query'; + +describe('sql query helpers', () => { + describe('isOfQueryType', () => { + it('should return true for a Query type query', () => { + const flag = isOfQueryType({ query: 'foo', language: 'test' }); + expect(flag).toBe(true); + }); + + it('should return false for an Aggregate type query', () => { + const flag = isOfQueryType({ sql: 'SELECT * FROM foo' }); + expect(flag).toBe(false); + }); + }); + + describe('isOfAggregateQueryType', () => { + it('should return false for a Query type query', () => { + const flag = isOfAggregateQueryType({ query: 'foo', language: 'test' }); + expect(flag).toBe(false); + }); + + it('should return true for an Aggregate type query', () => { + const flag = isOfAggregateQueryType({ sql: 'SELECT * FROM foo' }); + expect(flag).toBe(true); + }); + }); + + describe('getAggregateQueryMode', () => { + it('should return sql for an SQL AggregateQuery type', () => { + const mode = getAggregateQueryMode({ sql: 'SELECT * FROM foo' }); + expect(mode).toBe('sql'); + }); + + it('should return esql for an ESQL AggregateQuery type', () => { + const mode = getAggregateQueryMode({ esql: 'foo | where field > 100' }); + expect(mode).toBe('esql'); + }); + }); + + describe('getIndexPatternFromSQLQuery', () => { + it('should return the index pattern string from sql queries', () => { + const idxPattern1 = getIndexPatternFromSQLQuery('SELECT * FROM foo'); + expect(idxPattern1).toBe('foo'); + + const idxPattern2 = getIndexPatternFromSQLQuery('SELECT woof, meow FROM "foo"'); + expect(idxPattern2).toBe('foo'); + + const idxPattern3 = getIndexPatternFromSQLQuery('SELECT woof, meow FROM "the_index_pattern"'); + expect(idxPattern3).toBe('the_index_pattern'); + + const idxPattern4 = getIndexPatternFromSQLQuery('SELECT woof, meow FROM "the-index-pattern"'); + expect(idxPattern4).toBe('the-index-pattern'); + + const idxPattern5 = getIndexPatternFromSQLQuery('SELECT woof, meow from "the-index-pattern"'); + expect(idxPattern5).toBe('the-index-pattern'); + + const idxPattern6 = getIndexPatternFromSQLQuery('SELECT woof, meow from "logstash-*"'); + expect(idxPattern6).toBe('logstash-*'); + + const idxPattern7 = getIndexPatternFromSQLQuery( + 'SELECT woof, meow from logstash-1234! WHERE field > 100' + ); + expect(idxPattern7).toBe('logstash-1234!'); + + const idxPattern8 = getIndexPatternFromSQLQuery( + 'SELECT * FROM (SELECT woof, miaou FROM "logstash-1234!" GROUP BY woof)' + ); + expect(idxPattern8).toBe('logstash-1234!'); + }); + }); + + describe('getIndexPatternFromESQLQuery', () => { + it('should return the index pattern string from esql queries', () => { + const idxPattern1 = getIndexPatternFromESQLQuery('FROM foo'); + expect(idxPattern1).toBe('foo'); + + const idxPattern3 = getIndexPatternFromESQLQuery('from foo | project abc, def'); + expect(idxPattern3).toBe('foo'); + + const idxPattern4 = getIndexPatternFromESQLQuery('from foo | project a | limit 2'); + expect(idxPattern4).toBe('foo'); + + const idxPattern5 = getIndexPatternFromESQLQuery('from foo | limit 2'); + expect(idxPattern5).toBe('foo'); + }); + }); +}); diff --git a/packages/kbn-es-query/src/es_query/es_aggregate_query.ts b/packages/kbn-es-query/src/es_query/es_aggregate_query.ts new file mode 100644 index 0000000000000..1e87552e98b83 --- /dev/null +++ b/packages/kbn-es-query/src/es_query/es_aggregate_query.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { Query, AggregateQuery } from '../filters'; + +type Language = keyof AggregateQuery; + +// Checks if the query is of type Query +export function isOfQueryType(arg?: Query | AggregateQuery): arg is Query { + return Boolean(arg && 'query' in arg); +} + +// Checks if the query is of type AggregateQuery +// currently only supports the sql query type +// should be enhanced to support other query types +export function isOfAggregateQueryType( + query: AggregateQuery | Query | { [key: string]: any } +): query is AggregateQuery { + return Boolean(query && ('sql' in query || 'esql' in query)); +} + +// returns the language of the aggregate Query, sql, esql etc +export function getAggregateQueryMode(query: AggregateQuery): Language { + return Object.keys(query)[0] as Language; +} + +export function getLanguageDisplayName(language: string): string { + return language === 'esql' ? 'es|ql' : language; +} + +// retrieves the index pattern from the aggregate query for SQL +export function getIndexPatternFromSQLQuery(sqlQuery?: string): string { + let sql = sqlQuery?.replaceAll('"', '').replaceAll("'", ''); + const splitFroms = sql?.split(new RegExp(/FROM\s/, 'ig')); + const fromsLength = splitFroms?.length ?? 0; + if (splitFroms && splitFroms?.length > 2) { + sql = `${splitFroms[fromsLength - 2]} FROM ${splitFroms[fromsLength - 1]}`; + } + // case insensitive match for the index pattern + const regex = new RegExp(/FROM\s+([\w*-.!@$^()~;]+)/, 'i'); + const matches = sql?.match(regex); + if (matches) { + return matches[1]; + } + return ''; +} + +// retrieves the index pattern from the aggregate query for ES|QL +export function getIndexPatternFromESQLQuery(esql?: string): string { + const splitFroms = esql?.split(new RegExp(/FROM\s/, 'ig')); + const fromsLength = splitFroms?.length ?? 0; + if (splitFroms && splitFroms?.length > 2) { + esql = `${splitFroms[fromsLength - 2]} FROM ${splitFroms[fromsLength - 1]}`; + } + const parsedString = esql?.replaceAll('`', ''); + // case insensitive match for the index pattern + const regex = new RegExp(/FROM\s+([\w*-.!@$^()~;]+)/, 'i'); + const matches = parsedString?.match(regex); + if (matches) { + return matches[1]; + } + return ''; +} diff --git a/packages/kbn-es-query/src/es_query/es_query_sql.test.ts b/packages/kbn-es-query/src/es_query/es_query_sql.test.ts deleted file mode 100644 index da909c6e5f9b4..0000000000000 --- a/packages/kbn-es-query/src/es_query/es_query_sql.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - isOfQueryType, - isOfAggregateQueryType, - getAggregateQueryMode, - getIndexPatternFromSQLQuery, -} from './es_query_sql'; - -describe('sql query helpers', () => { - describe('isOfQueryType', () => { - it('should return true for a Query type query', () => { - const flag = isOfQueryType({ query: 'foo', language: 'test' }); - expect(flag).toBe(true); - }); - - it('should return false for an Aggregate type query', () => { - const flag = isOfQueryType({ sql: 'SELECT * FROM foo' }); - expect(flag).toBe(false); - }); - }); - - describe('isOfAggregateQueryType', () => { - it('should return false for a Query type query', () => { - const flag = isOfAggregateQueryType({ query: 'foo', language: 'test' }); - expect(flag).toBe(false); - }); - - it('should return true for an Aggregate type query', () => { - const flag = isOfAggregateQueryType({ sql: 'SELECT * FROM foo' }); - expect(flag).toBe(true); - }); - }); - - describe('getAggregateQueryMode', () => { - it('should return sql for an SQL AggregateQuery type', () => { - const mode = getAggregateQueryMode({ sql: 'SELECT * FROM foo' }); - expect(mode).toBe('sql'); - }); - - it('should return esql for an ESQL AggregateQuery type', () => { - const mode = getAggregateQueryMode({ esql: 'foo | where field > 100' }); - expect(mode).toBe('esql'); - }); - }); - - describe('getIndexPatternFromSQLQuery', () => { - it('should return the index pattern string from sql queries', () => { - const idxPattern1 = getIndexPatternFromSQLQuery('SELECT * FROM foo'); - expect(idxPattern1).toBe('foo'); - - const idxPattern2 = getIndexPatternFromSQLQuery('SELECT woof, meow FROM "foo"'); - expect(idxPattern2).toBe('foo'); - - const idxPattern3 = getIndexPatternFromSQLQuery('SELECT woof, meow FROM "the_index_pattern"'); - expect(idxPattern3).toBe('the_index_pattern'); - - const idxPattern4 = getIndexPatternFromSQLQuery('SELECT woof, meow FROM "the-index-pattern"'); - expect(idxPattern4).toBe('the-index-pattern'); - - const idxPattern5 = getIndexPatternFromSQLQuery('SELECT woof, meow from "the-index-pattern"'); - expect(idxPattern5).toBe('the-index-pattern'); - - const idxPattern6 = getIndexPatternFromSQLQuery('SELECT woof, meow from "logstash-*"'); - expect(idxPattern6).toBe('logstash-*'); - - const idxPattern7 = getIndexPatternFromSQLQuery( - 'SELECT woof, meow from logstash-1234! WHERE field > 100' - ); - expect(idxPattern7).toBe('logstash-1234!'); - - const idxPattern8 = getIndexPatternFromSQLQuery( - 'SELECT * FROM (SELECT woof, miaou FROM "logstash-1234!" GROUP BY woof)' - ); - expect(idxPattern8).toBe('logstash-1234!'); - }); - }); -}); diff --git a/packages/kbn-es-query/src/es_query/es_query_sql.ts b/packages/kbn-es-query/src/es_query/es_query_sql.ts deleted file mode 100644 index 46de33dc04e86..0000000000000 --- a/packages/kbn-es-query/src/es_query/es_query_sql.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import type { Query, AggregateQuery } from '../filters'; - -type Language = keyof AggregateQuery; - -// Checks if the query is of type Query -export function isOfQueryType(arg?: Query | AggregateQuery): arg is Query { - return Boolean(arg && 'query' in arg); -} - -// Checks if the query is of type AggregateQuery -// currently only supports the sql query type -// should be enhanced to support other query types -export function isOfAggregateQueryType( - query: AggregateQuery | Query | { [key: string]: any } -): query is AggregateQuery { - return Boolean(query && ('sql' in query || 'esql' in query)); -} - -// returns the language of the aggregate Query, sql, esql etc -export function getAggregateQueryMode(query: AggregateQuery): Language { - return Object.keys(query)[0] as Language; -} - -// retrieves the index pattern from the aggregate query -export function getIndexPatternFromSQLQuery(sqlQuery?: string): string { - let sql = sqlQuery?.replaceAll('"', '').replaceAll("'", ''); - const splitFroms = sql?.split(new RegExp(/FROM\s/, 'ig')); - const fromsLength = splitFroms?.length ?? 0; - if (splitFroms && splitFroms?.length > 2) { - sql = `${splitFroms[fromsLength - 2]} FROM ${splitFroms[fromsLength - 1]}`; - } - // case insensitive match for the index pattern - const regex = new RegExp(/FROM\s+([\w*-.!@$^()~;]+)/, 'i'); - const matches = sql?.match(regex); - if (matches) { - return matches[1]; - } - return ''; -} diff --git a/packages/kbn-es-query/src/es_query/index.ts b/packages/kbn-es-query/src/es_query/index.ts index 1dca3a5524cfb..22141a52e93f5 100644 --- a/packages/kbn-es-query/src/es_query/index.ts +++ b/packages/kbn-es-query/src/es_query/index.ts @@ -18,7 +18,9 @@ export { isOfAggregateQueryType, getAggregateQueryMode, getIndexPatternFromSQLQuery, -} from './es_query_sql'; + getLanguageDisplayName, + getIndexPatternFromESQLQuery, +} from './es_aggregate_query'; export { fromCombinedFilter } from './from_combined_filter'; export type { IFieldSubType, diff --git a/packages/kbn-es-types/index.ts b/packages/kbn-es-types/index.ts index e97df4d4eaa11..cd2d0a5f2618e 100644 --- a/packages/kbn-es-types/index.ts +++ b/packages/kbn-es-types/index.ts @@ -18,4 +18,5 @@ export type { AggregationResultOfMap, ESFilter, MaybeReadonlyArray, + ClusterDetails, } from './src'; diff --git a/packages/kbn-es-types/src/index.ts b/packages/kbn-es-types/src/index.ts index a18d6b39410f8..f22e43fc7e705 100644 --- a/packages/kbn-es-types/src/index.ts +++ b/packages/kbn-es-types/src/index.ts @@ -11,6 +11,7 @@ import { AggregateOf as AggregationResultOf, AggregateOfMap as AggregationResultOfMap, SearchHit, + ClusterDetails, } from './search'; export type ESFilter = estypes.QueryDslQueryContainer; @@ -34,4 +35,10 @@ export type ESSearchResponse< TOptions extends { restTotalHitsAsInt: boolean } = { restTotalHitsAsInt: false } > = InferSearchResponseOf; -export type { InferSearchResponseOf, AggregationResultOf, AggregationResultOfMap, SearchHit }; +export type { + InferSearchResponseOf, + AggregationResultOf, + AggregationResultOfMap, + SearchHit, + ClusterDetails, +}; diff --git a/packages/kbn-es-types/src/search.ts b/packages/kbn-es-types/src/search.ts index 13ebc02b65aa6..502a7464e5351 100644 --- a/packages/kbn-es-types/src/search.ts +++ b/packages/kbn-es-types/src/search.ts @@ -644,3 +644,12 @@ export type InferSearchResponseOf< >; }; }; + +export interface ClusterDetails { + status: 'running' | 'successful' | 'partial' | 'skipped' | 'failed'; + indices: string; + took?: number; + timed_out: boolean; + _shards?: estypes.ShardStatistics; + failures?: estypes.ShardFailure[]; +} diff --git a/packages/kbn-es/README.mdx b/packages/kbn-es/README.mdx index e6e5727ccb897..5927670030cdf 100644 --- a/packages/kbn-es/README.mdx +++ b/packages/kbn-es/README.mdx @@ -10,13 +10,19 @@ tags: ['kibana', 'dev', 'contributor', 'operations', 'es'] > A command line utility for running elasticsearch from snapshot, source, archive, docker, serverless or even building snapshot artifacts. ## Getting started +To run, go to the Kibana root and run `node scripts/es --help` to get the latest command line options. + +The script attempts to preserve the existing interfaces used by Elasticsearch CLI. This includes passing through options with the `-E` argument and the `ES_JAVA_OPTS` environment variable for Java options. + If running elasticsearch from source, elasticsearch needs to be cloned to a sibling directory of Kibana. -If running elasticsearch serverless or a docker container, docker is required to be installed locally. Installation instructions can be found [here](https://www.docker.com/). +### Serverless & Docker Prerequisites +If running elasticsearch serverless or a docker container, there is some required initial setup: -To run, go to the Kibana root and run `node scripts/es --help` to get the latest command line options. +1. Install Docker. Instructions can be found [here](https://www.docker.com/). +1. Authentication with Elastic's Docker registry [here](https://docker-auth.elastic.co/github_auth). +1. Increase OS virtual memory limits. More info in the [ES docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-prod-prerequisites). -The script attempts to preserve the existing interfaces used by Elasticsearch CLI. This includes passing through options with the `-E` argument and the `ES_JAVA_OPTS` environment variable for Java options. ### Examples diff --git a/packages/kbn-es/index.ts b/packages/kbn-es/index.ts index 3ccb220be6b52..f64664577695f 100644 --- a/packages/kbn-es/index.ts +++ b/packages/kbn-es/index.ts @@ -14,3 +14,4 @@ export { ELASTIC_SERVERLESS_SUPERUSER_PASSWORD, getDockerFileMountPath, } from './src/utils'; +export type { ArtifactLicense } from './src/artifact'; diff --git a/packages/kbn-es/src/cli_commands/archive.ts b/packages/kbn-es/src/cli_commands/archive.ts index af697975c59ed..075e47356b3dd 100644 --- a/packages/kbn-es/src/cli_commands/archive.ts +++ b/packages/kbn-es/src/cli_commands/archive.ts @@ -59,7 +59,11 @@ export const archive = { throw createCliError('you must provide a path to an ES tar file'); } - const { installPath } = await cluster.installArchive(path, options); + const { installPath } = await cluster.installArchive(path, { + basePath: options.basePath, + installPath: options.installPath, + esArgs: options.esArgs, + }); await cluster.run(installPath, { ...options, readyTimeout: parseTimeoutToMs(options.readyTimeout), diff --git a/packages/kbn-es/src/cli_commands/docker.ts b/packages/kbn-es/src/cli_commands/docker.ts index 07e4a64263b4a..3bbabe0e3f10c 100644 --- a/packages/kbn-es/src/cli_commands/docker.ts +++ b/packages/kbn-es/src/cli_commands/docker.ts @@ -28,7 +28,7 @@ export const docker: Command = { --image Full path to image of ES to run, has precedence over tag. [default: ${DOCKER_IMG}] --password Sets password for elastic user [default: ${password}] --port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}] - --ssl Sets up SSL on Elasticsearch + --ssl Sets up HTTP and Transport SSL and enables security plugin on Elasticsearch --kill Kill running ES nodes if detected -E Additional key=value settings to pass to Elasticsearch -D Override Docker command diff --git a/packages/kbn-es/src/cli_commands/serverless.ts b/packages/kbn-es/src/cli_commands/serverless.ts index 9be9e850a3e6e..7ee4f08fb94fe 100644 --- a/packages/kbn-es/src/cli_commands/serverless.ts +++ b/packages/kbn-es/src/cli_commands/serverless.ts @@ -12,7 +12,13 @@ import { ToolingLog } from '@kbn/tooling-log'; import { getTimeReporter } from '@kbn/ci-stats-reporter'; import { Cluster } from '../cluster'; -import { SERVERLESS_REPO, SERVERLESS_TAG, SERVERLESS_IMG, DEFAULT_PORT } from '../utils'; +import { + SERVERLESS_REPO, + SERVERLESS_TAG, + SERVERLESS_IMG, + DEFAULT_PORT, + ServerlessOptions, +} from '../utils'; import { Command } from './types'; export const serverless: Command = { @@ -22,14 +28,19 @@ export const serverless: Command = { return dedent` Options: - --tag Image tag of ESS to run from ${SERVERLESS_REPO} [default: ${SERVERLESS_TAG}] - --image Full path of ESS image to run, has precedence over tag. [default: ${SERVERLESS_IMG}] + --tag Image tag of ES serverless to run from ${SERVERLESS_REPO} [default: ${SERVERLESS_TAG}] + --image Full path of ES serverless image to run, has precedence over tag. [default: ${SERVERLESS_IMG}] + + --background Start ES serverless without attaching to the first node's logs + --basePath Path to the directory where the ES cluster will store data --clean Remove existing file system object store before running + --kill Kill running ES serverless nodes if detected on startup --port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}] - --ssl Sets up SSL on Elasticsearch - --kill Kill running ESS nodes if detected - --background Start ESS without attaching to the first node's logs - -E Additional key=value settings to pass to Elasticsearch + --ssl Enable HTTP SSL on the ES cluster + --skipTeardown If this process exits, leave the ES cluster running in the background + --waitForReady Wait for the ES cluster to be ready to serve requests + + -E Additional key=value settings to pass to ES -F Absolute paths for files to mount into containers Examples: @@ -54,11 +65,21 @@ export const serverless: Command = { files: 'F', }, - string: ['tag', 'image'], - boolean: ['clean', 'ssl', 'kill', 'background'], + string: ['tag', 'image', 'basePath'], + boolean: ['clean', 'ssl', 'kill', 'background', 'skipTeardown', 'waitForReady'], default: defaults, - }); + }) as unknown as ServerlessOptions; + + /* + * The nodes will be killed immediately if background = true and skipTeardown = false + * because the CLI process exits after starting the nodes. We handle this here instead of + * in runServerless because in FTR we run the nodes in the background but the parent + * process continues for testing and we want to be able to SIGINT for teardown. + */ + if (options.background && !options.skipTeardown) { + options.skipTeardown = true; + } const cluster = new Cluster(); await cluster.runServerless({ diff --git a/packages/kbn-es/src/cli_commands/snapshot.ts b/packages/kbn-es/src/cli_commands/snapshot.ts index 837acc3395e6b..cf8a5149bc892 100644 --- a/packages/kbn-es/src/cli_commands/snapshot.ts +++ b/packages/kbn-es/src/cli_commands/snapshot.ts @@ -73,16 +73,30 @@ export const snapshot: Command = { const cluster = new Cluster({ ssl: options.ssl }); if (options['download-only']) { - await cluster.downloadSnapshot(options); + await cluster.downloadSnapshot({ + version: options.version, + license: options.license, + basePath: options.basePath, + log, + useCached: options.useCached, + }); } else { const installStartTime = Date.now(); - const { installPath } = await cluster.installSnapshot(options); + const { installPath } = await cluster.installSnapshot({ + version: options.version, + license: options.license, + basePath: options.basePath, + log, + useCached: options.useCached, + password: options.password, + esArgs: options.esArgs, + }); if (options.dataArchive) { await cluster.extractDataDirectory(installPath, options.dataArchive); } if (options.plugins) { - await cluster.installPlugins(installPath, options.plugins, options); + await cluster.installPlugins(installPath, options.plugins, options.esJavaOpts); } if (typeof options.secureFiles === 'string' && options.secureFiles) { const pairs = options.secureFiles diff --git a/packages/kbn-es/src/cli_commands/source.ts b/packages/kbn-es/src/cli_commands/source.ts index 19bb59c057ac9..6916c082676c0 100644 --- a/packages/kbn-es/src/cli_commands/source.ts +++ b/packages/kbn-es/src/cli_commands/source.ts @@ -60,13 +60,20 @@ export const source: Command = { }); const cluster = new Cluster({ ssl: options.ssl }); - const { installPath } = await cluster.installSource(options); + const { installPath } = await cluster.installSource({ + sourcePath: options.sourcePath, + license: options.license, + password: options.password, + basePath: options.basePath, + installPath: options.installPath, + esArgs: options.esArgs, + }); if (options.dataArchive) { await cluster.extractDataDirectory(installPath, options.dataArchive); } if (options.plugins) { - await cluster.installPlugins(installPath, options.plugins, options); + await cluster.installPlugins(installPath, options.plugins, options.esJavaOpts); } if (typeof options.secureFiles === 'string' && options.secureFiles) { const pairs = options.secureFiles.split(',').map((kv) => kv.split('=').map((v) => v.trim())); diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js deleted file mode 100644 index dbbe3930c734f..0000000000000 --- a/packages/kbn-es/src/cluster.js +++ /dev/null @@ -1,609 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const fs = require('fs'); -const fsp = require('fs/promises'); -const execa = require('execa'); -const chalk = require('chalk'); -const path = require('path'); -const Rx = require('rxjs'); -const { Client } = require('@elastic/elasticsearch'); -const { downloadSnapshot, installSnapshot, installSource, installArchive } = require('./install'); -const { ES_BIN, ES_PLUGIN_BIN, ES_KEYSTORE_BIN } = require('./paths'); -const { - extractConfigFiles, - log: defaultLog, - NativeRealm, - parseEsLog, - parseTimeoutToMs, - runDockerContainer, - runServerlessCluster, - stopServerlessCluster, - teardownServerlessClusterSync, -} = require('./utils'); -const { createCliError } = require('./errors'); -const { promisify } = require('util'); -const treeKillAsync = promisify(require('tree-kill')); -const { parseSettings, SettingsFilter } = require('./settings'); -const { CA_CERT_PATH, ES_NOPASSWORD_P12_PATH, extract } = require('@kbn/dev-utils'); - -const DEFAULT_READY_TIMEOUT = parseTimeoutToMs('1m'); - -/** @typedef {import('./cluster_exec_options').EsClusterExecOptions} ExecOptions */ -/** @typedef {import('./utils').DockerOptions} DockerOptions */ -/** @typedef {import('./utils').ServerlessOptions}ServerlessrOptions */ - -// listen to data on stream until map returns anything but undefined -const first = (stream, map) => - new Promise((resolve) => { - const onData = (data) => { - const result = map(data); - if (result !== undefined) { - resolve(result); - stream.removeListener('data', onData); - } - }; - stream.on('data', onData); - }); - -exports.Cluster = class Cluster { - constructor({ log = defaultLog, ssl = false } = {}) { - this._log = log.withType('@kbn/es Cluster'); - this._ssl = ssl; - } - - /** - * Builds and installs ES from source - * - * @param {Object} options - * @property {Array} options.installPath - * @property {Array} options.sourcePath - * @returns {Promise<{installPath}>} - */ - async installSource(options = {}) { - this._log.info(chalk.bold('Installing from source')); - return await this._log.indent(4, async () => { - const { installPath } = await installSource({ log: this._log, ...options }); - return { installPath }; - }); - } - - /** - * Download ES from a snapshot - * - * @param {Object} options - * @property {Array} options.installPath - * @property {Array} options.sourcePath - * @returns {Promise<{installPath}>} - */ - async downloadSnapshot(options = {}) { - this._log.info(chalk.bold('Downloading snapshot')); - return await this._log.indent(4, async () => { - const { installPath } = await downloadSnapshot({ - log: this._log, - ...options, - }); - - return { installPath }; - }); - } - - /** - * Download and installs ES from a snapshot - * - * @param {Object} options - * @property {Array} options.installPath - * @property {Array} options.sourcePath - * @returns {Promise<{installPath}>} - */ - async installSnapshot(options = {}) { - this._log.info(chalk.bold('Installing from snapshot')); - return await this._log.indent(4, async () => { - const { installPath } = await installSnapshot({ - log: this._log, - ...options, - }); - - return { installPath }; - }); - } - - /** - * Installs ES from a local tar - * - * @param {String} path - * @param {Object} options - * @property {Array} options.installPath - * @returns {Promise<{installPath}>} - */ - async installArchive(path, options = {}) { - this._log.info(chalk.bold('Installing from an archive')); - return await this._log.indent(4, async () => { - const { installPath } = await installArchive(path, { - log: this._log, - ...options, - }); - - return { installPath }; - }); - } - - /** - * Unpacks a tar or zip file containing the data directory for an - * ES cluster. - * - * @param {String} installPath - * @param {String} archivePath - * @param {String} [extractDirName] - */ - async extractDataDirectory(installPath, archivePath, extractDirName = 'data') { - this._log.info(chalk.bold(`Extracting data directory`)); - await this._log.indent(4, async () => { - // stripComponents=1 excludes the root directory as that is how our archives are - // structured. This works in our favor as we can explicitly extract into the data dir - const extractPath = path.resolve(installPath, extractDirName); - this._log.info(`Data archive: ${archivePath}`); - this._log.info(`Extract path: ${extractPath}`); - - await extract({ - archivePath, - targetDir: extractPath, - stripComponents: 1, - }); - }); - } - - /** - * Starts ES and returns resolved promise once started - * - * @param {String} installPath - * @param {String} plugins - comma separated list of plugins to install - * @param {Object} options - * @returns {Promise} - */ - async installPlugins(installPath, plugins, options) { - const esJavaOpts = this.javaOptions(options); - for (const plugin of plugins.split(',')) { - await execa(ES_PLUGIN_BIN, ['install', plugin.trim()], { - cwd: installPath, - env: { - JAVA_HOME: '', // By default, we want to always unset JAVA_HOME so that the bundled JDK will be used - ES_JAVA_OPTS: esJavaOpts.trim(), - }, - }); - } - } - - async configureKeystoreWithSecureSettingsFiles(installPath, secureSettingsFiles) { - const env = { JAVA_HOME: '' }; - for (const [secureSettingName, secureSettingFile] of secureSettingsFiles) { - this._log.info( - `setting secure setting %s to %s`, - chalk.bold(secureSettingName), - chalk.bold(secureSettingFile) - ); - await execa(ES_KEYSTORE_BIN, ['add-file', secureSettingName, secureSettingFile], { - cwd: installPath, - env, - }); - } - } - - /** - * Starts ES and returns resolved promise once started - * - * @param {String} installPath - * @param {ExecOptions} options - * @returns {Promise} - */ - async start(installPath, options = {}) { - // _exec indents and we wait for our own end condition, so reset the indent level to it's current state after we're done waiting - await this._log.indent(0, async () => { - this._exec(installPath, options); - - await Promise.race([ - // wait for native realm to be setup and es to be started - Promise.all([ - first(this._process.stdout, (data) => { - if (/started/.test(data)) { - return true; - } - }), - this._setupPromise, - ]), - - // await the outcome of the process in case it exits before starting - this._outcome.then(() => { - throw createCliError('ES exited without starting'); - }), - ]); - }); - - if (options.onEarlyExit) { - this._outcome - .then( - () => { - if (!this._stopCalled) { - options.onEarlyExit(`ES exitted unexpectedly`); - } - }, - (error) => { - if (!this._stopCalled) { - options.onEarlyExit(`ES exitted unexpectedly: ${error.stack}`); - } - } - ) - .catch((error) => { - throw new Error(`failure handling early exit: ${error.stack}`); - }); - } - } - - /** - * Starts Elasticsearch and waits for Elasticsearch to exit - * - * @param {String} installPath - * @param {ExecOptions} options - * @returns {Promise} - */ - async run(installPath, options = {}) { - // _exec indents and we wait for our own end condition, so reset the indent level to it's current state after we're done waiting - await this._log.indent(0, async () => { - this._exec(installPath, options); - - // log native realm setup errors so they aren't uncaught - this._setupPromise.catch((error) => { - this._log.error(error); - this.stop(); - }); - - // await the final outcome of the process - await this._outcome; - }); - } - - /** - * Stops ES process, if it's running - * - * @returns {Promise} - */ - async stop() { - if (this._stopCalled) { - return; - } - this._stopCalled = true; - - if (this._serverlessNodes?.length) { - return await stopServerlessCluster(this._log, this._serverlessNodes); - } - - if (!this._process || !this._outcome) { - throw new Error('ES has not been started'); - } - - await treeKillAsync(this._process.pid); - - await this._outcome; - } - - /** - * Stops ES process, it it's running, without waiting for it to shutdown gracefully - */ - async kill() { - if (this._stopCalled) { - return; - } - - this._stopCalled; - - if (this._serverlessNodes?.length) { - return await stopServerlessCluster(this._log, this._serverlessNodes); - } - - if (!this._process || !this._outcome) { - throw new Error('ES has not been started'); - } - - await treeKillAsync(this._process.pid, 'SIGKILL'); - await this._outcome; - } - - /** - * Common logic from this.start() and this.run() - * - * Start the elasticsearch process (stored at `this._process`) - * and "pipe" its stdio to `this._log`. Also create `this._outcome` - * which will be resolved/rejected when the process exits. - * - * @private - * @param {String} installPath - * @param {ExecOptions} opts - */ - _exec(installPath, opts = {}) { - const { - skipNativeRealmSetup = false, - reportTime = () => {}, - startTime, - skipReadyCheck, - readyTimeout, - writeLogsToPath, - ...options - } = opts; - - if (this._process || this._outcome) { - throw new Error('ES has already been started'); - } - - /** @type {NodeJS.WritableStream | undefined} */ - let stdioTarget; - - if (writeLogsToPath) { - stdioTarget = fs.createWriteStream(writeLogsToPath, 'utf8'); - this._log.info( - chalk.bold('Starting'), - `and writing logs to ${path.relative(process.cwd(), writeLogsToPath)}` - ); - } else { - this._log.info(chalk.bold('Starting')); - } - - this._log.indent(4); - - const esArgs = new Map([ - ['action.destructive_requires_name', 'true'], - ['cluster.routing.allocation.disk.threshold_enabled', 'false'], - ['ingest.geoip.downloader.enabled', 'false'], - ['search.check_ccs_compatibility', 'true'], - ]); - - // options.esArgs overrides the default esArg values - for (const arg of [].concat(options.esArgs || [])) { - const [key, ...value] = arg.split('='); - esArgs.set(key.trim(), value.join('=').trim()); - } - - // Add to esArgs if ssl is enabled - if (this._ssl) { - esArgs.set('xpack.security.http.ssl.enabled', 'true'); - // Include default keystore settings only if ssl isn't disabled by esArgs and keystore isn't configured. - if (!esArgs.get('xpack.security.http.ssl.keystore.path')) { - // We are explicitly using ES_NOPASSWORD_P12_PATH instead of ES_P12_PATH + ES_P12_PASSWORD. The reasoning for this is that setting - // the keystore password using environment variables causes Elasticsearch to emit deprecation warnings. - esArgs.set(`xpack.security.http.ssl.keystore.path`, ES_NOPASSWORD_P12_PATH); - esArgs.set(`xpack.security.http.ssl.keystore.type`, `PKCS12`); - } - } - - const args = parseSettings( - extractConfigFiles( - Array.from(esArgs).map((e) => e.join('=')), - installPath, - { log: this._log } - ), - { - filter: SettingsFilter.NonSecureOnly, - } - ).reduce( - (acc, [settingName, settingValue]) => acc.concat(['-E', `${settingName}=${settingValue}`]), - [] - ); - - this._log.info('%s %s', ES_BIN, args.join(' ')); - const esJavaOpts = this.javaOptions(options); - - this._log.info('ES_JAVA_OPTS: %s', esJavaOpts); - - this._process = execa(ES_BIN, args, { - cwd: installPath, - env: { - ...(installPath ? { ES_TMPDIR: path.resolve(installPath, 'ES_TMPDIR') } : {}), - ...process.env, - JAVA_HOME: '', // By default, we want to always unset JAVA_HOME so that the bundled JDK will be used - ES_JAVA_OPTS: esJavaOpts, - }, - stdio: ['ignore', 'pipe', 'pipe'], - }); - - this._setupPromise = Promise.all([ - // parse log output to find http port - first(this._process.stdout, (data) => { - const match = data.toString('utf8').match(/HttpServer.+publish_address {[0-9.]+:([0-9]+)/); - - if (match) { - return match[1]; - } - }), - - // load the CA cert from disk if necessary - this._ssl ? fsp.readFile(CA_CERT_PATH) : null, - ]).then(async ([port, caCert]) => { - const client = new Client({ - node: `${caCert ? 'https:' : 'http:'}//localhost:${port}`, - auth: { - username: 'elastic', - password: options.password, - }, - tls: caCert - ? { - ca: caCert, - rejectUnauthorized: true, - } - : undefined, - }); - - if (!skipReadyCheck) { - await this._waitForClusterReady(client, readyTimeout); - } - - // once the cluster is ready setup the native realm - if (!skipNativeRealmSetup) { - const nativeRealm = new NativeRealm({ - log: this._log, - elasticPassword: options.password, - client, - }); - - await nativeRealm.setPasswords(options); - } - - this._log.success('kbn/es setup complete'); - }); - - let reportSent = false; - // parse and forward es stdout to the log - this._process.stdout.on('data', (data) => { - const chunk = data.toString(); - const lines = parseEsLog(chunk); - lines.forEach((line) => { - if (!reportSent && line.message.includes('publish_address')) { - reportSent = true; - reportTime(startTime, 'ready', { - success: true, - }); - } - - if (stdioTarget) { - stdioTarget.write(chunk); - } else { - this._log.info(line.formattedMessage); - } - }); - }); - - // forward es stderr to the log - this._process.stderr.on('data', (data) => { - const chunk = data.toString(); - if (stdioTarget) { - stdioTarget.write(chunk); - } else { - this._log.error(chalk.red(chunk.trim())); - } - }); - - // close the stdio target if we have one defined - if (stdioTarget) { - Rx.combineLatest([ - Rx.fromEvent(this._process.stderr, 'end'), - Rx.fromEvent(this._process.stdout, 'end'), - ]) - .pipe(Rx.first()) - .subscribe(() => { - stdioTarget.end(); - }); - } - - // observe the exit code of the process and reflect in _outcome promises - const exitCode = new Promise((resolve) => this._process.once('exit', resolve)); - this._outcome = exitCode.then((code) => { - if (this._stopCalled) { - return; - } - - // JVM exits with 143 on SIGTERM and 130 on SIGINT, dont' treat them as errors - if (code > 0 && !(code === 143 || code === 130)) { - reportTime(startTime, 'abort', { - success: true, - error: code, - }); - throw createCliError(`ES exited with code ${code}`); - } else { - reportTime(startTime, 'error', { - success: false, - error: `exited with ${code}`, - }); - } - }); - } - - async _waitForClusterReady(client, readyTimeout = DEFAULT_READY_TIMEOUT) { - let attempt = 0; - const start = Date.now(); - - this._log.info('waiting for ES cluster to report a yellow or green status'); - - while (true) { - attempt += 1; - - try { - const resp = await client.cluster.health(); - if (resp.status !== 'red') { - return; - } - - throw new Error(`not ready, cluster health is ${resp.status}`); - } catch (error) { - const timeSinceStart = Date.now() - start; - if (timeSinceStart > readyTimeout) { - const sec = readyTimeout / 1000; - throw new Error(`ES cluster failed to come online with the ${sec} second timeout`); - } - - if (error.message.startsWith('not ready,')) { - if (timeSinceStart > 10_000) { - this._log.warning(error.message); - } - } else { - this._log.warning( - `waiting for ES cluster to come online, attempt ${attempt} failed with: ${error.message}` - ); - } - - const waitSec = attempt * 1.5; - await new Promise((resolve) => setTimeout(resolve, waitSec * 1000)); - } - } - } - - javaOptions(options) { - let esJavaOpts = `${options.esJavaOpts || ''} ${process.env.ES_JAVA_OPTS || ''}`; - - // ES now automatically sets heap size to 50% of the machine's available memory - // so we need to set it to a smaller size for local dev and CI - // especially because we currently run many instances of ES on the same machine during CI - // inital and max must be the same, so we only need to check the max - if (!esJavaOpts.includes('Xmx')) { - // 1536m === 1.5g - esJavaOpts += ' -Xms1536m -Xmx1536m'; - } - return esJavaOpts.trim(); - } - - /** - * Run an Elasticsearch Serverless Docker cluster - * - * @param {ServerlessOptions} options - */ - async runServerless(options = {}) { - if (this._process || this._outcome) { - throw new Error('ES has already been started'); - } - - this._serverlessNodes = await runServerlessCluster(this._log, options); - - if (options.teardown) { - /** - * Ideally would be async and an event like beforeExit or SIGINT, - * but those events are not being triggered in FTR child process. - */ - process.on('exit', () => teardownServerlessClusterSync(this._log, options)); - } - } - - /** - * Run an Elasticsearch Docker container - * - * @param {DockerOptions} options - */ - async runDocker(options = {}) { - if (this._process || this._outcome) { - throw new Error('ES has already been started'); - } - - this._process = await runDockerContainer(this._log, options); - } -}; diff --git a/packages/kbn-es/src/cluster.ts b/packages/kbn-es/src/cluster.ts new file mode 100644 index 0000000000000..e2ddf43f32dc7 --- /dev/null +++ b/packages/kbn-es/src/cluster.ts @@ -0,0 +1,564 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs'; +import fsp from 'fs/promises'; +import chalk from 'chalk'; +import * as path from 'path'; +import execa from 'execa'; +import { Readable } from 'stream'; +import { combineLatest, fromEvent, first } from 'rxjs'; +import { Client } from '@elastic/elasticsearch'; +import { promisify } from 'util'; +import { CA_CERT_PATH, ES_NOPASSWORD_P12_PATH, extract } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/tooling-log'; +import treeKill from 'tree-kill'; +import { downloadSnapshot, installSnapshot, installSource, installArchive } from './install'; +import { ES_BIN, ES_PLUGIN_BIN, ES_KEYSTORE_BIN } from './paths'; +import { + DockerOptions, + extractConfigFiles, + log as defaultLog, + NativeRealm, + parseEsLog, + runDockerContainer, + runServerlessCluster, + ServerlessOptions, + stopServerlessCluster, + teardownServerlessClusterSync, +} from './utils'; +import { createCliError } from './errors'; +const treeKillAsync = promisify(treeKill); +import { parseSettings, SettingsFilter } from './settings'; +import { EsClusterExecOptions } from './cluster_exec_options'; +import { + DownloadSnapshotOptions, + InstallArchiveOptions, + InstallSnapshotOptions, + InstallSourceOptions, +} from './install/types'; +import { waitUntilClusterReady } from './utils/wait_until_cluster_ready'; + +// listen to data on stream until map returns anything but undefined +const firstResult = (stream: Readable, map: (data: Buffer) => string | true | undefined) => + new Promise((resolve) => { + const onData = (data: any) => { + const result = map(data); + if (result !== undefined) { + resolve(result); + stream.removeListener('data', onData); + } + }; + stream.on('data', onData); + }); + +interface StopOptions { + gracefully: boolean; +} +export class Cluster { + private log: ToolingLog; + private ssl: boolean; + private stopCalled: boolean; + private process: execa.ExecaChildProcess | null; + private outcome: Promise | null; + private serverlessNodes: string[]; + private setupPromise: Promise | null; + private stdioTarget: NodeJS.WritableStream | null; + + constructor({ log = defaultLog, ssl = false } = {}) { + this.log = log.withType('@kbn/es Cluster'); + this.ssl = ssl; + this.stopCalled = false; + // Serverless Elasticsearch node names, started via Docker + this.serverlessNodes = []; + // properties used exclusively for the locally started Elasticsearch cluster + this.process = null; + this.outcome = null; + this.setupPromise = null; + this.stdioTarget = null; + } + + /** + * Builds and installs ES from source + */ + async installSource(options: InstallSourceOptions) { + this.log.info(chalk.bold('Installing from source')); + return await this.log.indent(4, async () => { + const { installPath } = await installSource({ log: this.log, ...options }); + return { installPath }; + }); + } + + /** + * Download ES from a snapshot + */ + async downloadSnapshot(options: DownloadSnapshotOptions) { + this.log.info(chalk.bold('Downloading snapshot')); + return await this.log.indent(4, async () => { + const { downloadPath } = await downloadSnapshot({ + log: this.log, + ...options, + }); + + return { downloadPath }; + }); + } + + /** + * Download and installs ES from a snapshot + */ + async installSnapshot(options: InstallSnapshotOptions) { + this.log.info(chalk.bold('Installing from snapshot')); + return await this.log.indent(4, async () => { + const { installPath } = await installSnapshot({ + log: this.log, + ...options, + }); + + return { installPath }; + }); + } + + /** + * Installs ES from a local tar + */ + async installArchive(archivePath: string, options?: InstallArchiveOptions) { + this.log.info(chalk.bold('Installing from an archive')); + return await this.log.indent(4, async () => { + const { installPath } = await installArchive(archivePath, { + log: this.log, + ...(options || {}), + }); + + return { installPath }; + }); + } + + /** + * Unpacks a tar or zip file containing the data directory for an ES cluster. + */ + async extractDataDirectory(installPath: string, archivePath: string, extractDirName = 'data') { + this.log.info(chalk.bold(`Extracting data directory`)); + await this.log.indent(4, async () => { + // stripComponents=1 excludes the root directory as that is how our archives are + // structured. This works in our favor as we can explicitly extract into the data dir + const extractPath = path.resolve(installPath, extractDirName); + this.log.info(`Data archive: ${archivePath}`); + this.log.info(`Extract path: ${extractPath}`); + + await extract({ + archivePath, + targetDir: extractPath, + stripComponents: 1, + }); + }); + } + + /** + * Installs comma separated list of ES plugins to the specified path + */ + async installPlugins(installPath: string, plugins: string, esJavaOpts?: string) { + const javaOpts = this.getJavaOptions(esJavaOpts); + for (const plugin of plugins.split(',')) { + await execa(ES_PLUGIN_BIN, ['install', plugin.trim()], { + cwd: installPath, + env: { + JAVA_HOME: '', // By default, we want to always unset JAVA_HOME so that the bundled JDK will be used + ES_JAVA_OPTS: javaOpts, + }, + }); + } + } + + async configureKeystoreWithSecureSettingsFiles( + installPath: string, + secureSettingsFiles: string[][] + ) { + const env = { JAVA_HOME: '' }; + for (const [secureSettingName, secureSettingFile] of secureSettingsFiles) { + this.log.info( + `setting secure setting %s to %s`, + chalk.bold(secureSettingName), + chalk.bold(secureSettingFile) + ); + await execa(ES_KEYSTORE_BIN, ['add-file', secureSettingName, secureSettingFile], { + cwd: installPath, + env, + }); + } + } + + /** + * Starts ES and returns resolved promise once started + */ + async start(installPath: string, options: EsClusterExecOptions) { + // `exec` indents and we wait for our own end condition, so reset the indent level to it's current state after we're done waiting + await this.log.indent(0, async () => { + this.exec(installPath, options); + + await Promise.race([ + // wait for native realm to be setup and es to be started + Promise.all([ + firstResult(this.process?.stdout!, (data: Buffer) => { + if (/started/.test(data.toString('utf-8'))) { + return true; + } + }), + this.setupPromise, + ]), + + // await the outcome of the process in case it exits before starting + this.outcome?.then(() => { + throw createCliError('ES exited without starting'); + }), + ]); + }); + + if (options.onEarlyExit) { + this.outcome + ?.then( + () => { + if (!this.stopCalled && options.onEarlyExit) { + options.onEarlyExit(`ES exitted unexpectedly`); + } + }, + (error: Error) => { + if (!this.stopCalled && options.onEarlyExit) { + options.onEarlyExit(`ES exitted unexpectedly: ${error.stack}`); + } + } + ) + .catch((error: Error) => { + throw new Error(`failure handling early exit: ${error.stack}`); + }); + } + } + + /** + * Starts Elasticsearch and waits for Elasticsearch to exit + */ + async run(installPath: string, options: EsClusterExecOptions) { + // `exec` indents and we wait for our own end condition, so reset the indent level to it's current state after we're done waiting + await this.log.indent(0, async () => { + this.exec(installPath, options); + + // log native realm setup errors so they aren't uncaught + this.setupPromise?.catch((error: Error) => { + this.log.error(error); + this.stop(); + }); + + // await the final outcome of the process + await this.outcome; + }); + } + + /** + * Stops cluster + */ + private async stopCluster(options: StopOptions) { + if (this.stopCalled) { + return; + } + this.stopCalled = true; + + // Stop ES docker containers + if (this.serverlessNodes.length) { + return await stopServerlessCluster(this.log, this.serverlessNodes); + } + + // Stop local ES process + if (!this.process || !this.outcome) { + throw new Error('ES has not been started'); + } + + const pid = this.process.pid; + + if (pid) { + await treeKillAsync(pid, options.gracefully ? 'SIGTERM' : 'SIGKILL'); + } else { + throw Error(`ES process pid is not defined, can't stop it`); + } + + await this.outcome; + } + + /** + * Stops ES process, if it's running + */ + async stop() { + await this.stopCluster({ gracefully: true }); + } + + /** + * Stops ES process without waiting for it to shutdown gracefully + */ + async kill() { + await this.stopCluster({ gracefully: false }); + } + + /** + * Common logic from this.start() and this.run() + * + * Start the Elasticsearch process (stored at `this.process`) + * and "pipe" its stdio to `this.log`. Also create `this.outcome` + * which will be resolved/rejected when the process exits. + */ + private exec(installPath: string, opts: EsClusterExecOptions) { + const { + skipNativeRealmSetup = false, + reportTime = () => {}, + startTime, + skipReadyCheck, + readyTimeout, + writeLogsToPath, + ...options + } = opts; + + if (this.process || this.outcome) { + throw new Error('ES has already been started'); + } + + if (writeLogsToPath) { + this.stdioTarget = fs.createWriteStream(writeLogsToPath, 'utf8'); + this.log.info( + chalk.bold('Starting'), + `and writing logs to ${path.resolve(process.cwd(), writeLogsToPath)}` + ); + } else { + this.log.info(chalk.bold('Starting')); + } + + this.log.indent(4); + + const esArgs = new Map([ + ['action.destructive_requires_name', 'true'], + ['cluster.routing.allocation.disk.threshold_enabled', 'false'], + ['ingest.geoip.downloader.enabled', 'false'], + ['search.check_ccs_compatibility', 'true'], + ]); + + // options.esArgs overrides the default esArg values + const _esArgs = options.esArgs + ? Array.isArray(options.esArgs) + ? options.esArgs + : [options.esArgs] + : []; + for (const arg of _esArgs) { + const [key, ...value] = arg.split('='); + esArgs.set(key.trim(), value.join('=').trim()); + } + + // Add to esArgs if ssl is enabled + if (this.ssl) { + esArgs.set('xpack.security.http.ssl.enabled', 'true'); + // Include default keystore settings only if ssl isn't disabled by esArgs and keystore isn't configured. + if (!esArgs.get('xpack.security.http.ssl.keystore.path')) { + // We are explicitly using ES_NOPASSWORD_P12_PATH instead of ES_P12_PATH + ES_P12_PASSWORD. The reasoning for this is that setting + // the keystore password using environment variables causes Elasticsearch to emit deprecation warnings. + esArgs.set(`xpack.security.http.ssl.keystore.path`, ES_NOPASSWORD_P12_PATH); + esArgs.set(`xpack.security.http.ssl.keystore.type`, `PKCS12`); + } + } + + const args = parseSettings( + extractConfigFiles( + Array.from(esArgs).map((e) => e.join('=')), + installPath, + { log: this.log } + ), + { + filter: SettingsFilter.NonSecureOnly, + } + ).reduce( + (acc: string[], [settingName, settingValue]) => + acc.concat(['-E', `${settingName}=${settingValue}`]), + [] + ); + + this.log.info('%s %s', ES_BIN, args.join(' ')); + const esJavaOpts = this.getJavaOptions(options.esJavaOpts); + + this.log.info('ES_JAVA_OPTS: %s', esJavaOpts); + + this.process = execa(ES_BIN, args, { + cwd: installPath, + env: { + ...(installPath ? { ES_TMPDIR: path.resolve(installPath, 'ES_TMPDIR') } : {}), + ...process.env, + JAVA_HOME: '', // By default, we want to always unset JAVA_HOME so that the bundled JDK will be used + ES_JAVA_OPTS: esJavaOpts, + }, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + this.setupPromise = Promise.all([ + // parse log output to find http port + firstResult(this.process.stdout!, (data: Buffer) => { + const match = data.toString('utf8').match(/HttpServer.+publish_address {[0-9.]+:([0-9]+)/); + + if (match) { + return match[1]; + } + }), + + // load the CA cert from disk if necessary + this.ssl ? fsp.readFile(CA_CERT_PATH) : null, + ]).then(async ([port, caCert]) => { + const client = new Client({ + node: `${caCert ? 'https:' : 'http:'}//localhost:${port}`, + auth: { + username: 'elastic', + password: options.password!, + }, + tls: caCert + ? { + ca: caCert, + rejectUnauthorized: true, + } + : undefined, + }); + + if (!skipReadyCheck) { + await waitUntilClusterReady({ + client, + expectedStatus: 'yellow', + log: this.log, + readyTimeout, + }); + } + + // once the cluster is ready setup the native realm + if (!skipNativeRealmSetup) { + const nativeRealm = new NativeRealm({ + log: this.log, + elasticPassword: options.password, + client, + }); + + await nativeRealm.setPasswords(options); + } + + this.log.success('kbn/es setup complete'); + }); + + let reportSent = false; + // parse and forward es stdout to the log + this.process.stdout!.on('data', (data) => { + const chunk = data.toString(); + const lines = parseEsLog(chunk); + lines.forEach((line) => { + if (!reportSent && line.message.includes('publish_address')) { + reportSent = true; + reportTime(startTime, 'ready', { + success: true, + }); + } + + if (this.stdioTarget) { + this.stdioTarget.write(chunk); + } else { + this.log.info(line.formattedMessage); + } + }); + }); + + // forward es stderr to the log + this.process.stderr!.on('data', (data) => { + const chunk = data.toString(); + if (this.stdioTarget) { + this.stdioTarget.write(chunk); + } else { + this.log.error(chalk.red(chunk.trim())); + } + }); + + // close the stdio target if we have one defined + if (this.stdioTarget) { + combineLatest([ + fromEvent(this.process.stderr!, 'end'), + fromEvent(this.process.stdout!, 'end'), + ]) + .pipe(first()) + .subscribe(() => { + this.stdioTarget?.end(); + }); + } + + // observe the exit code of the process and reflect in `this.outcome` promises + const exitCode: Promise = new Promise((resolve) => this.process?.once('exit', resolve)); + this.outcome = exitCode.then((code) => { + if (this.stopCalled) { + return; + } + + // JVM exits with 143 on SIGTERM and 130 on SIGINT, dont' treat them as errors + if (code > 0 && !(code === 143 || code === 130)) { + reportTime(startTime, 'abort', { + success: true, + error: code, + }); + throw createCliError(`ES exited with code ${code}`); + } else { + reportTime(startTime, 'error', { + success: false, + error: `exited with ${code}`, + }); + } + }); + } + + private getJavaOptions(opts: string | undefined) { + let esJavaOpts = `${opts || ''} ${process.env.ES_JAVA_OPTS || ''}`; + // ES now automatically sets heap size to 50% of the machine's available memory + // so we need to set it to a smaller size for local dev and CI + // especially because we currently run many instances of ES on the same machine during CI + // inital and max must be the same, so we only need to check the max + if (!esJavaOpts.includes('Xmx')) { + // 1536m === 1.5g + esJavaOpts += ' -Xms1536m -Xmx1536m'; + } + return esJavaOpts.trim(); + } + + /** + * Runs an Elasticsearch Serverless Docker cluster and returns node names + */ + async runServerless(options: ServerlessOptions) { + if (this.process || this.outcome) { + throw new Error('ES stateful cluster has already been started'); + } + + if (this.serverlessNodes.length > 0) { + throw new Error('ES serverless docker cluster has already been started'); + } + + if (!options.skipTeardown) { + /** + * Ideally would be async and an event like beforeExit or SIGINT, + * but those events are not being triggered in FTR child process. + */ + process.on('exit', () => teardownServerlessClusterSync(this.log, options)); + } + + this.serverlessNodes = await runServerlessCluster(this.log, options); + + return this.serverlessNodes; + } + + /** + * Run an Elasticsearch Docker container + */ + async runDocker(options: DockerOptions) { + if (this.process || this.outcome) { + throw new Error('ES stateful cluster has already been started'); + } + + await runDockerContainer(this.log, options); + } +} diff --git a/packages/kbn-es/src/ess_resources/README.md b/packages/kbn-es/src/ess_resources/README.md deleted file mode 100644 index a7af386bcff1f..0000000000000 --- a/packages/kbn-es/src/ess_resources/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Elasticsearch Serverless Resources -The resources in this directory are used for seeding Elasticsearch Serverless (ESS) images with users, roles and tokens for SSL and authentication. ESS requires file realm authentication, so we will bind mount them into the containers at `/usr/share/elasticsearch/config/`. - -## Users - -### Default user - -The default superuser authentication to login to Kibana is: - -``` -username: elastic_serverless -password: changeme -``` - -### Adding users - -1. Add the user:encrypted_password to `users` file. The encrypted password for `elastic_serverless` is `changeme` if you want to reuse the value. -1. Set the new user's roles in `users_roles` file. -1. Add the username to `operator_users.yml` in the array for file realm users. - - -## Service Account and Tokens - -This section for Service Accounts was originally from the [ESS repository](https://github.com/elastic/elasticsearch-serverless/blob/main/serverless-build-tools/src/main/resources/README.service_tokens.md). - -The "service_tokens" file contains this line: -``` -elastic/kibana/kibana-dev:$2a$10$mY2RuGROhk56vLNh.Mgwue98BnkdQPlTR.yGh38ao5jhPJobvuBCq -``` - -That line defines a single service token -- For the `elastic/kibana` service account -- The token is named `kibana-dev` -- The token's secret is hashed using bcrypt (`$2a$`) using `10` rounds - -Although Elasticsearch used PBKDF2_STRETCH by default, the k8s controller -creates tokens using bcrypt, so we mimic that here. - -The hash is not reversible, so this README is here to tell you what the secret is. -The secret value is: `UUUUUULK-* Z4` -That produces an encoded token of: `AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS1kZXY6VVVVVVVVTEstKiBaNA` -Yes, the secret was specially chosen to produce an encoded value that can be more easily recognised in development. - -If a node is configured to use this `service_tokens` file, then you can authenticate to it with -``` -curl -H "Authorization: Bearer AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS1kZXY6VVVVVVVVTEstKiBaNA" http://localhost:9200/_security/_authenticate -``` - -The name of the token (`kibana-dev`) is important because the `operator_users.yml` file designates that token as an operator and allows us to seed an ESS cluster with this token. \ No newline at end of file diff --git a/packages/kbn-es/src/install/install_archive.ts b/packages/kbn-es/src/install/install_archive.ts index 9ccf00e09cf85..352b0fe6007db 100644 --- a/packages/kbn-es/src/install/install_archive.ts +++ b/packages/kbn-es/src/install/install_archive.ts @@ -19,15 +19,7 @@ import { BASE_PATH, ES_CONFIG, ES_KEYSTORE_BIN } from '../paths'; import { Artifact } from '../artifact'; import { parseSettings, SettingsFilter } from '../settings'; import { log as defaultLog } from '../utils/log'; - -interface InstallArchiveOptions { - license?: string; - password?: string; - basePath?: string; - installPath?: string; - log?: ToolingLog; - esArgs?: string[]; -} +import { InstallArchiveOptions } from './types'; const isHttpUrl = (str: string) => { try { @@ -40,7 +32,7 @@ const isHttpUrl = (str: string) => { /** * Extracts an ES archive and optionally installs plugins */ -export async function installArchive(archive: string, options: InstallArchiveOptions = {}) { +export async function installArchive(archive: string, options?: InstallArchiveOptions) { const { license = 'basic', password = 'changeme', @@ -48,7 +40,7 @@ export async function installArchive(archive: string, options: InstallArchiveOpt installPath = path.resolve(basePath, path.basename(archive, '.tar.gz')), log = defaultLog, esArgs = [], - } = options; + } = options || {}; let dest = archive; if (isHttpUrl(archive)) { diff --git a/packages/kbn-es/src/install/install_snapshot.ts b/packages/kbn-es/src/install/install_snapshot.ts index 1ab0facfd2601..e4a7fbd678f78 100644 --- a/packages/kbn-es/src/install/install_snapshot.ts +++ b/packages/kbn-es/src/install/install_snapshot.ts @@ -7,23 +7,13 @@ */ import path from 'path'; - import chalk from 'chalk'; -import { ToolingLog } from '@kbn/tooling-log'; import { BASE_PATH } from '../paths'; import { installArchive } from './install_archive'; import { log as defaultLog } from '../utils/log'; -import { Artifact, ArtifactLicense } from '../artifact'; - -interface DownloadSnapshotOptions { - version: string; - license?: ArtifactLicense; - basePath?: string; - installPath?: string; - log?: ToolingLog; - useCached?: boolean; -} +import { Artifact } from '../artifact'; +import { DownloadSnapshotOptions, InstallSnapshotOptions } from './types'; /** * Download an ES snapshot @@ -49,11 +39,6 @@ export async function downloadSnapshot({ }; } -interface InstallSnapshotOptions extends DownloadSnapshotOptions { - password?: string; - esArgs?: string[]; -} - /** * Installs ES from snapshot */ diff --git a/packages/kbn-es/src/install/install_source.ts b/packages/kbn-es/src/install/install_source.ts index 27b2fba7cf635..b3fca2b2ac046 100644 --- a/packages/kbn-es/src/install/install_source.ts +++ b/packages/kbn-es/src/install/install_source.ts @@ -20,16 +20,7 @@ import { log as defaultLog } from '../utils/log'; import { cache } from '../utils/cache'; import { buildSnapshot, archiveForPlatform } from '../utils/build_snapshot'; import { BASE_PATH } from '../paths'; - -interface InstallSourceOptions { - sourcePath: string; - license?: string; - password?: string; - basePath?: string; - installPath?: string; - log?: ToolingLog; - esArgs?: string[]; -} +import { InstallSourceOptions } from './types'; /** * Installs ES from source diff --git a/packages/kbn-es/src/install/types.ts b/packages/kbn-es/src/install/types.ts new file mode 100644 index 0000000000000..e4b750c0ec472 --- /dev/null +++ b/packages/kbn-es/src/install/types.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ToolingLog } from '@kbn/tooling-log'; +import type { ArtifactLicense } from '../artifact'; + +export interface InstallSourceOptions { + sourcePath: string; + license?: ArtifactLicense; + password?: string; + basePath?: string; + installPath?: string; + log?: ToolingLog; + esArgs?: string[]; +} + +export interface DownloadSnapshotOptions { + version: string; + license?: ArtifactLicense; + basePath?: string; + installPath?: string; + log?: ToolingLog; + useCached?: boolean; +} + +export interface InstallSnapshotOptions extends DownloadSnapshotOptions { + password?: string; + esArgs?: string[]; +} + +export interface InstallArchiveOptions { + license?: ArtifactLicense; + password?: string; + basePath?: string; + installPath?: string; + log?: ToolingLog; + esArgs?: string[]; +} diff --git a/packages/kbn-es/src/integration_tests/cluster.test.js b/packages/kbn-es/src/integration_tests/cluster.test.js deleted file mode 100644 index 9f62e0c46a018..0000000000000 --- a/packages/kbn-es/src/integration_tests/cluster.test.js +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const { ES_NOPASSWORD_P12_PATH } = require('@kbn/dev-utils'); -const { ToolingLog, ToolingLogCollectingWriter } = require('@kbn/tooling-log'); -const { createAnyInstanceSerializer, createStripAnsiSerializer } = require('@kbn/jest-serializers'); -const execa = require('execa'); -const { Cluster } = require('../cluster'); -const { installSource, installSnapshot, installArchive } = require('../install'); -const { extractConfigFiles } = require('../utils/extract_config_files'); - -expect.addSnapshotSerializer(createAnyInstanceSerializer(ToolingLog)); -expect.addSnapshotSerializer(createStripAnsiSerializer()); - -jest.mock('../install', () => ({ - installSource: jest.fn(), - installSnapshot: jest.fn(), - installArchive: jest.fn(), -})); - -jest.mock('execa', () => jest.fn()); -jest.mock('../utils/extract_config_files', () => ({ - extractConfigFiles: jest.fn(), -})); - -const log = new ToolingLog(); -const logWriter = new ToolingLogCollectingWriter(); -log.setWriters([logWriter]); - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function ensureNoResolve(promise) { - await Promise.race([ - sleep(100), - promise.then(() => { - throw new Error('promise was not supposed to resolve'); - }), - ]); -} - -async function ensureResolve(promise) { - return await Promise.race([ - promise, - sleep(100).then(() => { - throw new Error('promise was supposed to resolve with installSource() resolution'); - }), - ]); -} - -function mockEsBin({ exitCode, start }) { - execa.mockImplementationOnce((cmd, args, options) => - jest.requireActual('execa')( - process.execPath, - [ - '--require=@kbn/babel-register/install', - require.resolve('./__fixtures__/es_bin.js'), - JSON.stringify({ - exitCode, - start, - ssl: args.includes('xpack.security.http.ssl.enabled=true'), - }), - ], - options - ) - ); -} - -const initialEnv = { ...process.env }; - -beforeEach(() => { - jest.resetAllMocks(); - extractConfigFiles.mockImplementation((config) => config); - log.indent(-log.getIndent()); - logWriter.messages.length = 0; -}); - -afterEach(() => { - process.env = { ...initialEnv }; -}); - -describe('#installSource()', () => { - it('awaits installSource() promise and returns { installPath }', async () => { - let resolveInstallSource; - installSource.mockImplementationOnce( - () => - new Promise((resolve) => { - resolveInstallSource = () => { - resolve({ installPath: 'foo' }); - }; - }) - ); - - const cluster = new Cluster({ log }); - const promise = cluster.installSource(); - await ensureNoResolve(promise); - resolveInstallSource(); - await expect(ensureResolve(promise)).resolves.toEqual({ - installPath: 'foo', - }); - }); - - it('passes through all options+log to installSource()', async () => { - installSource.mockResolvedValue({}); - const cluster = new Cluster({ log }); - await cluster.installSource({ foo: 'bar' }); - expect(installSource.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "foo": "bar", - "log": , - }, - ], - ] - `); - expect(logWriter.messages).toMatchInlineSnapshot(` - Array [ - " info source[@kbn/es Cluster] Installing from source", - ] - `); - }); - - it('rejects if installSource() rejects', async () => { - installSource.mockRejectedValue(new Error('foo')); - const cluster = new Cluster({ log }); - await expect(cluster.installSource()).rejects.toThrowError('foo'); - }); -}); - -describe('#installSnapshot()', () => { - it('awaits installSnapshot() promise and returns { installPath }', async () => { - let resolveInstallSnapshot; - installSnapshot.mockImplementationOnce( - () => - new Promise((resolve) => { - resolveInstallSnapshot = () => { - resolve({ installPath: 'foo' }); - }; - }) - ); - - const cluster = new Cluster({ log }); - const promise = cluster.installSnapshot(); - await ensureNoResolve(promise); - resolveInstallSnapshot(); - await expect(ensureResolve(promise)).resolves.toEqual({ - installPath: 'foo', - }); - }); - - it('passes through all options+log to installSnapshot()', async () => { - installSnapshot.mockResolvedValue({}); - const cluster = new Cluster({ log }); - await cluster.installSnapshot({ foo: 'bar' }); - expect(installSnapshot.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "foo": "bar", - "log": , - }, - ], - ] - `); - expect(logWriter.messages).toMatchInlineSnapshot(` - Array [ - " info source[@kbn/es Cluster] Installing from snapshot", - ] - `); - }); - - it('rejects if installSnapshot() rejects', async () => { - installSnapshot.mockRejectedValue(new Error('foo')); - const cluster = new Cluster({ log }); - await expect(cluster.installSnapshot()).rejects.toThrowError('foo'); - }); -}); - -describe('#installArchive(path)', () => { - it('awaits installArchive() promise and returns { installPath }', async () => { - let resolveInstallArchive; - installArchive.mockImplementationOnce( - () => - new Promise((resolve) => { - resolveInstallArchive = () => { - resolve({ installPath: 'foo' }); - }; - }) - ); - - const cluster = new Cluster({ log }); - const promise = cluster.installArchive(); - await ensureNoResolve(promise); - resolveInstallArchive(); - await expect(ensureResolve(promise)).resolves.toEqual({ - installPath: 'foo', - }); - }); - - it('passes through path and all options+log to installArchive()', async () => { - installArchive.mockResolvedValue({}); - const cluster = new Cluster({ log }); - await cluster.installArchive('path', { foo: 'bar' }); - expect(installArchive.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "path", - Object { - "foo": "bar", - "log": , - }, - ], - ] - `); - expect(logWriter.messages).toMatchInlineSnapshot(` - Array [ - " info source[@kbn/es Cluster] Installing from an archive", - ] - `); - }); - - it('rejects if installArchive() rejects', async () => { - installArchive.mockRejectedValue(new Error('foo')); - const cluster = new Cluster({ log }); - await expect(cluster.installArchive()).rejects.toThrowError('foo'); - }); -}); - -describe('#start(installPath)', () => { - it('rejects when bin/elasticsearch exists with 0 before starting', async () => { - mockEsBin({ exitCode: 0, start: false }); - - await expect(new Cluster({ log }).start()).rejects.toThrowError('ES exited without starting'); - }); - - it('rejects when bin/elasticsearch exists with 143 before starting', async () => { - mockEsBin({ exitCode: 143, start: false }); - - await expect(new Cluster({ log }).start()).rejects.toThrowError('ES exited without starting'); - }); - - it('rejects when bin/elasticsearch exists with 130 before starting', async () => { - mockEsBin({ exitCode: 130, start: false }); - - await expect(new Cluster({ log }).start()).rejects.toThrowError('ES exited without starting'); - }); - - it('rejects when bin/elasticsearch exists with 1 before starting', async () => { - mockEsBin({ exitCode: 1, start: false }); - - await expect(new Cluster({ log }).start()).rejects.toThrowError('ES exited with code 1'); - }); - - it('resolves when bin/elasticsearch logs "started"', async () => { - mockEsBin({ start: true }); - - await new Cluster({ log }).start(); - }); - - it('rejects if #start() was called previously', async () => { - mockEsBin({ start: true }); - - const cluster = new Cluster({ log }); - await cluster.start(); - await expect(cluster.start()).rejects.toThrowError('ES has already been started'); - }); - - it('rejects if #run() was called previously', async () => { - mockEsBin({ start: true }); - - const cluster = new Cluster({ log }); - await cluster.run(); - await expect(cluster.start()).rejects.toThrowError('ES has already been started'); - }); - - it('sets up SSL when enabled', async () => { - mockEsBin({ start: true, ssl: true }); - - const cluster = new Cluster({ log, ssl: true }); - await cluster.start(); - - const config = extractConfigFiles.mock.calls[0][0]; - expect(config).toContain('xpack.security.http.ssl.enabled=true'); - expect(config).toContain(`xpack.security.http.ssl.keystore.path=${ES_NOPASSWORD_P12_PATH}`); - expect(config).toContain(`xpack.security.http.ssl.keystore.type=PKCS12`); - }); - - it(`doesn't setup SSL when disabled`, async () => { - mockEsBin({ start: true }); - - extractConfigFiles.mockReturnValueOnce([]); - - const cluster = new Cluster({ log, ssl: false }); - await cluster.start(); - - expect(extractConfigFiles.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Array [ - "action.destructive_requires_name=true", - "cluster.routing.allocation.disk.threshold_enabled=false", - "ingest.geoip.downloader.enabled=false", - "search.check_ccs_compatibility=true", - ], - undefined, - Object { - "log": , - }, - ], - ] - `); - }); - - it(`allows overriding search.check_ccs_compatibility`, async () => { - mockEsBin({ start: true }); - - extractConfigFiles.mockReturnValueOnce([]); - - const cluster = new Cluster({ - log, - ssl: false, - }); - - await cluster.start(undefined, { - esArgs: ['search.check_ccs_compatibility=false'], - }); - - expect(extractConfigFiles.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Array [ - "action.destructive_requires_name=true", - "cluster.routing.allocation.disk.threshold_enabled=false", - "ingest.geoip.downloader.enabled=false", - "search.check_ccs_compatibility=false", - ], - undefined, - Object { - "log": , - }, - ], - ] - `); - }); -}); - -describe('#run()', () => { - it('resolves when bin/elasticsearch exists with 0', async () => { - mockEsBin({ exitCode: 0 }); - - await new Cluster({ log }).run(); - }); - - it('resolves when bin/elasticsearch exists with 143', async () => { - mockEsBin({ exitCode: 143 }); - - await new Cluster({ log }).run(); - }); - - it('resolves when bin/elasticsearch exists with 130', async () => { - mockEsBin({ exitCode: 130 }); - - await new Cluster({ log }).run(); - }); - - it('rejects when bin/elasticsearch exists with 1', async () => { - mockEsBin({ exitCode: 1 }); - - await expect(new Cluster({ log }).run()).rejects.toThrowError('ES exited with code 1'); - }); - - it('rejects if #start() was called previously', async () => { - mockEsBin({ exitCode: 0, start: true }); - - const cluster = new Cluster({ log }); - await cluster.start(); - await expect(cluster.run()).rejects.toThrowError('ES has already been started'); - }); - - it('rejects if #run() was called previously', async () => { - mockEsBin({ exitCode: 0 }); - - const cluster = new Cluster({ log }); - await cluster.run(); - await expect(cluster.run()).rejects.toThrowError('ES has already been started'); - }); - - it('sets up SSL when enabled', async () => { - mockEsBin({ start: true, ssl: true }); - - const cluster = new Cluster({ log, ssl: true }); - await cluster.run(); - - const config = extractConfigFiles.mock.calls[0][0]; - expect(config).toContain('xpack.security.http.ssl.enabled=true'); - expect(config).toContain(`xpack.security.http.ssl.keystore.path=${ES_NOPASSWORD_P12_PATH}`); - expect(config).toContain(`xpack.security.http.ssl.keystore.type=PKCS12`); - }); - - it(`doesn't setup SSL when disabled`, async () => { - mockEsBin({ start: true }); - - extractConfigFiles.mockReturnValueOnce([]); - - const cluster = new Cluster({ log, ssl: false }); - await cluster.run(); - - expect(extractConfigFiles.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Array [ - "action.destructive_requires_name=true", - "cluster.routing.allocation.disk.threshold_enabled=false", - "ingest.geoip.downloader.enabled=false", - "search.check_ccs_compatibility=true", - ], - undefined, - Object { - "log": , - }, - ], - ] - `); - }); - - it('sets default Java heap', async () => { - mockEsBin({ start: true }); - - const cluster = new Cluster({ log }); - await cluster.run(); - - expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toMatchInlineSnapshot(`"-Xms1536m -Xmx1536m"`); - }); - - it('allows Java heap to be overwritten', async () => { - mockEsBin({ start: true }); - process.env.ES_JAVA_OPTS = '-Xms5g -Xmx5g'; - - const cluster = new Cluster({ log }); - await cluster.run(); - - expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toMatchInlineSnapshot(`"-Xms5g -Xmx5g"`); - }); -}); - -describe('#stop()', () => { - it('rejects if #run() or #start() was not called', async () => { - const cluster = new Cluster({ log }); - await expect(cluster.stop()).rejects.toThrowError('ES has not been started'); - }); - - it('resolves when ES exits with 0', async () => { - mockEsBin({ exitCode: 0, start: true }); - - const cluster = new Cluster({ log }); - await cluster.start(); - await cluster.stop(); - }); - - it('resolves when ES exits with 143', async () => { - mockEsBin({ exitCode: 143, start: true }); - - const cluster = new Cluster({ log }); - await cluster.start(); - await cluster.stop(); - }); - - it('resolves when ES exits with 130', async () => { - mockEsBin({ exitCode: 130, start: true }); - - const cluster = new Cluster({ log }); - await cluster.start(); - await cluster.stop(); - }); - - it('rejects when ES exits with 1', async () => { - mockEsBin({ exitCode: 1, start: true }); - - const cluster = new Cluster({ log }); - await expect(cluster.run()).rejects.toThrowError('ES exited with code 1'); - await expect(cluster.stop()).rejects.toThrowError('ES exited with code 1'); - }); -}); diff --git a/packages/kbn-es/src/integration_tests/cluster.test.ts b/packages/kbn-es/src/integration_tests/cluster.test.ts new file mode 100644 index 0000000000000..cf6bba5df669b --- /dev/null +++ b/packages/kbn-es/src/integration_tests/cluster.test.ts @@ -0,0 +1,853 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs'; +import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; +import * as extractConfig from '../utils/extract_config_files'; +import * as dockerUtils from '../utils/docker'; +import { createAnyInstanceSerializer, createStripAnsiSerializer } from '@kbn/jest-serializers'; +import * as installUtils from '../install'; +import * as waitClusterUtil from '../utils/wait_until_cluster_ready'; +import { Cluster } from '../cluster'; +import { ES_NOPASSWORD_P12_PATH } from '@kbn/dev-utils/src/certs'; +import { + DownloadSnapshotOptions, + InstallArchiveOptions, + InstallSnapshotOptions, + InstallSourceOptions, +} from '../install/types'; +import { Client } from '@elastic/elasticsearch'; + +expect.addSnapshotSerializer(createAnyInstanceSerializer(ToolingLog)); +expect.addSnapshotSerializer(createAnyInstanceSerializer(Client)); +expect.addSnapshotSerializer(createStripAnsiSerializer()); + +const log = new ToolingLog(); +const logWriter = new ToolingLogCollectingWriter(); +log.setWriters([logWriter]); +const KIBANA_ROOT = process.cwd(); +const installPath = `${KIBANA_ROOT}/.es`; +const esClusterExecOptions = {}; +const initialEnv = { ...process.env }; + +const sleep = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +const ensureResolve = async (promise: Promise, name: string) => { + return await Promise.race([ + promise, + sleep(100).then(() => { + throw new Error(`promise was supposed to resolve with ${name} resolution`); + }), + ]); +}; + +const ensureNoResolve = async (promise: Promise) => { + await Promise.race([ + sleep(100), + promise.then(() => { + throw new Error('promise was not supposed to resolve'); + }), + ]); +}; + +jest.mock('execa'); +const execaMock = jest.requireMock('execa'); + +const mockEsBin = ( + { + exitCode, + start, + ssl, + }: { + exitCode?: number; + start?: boolean; + ssl?: boolean; + } = { start: false, ssl: false } +) => { + execaMock.mockImplementationOnce((args: string[], options: {}) => + jest.requireActual('execa')( + process.execPath, + [ + '--require=@kbn/babel-register/install', + require.resolve('./__fixtures__/es_bin.js'), + JSON.stringify({ + exitCode, + start, + ssl: ssl || args.includes('xpack.security.http.ssl.enabled=true'), + }), + ], + options + ) + ); +}; + +jest.mock('../install', () => ({ + downloadSnapshot: jest.fn(), + installSource: jest.fn(), + installSnapshot: jest.fn(), + installArchive: jest.fn(), +})); + +jest.mock('../utils/extract_config_files', () => ({ + extractConfigFiles: jest.fn(), +})); + +jest.mock('../utils/docker', () => ({ + runServerlessCluster: jest.fn(), + runDockerContainer: jest.fn(), +})); + +jest.mock('../utils/wait_until_cluster_ready', () => ({ + waitUntilClusterReady: jest.fn(), +})); + +const downloadSnapshotMock = jest.spyOn(installUtils, 'downloadSnapshot'); +const installSourceMock = jest.spyOn(installUtils, 'installSource'); +const installSnapshotMock = jest.spyOn(installUtils, 'installSnapshot'); +const installArchiveMock = jest.spyOn(installUtils, 'installArchive'); +const extractConfigFilesMock = jest.spyOn(extractConfig, 'extractConfigFiles'); +const runServerlessClusterMock = jest.spyOn(dockerUtils, 'runServerlessCluster'); +const runDockerContainerMock = jest.spyOn(dockerUtils, 'runDockerContainer'); +const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady'); + +beforeEach(() => { + jest.resetAllMocks(); + extractConfigFilesMock.mockImplementation((config) => + Array.isArray(config) ? config : [config] + ); + log.indent(-log.getIndent()); + logWriter.messages.length = 0; +}); + +afterEach(() => { + process.env = { ...initialEnv }; +}); + +describe('#downloadSnapshot()', () => { + test('awaits downloadSnapshot() promise and returns { downloadPath }', async () => { + let resolveDownloadSnapshot: Function; + downloadSnapshotMock.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveDownloadSnapshot = () => { + resolve({ downloadPath: 'foo' }); + }; + }) + ); + + const cluster = new Cluster({ log }); + const promise = cluster.downloadSnapshot({ version: '8.10.0' }); + await ensureNoResolve(promise); + resolveDownloadSnapshot!(); + await expect(ensureResolve(promise, 'downloadSnapshot()')).resolves.toEqual({ + downloadPath: 'foo', + }); + }); + + test('passes through all options+log to downloadSnapshot()', async () => { + downloadSnapshotMock.mockResolvedValue({ downloadPath: 'foo' }); + const options: DownloadSnapshotOptions = { + version: '8.10.0', + license: 'trial', + basePath: 'someBasePath', + installPath: 'someInstallPath', + log, + useCached: true, + }; + const cluster = new Cluster({ log }); + await cluster.downloadSnapshot(options); + expect(downloadSnapshotMock.mock.calls[0][0]).toMatchObject(options); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info source[@kbn/es Cluster] Downloading snapshot", + ] + `); + }); + + test('rejects if downloadSnapshot() rejects', async () => { + downloadSnapshotMock.mockRejectedValue(new Error('foo')); + const cluster = new Cluster({ log }); + await expect(cluster.downloadSnapshot({ version: '8.10.0' })).rejects.toThrowError('foo'); + }); +}); + +describe('#installSource()', () => { + test('awaits installSource() promise and returns { installPath }', async () => { + let resolveInstallSource: Function; + installSourceMock.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveInstallSource = () => { + resolve({ installPath: 'foo' }); + }; + }) + ); + + const cluster = new Cluster({ log }); + const promise = cluster.installSource({ sourcePath: 'bar' }); + await ensureNoResolve(promise); + resolveInstallSource!(); + await expect(ensureResolve(promise, 'installSource()')).resolves.toEqual({ + installPath: 'foo', + }); + }); + + test('passes through all options+log to installSource()', async () => { + installSourceMock.mockResolvedValue({ installPath: 'foo' }); + const options: InstallSourceOptions = { + sourcePath: 'bar', + license: 'trial', + password: 'changeme', + basePath: 'someBasePath', + installPath: 'someInstallPath', + esArgs: ['foo=true'], + log, + }; + const cluster = new Cluster({ log }); + await cluster.installSource(options); + expect(installSourceMock.mock.calls[0][0]).toMatchObject(options); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info source[@kbn/es Cluster] Installing from source", + ] + `); + }); + + test('rejects if installSource() rejects', async () => { + installSourceMock.mockRejectedValue(new Error('foo')); + const cluster = new Cluster({ log }); + await expect(cluster.installSource({ sourcePath: 'bar' })).rejects.toThrowError('foo'); + }); +}); + +describe('#installSnapshot()', () => { + test('awaits installSnapshot() promise and returns { installPath }', async () => { + let resolveInstallSnapshot: Function; + installSnapshotMock.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveInstallSnapshot = () => { + resolve({ installPath: 'foo' }); + }; + }) + ); + + const cluster = new Cluster({ log }); + const promise = cluster.installSnapshot({ version: '8.10.0' }); + await ensureNoResolve(promise); + resolveInstallSnapshot!(); + await expect(ensureResolve(promise, 'installSnapshot()')).resolves.toEqual({ + installPath: 'foo', + }); + }); + + test('passes through all options+log to installSnapshot()', async () => { + installSnapshotMock.mockResolvedValue({ installPath: 'foo' }); + const options: InstallSnapshotOptions = { + version: '8.10.0', + license: 'trial', + password: 'changeme', + basePath: 'someBasePath', + installPath: 'someInstallPath', + esArgs: ['foo=true'], + useCached: true, + log, + }; + const cluster = new Cluster({ log }); + await cluster.installSnapshot(options); + expect(installSnapshotMock.mock.calls[0][0]).toMatchObject(options); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info source[@kbn/es Cluster] Installing from snapshot", + ] + `); + }); + + test('rejects if installSnapshot() rejects', async () => { + installSnapshotMock.mockRejectedValue(new Error('foo')); + const cluster = new Cluster({ log }); + await expect(cluster.installSnapshot({ version: '8.10.0' })).rejects.toThrowError('foo'); + }); +}); + +describe('#installArchive()', () => { + test('awaits installArchive() promise and returns { installPath }', async () => { + let resolveInstallArchive: Function; + installArchiveMock.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveInstallArchive = () => { + resolve({ installPath: 'foo' }); + }; + }) + ); + + const cluster = new Cluster({ log }); + const promise = cluster.installArchive('bar'); + await ensureNoResolve(promise); + resolveInstallArchive!(); + await expect(ensureResolve(promise, 'installArchive()')).resolves.toEqual({ + installPath: 'foo', + }); + }); + + test('passes through all options+log to installArchive()', async () => { + installArchiveMock.mockResolvedValue({ installPath: 'foo' }); + const options: InstallArchiveOptions = { + license: 'trial', + password: 'changeme', + basePath: 'someBasePath', + installPath: 'someInstallPath', + esArgs: ['foo=true'], + log, + }; + const cluster = new Cluster({ log }); + await cluster.installArchive('bar', options); + expect(installArchiveMock.mock.calls[0]).toMatchObject(['bar', options]); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info source[@kbn/es Cluster] Installing from an archive", + ] + `); + }); + + test('rejects if installArchive() rejects', async () => { + installArchiveMock.mockRejectedValue(new Error('foo')); + const cluster = new Cluster({ log }); + await expect(cluster.installArchive('bar')).rejects.toThrowError('foo'); + }); +}); + +describe('#start(installPath)', () => { + test('rejects when bin/elasticsearch exists with 0 before starting', async () => { + mockEsBin({ exitCode: 0, start: false }); + + await expect( + new Cluster({ log }).start(installPath, esClusterExecOptions) + ).rejects.toThrowError('ES exited without starting'); + }); + + test('rejects when bin/elasticsearch exists with 143 before starting', async () => { + mockEsBin({ exitCode: 143, start: false }); + + await expect( + new Cluster({ log }).start(installPath, esClusterExecOptions) + ).rejects.toThrowError('ES exited without starting'); + }); + + test('rejects when bin/elasticsearch exists with 130 before starting', async () => { + mockEsBin({ exitCode: 130, start: false }); + + await expect( + new Cluster({ log }).start(installPath, esClusterExecOptions) + ).rejects.toThrowError('ES exited without starting'); + }); + + test('rejects when bin/elasticsearch exists with 1 before starting', async () => { + mockEsBin({ exitCode: 1, start: false }); + + await expect( + new Cluster({ log }).start(installPath, esClusterExecOptions) + ).rejects.toThrowError('ES exited with code 1'); + }); + + test('resolves when bin/elasticsearch logs "started"', async () => { + mockEsBin({ start: true }); + + await new Cluster({ log }).start(installPath, esClusterExecOptions); + }); + + test(`writes logs to file when 'writeLogsToPath' is passed`, async () => { + mockEsBin({ start: true }); + const writeLogsToPath = `${KIBANA_ROOT}/es-cluster.log`; + + await new Cluster({ log }).start(installPath, { writeLogsToPath }); + + expect(logWriter.messages[0]).toContain(`and writing logs to ${writeLogsToPath}`); + expect(fs.existsSync(writeLogsToPath)).toBe(true); + }); + + test('calls waitUntilClusterReady() by default', async () => { + mockEsBin({ start: true }); + waitUntilClusterReadyMock.mockResolvedValue(); + + await new Cluster({ log }).start(installPath, esClusterExecOptions); + expect(waitUntilClusterReadyMock).toHaveBeenCalledTimes(1); + expect(waitUntilClusterReadyMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "client": , + "expectedStatus": "yellow", + "log": , + "readyTimeout": undefined, + }, + ] + `); + }); + + test(`doesn't call waitUntilClusterReady() if 'skipReadyCheck' is passed`, async () => { + mockEsBin({ start: true }); + waitUntilClusterReadyMock.mockResolvedValue(); + + await new Cluster({ log }).start(installPath, { skipReadyCheck: true }); + expect(waitUntilClusterReadyMock).toHaveBeenCalledTimes(0); + }); + + test(`rejects if waitUntilClusterReady() rejects`, async () => { + mockEsBin({ start: true }); + waitUntilClusterReadyMock.mockRejectedValue(new Error('foo')); + await expect( + new Cluster({ log }).start(installPath, esClusterExecOptions) + ).rejects.toThrowError('foo'); + }); + + test('rejects if #start() was called previously', async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await expect(cluster.start(installPath, esClusterExecOptions)).rejects.toThrowError( + 'ES has already been started' + ); + }); + + test('rejects if #run() was called previously', async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.run(installPath, esClusterExecOptions); + await expect(cluster.start(installPath, esClusterExecOptions)).rejects.toThrowError( + 'ES has already been started' + ); + }); + + test('sets up SSL when enabled', async () => { + mockEsBin({ start: true, ssl: true }); + + const cluster = new Cluster({ log, ssl: true }); + await cluster.start(installPath, esClusterExecOptions); + + const config = extractConfigFilesMock.mock.calls[0][0]; + expect(config).toContain('xpack.security.http.ssl.enabled=true'); + expect(config).toContain(`xpack.security.http.ssl.keystore.path=${ES_NOPASSWORD_P12_PATH}`); + expect(config).toContain(`xpack.security.http.ssl.keystore.type=PKCS12`); + }); + + test(`doesn't setup SSL when disabled`, async () => { + mockEsBin({ start: true }); + extractConfigFilesMock.mockReturnValueOnce([]); + + const cluster = new Cluster({ log, ssl: false }); + await cluster.start(installPath, esClusterExecOptions); + + expect(extractConfigFilesMock.mock.calls[0][0]).toMatchObject([ + 'action.destructive_requires_name=true', + 'cluster.routing.allocation.disk.threshold_enabled=false', + 'ingest.geoip.downloader.enabled=false', + 'search.check_ccs_compatibility=true', + ]); + }); + + test('allows overriding search.check_ccs_compatibility', async () => { + mockEsBin({ start: true }); + extractConfigFilesMock.mockReturnValueOnce([]); + + const cluster = new Cluster({ + log, + ssl: false, + }); + await cluster.start('undefined', { + esArgs: ['search.check_ccs_compatibility=false'], + }); + + expect(extractConfigFilesMock.mock.calls[0][0]).toMatchObject([ + 'action.destructive_requires_name=true', + 'cluster.routing.allocation.disk.threshold_enabled=false', + 'ingest.geoip.downloader.enabled=false', + 'search.check_ccs_compatibility=false', + ]); + }); +}); + +describe('#run()', () => { + test('resolves when bin/elasticsearch exists with 0', async () => { + mockEsBin({ exitCode: 0 }); + + await new Cluster({ log }).run(installPath, esClusterExecOptions); + }); + + test('resolves when bin/elasticsearch exists with 143', async () => { + mockEsBin({ exitCode: 143 }); + + await new Cluster({ log }).run(installPath, esClusterExecOptions); + }); + + test('resolves when bin/elasticsearch exists with 130', async () => { + mockEsBin({ exitCode: 130 }); + + await new Cluster({ log }).run(installPath, esClusterExecOptions); + }); + + test('rejects when bin/elasticsearch exists with 1', async () => { + mockEsBin({ exitCode: 1 }); + + await expect(new Cluster({ log }).run(installPath, esClusterExecOptions)).rejects.toThrowError( + 'ES exited with code 1' + ); + }); + + test('rejects if #start() was called previously', async () => { + mockEsBin({ exitCode: 0, start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await expect(cluster.run(installPath, esClusterExecOptions)).rejects.toThrowError( + 'ES has already been started' + ); + }); + + test('rejects if #run() was called previously', async () => { + mockEsBin({ exitCode: 0 }); + + const cluster = new Cluster({ log }); + await cluster.run(installPath, esClusterExecOptions); + await expect(cluster.run(installPath, esClusterExecOptions)).rejects.toThrowError( + 'ES has already been started' + ); + }); + + test('sets up SSL when enabled', async () => { + mockEsBin({ start: true, ssl: true }); + + const cluster = new Cluster({ log, ssl: true }); + await cluster.run(installPath, esClusterExecOptions); + + const config = extractConfigFilesMock.mock.calls[0][0]; + expect(config).toContain('xpack.security.http.ssl.enabled=true'); + expect(config).toContain(`xpack.security.http.ssl.keystore.path=${ES_NOPASSWORD_P12_PATH}`); + expect(config).toContain(`xpack.security.http.ssl.keystore.type=PKCS12`); + }); + + test(`doesn't setup SSL when disabled`, async () => { + mockEsBin({ start: true }); + extractConfigFilesMock.mockReturnValueOnce([]); + + const cluster = new Cluster({ log, ssl: false }); + await cluster.run(installPath, esClusterExecOptions); + + expect(extractConfigFilesMock.mock.calls[0][0]).toMatchObject([ + 'action.destructive_requires_name=true', + 'cluster.routing.allocation.disk.threshold_enabled=false', + 'ingest.geoip.downloader.enabled=false', + 'search.check_ccs_compatibility=true', + ]); + }); + + test('sets default Java heap', async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.run(installPath, esClusterExecOptions); + + expect(execaMock.mock.calls[0][2].env.ES_JAVA_OPTS).toMatchInlineSnapshot( + `"-Xms1536m -Xmx1536m"` + ); + }); + + test('allows Java heap to be overwritten', async () => { + mockEsBin({ start: true }); + process.env.ES_JAVA_OPTS = '-Xms5g -Xmx5g'; + + const cluster = new Cluster({ log }); + await cluster.run(installPath, esClusterExecOptions); + + expect(execaMock.mock.calls[0][2].env.ES_JAVA_OPTS).toMatchInlineSnapshot(`"-Xms5g -Xmx5g"`); + }); +}); + +describe('#installPlugins()', () => { + test('passes through installPath and runs execa for each plugin', async () => { + const cluster = new Cluster({ log }); + await cluster.installPlugins('foo', 'esPlugin1,esPlugin2', ''); + expect(execaMock.mock.calls.length).toBe(2); + expect(execaMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "bin/elasticsearch-plugin", + Array [ + "install", + "esPlugin1", + ], + Object { + "cwd": "foo", + "env": Object { + "ES_JAVA_OPTS": "-Xms1536m -Xmx1536m", + "JAVA_HOME": "", + }, + }, + ] + `); + + expect(execaMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + "bin/elasticsearch-plugin", + Array [ + "install", + "esPlugin2", + ], + Object { + "cwd": "foo", + "env": Object { + "ES_JAVA_OPTS": "-Xms1536m -Xmx1536m", + "JAVA_HOME": "", + }, + }, + ] + `); + }); + + test(`allows 'esJavaOpts' to be overwritten`, async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.installPlugins('foo', 'esPlugin1', '-Xms2g -Xmx2g'); + + expect(execaMock.mock.calls[0][2].env.ES_JAVA_OPTS).toMatchInlineSnapshot(`"-Xms2g -Xmx2g"`); + }); +}); + +describe('#configureKeystoreWithSecureSettingsFiles()', () => { + test('passes through installPath and runs execa for each pair of settings', async () => { + const cluster = new Cluster({ log }); + await cluster.configureKeystoreWithSecureSettingsFiles('foo', [ + ['name1', 'file1'], + ['name2', 'file2'], + ]); + expect(execaMock.mock.calls.length).toBe(2); + expect(execaMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "./bin/elasticsearch-keystore", + Array [ + "add-file", + "name1", + "file1", + ], + Object { + "cwd": "foo", + "env": Object { + "JAVA_HOME": "", + }, + }, + ] + `); + + expect(execaMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + "./bin/elasticsearch-keystore", + Array [ + "add-file", + "name2", + "file2", + ], + Object { + "cwd": "foo", + "env": Object { + "JAVA_HOME": "", + }, + }, + ] + `); + }); +}); + +describe('#stop()', () => { + test('rejects if #run() or #start() was not called', async () => { + const cluster = new Cluster({ log }); + await expect(cluster.stop()).rejects.toThrowError('ES has not been started'); + }); + + test('resolves when ES exits with 0', async () => { + mockEsBin({ exitCode: 0, start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await cluster.stop(); + }); + + test('resolves when ES exits with 143', async () => { + mockEsBin({ exitCode: 143, start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await cluster.stop(); + }); + + test('resolves when ES exits with 130', async () => { + mockEsBin({ exitCode: 130, start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await cluster.stop(); + }); + + test('rejects when ES exits with 1', async () => { + mockEsBin({ exitCode: 1, start: true }); + + const cluster = new Cluster({ log }); + await expect(cluster.run(installPath, esClusterExecOptions)).rejects.toThrowError( + 'ES exited with code 1' + ); + await expect(cluster.stop()).rejects.toThrowError('ES exited with code 1'); + }); +}); + +describe('#kill()', () => { + test('rejects if #run() or #start() was not called', async () => { + const cluster = new Cluster({ log }); + await expect(cluster.kill()).rejects.toThrowError('ES has not been started'); + }); + + test('resolves when ES exits with 0', async () => { + mockEsBin({ exitCode: 0, start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await cluster.kill(); + }); +}); + +describe('#runServerless()', () => { + test(`rejects if #start() was called before`, async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await expect(cluster.runServerless({ basePath: installPath })).rejects.toThrowError( + 'ES stateful cluster has already been started' + ); + }); + + test(`rejects if #run() was called before`, async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.run(installPath, esClusterExecOptions); + await expect(cluster.runServerless({ basePath: installPath })).rejects.toThrowError( + 'ES stateful cluster has already been started' + ); + }); + + test('awaits runServerlessCluster() promise and returns node names as string[]', async () => { + const nodeNames = ['es1', 'es2', 'es3']; + let resolveRunServerlessCluster: Function; + runServerlessClusterMock.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveRunServerlessCluster = () => { + resolve(nodeNames); + }; + }) + ); + + const cluster = new Cluster({ log }); + const promise = cluster.runServerless({ basePath: installPath }); + await ensureNoResolve(promise); + resolveRunServerlessCluster!(); + await expect(ensureResolve(promise, 'runServerless()')).resolves.toEqual(nodeNames); + }); + + test('rejects if #runServerless() was called before', async () => { + const nodeNames = ['es1', 'es2', 'es3']; + runServerlessClusterMock.mockResolvedValueOnce(nodeNames); + + const cluster = new Cluster({ log }); + await cluster.runServerless({ basePath: installPath }); + await expect(cluster.runServerless({ basePath: installPath })).rejects.toThrowError( + 'ES serverless docker cluster has already been started' + ); + }); + + test('rejects if #runServerlessCluster() rejects', async () => { + runServerlessClusterMock.mockRejectedValueOnce(new Error('foo')); + const cluster = new Cluster({ log }); + await expect(cluster.runServerless({ basePath: installPath })).rejects.toThrowError('foo'); + }); + + test('passes through all options+log to #runServerlessCluster()', async () => { + const nodeNames = ['es1', 'es2', 'es3']; + runServerlessClusterMock.mockResolvedValueOnce(nodeNames); + + const cluster = new Cluster({ log }); + const serverlessOptions = { + clean: true, + basePath: installPath, + teardown: true, + background: true, + waitForReady: true, + }; + await cluster.runServerless(serverlessOptions); + expect(runServerlessClusterMock.mock.calls[0][0]).toMatchInlineSnapshot(``); + expect(runServerlessClusterMock.mock.calls[0][1]).toBe(serverlessOptions); + }); +}); + +describe('#runDocker()', () => { + const dockerOptions = {}; + test(`rejects if #start() was called before`, async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.start(installPath, esClusterExecOptions); + await expect(cluster.runDocker(dockerOptions)).rejects.toThrowError( + 'ES stateful cluster has already been started' + ); + }); + + test('rejects if #run() was called before', async () => { + mockEsBin({ start: true }); + const cluster = new Cluster({ log }); + await cluster.run(installPath, esClusterExecOptions); + await expect(cluster.runDocker(dockerOptions)).rejects.toThrowError( + 'ES stateful cluster has already been started' + ); + }); + + test('await #runDockerContainer() promise', async () => { + let resolveRunDockerContainer: Function; + runDockerContainerMock.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveRunDockerContainer = () => { + resolve(); + }; + }) + ); + const cluster = new Cluster({ log }); + const promise = cluster.runDocker(dockerOptions); + await ensureNoResolve(promise); + resolveRunDockerContainer!(); + await expect(ensureResolve(promise, 'runDocker()')).resolves.toBeUndefined(); + }); + + test('rejects if #runDockerContainer() rejects', async () => { + runDockerContainerMock.mockRejectedValueOnce(new Error('foo')); + const cluster = new Cluster({ log }); + await expect(cluster.runDocker(dockerOptions)).rejects.toThrowError('foo'); + }); + + test('passes through all options+log to #runDockerContainer()', async () => { + const options = { dockerCmd: 'start -a es01' }; + runDockerContainerMock.mockResolvedValueOnce(); + + const cluster = new Cluster({ log }); + await cluster.runDocker(options); + expect(runDockerContainerMock.mock.calls[0][0]).toMatchInlineSnapshot(``); + expect(runDockerContainerMock.mock.calls[0][1]).toBe(options); + }); +}); diff --git a/packages/kbn-es/src/paths.ts b/packages/kbn-es/src/paths.ts index 76cf4271c7ce8..d9b4be41aa15b 100644 --- a/packages/kbn-es/src/paths.ts +++ b/packages/kbn-es/src/paths.ts @@ -24,29 +24,43 @@ export const ES_CONFIG = 'config/elasticsearch.yml'; export const ES_KEYSTORE_BIN = maybeUseBat('./bin/elasticsearch-keystore'); -export const ESS_OPERATOR_USERS_PATH = resolve(__dirname, './ess_resources/operator_users.yml'); -export const ESS_SERVICE_TOKENS_PATH = resolve(__dirname, './ess_resources/service_tokens'); +export const SERVERLESS_OPERATOR_USERS_PATH = resolve( + __dirname, + './serverless_resources/operator_users.yml' +); +export const SERVERLESS_SERVICE_TOKENS_PATH = resolve( + __dirname, + './serverless_resources/service_tokens' +); -export const ESS_USERS_PATH = resolve(__dirname, './ess_resources/users'); -export const ESS_USERS_ROLES_PATH = resolve(__dirname, './ess_resources/users_roles'); +export const SERVERLESS_USERS_PATH = resolve(__dirname, './serverless_resources/users'); +export const SERVERLESS_USERS_ROLES_PATH = resolve(__dirname, './serverless_resources/users_roles'); -export const ESS_ROLES_PATH = resolve(__dirname, './ess_resources/roles.yml'); -export const ESS_ROLE_MAPPING_PATH = resolve(__dirname, './ess_resources/role_mapping.yml'); +export const SERVERLESS_ROLES_PATH = resolve(__dirname, './serverless_resources/roles.yml'); +export const SERVERLESS_ROLE_MAPPING_PATH = resolve( + __dirname, + './serverless_resources/role_mapping.yml' +); -export const ESS_SECRETS_PATH = resolve(__dirname, './ess_resources/secrets.json'); +export const SERVERLESS_SECRETS_PATH = resolve(__dirname, './serverless_resources/secrets.json'); -export const ESS_JWKS_PATH = resolve(__dirname, './ess_resources/jwks.json'); +export const SERVERLESS_SECRETS_SSL_PATH = resolve( + __dirname, + './serverless_resources/secrets_ssl.json' +); -export const ESS_RESOURCES_PATHS = [ - ESS_OPERATOR_USERS_PATH, - ESS_ROLE_MAPPING_PATH, - ESS_ROLES_PATH, - ESS_SERVICE_TOKENS_PATH, - ESS_USERS_PATH, - ESS_USERS_ROLES_PATH, +export const SERVERLESS_JWKS_PATH = resolve(__dirname, './serverless_resources/jwks.json'); + +export const SERVERLESS_RESOURCES_PATHS = [ + SERVERLESS_OPERATOR_USERS_PATH, + SERVERLESS_ROLE_MAPPING_PATH, + SERVERLESS_ROLES_PATH, + SERVERLESS_SERVICE_TOKENS_PATH, + SERVERLESS_USERS_PATH, + SERVERLESS_USERS_ROLES_PATH, ]; -export const ESS_CONFIG_PATH = '/usr/share/elasticsearch/config/'; +export const SERVERLESS_CONFIG_PATH = '/usr/share/elasticsearch/config/'; // Files need to be inside config for permissions reasons inside the container -export const ESS_FILES_PATH = `${ESS_CONFIG_PATH}files/`; +export const SERVERLESS_FILES_PATH = `${SERVERLESS_CONFIG_PATH}files/`; diff --git a/packages/kbn-es/src/serverless_resources/README.md b/packages/kbn-es/src/serverless_resources/README.md new file mode 100644 index 0000000000000..d1ae204117075 --- /dev/null +++ b/packages/kbn-es/src/serverless_resources/README.md @@ -0,0 +1,49 @@ +# Elasticsearch Serverless Resources +The resources in this directory are used for seeding Elasticsearch Serverless images with users, roles and tokens for SSL and authentication. Serverless requires file realm authentication, so we will bind mount them into the containers at `/usr/share/elasticsearch/config/`. + +## Users + +### Default user + +The default superuser authentication to login to Kibana is: + +``` +username: elastic_serverless +password: changeme +``` + +### Adding users + +1. Add the user:encrypted_password to `users` file. The encrypted password for `elastic_serverless` is `changeme` if you want to reuse the value. +1. Set the new user's roles in `users_roles` file. +1. Add the username to `operator_users.yml` in the array for file realm users. + + +## Service Account and Tokens + +This section for Service Accounts was originally from the [ES Serverless repository](https://github.com/elastic/elasticsearch-serverless/blob/main/serverless-build-tools/src/main/resources/README.service_tokens.md). + +The "service_tokens" file contains this line: +``` +elastic/kibana/kibana-dev:$2a$10$mY2RuGROhk56vLNh.Mgwue98BnkdQPlTR.yGh38ao5jhPJobvuBCq +``` + +That line defines a single service token +- For the `elastic/kibana` service account +- The token is named `kibana-dev` +- The token's secret is hashed using bcrypt (`$2a$`) using `10` rounds + +Although Elasticsearch used PBKDF2_STRETCH by default, the k8s controller +creates tokens using bcrypt, so we mimic that here. + +The hash is not reversible, so this README is here to tell you what the secret is. +The secret value is: `UUUUUULK-* Z4` +That produces an encoded token of: `AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS1kZXY6VVVVVVVVTEstKiBaNA` +Yes, the secret was specially chosen to produce an encoded value that can be more easily recognised in development. + +If a node is configured to use this `service_tokens` file, then you can authenticate to it with +``` +curl -H "Authorization: Bearer AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS1kZXY6VVVVVVVVTEstKiBaNA" http://localhost:9200/_security/_authenticate +``` + +The name of the token (`kibana-dev`) is important because the `operator_users.yml` file designates that token as an operator and allows us to seed a serverless cluster with this token. \ No newline at end of file diff --git a/packages/kbn-es/src/ess_resources/jwks.json b/packages/kbn-es/src/serverless_resources/jwks.json similarity index 100% rename from packages/kbn-es/src/ess_resources/jwks.json rename to packages/kbn-es/src/serverless_resources/jwks.json diff --git a/packages/kbn-es/src/ess_resources/operator_users.yml b/packages/kbn-es/src/serverless_resources/operator_users.yml similarity index 100% rename from packages/kbn-es/src/ess_resources/operator_users.yml rename to packages/kbn-es/src/serverless_resources/operator_users.yml diff --git a/packages/kbn-es/src/ess_resources/role_mapping.yml b/packages/kbn-es/src/serverless_resources/role_mapping.yml similarity index 100% rename from packages/kbn-es/src/ess_resources/role_mapping.yml rename to packages/kbn-es/src/serverless_resources/role_mapping.yml diff --git a/packages/kbn-es/src/ess_resources/roles.yml b/packages/kbn-es/src/serverless_resources/roles.yml similarity index 100% rename from packages/kbn-es/src/ess_resources/roles.yml rename to packages/kbn-es/src/serverless_resources/roles.yml diff --git a/packages/kbn-es/src/serverless_resources/secrets.json b/packages/kbn-es/src/serverless_resources/secrets.json new file mode 100644 index 0000000000000..a70e5cf711c19 --- /dev/null +++ b/packages/kbn-es/src/serverless_resources/secrets.json @@ -0,0 +1,10 @@ +{ + "metadata": { + "version": "1", + "compatibility": "8.11.0" + }, + "string_secrets": { + "xpack.security.transport.ssl.keystore.secure_password": "storepass", + "xpack.security.authc.realms.jwt.jwt1.client_authentication.shared_secret": "my_super_secret" + } +} \ No newline at end of file diff --git a/packages/kbn-es/src/ess_resources/secrets.json b/packages/kbn-es/src/serverless_resources/secrets_ssl.json similarity index 100% rename from packages/kbn-es/src/ess_resources/secrets.json rename to packages/kbn-es/src/serverless_resources/secrets_ssl.json diff --git a/packages/kbn-es/src/ess_resources/service_tokens b/packages/kbn-es/src/serverless_resources/service_tokens similarity index 100% rename from packages/kbn-es/src/ess_resources/service_tokens rename to packages/kbn-es/src/serverless_resources/service_tokens diff --git a/packages/kbn-es/src/ess_resources/users b/packages/kbn-es/src/serverless_resources/users similarity index 100% rename from packages/kbn-es/src/ess_resources/users rename to packages/kbn-es/src/serverless_resources/users diff --git a/packages/kbn-es/src/ess_resources/users_roles b/packages/kbn-es/src/serverless_resources/users_roles similarity index 100% rename from packages/kbn-es/src/ess_resources/users_roles rename to packages/kbn-es/src/serverless_resources/users_roles diff --git a/packages/kbn-es/src/utils/docker.test.ts b/packages/kbn-es/src/utils/docker.test.ts index c42ac1af577f0..d48cddd6fdb6d 100644 --- a/packages/kbn-es/src/utils/docker.test.ts +++ b/packages/kbn-es/src/utils/docker.test.ts @@ -9,6 +9,7 @@ import mockFs from 'mock-fs'; import { existsSync } from 'fs'; import { stat } from 'fs/promises'; +import { basename } from 'path'; import { DOCKER_IMG, @@ -27,13 +28,29 @@ import { stopServerlessCluster, teardownServerlessClusterSync, verifyDockerInstalled, + getESp12Volume, } from './docker'; import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; import { ES_P12_PATH } from '@kbn/dev-utils'; -import { ESS_RESOURCES_PATHS } from '../paths'; +import { + SERVERLESS_CONFIG_PATH, + SERVERLESS_RESOURCES_PATHS, + SERVERLESS_SECRETS_PATH, + SERVERLESS_JWKS_PATH, +} from '../paths'; +import * as waitClusterUtil from './wait_until_cluster_ready'; jest.mock('execa'); const execa = jest.requireMock('execa'); +jest.mock('@elastic/elasticsearch', () => { + return { + Client: jest.fn(), + }; +}); + +jest.mock('./wait_until_cluster_ready', () => ({ + waitUntilClusterReady: jest.fn(), +})); const log = new ToolingLog(); const logWriter = new ToolingLogCollectingWriter(); @@ -44,6 +61,8 @@ const baseEsPath = `${KIBANA_ROOT}/.es`; const serverlessDir = 'stateless'; const serverlessObjectStorePath = `${baseEsPath}/${serverlessDir}`; +const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady'); + beforeEach(() => { jest.resetAllMocks(); log.indent(-log.getIndent()); @@ -63,9 +82,23 @@ afterEach(() => { jest.clearAllMocks(); }); +const serverlessResources = SERVERLESS_RESOURCES_PATHS.reduce((acc, path) => { + acc.push(`${path}:${SERVERLESS_CONFIG_PATH}${basename(path)}`); + + return acc; +}, []); + const volumeCmdTest = async (volumeCmd: string[]) => { - expect(volumeCmd).toHaveLength(2); - expect(volumeCmd).toEqual(expect.arrayContaining(['--volume', `${baseEsPath}:/objectstore:z`])); + expect(volumeCmd).toHaveLength(20); + expect(volumeCmd).toEqual( + expect.arrayContaining([ + ...getESp12Volume(), + ...serverlessResources, + `${baseEsPath}:/objectstore:z`, + `${SERVERLESS_SECRETS_PATH}:${SERVERLESS_CONFIG_PATH}secrets/secrets.json:z`, + `${SERVERLESS_JWKS_PATH}:${SERVERLESS_CONFIG_PATH}secrets/jwks.json:z`, + ]) + ); // extract only permission from mode // eslint-disable-next-line no-bitwise @@ -336,13 +369,10 @@ describe('resolveEsArgs()', () => { `); }); - test('should add SSL args and enable security when SSL is passed', () => { - const esArgs = resolveEsArgs([...defaultEsArgs, ['xpack.security.enabled', 'false']], { - ssl: true, - }); + test('should add SSL args when SSL is passed', () => { + const esArgs = resolveEsArgs(defaultEsArgs, { ssl: true }); - expect(esArgs).toHaveLength(20); - expect(esArgs).not.toEqual(expect.arrayContaining(['xpack.security.enabled=false'])); + expect(esArgs).toHaveLength(10); expect(esArgs).toMatchInlineSnapshot(` Array [ "--env", @@ -350,21 +380,11 @@ describe('resolveEsArgs()', () => { "--env", "qux=zip", "--env", - "xpack.security.enabled=true", - "--env", "xpack.security.http.ssl.enabled=true", "--env", "xpack.security.http.ssl.keystore.path=/usr/share/elasticsearch/config/certs/elasticsearch.p12", "--env", "xpack.security.http.ssl.verification_mode=certificate", - "--env", - "xpack.security.transport.ssl.enabled=true", - "--env", - "xpack.security.transport.ssl.keystore.path=/usr/share/elasticsearch/config/certs/elasticsearch.p12", - "--env", - "xpack.security.transport.ssl.verification_mode=certificate", - "--env", - "xpack.security.operator_privileges.enabled=true", ] `); }); @@ -413,7 +433,11 @@ describe('setupServerlessVolumes()', () => { const volumeCmd = await setupServerlessVolumes(log, { basePath: baseEsPath, ssl: true }); - const requiredPaths = [`${baseEsPath}:/objectstore:z`, ES_P12_PATH, ...ESS_RESOURCES_PATHS]; + const requiredPaths = [ + `${baseEsPath}:/objectstore:z`, + ES_P12_PATH, + ...SERVERLESS_RESOURCES_PATHS, + ]; const pathsNotIncludedInCmd = requiredPaths.filter( (path) => !volumeCmd.some((cmd) => cmd.includes(path)) ); @@ -465,6 +489,19 @@ describe('runServerlessCluster()', () => { // setupDocker execa calls then run three nodes and attach logger expect(execa.mock.calls).toHaveLength(8); }); + + test(`should wait for serverless nodes to return 'green' status`, async () => { + waitUntilClusterReadyMock.mockResolvedValue(); + mockFs({ + [baseEsPath]: {}, + }); + execa.mockImplementation(() => Promise.resolve({ stdout: '' })); + + await runServerlessCluster(log, { basePath: baseEsPath, waitForReady: true }); + expect(waitUntilClusterReadyMock).toHaveBeenCalledTimes(1); + expect(waitUntilClusterReadyMock.mock.calls[0][0].expectedStatus).toEqual('green'); + expect(waitUntilClusterReadyMock.mock.calls[0][0].readyTimeout).toEqual(undefined); + }); }); describe('stopServerlessCluster()', () => { @@ -532,8 +569,7 @@ describe('resolveDockerCmd()', () => { describe('runDockerContainer()', () => { test('should resolve', async () => { execa.mockImplementation(() => Promise.resolve({ stdout: '' })); - - await expect(runDockerContainer(log, {})).resolves.toEqual({ stdout: '' }); + await expect(runDockerContainer(log, {})).resolves.toBeUndefined(); // setupDocker execa calls then run container expect(execa.mock.calls).toHaveLength(5); }); diff --git a/packages/kbn-es/src/utils/docker.ts b/packages/kbn-es/src/utils/docker.ts index 381d3c13769d8..00a1d7ce9dc54 100644 --- a/packages/kbn-es/src/utils/docker.ts +++ b/packages/kbn-es/src/utils/docker.ts @@ -10,26 +10,40 @@ import execa from 'execa'; import fs from 'fs'; import Fsp from 'fs/promises'; import { resolve, basename, join } from 'path'; +import { Client, ClientOptions, HttpConnection } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; import { kibanaPackageJson as pkg, REPO_ROOT } from '@kbn/repo-info'; -import { ES_P12_PASSWORD, ES_P12_PATH } from '@kbn/dev-utils'; +import { + CA_CERT_PATH, + ES_P12_PASSWORD, + ES_P12_PATH, + kibanaDevServiceAccount, +} from '@kbn/dev-utils'; import { createCliError } from '../errors'; import { EsClusterExecOptions } from '../cluster_exec_options'; import { - ESS_RESOURCES_PATHS, - ESS_SECRETS_PATH, - ESS_JWKS_PATH, - ESS_CONFIG_PATH, - ESS_FILES_PATH, + SERVERLESS_RESOURCES_PATHS, + SERVERLESS_SECRETS_PATH, + SERVERLESS_JWKS_PATH, + SERVERLESS_CONFIG_PATH, + SERVERLESS_FILES_PATH, + SERVERLESS_SECRETS_SSL_PATH, } from '../paths'; +import { + ELASTIC_SERVERLESS_SUPERUSER, + ELASTIC_SERVERLESS_SUPERUSER_PASSWORD, +} from './serverless_file_realm'; +import { SYSTEM_INDICES_SUPERUSER } from './native_realm'; +import { waitUntilClusterReady } from './wait_until_cluster_ready'; interface BaseOptions { tag?: string; image?: string; port?: number; ssl?: boolean; + /** Kill running cluster before starting a new cluster */ kill?: boolean; files?: string | string[]; } @@ -39,10 +53,16 @@ export interface DockerOptions extends EsClusterExecOptions, BaseOptions { } export interface ServerlessOptions extends EsClusterExecOptions, BaseOptions { + /** Clean (or delete) all data created by the ES cluster after it is stopped */ clean?: boolean; + /** Path to the directory where the ES cluster will store data */ basePath: string; - teardown?: boolean; + /** If this process exits, leave the ES cluster running in the background */ + skipTeardown?: boolean; + /** Start the ES cluster in the background instead of remaining attached: useful for running tests */ background?: boolean; + /** Wait for the ES cluster to be ready to serve requests */ + waitForReady?: boolean; } interface ServerlessEsNodeArgs { @@ -134,44 +154,58 @@ const DEFAULT_SERVERLESS_ESARGS: Array<[string, string]> = [ ['xpack.ml.enabled', 'true'], - ['xpack.security.enabled', 'false'], -]; - -const DEFAULT_SSL_ESARGS: Array<[string, string]> = [ ['xpack.security.enabled', 'true'], - ['xpack.security.http.ssl.enabled', 'true'], + // JWT realm settings are to closer emulate a real ES serverless env + ['xpack.security.authc.realms.jwt.jwt1.allowed_audiences', 'elasticsearch'], - ['xpack.security.http.ssl.keystore.path', `${ESS_CONFIG_PATH}certs/elasticsearch.p12`], + ['xpack.security.authc.realms.jwt.jwt1.allowed_issuer', 'https://kibana.elastic.co/jwt/'], - ['xpack.security.http.ssl.verification_mode', 'certificate'], + ['xpack.security.authc.realms.jwt.jwt1.claims.principal', 'sub'], - ['xpack.security.transport.ssl.enabled', 'true'], + ['xpack.security.authc.realms.jwt.jwt1.client_authentication.type', 'shared_secret'], - ['xpack.security.transport.ssl.keystore.path', `${ESS_CONFIG_PATH}certs/elasticsearch.p12`], + ['xpack.security.authc.realms.jwt.jwt1.order', '-98'], - ['xpack.security.transport.ssl.verification_mode', 'certificate'], + [ + 'xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path', + `${SERVERLESS_CONFIG_PATH}secrets/jwks.json`, + ], ['xpack.security.operator_privileges.enabled', 'true'], -]; -const SERVERLESS_SSL_ESARGS: Array<[string, string]> = [ - ['xpack.security.authc.realms.jwt.jwt1.client_authentication.type', 'shared_secret'], + ['xpack.security.transport.ssl.enabled', 'true'], - ['xpack.security.authc.realms.jwt.jwt1.order', '-98'], + [ + 'xpack.security.transport.ssl.keystore.path', + `${SERVERLESS_CONFIG_PATH}certs/elasticsearch.p12`, + ], - ['xpack.security.authc.realms.jwt.jwt1.allowed_issuer', 'https://kibana.elastic.co/jwt/'], + ['xpack.security.transport.ssl.verification_mode', 'certificate'], +]; - ['xpack.security.authc.realms.jwt.jwt1.allowed_audiences', 'elasticsearch'], +const DEFAULT_SSL_ESARGS: Array<[string, string]> = [ + ['xpack.security.http.ssl.enabled', 'true'], - ['xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path', `${ESS_CONFIG_PATH}secrets/jwks.json`], + ['xpack.security.http.ssl.keystore.path', `${SERVERLESS_CONFIG_PATH}certs/elasticsearch.p12`], - ['xpack.security.authc.realms.jwt.jwt1.claims.principal', 'sub'], + ['xpack.security.http.ssl.verification_mode', 'certificate'], ]; const DOCKER_SSL_ESARGS: Array<[string, string]> = [ + ['xpack.security.enabled', 'true'], + ['xpack.security.http.ssl.keystore.password', ES_P12_PASSWORD], + ['xpack.security.transport.ssl.enabled', 'true'], + + [ + 'xpack.security.transport.ssl.keystore.path', + `${SERVERLESS_CONFIG_PATH}certs/elasticsearch.p12`, + ], + + ['xpack.security.transport.ssl.verification_mode', 'certificate'], + ['xpack.security.transport.ssl.keystore.password', ES_P12_PASSWORD], ]; @@ -320,10 +354,15 @@ export async function maybePullDockerImage(log: ToolingLog, image: string) { log.info(chalk.bold(`Checking for image: ${image}`)); await execa('docker', ['pull', image], { - // inherit is required to show Docker output - stdio: ['ignore', 'inherit', 'inherit'], + // inherit is required to show Docker pull output + stdio: ['ignore', 'inherit', 'pipe'], }).catch(({ message }) => { - throw createCliError(message); + throw createCliError( + `Error pulling image. This is likely an issue authenticating with ${DOCKER_REGISTRY}. +Visit ${chalk.bold.cyan('https://docker-auth.elastic.co/github_auth')} to login. + +${message}` + ); }); } @@ -393,6 +432,7 @@ export function resolveEsArgs( args.forEach((arg) => { const [key, ...value] = arg.split('='); + esArgs.set(key.trim(), value.join('=').trim()); }); } @@ -404,17 +444,17 @@ export function resolveEsArgs( return Array.from(esArgs).flatMap((e) => ['--env', e.join('=')]); } -function getESp12Volume() { - return ['--volume', `${ES_P12_PATH}:${ESS_CONFIG_PATH}certs/elasticsearch.p12`]; +export function getESp12Volume() { + return ['--volume', `${ES_P12_PATH}:${SERVERLESS_CONFIG_PATH}certs/elasticsearch.p12`]; } /** * Removes REPO_ROOT from hostPath. Keep the rest to avoid filename collisions. - * Returns the path where a file will be mounted inside the ES or ESS container. + * Returns the path where a file will be mounted inside the ES or ES serverless container. * /root/kibana/package/foo/bar.json => /usr/share/elasticsearch/files/package/foo/bar.json */ export function getDockerFileMountPath(hostPath: string) { - return join(ESS_FILES_PATH, hostPath.replace(REPO_ROOT, '')); + return join(SERVERLESS_FILES_PATH, hostPath.replace(REPO_ROOT, '')); } /** @@ -460,24 +500,24 @@ export async function setupServerlessVolumes(log: ToolingLog, options: Serverles volumeCmds.push(...fileCmds); } - if (ssl) { - const essResources = ESS_RESOURCES_PATHS.reduce((acc, path) => { - acc.push('--volume', `${path}:${ESS_CONFIG_PATH}${basename(path)}`); + const serverlessResources = SERVERLESS_RESOURCES_PATHS.reduce((acc, path) => { + acc.push('--volume', `${path}:${SERVERLESS_CONFIG_PATH}${basename(path)}`); - return acc; - }, []); + return acc; + }, []); - volumeCmds.push( - ...getESp12Volume(), - ...essResources, + volumeCmds.push( + ...getESp12Volume(), + ...serverlessResources, - '--volume', - `${ESS_SECRETS_PATH}:${ESS_CONFIG_PATH}secrets/secrets.json:z`, + '--volume', + `${ + ssl ? SERVERLESS_SECRETS_SSL_PATH : SERVERLESS_SECRETS_PATH + }:${SERVERLESS_CONFIG_PATH}secrets/secrets.json:z`, - '--volume', - `${ESS_JWKS_PATH}:${ESS_CONFIG_PATH}secrets/jwks.json:z` - ); - } + '--volume', + `${SERVERLESS_JWKS_PATH}:${SERVERLESS_CONFIG_PATH}secrets/jwks.json:z` + ); return volumeCmds; } @@ -522,6 +562,13 @@ export async function runServerlessEsNode( ); } +function getESClient(clientOptions: ClientOptions): Client { + return new Client({ + Connection: HttpConnection, + ...clientOptions, + }); +} + /** * Runs an ES Serverless Cluster through Docker */ @@ -530,6 +577,7 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO await setupDocker({ log, image, options }); const volumeCmd = await setupServerlessVolumes(log, options); + const portCmd = resolvePort(options); const nodeNames = await Promise.all( SERVERLESS_NODES.map(async (node, i) => { @@ -537,14 +585,8 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO ...node, image, params: node.params.concat( - resolveEsArgs( - DEFAULT_SERVERLESS_ESARGS.concat( - node.esArgs ?? [], - options.ssl ? SERVERLESS_SSL_ESARGS : [] - ), - options - ), - i === 0 ? resolvePort(options) : [], + resolveEsArgs(DEFAULT_SERVERLESS_ESARGS.concat(node.esArgs ?? []), options), + i === 0 ? portCmd : [], volumeCmd ), }); @@ -553,14 +595,62 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO ); log.success(`Serverless ES cluster running. - Stop the cluster: ${chalk.bold(`docker container stop ${nodeNames.join(' ')}`)} + Login with username ${chalk.bold.cyan(ELASTIC_SERVERLESS_SUPERUSER)} or ${chalk.bold.cyan( + SYSTEM_INDICES_SUPERUSER + )} and password ${chalk.bold.magenta(ELASTIC_SERVERLESS_SUPERUSER_PASSWORD)} + Stop the cluster: ${chalk.bold(`docker container stop ${nodeNames.join(' ')}`)} + `); + + if (options.ssl) { + log.warning(`SSL has been enabled for ES. Kibana should be started with the SSL flag so that it can authenticate with ES. + See packages/kbn-es/src/serverless_resources/README.md for additional information on authentication. `); + } + + if (!options.skipTeardown) { + // SIGINT will not trigger in FTR (see cluster.runServerless for FTR signal) + process.on('SIGINT', () => teardownServerlessClusterSync(log, options)); + } + + if (options.waitForReady) { + log.info('Waiting until ES is ready to serve requests...'); + + const esNodeUrl = `${options.ssl ? 'https' : 'http'}://${portCmd[1].substring( + 0, + portCmd[1].lastIndexOf(':') + )}`; + + const client = getESClient({ + node: esNodeUrl, + auth: { bearer: kibanaDevServiceAccount.token }, + ...(options.ssl + ? { + tls: { + ca: [fs.readFileSync(CA_CERT_PATH)], + // NOTE: Even though we've added ca into the tls options, we are using 127.0.0.1 instead of localhost + // for the ip which is not validated. As such we are getting the error + // Hostname/IP does not match certificate's altnames: IP: 127.0.0.1 is not in the cert's list: + // To work around that we are overriding the function checkServerIdentity too + checkServerIdentity: () => { + return undefined; + }, + }, + } + : {}), + }); + await waitUntilClusterReady({ client, expectedStatus: 'green', log }); + } if (!options.background) { - // The ESS cluster has to be started detached, so we attach a logger afterwards for output + // The serverless cluster has to be started detached, so we attach a logger afterwards for output await execa('docker', ['logs', '-f', SERVERLESS_NODES[0].name], { // inherit is required to show Docker output and Java console output for pw, enrollment token, etc stdio: ['ignore', 'inherit', 'inherit'], + }).catch(() => { + /** + * docker logs will throw errors when the nodes are killed through SIGINT + * and the entrypoint doesn't exit normally, so we silence the errors. + */ }); } @@ -630,7 +720,7 @@ export async function runDockerContainer(log: ToolingLog, options: DockerOptions const dockerCmd = resolveDockerCmd(options, image); log.info(chalk.dim(`docker ${dockerCmd.join(' ')}`)); - return await execa('docker', dockerCmd, { + await execa('docker', dockerCmd, { // inherit is required to show Docker output and Java console output for pw, enrollment token, etc stdio: ['ignore', 'inherit', 'inherit'], }); diff --git a/packages/kbn-es/src/utils/index.ts b/packages/kbn-es/src/utils/index.ts index cc46d0bf28271..3110b82033be2 100644 --- a/packages/kbn-es/src/utils/index.ts +++ b/packages/kbn-es/src/utils/index.ts @@ -17,4 +17,4 @@ export { buildSnapshot } from './build_snapshot'; export { archiveForPlatform } from './build_snapshot'; export * from './parse_timeout_to_ms'; export * from './docker'; -export * from './ess_file_realm'; +export * from './serverless_file_realm'; diff --git a/packages/kbn-es/src/utils/ess_file_realm.ts b/packages/kbn-es/src/utils/serverless_file_realm.ts similarity index 100% rename from packages/kbn-es/src/utils/ess_file_realm.ts rename to packages/kbn-es/src/utils/serverless_file_realm.ts diff --git a/packages/kbn-es/src/utils/wait_until_cluster_ready.test.ts b/packages/kbn-es/src/utils/wait_until_cluster_ready.test.ts new file mode 100644 index 0000000000000..c662ba4ecaf8c --- /dev/null +++ b/packages/kbn-es/src/utils/wait_until_cluster_ready.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Client } from '@elastic/elasticsearch'; +import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; +import { waitUntilClusterReady } from './wait_until_cluster_ready'; + +jest.mock('@elastic/elasticsearch', () => { + return { + Client: jest.fn(), + }; +}); + +const log = new ToolingLog(); +const logWriter = new ToolingLogCollectingWriter(); +log.setWriters([logWriter]); + +const health = jest.fn(); + +beforeEach(() => { + jest.resetAllMocks(); + jest + .requireMock('@elastic/elasticsearch') + .Client.mockImplementation(() => ({ cluster: { health } })); + log.indent(-log.getIndent()); + logWriter.messages.length = 0; +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('waitUntilClusterReady', () => { + test(`waits for node to return 'green' status`, async () => { + health.mockImplementationOnce(() => Promise.reject(new Error('foo'))); + health.mockImplementationOnce(() => Promise.resolve({ status: 'red' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'yellow' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'green' })); // 4th call returns expected status + + const client = new Client({}); + + await waitUntilClusterReady({ client, log, expectedStatus: 'green' }); + expect(health).toHaveBeenCalledTimes(4); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info waiting for ES cluster to report a green status", + " warn waiting for ES cluster to come online, attempt 1 failed with: foo", + " succ ES cluster is ready", + ] + `); + }, 10000); + + test(`waits for node to return 'yellow' status`, async () => { + health.mockImplementationOnce(() => Promise.reject(new Error('foo'))); + health.mockImplementationOnce(() => Promise.resolve({ status: 'red' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'YELLOW' })); // 3rd call returns expected status + health.mockImplementationOnce(() => Promise.resolve({ status: 'yellow' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'green' })); + + const client = new Client({}); + + await waitUntilClusterReady({ client, log, expectedStatus: 'yellow' }); + expect(health).toHaveBeenCalledTimes(3); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info waiting for ES cluster to report a yellow status", + " warn waiting for ES cluster to come online, attempt 1 failed with: foo", + " succ ES cluster is ready", + ] + `); + }, 10000); + + test(`rejects when 'readyTimeout' is exceeded`, async () => { + health.mockImplementationOnce(() => Promise.reject(new Error('foo'))); + health.mockImplementationOnce(() => Promise.resolve({ status: 'red' })); + const client = new Client({}); + await expect( + waitUntilClusterReady({ client, log, expectedStatus: 'yellow', readyTimeout: 1000 }) + ).rejects.toThrow('ES cluster failed to come online with the 1 second timeout'); + }); +}); diff --git a/packages/kbn-es/src/utils/wait_until_cluster_ready.ts b/packages/kbn-es/src/utils/wait_until_cluster_ready.ts new file mode 100644 index 0000000000000..093d6192775ba --- /dev/null +++ b/packages/kbn-es/src/utils/wait_until_cluster_ready.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Client } from '@elastic/elasticsearch'; +import { HealthStatus } from '@elastic/elasticsearch/lib/api/types'; +import { ToolingLog } from '@kbn/tooling-log'; +const DEFAULT_READY_TIMEOUT = 60 * 1000; // 1 minute + +export type ClusterReadyStatus = 'green' | 'yellow'; +export interface WaitOptions { + client: Client; + expectedStatus: ClusterReadyStatus; + log: ToolingLog; + readyTimeout?: number; +} + +const checkStatus = (readyStatus: ClusterReadyStatus) => { + return readyStatus === 'yellow' + ? (status: HealthStatus) => status.toLocaleLowerCase() !== 'red' + : (status: HealthStatus) => status.toLocaleLowerCase() === 'green'; +}; + +/** + * General method to wait for the ES cluster status to be yellow or green + */ +export async function waitUntilClusterReady({ + client, + expectedStatus, + log, + readyTimeout = DEFAULT_READY_TIMEOUT, +}: WaitOptions) { + let attempt = 0; + const start = Date.now(); + + // The loop will continue until timeout even if SIGINT is signaled, so force exit + process.on('SIGINT', () => process.exit()); + + log.info(`waiting for ES cluster to report a ${expectedStatus} status`); + + const isReady = checkStatus(expectedStatus); + + while (true) { + attempt += 1; + + try { + const resp = await client.cluster.health(); + const status: HealthStatus = resp.status; + if (isReady(status)) { + log.success('ES cluster is ready'); + return; + } + + throw new Error(`not ready, cluster health is ${status}`); + } catch (error) { + const timeSinceStart = Date.now() - start; + if (timeSinceStart > readyTimeout) { + const sec = readyTimeout / 1000; + throw new Error(`ES cluster failed to come online with the ${sec} second timeout`); + } + + if (error?.message?.startsWith('not ready,')) { + if (timeSinceStart > 10_000) { + log.warning(error.message); + } + } else { + log.warning( + `waiting for ES cluster to come online, attempt ${attempt} failed with: ${error?.message}` + ); + } + + const waitSec = attempt * 1.5; + await new Promise((resolve) => setTimeout(resolve, waitSec * 1000)); + } + } +} diff --git a/packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts b/packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts index b739dc5116c1c..ef0e32c1d2755 100644 --- a/packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts +++ b/packages/kbn-eslint-plugin-telemetry/helpers/check_node_for_existing_data_test_subj_prop.ts @@ -37,7 +37,7 @@ export function checkNodeForExistingDataTestSubjProp( const variable = getScope().variables.find((v) => v.name === name); // the variable definition of the spreaded variable return variable && variable.defs.length > 0 - ? variable.defs[0].node.init.properties.find((property: TSESTree.Property) => { + ? variable.defs[0].node.init?.properties.find((property: TSESTree.Property) => { if ('value' in property.key) { return property.key.value === 'data-test-subj'; } diff --git a/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts index c8829f05efd21..f08cfa26cbc90 100644 --- a/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts +++ b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.test.ts @@ -63,7 +63,8 @@ for (const [name, tester] of [tsTester, babelTester]) { ], output: `<${element} data-test-subj="Value${element .replace('Eui', '') - .replace('Empty', '')}">Value`, + .replace('Empty', '') + .replace('Icon', '')}">Value`, })), } ); diff --git a/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.ts b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.ts index d2069d2845e59..7242ff1645f5e 100644 --- a/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.ts +++ b/packages/kbn-eslint-plugin-telemetry/rules/event_generating_elements_should_be_instrumented.ts @@ -17,6 +17,7 @@ import { getFunctionName } from '../helpers/get_function_name'; export const EVENT_GENERATING_ELEMENTS = [ 'EuiButton', 'EuiButtonEmpty', + 'EuiButtonIcon', 'EuiLink', 'EuiFieldText', 'EuiFieldSearch', @@ -74,7 +75,7 @@ export const EventGeneratingElementsShouldBeInstrumented: Rule.RuleModule = { const intent = getIntentFromNode(parent); // 4. The element name that generates the events - const element = name.replace('Eui', '').replace('Empty', ''); + const element = name.replace('Eui', '').replace('Empty', '').replace('Icon', ''); const suggestion = `${appName}${componentName}${intent}${element}`; // 'o11yHeaderActionsSubmitButton' diff --git a/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx b/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx index 21b7870db45db..e2a0dbdeae380 100644 --- a/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx +++ b/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx @@ -322,7 +322,6 @@ const AnnotationEditorControls = ({ )} { - return { - ...jest.requireActual('@elastic/eui'), - EuiDatePicker: () => <>, // for some reason this component caused an infinite loop when the props updated - }; -}); - -describe('event annotation group editor', () => { - const dataViewId = 'my-index-pattern'; - const adHocDataViewId = 'ad-hoc'; - const adHocDataViewSpec = { - id: adHocDataViewId, - title: 'Ad Hoc Data View', - }; - - const group: EventAnnotationGroupConfig = { - annotations: [], - description: '', - tags: [], - indexPatternId: dataViewId, - title: 'My group', - ignoreGlobalFilters: false, - dataViewSpec: adHocDataViewSpec, - }; - - let wrapper: ReactWrapper; - let updateMock: jest.Mock; - let setSelectedAnnotationMock: jest.Mock; - - const TagSelector = (_props: { onTagsSelected: (tags: string[]) => void }) =>
    ; - - beforeEach(async () => { - updateMock = jest.fn(); - setSelectedAnnotationMock = jest.fn(); - - wrapper = mountWithIntl( - - Promise.resolve({ - id: spec.id, - title: spec.title, - toSpec: () => spec, - } as unknown as DataView) - } - queryInputServices={{} as QueryInputServices} - showValidation={false} - /> - ); - - await act(async () => { - await new Promise((resolve) => setImmediate(resolve)); - wrapper.update(); - }); - }); - - it('reports group updates', () => { - ( - wrapper.find( - "EuiFieldText[data-test-subj='annotationGroupTitle']" - ) as ReactWrapper - ).prop('onChange')!({ - target: { - value: 'im a new title!', - } as Partial as EventTarget, - } as FormEvent); - - ( - wrapper.find( - "EuiTextArea[data-test-subj='annotationGroupDescription']" - ) as ReactWrapper - ).prop('onChange')!({ - target: { - value: 'im a new description!', - }, - } as ChangeEvent); - - act(() => { - wrapper.find(TagSelector).prop('onTagsSelected')(['im a new tag!']); - }); - - // TODO - reenable data view selection tests when ENABLE_INDIVIDUAL_ANNOTATION_EDITING is set to true! - // this will happen in https://github.com/elastic/kibana/issues/158774 - - // const setDataViewId = (id: string) => - // ( - // wrapper.find( - // "EuiSelect[data-test-subj='annotationDataViewSelection']" - // ) as ReactWrapper - // ).prop('onChange')!({ target: { value: id } } as React.ChangeEvent); - - // setDataViewId(dataViewId); - // setDataViewId(adHocDataViewId); - - expect(updateMock.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "annotations": Array [], - "dataViewSpec": Object { - "id": "ad-hoc", - "title": "Ad Hoc Data View", - }, - "description": "", - "ignoreGlobalFilters": false, - "indexPatternId": "my-index-pattern", - "tags": Array [], - "title": "im a new title!", - }, - ], - Array [ - Object { - "annotations": Array [], - "dataViewSpec": Object { - "id": "ad-hoc", - "title": "Ad Hoc Data View", - }, - "description": "im a new description!", - "ignoreGlobalFilters": false, - "indexPatternId": "my-index-pattern", - "tags": Array [], - "title": "My group", - }, - ], - Array [ - Object { - "annotations": Array [], - "dataViewSpec": Object { - "id": "ad-hoc", - "title": "Ad Hoc Data View", - }, - "description": "", - "ignoreGlobalFilters": false, - "indexPatternId": "my-index-pattern", - "tags": Array [ - "im a new tag!", - ], - "title": "My group", - }, - ], - ] - `); - }); - - if (ENABLE_INDIVIDUAL_ANNOTATION_EDITING) { - it('adds a new annotation group', () => { - act(() => { - wrapper.find('button[data-test-subj="addAnnotation"]').simulate('click'); - }); - - expect(updateMock).toHaveBeenCalledTimes(2); - const newAnnotations = (updateMock.mock.calls[0][0] as EventAnnotationGroupConfig) - .annotations; - expect(newAnnotations.length).toBe(group.annotations.length + 1); - expect(wrapper.exists(AnnotationEditorControls)); // annotation controls opened - }); - - it('incorporates annotation updates into group', () => { - const annotations = [ - getDefaultManualAnnotation('1', ''), - getDefaultManualAnnotation('2', ''), - ]; - - act(() => { - wrapper.setProps({ - selectedAnnotation: annotations[0], - group: { ...group, annotations }, - }); - }); - - wrapper.find(AnnotationEditorControls).prop('onAnnotationChange')({ - ...annotations[0], - color: 'newColor', - }); - - expect(updateMock).toHaveBeenCalledTimes(1); - expect(updateMock.mock.calls[0][0].annotations[0].color).toBe('newColor'); - expect(setSelectedAnnotationMock).toHaveBeenCalledTimes(1); - }); - - it('removes an annotation from a group', () => { - const annotations = [ - getDefaultManualAnnotation('1', ''), - getDefaultManualAnnotation('2', ''), - ]; - - act(() => { - wrapper.setProps({ - group: { ...group, annotations }, - }); - }); - - act(() => { - wrapper - .find('button[data-test-subj="indexPattern-dimension-remove"]') - .last() - .simulate('click'); - }); - - expect(updateMock).toHaveBeenCalledTimes(1); - expect(updateMock.mock.calls[0][0].annotations).toEqual(annotations.slice(0, 1)); - }); - } -}); diff --git a/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx b/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx deleted file mode 100644 index eb5a0dc7ad2fb..0000000000000 --- a/packages/kbn-event-annotation-components/components/group_editor_controls/group_editor_controls.tsx +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - EuiFieldText, - EuiForm, - EuiFormRow, - EuiSelect, - EuiText, - EuiTextArea, - EuiTitle, -} from '@elastic/eui'; -import { css } from '@emotion/react'; -import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { SavedObjectsTaggingApiUiComponent } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { QueryInputServices } from '@kbn/visualization-ui-components'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { - EventAnnotationConfig, - EventAnnotationGroupConfig, -} from '@kbn/event-annotation-common'; -import { EVENT_ANNOTATION_APP_NAME } from '../../constants'; -import { AnnotationEditorControls } from '../annotation_editor_controls'; -import { AnnotationList } from './annotation_list'; - -export const ENABLE_INDIVIDUAL_ANNOTATION_EDITING = false; - -const isTitleValid = (title: string) => Boolean(title.length); - -export const isGroupValid = (group: EventAnnotationGroupConfig) => isTitleValid(group.title); - -export const GroupEditorControls = ({ - group, - update, - setSelectedAnnotation: _setSelectedAnnotation, - selectedAnnotation, - TagSelector, - dataViews: globalDataViews, - createDataView, - queryInputServices, - showValidation, -}: { - group: EventAnnotationGroupConfig; - update: (group: EventAnnotationGroupConfig) => void; - selectedAnnotation: EventAnnotationConfig | undefined; - setSelectedAnnotation: (annotation: EventAnnotationConfig) => void; - TagSelector: SavedObjectsTaggingApiUiComponent['SavedObjectSaveModalTagSelector']; - dataViews: DataView[]; - createDataView: (spec: DataViewSpec) => Promise; - queryInputServices: QueryInputServices; - showValidation: boolean; -}) => { - // save the spec for the life of the component since the user might change their mind after selecting another data view - const [adHocDataView, setAdHocDataView] = useState(); - - useEffect(() => { - if (group.dataViewSpec) { - createDataView(group.dataViewSpec).then(setAdHocDataView); - } - }, [createDataView, group.dataViewSpec]); - - const setSelectedAnnotation = useCallback( - (newSelection: EventAnnotationConfig) => { - update({ - ...group, - annotations: group.annotations.map((annotation) => - annotation.id === newSelection.id ? newSelection : annotation - ), - }); - _setSelectedAnnotation(newSelection); - }, - [_setSelectedAnnotation, group, update] - ); - - const dataViews = useMemo(() => { - const items = [...globalDataViews]; - if (adHocDataView) { - items.push(adHocDataView); - } - return items; - }, [adHocDataView, globalDataViews]); - - const currentDataView = useMemo( - () => dataViews.find((dataView) => dataView.id === group.indexPatternId) || dataViews[0], - [dataViews, group.indexPatternId] - ); - - return !selectedAnnotation ? ( - <> - -

    - -

    -
    - - - - update({ - ...group, - title: value, - }) - } - /> - - - - - } - > - - update({ - ...group, - description: value, - }) - } - /> - - - - update({ - ...group, - tags, - }) - } - /> - - {ENABLE_INDIVIDUAL_ANNOTATION_EDITING && ( - <> - - ({ - value, - text: name ?? title, - }))} - value={group.indexPatternId} - onChange={({ target: { value } }) => - update({ - ...group, - indexPatternId: value, - dataViewSpec: - value === adHocDataView?.id ? adHocDataView.toSpec(false) : undefined, - }) - } - /> - - - update({ ...group, annotations: newAnnotations })} - /> - - - )} - - - ) : ( - setSelectedAnnotation({ ...selectedAnnotation, ...changes })} - dataView={currentDataView} - getDefaultRangeEnd={(rangeStart) => rangeStart} - queryInputServices={queryInputServices} - appName={EVENT_ANNOTATION_APP_NAME} - /> - ); -}; diff --git a/packages/kbn-event-annotation-components/components/group_editor_flyout.tsx b/packages/kbn-event-annotation-components/components/group_editor_flyout.tsx deleted file mode 100644 index 9403a69a5d8b4..0000000000000 --- a/packages/kbn-event-annotation-components/components/group_editor_flyout.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - EuiFlyout, - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiButton, - htmlIdGenerator, -} from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; -import type { QueryInputServices } from '@kbn/visualization-ui-components'; -import type { - EventAnnotationConfig, - EventAnnotationGroupConfig, -} from '@kbn/event-annotation-common'; -import { GroupEditorControls, isGroupValid } from './group_editor_controls'; - -export const GroupEditorFlyout = ({ - group, - updateGroup, - onClose: parentOnClose, - onSave, - savedObjectsTagging, - dataViews, - createDataView, - queryInputServices, -}: { - group: EventAnnotationGroupConfig; - updateGroup: (newGroup: EventAnnotationGroupConfig) => void; - onClose: () => void; - onSave: () => void; - savedObjectsTagging: SavedObjectsTaggingApi; - dataViews: DataView[]; - createDataView: (spec: DataViewSpec) => Promise; - queryInputServices: QueryInputServices; -}) => { - const flyoutHeadingId = useMemo(() => htmlIdGenerator()(), []); - const flyoutBodyOverflowRef = useRef(null); - useEffect(() => { - if (!flyoutBodyOverflowRef.current) { - flyoutBodyOverflowRef.current = document.querySelector('.euiFlyoutBody__overflow'); - } - }, []); - - const [hasAttemptedSave, setHasAttemptedSave] = useState(false); - - const resetContentScroll = useCallback( - () => flyoutBodyOverflowRef.current && flyoutBodyOverflowRef.current.scroll(0, 0), - [] - ); - - const [selectedAnnotation, _setSelectedAnnotation] = useState(); - const setSelectedAnnotation = useCallback( - (newValue: EventAnnotationConfig | undefined) => { - if ((!newValue && selectedAnnotation) || (newValue && !selectedAnnotation)) - resetContentScroll(); - _setSelectedAnnotation(newValue); - }, - [resetContentScroll, selectedAnnotation] - ); - - const onClose = () => (selectedAnnotation ? setSelectedAnnotation(undefined) : parentOnClose()); - - return ( - - - -

    - -

    -
    -
    - - - - - - - - {selectedAnnotation ? ( - - setSelectedAnnotation(undefined)} - > - - - - ) : ( - <> - - - - - - - { - setHasAttemptedSave(true); - - if (isGroupValid(group)) { - onSave(); - } - }} - > - - - - - )} - - -
    - ); -}; diff --git a/packages/kbn-event-annotation-components/components/index.ts b/packages/kbn-event-annotation-components/components/index.ts index d2a46bce9ef8c..f06df6bc48b59 100644 --- a/packages/kbn-event-annotation-components/components/index.ts +++ b/packages/kbn-event-annotation-components/components/index.ts @@ -9,8 +9,4 @@ // TODO - is this file needed? export { AnnotationEditorControls, annotationsIconSet } from './annotation_editor_controls'; -export * from './group_editor_controls'; - export * from './get_annotation_accessor'; - -export * from './table_list'; diff --git a/packages/kbn-event-annotation-components/components/table_list.tsx b/packages/kbn-event-annotation-components/components/table_list.tsx deleted file mode 100644 index 0085678cbd786..0000000000000 --- a/packages/kbn-event-annotation-components/components/table_list.tsx +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useCallback, useState } from 'react'; -import { TableListViewTable } from '@kbn/content-management-table-list-view-table'; -import type { TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view'; -import { i18n } from '@kbn/i18n'; -import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import type { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-browser'; -import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; -import type { QueryInputServices } from '@kbn/visualization-ui-components'; -import { IToasts } from '@kbn/core-notifications-browser'; -import { EuiButton, EuiEmptyPrompt, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { - EventAnnotationGroupConfig, - EventAnnotationGroupContent, -} from '@kbn/event-annotation-common'; -import type { EventAnnotationServiceType } from '../types'; -import { GroupEditorFlyout } from './group_editor_flyout'; - -export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; -export const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; - -const getCustomColumn = (dataViews: DataView[]) => { - const dataViewNameMap = Object.fromEntries( - dataViews.map((dataView) => [dataView.id, dataView.name ?? dataView.title]) - ); - - return { - field: 'dataView', - name: i18n.translate('eventAnnotationComponents.tableList.dataView', { - defaultMessage: 'Data view', - }), - sortable: false, - width: '150px', - render: (_field: string, record: EventAnnotationGroupContent) => ( -
    - {record.attributes.dataViewSpec - ? record.attributes.dataViewSpec.name - : dataViewNameMap[record.attributes.indexPatternId]} -
    - ), - }; -}; - -export const EventAnnotationGroupTableList = ({ - uiSettings, - eventAnnotationService, - visualizeCapabilities, - savedObjectsTagging, - parentProps, - dataViews, - createDataView, - queryInputServices, - toasts, - navigateToLens, -}: { - uiSettings: IUiSettingsClient; - eventAnnotationService: EventAnnotationServiceType; - visualizeCapabilities: Record>; - savedObjectsTagging: SavedObjectsTaggingApi; - parentProps: TableListTabParentProps; - dataViews: DataView[]; - createDataView: (spec: DataViewSpec) => Promise; - queryInputServices: QueryInputServices; - toasts: IToasts; - navigateToLens: () => void; -}) => { - const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); - const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); - - const [refreshListBouncer, setRefreshListBouncer] = useState(false); - - const refreshList = useCallback(() => { - setRefreshListBouncer((prev) => !prev); - }, []); - - const fetchItems = useCallback( - ( - searchTerm: string, - { - references, - referencesToExclude, - }: { - references?: SavedObjectsFindOptionsReference[]; - referencesToExclude?: SavedObjectsFindOptionsReference[]; - } = {} - ) => { - // todo - allow page size changes - return eventAnnotationService.findAnnotationGroupContent( - searchTerm, - listingLimit, // TODO is this right? - references?.map(({ id }) => id), - referencesToExclude?.map(({ id }) => id) - ); - }, - [eventAnnotationService, listingLimit] - ); - - const editItem = useCallback( - ({ id }: EventAnnotationGroupContent) => { - if (visualizeCapabilities.save) { - eventAnnotationService - .loadAnnotationGroup(id) - .then((group) => setGroupToEditInfo({ group, id })); - } - }, - [eventAnnotationService, visualizeCapabilities.save] - ); - - const [groupToEditInfo, setGroupToEditInfo] = useState<{ - group: EventAnnotationGroupConfig; - id: string; - }>(); - - const flyout = groupToEditInfo ? ( - setGroupToEditInfo({ group: newGroup, id: groupToEditInfo.id })} - onClose={() => setGroupToEditInfo(undefined)} - onSave={() => - (groupToEditInfo.id - ? eventAnnotationService.updateAnnotationGroup(groupToEditInfo.group, groupToEditInfo.id) - : eventAnnotationService.createAnnotationGroup(groupToEditInfo.group) - ).then(() => { - setGroupToEditInfo(undefined); - toasts.addSuccess(`Saved "${groupToEditInfo.group.title}"`); - refreshList(); - }) - } - savedObjectsTagging={savedObjectsTagging} - dataViews={dataViews} - createDataView={createDataView} - queryInputServices={queryInputServices} - /> - ) : undefined; - - return ( - <> - - refreshListBouncer={refreshListBouncer} - tableCaption={i18n.translate('eventAnnotationComponents.tableList.listTitle', { - defaultMessage: 'Annotation Library', - })} - findItems={fetchItems} - deleteItems={ - visualizeCapabilities.delete - ? (items) => eventAnnotationService.deleteAnnotationGroups(items.map(({ id }) => id)) - : undefined - } - editItem={editItem} - listingLimit={listingLimit} - initialPageSize={initialPageSize} - initialFilter={''} - customTableColumn={getCustomColumn(dataViews)} - emptyPrompt={ - -

    - -

    - - } - body={ -

    - -

    - } - actions={ - - - - } - iconType="flag" - /> - } - entityName={i18n.translate('eventAnnotationComponents.tableList.entityName', { - defaultMessage: 'annotation group', - })} - entityNamePlural={i18n.translate('eventAnnotationComponents.tableList.entityNamePlural', { - defaultMessage: 'annotation groups', - })} - onClickTitle={editItem} - {...parentProps} - /> - {flyout} - - ); -}; diff --git a/packages/kbn-event-annotation-components/index.ts b/packages/kbn-event-annotation-components/index.ts index 5364609ab7e1e..e5dcd2f52e695 100644 --- a/packages/kbn-event-annotation-components/index.ts +++ b/packages/kbn-event-annotation-components/index.ts @@ -10,7 +10,7 @@ export { AnnotationEditorControls, annotationsIconSet, } from './components/annotation_editor_controls'; -export { EventAnnotationGroupTableList, getAnnotationAccessor } from './components'; +export { getAnnotationAccessor } from './components'; export { defaultAnnotationColor, defaultAnnotationRangeColor, diff --git a/packages/kbn-event-annotation-components/jest.config.js b/packages/kbn-event-annotation-components/jest.config.js index 542264f8faaa4..18afc01787e6d 100644 --- a/packages/kbn-event-annotation-components/jest.config.js +++ b/packages/kbn-event-annotation-components/jest.config.js @@ -10,4 +10,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../..', roots: ['/packages/kbn-event-annotation-components'], + setupFiles: ['jest-canvas-mock'], }; diff --git a/packages/kbn-event-annotation-components/tsconfig.json b/packages/kbn-event-annotation-components/tsconfig.json index 7a26b61878e2f..462501c387a04 100644 --- a/packages/kbn-event-annotation-components/tsconfig.json +++ b/packages/kbn-event-annotation-components/tsconfig.json @@ -22,19 +22,11 @@ "@kbn/unified-field-list", "@kbn/data-views-plugin", "@kbn/data-plugin", - "@kbn/test-jest-helpers", - "@kbn/saved-objects-tagging-oss-plugin", "@kbn/core-ui-settings-browser", - "@kbn/dom-drag-drop", - "@kbn/content-management-table-list-view-table", - "@kbn/content-management-tabbed-table-list-view", + "@kbn/content-management-plugin", "@kbn/event-annotation-common", "@kbn/i18n-react", "@kbn/saved-objects-finder-plugin", - "@kbn/core-notifications-browser-mocks", - "@kbn/core-notifications-browser", - "@kbn/core-saved-objects-api-browser", "@kbn/expressions-plugin", - "@kbn/content-management-plugin", ] } diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap index ab6e52ceabfef..ba2969ab4a0b3 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap +++ b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap @@ -46,7 +46,7 @@ exports[`guide cards snapshots should render all cards 1`] = ` Object { "icon": "magnifyWithPlus", "navigateTo": Object { - "appId": "enterpriseSearchEsre", + "appId": "enterpriseSearchAISearch", }, "order": 4, "solution": "search", @@ -297,6 +297,33 @@ exports[`guide cards snapshots should render all cards 1`] = ` size="m" /> + + + + = Pick, 'isSavingEnabled'> & + Pick, 'value' | 'userValue'>; + +/** + * Interface defining available {@link https://storybook.js.org/docs/react/writing-stories/parameters parameters} + * for a {@link FieldInput} Storybook story. + */ +interface Params { + argTypes?: Record; + settingFields?: Partial>; +} + +/** + * Interface defining types for available {@link https://storybook.js.org/docs/react/writing-stories/args arguments} + * for a {@link FieldInput} Storybook story. + */ +export interface Args { + /** True if the field is disabled, false otherwise. */ + isSavingEnabled: boolean; + userValue: unknown; + value: unknown; +} + +/** + * Utility function for returning a {@link FieldInput} Storybook story + * definition. + * @param title The title displayed in the Storybook UI. + * @param description The description of the story. + * @returns A Storybook Story. + */ +export const getStory = (title: string, description: string) => + ({ + title: `Settings/Field Input/${title}`, + description, + argTypes: { + isSavingEnabled: { + name: 'Is saving enabled?', + }, + value: { + name: 'Default value', + }, + userValue: { + name: 'Current saved value', + }, + }, + decorators: [ + (Story) => ( + + + + + + ), + ], + } as ComponentMeta); + +/** + * Utility function for returning a {@link FieldInput} Storybook story. + * @param type The type of the UiSetting for this {@link FieldRow}. + * @param params Additional, optional {@link https://storybook.js.org/docs/react/writing-stories/parameters parameters}. + * @returns A Storybook Story. + */ +export const getInputStory = (type: SettingType, params: Params = {}) => { + const Story = ({ userValue, value, isSavingEnabled }: StoryProps) => { + const [unsavedChange, setUnsavedChange] = useState< + UnsavedFieldChange | undefined + >(); + + const setting: UiSettingMetadata = { + type, + value, + userValue, + ...params.settingFields, + }; + + const field = getFieldDefinition({ + id: setting.name?.split(' ').join(':').toLowerCase() || setting.type, + setting, + }); + + const onChange: OnChangeFn = (newChange) => { + setUnsavedChange(newChange); + + action('onChange')({ + type, + unsavedValue: newChange?.unsavedValue, + savedValue: field.savedValue, + }); + }; + + return ; + }; + + Story.argTypes = { + ...params.argTypes, + }; + + Story.args = { + isSavingEnabled: true, + value: getDefaultValue(type), + userValue: getUserValue(type), + ...params.argTypes, + }; + + return Story; +}; diff --git a/packages/kbn-management/settings/components/field_input/__stories__/image_input.stories.tsx b/packages/kbn-management/settings/components/field_input/__stories__/image_input.stories.tsx new file mode 100644 index 0000000000000..28a87465c680a --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/__stories__/image_input.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getInputStory, getStory } from './common'; + +export default getStory('Image Input', 'An input with an image value.'); +export const ImageInput = getInputStory('image' as const); diff --git a/packages/kbn-management/settings/components/field_input/__stories__/json_input.stories.tsx b/packages/kbn-management/settings/components/field_input/__stories__/json_input.stories.tsx new file mode 100644 index 0000000000000..f00fca4e5e9a5 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/__stories__/json_input.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getInputStory, getStory } from './common'; + +export default getStory('JSON Input', 'An input with a JSON value.'); +export const JSONInput = getInputStory('json' as const); diff --git a/packages/kbn-management/settings/components/field_input/__stories__/markdown_input.stories.tsx b/packages/kbn-management/settings/components/field_input/__stories__/markdown_input.stories.tsx new file mode 100644 index 0000000000000..ef0f9d358462f --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/__stories__/markdown_input.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getInputStory, getStory } from './common'; + +export default getStory('Markdown Input', 'An input with a markdown value.'); +export const MarkdownInput = getInputStory('markdown' as const); diff --git a/packages/kbn-management/settings/components/field_input/__stories__/number_input.stories.tsx b/packages/kbn-management/settings/components/field_input/__stories__/number_input.stories.tsx new file mode 100644 index 0000000000000..1d6aaa8952a3f --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/__stories__/number_input.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getInputStory, getStory } from './common'; + +export default getStory('Number Input', 'An input with a number value.'); +export const NumberInput = getInputStory('number' as const); diff --git a/packages/kbn-management/settings/components/field_input/__stories__/select_input.stories.tsx b/packages/kbn-management/settings/components/field_input/__stories__/select_input.stories.tsx new file mode 100644 index 0000000000000..08a46e3618c6c --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/__stories__/select_input.stories.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getInputStory, getStory } from './common'; + +const argTypes = { + value: { + name: 'Default value', + control: { + type: 'select', + options: ['option1', 'option2', 'option3'], + }, + }, + userValue: { + name: 'Current saved value', + control: { + type: 'select', + options: ['option1', 'option2', 'option3'], + }, + }, +}; + +const settingFields = { + optionLabels: { option1: 'Option 1', option2: 'Option 2', option3: 'Option 3' }, + options: ['option1', 'option2', 'option3'], +}; + +export default getStory('Select Input', 'An input with multiple values.'); +export const SelectInput = getInputStory('select' as const, { argTypes, settingFields }); + +SelectInput.args = { + isSavingEnabled: true, + value: 'option1', + userValue: 'option2', +}; diff --git a/packages/kbn-management/settings/components/field_input/__stories__/text_input.stories.tsx b/packages/kbn-management/settings/components/field_input/__stories__/text_input.stories.tsx new file mode 100644 index 0000000000000..39de404bde404 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/__stories__/text_input.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getInputStory, getStory } from './common'; + +export default getStory('String Input', 'An input with a string value.'); +export const StringInput = getInputStory('string' as const); diff --git a/packages/kbn-management/settings/components/field_input/code_editor.tsx b/packages/kbn-management/settings/components/field_input/code_editor.tsx new file mode 100644 index 0000000000000..3f46778917fdd --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/code_editor.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// This component was ported directly from `advancedSettings`, and hasn't really +// been vetted. It has, however, been refactored to be compliant with our +// current standards. +// +// @see src/plugins/advanced_settings/public/management_app/components/field/field_code_editor.tsx +// + +import React, { useCallback } from 'react'; +import { monaco, XJsonLang } from '@kbn/monaco'; +import { + CodeEditor as KibanaReactCodeEditor, + MarkdownLang, + type CodeEditorProps as KibanaReactCodeEditorProps, +} from '@kbn/kibana-react-plugin/public'; + +type Props = Pick; +type Options = KibanaReactCodeEditorProps['options']; + +export interface CodeEditorProps extends Props { + type: 'markdown' | 'json'; + isReadOnly: boolean; + name: string; +} + +const MIN_DEFAULT_LINES_COUNT = 6; +const MAX_DEFAULT_LINES_COUNT = 30; + +export const CodeEditor = ({ onChange, type, isReadOnly, name, ...props }: CodeEditorProps) => { + // setting editor height based on lines height and count to stretch and fit its content + const setEditorCalculatedHeight = useCallback( + (editor: monaco.editor.IStandaloneCodeEditor) => { + const editorElement = editor.getDomNode(); + + if (!editorElement) { + return; + } + + const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight); + let lineCount = editor.getModel()?.getLineCount() || MIN_DEFAULT_LINES_COUNT; + if (lineCount < MIN_DEFAULT_LINES_COUNT) { + lineCount = MIN_DEFAULT_LINES_COUNT; + } else if (lineCount > MAX_DEFAULT_LINES_COUNT) { + lineCount = MAX_DEFAULT_LINES_COUNT; + } + const height = lineHeight * lineCount; + + editorElement.id = name; + editorElement.style.height = `${height}px`; + editor.layout(); + }, + [name] + ); + + const trimEditorBlankLines = useCallback((editor: monaco.editor.IStandaloneCodeEditor) => { + const editorModel = editor.getModel(); + + if (!editorModel) { + return; + } + const trimmedValue = editorModel.getValue().trim(); + editorModel.setValue(trimmedValue); + }, []); + + const editorDidMount = useCallback( + (editor) => { + setEditorCalculatedHeight(editor); + + editor.onDidChangeModelContent(() => { + setEditorCalculatedHeight(editor); + }); + + editor.onDidBlurEditorWidget(() => { + trimEditorBlankLines(editor); + }); + }, + [setEditorCalculatedHeight, trimEditorBlankLines] + ); + + const options: Options = { + readOnly: isReadOnly, + lineNumbers: 'off', + scrollBeyondLastLine: false, + automaticLayout: true, + folding: false, + tabSize: 2, + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + wordWrap: 'on', + wrappingIndent: 'indent', + }; + + return ( + + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/field_input.test.tsx b/packages/kbn-management/settings/components/field_input/field_input.test.tsx new file mode 100644 index 0000000000000..0305636fd35e6 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/field_input.test.tsx @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { FieldInput, FieldInputProps } from './field_input'; +import { FieldDefinition, SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { TEST_SUBJ_PREFIX_FIELD } from './input'; +import { wrap } from './mocks'; +import { CodeEditorProps } from './code_editor'; + +const name = 'test'; + +jest.mock('./code_editor', () => ({ + CodeEditor: ({ value, onChange }: CodeEditorProps) => ( + { + if (onChange) { + onChange(e.target.value, e as any); + } + }} + /> + ), +})); + +describe('FieldInput', () => { + const getDefaultProps = (type: SettingType): FieldInputProps => { + let options; + if (type === 'select') { + options = { + labels: { + option1: 'Option 1', + option2: 'Option 2', + option3: 'Option 3', + }, + values: ['option1', 'option2', 'option3'], + }; + } + + const props: FieldInputProps = { + field: { + id: 'test', + name, + type, + ariaAttributes: { + ariaLabel: 'Test', + }, + options, + } as FieldDefinition, + onChange: jest.fn(), + isSavingEnabled: true, + }; + + return props; + }; + + it('renders without errors', () => { + const { container } = render(wrap()); + expect(container).toBeInTheDocument(); + }); + + it('renders a TextInput for a string field', () => { + const props = getDefaultProps('string'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('renders a NumberInput for a number field', () => { + const props = getDefaultProps('number'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('renders a BooleanInput for a boolean field', () => { + const props = getDefaultProps('boolean'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('renders a ColorInput for a color field', () => { + const props = getDefaultProps('color'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`euiColorPickerAnchor ${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('renders a ImageInput for a color field', () => { + const props = getDefaultProps('image'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('renders a JsonInput for a json field', () => { + const props = getDefaultProps('json'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('renders a MarkdownInput for a markdown field', () => { + const props = getDefaultProps('markdown'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('renders a SelectInput for an select field', () => { + const props = { + ...getDefaultProps('select'), + value: 'option2', + }; + + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeInTheDocument(); + }); + + it('calls the onChange prop when the value changes', () => { + const props = getDefaultProps('string'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + fireEvent.change(input, { target: { value: 'new value' } }); + expect(props.onChange).toHaveBeenCalledWith({ type: 'string', unsavedValue: 'new value' }); + }); + + it('disables the input when isDisabled prop is true', () => { + const props = getDefaultProps('string'); + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); + expect(input).toBeDisabled(); + }); + + it('throws an error if the field and unsavedChange types do not match', () => { + const consoleMock = jest.spyOn(console, 'error').mockImplementation(() => {}); + + [ + 'array', + 'boolean', + 'color', + 'image', + 'json', + 'markdown', + 'string', + 'select', + 'undefined', + ].forEach((type) => { + expect(() => + render( + wrap( + } + /> + ) + ) + ).toThrowError(`Unsaved change for ${type} mismatch: number`); + }); + + expect(() => + render( + wrap( + } + /> + ) + ) + ).toThrowError(`Unsaved change for number mismatch: string`); + + consoleMock.mockRestore(); + }); + + it('throws an error if type is unknown or incompatible', () => { + const consoleMock = jest.spyOn(console, 'error').mockImplementation(() => {}); + const defaultProps = getDefaultProps('string'); + const props = { + ...defaultProps, + field: { + ...defaultProps.field, + type: 'foobar', + }, + } as unknown as FieldInputProps; + + expect(() => render(wrap())).toThrowError( + 'Unknown or incompatible field type: foobar' + ); + + consoleMock.mockRestore(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/field_input.tsx b/packages/kbn-management/settings/components/field_input/field_input.tsx new file mode 100644 index 0000000000000..ae9e3c5c6c536 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/field_input.tsx @@ -0,0 +1,220 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useImperativeHandle, useRef } from 'react'; + +import type { + FieldDefinition, + OnChangeFn, + ResetInputRef, + SettingType, + UnsavedFieldChange, +} from '@kbn/management-settings-types'; + +import { + isArrayFieldDefinition, + isBooleanFieldDefinition, + isColorFieldDefinition, + isImageFieldDefinition, + isJsonFieldDefinition, + isMarkdownFieldDefinition, + isNumberFieldDefinition, + isSelectFieldDefinition, + isStringFieldDefinition, + isUndefinedFieldDefinition, +} from '@kbn/management-settings-field-definition'; + +import { + isArrayFieldUnsavedChange, + isBooleanFieldUnsavedChange, + isColorFieldUnsavedChange, + isImageFieldUnsavedChange, + isJsonFieldUnsavedChange, + isMarkdownFieldUnsavedChange, + isNumberFieldUnsavedChange, + isSelectFieldUnsavedChange, + isStringFieldUnsavedChange, + isUndefinedFieldUnsavedChange, +} from '@kbn/management-settings-field-definition/is'; + +import { + BooleanInput, + CodeEditorInput, + ColorPickerInput, + ImageInput, + NumberInput, + SelectInput, + TextInput, + ArrayInput, +} from './input'; + +/** + * The props that are passed to the {@link FieldInput} component. + */ +export interface FieldInputProps { + /** The {@link FieldDefinition} for the component. */ + field: Pick, 'type' | 'id' | 'name' | 'ariaAttributes'>; + /** An {@link UnsavedFieldChange} for the component, if any. */ + unsavedChange?: UnsavedFieldChange; + /** The `onChange` handler for the input. */ + onChange: OnChangeFn; + /** True if the input can be saved, false otherwise. */ + isSavingEnabled: boolean; + /** True if the value within the input is invalid, false otherwise. */ + isInvalid?: boolean; +} + +/** + * Build and return an `Error` if the type of the {@link UnsavedFieldChange} does not + * match the type of the {@link FieldDefinition}. + */ +const getMismatchError = (type: SettingType, unsavedType?: SettingType) => + new Error(`Unsaved change for ${type} mismatch: ${unsavedType}`); + +/** + * An input that allows one to change a setting in Kibana. + * + * @param props The props for the {@link FieldInput} component. + */ +export const FieldInput = React.forwardRef>( + (props, ref) => { + const { field, unsavedChange, onChange, isSavingEnabled } = props; + + // Create a ref for those input fields that require an imperative handle. + const inputRef = useRef(null); + + // Create an imperative handle that passes the invocation to any internal input that + // may require it. + useImperativeHandle(ref, () => ({ + reset: () => { + if (inputRef.current) { + inputRef.current.reset(); + } + }, + })); + + const inputProps = { isSavingEnabled, onChange }; + + // These checks might seem excessive or redundant, but they are necessary to ensure that + // the types are honored correctly using type guards. These checks get compiled down to + // checks against the `type` property-- which we were doing in the previous code, albeit + // in an unenforceable way. + // + // Based on the success of a check, we can render the `FieldInput` in a indempotent and + // type-safe way. + // + if (isArrayFieldDefinition(field)) { + // If the composing component mistakenly provides an incompatible `UnsavedFieldChange`, + // we can throw an `Error`. We might consider switching to a `console.error` and not + // rendering the input, but that might be less helpful. + if (!isArrayFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ; + } + + if (isBooleanFieldDefinition(field)) { + if (!isBooleanFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ; + } + + if (isColorFieldDefinition(field)) { + if (!isColorFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ; + } + + if (isImageFieldDefinition(field)) { + if (!isImageFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ; + } + + if (isJsonFieldDefinition(field)) { + if (!isJsonFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ( + + ); + } + + if (isMarkdownFieldDefinition(field)) { + if (!isMarkdownFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ( + + ); + } + + if (isNumberFieldDefinition(field)) { + if (!isNumberFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ; + } + + if (isSelectFieldDefinition(field)) { + if (!isSelectFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + const { + options: { values: optionValues, labels: optionLabels }, + } = field; + + return ( + + ); + } + + if (isStringFieldDefinition(field)) { + if (!isStringFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ; + } + + if (isUndefinedFieldDefinition(field)) { + if (!isUndefinedFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); + } + + return ( + } + unsavedChange={unsavedChange as unknown as UnsavedFieldChange<'string'>} + {...inputProps} + /> + ); + } + + throw new Error(`Unknown or incompatible field type: ${field.type}`); + } +); diff --git a/packages/kbn-management/settings/components/field_input/index.ts b/packages/kbn-management/settings/components/field_input/index.ts new file mode 100644 index 0000000000000..dc516877e8d51 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldInput, type FieldInputProps } from './field_input'; + +export type { FieldInputKibanaDependencies, FieldInputServices, InputProps } from './types'; diff --git a/packages/kbn-management/settings/components/field_input/input/array_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/array_input.test.tsx new file mode 100644 index 0000000000000..6f80de3039b70 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/array_input.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { ArrayInput } from './array_input'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; +import { wrap } from '../mocks'; +import { InputProps } from '../types'; + +const name = 'Some array field'; +const id = 'some:array:field'; + +describe('ArrayInput', () => { + const onChange = jest.fn(); + const defaultProps: InputProps<'array'> = { + onChange, + field: { + name, + type: 'array', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: ['foo', 'bar'], + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container } = render(wrap()); + expect(container).toBeInTheDocument(); + }); + + it('renders an array of strings', () => { + render(wrap()); + expect(screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`)).toHaveValue('foo, bar'); + }); + + it('renders saved value when present', () => { + render( + wrap( + + ) + ); + expect(screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`)).toHaveValue('foo, bar, baz'); + }); + + it('formats array when blurred', () => { + render(wrap()); + const input = screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.focus(input); + userEvent.type(input, ',baz'); + expect(input).toHaveValue('foo, bar,baz'); + input.blur(); + expect(input).toHaveValue('foo, bar, baz'); + }); + + it('only calls onChange when blurred ', () => { + render(wrap()); + const input = screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + + fireEvent.focus(input); + userEvent.type(input, ',baz'); + + expect(input).toHaveValue('foo, bar,baz'); + expect(defaultProps.onChange).not.toHaveBeenCalled(); + + act(() => { + input.blur(); + }); + + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'array', + unsavedValue: ['foo', 'bar', 'baz'], + }); + }); + + it('disables the input when isDisabled prop is true', () => { + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toBeDisabled(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/array_input.tsx b/packages/kbn-management/settings/components/field_input/input/array_input.tsx new file mode 100644 index 0000000000000..dfc92dc980bbd --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/array_input.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect, useState } from 'react'; +import { EuiFieldText, EuiFieldTextProps } from '@elastic/eui'; + +import { getFieldInputValue } from '@kbn/management-settings-utilities'; +import { useUpdate } from '@kbn/management-settings-utilities'; + +import { InputProps } from '../types'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +/** + * Props for an {@link ArrayFieldInput} component. + */ +export type ArrayInputProps = InputProps<'array'>; + +const REGEX = /,\s+/g; + +/** + * Component for manipulating an `array` field. + */ +export const ArrayInput = ({ + field, + unsavedChange, + isSavingEnabled, + onChange: onChangeProp, +}: ArrayInputProps) => { + const [inputValue] = getFieldInputValue(field, unsavedChange) || []; + const [value, setValue] = useState(inputValue?.join(', ')); + + const onChange: EuiFieldTextProps['onChange'] = (event) => { + const newValue = event.target.value; + setValue(newValue); + }; + + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + useEffect(() => { + setValue(inputValue?.join(', ')); + }, [inputValue]); + + // In the past, each keypress would invoke the `onChange` callback. This + // is likely wasteful, so we've switched it to `onBlur` instead. + const onBlur = (event: React.ChangeEvent) => { + const blurValue = event.target.value + .replace(REGEX, ',') + .split(',') + .filter((v) => v !== ''); + onUpdate({ type: field.type, unsavedValue: blurValue }); + setValue(blurValue.join(', ')); + }; + + const { id, name, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + + return ( + + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/input/boolean_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/boolean_input.test.tsx new file mode 100644 index 0000000000000..b9f3ac883421b --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/boolean_input.test.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; + +import { BooleanInput } from './boolean_input'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +import { wrap } from '../mocks'; +import { InputProps } from '../types'; + +const name = 'Some boolean field'; +const id = 'some:boolean:field'; + +describe('BooleanInput', () => { + const onChange = jest.fn(); + const defaultProps: InputProps<'boolean'> = { + onChange, + field: { + name, + type: 'boolean', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: false, + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders false', () => { + render(wrap()); + expect(screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`)).not.toBeChecked(); + }); + + it('renders true', () => { + render( + wrap() + ); + expect(screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`)).toBeChecked(); + }); + + it('renders unsaved value if present', () => { + render( + wrap( + + ) + ); + expect(screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`)).toBeChecked(); + }); + + it('calls onChange when toggled', () => { + render(wrap()); + const input = screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(defaultProps.onChange).not.toHaveBeenCalled(); + + act(() => { + fireEvent.click(input); + }); + + expect(defaultProps.onChange).toBeCalledWith({ type: 'boolean', unsavedValue: true }); + + act(() => { + fireEvent.click(input); + }); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/boolean_input.tsx b/packages/kbn-management/settings/components/field_input/input/boolean_input.tsx new file mode 100644 index 0000000000000..4f523da8067eb --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/boolean_input.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { EuiSwitch, EuiSwitchProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { getFieldInputValue, useUpdate } from '@kbn/management-settings-utilities'; + +import type { InputProps } from '../types'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +/** + * Props for a {@link BooleanInput} component. + */ +export type BooleanInputProps = InputProps<'boolean'>; + +/** + * Component for manipulating a `boolean` field. + */ +export const BooleanInput = ({ + field, + unsavedChange, + isSavingEnabled, + onChange: onChangeProp, +}: BooleanInputProps) => { + const onChange: EuiSwitchProps['onChange'] = (event) => { + const inputValue = event.target.checked; + onUpdate({ type: field.type, unsavedValue: inputValue }); + }; + + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + const { id, name, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + const [value] = getFieldInputValue(field, unsavedChange); + + return ( + + ) : ( + + ) + } + aria-label={ariaLabel} + aria-describedby={ariaDescribedBy} + checked={!!value} + data-test-subj={`${TEST_SUBJ_PREFIX_FIELD}-${id}`} + disabled={!isSavingEnabled} + {...{ name, onChange }} + /> + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/input/code_editor_input.tsx b/packages/kbn-management/settings/components/field_input/input/code_editor_input.tsx new file mode 100644 index 0000000000000..dc6c1e15043aa --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/code_editor_input.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { SettingType } from '@kbn/management-settings-types'; +import { getFieldInputValue, useUpdate } from '@kbn/management-settings-utilities'; + +import { CodeEditor, CodeEditorProps } from '../code_editor'; +import type { InputProps } from '../types'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +type Type = Extract; + +/** + * Props for a {@link CodeEditorInput} component. + */ +export interface CodeEditorInputProps extends InputProps { + /** The default value of the {@link CodeEditor} component. */ + defaultValue?: string; + /** + * The {@link UiSettingType}, expanded to include both `markdown` + * and `json` + */ + type: Type; +} + +/** + * Component for manipulating a `json` or `markdown` field. + * + * TODO: clintandrewhall - `kibana_react` `CodeEditor` does not support `disabled`. + */ +export const CodeEditorInput = ({ + field, + unsavedChange, + type, + isSavingEnabled, + defaultValue, + onChange: onChangeProp, +}: CodeEditorInputProps) => { + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + const onChange: CodeEditorProps['onChange'] = (inputValue) => { + let newUnsavedValue; + let errorParams = {}; + + switch (type) { + case 'json': + const isJsonArray = Array.isArray(JSON.parse(defaultValue || '{}')); + newUnsavedValue = inputValue || (isJsonArray ? '[]' : '{}'); + + try { + JSON.parse(newUnsavedValue); + } catch (e) { + errorParams = { + error: i18n.translate('management.settings.field.codeEditorSyntaxErrorMessage', { + defaultMessage: 'Invalid JSON syntax', + }), + isInvalid: true, + }; + } + break; + default: + newUnsavedValue = inputValue; + } + + onUpdate({ type: field.type, unsavedValue: inputValue, ...errorParams }); + }; + + const { id, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + // @ts-expect-error + const [value] = getFieldInputValue(field, unsavedChange); + + return ( +
    + +
    + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/input/color_picker_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/color_picker_input.test.tsx new file mode 100644 index 0000000000000..0bd73ad51645c --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/color_picker_input.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { ColorPickerInput, ColorPickerInputProps } from './color_picker_input'; +import { wrap } from '../mocks'; + +const name = 'Some color field'; +const id = 'some:color:field'; + +describe('ColorPickerInput', () => { + const onChange = jest.fn(); + const defaultProps: ColorPickerInputProps = { + onChange, + field: { + name, + type: 'color', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: '#000000', + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container, getByRole } = render(wrap()); + expect(container).toBeInTheDocument(); + const input = getByRole('textbox'); + expect(input).toHaveValue('#000000'); + }); + + it('calls the onChange prop when the value changes', () => { + const { getByRole } = render(wrap()); + const input = getByRole('textbox'); + const newValue = '#ffffff'; + fireEvent.change(input, { target: { value: newValue } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'color', unsavedValue: newValue }); + }); + + it('calls the onChange prop with an error when the value is malformed', () => { + const { getByRole } = render(wrap()); + const input = getByRole('textbox'); + const newValue = '#1234'; + fireEvent.change(input, { target: { value: newValue } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'color', + unsavedValue: newValue, + isInvalid: true, + error: 'Provide a valid color value', + }); + }); + + it('disables the input when isDisabled prop is true', () => { + const { getByRole } = render( + wrap() + ); + const input = getByRole('textbox'); + expect(input).toBeDisabled(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/color_picker_input.tsx b/packages/kbn-management/settings/components/field_input/input/color_picker_input.tsx new file mode 100644 index 0000000000000..8533fc0545eab --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/color_picker_input.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiColorPicker, EuiColorPickerProps } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { getFieldInputValue, useUpdate } from '@kbn/management-settings-utilities'; +import { UnsavedFieldChange } from '@kbn/management-settings-types'; + +import { TEST_SUBJ_PREFIX_FIELD } from '.'; +import { InputProps } from '../types'; + +/** + * Props for a {@link ColorPickerInput} component. + */ +export type ColorPickerInputProps = InputProps<'color'>; + +const invalidMessage = i18n.translate('management.settings.fieldInput.color.invalidMessage', { + defaultMessage: 'Provide a valid color value', +}); + +/** + * Component for manipulating a `color` field. + */ +export const ColorPickerInput = ({ + field, + unsavedChange, + isSavingEnabled, + onChange: onChangeProp, +}: ColorPickerInputProps) => { + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + const onChange: EuiColorPickerProps['onChange'] = (newColor, { isValid }) => { + const update: UnsavedFieldChange<'color'> = { type: field.type, unsavedValue: newColor }; + + if (isValid) { + onUpdate(update); + } else { + onUpdate({ ...update, isInvalid: true, error: invalidMessage }); + } + }; + + const { id, name, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + const [color] = getFieldInputValue(field, unsavedChange); + + return ( + + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/input/image_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/image_input.test.tsx new file mode 100644 index 0000000000000..cbbec332330d1 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/image_input.test.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { ImageInput, ImageInputProps } from './image_input'; +import { wrap } from '../mocks'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; +import { act } from 'react-dom/test-utils'; +import userEvent from '@testing-library/user-event'; + +const name = 'Some image field'; +const id = 'some:image:field'; + +describe('ImageInput', () => { + const onChange = jest.fn(); + const defaultProps: ImageInputProps = { + onChange, + field: { + name, + type: 'image', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: null, + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container } = render(wrap()); + expect(container).toBeInTheDocument(); + }); + + it('calls the onChange prop when a file is selected', async () => { + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`) as HTMLInputElement; + const file = new File(['(⌐□_□)'], 'test.png', { type: 'image/png' }); + + act(() => { + userEvent.upload(input, [file]); + }); + + expect(input.files?.length).toBe(1); + + // This doesn't work for some reason. + // expect(defaultProps.onChange).toHaveBeenCalledWith({ value: file }); + }); + + it('disables the input when isDisabled prop is true', () => { + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toBeDisabled(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/image_input.tsx b/packages/kbn-management/settings/components/field_input/input/image_input.tsx new file mode 100644 index 0000000000000..286cba2c49d4c --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/image_input.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useImperativeHandle, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFilePicker, EuiFilePickerProps, EuiImage } from '@elastic/eui'; + +import { ResetInputRef } from '@kbn/management-settings-types'; +import { getFieldInputValue, useUpdate } from '@kbn/management-settings-utilities'; + +import type { InputProps } from '../types'; +import { useServices } from '../services'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +/** + * Props for a {@link ImageInput} component. + */ +export type ImageInputProps = InputProps<'image'>; + +const getImageAsBase64 = async (file: Blob): Promise => { + const reader = new FileReader(); + reader.readAsDataURL(file); + + return new Promise((resolve, reject) => { + reader.onload = () => { + resolve(reader.result!); + }; + reader.onerror = (err) => { + reject(err); + }; + }); +}; + +const errorMessage = i18n.translate('management.settings.field.imageChangeErrorMessage', { + defaultMessage: 'Image could not be saved', +}); + +/** + * Component for manipulating an `image` field. + */ +export const ImageInput = React.forwardRef( + ({ field, unsavedChange, isSavingEnabled, onChange: onChangeProp }, ref) => { + const inputRef = useRef(null); + + useImperativeHandle(ref, () => ({ + reset: () => inputRef.current?.removeFiles(), + })); + + const { showDanger } = useServices(); + + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + const onChange: EuiFilePickerProps['onChange'] = async (files: FileList | null) => { + if (files === null || !files.length) { + onUpdate(); + return null; + } + + const file = files[0]; + + try { + let base64Image = ''; + + if (file instanceof File) { + base64Image = String(await getImageAsBase64(file)); + } + + onUpdate({ type: field.type, unsavedValue: base64Image }); + } catch (err) { + showDanger(errorMessage); + onUpdate({ type: field.type, unsavedValue: '', error: errorMessage, isInvalid: true }); + } + }; + + const { id, name, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + const [value] = getFieldInputValue(field, unsavedChange); + + const a11yProps = { + 'aria-label': ariaLabel, + 'aria-describedby': ariaDescribedBy, + }; + + // TODO: this check will be a bug, if a default image is ever actually + // defined in Kibana. + // + // see: https://github.com/elastic/kibana/issues/166578 + // + if (value) { + return ; + } else { + return ( + + ); + } + } +); diff --git a/packages/kbn-management/settings/components/field_input/input/index.ts b/packages/kbn-management/settings/components/field_input/input/index.ts new file mode 100644 index 0000000000000..2790604feb9e9 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { ArrayInput, type ArrayInputProps } from './array_input'; +export { CodeEditorInput, type CodeEditorInputProps } from './code_editor_input'; +export { BooleanInput, type BooleanInputProps } from './boolean_input'; +export { ColorPickerInput, type ColorPickerInputProps } from './color_picker_input'; +export { ImageInput, type ImageInputProps } from './image_input'; +export { NumberInput, type NumberInputProps } from './number_input'; +export { SelectInput, type SelectInputProps } from './select_input'; +export { TextInput, type TextInputProps } from './text_input'; + +export const TEST_SUBJ_PREFIX_FIELD = 'management-settings-editField'; diff --git a/packages/kbn-management/settings/components/field_input/input/json_editor_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/json_editor_input.test.tsx new file mode 100644 index 0000000000000..2cd34de067ffc --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/json_editor_input.test.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; + +import { CodeEditorInput, CodeEditorInputProps } from './code_editor_input'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; +import { CodeEditorProps } from '../code_editor'; + +const name = 'Some json field'; +const id = 'some:json:field'; +const initialValue = '{"foo":"bar"}'; + +jest.mock('../code_editor', () => ({ + CodeEditor: ({ value, onChange }: CodeEditorProps) => ( + { + if (onChange) { + onChange(e.target.value, e as any); + } + }} + /> + ), +})); + +describe('JsonEditorInput', () => { + const onChange = jest.fn(); + const defaultProps: CodeEditorInputProps = { + onChange, + type: 'json', + field: { + name, + type: 'json', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: initialValue, + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); + }); + + it('renders the value prop', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toHaveValue(initialValue); + }); + + it('calls the onChange prop when the object value changes', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '{"bar":"foo"}' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'json', + unsavedValue: '{"bar":"foo"}', + }); + }); + + it('calls the onChange prop when the object value changes with no value', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '' }); + }); + + it('calls the onChange prop with an error when the object value changes to invalid JSON', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '{"bar" "foo"}' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'json', + unsavedValue: '{"bar" "foo"}', + error: 'Invalid JSON syntax', + isInvalid: true, + }); + }); + + it('calls the onChange prop when the array value changes', () => { + const props = { ...defaultProps, defaultValue: '["bar", "foo"]', value: undefined }; + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '["foo", "bar", "baz"]' } }); + waitFor(() => + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'json', + unsavedValue: '["foo", "bar", "baz"]', + }) + ); + }); + + it('calls the onChange prop when the array value changes with no value', () => { + const props = { + ...defaultProps, + defaultValue: '["bar", "foo"]', + value: '["bar", "foo"]', + }; + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '' }); + }); + + it('calls the onChange prop with an array when the array value changes to invalid JSON', () => { + const props = { ...defaultProps, defaultValue: '["bar", "foo"]', value: undefined }; + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '["bar", "foo" | "baz"]' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'json', + unsavedValue: '["bar", "foo" | "baz"]', + error: 'Invalid JSON syntax', + isInvalid: true, + }); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/markdown_editor_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/markdown_editor_input.test.tsx new file mode 100644 index 0000000000000..dd15b250cf1e0 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/markdown_editor_input.test.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; + +import { CodeEditorInput, CodeEditorInputProps } from './code_editor_input'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; +import { CodeEditorProps } from '../code_editor'; + +const name = 'Some markdown field'; +const id = 'some:markdown:field'; +const initialValue = '# A Markdown Title'; + +jest.mock('../code_editor', () => ({ + CodeEditor: ({ value, onChange }: CodeEditorProps) => ( + { + if (onChange) { + onChange(e.target.value, e as any); + } + }} + /> + ), +})); + +describe('MarkdownEditorInput', () => { + const onChange = jest.fn(); + const defaultProps: CodeEditorInputProps = { + onChange, + type: 'markdown', + field: { + name, + type: 'markdown', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: initialValue, + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); + }); + + it('renders the value prop', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toHaveValue(initialValue); + }); + + it('calls the onChange prop when the value changes', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '# New Markdown Title' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'markdown', + unsavedValue: '# New Markdown Title', + }); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx new file mode 100644 index 0000000000000..3fd6518102a46 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/number_input.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import { NumberInput, NumberInputProps } from './number_input'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; +import { wrap } from '../mocks'; + +const name = 'Some number field'; +const id = 'some:number:field'; + +describe('NumberInput', () => { + const onChange = jest.fn(); + const defaultProps: NumberInputProps = { + onChange, + field: { + name, + type: 'number', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: 12345, + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container, getByTestId } = render(wrap()); + expect(container).toBeInTheDocument(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toHaveValue(defaultProps.field.defaultValue); + }); + + it('renders the saved value if present', () => { + const { getByTestId } = render( + wrap() + ); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toHaveValue(9876); + }); + + it('renders the unsaved value if present', () => { + const { getByTestId } = render( + wrap( + + ) + ); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toHaveValue(4321); + }); + + it('calls the onChange prop when the value changes', () => { + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: '54321' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'number', unsavedValue: 54321 }); + }); + + it('disables the input when isDisabled prop is true', () => { + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toBeDisabled(); + }); + + it('recovers if value is null', () => { + const { getByTestId } = render( + wrap() + ); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + waitFor(() => expect(input).toHaveValue(undefined)); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/number_input.tsx b/packages/kbn-management/settings/components/field_input/input/number_input.tsx new file mode 100644 index 0000000000000..f67e27be505e2 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/number_input.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiFieldNumber, EuiFieldNumberProps } from '@elastic/eui'; + +import { getFieldInputValue, useUpdate } from '@kbn/management-settings-utilities'; + +import { InputProps } from '../types'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +/** + * Props for a {@link NumberInput} component. + */ +export type NumberInputProps = InputProps<'number'>; + +/** + * Component for manipulating a `number` field. + */ +export const NumberInput = ({ + field, + unsavedChange, + isSavingEnabled, + onChange: onChangeProp, +}: NumberInputProps) => { + const onChange: EuiFieldNumberProps['onChange'] = (event) => { + const inputValue = Number(event.target.value); + onUpdate({ type: field.type, unsavedValue: inputValue }); + }; + + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + const { id, name, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + const [rawValue] = getFieldInputValue(field, unsavedChange); + + const value = rawValue === null ? undefined : rawValue; + + return ( + + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/input/select_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/select_input.test.tsx new file mode 100644 index 0000000000000..ca2e875a65604 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/select_input.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { SelectInput, SelectInputProps } from './select_input'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; +import { wrap } from '../mocks'; + +const name = 'Some select field'; +const id = 'some:select:field'; + +describe('SelectInput', () => { + const onChange = jest.fn(); + const defaultProps: SelectInputProps = { + onChange, + field: { + name, + type: 'select', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: 'option2', + }, + optionLabels: { + option1: 'Option 1', + option2: 'Option 2', + option3: 'Option 3', + }, + optionValues: ['option1', 'option2', 'option3'], + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container, getByTestId } = render(wrap()); + expect(container).toBeInTheDocument(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toHaveValue('option2'); + }); + + it('calls the onChange prop when the value changes', () => { + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: 'option3' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'select', unsavedValue: 'option3' }); + }); + + it('disables the input when isDisabled prop is true', () => { + const { getByTestId } = render(wrap()); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toBeDisabled(); + }); + + it('throws when optionValues is not provided', () => { + const consoleMock = jest.spyOn(console, 'error').mockImplementation(() => {}); + const props = { + ...defaultProps, + optionLabels: undefined as any, + optionValues: [], + } as SelectInputProps; + + expect(() => render(wrap())).toThrowError( + 'non-empty `optionValues` are required for `SelectInput`.' + ); + consoleMock.mockRestore(); + }); + + it('recovers if optionLabel is missing', () => { + const props = { + ...defaultProps, + optionLabels: {}, + } as SelectInputProps; + const { container } = render(wrap()); + + expect(container).toBeInTheDocument(); + }); + + it('recovers if value is null', () => { + const props = { + ...defaultProps, + value: null, + } as SelectInputProps; + const { container } = render(wrap()); + + expect(container).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/select_input.tsx b/packages/kbn-management/settings/components/field_input/input/select_input.tsx new file mode 100644 index 0000000000000..bd53fb9913ec5 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/select_input.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo } from 'react'; +import { EuiSelect, EuiSelectProps } from '@elastic/eui'; + +import { getFieldInputValue, useUpdate } from '@kbn/management-settings-utilities'; + +import { InputProps } from '../types'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +/** + * Props for a {@link SelectInput} component. + */ +export interface SelectInputProps extends InputProps<'select'> { + /** Specify the option labels to their values. */ + optionLabels: Record; + /** Specify the option values. */ + optionValues: Array; +} + +/** + * Component for manipulating a `select` field. + */ +export const SelectInput = ({ + field, + unsavedChange, + onChange: onChangeProp, + optionLabels = {}, + optionValues: optionsProp, + isSavingEnabled, +}: SelectInputProps) => { + if (optionsProp.length === 0) { + throw new Error('non-empty `optionValues` are required for `SelectInput`.'); + } + + const options = useMemo( + () => + optionsProp?.map((option) => ({ + text: optionLabels.hasOwnProperty(option) ? optionLabels[option] : option, + value: option, + })), + [optionsProp, optionLabels] + ); + + const onChange: EuiSelectProps['onChange'] = (event) => { + const inputValue = event.target.value; + onUpdate({ type: field.type, unsavedValue: inputValue }); + }; + + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + const { id, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + const [value] = getFieldInputValue(field, unsavedChange); + + return ( + + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/input/text_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/text_input.test.tsx new file mode 100644 index 0000000000000..9dcdb8a04d5ea --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/text_input.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; + +import { TextInput, TextInputProps } from './text_input'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +const name = 'Some text field'; +const id = 'some:text:field'; + +describe('TextInput', () => { + const onChange = jest.fn(); + const defaultProps: TextInputProps = { + onChange, + field: { + name, + type: 'string', + ariaAttributes: { + ariaLabel: name, + }, + id, + isOverridden: false, + defaultValue: 'initial value', + }, + isSavingEnabled: true, + }; + + beforeEach(() => { + onChange.mockClear(); + }); + + it('renders without errors', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); + }); + + it('renders the value prop', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toHaveValue('initial value'); + }); + + it('calls the onChange prop when the value changes', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + fireEvent.change(input, { target: { value: 'new value' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith({ + type: 'string', + unsavedValue: 'new value', + }); + }); + + it('disables the input when isDisabled prop is true', () => { + const { getByTestId } = render(); + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); + expect(input).toBeDisabled(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_input/input/text_input.tsx b/packages/kbn-management/settings/components/field_input/input/text_input.tsx new file mode 100644 index 0000000000000..f4f9450e9577f --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/input/text_input.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiFieldText, EuiFieldTextProps } from '@elastic/eui'; + +import { getFieldInputValue, useUpdate } from '@kbn/management-settings-utilities'; + +import { InputProps } from '../types'; +import { TEST_SUBJ_PREFIX_FIELD } from '.'; + +/** + * Props for a {@link TextInput} component. + */ +export type TextInputProps = InputProps<'string'>; + +/** + * Component for manipulating a `string` field. + */ +export const TextInput = ({ + field, + unsavedChange, + isSavingEnabled, + onChange: onChangeProp, +}: TextInputProps) => { + const onChange: EuiFieldTextProps['onChange'] = (event) => { + const inputValue = event.target.value; + onUpdate({ type: field.type, unsavedValue: inputValue }); + }; + + const onUpdate = useUpdate({ onChange: onChangeProp, field }); + + const { id, name, ariaAttributes } = field; + const { ariaLabel, ariaDescribedBy } = ariaAttributes; + const [value] = getFieldInputValue(field, unsavedChange); + + return ( + + ); +}; diff --git a/packages/kbn-management/settings/components/field_input/kibana.jsonc b/packages/kbn-management/settings/components/field_input/kibana.jsonc new file mode 100644 index 0000000000000..afd721741627b --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-components-field-input", + "owner": "@elastic/platform-deployment-management" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_input/mocks/context.mock.tsx b/packages/kbn-management/settings/components/field_input/mocks/context.mock.tsx new file mode 100644 index 0000000000000..daf926561bc84 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/mocks/context.mock.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { ReactChild } from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; + +import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root'; +import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { I18nStart } from '@kbn/core-i18n-browser'; + +import { FieldInputProvider } from '../services'; +import { FieldInputServices } from '../types'; + +const createRootMock = () => { + const i18n: I18nStart = { + Context: ({ children }) => {children}, + }; + const theme = themeServiceMock.createStartContract(); + return { + i18n, + theme, + }; +}; + +export const createFieldInputServicesMock = (): FieldInputServices => ({ + showDanger: jest.fn(), +}); + +export const TestWrapper = ({ + children, + services = createFieldInputServicesMock(), +}: { + children: ReactChild; + services?: FieldInputServices; +}) => { + return ( + + {children} + + ); +}; + +export const wrap = ( + component: JSX.Element, + services: FieldInputServices = createFieldInputServicesMock() +) => ( + + {component} + +); diff --git a/packages/kbn-management/settings/components/field_input/mocks/index.ts b/packages/kbn-management/settings/components/field_input/mocks/index.ts new file mode 100644 index 0000000000000..8eb7547c59584 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/mocks/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TestWrapper, createFieldInputServicesMock, wrap } from './context.mock'; + +export type { FieldInputProvider } from '../services'; +export type { FieldInputServices } from '../types'; diff --git a/packages/kbn-management/settings/components/field_input/package.json b/packages/kbn-management/settings/components/field_input/package.json new file mode 100644 index 0000000000000..ca9dda8f8b384 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-components-field-input", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_input/services.tsx b/packages/kbn-management/settings/components/field_input/services.tsx new file mode 100644 index 0000000000000..b76c9b7a9a6a5 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/services.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC, useContext } from 'react'; +import type { FieldInputServices, FieldInputKibanaDependencies } from './types'; + +const FieldInputContext = React.createContext(null); + +/** + * React Provider that provides services to a {@link FieldInput} component and its dependents. + */ +export const FieldInputProvider: FC = ({ children, ...services }) => { + // Typescript types are widened to accept more than what is needed. Take only what is necessary + // so the context remains clean. + const { showDanger } = services; + + return {children}; +}; + +/** + * Kibana-specific Provider that maps Kibana plugins and services to a {@link FieldInputProvider}. + */ +export const FieldInputKibanaProvider: FC = ({ + children, + toasts, +}) => { + return ( + toasts.addDanger(message), + }} + > + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + * + * @see {@link FieldInputServices} + */ +export const useServices = () => { + const context = useContext(FieldInputContext); + + if (!context) { + throw new Error( + 'FieldInputContext is missing. Ensure your component or React root is wrapped with FieldInputProvider.' + ); + } + + return context; +}; diff --git a/packages/kbn-management/settings/components/field_input/setup_tests.ts b/packages/kbn-management/settings/components/field_input/setup_tests.ts new file mode 100644 index 0000000000000..8d1acb9232934 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/setup_tests.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; diff --git a/packages/kbn-management/settings/components/field_input/tsconfig.json b/packages/kbn-management/settings/components/field_input/tsconfig.json new file mode 100644 index 0000000000000..a6fe848abc2a9 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-types", + "@kbn/management-settings-field-definition", + "@kbn/monaco", + "@kbn/kibana-react-plugin", + "@kbn/management-settings-utilities", + "@kbn/i18n-react", + "@kbn/i18n", + "@kbn/core-notifications-browser", + "@kbn/core-ui-settings-common", + "@kbn/react-kibana-context-root", + "@kbn/core-theme-browser-mocks", + "@kbn/core-i18n-browser", + ] +} diff --git a/packages/kbn-management/settings/components/field_input/types.ts b/packages/kbn-management/settings/components/field_input/types.ts new file mode 100644 index 0000000000000..e5d5c11e2f199 --- /dev/null +++ b/packages/kbn-management/settings/components/field_input/types.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + FieldDefinition, + OnChangeFn, + SettingType, + UnsavedFieldChange, +} from '@kbn/management-settings-types'; +import { ToastsStart } from '@kbn/core-notifications-browser'; + +/** + * Contextual services used by a {@link FieldInput} component. + */ +export interface FieldInputServices { + /** + * Displays a danger toast message. + * @param value The message to display. + */ + showDanger: (value: string) => void; +} + +/** + * An interface containing a collection of Kibana plugins and services required to + * render this component. + */ +export interface FieldInputKibanaDependencies { + /** The portion of the {@link ToastsStart} contract used by this component. */ + toasts: Pick; +} + +/** + * Props passed to a {@link FieldInput} component. + */ +export interface InputProps { + field: Pick< + FieldDefinition, + 'ariaAttributes' | 'defaultValue' | 'id' | 'name' | 'savedValue' | 'type' | 'isOverridden' + >; + unsavedChange?: UnsavedFieldChange; + isSavingEnabled: boolean; + /** The `onChange` handler. */ + onChange: OnChangeFn; +} diff --git a/packages/kbn-management/settings/components/field_row/README.mdx b/packages/kbn-management/settings/components/field_row/README.mdx new file mode 100644 index 0000000000000..6fe238938407c --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/README.mdx @@ -0,0 +1,37 @@ +--- +id: management/settings/components/fieldRow +slug: /management/settings/components/field-row +title: Management Settings Field Row Component +description: A package containing a component for rendering and manipulating a UiSetting in the Advanced Settings UI. +tags: ['management', 'settings'] +date: 2023-08-31 +--- + +## Description + +This package contains a component for rendering and manipulating a single UiSetting in the Advanced Settings UI. + +For reference, this is an example of the current Advanced Settings UI: + +
    Advanced Settings as a form.
    + +*Advanced Settings as a form.* + +## Implementation + +A `FormRow` represents a single UiSetting, and is responsible for rendering the UiSetting's label, description, and equivalent value input. It displays the state of any unsaved change, (e.g. error). It also handles the logic for updating the UiSetting's value in a consuming component through the `onChange` handler. + +
    Anatomy of a `FormRow`
    + +*Anatomy of a `FormRow`* + +## Notes + +- This implementation was extracted from the `advancedSettings` plugin. +- The type for a `UiSettingMetadata` is limited due to the permissive nature of the [`UiSettingsParam` type](packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts). +- The source includes notations of several bugs which will surface if the assumptions about default settings from Kibana change. + +## Testing + +- Code coverage stands at 95%. +- Storybook stories are included. Run `yarn storybook management` to view them. \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_row/__stories__/array_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/array_field.stories.tsx new file mode 100644 index 0000000000000..dfe384fdd2349 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/array_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +export default getStory('Array Row', 'A setting with an array of values.'); +export const ArrayRow = getFieldRowStory('array' as const); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/boolean_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/boolean_field.stories.tsx new file mode 100644 index 0000000000000..0d663a26cb5f8 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/boolean_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getStory, getFieldRowStory } from './common'; + +export default getStory('Boolean Row', 'A setting with a boolean value.'); +export const BooleanRow = getFieldRowStory('boolean' as const); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/color_picker_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/color_picker_field.stories.tsx new file mode 100644 index 0000000000000..61b0033d19175 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/color_picker_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +export default getStory('Color Row', 'A setting with an base64 image value.'); +export const ColorRow = getFieldRowStory('color' as const); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/common.tsx b/packages/kbn-management/settings/components/field_row/__stories__/common.tsx new file mode 100644 index 0000000000000..58e145146cc40 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/common.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; +import type { ComponentMeta } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { EuiPanel } from '@elastic/eui'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; + +import { KnownTypeToMetadata, UiSettingMetadata } from '@kbn/management-settings-types/metadata'; +import { getDefaultValue, getUserValue } from '@kbn/management-settings-utilities/storybook'; +import { getFieldDefinition } from '@kbn/management-settings-field-definition'; +import { FieldRow as Component, FieldRow } from '../field_row'; +import { FieldRowProvider } from '../services'; +import { RowOnChangeFn } from '../types'; + +/** + * Props for a {@link FieldInput} Storybook story. + */ +export interface StoryProps + extends Pick, 'userValue' | 'value'> { + /** Simulate if the UiSetting is custom. */ + isCustom: boolean; + /** Simulate if the UiSetting is deprecated. */ + isDeprecated: boolean; + /** Simulate if the UiSetting is overriden. */ + isOverridden: boolean; + /** Simulate if saving settings is enabled in the UI. */ + isSavingEnabled: boolean; +} + +/** + * Utility function for returning a {@link FieldRow} Storybook story + * definition. + * @param title The title displayed in the Storybook UI. + * @param description The description of the Story. + * @returns A Storybook Story. + */ +export const getStory = ( + title: string, + description: string, + argTypes: Record = {} +) => + ({ + title: `Settings/Field Row/${title}`, + description, + argTypes: { + userValue: { + name: 'Current saved value', + }, + value: { + name: 'Default value from Kibana', + }, + isSavingEnabled: { + name: 'Saving is enabled?', + }, + isCustom: { + name: 'Setting is custom?', + }, + isDeprecated: { + name: 'Setting is deprecated?', + }, + isOverridden: { + name: 'Setting is overridden?', + }, + ...argTypes, + }, + decorators: [ + (Story) => ( + + + + + + ), + ], + } as ComponentMeta); + +/** + * Default argument values for a {@link FieldInput} Storybook story. + */ +export const storyArgs = { + /** True if the saving settings is disabled, false otherwise. */ + isSavingEnabled: true, + /** True if the UiSetting is custom, false otherwise. */ + isCustom: false, + /** True if the UiSetting is deprecated, false otherwise. */ + isDeprecated: false, + /** True if the UiSetting is overridden, false otherwise. */ + isOverridden: false, +}; + +/** + * Utility function for returning a {@link FieldRow} Storybook story. + * @param type The type of the UiSetting for this {@link FieldRow}. + * @returns A Storybook Story. + */ +export const getFieldRowStory = ( + type: SettingType, + settingFields?: Partial> +) => { + const Story = ({ + isCustom, + isDeprecated, + isOverridden, + isSavingEnabled, + userValue, + value, + }: StoryProps) => { + const [unsavedChange, setUnsavedChange] = useState< + UnsavedFieldChange | undefined + >(); + + const setting: UiSettingMetadata = { + type, + value, + userValue: userValue === '' ? null : userValue, + name: `Some ${type} setting`, + deprecation: isDeprecated + ? { message: 'This setting is deprecated', docLinksKey: 'storybook' } + : undefined, + category: ['categoryOne', 'categoryTwo'], + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu odio velit. Integer et mauris quis ligula elementum commodo. Morbi eu ipsum diam. Nulla auctor orci eget egestas vehicula. Aliquam gravida, dolor eu posuere vulputate, neque enim viverra odio, id viverra ipsum quam et ipsum.', + requiresPageReload: false, + ...settingFields, + }; + + const field = getFieldDefinition({ + id: setting.name?.split(' ').join(':').toLowerCase() || setting.type, + setting, + params: { + isCustom, + isOverridden, + }, + }); + + const onChange: RowOnChangeFn = (_id, newChange) => { + setUnsavedChange(newChange); + + action('onChange')({ + type, + unsavedValue: newChange?.unsavedValue, + savedValue: field.savedValue, + }); + }; + + return ; + }; + + // In Kibana, the image default value is never anything other than null. There would be a number + // of issues if it was anything but, so, in Storybook, we want to remove the default value argument. + if (type === 'image') { + Story.args = { + userValue: getUserValue(type), + ...storyArgs, + }; + } else { + Story.args = { + userValue: getUserValue(type), + value: getDefaultValue(type), + ...storyArgs, + }; + } + + return Story; +}; diff --git a/packages/kbn-management/settings/components/field_row/__stories__/image_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/image_field.stories.tsx new file mode 100644 index 0000000000000..26975a2c8e4af --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/image_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +export default getStory('Image Row', 'A setting with an base64 image value.'); +export const ImageRow = getFieldRowStory('image' as const); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/json_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/json_field.stories.tsx new file mode 100644 index 0000000000000..8a941a3abd804 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/json_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +export default getStory('JSON Row', 'A setting with a JSON value.'); +export const JSONRow = getFieldRowStory('json' as const); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/markdown_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/markdown_field.stories.tsx new file mode 100644 index 0000000000000..0a858d5ec5ae7 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/markdown_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +export default getStory('Markdown Row', 'A setting with a Markdown value.'); +export const MarkdownRow = getFieldRowStory('markdown' as const); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/number_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/number_field.stories.tsx new file mode 100644 index 0000000000000..dc97a11386afc --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/number_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +export default getStory('Number Row', 'A setting with a numeric value.'); +export const NumberRow = getFieldRowStory('number' as const); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/select_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/select_field.stories.tsx new file mode 100644 index 0000000000000..531a708afc635 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/select_field.stories.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +const argTypes = { + userValue: { + name: 'Current saved value', + control: { + type: 'select', + options: ['option1', 'option2', 'option3'], + }, + }, +}; + +const settingFields = { + optionLabels: { option1: 'Option 1', option2: 'Option 2', option3: 'Option 3' }, + options: ['option1', 'option2', 'option3'], +}; + +export default getStory('Select Row', 'A setting with a boolean value.', argTypes); +export const SelectRow = getFieldRowStory('select' as const, settingFields); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/text_field.stories.tsx b/packages/kbn-management/settings/components/field_row/__stories__/text_field.stories.tsx new file mode 100644 index 0000000000000..09ca6ada1d88d --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/__stories__/text_field.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getFieldRowStory, getStory } from './common'; + +export default getStory('String Row', 'A setting with a string value.'); +export const StringRow = getFieldRowStory('string' as const); diff --git a/packages/kbn-management/settings/components/field_row/assets/form_row.png b/packages/kbn-management/settings/components/field_row/assets/form_row.png new file mode 100644 index 0000000000000..e880adf032d8e Binary files /dev/null and b/packages/kbn-management/settings/components/field_row/assets/form_row.png differ diff --git a/packages/kbn-management/settings/components/field_row/assets/page.png b/packages/kbn-management/settings/components/field_row/assets/page.png new file mode 100644 index 0000000000000..9654e548193d4 Binary files /dev/null and b/packages/kbn-management/settings/components/field_row/assets/page.png differ diff --git a/packages/kbn-management/settings/components/field_row/description/default_value.test.tsx b/packages/kbn-management/settings/components/field_row/description/default_value.test.tsx new file mode 100644 index 0000000000000..3071a4cd9ca0b --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/description/default_value.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { FieldDefaultValue, DATA_TEST_SUBJ_DEFAULT_DISPLAY_PREFIX } from './default_value'; +import { wrap } from '../mocks'; + +describe('FieldDefaultValue', () => { + it('renders without errors', () => { + const { container } = render( + wrap( + + ) + ); + + expect(container).toBeInTheDocument(); + }); + + it('renders nothing if the default value is set', () => { + const { container } = render( + wrap( + + ) + ); + + expect(container).toBeEmptyDOMElement(); + }); + + it('renders nothing if an unsaved change matches the default value', () => { + const { container } = render( + wrap( + + ) + ); + + expect(container).toBeEmptyDOMElement(); + }); + + it('does not render a code block for string fields', () => { + const { queryByTestId, getByText } = render( + wrap( + + ) + ); + const input = queryByTestId(`${DATA_TEST_SUBJ_DEFAULT_DISPLAY_PREFIX}-test`); + expect(input).not.toBeInTheDocument(); + const label = getByText('hello world'); + expect(label).toBeInTheDocument(); + }); + + it('renders a code block for JSON fields', () => { + const { getByTestId } = render( + wrap( + + ) + ); + const input = getByTestId(`${DATA_TEST_SUBJ_DEFAULT_DISPLAY_PREFIX}-test`); + expect(input).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_row/description/default_value.tsx b/packages/kbn-management/settings/components/field_row/description/default_value.tsx new file mode 100644 index 0000000000000..535dbbdba8028 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/description/default_value.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiCode, EuiCodeBlock, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { + isJsonFieldDefinition, + isMarkdownFieldDefinition, +} from '@kbn/management-settings-field-definition'; +import { FieldDefinition, SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; + +export const DATA_TEST_SUBJ_DEFAULT_DISPLAY_PREFIX = 'default-display-block'; +/** + * Props for a {@link FieldDefaultValue} component. + */ +export interface FieldDefaultValueProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Pick< + FieldDefinition, + 'id' | 'type' | 'isDefaultValue' | 'defaultValueDisplay' | 'defaultValue' + >; + unsavedChange?: UnsavedFieldChange; +} + +/** + * Component for displaying the default value of a {@link FieldDefinition} + * in the {@link FieldRow}. + */ +export const FieldDefaultValue = ({ + field, + unsavedChange, +}: FieldDefaultValueProps) => { + if (field.isDefaultValue) { + return null; + } + + if ( + unsavedChange && + (unsavedChange.unsavedValue === field.defaultValue || unsavedChange.unsavedValue === undefined) + ) { + return null; + } + + const { defaultValueDisplay: display, id } = field; + + let value = {display}; + + if (isJsonFieldDefinition(field) || isMarkdownFieldDefinition(field)) { + value = ( + = 500 ? 300 : undefined} + > + {display} + + ); + } + + return ( + + + + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/description/deprecation.test.tsx b/packages/kbn-management/settings/components/field_row/description/deprecation.test.tsx new file mode 100644 index 0000000000000..47b43ec7b085a --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/description/deprecation.test.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { FieldDeprecation } from './deprecation'; +import { wrap } from '../mocks'; + +describe('FieldDeprecation', () => { + const defaultProps = { + field: { + id: 'test:field', + name: 'test', + type: 'string', + deprecation: undefined, + }, + }; + + it('renders without errors', () => { + const { container } = render( + wrap( + + ) + ); + expect(container).toBeInTheDocument(); + }); + + it('renders nothing if there is no deprecation', () => { + const { container } = render(wrap()); + expect(container.firstChild).toBeNull(); + }); + + it('renders a warning badge if there is a deprecation', () => { + const { getByText } = render( + wrap( + + ) + ); + const badge = getByText('Deprecated'); + expect(badge).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_row/description/deprecation.tsx b/packages/kbn-management/settings/components/field_row/description/deprecation.tsx new file mode 100644 index 0000000000000..76f8895df6603 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/description/deprecation.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; +import { useServices } from '../services'; + +export const DATA_TEST_SUBJ_DEPRECATION_PREFIX = 'description-block-deprecation'; + +type Field = Pick, 'id' | 'deprecation' | 'name'>; + +/** + * Props for a {@link FieldDeprecation} component. + */ +export interface FieldDeprecationProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Field; +} + +/** + * + */ +export const FieldDeprecation = ({ field }: FieldDeprecationProps) => { + const { links } = useServices(); + const { deprecation, name, id } = field; + + if (!deprecation) { + return null; + } + + return ( +
    + + { + window.open(links[deprecation!.docLinksKey], '_blank'); + }} + onClickAriaLabel={i18n.translate('management.settings.field.deprecationClickAreaLabel', { + defaultMessage: 'Click to view deprecation documentation for {name}.', + values: { + name, + }, + })} + > + Deprecated + + +
    + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/description/description.test.tsx b/packages/kbn-management/settings/components/field_row/description/description.test.tsx new file mode 100644 index 0000000000000..92f66d797eb31 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/description/description.test.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { FieldDescription } from './description'; +import { FieldDefinition } from '@kbn/management-settings-types'; +import { wrap } from '../mocks'; + +const description = 'hello world description'; + +describe('FieldDescription', () => { + const defaultProps = { + field: { + defaultValue: null, + defaultValueDisplay: 'null', + id: 'test', + isDefaultValue: false, + name: 'test', + savedValue: 'hello world', + type: 'string', + } as FieldDefinition<'string'>, + }; + + it('renders without errors', () => { + const { getByText } = render( + wrap( + + ) + ); + expect(getByText(description)).toBeInTheDocument(); + }); + + it('renders a React Element', () => { + const value = 'This is a description.'; + const element =
    {value}
    ; + const { getByText } = render( + wrap( + + ) + ); + expect(getByText(value)).toBeInTheDocument(); + }); + + it('renders no description without one', () => { + const { queryByText } = render(wrap()); + expect(queryByText(description)).toBeNull(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_row/description/description.tsx b/packages/kbn-management/settings/components/field_row/description/description.tsx new file mode 100644 index 0000000000000..757cb22ba799c --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/description/description.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { ReactElement } from 'react'; + +import { FieldDefinition, SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { EuiText } from '@elastic/eui'; + +import { useFieldStyles } from '../field_row.styles'; +import { FieldDeprecation } from './deprecation'; +import { FieldDefaultValue } from './default_value'; + +export const DATA_TEST_SUBJ_DESCRIPTION = 'settings-description'; + +type Field = Pick< + FieldDefinition, + | 'defaultValue' + | 'defaultValueDisplay' + | 'description' + | 'id' + | 'isDefaultValue' + | 'name' + | 'savedValue' + | 'type' +>; + +/** + * Props for a {@link FieldDescription} component. + */ +export interface FieldDescriptionProps { + field: Field; + unsavedChange?: UnsavedFieldChange; +} + +/** + * Component for displaying the description of a {@link FieldDefinition}. + */ +export const FieldDescription = ({ + field, + unsavedChange, +}: FieldDescriptionProps) => { + const { cssDescription } = useFieldStyles({ field, unsavedChange }); + const { description, name } = field; + + // TODO - this does *not* match the `UiSetting` type. + // @see packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts + let content: ReactElement | string | undefined = description; + + if (!React.isValidElement(content)) { + content = ( +
    + ); + } + + if (content) { + content = ( + + {content} + + ); + } + + return ( +
    + + {content} + +
    + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/description/index.ts b/packages/kbn-management/settings/components/field_row/description/index.ts new file mode 100644 index 0000000000000..e0b513037b6d1 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/description/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldDescription, type FieldDescriptionProps } from './description'; diff --git a/packages/kbn-management/settings/components/field_row/field_row.styles.ts b/packages/kbn-management/settings/components/field_row/field_row.styles.ts new file mode 100644 index 0000000000000..ece92a9fbd1aa --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/field_row.styles.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { UnsavedFieldChange, FieldDefinition, SettingType } from '@kbn/management-settings-types'; +import { hasUnsavedChange } from '@kbn/management-settings-utilities'; + +/** + * Parameters for the {@link useFieldStyles} hook. + */ +export interface Params { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Pick, 'savedValue'>; + /** The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. */ + unsavedChange?: UnsavedFieldChange; +} + +/** + * A React hook that provides stateful `css` classes for the {@link FieldRow} component. + */ +export const useFieldStyles = ({ field, unsavedChange }: Params) => { + const { + euiTheme: { size, colors }, + } = useEuiTheme(); + + const unsaved = hasUnsavedChange(field, unsavedChange); + const error = unsavedChange?.error; + + return { + cssFieldFormGroup: css` + + * { + margin-top: ${size.base}; + } + `, + cssFieldTitle: css` + font-weight: bold; + padding-left: ${size.s}; + margin-left: -${size.s}; + + ${unsaved ? `box-shadow: -${size.xs} 0 ${colors.warning};` : ''} + + ${error ? `box-shadow: -${size.xs} 0 ${colors.danger};` : ''} + `, + cssDescription: css` + & > div { + margin-bottom: ${size.s}; + } + `, + }; +}; diff --git a/packages/kbn-management/settings/components/field_row/field_row.test.tsx b/packages/kbn-management/settings/components/field_row/field_row.test.tsx new file mode 100644 index 0000000000000..afa425d2a459a --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/field_row.test.tsx @@ -0,0 +1,548 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; + +import { SettingType } from '@kbn/management-settings-types'; +import { getFieldDefinition } from '@kbn/management-settings-field-definition'; +import { KnownTypeToMetadata } from '@kbn/management-settings-types/metadata'; + +import { DATA_TEST_SUBJ_SCREEN_READER_MESSAGE, FieldRow } from './field_row'; +import { wrap } from './mocks'; + +import { TEST_SUBJ_PREFIX_FIELD } from '@kbn/management-settings-components-field-input/input'; +import { DATA_TEST_SUBJ_RESET_PREFIX } from './footer/reset_link'; +import { DATA_TEST_SUBJ_CHANGE_LINK_PREFIX } from './footer/change_image_link'; + +const defaults = { + requiresPageReload: false, + readonly: false, + category: ['category'], +}; + +const defaultValues: Record = { + array: ['example_value'], + boolean: true, + color: '#FF00CC', + image: '', + json: "{ foo: 'bar2' }", + markdown: 'Hello World', + number: 1, + select: 'apple', + string: 'hello world', + undefined: 'undefined', +}; + +const defaultInputValues: Record = { + array: 'example_value', + boolean: true, + color: '#FF00CC', + image: '', + json: '{"hello": "world"}', + markdown: '**bold**', + number: 1, + select: 'apple', + string: 'hello world', + undefined: 'undefined', +}; + +const userValues: Record = { + array: ['user', 'value'], + boolean: false, + image: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + json: '{"hello": "world"}', + markdown: '**bold**', + number: 10, + select: 'banana', + string: 'foo', + color: '#FACF0C', + undefined: 'something', +}; + +const userInputValues: Record = { + array: 'user, value', + boolean: false, + image: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + json: '{"hello": "world"}', + markdown: '**bold**', + number: 10, + select: 'banana', + string: 'foo', + color: '#FACF0C', + undefined: 'something', +}; + +type Settings = { + [key in SettingType]: KnownTypeToMetadata; +}; + +const settings: Omit = { + array: { + description: 'Description for Array test setting', + name: 'array:test:setting', + type: 'array', + userValue: null, + value: defaultValues.array, + ...defaults, + }, + boolean: { + description: 'Description for Boolean test setting', + name: 'boolean:test:setting', + type: 'boolean', + userValue: null, + value: defaultValues.boolean, + ...defaults, + }, + color: { + description: 'Description for Color test setting', + name: 'color:test:setting', + type: 'color', + userValue: null, + value: defaultValues.color, + ...defaults, + }, + image: { + description: 'Description for Image test setting', + name: 'image:test:setting', + type: 'image', + userValue: null, + value: defaultValues.image, + ...defaults, + }, + // This is going to take a lot of mocks to test. + // + // json: { + // name: 'json:test:setting', + // description: 'Description for Json test setting', + // type: 'json', + // userValue: '{"foo": "bar"}', + // value: '{}', + // ...defaults, + // }, + // + // This is going to take a lot of mocks to test. + // + // markdown: { + // name: 'markdown:test:setting', + // description: 'Description for Markdown test setting', + // type: 'markdown', + // userValue: null, + // value: '', + // ...defaults, + // }, + number: { + description: 'Description for Number test setting', + name: 'number:test:setting', + type: 'number', + userValue: null, + value: defaultValues.number, + ...defaults, + }, + select: { + description: 'Description for Select test setting', + name: 'select:test:setting', + options: ['apple', 'orange', 'banana'], + optionLabels: { + apple: 'Apple', + orange: 'Orange', + banana: 'Banana', + }, + type: 'select', + userValue: null, + value: defaultValues.select, + ...defaults, + }, + string: { + description: 'Description for String test setting', + name: 'string:test:setting', + type: 'string', + userValue: null, + value: defaultValues.string, + ...defaults, + }, + undefined: { + description: 'Description for Undefined test setting', + name: 'undefined:test:setting', + type: 'undefined', + userValue: null, + value: defaultValues.undefined, + ...defaults, + }, +}; + +const handleChange = jest.fn(); + +describe('Field', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + (Object.keys(settings) as SettingType[]).forEach((type) => { + if (type === 'json' || type === 'markdown') { + return; + } + + const setting = settings[type]; + const id = settings[type].name || type; + const inputTestSubj = `${TEST_SUBJ_PREFIX_FIELD}-${id}`; + + describe(`for ${type} setting`, () => { + it('should render', () => { + const { container } = render( + wrap( + + ) + ); + + expect(container).toBeInTheDocument(); + }); + + it('should render default value if there is no user value set', () => { + const { getByTestId } = render( + wrap( + + ) + ); + + if (type === 'boolean') { + expect(getByTestId(inputTestSubj)).toHaveAttribute('aria-checked', 'true'); + } else if (type === 'color') { + expect(getByTestId(`euiColorPickerAnchor ${inputTestSubj}`)).toHaveValue( + defaultInputValues[type] + ); + } else if (type === 'number') { + expect(getByTestId(inputTestSubj)).toHaveValue(defaultInputValues[type]); + } else if (type === 'image') { + expect(getByTestId(inputTestSubj)).toBeInTheDocument(); + expect(getByTestId(inputTestSubj)).toHaveAttribute('type', 'file'); + } else { + expect(getByTestId(inputTestSubj)).toHaveValue(String(defaultInputValues[type]) as any); + } + }); + + it('should render as read only with help text if overridden', async () => { + const { getByTestId } = render( + wrap( + + ) + ); + if (type === 'color') { + expect(getByTestId(`euiColorPickerAnchor ${inputTestSubj}`)).toBeDisabled(); + } else { + expect(getByTestId(inputTestSubj)).toBeDisabled(); + } + + // expect(getByTestId(`${DATA_TEST_SUBJ_OVERRIDDEN_PREFIX}-${id}`)).toBeInTheDocument(); + }); + + it('should render as read only if saving is disabled', () => { + const { getByTestId } = render( + wrap( + + ) + ); + if (type === 'color') { + expect(getByTestId(`euiColorPickerAnchor ${inputTestSubj}`)).toBeDisabled(); + } else { + expect(getByTestId(inputTestSubj)).toBeDisabled(); + } + }); + + it('should render user value if there is user value is set', async () => { + const { getByTestId, getByAltText } = render( + wrap( + + ) + ); + + if (type === 'boolean') { + expect(getByTestId(inputTestSubj)).toHaveAttribute('aria-checked', 'false'); + } else if (type === 'color') { + expect(getByTestId(`euiColorPickerAnchor ${inputTestSubj}`)).toHaveValue( + userValues[type] + ); + } else if (type === 'number') { + expect(getByTestId(inputTestSubj)).toHaveValue(userValues[type]); + } else if (type === 'image') { + expect(getByAltText(id)).toBeInTheDocument(); + expect(getByAltText(id)).toHaveAttribute('src', userValues[type]); + } else { + expect(getByTestId(inputTestSubj)).toHaveValue(String(userInputValues[type]) as any); + } + }); + + it('should render custom setting icon if it is custom', () => { + const { getByText } = render( + wrap( + + ) + ); + + expect(getByText('Custom setting')).toBeInTheDocument(); + }); + + it('should render unsaved value if there are unsaved changes', () => { + const { getByTestId, getByAltText } = render( + wrap( + + ) + ); + + if (type === 'boolean') { + expect(getByTestId(inputTestSubj)).toHaveAttribute('aria-checked', 'false'); + } else if (type === 'color') { + expect(getByTestId(`euiColorPickerAnchor ${inputTestSubj}`)).toHaveValue( + userInputValues[type] + ); + } else if (type === 'number') { + expect(getByTestId(inputTestSubj)).toHaveValue(userInputValues[type]); + } else if (type === 'image') { + expect(getByAltText(id)).toBeInTheDocument(); + expect(getByAltText(id)).toHaveAttribute('src', userValues[type]); + } else { + expect(getByTestId(inputTestSubj)).toHaveValue(String(userInputValues[type]) as any); + } + }); + + it('should reset when reset link is clicked', () => { + const field = getFieldDefinition({ + id, + setting: { + ...setting, + userValue: userValues[type], + }, + }); + + const { getByTestId } = render( + wrap() + ); + + const input = getByTestId(`${DATA_TEST_SUBJ_RESET_PREFIX}-${field.id}`); + fireEvent.click(input); + expect(handleChange).toHaveBeenCalledWith(field.id, { + type, + unsavedValue: field.defaultValue, + }); + }); + + it('should reset when reset link is clicked with an unsaved change', () => { + const field = getFieldDefinition({ + id, + setting, + }); + + const { getByTestId } = render( + wrap( + + ) + ); + + const input = getByTestId(`${DATA_TEST_SUBJ_RESET_PREFIX}-${field.id}`); + fireEvent.click(input); + expect(handleChange).toHaveBeenCalledWith(field.id, undefined); + }); + }); + }); + + it('should fire onChange when input changes', () => { + const setting = settings.string; + const field = getFieldDefinition({ id: setting.name || setting.type, setting }); + + const { getByTestId } = render( + wrap() + ); + + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${field.id}`); + fireEvent.change(input, { target: { value: 'new value' } }); + expect(handleChange).toHaveBeenCalledWith(field.id, { + type: 'string', + unsavedValue: 'new value', + }); + }); + + it('should fire onChange with an error when input changes with invalid value', () => { + const setting = settings.color; + const field = getFieldDefinition({ id: setting.name || setting.type, setting }); + + const { getByTestId } = render( + wrap() + ); + + const input = getByTestId(`euiColorPickerAnchor ${TEST_SUBJ_PREFIX_FIELD}-${field.id}`); + fireEvent.change(input, { target: { value: '#1234' } }); + + expect(handleChange).toHaveBeenCalledWith(field.id, { + type: 'color', + error: 'Provide a valid color value', + isInvalid: true, + unsavedValue: '#1234', + }); + }); + + it('should show screen reader content with an unsaved change.', () => { + const setting = settings.color; + const field = getFieldDefinition({ id: setting.name || setting.type, setting }); + + const { getByText, getByTestId } = render( + wrap( + + ) + ); + + expect(getByText('Setting is currently not saved.')).toBeInTheDocument(); + const input = getByTestId(`euiColorPickerAnchor ${TEST_SUBJ_PREFIX_FIELD}-${field.id}`); + fireEvent.change(input, { target: { value: '#1235' } }); + + waitFor(() => expect(input).toHaveValue('#1235')); + + waitFor(() => + expect(getByTestId(`${DATA_TEST_SUBJ_SCREEN_READER_MESSAGE}-${field.id}`)).toBe( + 'Provide a valid color value' + ) + ); + }); + + it('should clear the unsaved value if the new value matches the saved value', () => { + const setting = settings.string; + const field = getFieldDefinition({ + id: setting.name || setting.type, + setting: { + ...setting, + userValue: 'saved value', + }, + }); + + const unsavedChange = { + type: 'string' as const, + unsavedValue: 'new value', + }; + + const { getByTestId } = render( + wrap( + + ) + ); + + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${field.id}`); + fireEvent.change(input, { target: { value: field.savedValue } }); + expect(handleChange).toHaveBeenCalledWith(field.id, undefined); + }); + + it('should clear the current image when Change Image is clicked', () => { + const setting = settings.image; + + const field = getFieldDefinition({ + id: setting.name || setting.type, + setting: { + ...setting, + userValue: userInputValues.image, + }, + }); + + const { getByTestId, getByAltText } = render( + wrap() + ); + + const link = getByTestId(`${DATA_TEST_SUBJ_CHANGE_LINK_PREFIX}-${field.id}`); + fireEvent.click(link); + waitFor(() => expect(getByAltText(field.id)).not.toBeInTheDocument()); + }); + + it('should clear the unsaved image when Change Image is clicked', () => { + const setting = settings.image; + + const field = getFieldDefinition({ + id: setting.name || setting.type, + setting: { + ...setting, + }, + }); + + const { getByTestId, getByAltText } = render( + wrap( + + ) + ); + + const link = getByTestId(`${DATA_TEST_SUBJ_CHANGE_LINK_PREFIX}-${field.id}`); + fireEvent.click(link); + waitFor(() => expect(getByAltText(field.id)).not.toBeInTheDocument()); + }); +}); diff --git a/packages/kbn-management/settings/components/field_row/field_row.tsx b/packages/kbn-management/settings/components/field_row/field_row.tsx new file mode 100644 index 0000000000000..9058511c955d1 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/field_row.tsx @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useRef } from 'react'; + +import { + EuiScreenReaderOnly, + EuiDescribedFormGroup, + EuiFormRow, + EuiErrorBoundary, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { + FieldDefinition, + ResetInputRef, + SettingType, + UnsavedFieldChange, + OnChangeFn, +} from '@kbn/management-settings-types'; +import { isImageFieldDefinition } from '@kbn/management-settings-field-definition'; +import { FieldInput } from '@kbn/management-settings-components-field-input'; + +import { hasUnsavedChange } from '@kbn/management-settings-utilities'; +import { FieldDescription } from './description'; +import { FieldTitle } from './title'; +import { useFieldStyles } from './field_row.styles'; +import { RowOnChangeFn } from './types'; +import { FieldInputFooter } from './footer'; + +export const DATA_TEST_SUBJ_SCREEN_READER_MESSAGE = 'fieldRowScreenReaderMessage'; + +type Definition = Pick< + FieldDefinition, + | 'ariaAttributes' + | 'defaultValue' + | 'defaultValueDisplay' + | 'displayName' + | 'groupId' + | 'id' + | 'isCustom' + | 'isDefaultValue' + | 'isOverridden' + | 'name' + | 'savedValue' + | 'type' + | 'unsavedFieldId' +>; + +/** + * Props for a {@link FieldRow} component. + */ +export interface FieldRowProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Definition; + /** True if saving settings is enabled, false otherwise. */ + isSavingEnabled: boolean; + /** The {@link OnChangeFn} handler. */ + onChange: RowOnChangeFn; + /** + * The onClear handler, if a value is cleared to an empty or default state. + * @param id The id relating to the field to clear. + */ + onClear?: (id: string) => void; + /** The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. */ + unsavedChange?: UnsavedFieldChange; +} + +/** + * Component for displaying a {@link FieldDefinition} in a form row, using a {@link FieldInput}. + * @param props The {@link FieldRowProps} for the {@link FieldRow} component. + */ +export const FieldRow = (props: FieldRowProps) => { + const { isSavingEnabled, onChange: onChangeProp, field, unsavedChange } = props; + const { id, groupId, isOverridden, unsavedFieldId } = field; + const { cssFieldFormGroup } = useFieldStyles({ + field, + unsavedChange, + }); + + // Create a ref for those input fields that use a `reset` handle. + const ref = useRef(null); + + // Route any change to the `onChange` handler, along with the field id. + const onChange: OnChangeFn = (update) => { + onChangeProp(id, update); + }; + + const onReset = () => { + ref.current?.reset(); + + const update = { type: field.type, unsavedValue: field.defaultValue }; + + if (hasUnsavedChange(field, update)) { + onChange(update); + } else { + onChange(); + } + }; + + const onClear = () => { + if (ref.current) { + ref.current.reset(); + } + + // Indicate a field is being cleared for a new value by setting its unchanged + // value to`undefined`. Currently, this only applies to `image` fields. + if (field.savedValue !== undefined && field.savedValue !== null) { + onChange({ type: field.type, unsavedValue: undefined }); + } else { + onChange(); + } + }; + + const title = ; + const description = ; + const error = unsavedChange?.error; + const isInvalid = unsavedChange?.isInvalid; + let unsavedScreenReaderMessage = null; + + // Provide a screen-reader only message if there's an unsaved change. + if (unsavedChange) { + unsavedScreenReaderMessage = ( + +

    + {error + ? error + : i18n.translate('management.settings.field.settingIsUnsaved', { + defaultMessage: 'Setting is currently not saved.', + })} +

    +
    + ); + } + + return ( + + + + } + {...{ isInvalid, error }} + > + <> + + {unsavedScreenReaderMessage} + + + + + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/footer/change_image_link.test.tsx b/packages/kbn-management/settings/components/field_row/footer/change_image_link.test.tsx new file mode 100644 index 0000000000000..201942bc89d44 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/change_image_link.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { ChangeImageLink, type ChangeImageLinkProps } from './change_image_link'; +import { wrap } from '../mocks'; + +const IMAGE = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC +`; + +describe('ChangeImageLink', () => { + const defaultProps: ChangeImageLinkProps = { + field: { + name: 'test', + type: 'image', + ariaAttributes: { + ariaLabel: 'test', + }, + isOverridden: false, + savedValue: null, + }, + unsavedChange: undefined, + onClear: jest.fn(), + }; + + it('does not render with no saved value and no unsaved change', () => { + const { container } = render(wrap()); + expect(container.firstChild).toBeNull(); + }); + + it('renders with a saved value and no unsaved change', () => { + const { container } = render( + wrap( + + ) + ); + expect(container.firstChild).not.toBeNull(); + }); + + it('does not render if there is a saved value and the unsaved value is undefined', () => { + const { container } = render( + wrap( + + ) + ); + expect(container.firstChild).toBeNull(); + }); + + it('renders when there is an unsaved change', () => { + const { container } = render( + wrap( + + ) + ); + expect(container.firstChild).not.toBeNull(); + }); + + it('renders nothing if the unsaved change value is undefined', () => { + const { container } = render( + wrap( + + ) + ); + expect(container.firstChild).toBeNull(); + }); + + it('renders an aria-label', () => { + const { getByLabelText } = render( + wrap( + + ) + ); + const link = getByLabelText('Change test'); + expect(link).not.toBeNull(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_row/footer/change_image_link.tsx b/packages/kbn-management/settings/components/field_row/footer/change_image_link.tsx new file mode 100644 index 0000000000000..78fedea1c8644 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/change_image_link.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { FieldDefinition, SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { hasUnsavedChange } from '@kbn/management-settings-utilities'; + +export const DATA_TEST_SUBJ_CHANGE_LINK_PREFIX = 'management-settings-change-image'; + +type Field = Pick< + FieldDefinition, + 'id' | 'type' | 'savedValue' | 'ariaAttributes' | 'isOverridden' +>; + +/** + * Props for a {@link ChangeImageLink} component. + */ +export interface ChangeImageLinkProps { + /** The {@link ImageFieldDefinition} corresponding the setting. */ + field: Field; + unsavedChange?: UnsavedFieldChange; + onClear: () => void; +} + +/** + * Component for rendering a link to change the image in a {@link FieldRow} of + * an {@link ImageFieldDefinition}. + */ +export const ChangeImageLink = ({ + field, + onClear, + unsavedChange, +}: ChangeImageLinkProps) => { + if (field.type !== 'image') { + return null; + } + + const { + ariaAttributes: { ariaLabel }, + isOverridden, + savedValue, + } = field; + + if ( + // If the field is overridden... + isOverridden || + // ... or if there's a saved value but no unsaved change... + (!savedValue && !hasUnsavedChange(field, unsavedChange)) || + // ... or if there's a saved value and an undefined unsaved value... + (savedValue && !!unsavedChange && unsavedChange.unsavedValue === undefined) + ) { + // ...don't render the link. + return null; + } + + // Use the type-guards on the definition and unsaved change. + return ( + + onClear()} + data-test-subj={`${DATA_TEST_SUBJ_CHANGE_LINK_PREFIX}-${field.id}`} + > + + + + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/footer/index.ts b/packages/kbn-management/settings/components/field_row/footer/index.ts new file mode 100644 index 0000000000000..0322c72010380 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldInputFooter, type FieldInputFooterProps } from './input_footer'; +export { InputResetLink, type FieldResetLinkProps } from './reset_link'; diff --git a/packages/kbn-management/settings/components/field_row/footer/input_footer.styles.ts b/packages/kbn-management/settings/components/field_row/footer/input_footer.styles.ts new file mode 100644 index 0000000000000..81db664b55bf0 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/input_footer.styles.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; + +/** + * A React hook that provides stateful `css` classes for the {@link FieldRow} component. + */ +export const useInputFooterStyles = () => { + const { + euiTheme: { size }, + } = useEuiTheme(); + + return { + footerCSS: css` + margin-top: ${size.s}; + > * { + margin-right: ${size.s}; + } + `, + }; +}; diff --git a/packages/kbn-management/settings/components/field_row/footer/input_footer.tsx b/packages/kbn-management/settings/components/field_row/footer/input_footer.tsx new file mode 100644 index 0000000000000..cd931ad3ae55c --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/input_footer.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import type { + FieldDefinition, + SettingType, + UnsavedFieldChange, +} from '@kbn/management-settings-types'; + +import { InputResetLink } from './reset_link'; +import { ChangeImageLink } from './change_image_link'; +import { FieldOverriddenMessage } from './overridden_message'; +import { useInputFooterStyles } from './input_footer.styles'; + +export const DATA_TEST_SUBJ_FOOTER_PREFIX = 'field-row-input-footer'; + +type Field = Pick< + FieldDefinition, + 'id' | 'name' | 'isOverridden' | 'type' | 'ariaAttributes' | 'isDefaultValue' +>; + +/** + * Props for a {@link FieldInputFooter} component. + */ +export interface FieldInputFooterProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Field; + /** The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. */ + unsavedChange?: UnsavedFieldChange; + /** A handler for clearing, rather than resetting the field. */ + onClear: () => void; + /** A handler for when a field is reset to its default or saved value. */ + onReset: () => void; + /** True if saving this setting is enabled, false otherwise. */ + isSavingEnabled: boolean; +} + +export const FieldInputFooter = ({ + field, + isSavingEnabled, + onClear, + onReset, + unsavedChange, +}: FieldInputFooterProps) => { + const { footerCSS } = useInputFooterStyles(); + + if (field.isOverridden) { + return ; + } + + if (isSavingEnabled) { + return ( + + + + + ); + } + + return null; +}; diff --git a/packages/kbn-management/settings/components/field_row/footer/overridden_message.test.tsx b/packages/kbn-management/settings/components/field_row/footer/overridden_message.test.tsx new file mode 100644 index 0000000000000..ab894cf013174 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/overridden_message.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { FieldOverriddenMessage } from './overridden_message'; +import { FieldDefinition } from '@kbn/management-settings-types'; + +describe('FieldOverriddenMessage', () => { + const defaultProps = { + field: { + name: 'test', + type: 'string', + isOverridden: false, + } as FieldDefinition<'string'>, + }; + + it('renders without errors', () => { + const { container } = render( + + ); + expect(container).toBeInTheDocument(); + }); + + it('renders nothing if the field is not overridden', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_row/footer/overridden_message.tsx b/packages/kbn-management/settings/components/field_row/footer/overridden_message.tsx new file mode 100644 index 0000000000000..bff68afb370c2 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/overridden_message.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { FieldDefinition, SettingType } from '@kbn/management-settings-types'; + +type Field = Pick, 'id' | 'isOverridden' | 'name'>; + +export const DATA_TEST_SUBJ_OVERRIDDEN_PREFIX = 'field-row-input-overridden-message'; + +/** + * Props for a {@link FieldOverriddenMessage} component. + */ +export interface FieldOverriddenMessageProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Field; +} + +export const FieldOverriddenMessage = ({ + field, +}: FieldOverriddenMessageProps) => { + if (!field.isOverridden) { + return null; + } + + return ( + + + + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/footer/reset_link.test.tsx b/packages/kbn-management/settings/components/field_row/footer/reset_link.test.tsx new file mode 100644 index 0000000000000..2704e9e33d743 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/reset_link.test.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; + +import { SettingType } from '@kbn/management-settings-types'; + +import { wrap } from '../mocks'; +import { InputResetLink, InputResetLinkProps } from './reset_link'; + +describe('InputResetLink', () => { + const defaultProps: InputResetLinkProps = { + field: { + type: 'string', + id: 'test', + isOverridden: false, + ariaAttributes: { + ariaLabel: 'Test', + }, + defaultValue: 'default', + }, + onReset: jest.fn(), + }; + + it('renders nothing if the field is already at its default value', () => { + const { container } = render(wrap()); + expect(container.firstChild).toBeNull(); + }); + + it('renders a link to reset the field if there is a different saved value', () => { + const { getByText } = render( + wrap( + + ) + ); + const link = getByText('Reset to default'); + expect(link).toBeInTheDocument(); + }); + + it('renders a link to reset the field if there is a different unsaved value', () => { + const { getByText } = render( + wrap( + + ) + ); + const link = getByText('Reset to default'); + expect(link).toBeInTheDocument(); + }); + + it('renders nothing if there is a different saved value but the same unsaved value', () => { + const { container } = render( + wrap( + + ) + ); + expect(container.firstChild).toBeNull(); + }); + + it('calls the onReset prop when the link is clicked', () => { + const { getByText } = render( + wrap( + + ) + ); + const link = getByText('Reset to default'); + fireEvent.click(link); + expect(defaultProps.onReset).toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-management/settings/components/field_row/footer/reset_link.tsx b/packages/kbn-management/settings/components/field_row/footer/reset_link.tsx new file mode 100644 index 0000000000000..35a34350e0be8 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/footer/reset_link.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { + FieldDefinition, + SettingType, + UnsavedFieldChange, +} from '@kbn/management-settings-types'; +import { isFieldDefaultValue } from '@kbn/management-settings-utilities'; + +/** + * Props for a {@link InputResetLink} component. + */ +export interface InputResetLinkProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Pick< + FieldDefinition, + 'ariaAttributes' | 'id' | 'savedValue' | 'isOverridden' | 'defaultValue' | 'type' + >; + /** A handler for when a field is reset to its default or saved value. */ + onReset: () => void; + /** A change to the current field, if any. */ + unsavedChange?: UnsavedFieldChange; +} + +export const DATA_TEST_SUBJ_RESET_PREFIX = 'management-settings-resetField'; +/** + * Component for rendering a link to reset a {@link FieldDefinition} to its default + * or saved value. + */ +export const InputResetLink = ({ + onReset: onClick, + field, + unsavedChange, +}: InputResetLinkProps) => { + if (isFieldDefaultValue(field, unsavedChange) || field.isOverridden) { + return null; + } + + const { + id, + ariaAttributes: { ariaLabel }, + } = field; + + return ( + + + + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/index.ts b/packages/kbn-management/settings/components/field_row/index.ts new file mode 100644 index 0000000000000..98c64f1cd494d --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldRow, type FieldRowProps as FieldProps } from './field_row'; +export { FieldRowProvider, FieldRowKibanaProvider, type FieldRowProviderProps } from './services'; +export type { + FieldRowServices, + FieldRowKibanaDependencies, + RowOnChangeFn, + KibanaDependencies, + Services, +} from './types'; diff --git a/packages/kbn-management/settings/components/field_row/kibana.jsonc b/packages/kbn-management/settings/components/field_row/kibana.jsonc new file mode 100644 index 0000000000000..f931d719df741 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-components-field-row", + "owner": "@elastic/platform-deployment-management" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_row/mocks/context.tsx b/packages/kbn-management/settings/components/field_row/mocks/context.tsx new file mode 100644 index 0000000000000..f8109b6dd08b1 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/mocks/context.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { ReactChild } from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; + +import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root'; +import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { I18nStart } from '@kbn/core-i18n-browser'; + +import { createFieldInputServicesMock } from '@kbn/management-settings-components-field-input/mocks'; +import { FieldInputServices } from '@kbn/management-settings-components-field-input/mocks'; +import { FieldRowProvider } from '../services'; +import { FieldRowServices } from '../types'; + +const createRootMock = () => { + const i18n: I18nStart = { + Context: ({ children }) => {children}, + }; + const theme = themeServiceMock.createStartContract(); + return { + i18n, + theme, + }; +}; + +export const createFieldRowServicesMock = (): FieldRowServices => ({ + ...createFieldInputServicesMock(), + links: { deprecationKey: 'link/to/deprecation/docs' }, +}); + +export const TestWrapper = ({ + children, + services = createFieldRowServicesMock(), +}: { + children: ReactChild; + services?: FieldRowServices; +}) => { + return ( + + {children} + + ); +}; + +export const wrap = ( + component: JSX.Element, + services: FieldInputServices = createFieldRowServicesMock() +) => {component}; diff --git a/packages/kbn-management/settings/components/field_row/mocks/index.ts b/packages/kbn-management/settings/components/field_row/mocks/index.ts new file mode 100644 index 0000000000000..2fbe57cd37108 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/mocks/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TestWrapper, createFieldRowServicesMock, wrap } from './context'; diff --git a/packages/kbn-management/settings/components/field_row/package.json b/packages/kbn-management/settings/components/field_row/package.json new file mode 100644 index 0000000000000..aa5daf8a30cd7 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-components-field-row", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_row/services.tsx b/packages/kbn-management/settings/components/field_row/services.tsx new file mode 100644 index 0000000000000..e138307979db1 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/services.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + FieldInputKibanaProvider, + FieldInputProvider, +} from '@kbn/management-settings-components-field-input/services'; +import React, { FC, useContext } from 'react'; + +import type { FieldRowServices, FieldRowKibanaDependencies, Services } from './types'; + +const FieldRowContext = React.createContext(null); + +/** + * Props for {@link FieldRowProvider}. + */ +export interface FieldRowProviderProps extends FieldRowServices { + children: React.ReactNode; +} + +/** + * React Provider that provides services to a {@link FieldRow} component and its dependents.\ + */ +export const FieldRowProvider = ({ children, ...services }: FieldRowProviderProps) => { + // Typescript types are widened to accept more than what is needed. Take only what is necessary + // so the context remains clean. + const { links, showDanger } = services; + + return ( + + {children} + + ); +}; + +/** + * Kibana-specific Provider that maps Kibana plugins and services to a {@link FieldRowProvider}. + */ +export const FieldRowKibanaProvider: FC = ({ + children, + docLinks, + toasts, +}) => { + return ( + + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + */ +export const useServices = () => { + const context = useContext(FieldRowContext); + + if (!context) { + throw new Error( + 'FieldRowContext is missing. Ensure your component or React root is wrapped with FieldRowProvider.' + ); + } + + return context; +}; diff --git a/packages/kbn-management/settings/components/field_row/setup_tests.ts b/packages/kbn-management/settings/components/field_row/setup_tests.ts new file mode 100644 index 0000000000000..8d1acb9232934 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/setup_tests.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; diff --git a/packages/kbn-management/settings/components/field_row/title/icon_custom.tsx b/packages/kbn-management/settings/components/field_row/title/icon_custom.tsx new file mode 100644 index 0000000000000..d773eb136b3b0 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/title/icon_custom.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { EuiIconTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; + +/** + * Props for a {@link FieldTitle} component. + */ +export interface TitleProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Pick, 'isCustom'>; +} + +/** + * + */ +export const FieldTitleCustomIcon = ({ field }: TitleProps) => { + if (!field.isCustom) { + return null; + } + + return ( + + } + /> + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/title/icon_unsaved.tsx b/packages/kbn-management/settings/components/field_row/title/icon_unsaved.tsx new file mode 100644 index 0000000000000..bf44a0686d60e --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/title/icon_unsaved.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { EuiIconTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FieldDefinition, UnsavedFieldChange, SettingType } from '@kbn/management-settings-types'; +import { hasUnsavedChange } from '@kbn/management-settings-utilities'; + +/** + * Props for a {@link FieldTitle} component. + */ +export interface TitleProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Pick, 'id' | 'type' | 'isOverridden' | 'savedValue'>; + /** The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. */ + unsavedChange?: UnsavedFieldChange; +} + +/** + * + */ +export const FieldTitleUnsavedIcon = ({ + field, + unsavedChange, +}: TitleProps) => { + if (!unsavedChange || !hasUnsavedChange(field, unsavedChange)) { + return null; + } + + const { isInvalid } = unsavedChange; + + const invalidLabel = i18n.translate('management.settings.field.invalidIconLabel', { + defaultMessage: 'Invalid', + }); + + const unsavedLabel = i18n.translate('management.settings.field.unsavedIconLabel', { + defaultMessage: 'Unsaved', + }); + + const unsavedIconLabel = unsavedChange.isInvalid ? invalidLabel : unsavedLabel; + + return ( + + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/title/index.ts b/packages/kbn-management/settings/components/field_row/title/index.ts new file mode 100644 index 0000000000000..f2a757252e699 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/title/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldTitle, type TitleProps } from './title'; diff --git a/packages/kbn-management/settings/components/field_row/title/title.tsx b/packages/kbn-management/settings/components/field_row/title/title.tsx new file mode 100644 index 0000000000000..dded57a3d7a87 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/title/title.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { Interpolation, Theme } from '@emotion/react'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { FieldDefinition, UnsavedFieldChange, SettingType } from '@kbn/management-settings-types'; + +import { useFieldStyles } from '../field_row.styles'; +import { FieldTitleCustomIcon } from './icon_custom'; +import { FieldTitleUnsavedIcon } from './icon_unsaved'; + +/** + * Props for a {@link FieldTitle} component. + */ +export interface TitleProps { + /** The {@link FieldDefinition} corresponding the setting. */ + field: Pick< + FieldDefinition, + 'displayName' | 'savedValue' | 'isCustom' | 'id' | 'type' | 'isOverridden' + >; + /** Emotion-based `css` for the root React element. */ + css?: Interpolation; + /** Classname for the root React element. */ + className?: string; + /** The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. */ + unsavedChange?: UnsavedFieldChange; +} + +/** + * Component for displaying the `displayName` and status of a {@link FieldDefinition} in + * the {@link FieldRow}. + */ +export const FieldTitle = ({ + field, + unsavedChange, + ...props +}: TitleProps) => { + const { cssFieldTitle } = useFieldStyles({ + field, + unsavedChange, + }); + + return ( + + +

    {field.displayName}

    +
    + + + + + + +
    + ); +}; diff --git a/packages/kbn-management/settings/components/field_row/tsconfig.json b/packages/kbn-management/settings/components/field_row/tsconfig.json new file mode 100644 index 0000000000000..173fbd57d08b6 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/tsconfig.json @@ -0,0 +1,33 @@ +{ + "extends": "../../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@kbn/ambient-ui-types", + "@kbn/ambient-storybook-types", + "@emotion/react/types/css-prop" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-types", + "@kbn/management-settings-field-definition", + "@kbn/i18n", + "@kbn/i18n-react", + "@kbn/management-settings-utilities", + "@kbn/management-settings-components-field-input", + "@kbn/core-doc-links-browser", + "@kbn/react-kibana-context-root", + "@kbn/core-theme-browser-mocks", + "@kbn/core-i18n-browser", + ] +} diff --git a/packages/kbn-management/settings/components/field_row/types.ts b/packages/kbn-management/settings/components/field_row/types.ts new file mode 100644 index 0000000000000..d353cd91fba49 --- /dev/null +++ b/packages/kbn-management/settings/components/field_row/types.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DocLinksStart } from '@kbn/core-doc-links-browser'; + +import type { + FieldInputServices, + FieldInputKibanaDependencies, +} from '@kbn/management-settings-components-field-input'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; + +/** + * Contextual services used by a {@link FieldRow} component. + */ +export interface Services { + links: { [key: string]: string }; +} + +/** + * Contextual services used by a {@link FieldRow} component and its dependents. + */ +export type FieldRowServices = FieldInputServices & Services; + +/** + * An interface containing a collection of Kibana plugins and services required to + * render a {@link FieldRow} component. + */ +export interface KibanaDependencies { + docLinks: { + links: { + management: DocLinksStart['links']['management']; + }; + }; +} + +/** + * An interface containing a collection of Kibana plugins and services required to + * render a {@link FieldRow} component and its dependents. + */ +export type FieldRowKibanaDependencies = KibanaDependencies & FieldInputKibanaDependencies; + +/** + * An `onChange` handler for a {@link FieldRow} component. + * @param id A unique id corresponding to the particular setting being changed. + * @param change The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. + */ +export type RowOnChangeFn = ( + id: string, + change?: UnsavedFieldChange +) => void; diff --git a/packages/kbn-management/settings/field_definition/README.mdx b/packages/kbn-management/settings/field_definition/README.mdx new file mode 100644 index 0000000000000..c26b5d850358c --- /dev/null +++ b/packages/kbn-management/settings/field_definition/README.mdx @@ -0,0 +1,14 @@ +--- +id: management/settings/fieldDefinition +slug: /management/settings/field-definition +title: Management Settings Field Definition +description: A package containing utilities for creating and examining Field Definitions from Advanced Settings. +tags: ['management', 'settings'] +date: 2023-08-31 +--- + +## Description + +This package contains utilities for creating and examining Field Definitions from Advanced Settings. + +Since a raw `UiSetting` is not type-safe and can be difficult to work with in the UX, this `FieldDefinition` provides a type-safe abstraction over the raw `UiSetting` _and_ provides additional UI-centric information derived from the setting. diff --git a/packages/kbn-management/settings/field_definition/get_definition.ts b/packages/kbn-management/settings/field_definition/get_definition.ts new file mode 100644 index 0000000000000..8e7204b137693 --- /dev/null +++ b/packages/kbn-management/settings/field_definition/get_definition.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import words from 'lodash/words'; + +import { Query } from '@elastic/eui'; +import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; +import { UiSettingMetadata } from '@kbn/management-settings-types'; +import { isSettingDefaultValue } from '@kbn/management-settings-utilities'; + +/** + * The portion of the setting name that defines the category of the setting. + */ +export const CATEGORY_FIELD = 'category'; + +/** + * The default category for a setting, if not supplied. + */ +export const DEFAULT_CATEGORY = 'general'; + +const mapWords = (name?: string): string => + words(name ?? '') + .map((word) => word.toLowerCase()) + .join(' '); + +/** + * Derive the aria-label for a given setting based on its name and category. + */ +const getAriaLabel = (name: string = '') => { + if (!name) { + return ''; + } + + const query = Query.parse(name); + + if (query.hasOrFieldClause(CATEGORY_FIELD)) { + const categories = query.getOrFieldClause(CATEGORY_FIELD); + const termValue = mapWords(query.removeOrFieldClauses(CATEGORY_FIELD).text); + + if (!categories || !Array.isArray(categories.value)) { + return termValue; + } + + let categoriesQuery = Query.parse(''); + categories.value.forEach((v) => { + categoriesQuery = categoriesQuery.addOrFieldValue(CATEGORY_FIELD, v); + }); + + return `${termValue} ${categoriesQuery.text}`; + } + + return mapWords(name); +}; + +/** + * Parameters for converting a {@link UiSettingMetadata} object into a {@link FieldDefinition} + * for use in the UI. + * @internal + */ +interface GetDefinitionParams { + /** The id of the field. */ + id: string; + /** The source setting from Kibana. */ + setting: UiSettingMetadata; + /** Optional parameters */ + params?: { + /** True if the setting it custom, false otherwise */ + isCustom?: boolean; + /** True if the setting is overridden in Kibana, false otherwise. */ + isOverridden?: boolean; + }; +} + +/** + * Create a {@link FieldDefinition} from a {@link UiSettingMetadata} object for use + * in the UI. + * + * @param parameters The {@link GetDefinitionParams} for creating the {@link FieldDefinition}. + */ +export const getFieldDefinition = ( + parameters: GetDefinitionParams +): FieldDefinition => { + const { id, setting, params = { isCustom: false, isOverridden: false } } = parameters; + + const { + category, + deprecation, + description, + metric, + name, + optionLabels, + options: optionValues, + order, + readonly, + requiresPageReload, + type, + userValue: savedValue, + value: defaultValue, + } = setting; + + const { isCustom, isOverridden } = params; + const categories = category && category.length ? category : [DEFAULT_CATEGORY]; + + const options = { + values: optionValues || [], + labels: optionLabels || {}, + }; + + const defaultValueDisplay = + defaultValue === undefined || defaultValue === null || defaultValue === '' + ? 'null' + : String(defaultValue); + + const definition: FieldDefinition = { + ariaAttributes: { + ariaLabel: name || getAriaLabel(name), + // ariaDescribedBy: unsavedChange.value ? `${groupId} ${unsavedId}` : undefined, + }, + categories, + defaultValue, + defaultValueDisplay, + deprecation, + description, + displayName: name || id, + groupId: `${name || id}-group`, + id, + isCustom: isCustom || false, + isDefaultValue: isSettingDefaultValue(setting), + isOverridden: isOverridden || false, + isReadOnly: !!readonly, + metric, + name: name || id, + options, + order, + requiresPageReload: !!requiresPageReload, + savedValue, + type, + unsavedFieldId: `${id}-unsaved`, + }; + + // TODO: clintandrewhall - add validation (e.g. `select` contains non-empty `options`) + return definition; +}; diff --git a/packages/kbn-management/settings/field_definition/get_definitions.ts b/packages/kbn-management/settings/field_definition/get_definitions.ts new file mode 100644 index 0000000000000..c42613c8c2ce1 --- /dev/null +++ b/packages/kbn-management/settings/field_definition/get_definitions.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { FieldDefinition, SettingType, UiSettingMetadata } from '@kbn/management-settings-types'; +import { getFieldDefinition } from './get_definition'; + +/** + * Convenience function to convert settings taken from a UiSettingsClient into + * {@link FieldDefinition} objects. + * + * @param settings The settings retreived from the UiSettingsClient. + * @param client The client itself, used to determine if a setting is custom or overridden. + * @returns An array of {@link FieldDefinition} objects. + */ +export const getFieldDefinitions = ( + settings: Record>, + client: IUiSettingsClient +): Array> => + Object.entries(settings).map(([id, setting]) => + getFieldDefinition({ + id, + setting, + params: { isCustom: client.isCustom(id), isOverridden: client.isOverridden(id) }, + }) + ); diff --git a/packages/kbn-management/settings/field_definition/index.ts b/packages/kbn-management/settings/field_definition/index.ts new file mode 100644 index 0000000000000..0c39e349ec3cf --- /dev/null +++ b/packages/kbn-management/settings/field_definition/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + isArrayFieldDefinition, + isArrayFieldUnsavedChange, + isBooleanFieldDefinition, + isBooleanFieldUnsavedChange, + isColorFieldDefinition, + isColorFieldUnsavedChange, + isImageFieldDefinition, + isImageFieldUnsavedChange, + isJsonFieldDefinition, + isJsonFieldUnsavedChange, + isMarkdownFieldDefinition, + isMarkdownFieldUnsavedChange, + isNumberFieldDefinition, + isNumberFieldUnsavedChange, + isSelectFieldDefinition, + isSelectFieldUnsavedChange, + isStringFieldDefinition, + isStringFieldUnsavedChange, + isUndefinedFieldDefinition, + isUndefinedFieldUnsavedChange, +} from './is'; + +export { getFieldDefinition } from './get_definition'; +export { getFieldDefinitions } from './get_definitions'; diff --git a/packages/kbn-management/settings/field_definition/is/field_definition.ts b/packages/kbn-management/settings/field_definition/is/field_definition.ts new file mode 100644 index 0000000000000..52c6e83468177 --- /dev/null +++ b/packages/kbn-management/settings/field_definition/is/field_definition.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// This file is enormous and looks a bit excessive, but it's actually a collection +// of type guards. +// +// In the past, the UI would key off of the `type` property of a UISetting to do +// its work. This was not at all type-safe, and it was easy to make mistakes. +// +// These type guards narrow a given {@link FieldDefinition} to its correct Typescript +// interface. What's interesting is that these guards compile to checking the `type` +// property of the object-- just as we did before-- but with the benefit of Typescript. + +import { + ArrayFieldDefinition, + BooleanFieldDefinition, + ColorFieldDefinition, + FieldDefinition, + ImageFieldDefinition, + JsonFieldDefinition, + MarkdownFieldDefinition, + NumberFieldDefinition, + SelectFieldDefinition, + SettingType, + StringFieldDefinition, + UndefinedFieldDefinition, +} from '@kbn/management-settings-types'; + +/** Simplifed type for a {@link FieldDefinition} */ +type Definition = Pick, 'type'>; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link ArrayFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isArrayFieldDefinition = (d: Definition): d is ArrayFieldDefinition => + d.type === 'array'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link BooleanFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isBooleanFieldDefinition = (d: Definition): d is BooleanFieldDefinition => + d.type === 'boolean'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link ColorFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isColorFieldDefinition = (d: Definition): d is ColorFieldDefinition => + d.type === 'color'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link ImageFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isImageFieldDefinition = (d: Definition): d is ImageFieldDefinition => + d.type === 'image'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link JsonFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isJsonFieldDefinition = (d: Definition): d is JsonFieldDefinition => d.type === 'json'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link MarkdownFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isMarkdownFieldDefinition = (d: Definition): d is MarkdownFieldDefinition => + d.type === 'markdown'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link NumberFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isNumberFieldDefinition = (d: Definition): d is NumberFieldDefinition => + d.type === 'number'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link SelectFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isSelectFieldDefinition = (d: Definition): d is SelectFieldDefinition => + d.type === 'select'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link StringFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isStringFieldDefinition = (d: Definition): d is StringFieldDefinition => + d.type === 'string'; + +/** + * Returns `true` if the given {@link FieldDefinition} is an {@link UndefinedFieldDefinition}, + * `false` otherwise. + * @param d The {@link FieldDefinition} to check. + */ +export const isUndefinedFieldDefinition = (d: Definition): d is UndefinedFieldDefinition => + d.type === 'undefined'; diff --git a/packages/kbn-management/settings/field_definition/is/index.ts b/packages/kbn-management/settings/field_definition/is/index.ts new file mode 100644 index 0000000000000..ad5eb46cd3f53 --- /dev/null +++ b/packages/kbn-management/settings/field_definition/is/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + isArrayFieldUnsavedChange, + isBooleanFieldUnsavedChange, + isColorFieldUnsavedChange, + isImageFieldUnsavedChange, + isJsonFieldUnsavedChange, + isMarkdownFieldUnsavedChange, + isNumberFieldUnsavedChange, + isSelectFieldUnsavedChange, + isStringFieldUnsavedChange, + isUndefinedFieldUnsavedChange, +} from './unsaved_change'; + +export { + isArrayFieldDefinition, + isBooleanFieldDefinition, + isColorFieldDefinition, + isImageFieldDefinition, + isJsonFieldDefinition, + isMarkdownFieldDefinition, + isNumberFieldDefinition, + isSelectFieldDefinition, + isStringFieldDefinition, + isUndefinedFieldDefinition, +} from './field_definition'; diff --git a/packages/kbn-management/settings/field_definition/is/unsaved_change.ts b/packages/kbn-management/settings/field_definition/is/unsaved_change.ts new file mode 100644 index 0000000000000..6af63db17e36a --- /dev/null +++ b/packages/kbn-management/settings/field_definition/is/unsaved_change.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// This file is enormous and looks a bit excessive, but it's actually a collection +// of type guards. +// +// In the past, the UI would key off of the `type` property of a UISetting to do +// its work. This was not at all type-safe, and it was easy to make mistakes. +// +// These type guards narrow a given {@link UnsavedFieldChange} to its correct Typescript +// interface. What's interesting is that these guards compile to checking the `type` +// property of the object-- just as we did before-- but with the benefit of Typescript. + +import { + ArrayUnsavedFieldChange, + BooleanUnsavedFieldChange, + ColorUnsavedFieldChange, + ImageUnsavedFieldChange, + JsonUnsavedFieldChange, + MarkdownUnsavedFieldChange, + NumberUnsavedFieldChange, + SelectUnsavedFieldChange, + StringUnsavedFieldChange, + UndefinedUnsavedFieldChange, + SettingType, + UnsavedFieldChange, +} from '@kbn/management-settings-types'; + +/** Simplifed type for a {@link UnsavedFieldChange} */ +type Change = UnsavedFieldChange; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link ArrayUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isArrayFieldUnsavedChange = (c?: Change): c is ArrayUnsavedFieldChange => + !c || c.type === undefined || c.type === 'array'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link BooleanUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isBooleanFieldUnsavedChange = (c?: Change): c is BooleanUnsavedFieldChange => + !c || c.type === undefined || c.type === 'boolean'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link ColorUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isColorFieldUnsavedChange = (c?: Change): c is ColorUnsavedFieldChange => + !c || c.type === undefined || c.type === 'color'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link ImageUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isImageFieldUnsavedChange = (c?: Change): c is ImageUnsavedFieldChange => + !c || c.type === undefined || c.type === 'image'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link JsonUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isJsonFieldUnsavedChange = (c?: Change): c is JsonUnsavedFieldChange => + !c || c.type === undefined || c.type === 'json'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link MarkdownUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isMarkdownFieldUnsavedChange = (c?: Change): c is MarkdownUnsavedFieldChange => + !c || c.type === undefined || c.type === 'markdown'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link NumberUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isNumberFieldUnsavedChange = (c?: Change): c is NumberUnsavedFieldChange => + !c || c.type === undefined || c.type === 'number'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link SelectUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isSelectFieldUnsavedChange = (c?: Change): c is SelectUnsavedFieldChange => + !c || c.type === undefined || c.type === 'select'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link StringUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isStringFieldUnsavedChange = (c?: Change): c is StringUnsavedFieldChange => + !c || c.type === undefined || c.type === 'string'; + +/** + * Returns `true` if the given {@link FieldUnsavedChange} is an {@link UndefinedUnsavedFieldChange}, + * `false` otherwise. + * @param c The {@link FieldUnsavedChange} to check. + */ +export const isUndefinedFieldUnsavedChange = (c?: Change): c is UndefinedUnsavedFieldChange => + !c || c.type === undefined || c.type === 'undefined'; diff --git a/packages/kbn-management/settings/field_definition/kibana.jsonc b/packages/kbn-management/settings/field_definition/kibana.jsonc new file mode 100644 index 0000000000000..43e2f19c5363f --- /dev/null +++ b/packages/kbn-management/settings/field_definition/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-field-definition", + "owner": "@elastic/platform-deployment-management" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/field_definition/package.json b/packages/kbn-management/settings/field_definition/package.json new file mode 100644 index 0000000000000..63a4f90a3ee16 --- /dev/null +++ b/packages/kbn-management/settings/field_definition/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-field-definition", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/field_definition/tsconfig.json b/packages/kbn-management/settings/field_definition/tsconfig.json new file mode 100644 index 0000000000000..26d2dc3afb883 --- /dev/null +++ b/packages/kbn-management/settings/field_definition/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-types", + "@kbn/core-ui-settings-browser", + "@kbn/management-settings-utilities", + ] +} diff --git a/packages/kbn-management/settings/jest.config.js b/packages/kbn-management/settings/jest.config.js new file mode 100644 index 0000000000000..f9df4c078fa83 --- /dev/null +++ b/packages/kbn-management/settings/jest.config.js @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/kbn-management/settings'], + coverageDirectory: '/target/kibana-coverage/jest/packages/kbn-management/settings', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/packages/kbn-management/settings/**/*.{ts,tsx}'], + coveragePathIgnorePatterns: ['__stories__', '.stories.tsx', 'storybook', 'mocks'], +}; diff --git a/packages/kbn-management/settings/section_registry/jest.config.js b/packages/kbn-management/settings/section_registry/jest.config.js deleted file mode 100644 index f183446f77bc6..0000000000000 --- a/packages/kbn-management/settings/section_registry/jest.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../../..', - roots: ['/packages/kbn-management/settings/section_registry'], - coverageDirectory: - '/target/kibana-coverage/jest/packages/kbn-management/settings/section_registry', - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/packages/kbn-management/settings/section_registry/**/*.{ts,tsx}', - ], -}; diff --git a/packages/kbn-management/settings/setting_ids/README.mdx b/packages/kbn-management/settings/setting_ids/README.mdx new file mode 100644 index 0000000000000..e3109e2bcf1b5 --- /dev/null +++ b/packages/kbn-management/settings/setting_ids/README.mdx @@ -0,0 +1,39 @@ +--- +id: kbn-management/settings/ids +slug: /kbn-management/settings/setting_ids/ +title: Setting ID's +description: ID's of all advanced settings. +tags: ['management', 'settings'] +date: 2023-09-04 +--- + +This package contains the id's of all advanced settings. + +When registering an advanced setting, add its id to this package and import it for use in the setting definition: + +**packages/kbn-management/settings/setting_ids/index.ts** + +```ts +export const MY_SETTING_ID = 'mySetting'; +``` + +**src/plugins/my_plugin/server/plugin.ts** + +```ts +import { MY_SETTING_ID } from '@kbn/management-settings-ids'; + +export class MyPlugin implements Plugin { + public setup(core: CoreSetup, dependencies: SetupDependencies) { + core.uiSettings.register({ + [MY_SETTING_ID]: { + name: 'My setting', + value: 10, + schema: schema.number(), + }, + ... + }); + + return {}; + } +} +``` diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts new file mode 100644 index 0000000000000..1069050ccd304 --- /dev/null +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// General settings +export const DISABLE_REQUEST_BATCHING_ID = 'bfetch:disable'; +export const DISABLE_BATCH_COMPRESSION_ID = 'bfetch:disableCompression'; +export const CSV_QUOTE_VALUES_ID = 'csv:quoteValues'; +export const CSV_SEPARATOR_ID = 'csv:separator'; +export const DATE_FORMAT_ID = 'dateFormat'; +export const DATE_FORMAT_DOW_ID = 'dateFormat:dow'; +export const DATE_FORMAT_SCALED_ID = 'dateFormat:scaled'; +export const DATE_FORMAT_TZ_ID = 'dateFormat:tz'; +export const DATE_FORMAT_NANOS_ID = 'dateNanosFormat'; +export const DEFAULT_INDEX_ID = 'defaultIndex'; +export const DEFAULT_ROUTE_ID = 'defaultRoute'; +export const FIELDS_POPULAR_LIMIT_ID = 'fields:popularLimit'; +export const FILE_UPLOAD_MAX_SIZE_ID = 'fileUpload:maxFileSize'; +export const FILTER_EDITOR_SUGGEST_VALUES_ID = 'filterEditor:suggestValues'; +export const FILTERS_PINNED_BY_DEFAULT_ID = 'filters:pinnedByDefault'; +export const FORMAT_BYTES_DEFAULT_PATTERN_ID = 'format:bytes:defaultPattern'; +export const FORMAT_CURRENCY_DEFAULT_PATTERN_ID = 'format:currency:defaultPattern'; +export const FORMAT_DEFAULT_TYPE_MAP_ID = 'format:defaultTypeMap'; +export const FORMAT_NUMBER_DEFAULT_LOCALE_ID = 'format:number:defaultLocale'; +export const FORMAT_NUMBER_DEFAULT_PATTERN_ID = 'format:number:defaultPattern'; +export const FORMAT_PERCENT_DEFAULT_PATTERN_ID = 'format:percent:defaultPattern'; +export const HIDE_ANNOUNCEMENTS_ID = 'hideAnnouncements'; +export const HISTOGRAM_BAR_TARGET_ID = 'histogram:barTarget'; +export const HISTOGRAM_MAX_BARS_ID = 'histogram:maxBars'; +export const HISTORY_LIMIT_ID = 'history:limit'; +export const META_FIELDS_ID = 'metaFields'; +export const METRICS_ALLOW_CHECKING_FOR_FAILED_SHARDS_ID = 'metrics:allowCheckingForFailedShards'; +export const METRICS_ALLOW_STRING_INDICES_ID = 'metrics:allowStringIndices'; +export const METRICS_MAX_BUCKETS_ID = 'metrics:max_buckets'; +export const QUERY_ALLOW_LEADING_WILDCARDS_ID = 'query:allowLeadingWildcards'; +export const QUERY_STRING_OPTIONS_ID = 'query:queryString:options'; +export const SAVED_OBJECTS_LISTING_LIMIT_ID = 'savedObjects:listingLimit'; +export const SAVED_OBJECTS_PER_PAGE_ID = 'savedObjects:perPage'; +export const SEARCH_QUERY_LANGUAGE_ID = 'search:queryLanguage'; +export const SHORT_DOTS_ENABLE_ID = 'shortDots:enable'; +export const SORT_OPTIONS_ID = 'sort:options'; +export const STATE_STORE_IN_SESSION_STORAGE_ID = 'state:storeInSessionStorage'; +export const THEME_DARK_MODE_ID = 'theme:darkMode'; +export const TIMEPICKER_QUICK_RANGES_ID = 'timepicker:quickRanges'; +export const TIMEPICKER_REFRESH_INTERVAL_DEFAULTS_ID = 'timepicker:refreshIntervalDefaults'; +export const TIMEPICKER_TIME_DEFAULTS_ID = 'timepicker:timeDefaults'; + +// Presentation labs settings +export const LABS_CANVAS_BY_VALUE_EMBEDDABLE_ID = 'labs:canvas:byValueEmbeddable'; +export const LABS_CANVAS_ENABLE_UI_ID = 'labs:canvas:enable_ui'; +export const LABS_DASHBOARD_CONTROLS_ID = 'labs:dashboard:dashboardControls'; +export const LABS_DASHBOARD_DEFER_BELOW_FOLD_ID = 'labs:dashboard:deferBelowFold'; +export const LABS_DASHBOARDS_ENABLE_UI_ID = 'labs:dashboard:enable_ui'; + +// Accessibility settings +export const ACCESSIBILITY_DISABLE_ANIMATIONS_ID = 'accessibility:disableAnimations'; + +// Autocomplete settings +export const AUTOCOMPLETE_USE_TIME_RANGE_ID = 'autocomplete:useTimeRange'; +export const AUTOCOMPLETE_VALUE_SUGGESTION_METHOD_ID = 'autocomplete:valueSuggestionMethod'; + +// Banner settings +export const BANNERS_PLACEMENT_ID = 'banners:placement'; +export const BANNERS_TEXT_CONTENT_ID = 'banners:textContent'; +export const BANNERS_TEXT_COLOR_ID = 'banners:textColor'; +export const BANNERS_BACKGROUND_COLOR_ID = 'banners:backgroundColor'; + +// Discover settings +export const CONTEXT_DEFAULT_SIZE_ID = 'context:defaultSize'; +export const CONTEXT_STEP_ID = 'context:step'; +export const CONTEXT_TIE_BREAKER_FIELDS_ID = 'context:tieBreakerFields'; +export const DEFAULT_COLUMNS_ID = 'defaultColumns'; +export const DISCOVER_ENABLE_SQL_ID = 'discover:enableSql'; +export const DISCOVER_MAX_DOC_FIELDS_DISPLAYED_ID = 'discover:maxDocFieldsDisplayed'; +export const DISCOVER_MODIFY_COLUMNS_ON_SWITCH_ID = 'discover:modifyColumnsOnSwitch'; +export const DISCOVER_ROW_HEIGHT_OPTION_ID = 'discover:rowHeightOption'; +export const DISCOVER_SAMPLE_ROWS_PER_PAGE_ID = 'discover:sampleRowsPerPage'; +export const DISCOVER_SAMPLE_SIZE_ID = 'discover:sampleSize'; +export const DISCOVER_SEARCH_FIELDS_FROM_SOURCE_ID = 'discover:searchFieldsFromSource'; +export const DISCOVER_SEARCH_ON_PAGE_LOAD_ID = 'discover:searchOnPageLoad'; +export const DISCOVER_SHOW_FIELD_STATISTICS_ID = 'discover:showFieldStatistics'; +export const DISCOVER_SHOW_LEGACY_FIELD_TOP_VALUES_ID = 'discover:showLegacyFieldTopValues'; +export const DISCOVER_SHOW_MULTI_FIELDS_ID = 'discover:showMultiFields'; +export const DISCOVER_SORT_DEFAULT_ORDER_ID = 'discover:sort:defaultOrder'; +export const DOC_TABLE_HIDE_TIME_COLUMNS_ID = 'doc_table:hideTimeColumn'; +export const DOC_TABLE_HIGHLIGHT_ID = 'doc_table:highlight'; +export const DOC_TABLE_LEGACY_ID = 'doc_table:legacy'; +export const TRUNCATE_MAX_HEIGHT_ID = 'truncate:maxHeight'; + +// Machine learning settings +export const ML_ANOMALY_DETECTION_RESULTS_ENABLE_TIME_DEFAULTS_ID = + 'ml:anomalyDetection:results:enableTimeDefaults'; +export const ML_ANOMALY_DETECTION_RESULTS_TIME_DEFAULTS_ID = + 'ml:anomalyDetection:results:timeDefaults'; + +// Notifications settings +export const NOTIFICATIONS_BANNER_ID = 'notifications:banner'; +export const NOTIFICATIONS_LIFETIME_BANNER_ID = 'notifications:lifetime:banner'; +export const NOTIFICATIONS_LIFETIME_ERROR_ID = 'notifications:lifetime:error'; +export const NOTIFICATIONS_LIFETIME_INFO_ID = 'notifications:lifetime:info'; +export const NOTIFICATIONS_LIFETIME_WARNING_ID = 'notifications:lifetime:warning'; + +// Observability settings +export const OBSERVABILITY_APM_AWS_LAMBDA_PRICE_FACTOR_ID = 'observability:apmAWSLambdaPriceFactor'; +export const OBSERVABILITY_APM_AWS_LAMBDA_REQUEST_COST_PER_MILLION_ID = + 'observability:apmAWSLambdaRequestCostPerMillion'; +export const OBSERVABILITY_APM_AGENT_EXPLORER_VIEW_ID = 'observability:apmAgentExplorerView'; +export const OBSERVABILITY_APM_DEFAULT_SERVICE_ENVIRONMENT_ID = + 'observability:apmDefaultServiceEnvironment'; +export const OBSERVABILITY_APM_ENABLE_CRITICAL_PATH_ID = 'observability:apmEnableCriticalPath'; +export const OBSERVABILITY_APM_LABS_BUTTON_ID = 'observability:apmLabsButton'; +export const OBSERVABILITY_APM_PROGRESSIVE_LOADING_ID = 'observability:apmProgressiveLoading'; +export const OBSERVABILITY_APM_SERVICE_GROUP_MAX_NUMBER_OF_SERVCIE_ID = + 'observability:apmServiceGroupMaxNumberOfServices'; +export const OBSERVABILITY_APM_SERVICE_INVENTORY_OPTIMIZED_SORTING_ID = + 'observability:apmServiceInventoryOptimizedSorting'; +export const OBSERVABILITY_APM_TRACE_EXPLORER_TAB_ID = 'observability:apmTraceExplorerTab'; +export const OBSERVABILITY_ENABLE_AWS_LAMBDA_METRICS_ID = 'observability:enableAwsLambdaMetrics'; +export const OBSERVABILITY_ENABLE_COMPARISON_BY_DEFAULT_ID = + 'observability:enableComparisonByDefault'; +export const OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_VIEW_ID = + 'observability:enableInfrastructureHostsView'; +export const OBSERVABILITY_ENABLE_INSPECT_ES_QUERIES_ID = 'observability:enableInspectEsQueries'; +export const OBSERVABILITY_MAX_SUGGESTIONS_ID = 'observability:maxSuggestions'; +export const OBSERVABILITY_PROFILING_ELASTICSEARCH_PLUGIN_ID = + 'observability:profilingElasticsearchPlugin'; + +// Reporting settings +export const XPACK_REPORTING_CUSTOM_PDF_LOGO_ID = 'xpackReporting:customPdfLogo'; + +// Rollups settings +export const ROLLUPS_ENABLE_INDEX_PATTERNS_ID = 'rollups.enableIndexPatterns'; + +// Search settings +export const COURIER_CUSTOM_REQUEST_PREFERENCE_ID = 'courier:customRequestPreference'; +export const COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX_ID = + 'courier:ignoreFilterIfFieldNotInIndex'; +export const COURIER_MAX_CONCURRENT_SHARD_REQUEST_ID = 'courier:maxConcurrentShardRequests'; +export const COURIER_SET_REQUEST_PREFERENCE_ID = 'courier:setRequestPreference'; +export const SEARCH_INCLUDE_FROZEN_ID = 'search:includeFrozen'; +export const SEARCH_TIMEOUT_ID = 'search:timeout'; + +// Security solution settings +export const SECURITY_SOLUTION_REFRESH_INTERVAL_DEFAULTS_ID = + 'securitySolution:refreshIntervalDefaults'; +export const SECURITY_SOLUTION_TIME_DEFAULTS_ID = 'securitySolution:timeDefaults'; +export const SECURITY_SOLUTION_DEFAULT_INDEX_ID = 'securitySolution:defaultIndex'; +export const SECURITY_SOLUTION_DEFAULT_THREAT_INDEX_ID = 'securitySolution:defaultThreatIndex'; +export const SECURITY_SOLUTION_DEFAULT_ANOMALY_SCORE_ID = 'securitySolution:defaultAnomalyScore'; +export const SECURITY_SOLUTION_ENABLE_GROUPED_NAV_ID = 'securitySolution:enableGroupedNav'; +export const SECURITY_SOLUTION_ENABLE_NEWS_FEED_ID = 'securitySolution:enableNewsFeed'; +export const SECURITY_SOLUTION_RULES_TABLE_REFRESH_ID = 'securitySolution:rulesTableRefresh'; +export const SECURITY_SOLUTION_NEWS_FEED_URL_ID = 'securitySolution:newsFeedUrl'; +export const SECURITY_SOLUTION_IP_REPUTATION_LINKS_ID = 'securitySolution:ipReputationLinks'; +export const SECURITY_SOLUTION_ENABLE_CCS_WARNING_ID = 'securitySolution:enableCcsWarning'; +export const SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID = + 'securitySolution:showRelatedIntegrations'; + +// Timelion settings +export const TIMELION_ES_DEFAULT_INDEX_ID = 'timelion:es.default_index'; +export const TIMELION_ES_TIME_FIELD_ID = 'timelion:es.timefield'; +export const TIMELION_MAX_BUCKETS_ID = 'timelion:max_buckets'; +export const TIMELION_MIN_INTERVAL_ID = 'timelion:min_interval'; +export const TIMELION_TARGET_BUCKETS_ID = 'timelion:target_buckets'; + +// Visualization settings +export const VISUALIZATION_COLOR_MAPPING_ID = 'visualization:colorMapping'; +export const VISUALIZATION_HEATMAP_MAX_BUCKETS_ID = 'visualization:heatmap:maxBuckets'; +export const VISUALIZATION_USE_LEGACY_TIME_AXIS_ID = 'visualization:useLegacyTimeAxis'; +export const VISUALIZATION_LEGACY_GAUGE_CHARTS_LIBRARY_ID = + 'visualization:visualize:legacyGaugeChartsLibrary'; +export const VISUALIZATION_LEGACY_HEATMAP_CHARTS_LIBRARY_ID = + 'visualization:visualize:legacyHeatmapChartsLibrary'; +export const VISUALIZATION_ENABLE_LABS_ID = 'visualize:enableLabs'; diff --git a/packages/kbn-management/settings/setting_ids/kibana.jsonc b/packages/kbn-management/settings/setting_ids/kibana.jsonc new file mode 100644 index 0000000000000..934b0dd5baaac --- /dev/null +++ b/packages/kbn-management/settings/setting_ids/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-ids", + "owner": "@elastic/appex-sharedux @elastic/platform-deployment-management" +} diff --git a/packages/kbn-management/settings/setting_ids/package.json b/packages/kbn-management/settings/setting_ids/package.json new file mode 100644 index 0000000000000..59c9769cd1bf7 --- /dev/null +++ b/packages/kbn-management/settings/setting_ids/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-ids", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/setting_ids/tsconfig.json b/packages/kbn-management/settings/setting_ids/tsconfig.json new file mode 100644 index 0000000000000..53e5c76cbab87 --- /dev/null +++ b/packages/kbn-management/settings/setting_ids/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], +} diff --git a/packages/kbn-management/settings/types/README.mdx b/packages/kbn-management/settings/types/README.mdx new file mode 100644 index 0000000000000..be258389beefe --- /dev/null +++ b/packages/kbn-management/settings/types/README.mdx @@ -0,0 +1,12 @@ +--- +id: management/settings/types +slug: /management/settings/types +title: Management Settings Typescript Types +description: Common types for objects and functions for Advanced Settings in Stack Management. +tags: ['management', 'settings'] +date: 2023-08-31 +--- + +## Description + +This package contains common types used throughout the `@kbn/management-settings-*` packages. diff --git a/packages/kbn-management/settings/types/field_definition.ts b/packages/kbn-management/settings/types/field_definition.ts new file mode 100644 index 0000000000000..eb34df3b67868 --- /dev/null +++ b/packages/kbn-management/settings/types/field_definition.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ReactElement } from 'react'; + +import { UiCounterMetricType } from '@kbn/analytics'; +import { DeprecationSettings } from '@kbn/core-ui-settings-common'; + +import { KnownTypeToValue, SettingType } from './setting_type'; + +/** + * A {@link FieldDefinition} adapts a {@link UiSettingMetadata} object to be more + * easily consumed by the UI. It contains additional information about the field + * that is determined from a given UiSettingMetadata object, (which is a type + * representing a UiSetting). + * @public + */ +export interface FieldDefinition | null> { + /** UX ARIA attributes derived from the setting. */ + ariaAttributes: { + /** The `aria-label` attribute for the field input. */ + ariaLabel: string; + /** The `aria-describedby` attribute for the field input. */ + ariaDescribedBy?: string; + }; + /** A list of categories related to the field. */ + categories: string[]; + /** The default value of the field from Kibana. */ + defaultValue?: V; + /** The text-based display of the default value, for use in the UI. */ + defaultValueDisplay: string; + /** + * Deprecation information for the field + * @see {@link DeprecationSettings} + */ + deprecation?: DeprecationSettings; + /** A description of the field. */ + description?: string | ReactElement; + /** The name of the field suitable for display in the UX. */ + displayName: string; + /** The grouping identifier for the field. */ + groupId: string; + /** The unique identifier of the field, typically separated by `:` */ + id: string; + /** True if the field is a custom setting, false otherwise. */ + isCustom: boolean; + /** True if the current saved setting matches the default setting. */ + isDefaultValue: boolean; + /** True if the setting is overridden in Kibana, false otherwise. */ + isOverridden: boolean; + /** True if the setting is read-only, false otherwise. */ + isReadOnly: boolean; + /** Metric information when one interacts with the field. */ + metric?: { + /** The metric name. */ + name?: string; + /** The metric type. */ + type?: UiCounterMetricType; + }; + /** The name of the field suitable for use in the UX. */ + name: string; + /** Option information if the field represents a `select` setting. */ + options?: { + /** Option values for the field. */ + values: string[] | number[]; + /** Option labels organized by value. */ + labels: Record; + }; + /** A rank order for the field relative to other fields. */ + order: number | undefined; + /** True if the browser must be reloaded for the setting to take effect, false otherwise. */ + requiresPageReload: boolean; + /** The current saved value of the setting. */ + savedValue?: V; + /** + * The type of setting the field represents. + * @see {@link SettingType} + */ + type: T; + /** An identifier of the field when it has an unsaved change. */ + unsavedFieldId: string; +} + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `array` type + * for use in the UI. + */ +export type ArrayFieldDefinition = FieldDefinition<'array'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `boolean` type + * for use in the UI. + */ +export type BooleanFieldDefinition = FieldDefinition<'boolean'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `color` type + * for use in the UI. + */ +export type ColorFieldDefinition = FieldDefinition<'color'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `image` type + * for use in the UI. + */ +export type ImageFieldDefinition = FieldDefinition<'image'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `json` type + * for use in the UI. + */ +export type JsonFieldDefinition = FieldDefinition<'json'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `markdown` type + * for use in the UI. + */ +export type MarkdownFieldDefinition = FieldDefinition<'markdown'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `number` type + * for use in the UI. + */ +export type NumberFieldDefinition = FieldDefinition<'number'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `select` type + * for use in the UI. + */ +export interface SelectFieldDefinition extends FieldDefinition<'select'> { + /** Options are required when this definition is used. */ + options: { + /** Option values for the field. */ + values: string[] | number[]; + /** Option labels organized by value. */ + labels: Record; + }; +} + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `string` type + * for use in the UI. + */ +export type StringFieldDefinition = FieldDefinition<'string'>; + +/** + * This is a {@link FieldDefinition} representing {@link UiSetting} `undefined` type + * for use in the UI. + */ +export type UndefinedFieldDefinition = FieldDefinition<'undefined'>; diff --git a/packages/kbn-management/settings/types/index.ts b/packages/kbn-management/settings/types/index.ts new file mode 100644 index 0000000000000..08cd1ae1df3bb --- /dev/null +++ b/packages/kbn-management/settings/types/index.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SettingType } from './setting_type'; +import { UnsavedFieldChange } from './unsaved_change'; + +export type { + ArrayFieldDefinition, + BooleanFieldDefinition, + ColorFieldDefinition, + ImageFieldDefinition, + JsonFieldDefinition, + FieldDefinition, + MarkdownFieldDefinition, + NumberFieldDefinition, + SelectFieldDefinition, + StringFieldDefinition, + UndefinedFieldDefinition, +} from './field_definition'; + +export type { + ArrayUiSettingMetadata, + BooleanUiSettingMetadata, + ColorUiSettingMetadata, + ImageUiSettingMetadata, + JsonUiSettingMetadata, + MarkdownUiSettingMetadata, + NumberUiSettingMetadata, + SelectUiSettingMetadata, + StringUiSettingMetadata, + UndefinedUiSettingMetadata, + UiSettingMetadata, + KnownTypeToMetadata, + UiSetting, +} from './metadata'; + +export type { + ArrayUnsavedFieldChange, + BooleanUnsavedFieldChange, + ColorUnsavedFieldChange, + ImageUnsavedFieldChange, + JsonUnsavedFieldChange, + MarkdownUnsavedFieldChange, + NumberUnsavedFieldChange, + SelectUnsavedFieldChange, + StringUnsavedFieldChange, + UndefinedUnsavedFieldChange, + UnsavedFieldChange, +} from './unsaved_change'; + +export type { + ArraySettingType, + BooleanSettingType, + KnownTypeToValue, + NumberSettingType, + SettingType, + StringSettingType, + UndefinedSettingType, + Value, +} from './setting_type'; + +/** + * A React `ref` that indicates an input can be reset using an + * imperative handle. + */ +export type ResetInputRef = { + reset: () => void; +} | null; + +/** + * A function that is called when the value of a {@link FieldInput} changes. + * @param change The {@link UnsavedFieldChange} passed to the handler. + */ +export type OnChangeFn = (change?: UnsavedFieldChange) => void; diff --git a/packages/kbn-management/settings/types/kibana.jsonc b/packages/kbn-management/settings/types/kibana.jsonc new file mode 100644 index 0000000000000..2a8c86bd31196 --- /dev/null +++ b/packages/kbn-management/settings/types/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-types", + "owner": "@elastic/platform-deployment-management" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/types/metadata.ts b/packages/kbn-management/settings/types/metadata.ts new file mode 100644 index 0000000000000..c0a79549039de --- /dev/null +++ b/packages/kbn-management/settings/types/metadata.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PublicUiSettingsParams, UserProvidedValues } from '@kbn/core/public'; +import { KnownTypeToValue, SettingType } from './setting_type'; + +/** + * Creating this type based on {@link UiSettingsClientCommon} and exporting for ease. + */ +export type UiSetting = PublicUiSettingsParams & UserProvidedValues; + +/** + * This is an type-safe abstraction over the {@link UiSetting} type, whose fields + * are not only optional, but also not strongly typed to + * {@link @kbn/core-ui-settings-common#UiSettingsType}. + * + * @public + */ +export interface UiSettingMetadata | null> + extends UiSetting { + /** + * The type of setting being represented. + * @see{@link SettingType} + */ + type: T; + /** The default value in Kibana for the setting. */ + value?: V; + /** The value saved by the user. */ + userValue?: V; +} + +/** + * This is an type-safe abstraction over the {@link UiSetting} `array` type. + * @public + */ +export type ArrayUiSettingMetadata = UiSettingMetadata<'array'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `boolean` type. + * @public + */ +export type BooleanUiSettingMetadata = UiSettingMetadata<'boolean'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `color` type. + * @public + */ +export type ColorUiSettingMetadata = UiSettingMetadata<'color'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `image` type. + * @public + */ +export type ImageUiSettingMetadata = UiSettingMetadata<'image'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `json` type. + * @public + */ +export type JsonUiSettingMetadata = UiSettingMetadata<'json'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `markdown` type. + * @public + */ +export type MarkdownUiSettingMetadata = UiSettingMetadata<'markdown'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `number` type. + * @public + */ +export type NumberUiSettingMetadata = UiSettingMetadata<'number'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `select` type. + * @public + */ +export type SelectUiSettingMetadata = UiSettingMetadata<'select'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `string` type. + * @public + */ +export type StringUiSettingMetadata = UiSettingMetadata<'string'>; + +/** + * This is an type-safe abstraction over the {@link UiSetting} `undefined` type. + * @public + */ +export type UndefinedUiSettingMetadata = UiSettingMetadata<'undefined'>; + +// prettier-ignore +/** + * This is a narrowing type, which finds the correct {@link UiSettingMetadata} + * type based on a given {@link SettingType}. + * @public + */ +export type KnownTypeToMetadata = + T extends 'array' ? ArrayUiSettingMetadata + : T extends 'boolean' ? BooleanUiSettingMetadata + : T extends 'color' ? ColorUiSettingMetadata + : T extends 'image' ? ImageUiSettingMetadata + : T extends 'json' ? JsonUiSettingMetadata + : T extends 'markdown' ? MarkdownUiSettingMetadata + : T extends 'number' ? NumberUiSettingMetadata + : T extends 'select' ? SelectUiSettingMetadata + : T extends 'string' ? StringUiSettingMetadata + : T extends 'undefined' ? UndefinedUiSettingMetadata + : never; diff --git a/packages/kbn-management/settings/types/package.json b/packages/kbn-management/settings/types/package.json new file mode 100644 index 0000000000000..43ed71ecaca83 --- /dev/null +++ b/packages/kbn-management/settings/types/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-types", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/types/setting_type.ts b/packages/kbn-management/settings/types/setting_type.ts new file mode 100644 index 0000000000000..da297c6d94171 --- /dev/null +++ b/packages/kbn-management/settings/types/setting_type.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UiSettingsType } from '@kbn/core-ui-settings-common'; + +/** + * This is a local type equivalent to {@link UiSettingsType} for flexibility. + * @public + */ +export type SettingType = UiSettingsType; + +/** + * A narrowing type representing all {@link SettingType} values that correspond + * to an `array` primitive type value. + * @public + */ +export type ArraySettingType = Extract; + +/** + * A narrowing type representing all {@link SettingType} values that correspond + * to an `boolean` primitive type value. + * @public + */ +export type BooleanSettingType = Extract; + +/** + * A narrowing type representing all {@link SettingType} values that correspond + * to an `number` primitive type value. + * @public + */ +export type NumberSettingType = Extract; + +/** + * A narrowing type representing all {@link SettingType} values that correspond + * to an `string` primitive type value. + * @public + */ +export type StringSettingType = Extract< + SettingType, + 'color' | 'image' | 'json' | 'markdown' | 'select' | 'string' +>; + +/** + * A narrowing type representing all {@link SettingType} values that correspond + * to an `undefined` type value. + * @public + */ +export type UndefinedSettingType = Extract; + +/** + * A type representing all possible values corresponding to a given {@link SettingType}. + */ +export type Value = string | boolean | number | Array | undefined | null; + +// prettier-ignore +/** + * This is a narrowing type, which finds the correct primitive type based on a + * given {@link SettingType}. + * @public + */ +export type KnownTypeToValue = + T extends 'color' | 'image' | 'json' | 'markdown' | 'select' | 'string' ? string : + T extends 'boolean' ? boolean : + T extends 'number' | 'bigint' ? number : + T extends 'array' ? Array : + T extends 'undefined' ? undefined: + never; diff --git a/packages/kbn-management/settings/types/tsconfig.json b/packages/kbn-management/settings/types/tsconfig.json new file mode 100644 index 0000000000000..345fbe3125a79 --- /dev/null +++ b/packages/kbn-management/settings/types/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/analytics", + "@kbn/core", + "@kbn/core-ui-settings-common", + ] +} diff --git a/packages/kbn-management/settings/types/unsaved_change.ts b/packages/kbn-management/settings/types/unsaved_change.ts new file mode 100644 index 0000000000000..3bd815187f70a --- /dev/null +++ b/packages/kbn-management/settings/types/unsaved_change.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { KnownTypeToValue, SettingType } from './setting_type'; + +/** + * A {@link UnsavedFieldChange} represents local changes to a field that have not + * yet been saved. + * @public + */ +export interface UnsavedFieldChange { + /** + * The type of setting. + * @see {@link SettingType} + */ + type: T; + /** An error message, if any, from the change. */ + error?: string | null; + /** True if the change is invalid for the field, false otherwise. */ + isInvalid?: boolean; + /** The current unsaved value stored in the field. */ + unsavedValue?: KnownTypeToValue | null; +} + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `number` value + * for use in the UI. + * @public + */ +export type ArrayUnsavedFieldChange = UnsavedFieldChange<'array'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `boolean` value + * for use in the UI. + * @public + */ +export type BooleanUnsavedFieldChange = UnsavedFieldChange<'boolean'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `color` value + * for use in the UI. + * @public + */ +export type ColorUnsavedFieldChange = UnsavedFieldChange<'color'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `image` value + * for use in the UI. + * @public + */ +export type ImageUnsavedFieldChange = UnsavedFieldChange<'image'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `json` value + * for use in the UI. + * @public + */ +export type JsonUnsavedFieldChange = UnsavedFieldChange<'json'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `markdown` value + * for use in the UI. + * @public + */ +export type MarkdownUnsavedFieldChange = UnsavedFieldChange<'markdown'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `number` value + * for use in the UI. + * @public + */ +export type NumberUnsavedFieldChange = UnsavedFieldChange<'number'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `select` value + * for use in the UI. + * @public + */ +export type SelectUnsavedFieldChange = UnsavedFieldChange<'select'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `string` value + * for use in the UI. + * @public + */ +export type StringUnsavedFieldChange = UnsavedFieldChange<'string'>; + +/** + * This is a {@link UnsavedFieldChange} representing an unsaved change to a + * {@link FieldDefinition} which has a {@link UiSetting} `undefined` value + * for use in the UI. + * @public + */ +export type UndefinedUnsavedFieldChange = UnsavedFieldChange<'undefined'>; + +// prettier-ignore +/** + * This is a narrowing type, which finds the correct primitive type based on a + * given {@link SettingType}. + * @public + */ +export type KnownTypeToUnsavedChange = + T extends 'array' ? ArrayUnsavedFieldChange : + T extends 'boolean' ? BooleanUnsavedFieldChange : + T extends 'color' ? ColorUnsavedFieldChange : + T extends 'image' ? ImageUnsavedFieldChange : + T extends 'json' ? JsonUnsavedFieldChange : + T extends 'markdown' ? MarkdownUnsavedFieldChange : + T extends 'number' | 'bigint' ? NumberUnsavedFieldChange : + T extends 'select' ? SelectUnsavedFieldChange : + T extends 'string' ? StringUnsavedFieldChange: + T extends 'undefined' ? UndefinedUnsavedFieldChange : + never; diff --git a/packages/kbn-management/settings/utilities/README.mdx b/packages/kbn-management/settings/utilities/README.mdx new file mode 100644 index 0000000000000..ef147d2fac252 --- /dev/null +++ b/packages/kbn-management/settings/utilities/README.mdx @@ -0,0 +1,12 @@ +--- +id: management/settings/utilities +slug: /management/settings/utilities +title: Management Settings Utilities +description: Utilities for working with Advanced Settings in Stack Management. +tags: ['management', 'settings'] +date: 2023-08-31 +--- + +## Description + +This package contains common utility functions for working with Advanced Settings in Stack Management. diff --git a/packages/kbn-management/settings/utilities/field/get_input_value.ts b/packages/kbn-management/settings/utilities/field/get_input_value.ts new file mode 100644 index 0000000000000..5d8b72420e51e --- /dev/null +++ b/packages/kbn-management/settings/utilities/field/get_input_value.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SettingType, UnsavedFieldChange, FieldDefinition } from '@kbn/management-settings-types'; +import { hasUnsavedChange } from './has_unsaved_change'; + +type F = Pick, 'savedValue' | 'defaultValue'>; +type C = UnsavedFieldChange; + +/** + * Convenience function to compare an `array` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `array` {@link FieldDefinition} to compare. + * @param change The `array` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'array'>, change?: C<'array'>): [string[], boolean]; +/** + * Convenience function to compare an `color` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `color` {@link FieldDefinition} to compare. + * @param change The `color` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'color'>, change?: C<'color'>): [string, boolean]; +/** + * Convenience function to compare an `boolean` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `boolean` {@link FieldDefinition} to compare. + * @param change The `boolean` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'boolean'>, change?: C<'boolean'>): [boolean, boolean]; +/** + * Convenience function to compare an `image` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `image` {@link FieldDefinition} to compare. + * @param change The `image` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'image'>, change?: C<'image'>): [string, boolean]; +/** + * Convenience function to compare an `json` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `json` {@link FieldDefinition} to compare. + * @param change The `json` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'json'>, change?: C<'json'>): [string, boolean]; +/** + * Convenience function to compare an `markdown` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `markdown` {@link FieldDefinition} to compare. + * @param change The `markdown` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'markdown'>, change?: C<'markdown'>): [string, boolean]; +/** + * Convenience function to compare an `number` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `number` {@link FieldDefinition} to compare. + * @param change The `number` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'number'>, change?: C<'number'>): [number, boolean]; +/** + * Convenience function to compare an `select` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `select` {@link FieldDefinition} to compare. + * @param change The `select` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'select'>, change?: C<'select'>): [string, boolean]; +/** + * Convenience function to compare an `string` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `string` {@link FieldDefinition} to compare. + * @param change The `string` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue(field: F<'string'>, change?: C<'string'>): [string, boolean]; +/** + * Convenience function to compare an `undefined` {@link FieldDefinition} and its {@link UnsavedFieldChange}, + * + * @param field The `undefined` {@link FieldDefinition} to compare. + * @param change The `undefined` {@link UnsavedFieldChange } to compare. + */ +export function getFieldInputValue( + field: F<'undefined'>, + change?: C<'undefined'> +): [string | null | undefined, boolean]; +/** + * Convenience function that, given a {@link FieldDefinition} and an {@link UnsavedFieldChange}, + * returns the value to be displayed in the input field, and a boolean indicating whether the + * value is an unsaved value. + * + * @param field The {@link FieldDefinition} to compare. + * @param change The {@link UnsavedFieldChange} to compare. + */ +export function getFieldInputValue(field: F, change?: C) { + const isUnsavedChange = hasUnsavedChange(field, change); + + const value = isUnsavedChange + ? change?.unsavedValue + : field.savedValue !== undefined && field.savedValue !== null + ? field.savedValue + : field.defaultValue; + + return [value, isUnsavedChange]; +} diff --git a/packages/kbn-management/settings/utilities/field/has_unsaved_change.test.ts b/packages/kbn-management/settings/utilities/field/has_unsaved_change.test.ts new file mode 100644 index 0000000000000..f2761ab561c97 --- /dev/null +++ b/packages/kbn-management/settings/utilities/field/has_unsaved_change.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { hasUnsavedChange } from './has_unsaved_change'; + +describe('hasUnsavedChange', () => { + it('returns false if the unsaved change is undefined', () => { + expect(hasUnsavedChange({ savedValue: 'foo', defaultValue: 'bar' })).toBe(false); + }); + + it('returns true if the unsaved change value is undefined or null', () => { + expect( + hasUnsavedChange({ savedValue: 'foo', defaultValue: 'bar' }, { unsavedValue: undefined }) + ).toBe(true); + expect( + hasUnsavedChange({ savedValue: 'foo', defaultValue: 'bar' }, { unsavedValue: null }) + ).toBe(true); + }); + + it('returns false if the unsaved change value is equal to the saved value', () => { + expect( + hasUnsavedChange({ savedValue: 'foo', defaultValue: 'bar' }, { unsavedValue: 'foo' }) + ).toBe(false); + }); + + it('returns false if the saved value is undefined, but the unsaved change value is equal to the default value', () => { + expect( + hasUnsavedChange({ savedValue: undefined, defaultValue: 'bar' }, { unsavedValue: 'bar' }) + ).toBe(false); + }); + + it('returns true if the unsaved change value is not equal to the saved value', () => { + expect( + hasUnsavedChange({ savedValue: 'foo', defaultValue: 'bar' }, { unsavedValue: 'baz' }) + ).toBe(true); + }); + + it('returns true if the saved value is undefined, but the unsaved change value is not equal to the default value', () => { + expect( + hasUnsavedChange({ savedValue: undefined, defaultValue: 'bar' }, { unsavedValue: 'baz' }) + ).toBe(true); + }); + + it('returns false if the saved value is undefined, and the unsaved change value is equal to the default value', () => { + expect( + hasUnsavedChange({ savedValue: undefined, defaultValue: 'bar' }, { unsavedValue: 'bar' }) + ).toBe(false); + }); +}); diff --git a/packages/kbn-management/settings/utilities/field/has_unsaved_change.ts b/packages/kbn-management/settings/utilities/field/has_unsaved_change.ts new file mode 100644 index 0000000000000..08fc5d3c34095 --- /dev/null +++ b/packages/kbn-management/settings/utilities/field/has_unsaved_change.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import isEqual from 'lodash/isEqual'; + +import type { + FieldDefinition, + SettingType, + UnsavedFieldChange, +} from '@kbn/management-settings-types'; + +/** + * Compares a given {@link FieldDefinition} to an {@link UnsavedFieldChange} to determine + * if the field has an unsaved change in the UI. + * + * @param field The field to compare. + * @param unsavedChange The unsaved change to compare. + */ +export const hasUnsavedChange = ( + field: Pick, 'savedValue' | 'defaultValue'>, + unsavedChange?: Pick, 'unsavedValue'> +) => { + // If there's no unsaved change, return false. + if (!unsavedChange) { + return false; + } + + const { unsavedValue } = unsavedChange; + + const { savedValue, defaultValue } = field; + const hasSavedValue = savedValue !== undefined && savedValue !== null; + + // Return a comparison of the unsaved value to: + // the saved value, if the field has a saved value, or + // the default value, if the field does not have a saved value. + return !isEqual(unsavedValue, hasSavedValue ? savedValue : defaultValue); +}; diff --git a/packages/kbn-management/settings/utilities/field/index.ts b/packages/kbn-management/settings/utilities/field/index.ts new file mode 100644 index 0000000000000..b82ea8926089c --- /dev/null +++ b/packages/kbn-management/settings/utilities/field/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getFieldInputValue } from './get_input_value'; +export { hasUnsavedChange } from './has_unsaved_change'; +export { isFieldDefaultValue } from './is_default_value'; +export { useUpdate, type UseUpdateParameters } from './use_update'; diff --git a/packages/kbn-management/settings/utilities/field/is_default_value.ts b/packages/kbn-management/settings/utilities/field/is_default_value.ts new file mode 100644 index 0000000000000..a0a6dec865e7a --- /dev/null +++ b/packages/kbn-management/settings/utilities/field/is_default_value.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import isEqual from 'lodash/isEqual'; + +import { FieldDefinition, SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { hasUnsavedChange } from './has_unsaved_change'; + +type F = Pick, 'savedValue' | 'defaultValue'>; +type C = UnsavedFieldChange; + +/** + * Utility function to determine if a given value is equal to the default value of + * a {@link FieldDefinition}. + * + * @param field The field to compare. + * @param change The unsaved change to compare. + */ +export function isFieldDefaultValue(field: F, change?: C): boolean { + const { defaultValue } = field; + const isUnsavedChange = hasUnsavedChange(field, change); + + const value = isUnsavedChange + ? change?.unsavedValue + : field.savedValue !== undefined && field.savedValue !== null + ? field.savedValue + : field.defaultValue; + + return isEqual(value, defaultValue); +} diff --git a/packages/kbn-management/settings/utilities/field/use_update.ts b/packages/kbn-management/settings/utilities/field/use_update.ts new file mode 100644 index 0000000000000..4744d59dd90e7 --- /dev/null +++ b/packages/kbn-management/settings/utilities/field/use_update.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { FieldDefinition, SettingType, OnChangeFn } from '@kbn/management-settings-types'; +import { hasUnsavedChange } from './has_unsaved_change'; + +export interface UseUpdateParameters { + /** The {@link OnChangeFn} to invoke. */ + onChange: OnChangeFn; + /** The {@link FieldDefinition} to use to create an update. */ + field: Pick, 'defaultValue' | 'savedValue'>; +} + +/** + * Hook to provide a standard {@link OnChangeFn} that will send an update to the + * field. + * + * @param params The {@link UseUpdateParameters} to use. + * @returns An {@link OnChangeFn} that will send an update to the field. + */ +export const useUpdate = (params: UseUpdateParameters): OnChangeFn => { + const { onChange, field } = params; + + return (update) => { + if (hasUnsavedChange(field, update)) { + onChange(update); + } else { + onChange(); + } + }; +}; diff --git a/packages/kbn-management/settings/utilities/index.ts b/packages/kbn-management/settings/utilities/index.ts new file mode 100644 index 0000000000000..4e4523f66eb59 --- /dev/null +++ b/packages/kbn-management/settings/utilities/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { isSettingDefaultValue, normalizeSettings } from './setting'; +export { + getFieldInputValue, + hasUnsavedChange, + isFieldDefaultValue, + useUpdate, + type UseUpdateParameters, +} from './field'; diff --git a/packages/kbn-management/settings/utilities/kibana.jsonc b/packages/kbn-management/settings/utilities/kibana.jsonc new file mode 100644 index 0000000000000..ebafaeba6d884 --- /dev/null +++ b/packages/kbn-management/settings/utilities/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-utilities", + "owner": "@elastic/platform-deployment-management" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/utilities/package.json b/packages/kbn-management/settings/utilities/package.json new file mode 100644 index 0000000000000..b82429aa30707 --- /dev/null +++ b/packages/kbn-management/settings/utilities/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-utilities", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/utilities/setting/index.ts b/packages/kbn-management/settings/utilities/setting/index.ts new file mode 100644 index 0000000000000..c2dd8f654d1b8 --- /dev/null +++ b/packages/kbn-management/settings/utilities/setting/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { isSettingDefaultValue } from './is_default_value'; +export { normalizeSettings } from './normalize_settings'; diff --git a/packages/kbn-management/settings/utilities/setting/is_default_value.ts b/packages/kbn-management/settings/utilities/setting/is_default_value.ts new file mode 100644 index 0000000000000..b59467b7410ac --- /dev/null +++ b/packages/kbn-management/settings/utilities/setting/is_default_value.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SettingType, UiSettingMetadata, Value } from '@kbn/management-settings-types'; +import isEqual from 'lodash/isEqual'; + +/** + * Utility function to compare a value to the default value of a {@link UiSettingMetadata}. + * @param setting The source {@link UiSettingMetadata} object. + * @param userValue The value to compare to the setting's default value. Default is the + * {@link UiSettingMetadata}'s user value. + * @returns True if the provided value is equal to the setting's default value, false otherwise. + */ +export const isSettingDefaultValue = ( + setting: UiSettingMetadata, + userValue: Value = setting.userValue +) => { + const { value } = setting; + + if (userValue === undefined || userValue === null) { + return true; + } + + return isEqual(value, userValue); +}; diff --git a/packages/kbn-management/settings/utilities/setting/normalize_settings.test.ts b/packages/kbn-management/settings/utilities/setting/normalize_settings.test.ts new file mode 100644 index 0000000000000..48ed648e58c2f --- /dev/null +++ b/packages/kbn-management/settings/utilities/setting/normalize_settings.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { normalizeSettings } from './normalize_settings'; + +describe('normalizeSettings', () => { + describe('adds a missing type if there is a value', () => { + it('a string value', () => { + const setting = { name: 'foo', value: 'bar' }; + const settings = { foo: setting }; + + expect(normalizeSettings(settings)).toEqual({ + foo: { type: 'string', ...setting }, + }); + }); + it('a boolean value', () => { + const setting = { name: 'foo', value: true }; + const settings = { foo: setting }; + + expect(normalizeSettings(settings)).toEqual({ + foo: { type: 'boolean', ...setting }, + }); + }); + it('an array value', () => { + const setting = { name: 'foo', value: ['foo', 'bar'] }; + const settings = { foo: setting }; + + expect(normalizeSettings(settings)).toEqual({ + foo: { type: 'array', ...setting }, + }); + }); + // + // can't test a bigint value unless Jest is set to use only one + // webworker. see: https://github.com/jestjs/jest/issues/11617 + // + // it('a bigint value', () => { + // const setting = { name: 'foo', value: BigInt(9007199254740991) }; + // const settings = { foo: setting }; + + // expect(normalizeSettings(settings)).toEqual({ + // foo: { type: 'number', ...setting }, + // }); + // }); + // + it('a numeric value', () => { + const setting = { name: 'foo', value: 10 }; + const settings = { foo: setting }; + + expect(normalizeSettings(settings)).toEqual({ + foo: { type: 'number', ...setting }, + }); + }); + }); + + it('throws if the value is an object', () => { + const setting = { name: 'foo', value: { bar: 'baz' } }; + const settings = { foo: setting }; + + expect(() => normalizeSettings(settings)).toThrowError( + `incompatible SettingType: 'foo' type object | {"name":"foo","value":{"bar":"baz"}}` + ); + }); + + it('does nothing if the type and value are already set', () => { + const setting = { name: 'foo', value: 'bar', type: 'string' as 'string' }; + const settings = { foo: setting }; + + expect(normalizeSettings(settings)).toEqual(settings); + }); +}); diff --git a/packages/kbn-management/settings/utilities/setting/normalize_settings.ts b/packages/kbn-management/settings/utilities/setting/normalize_settings.ts new file mode 100644 index 0000000000000..fa247151c7751 --- /dev/null +++ b/packages/kbn-management/settings/utilities/setting/normalize_settings.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SettingType, UiSetting, UiSettingMetadata, Value } from '@kbn/management-settings-types'; + +type RawSettings = Record>; + +/** + * UiSettings have an extremely permissive set of types, which makes it difficult to code + * against them. Sometimes the `type` field-- the property that tells us what input to render + * to change the setting-- is missing. This function attempts to derive that `type` property + * from the `value` or `userValue` fields of the setting. + * + * @param setting The setting from which to derive the type. + * @returns The derived {@link SettingType}. + */ +const deriveType = (setting: UiSetting): SettingType => { + const { type, value: defaultValue, userValue: savedValue } = setting; + + if (type) { + return type; + } + + if (Array.isArray(defaultValue) || Array.isArray(savedValue)) { + return 'array'; + } + + const typeofVal = defaultValue != null ? typeof defaultValue : typeof savedValue; + + if (typeofVal === 'bigint') { + return 'number'; + } + + if (typeofVal === 'boolean') { + return 'boolean'; + } + + if (typeofVal === 'symbol' || typeofVal === 'object' || typeofVal === 'function') { + throw new Error( + `incompatible SettingType: '${setting.name}' type ${typeofVal} | ${JSON.stringify(setting)}` + ); + } + + return typeofVal; +}; + +/** + * UiSettings have an extremely permissive set of types, which makes it difficult to code + * against them. The `value` property is typed as `unknown`, but the setting has a `type` + * property that tells us what type the value should be. This function attempts to cast + * the value from a given type. + * + * @param type The {@link SettingType} to which to cast the value. + * @param value The value to cast. + */ +const deriveValue = (type: SettingType, value: unknown): Value => { + if (value === null) { + return null; + } + + switch (type) { + case 'color': + case 'image': + case 'json': + case 'markdown': + case 'string': + return value as string; + case 'number': + return value ? Number(value) : undefined; + case 'boolean': + return Boolean(value); + case 'array': + return Array.isArray(value) ? value : [value]; + default: + return value as string; + } +}; + +/** + * UiSettings have an extremely permissive set of types, which makes it difficult to code + * against them. The `type` and `value` properties are inherently related, and important, + * but in some cases one or both are missing. This function attempts to normalize the + * settings to a strongly-typed format, {@link UiSettingMetadata} based on the information + * in the setting at runtime. + * + * @param rawSettings The raw settings retrieved from the {@link IUiSettingsClient}, which + * may be missing the `type` or `value` properties. + * @returns A mapped collection of normalized {@link UiSetting} objects. + */ +export const normalizeSettings = ( + rawSettings: RawSettings +): Record> => { + const normalizedSettings: Record> = {}; + + const entries = Object.entries(rawSettings); + + entries.forEach(([id, rawSetting]) => { + const type = deriveType(rawSetting); + const value = deriveValue(type, rawSetting.value); + + const setting = { + ...rawSetting, + type, + value, + }; + + if (setting) { + normalizedSettings[id] = setting; + } + }); + + return normalizedSettings; +}; diff --git a/packages/kbn-management/settings/utilities/storybook/index.ts b/packages/kbn-management/settings/utilities/storybook/index.ts new file mode 100644 index 0000000000000..188d7d0d49725 --- /dev/null +++ b/packages/kbn-management/settings/utilities/storybook/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getDefaultValue, getUserValue, IMAGE } from './values'; diff --git a/packages/kbn-management/settings/utilities/storybook/values.ts b/packages/kbn-management/settings/utilities/storybook/values.ts new file mode 100644 index 0000000000000..875f3eb11205a --- /dev/null +++ b/packages/kbn-management/settings/utilities/storybook/values.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SettingType } from '@kbn/management-settings-types'; + +const LOREM = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu odio velit. Integer et mauris quis ligula elementum commodo. Morbi eu ipsum diam. Nulla auctor orci eget egestas vehicula. Aliquam gravida, dolor eu posuere vulputate, neque enim viverra odio, id viverra ipsum quam et ipsum.'; + +const JSON_DEFAULT = `{ + "foo": "bar" +}`; + +const JSON_USER = `{ + "foo": "baz", + "bar": "qux" +}`; + +const MARKDOWN = `# Heading 1 + +${LOREM.split('. ') + .map((sentence) => `- ${sentence}.`) + .join('\n')} +`; + +/** + * A predefined Image as a Base64 string. + */ +export const IMAGE = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC +`; + +/** + * Given a {@link SettingType}, returns a compatible user-defined value. + */ +export const getUserValue = (type: SettingType) => { + switch (type) { + case 'array': + return ['foo', 'bar']; + case 'boolean': + return true; + case 'color': + return '#654321'; + case 'image': + return IMAGE; + case 'json': + return JSON_USER; + case 'markdown': + return MARKDOWN; + case 'number': + return 54321; + case 'select': + return 'option2'; + case 'string': + default: + return 'some user value'; + } +}; + +/** + * Given a {@link SettingType}, returns a compatible default value. + */ +export const getDefaultValue = (type: SettingType) => { + switch (type) { + case 'array': + return ['foo', 'bar', 'baz']; + case 'boolean': + return false; + case 'color': + return '#123456'; + case 'image': + return ''; + case 'json': + return JSON_DEFAULT; + case 'markdown': + return ''; + case 'number': + return 12345; + case 'select': + return 'option1'; + case 'string': + default: + return 'some default'; + } +}; diff --git a/packages/kbn-management/settings/utilities/tsconfig.json b/packages/kbn-management/settings/utilities/tsconfig.json new file mode 100644 index 0000000000000..1247d2cd18707 --- /dev/null +++ b/packages/kbn-management/settings/utilities/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-types", + ] +} diff --git a/packages/kbn-management/storybook/config/tsconfig.json b/packages/kbn-management/storybook/config/tsconfig.json index 52ae9f82c90f6..d383f3b0ba61e 100644 --- a/packages/kbn-management/storybook/config/tsconfig.json +++ b/packages/kbn-management/storybook/config/tsconfig.json @@ -4,7 +4,10 @@ "outDir": "target/types", "types": [ "jest", - "node" + "node", + "@kbn/ambient-ui-types", + "@kbn/ambient-storybook-types", + "@emotion/react/types/css-prop" ] }, "include": [ diff --git a/packages/kbn-monaco/src/esql/antlr/.gitignore b/packages/kbn-monaco/src/esql/antlr/.gitignore new file mode 100644 index 0000000000000..f7754e5f01083 --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/.gitignore @@ -0,0 +1 @@ +.antlr/* \ No newline at end of file diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.g4 b/packages/kbn-monaco/src/esql/antlr/esql_lexer.g4 index 49d73416712a8..815cf7f237bfb 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_lexer.g4 +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.g4 @@ -7,15 +7,23 @@ lexer grammar esql_lexer; -EVAL : 'eval' -> pushMode(EXPRESSION); -EXPLAIN : 'explain' -> pushMode(EXPRESSION); -FROM : 'from' -> pushMode(SOURCE_IDENTIFIERS); -ROW : 'row' -> pushMode(EXPRESSION); -STATS : 'stats' -> pushMode(EXPRESSION); -WHERE : 'where' -> pushMode(EXPRESSION); -SORT : 'sort' -> pushMode(EXPRESSION); -LIMIT : 'limit' -> pushMode(EXPRESSION); -PROJECT : 'project' -> pushMode(SOURCE_IDENTIFIERS); +DISSECT : D I S S E C T -> pushMode(EXPRESSION); +GROK : G R O K -> pushMode(EXPRESSION); +EVAL : E V A L -> pushMode(EXPRESSION); +EXPLAIN : E X P L A I N -> pushMode(EXPLAIN_MODE); +FROM : F R O M -> pushMode(SOURCE_IDENTIFIERS); +ROW : R O W -> pushMode(EXPRESSION); +STATS : S T A T S -> pushMode(EXPRESSION); +WHERE : W H E R E -> pushMode(EXPRESSION); +SORT : S O R T -> pushMode(EXPRESSION); +MV_EXPAND : M V UNDERSCORE E X P A N D -> pushMode(EXPRESSION); +LIMIT : L I M I T -> pushMode(EXPRESSION); +PROJECT : P R O J E C T -> pushMode(EXPRESSION); +DROP : D R O P -> pushMode(EXPRESSION); +RENAME : R E N A M E -> pushMode(EXPRESSION); +SHOW : S H O W -> pushMode(EXPRESSION); +ENRICH : E N R I C H -> pushMode(ENRICH_IDENTIFIERS); +KEEP : K E E P -> pushMode(EXPRESSION); LINE_COMMENT : '//' ~[\r\n]* '\r'? '\n'? -> channel(HIDDEN) @@ -28,7 +36,12 @@ MULTILINE_COMMENT WS : [ \r\n\t]+ -> channel(HIDDEN) ; - +mode EXPLAIN_MODE; +EXPLAIN_OPENING_BRACKET : '[' -> type(OPENING_BRACKET), pushMode(DEFAULT_MODE); +EXPLAIN_PIPE : '|' -> type(PIPE), popMode; +EXPLAIN_WS : WS -> channel(HIDDEN); +EXPLAIN_LINE_COMMENT : LINE_COMMENT -> channel(HIDDEN); +EXPLAIN_MULTILINE_COMMENT : MULTILINE_COMMENT -> channel(HIDDEN); mode EXPRESSION; PIPE : '|' -> popMode; @@ -71,17 +84,44 @@ DECIMAL_LITERAL BY : 'by'; +DATE_LITERAL + : 'year' + | 'month' + | 'day' + | 'second' + | 'minute' + | 'hour' + | 'week' + | 'millisecond' + | 'years' + | 'months' + | 'days' + | 'seconds' + | 'minutes' + | 'hours' + | 'weeks' + | 'milliseconds' + ; + AND : 'and'; ASSIGN : '='; COMMA : ','; DOT : '.'; LP : '('; -OPENING_BRACKET : '[' -> pushMode(DEFAULT_MODE); -CLOSING_BRACKET : ']' -> popMode, popMode; // pop twice, once to clear mode of current cmd and once to exit DEFAULT_MODE -NOT : 'not'; -NULL : 'null'; +OPENING_BRACKET : '[' -> pushMode(EXPRESSION), pushMode(EXPRESSION); +CLOSING_BRACKET : ']' -> popMode, popMode; +NOT : N O T; +LIKE: L I K E; +RLIKE: R L I K E; +IN: I N; +IS: I S; +AS: A S; +NULL : N U L L; OR : 'or'; RP : ')'; +UNDERSCORE: '_'; +INFO : 'info'; +FUNCTIONS : 'functions'; BOOLEAN_VALUE : 'true' @@ -102,6 +142,7 @@ MINUS : '-'; ASTERISK : '*'; SLASH : '/'; PERCENT : '%'; +TEN: '10'; ORDERING : 'asc' @@ -114,16 +155,96 @@ NULLS_ORDERING_DIRECTION | 'last' ; +MATH_FUNCTION + : R O U N D + | A B S + | P O W + | L O G TEN + | P I + | T A U + | E + | S U B S T R I N G + | T R I M + | C O N C A T + | C O A L E S C E + | G R E A T E S T + | L E F T + | N O W + | R I G H T + | S T A R T S UNDERSCORE W I T H + | D A T E UNDERSCORE F O R M A T + | D A T E UNDERSCORE T R U N C + | D A T E UNDERSCORE P A R S E + | A U T O UNDERSCORE B U C K E T + | D A T E UNDERSCORE E X T R A C T + | I S UNDERSCORE F I N I T E + | I S UNDERSCORE I N F I N I T E + | C A S E + | L E N G T H + | M V UNDERSCORE M A X + | M V UNDERSCORE M I N + | M V UNDERSCORE A V G + | M V UNDERSCORE S U M + | M V UNDERSCORE C O U N T + | M V UNDERSCORE C O N C A T + | M V UNDERSCORE J O I N + | M V UNDERSCORE M E D I A N + | M V UNDERSCORE D E D U P E + | M E T A D A T A + | S P L I T + | T O UNDERSCORE S T R I N G + | T O UNDERSCORE S T R + | T O UNDERSCORE B O O L + | T O UNDERSCORE B O O L E A N + | T O UNDERSCORE D A T E T I M E + | T O UNDERSCORE D T + | T O UNDERSCORE D B L + | T O UNDERSCORE D O U B L E + | T O UNDERSCORE D E G R E E S + | T O UNDERSCORE I N T + | T O UNDERSCORE I N T E G E R + | T O UNDERSCORE I P + | T O UNDERSCORE L O N G + | T O UNDERSCORE R A D I A N S + | T O UNDERSCORE V E R S I O N + | T O UNDERSCORE U N S I G N E D UNDERSCORE L O N G + ; + UNARY_FUNCTION - : 'round' - | 'avg' - | 'min' - | 'max' - | 'sum' + : A V G + | M I N + | M A X + | S U M + | C O U N T + | C O U N T UNDERSCORE D I S T I N C T + | P E R C E N T I L E + | M E D I A N + | M E D I A N UNDERSCORE A B S O L U T E UNDERSCORE D E V I A T I O N + | A C O S + | A S I N + | A T A N + | A T A N '2' + | C E I L + | C O S + | C O S H + | F L O O R + | L T R I M + | S I N + | S I N H + | S Q R T + | T A N + | T A N H + ; + +WHERE_FUNCTIONS + : C I D R UNDERSCORE M A T C H ; UNQUOTED_IDENTIFIER - : (LETTER | '_') (LETTER | DIGIT | '_')* + : LETTER (LETTER | DIGIT | '_' | ASTERISK)* + // only allow @ at beginning of identifier to keep the option to allow @ as infix operator in the future + // also, single `_` and `@` characters are not valid identifiers + | ('_' | '@') (LETTER | DIGIT | '_' | ASTERISK)+ ; QUOTED_IDENTIFIER @@ -146,9 +267,11 @@ EXPR_WS mode SOURCE_IDENTIFIERS; SRC_PIPE : '|' -> type(PIPE), popMode; +SRC_OPENING_BRACKET : '[' -> type(OPENING_BRACKET), pushMode(SOURCE_IDENTIFIERS), pushMode(SOURCE_IDENTIFIERS); SRC_CLOSING_BRACKET : ']' -> popMode, popMode, type(CLOSING_BRACKET); SRC_COMMA : ',' -> type(COMMA); SRC_ASSIGN : '=' -> type(ASSIGN); +METADATA: M E T A D A T A; SRC_UNQUOTED_IDENTIFIER : SRC_UNQUOTED_IDENTIFIER_PART+ @@ -174,3 +297,65 @@ SRC_MULTILINE_COMMENT SRC_WS : WS -> channel(HIDDEN) ; + +mode ENRICH_IDENTIFIERS; + +ON : O N; +WITH : W I T H; + +ENR_PIPE : '|' -> type(PIPE), popMode; +ENR_CLOSING_BRACKET : ']' -> popMode, popMode, type(CLOSING_BRACKET); +ENR_COMMA : ',' -> type(COMMA); +ENR_ASSIGN : '=' -> type(ASSIGN); + +ENR_UNQUOTED_IDENTIFIER + : ENR_UNQUOTED_IDENTIFIER_PART+ + ; + +fragment ENR_UNQUOTED_IDENTIFIER_PART + : ~[=`|,[\]/ \t\r\n]+ + | '/' ~[*/] // allow single / but not followed by another / or * which would start a comment + ; + +ENR_QUOTED_IDENTIFIER + : QUOTED_IDENTIFIER + ; + +ENR_LINE_COMMENT + : LINE_COMMENT -> channel(HIDDEN) + ; + +ENR_MULTILINE_COMMENT + : MULTILINE_COMMENT -> channel(HIDDEN) + ; + +ENR_WS + : WS -> channel(HIDDEN) + ; + +fragment A : [aA]; // match either an 'a' or 'A' +fragment B : [bB]; +fragment C : [cC]; +fragment D : [dD]; +fragment E : [eE]; +fragment F : [fF]; +fragment G : [gG]; +fragment H : [hH]; +fragment I : [iI]; +fragment J : [jJ]; +fragment K : [kK]; +fragment L : [lL]; +fragment M : [mM]; +fragment N : [nN]; +fragment O : [oO]; +fragment P : [pP]; +fragment Q : [qQ]; +fragment R : [rR]; +fragment S : [sS]; +fragment T : [tT]; +fragment U : [uU]; +fragment V : [vV]; +fragment W : [wW]; +fragment X : [xX]; +fragment Y : [yY]; +fragment Z : [zZ]; \ No newline at end of file diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.interp b/packages/kbn-monaco/src/esql/antlr/esql_lexer.interp index a7fee84591c3b..80f8a20f9d322 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_lexer.interp +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.interp @@ -1,14 +1,25 @@ token literal names: null -'eval' -'explain' -'from' -'row' -'stats' -'where' -'sort' -'limit' -'project' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null null null null @@ -17,17 +28,26 @@ null null null 'by' +null 'and' null null '.' '(' -'[' +null ']' -'not' -'null' +null +null +null +null +null +null +null 'or' ')' +'_' +'info' +'functions' null null '+' @@ -35,6 +55,7 @@ null '*' '/' '%' +'10' null 'nulls' null @@ -49,9 +70,22 @@ null null null null +null +null +null +null +null +null +null +null +null +null +null token symbolic names: null +DISSECT +GROK EVAL EXPLAIN FROM @@ -59,16 +93,26 @@ ROW STATS WHERE SORT +MV_EXPAND LIMIT PROJECT +DROP +RENAME +SHOW +ENRICH +KEEP LINE_COMMENT MULTILINE_COMMENT WS +EXPLAIN_WS +EXPLAIN_LINE_COMMENT +EXPLAIN_MULTILINE_COMMENT PIPE STRING INTEGER_LITERAL DECIMAL_LITERAL BY +DATE_LITERAL AND ASSIGN COMMA @@ -77,9 +121,17 @@ LP OPENING_BRACKET CLOSING_BRACKET NOT +LIKE +RLIKE +IN +IS +AS NULL OR RP +UNDERSCORE +INFO +FUNCTIONS BOOLEAN_VALUE COMPARISON_OPERATOR PLUS @@ -87,22 +139,36 @@ MINUS ASTERISK SLASH PERCENT +TEN ORDERING NULLS_ORDERING NULLS_ORDERING_DIRECTION +MATH_FUNCTION UNARY_FUNCTION +WHERE_FUNCTIONS UNQUOTED_IDENTIFIER QUOTED_IDENTIFIER EXPR_LINE_COMMENT EXPR_MULTILINE_COMMENT EXPR_WS +METADATA SRC_UNQUOTED_IDENTIFIER SRC_QUOTED_IDENTIFIER SRC_LINE_COMMENT SRC_MULTILINE_COMMENT SRC_WS +ON +WITH +ENR_UNQUOTED_IDENTIFIER +ENR_QUOTED_IDENTIFIER +ENR_LINE_COMMENT +ENR_MULTILINE_COMMENT +ENR_WS +EXPLAIN_PIPE rule names: +DISSECT +GROK EVAL EXPLAIN FROM @@ -110,11 +176,22 @@ ROW STATS WHERE SORT +MV_EXPAND LIMIT PROJECT +DROP +RENAME +SHOW +ENRICH +KEEP LINE_COMMENT MULTILINE_COMMENT WS +EXPLAIN_OPENING_BRACKET +EXPLAIN_PIPE +EXPLAIN_WS +EXPLAIN_LINE_COMMENT +EXPLAIN_MULTILINE_COMMENT PIPE DIGIT LETTER @@ -125,6 +202,7 @@ STRING INTEGER_LITERAL DECIMAL_LITERAL BY +DATE_LITERAL AND ASSIGN COMMA @@ -133,9 +211,17 @@ LP OPENING_BRACKET CLOSING_BRACKET NOT +LIKE +RLIKE +IN +IS +AS NULL OR RP +UNDERSCORE +INFO +FUNCTIONS BOOLEAN_VALUE COMPARISON_OPERATOR PLUS @@ -143,25 +229,68 @@ MINUS ASTERISK SLASH PERCENT +TEN ORDERING NULLS_ORDERING NULLS_ORDERING_DIRECTION +MATH_FUNCTION UNARY_FUNCTION +WHERE_FUNCTIONS UNQUOTED_IDENTIFIER QUOTED_IDENTIFIER EXPR_LINE_COMMENT EXPR_MULTILINE_COMMENT EXPR_WS SRC_PIPE +SRC_OPENING_BRACKET SRC_CLOSING_BRACKET SRC_COMMA SRC_ASSIGN +METADATA SRC_UNQUOTED_IDENTIFIER SRC_UNQUOTED_IDENTIFIER_PART SRC_QUOTED_IDENTIFIER SRC_LINE_COMMENT SRC_MULTILINE_COMMENT SRC_WS +ON +WITH +ENR_PIPE +ENR_CLOSING_BRACKET +ENR_COMMA +ENR_ASSIGN +ENR_UNQUOTED_IDENTIFIER +ENR_UNQUOTED_IDENTIFIER_PART +ENR_QUOTED_IDENTIFIER +ENR_LINE_COMMENT +ENR_MULTILINE_COMMENT +ENR_WS +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z channel names: DEFAULT_TOKEN_CHANNEL @@ -169,8 +298,10 @@ HIDDEN mode names: DEFAULT_MODE +EXPLAIN_MODE EXPRESSION SOURCE_IDENTIFIERS +ENRICH_IDENTIFIERS atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 51, 533, 8, 1, 8, 1, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 7, 11, 199, 10, 11, 12, 11, 14, 11, 202, 11, 11, 3, 11, 5, 11, 205, 10, 11, 3, 11, 5, 11, 208, 10, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 217, 10, 12, 12, 12, 14, 12, 220, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 13, 6, 13, 228, 10, 13, 13, 13, 14, 13, 229, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 5, 19, 249, 10, 19, 3, 19, 6, 19, 252, 10, 19, 13, 19, 14, 19, 253, 3, 20, 3, 20, 3, 20, 7, 20, 259, 10, 20, 12, 20, 14, 20, 262, 11, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 7, 20, 270, 10, 20, 12, 20, 14, 20, 273, 11, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 5, 20, 280, 10, 20, 3, 20, 5, 20, 283, 10, 20, 5, 20, 285, 10, 20, 3, 21, 6, 21, 288, 10, 21, 13, 21, 14, 21, 289, 3, 22, 6, 22, 293, 10, 22, 13, 22, 14, 22, 294, 3, 22, 3, 22, 7, 22, 299, 10, 22, 12, 22, 14, 22, 302, 11, 22, 3, 22, 3, 22, 6, 22, 306, 10, 22, 13, 22, 14, 22, 307, 3, 22, 6, 22, 311, 10, 22, 13, 22, 14, 22, 312, 3, 22, 3, 22, 7, 22, 317, 10, 22, 12, 22, 14, 22, 320, 11, 22, 5, 22, 322, 10, 22, 3, 22, 3, 22, 3, 22, 3, 22, 6, 22, 328, 10, 22, 13, 22, 14, 22, 329, 3, 22, 3, 22, 5, 22, 334, 10, 22, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 5, 35, 383, 10, 35, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 5, 36, 395, 10, 36, 3, 37, 3, 37, 3, 38, 3, 38, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 42, 3, 42, 3, 42, 3, 42, 5, 42, 414, 10, 42, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 5, 44, 431, 10, 44, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 5, 45, 450, 10, 45, 3, 46, 3, 46, 5, 46, 454, 10, 46, 3, 46, 3, 46, 3, 46, 7, 46, 459, 10, 46, 12, 46, 14, 46, 462, 11, 46, 3, 47, 3, 47, 3, 47, 3, 47, 7, 47, 468, 10, 47, 12, 47, 14, 47, 471, 11, 47, 3, 47, 3, 47, 3, 48, 3, 48, 3, 48, 3, 48, 3, 49, 3, 49, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 52, 3, 52, 3, 52, 3, 53, 3, 53, 3, 53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 54, 3, 55, 6, 55, 507, 10, 55, 13, 55, 14, 55, 508, 3, 56, 6, 56, 512, 10, 56, 13, 56, 14, 56, 513, 3, 56, 3, 56, 5, 56, 518, 10, 56, 3, 57, 3, 57, 3, 58, 3, 58, 3, 58, 3, 58, 3, 59, 3, 59, 3, 59, 3, 59, 3, 60, 3, 60, 3, 60, 3, 60, 4, 218, 271, 2, 2, 61, 5, 2, 3, 7, 2, 4, 9, 2, 5, 11, 2, 6, 13, 2, 7, 15, 2, 8, 17, 2, 9, 19, 2, 10, 21, 2, 11, 23, 2, 12, 25, 2, 13, 27, 2, 14, 29, 2, 15, 31, 2, 2, 33, 2, 2, 35, 2, 2, 37, 2, 2, 39, 2, 2, 41, 2, 16, 43, 2, 17, 45, 2, 18, 47, 2, 19, 49, 2, 20, 51, 2, 21, 53, 2, 22, 55, 2, 23, 57, 2, 24, 59, 2, 25, 61, 2, 26, 63, 2, 27, 65, 2, 28, 67, 2, 29, 69, 2, 30, 71, 2, 31, 73, 2, 32, 75, 2, 33, 77, 2, 34, 79, 2, 35, 81, 2, 36, 83, 2, 37, 85, 2, 38, 87, 2, 39, 89, 2, 40, 91, 2, 41, 93, 2, 42, 95, 2, 43, 97, 2, 44, 99, 2, 45, 101, 2, 46, 103, 2, 2, 105, 2, 2, 107, 2, 2, 109, 2, 2, 111, 2, 47, 113, 2, 2, 115, 2, 48, 117, 2, 49, 119, 2, 50, 121, 2, 51, 5, 2, 3, 4, 13, 4, 2, 12, 12, 15, 15, 5, 2, 11, 12, 15, 15, 34, 34, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 7, 2, 36, 36, 94, 94, 112, 112, 116, 116, 118, 118, 6, 2, 12, 12, 15, 15, 36, 36, 94, 94, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 3, 2, 98, 98, 12, 2, 11, 12, 15, 15, 34, 34, 46, 46, 49, 49, 63, 63, 93, 93, 95, 95, 98, 98, 126, 126, 4, 2, 44, 44, 49, 49, 2, 570, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 3, 29, 3, 2, 2, 2, 3, 41, 3, 2, 2, 2, 3, 43, 3, 2, 2, 2, 3, 45, 3, 2, 2, 2, 3, 47, 3, 2, 2, 2, 3, 49, 3, 2, 2, 2, 3, 51, 3, 2, 2, 2, 3, 53, 3, 2, 2, 2, 3, 55, 3, 2, 2, 2, 3, 57, 3, 2, 2, 2, 3, 59, 3, 2, 2, 2, 3, 61, 3, 2, 2, 2, 3, 63, 3, 2, 2, 2, 3, 65, 3, 2, 2, 2, 3, 67, 3, 2, 2, 2, 3, 69, 3, 2, 2, 2, 3, 71, 3, 2, 2, 2, 3, 73, 3, 2, 2, 2, 3, 75, 3, 2, 2, 2, 3, 77, 3, 2, 2, 2, 3, 79, 3, 2, 2, 2, 3, 81, 3, 2, 2, 2, 3, 83, 3, 2, 2, 2, 3, 85, 3, 2, 2, 2, 3, 87, 3, 2, 2, 2, 3, 89, 3, 2, 2, 2, 3, 91, 3, 2, 2, 2, 3, 93, 3, 2, 2, 2, 3, 95, 3, 2, 2, 2, 3, 97, 3, 2, 2, 2, 3, 99, 3, 2, 2, 2, 3, 101, 3, 2, 2, 2, 4, 103, 3, 2, 2, 2, 4, 105, 3, 2, 2, 2, 4, 107, 3, 2, 2, 2, 4, 109, 3, 2, 2, 2, 4, 111, 3, 2, 2, 2, 4, 115, 3, 2, 2, 2, 4, 117, 3, 2, 2, 2, 4, 119, 3, 2, 2, 2, 4, 121, 3, 2, 2, 2, 5, 123, 3, 2, 2, 2, 7, 130, 3, 2, 2, 2, 9, 140, 3, 2, 2, 2, 11, 147, 3, 2, 2, 2, 13, 153, 3, 2, 2, 2, 15, 161, 3, 2, 2, 2, 17, 169, 3, 2, 2, 2, 19, 176, 3, 2, 2, 2, 21, 184, 3, 2, 2, 2, 23, 194, 3, 2, 2, 2, 25, 211, 3, 2, 2, 2, 27, 227, 3, 2, 2, 2, 29, 233, 3, 2, 2, 2, 31, 237, 3, 2, 2, 2, 33, 239, 3, 2, 2, 2, 35, 241, 3, 2, 2, 2, 37, 244, 3, 2, 2, 2, 39, 246, 3, 2, 2, 2, 41, 284, 3, 2, 2, 2, 43, 287, 3, 2, 2, 2, 45, 333, 3, 2, 2, 2, 47, 335, 3, 2, 2, 2, 49, 338, 3, 2, 2, 2, 51, 342, 3, 2, 2, 2, 53, 344, 3, 2, 2, 2, 55, 346, 3, 2, 2, 2, 57, 348, 3, 2, 2, 2, 59, 350, 3, 2, 2, 2, 61, 354, 3, 2, 2, 2, 63, 359, 3, 2, 2, 2, 65, 363, 3, 2, 2, 2, 67, 368, 3, 2, 2, 2, 69, 371, 3, 2, 2, 2, 71, 382, 3, 2, 2, 2, 73, 394, 3, 2, 2, 2, 75, 396, 3, 2, 2, 2, 77, 398, 3, 2, 2, 2, 79, 400, 3, 2, 2, 2, 81, 402, 3, 2, 2, 2, 83, 404, 3, 2, 2, 2, 85, 413, 3, 2, 2, 2, 87, 415, 3, 2, 2, 2, 89, 430, 3, 2, 2, 2, 91, 449, 3, 2, 2, 2, 93, 453, 3, 2, 2, 2, 95, 463, 3, 2, 2, 2, 97, 474, 3, 2, 2, 2, 99, 478, 3, 2, 2, 2, 101, 482, 3, 2, 2, 2, 103, 486, 3, 2, 2, 2, 105, 491, 3, 2, 2, 2, 107, 497, 3, 2, 2, 2, 109, 501, 3, 2, 2, 2, 111, 506, 3, 2, 2, 2, 113, 517, 3, 2, 2, 2, 115, 519, 3, 2, 2, 2, 117, 521, 3, 2, 2, 2, 119, 525, 3, 2, 2, 2, 121, 529, 3, 2, 2, 2, 123, 124, 7, 103, 2, 2, 124, 125, 7, 120, 2, 2, 125, 126, 7, 99, 2, 2, 126, 127, 7, 110, 2, 2, 127, 128, 3, 2, 2, 2, 128, 129, 8, 2, 2, 2, 129, 6, 3, 2, 2, 2, 130, 131, 7, 103, 2, 2, 131, 132, 7, 122, 2, 2, 132, 133, 7, 114, 2, 2, 133, 134, 7, 110, 2, 2, 134, 135, 7, 99, 2, 2, 135, 136, 7, 107, 2, 2, 136, 137, 7, 112, 2, 2, 137, 138, 3, 2, 2, 2, 138, 139, 8, 3, 2, 2, 139, 8, 3, 2, 2, 2, 140, 141, 7, 104, 2, 2, 141, 142, 7, 116, 2, 2, 142, 143, 7, 113, 2, 2, 143, 144, 7, 111, 2, 2, 144, 145, 3, 2, 2, 2, 145, 146, 8, 4, 3, 2, 146, 10, 3, 2, 2, 2, 147, 148, 7, 116, 2, 2, 148, 149, 7, 113, 2, 2, 149, 150, 7, 121, 2, 2, 150, 151, 3, 2, 2, 2, 151, 152, 8, 5, 2, 2, 152, 12, 3, 2, 2, 2, 153, 154, 7, 117, 2, 2, 154, 155, 7, 118, 2, 2, 155, 156, 7, 99, 2, 2, 156, 157, 7, 118, 2, 2, 157, 158, 7, 117, 2, 2, 158, 159, 3, 2, 2, 2, 159, 160, 8, 6, 2, 2, 160, 14, 3, 2, 2, 2, 161, 162, 7, 121, 2, 2, 162, 163, 7, 106, 2, 2, 163, 164, 7, 103, 2, 2, 164, 165, 7, 116, 2, 2, 165, 166, 7, 103, 2, 2, 166, 167, 3, 2, 2, 2, 167, 168, 8, 7, 2, 2, 168, 16, 3, 2, 2, 2, 169, 170, 7, 117, 2, 2, 170, 171, 7, 113, 2, 2, 171, 172, 7, 116, 2, 2, 172, 173, 7, 118, 2, 2, 173, 174, 3, 2, 2, 2, 174, 175, 8, 8, 2, 2, 175, 18, 3, 2, 2, 2, 176, 177, 7, 110, 2, 2, 177, 178, 7, 107, 2, 2, 178, 179, 7, 111, 2, 2, 179, 180, 7, 107, 2, 2, 180, 181, 7, 118, 2, 2, 181, 182, 3, 2, 2, 2, 182, 183, 8, 9, 2, 2, 183, 20, 3, 2, 2, 2, 184, 185, 7, 114, 2, 2, 185, 186, 7, 116, 2, 2, 186, 187, 7, 113, 2, 2, 187, 188, 7, 108, 2, 2, 188, 189, 7, 103, 2, 2, 189, 190, 7, 101, 2, 2, 190, 191, 7, 118, 2, 2, 191, 192, 3, 2, 2, 2, 192, 193, 8, 10, 3, 2, 193, 22, 3, 2, 2, 2, 194, 195, 7, 49, 2, 2, 195, 196, 7, 49, 2, 2, 196, 200, 3, 2, 2, 2, 197, 199, 10, 2, 2, 2, 198, 197, 3, 2, 2, 2, 199, 202, 3, 2, 2, 2, 200, 198, 3, 2, 2, 2, 200, 201, 3, 2, 2, 2, 201, 204, 3, 2, 2, 2, 202, 200, 3, 2, 2, 2, 203, 205, 7, 15, 2, 2, 204, 203, 3, 2, 2, 2, 204, 205, 3, 2, 2, 2, 205, 207, 3, 2, 2, 2, 206, 208, 7, 12, 2, 2, 207, 206, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 209, 3, 2, 2, 2, 209, 210, 8, 11, 4, 2, 210, 24, 3, 2, 2, 2, 211, 212, 7, 49, 2, 2, 212, 213, 7, 44, 2, 2, 213, 218, 3, 2, 2, 2, 214, 217, 5, 25, 12, 2, 215, 217, 11, 2, 2, 2, 216, 214, 3, 2, 2, 2, 216, 215, 3, 2, 2, 2, 217, 220, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 219, 221, 3, 2, 2, 2, 220, 218, 3, 2, 2, 2, 221, 222, 7, 44, 2, 2, 222, 223, 7, 49, 2, 2, 223, 224, 3, 2, 2, 2, 224, 225, 8, 12, 4, 2, 225, 26, 3, 2, 2, 2, 226, 228, 9, 3, 2, 2, 227, 226, 3, 2, 2, 2, 228, 229, 3, 2, 2, 2, 229, 227, 3, 2, 2, 2, 229, 230, 3, 2, 2, 2, 230, 231, 3, 2, 2, 2, 231, 232, 8, 13, 4, 2, 232, 28, 3, 2, 2, 2, 233, 234, 7, 126, 2, 2, 234, 235, 3, 2, 2, 2, 235, 236, 8, 14, 5, 2, 236, 30, 3, 2, 2, 2, 237, 238, 9, 4, 2, 2, 238, 32, 3, 2, 2, 2, 239, 240, 9, 5, 2, 2, 240, 34, 3, 2, 2, 2, 241, 242, 7, 94, 2, 2, 242, 243, 9, 6, 2, 2, 243, 36, 3, 2, 2, 2, 244, 245, 10, 7, 2, 2, 245, 38, 3, 2, 2, 2, 246, 248, 9, 8, 2, 2, 247, 249, 9, 9, 2, 2, 248, 247, 3, 2, 2, 2, 248, 249, 3, 2, 2, 2, 249, 251, 3, 2, 2, 2, 250, 252, 5, 31, 15, 2, 251, 250, 3, 2, 2, 2, 252, 253, 3, 2, 2, 2, 253, 251, 3, 2, 2, 2, 253, 254, 3, 2, 2, 2, 254, 40, 3, 2, 2, 2, 255, 260, 7, 36, 2, 2, 256, 259, 5, 35, 17, 2, 257, 259, 5, 37, 18, 2, 258, 256, 3, 2, 2, 2, 258, 257, 3, 2, 2, 2, 259, 262, 3, 2, 2, 2, 260, 258, 3, 2, 2, 2, 260, 261, 3, 2, 2, 2, 261, 263, 3, 2, 2, 2, 262, 260, 3, 2, 2, 2, 263, 285, 7, 36, 2, 2, 264, 265, 7, 36, 2, 2, 265, 266, 7, 36, 2, 2, 266, 267, 7, 36, 2, 2, 267, 271, 3, 2, 2, 2, 268, 270, 10, 2, 2, 2, 269, 268, 3, 2, 2, 2, 270, 273, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 271, 269, 3, 2, 2, 2, 272, 274, 3, 2, 2, 2, 273, 271, 3, 2, 2, 2, 274, 275, 7, 36, 2, 2, 275, 276, 7, 36, 2, 2, 276, 277, 7, 36, 2, 2, 277, 279, 3, 2, 2, 2, 278, 280, 7, 36, 2, 2, 279, 278, 3, 2, 2, 2, 279, 280, 3, 2, 2, 2, 280, 282, 3, 2, 2, 2, 281, 283, 7, 36, 2, 2, 282, 281, 3, 2, 2, 2, 282, 283, 3, 2, 2, 2, 283, 285, 3, 2, 2, 2, 284, 255, 3, 2, 2, 2, 284, 264, 3, 2, 2, 2, 285, 42, 3, 2, 2, 2, 286, 288, 5, 31, 15, 2, 287, 286, 3, 2, 2, 2, 288, 289, 3, 2, 2, 2, 289, 287, 3, 2, 2, 2, 289, 290, 3, 2, 2, 2, 290, 44, 3, 2, 2, 2, 291, 293, 5, 31, 15, 2, 292, 291, 3, 2, 2, 2, 293, 294, 3, 2, 2, 2, 294, 292, 3, 2, 2, 2, 294, 295, 3, 2, 2, 2, 295, 296, 3, 2, 2, 2, 296, 300, 5, 55, 27, 2, 297, 299, 5, 31, 15, 2, 298, 297, 3, 2, 2, 2, 299, 302, 3, 2, 2, 2, 300, 298, 3, 2, 2, 2, 300, 301, 3, 2, 2, 2, 301, 334, 3, 2, 2, 2, 302, 300, 3, 2, 2, 2, 303, 305, 5, 55, 27, 2, 304, 306, 5, 31, 15, 2, 305, 304, 3, 2, 2, 2, 306, 307, 3, 2, 2, 2, 307, 305, 3, 2, 2, 2, 307, 308, 3, 2, 2, 2, 308, 334, 3, 2, 2, 2, 309, 311, 5, 31, 15, 2, 310, 309, 3, 2, 2, 2, 311, 312, 3, 2, 2, 2, 312, 310, 3, 2, 2, 2, 312, 313, 3, 2, 2, 2, 313, 321, 3, 2, 2, 2, 314, 318, 5, 55, 27, 2, 315, 317, 5, 31, 15, 2, 316, 315, 3, 2, 2, 2, 317, 320, 3, 2, 2, 2, 318, 316, 3, 2, 2, 2, 318, 319, 3, 2, 2, 2, 319, 322, 3, 2, 2, 2, 320, 318, 3, 2, 2, 2, 321, 314, 3, 2, 2, 2, 321, 322, 3, 2, 2, 2, 322, 323, 3, 2, 2, 2, 323, 324, 5, 39, 19, 2, 324, 334, 3, 2, 2, 2, 325, 327, 5, 55, 27, 2, 326, 328, 5, 31, 15, 2, 327, 326, 3, 2, 2, 2, 328, 329, 3, 2, 2, 2, 329, 327, 3, 2, 2, 2, 329, 330, 3, 2, 2, 2, 330, 331, 3, 2, 2, 2, 331, 332, 5, 39, 19, 2, 332, 334, 3, 2, 2, 2, 333, 292, 3, 2, 2, 2, 333, 303, 3, 2, 2, 2, 333, 310, 3, 2, 2, 2, 333, 325, 3, 2, 2, 2, 334, 46, 3, 2, 2, 2, 335, 336, 7, 100, 2, 2, 336, 337, 7, 123, 2, 2, 337, 48, 3, 2, 2, 2, 338, 339, 7, 99, 2, 2, 339, 340, 7, 112, 2, 2, 340, 341, 7, 102, 2, 2, 341, 50, 3, 2, 2, 2, 342, 343, 7, 63, 2, 2, 343, 52, 3, 2, 2, 2, 344, 345, 7, 46, 2, 2, 345, 54, 3, 2, 2, 2, 346, 347, 7, 48, 2, 2, 347, 56, 3, 2, 2, 2, 348, 349, 7, 42, 2, 2, 349, 58, 3, 2, 2, 2, 350, 351, 7, 93, 2, 2, 351, 352, 3, 2, 2, 2, 352, 353, 8, 29, 6, 2, 353, 60, 3, 2, 2, 2, 354, 355, 7, 95, 2, 2, 355, 356, 3, 2, 2, 2, 356, 357, 8, 30, 5, 2, 357, 358, 8, 30, 5, 2, 358, 62, 3, 2, 2, 2, 359, 360, 7, 112, 2, 2, 360, 361, 7, 113, 2, 2, 361, 362, 7, 118, 2, 2, 362, 64, 3, 2, 2, 2, 363, 364, 7, 112, 2, 2, 364, 365, 7, 119, 2, 2, 365, 366, 7, 110, 2, 2, 366, 367, 7, 110, 2, 2, 367, 66, 3, 2, 2, 2, 368, 369, 7, 113, 2, 2, 369, 370, 7, 116, 2, 2, 370, 68, 3, 2, 2, 2, 371, 372, 7, 43, 2, 2, 372, 70, 3, 2, 2, 2, 373, 374, 7, 118, 2, 2, 374, 375, 7, 116, 2, 2, 375, 376, 7, 119, 2, 2, 376, 383, 7, 103, 2, 2, 377, 378, 7, 104, 2, 2, 378, 379, 7, 99, 2, 2, 379, 380, 7, 110, 2, 2, 380, 381, 7, 117, 2, 2, 381, 383, 7, 103, 2, 2, 382, 373, 3, 2, 2, 2, 382, 377, 3, 2, 2, 2, 383, 72, 3, 2, 2, 2, 384, 385, 7, 63, 2, 2, 385, 395, 7, 63, 2, 2, 386, 387, 7, 35, 2, 2, 387, 395, 7, 63, 2, 2, 388, 395, 7, 62, 2, 2, 389, 390, 7, 62, 2, 2, 390, 395, 7, 63, 2, 2, 391, 395, 7, 64, 2, 2, 392, 393, 7, 64, 2, 2, 393, 395, 7, 63, 2, 2, 394, 384, 3, 2, 2, 2, 394, 386, 3, 2, 2, 2, 394, 388, 3, 2, 2, 2, 394, 389, 3, 2, 2, 2, 394, 391, 3, 2, 2, 2, 394, 392, 3, 2, 2, 2, 395, 74, 3, 2, 2, 2, 396, 397, 7, 45, 2, 2, 397, 76, 3, 2, 2, 2, 398, 399, 7, 47, 2, 2, 399, 78, 3, 2, 2, 2, 400, 401, 7, 44, 2, 2, 401, 80, 3, 2, 2, 2, 402, 403, 7, 49, 2, 2, 403, 82, 3, 2, 2, 2, 404, 405, 7, 39, 2, 2, 405, 84, 3, 2, 2, 2, 406, 407, 7, 99, 2, 2, 407, 408, 7, 117, 2, 2, 408, 414, 7, 101, 2, 2, 409, 410, 7, 102, 2, 2, 410, 411, 7, 103, 2, 2, 411, 412, 7, 117, 2, 2, 412, 414, 7, 101, 2, 2, 413, 406, 3, 2, 2, 2, 413, 409, 3, 2, 2, 2, 414, 86, 3, 2, 2, 2, 415, 416, 7, 112, 2, 2, 416, 417, 7, 119, 2, 2, 417, 418, 7, 110, 2, 2, 418, 419, 7, 110, 2, 2, 419, 420, 7, 117, 2, 2, 420, 88, 3, 2, 2, 2, 421, 422, 7, 104, 2, 2, 422, 423, 7, 107, 2, 2, 423, 424, 7, 116, 2, 2, 424, 425, 7, 117, 2, 2, 425, 431, 7, 118, 2, 2, 426, 427, 7, 110, 2, 2, 427, 428, 7, 99, 2, 2, 428, 429, 7, 117, 2, 2, 429, 431, 7, 118, 2, 2, 430, 421, 3, 2, 2, 2, 430, 426, 3, 2, 2, 2, 431, 90, 3, 2, 2, 2, 432, 433, 7, 116, 2, 2, 433, 434, 7, 113, 2, 2, 434, 435, 7, 119, 2, 2, 435, 436, 7, 112, 2, 2, 436, 450, 7, 102, 2, 2, 437, 438, 7, 99, 2, 2, 438, 439, 7, 120, 2, 2, 439, 450, 7, 105, 2, 2, 440, 441, 7, 111, 2, 2, 441, 442, 7, 107, 2, 2, 442, 450, 7, 112, 2, 2, 443, 444, 7, 111, 2, 2, 444, 445, 7, 99, 2, 2, 445, 450, 7, 122, 2, 2, 446, 447, 7, 117, 2, 2, 447, 448, 7, 119, 2, 2, 448, 450, 7, 111, 2, 2, 449, 432, 3, 2, 2, 2, 449, 437, 3, 2, 2, 2, 449, 440, 3, 2, 2, 2, 449, 443, 3, 2, 2, 2, 449, 446, 3, 2, 2, 2, 450, 92, 3, 2, 2, 2, 451, 454, 5, 33, 16, 2, 452, 454, 7, 97, 2, 2, 453, 451, 3, 2, 2, 2, 453, 452, 3, 2, 2, 2, 454, 460, 3, 2, 2, 2, 455, 459, 5, 33, 16, 2, 456, 459, 5, 31, 15, 2, 457, 459, 7, 97, 2, 2, 458, 455, 3, 2, 2, 2, 458, 456, 3, 2, 2, 2, 458, 457, 3, 2, 2, 2, 459, 462, 3, 2, 2, 2, 460, 458, 3, 2, 2, 2, 460, 461, 3, 2, 2, 2, 461, 94, 3, 2, 2, 2, 462, 460, 3, 2, 2, 2, 463, 469, 7, 98, 2, 2, 464, 468, 10, 10, 2, 2, 465, 466, 7, 98, 2, 2, 466, 468, 7, 98, 2, 2, 467, 464, 3, 2, 2, 2, 467, 465, 3, 2, 2, 2, 468, 471, 3, 2, 2, 2, 469, 467, 3, 2, 2, 2, 469, 470, 3, 2, 2, 2, 470, 472, 3, 2, 2, 2, 471, 469, 3, 2, 2, 2, 472, 473, 7, 98, 2, 2, 473, 96, 3, 2, 2, 2, 474, 475, 5, 23, 11, 2, 475, 476, 3, 2, 2, 2, 476, 477, 8, 48, 4, 2, 477, 98, 3, 2, 2, 2, 478, 479, 5, 25, 12, 2, 479, 480, 3, 2, 2, 2, 480, 481, 8, 49, 4, 2, 481, 100, 3, 2, 2, 2, 482, 483, 5, 27, 13, 2, 483, 484, 3, 2, 2, 2, 484, 485, 8, 50, 4, 2, 485, 102, 3, 2, 2, 2, 486, 487, 7, 126, 2, 2, 487, 488, 3, 2, 2, 2, 488, 489, 8, 51, 7, 2, 489, 490, 8, 51, 5, 2, 490, 104, 3, 2, 2, 2, 491, 492, 7, 95, 2, 2, 492, 493, 3, 2, 2, 2, 493, 494, 8, 52, 5, 2, 494, 495, 8, 52, 5, 2, 495, 496, 8, 52, 8, 2, 496, 106, 3, 2, 2, 2, 497, 498, 7, 46, 2, 2, 498, 499, 3, 2, 2, 2, 499, 500, 8, 53, 9, 2, 500, 108, 3, 2, 2, 2, 501, 502, 7, 63, 2, 2, 502, 503, 3, 2, 2, 2, 503, 504, 8, 54, 10, 2, 504, 110, 3, 2, 2, 2, 505, 507, 5, 113, 56, 2, 506, 505, 3, 2, 2, 2, 507, 508, 3, 2, 2, 2, 508, 506, 3, 2, 2, 2, 508, 509, 3, 2, 2, 2, 509, 112, 3, 2, 2, 2, 510, 512, 10, 11, 2, 2, 511, 510, 3, 2, 2, 2, 512, 513, 3, 2, 2, 2, 513, 511, 3, 2, 2, 2, 513, 514, 3, 2, 2, 2, 514, 518, 3, 2, 2, 2, 515, 516, 7, 49, 2, 2, 516, 518, 10, 12, 2, 2, 517, 511, 3, 2, 2, 2, 517, 515, 3, 2, 2, 2, 518, 114, 3, 2, 2, 2, 519, 520, 5, 95, 47, 2, 520, 116, 3, 2, 2, 2, 521, 522, 5, 23, 11, 2, 522, 523, 3, 2, 2, 2, 523, 524, 8, 58, 4, 2, 524, 118, 3, 2, 2, 2, 525, 526, 5, 25, 12, 2, 526, 527, 3, 2, 2, 2, 527, 528, 8, 59, 4, 2, 528, 120, 3, 2, 2, 2, 529, 530, 5, 27, 13, 2, 530, 531, 3, 2, 2, 2, 531, 532, 8, 60, 4, 2, 532, 122, 3, 2, 2, 2, 41, 2, 3, 4, 200, 204, 207, 216, 218, 229, 248, 253, 258, 260, 271, 279, 282, 284, 289, 294, 300, 307, 312, 318, 321, 329, 333, 382, 394, 413, 430, 449, 453, 458, 460, 467, 469, 508, 513, 517, 11, 7, 3, 2, 7, 4, 2, 2, 3, 2, 6, 2, 2, 7, 2, 2, 9, 15, 2, 9, 26, 2, 9, 22, 2, 9, 21, 2] \ No newline at end of file +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 83, 1600, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 4, 68, 9, 68, 4, 69, 9, 69, 4, 70, 9, 70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 4, 74, 9, 74, 4, 75, 9, 75, 4, 76, 9, 76, 4, 77, 9, 77, 4, 78, 9, 78, 4, 79, 9, 79, 4, 80, 9, 80, 4, 81, 9, 81, 4, 82, 9, 82, 4, 83, 9, 83, 4, 84, 9, 84, 4, 85, 9, 85, 4, 86, 9, 86, 4, 87, 9, 87, 4, 88, 9, 88, 4, 89, 9, 89, 4, 90, 9, 90, 4, 91, 9, 91, 4, 92, 9, 92, 4, 93, 9, 93, 4, 94, 9, 94, 4, 95, 9, 95, 4, 96, 9, 96, 4, 97, 9, 97, 4, 98, 9, 98, 4, 99, 9, 99, 4, 100, 9, 100, 4, 101, 9, 101, 4, 102, 9, 102, 4, 103, 9, 103, 4, 104, 9, 104, 4, 105, 9, 105, 4, 106, 9, 106, 4, 107, 9, 107, 4, 108, 9, 108, 4, 109, 9, 109, 4, 110, 9, 110, 4, 111, 9, 111, 4, 112, 9, 112, 4, 113, 9, 113, 4, 114, 9, 114, 4, 115, 9, 115, 4, 116, 9, 116, 4, 117, 9, 117, 4, 118, 9, 118, 4, 119, 9, 119, 4, 120, 9, 120, 4, 121, 9, 121, 4, 122, 9, 122, 4, 123, 9, 123, 4, 124, 9, 124, 4, 125, 9, 125, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19, 7, 19, 399, 10, 19, 12, 19, 14, 19, 402, 11, 19, 3, 19, 5, 19, 405, 10, 19, 3, 19, 5, 19, 408, 10, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 7, 20, 417, 10, 20, 12, 20, 14, 20, 420, 11, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 21, 6, 21, 428, 10, 21, 13, 21, 14, 21, 429, 3, 21, 3, 21, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 32, 3, 32, 5, 32, 471, 10, 32, 3, 32, 6, 32, 474, 10, 32, 13, 32, 14, 32, 475, 3, 33, 3, 33, 3, 33, 7, 33, 481, 10, 33, 12, 33, 14, 33, 484, 11, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 7, 33, 492, 10, 33, 12, 33, 14, 33, 495, 11, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 502, 10, 33, 3, 33, 5, 33, 505, 10, 33, 5, 33, 507, 10, 33, 3, 34, 6, 34, 510, 10, 34, 13, 34, 14, 34, 511, 3, 35, 6, 35, 515, 10, 35, 13, 35, 14, 35, 516, 3, 35, 3, 35, 7, 35, 521, 10, 35, 12, 35, 14, 35, 524, 11, 35, 3, 35, 3, 35, 6, 35, 528, 10, 35, 13, 35, 14, 35, 529, 3, 35, 6, 35, 533, 10, 35, 13, 35, 14, 35, 534, 3, 35, 3, 35, 7, 35, 539, 10, 35, 12, 35, 14, 35, 542, 11, 35, 5, 35, 544, 10, 35, 3, 35, 3, 35, 3, 35, 3, 35, 6, 35, 550, 10, 35, 13, 35, 14, 35, 551, 3, 35, 3, 35, 5, 35, 556, 10, 35, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 5, 37, 655, 10, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3, 45, 3, 45, 3, 45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 48, 3, 48, 3, 48, 3, 49, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 3, 56, 3, 56, 3, 56, 3, 56, 3, 56, 3, 56, 3, 56, 3, 57, 3, 57, 3, 57, 3, 57, 3, 57, 3, 57, 3, 57, 3, 57, 3, 57, 5, 57, 739, 10, 57, 3, 58, 3, 58, 3, 58, 3, 58, 3, 58, 3, 58, 3, 58, 3, 58, 3, 58, 3, 58, 5, 58, 751, 10, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 3, 65, 3, 65, 3, 65, 3, 65, 5, 65, 773, 10, 65, 3, 66, 3, 66, 3, 66, 3, 66, 3, 66, 3, 66, 3, 67, 3, 67, 3, 67, 3, 67, 3, 67, 3, 67, 3, 67, 3, 67, 3, 67, 5, 67, 790, 10, 67, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 3, 68, 5, 68, 1222, 10, 68, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 3, 69, 5, 69, 1375, 10, 69, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 71, 3, 71, 3, 71, 3, 71, 3, 71, 7, 71, 1393, 10, 71, 12, 71, 14, 71, 1396, 11, 71, 3, 71, 3, 71, 3, 71, 3, 71, 3, 71, 6, 71, 1403, 10, 71, 13, 71, 14, 71, 1404, 5, 71, 1407, 10, 71, 3, 72, 3, 72, 3, 72, 3, 72, 7, 72, 1413, 10, 72, 12, 72, 14, 72, 1416, 11, 72, 3, 72, 3, 72, 3, 73, 3, 73, 3, 73, 3, 73, 3, 74, 3, 74, 3, 74, 3, 74, 3, 75, 3, 75, 3, 75, 3, 75, 3, 76, 3, 76, 3, 76, 3, 76, 3, 76, 3, 77, 3, 77, 3, 77, 3, 77, 3, 77, 3, 77, 3, 78, 3, 78, 3, 78, 3, 78, 3, 78, 3, 78, 3, 79, 3, 79, 3, 79, 3, 79, 3, 80, 3, 80, 3, 80, 3, 80, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 82, 6, 82, 1467, 10, 82, 13, 82, 14, 82, 1468, 3, 83, 6, 83, 1472, 10, 83, 13, 83, 14, 83, 1473, 3, 83, 3, 83, 5, 83, 1478, 10, 83, 3, 84, 3, 84, 3, 85, 3, 85, 3, 85, 3, 85, 3, 86, 3, 86, 3, 86, 3, 86, 3, 87, 3, 87, 3, 87, 3, 87, 3, 88, 3, 88, 3, 88, 3, 89, 3, 89, 3, 89, 3, 89, 3, 89, 3, 90, 3, 90, 3, 90, 3, 90, 3, 90, 3, 91, 3, 91, 3, 91, 3, 91, 3, 91, 3, 91, 3, 92, 3, 92, 3, 92, 3, 92, 3, 93, 3, 93, 3, 93, 3, 93, 3, 94, 6, 94, 1522, 10, 94, 13, 94, 14, 94, 1523, 3, 95, 6, 95, 1527, 10, 95, 13, 95, 14, 95, 1528, 3, 95, 3, 95, 5, 95, 1533, 10, 95, 3, 96, 3, 96, 3, 97, 3, 97, 3, 97, 3, 97, 3, 98, 3, 98, 3, 98, 3, 98, 3, 99, 3, 99, 3, 99, 3, 99, 3, 100, 3, 100, 3, 101, 3, 101, 3, 102, 3, 102, 3, 103, 3, 103, 3, 104, 3, 104, 3, 105, 3, 105, 3, 106, 3, 106, 3, 107, 3, 107, 3, 108, 3, 108, 3, 109, 3, 109, 3, 110, 3, 110, 3, 111, 3, 111, 3, 112, 3, 112, 3, 113, 3, 113, 3, 114, 3, 114, 3, 115, 3, 115, 3, 116, 3, 116, 3, 117, 3, 117, 3, 118, 3, 118, 3, 119, 3, 119, 3, 120, 3, 120, 3, 121, 3, 121, 3, 122, 3, 122, 3, 123, 3, 123, 3, 124, 3, 124, 3, 125, 3, 125, 4, 418, 493, 2, 2, 126, 7, 2, 3, 9, 2, 4, 11, 2, 5, 13, 2, 6, 15, 2, 7, 17, 2, 8, 19, 2, 9, 21, 2, 10, 23, 2, 11, 25, 2, 12, 27, 2, 13, 29, 2, 14, 31, 2, 15, 33, 2, 16, 35, 2, 17, 37, 2, 18, 39, 2, 19, 41, 2, 20, 43, 2, 21, 45, 2, 22, 47, 2, 2, 49, 2, 83, 51, 2, 23, 53, 2, 24, 55, 2, 25, 57, 2, 26, 59, 2, 2, 61, 2, 2, 63, 2, 2, 65, 2, 2, 67, 2, 2, 69, 2, 27, 71, 2, 28, 73, 2, 29, 75, 2, 30, 77, 2, 31, 79, 2, 32, 81, 2, 33, 83, 2, 34, 85, 2, 35, 87, 2, 36, 89, 2, 37, 91, 2, 38, 93, 2, 39, 95, 2, 40, 97, 2, 41, 99, 2, 42, 101, 2, 43, 103, 2, 44, 105, 2, 45, 107, 2, 46, 109, 2, 47, 111, 2, 48, 113, 2, 49, 115, 2, 50, 117, 2, 51, 119, 2, 52, 121, 2, 53, 123, 2, 54, 125, 2, 55, 127, 2, 56, 129, 2, 57, 131, 2, 58, 133, 2, 59, 135, 2, 60, 137, 2, 61, 139, 2, 62, 141, 2, 63, 143, 2, 64, 145, 2, 65, 147, 2, 66, 149, 2, 67, 151, 2, 68, 153, 2, 69, 155, 2, 2, 157, 2, 2, 159, 2, 2, 161, 2, 2, 163, 2, 2, 165, 2, 70, 167, 2, 71, 169, 2, 2, 171, 2, 72, 173, 2, 73, 175, 2, 74, 177, 2, 75, 179, 2, 76, 181, 2, 77, 183, 2, 2, 185, 2, 2, 187, 2, 2, 189, 2, 2, 191, 2, 78, 193, 2, 2, 195, 2, 79, 197, 2, 80, 199, 2, 81, 201, 2, 82, 203, 2, 2, 205, 2, 2, 207, 2, 2, 209, 2, 2, 211, 2, 2, 213, 2, 2, 215, 2, 2, 217, 2, 2, 219, 2, 2, 221, 2, 2, 223, 2, 2, 225, 2, 2, 227, 2, 2, 229, 2, 2, 231, 2, 2, 233, 2, 2, 235, 2, 2, 237, 2, 2, 239, 2, 2, 241, 2, 2, 243, 2, 2, 245, 2, 2, 247, 2, 2, 249, 2, 2, 251, 2, 2, 253, 2, 2, 7, 2, 3, 4, 5, 6, 39, 4, 2, 12, 12, 15, 15, 5, 2, 11, 12, 15, 15, 34, 34, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 7, 2, 36, 36, 94, 94, 112, 112, 116, 116, 118, 118, 6, 2, 12, 12, 15, 15, 36, 36, 94, 94, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 4, 2, 66, 66, 97, 97, 3, 2, 98, 98, 12, 2, 11, 12, 15, 15, 34, 34, 46, 46, 49, 49, 63, 63, 93, 93, 95, 95, 98, 98, 126, 126, 4, 2, 44, 44, 49, 49, 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, 101, 4, 2, 70, 70, 102, 102, 4, 2, 72, 72, 104, 104, 4, 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, 107, 4, 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, 110, 4, 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, 113, 4, 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, 116, 4, 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, 119, 4, 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, 122, 4, 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 1700, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 3, 47, 3, 2, 2, 2, 3, 49, 3, 2, 2, 2, 3, 51, 3, 2, 2, 2, 3, 53, 3, 2, 2, 2, 3, 55, 3, 2, 2, 2, 4, 57, 3, 2, 2, 2, 4, 69, 3, 2, 2, 2, 4, 71, 3, 2, 2, 2, 4, 73, 3, 2, 2, 2, 4, 75, 3, 2, 2, 2, 4, 77, 3, 2, 2, 2, 4, 79, 3, 2, 2, 2, 4, 81, 3, 2, 2, 2, 4, 83, 3, 2, 2, 2, 4, 85, 3, 2, 2, 2, 4, 87, 3, 2, 2, 2, 4, 89, 3, 2, 2, 2, 4, 91, 3, 2, 2, 2, 4, 93, 3, 2, 2, 2, 4, 95, 3, 2, 2, 2, 4, 97, 3, 2, 2, 2, 4, 99, 3, 2, 2, 2, 4, 101, 3, 2, 2, 2, 4, 103, 3, 2, 2, 2, 4, 105, 3, 2, 2, 2, 4, 107, 3, 2, 2, 2, 4, 109, 3, 2, 2, 2, 4, 111, 3, 2, 2, 2, 4, 113, 3, 2, 2, 2, 4, 115, 3, 2, 2, 2, 4, 117, 3, 2, 2, 2, 4, 119, 3, 2, 2, 2, 4, 121, 3, 2, 2, 2, 4, 123, 3, 2, 2, 2, 4, 125, 3, 2, 2, 2, 4, 127, 3, 2, 2, 2, 4, 129, 3, 2, 2, 2, 4, 131, 3, 2, 2, 2, 4, 133, 3, 2, 2, 2, 4, 135, 3, 2, 2, 2, 4, 137, 3, 2, 2, 2, 4, 139, 3, 2, 2, 2, 4, 141, 3, 2, 2, 2, 4, 143, 3, 2, 2, 2, 4, 145, 3, 2, 2, 2, 4, 147, 3, 2, 2, 2, 4, 149, 3, 2, 2, 2, 4, 151, 3, 2, 2, 2, 4, 153, 3, 2, 2, 2, 5, 155, 3, 2, 2, 2, 5, 157, 3, 2, 2, 2, 5, 159, 3, 2, 2, 2, 5, 161, 3, 2, 2, 2, 5, 163, 3, 2, 2, 2, 5, 165, 3, 2, 2, 2, 5, 167, 3, 2, 2, 2, 5, 171, 3, 2, 2, 2, 5, 173, 3, 2, 2, 2, 5, 175, 3, 2, 2, 2, 5, 177, 3, 2, 2, 2, 6, 179, 3, 2, 2, 2, 6, 181, 3, 2, 2, 2, 6, 183, 3, 2, 2, 2, 6, 185, 3, 2, 2, 2, 6, 187, 3, 2, 2, 2, 6, 189, 3, 2, 2, 2, 6, 191, 3, 2, 2, 2, 6, 195, 3, 2, 2, 2, 6, 197, 3, 2, 2, 2, 6, 199, 3, 2, 2, 2, 6, 201, 3, 2, 2, 2, 7, 255, 3, 2, 2, 2, 9, 265, 3, 2, 2, 2, 11, 272, 3, 2, 2, 2, 13, 279, 3, 2, 2, 2, 15, 289, 3, 2, 2, 2, 17, 296, 3, 2, 2, 2, 19, 302, 3, 2, 2, 2, 21, 310, 3, 2, 2, 2, 23, 318, 3, 2, 2, 2, 25, 325, 3, 2, 2, 2, 27, 337, 3, 2, 2, 2, 29, 345, 3, 2, 2, 2, 31, 355, 3, 2, 2, 2, 33, 362, 3, 2, 2, 2, 35, 371, 3, 2, 2, 2, 37, 378, 3, 2, 2, 2, 39, 387, 3, 2, 2, 2, 41, 394, 3, 2, 2, 2, 43, 411, 3, 2, 2, 2, 45, 427, 3, 2, 2, 2, 47, 433, 3, 2, 2, 2, 49, 438, 3, 2, 2, 2, 51, 443, 3, 2, 2, 2, 53, 447, 3, 2, 2, 2, 55, 451, 3, 2, 2, 2, 57, 455, 3, 2, 2, 2, 59, 459, 3, 2, 2, 2, 61, 461, 3, 2, 2, 2, 63, 463, 3, 2, 2, 2, 65, 466, 3, 2, 2, 2, 67, 468, 3, 2, 2, 2, 69, 506, 3, 2, 2, 2, 71, 509, 3, 2, 2, 2, 73, 555, 3, 2, 2, 2, 75, 557, 3, 2, 2, 2, 77, 654, 3, 2, 2, 2, 79, 656, 3, 2, 2, 2, 81, 660, 3, 2, 2, 2, 83, 662, 3, 2, 2, 2, 85, 664, 3, 2, 2, 2, 87, 666, 3, 2, 2, 2, 89, 668, 3, 2, 2, 2, 91, 673, 3, 2, 2, 2, 93, 678, 3, 2, 2, 2, 95, 682, 3, 2, 2, 2, 97, 687, 3, 2, 2, 2, 99, 693, 3, 2, 2, 2, 101, 696, 3, 2, 2, 2, 103, 699, 3, 2, 2, 2, 105, 702, 3, 2, 2, 2, 107, 707, 3, 2, 2, 2, 109, 710, 3, 2, 2, 2, 111, 712, 3, 2, 2, 2, 113, 714, 3, 2, 2, 2, 115, 719, 3, 2, 2, 2, 117, 738, 3, 2, 2, 2, 119, 750, 3, 2, 2, 2, 121, 752, 3, 2, 2, 2, 123, 754, 3, 2, 2, 2, 125, 756, 3, 2, 2, 2, 127, 758, 3, 2, 2, 2, 129, 760, 3, 2, 2, 2, 131, 762, 3, 2, 2, 2, 133, 772, 3, 2, 2, 2, 135, 774, 3, 2, 2, 2, 137, 789, 3, 2, 2, 2, 139, 1221, 3, 2, 2, 2, 141, 1374, 3, 2, 2, 2, 143, 1376, 3, 2, 2, 2, 145, 1406, 3, 2, 2, 2, 147, 1408, 3, 2, 2, 2, 149, 1419, 3, 2, 2, 2, 151, 1423, 3, 2, 2, 2, 153, 1427, 3, 2, 2, 2, 155, 1431, 3, 2, 2, 2, 157, 1436, 3, 2, 2, 2, 159, 1442, 3, 2, 2, 2, 161, 1448, 3, 2, 2, 2, 163, 1452, 3, 2, 2, 2, 165, 1456, 3, 2, 2, 2, 167, 1466, 3, 2, 2, 2, 169, 1477, 3, 2, 2, 2, 171, 1479, 3, 2, 2, 2, 173, 1481, 3, 2, 2, 2, 175, 1485, 3, 2, 2, 2, 177, 1489, 3, 2, 2, 2, 179, 1493, 3, 2, 2, 2, 181, 1496, 3, 2, 2, 2, 183, 1501, 3, 2, 2, 2, 185, 1506, 3, 2, 2, 2, 187, 1512, 3, 2, 2, 2, 189, 1516, 3, 2, 2, 2, 191, 1521, 3, 2, 2, 2, 193, 1532, 3, 2, 2, 2, 195, 1534, 3, 2, 2, 2, 197, 1536, 3, 2, 2, 2, 199, 1540, 3, 2, 2, 2, 201, 1544, 3, 2, 2, 2, 203, 1548, 3, 2, 2, 2, 205, 1550, 3, 2, 2, 2, 207, 1552, 3, 2, 2, 2, 209, 1554, 3, 2, 2, 2, 211, 1556, 3, 2, 2, 2, 213, 1558, 3, 2, 2, 2, 215, 1560, 3, 2, 2, 2, 217, 1562, 3, 2, 2, 2, 219, 1564, 3, 2, 2, 2, 221, 1566, 3, 2, 2, 2, 223, 1568, 3, 2, 2, 2, 225, 1570, 3, 2, 2, 2, 227, 1572, 3, 2, 2, 2, 229, 1574, 3, 2, 2, 2, 231, 1576, 3, 2, 2, 2, 233, 1578, 3, 2, 2, 2, 235, 1580, 3, 2, 2, 2, 237, 1582, 3, 2, 2, 2, 239, 1584, 3, 2, 2, 2, 241, 1586, 3, 2, 2, 2, 243, 1588, 3, 2, 2, 2, 245, 1590, 3, 2, 2, 2, 247, 1592, 3, 2, 2, 2, 249, 1594, 3, 2, 2, 2, 251, 1596, 3, 2, 2, 2, 253, 1598, 3, 2, 2, 2, 255, 256, 5, 209, 103, 2, 256, 257, 5, 219, 108, 2, 257, 258, 5, 239, 118, 2, 258, 259, 5, 239, 118, 2, 259, 260, 5, 211, 104, 2, 260, 261, 5, 207, 102, 2, 261, 262, 5, 241, 119, 2, 262, 263, 3, 2, 2, 2, 263, 264, 8, 2, 2, 2, 264, 8, 3, 2, 2, 2, 265, 266, 5, 215, 106, 2, 266, 267, 5, 237, 117, 2, 267, 268, 5, 231, 114, 2, 268, 269, 5, 223, 110, 2, 269, 270, 3, 2, 2, 2, 270, 271, 8, 3, 2, 2, 271, 10, 3, 2, 2, 2, 272, 273, 5, 211, 104, 2, 273, 274, 5, 245, 121, 2, 274, 275, 5, 203, 100, 2, 275, 276, 5, 225, 111, 2, 276, 277, 3, 2, 2, 2, 277, 278, 8, 4, 2, 2, 278, 12, 3, 2, 2, 2, 279, 280, 5, 211, 104, 2, 280, 281, 5, 249, 123, 2, 281, 282, 5, 233, 115, 2, 282, 283, 5, 225, 111, 2, 283, 284, 5, 203, 100, 2, 284, 285, 5, 219, 108, 2, 285, 286, 5, 229, 113, 2, 286, 287, 3, 2, 2, 2, 287, 288, 8, 5, 3, 2, 288, 14, 3, 2, 2, 2, 289, 290, 5, 213, 105, 2, 290, 291, 5, 237, 117, 2, 291, 292, 5, 231, 114, 2, 292, 293, 5, 227, 112, 2, 293, 294, 3, 2, 2, 2, 294, 295, 8, 6, 4, 2, 295, 16, 3, 2, 2, 2, 296, 297, 5, 237, 117, 2, 297, 298, 5, 231, 114, 2, 298, 299, 5, 247, 122, 2, 299, 300, 3, 2, 2, 2, 300, 301, 8, 7, 2, 2, 301, 18, 3, 2, 2, 2, 302, 303, 5, 239, 118, 2, 303, 304, 5, 241, 119, 2, 304, 305, 5, 203, 100, 2, 305, 306, 5, 241, 119, 2, 306, 307, 5, 239, 118, 2, 307, 308, 3, 2, 2, 2, 308, 309, 8, 8, 2, 2, 309, 20, 3, 2, 2, 2, 310, 311, 5, 247, 122, 2, 311, 312, 5, 217, 107, 2, 312, 313, 5, 211, 104, 2, 313, 314, 5, 237, 117, 2, 314, 315, 5, 211, 104, 2, 315, 316, 3, 2, 2, 2, 316, 317, 8, 9, 2, 2, 317, 22, 3, 2, 2, 2, 318, 319, 5, 239, 118, 2, 319, 320, 5, 231, 114, 2, 320, 321, 5, 237, 117, 2, 321, 322, 5, 241, 119, 2, 322, 323, 3, 2, 2, 2, 323, 324, 8, 10, 2, 2, 324, 24, 3, 2, 2, 2, 325, 326, 5, 227, 112, 2, 326, 327, 5, 245, 121, 2, 327, 328, 5, 111, 54, 2, 328, 329, 5, 211, 104, 2, 329, 330, 5, 249, 123, 2, 330, 331, 5, 233, 115, 2, 331, 332, 5, 203, 100, 2, 332, 333, 5, 229, 113, 2, 333, 334, 5, 209, 103, 2, 334, 335, 3, 2, 2, 2, 335, 336, 8, 11, 2, 2, 336, 26, 3, 2, 2, 2, 337, 338, 5, 225, 111, 2, 338, 339, 5, 219, 108, 2, 339, 340, 5, 227, 112, 2, 340, 341, 5, 219, 108, 2, 341, 342, 5, 241, 119, 2, 342, 343, 3, 2, 2, 2, 343, 344, 8, 12, 2, 2, 344, 28, 3, 2, 2, 2, 345, 346, 5, 233, 115, 2, 346, 347, 5, 237, 117, 2, 347, 348, 5, 231, 114, 2, 348, 349, 5, 221, 109, 2, 349, 350, 5, 211, 104, 2, 350, 351, 5, 207, 102, 2, 351, 352, 5, 241, 119, 2, 352, 353, 3, 2, 2, 2, 353, 354, 8, 13, 2, 2, 354, 30, 3, 2, 2, 2, 355, 356, 5, 209, 103, 2, 356, 357, 5, 237, 117, 2, 357, 358, 5, 231, 114, 2, 358, 359, 5, 233, 115, 2, 359, 360, 3, 2, 2, 2, 360, 361, 8, 14, 2, 2, 361, 32, 3, 2, 2, 2, 362, 363, 5, 237, 117, 2, 363, 364, 5, 211, 104, 2, 364, 365, 5, 229, 113, 2, 365, 366, 5, 203, 100, 2, 366, 367, 5, 227, 112, 2, 367, 368, 5, 211, 104, 2, 368, 369, 3, 2, 2, 2, 369, 370, 8, 15, 2, 2, 370, 34, 3, 2, 2, 2, 371, 372, 5, 239, 118, 2, 372, 373, 5, 217, 107, 2, 373, 374, 5, 231, 114, 2, 374, 375, 5, 247, 122, 2, 375, 376, 3, 2, 2, 2, 376, 377, 8, 16, 2, 2, 377, 36, 3, 2, 2, 2, 378, 379, 5, 211, 104, 2, 379, 380, 5, 229, 113, 2, 380, 381, 5, 237, 117, 2, 381, 382, 5, 219, 108, 2, 382, 383, 5, 207, 102, 2, 383, 384, 5, 217, 107, 2, 384, 385, 3, 2, 2, 2, 385, 386, 8, 17, 5, 2, 386, 38, 3, 2, 2, 2, 387, 388, 5, 223, 110, 2, 388, 389, 5, 211, 104, 2, 389, 390, 5, 211, 104, 2, 390, 391, 5, 233, 115, 2, 391, 392, 3, 2, 2, 2, 392, 393, 8, 18, 2, 2, 393, 40, 3, 2, 2, 2, 394, 395, 7, 49, 2, 2, 395, 396, 7, 49, 2, 2, 396, 400, 3, 2, 2, 2, 397, 399, 10, 2, 2, 2, 398, 397, 3, 2, 2, 2, 399, 402, 3, 2, 2, 2, 400, 398, 3, 2, 2, 2, 400, 401, 3, 2, 2, 2, 401, 404, 3, 2, 2, 2, 402, 400, 3, 2, 2, 2, 403, 405, 7, 15, 2, 2, 404, 403, 3, 2, 2, 2, 404, 405, 3, 2, 2, 2, 405, 407, 3, 2, 2, 2, 406, 408, 7, 12, 2, 2, 407, 406, 3, 2, 2, 2, 407, 408, 3, 2, 2, 2, 408, 409, 3, 2, 2, 2, 409, 410, 8, 19, 6, 2, 410, 42, 3, 2, 2, 2, 411, 412, 7, 49, 2, 2, 412, 413, 7, 44, 2, 2, 413, 418, 3, 2, 2, 2, 414, 417, 5, 43, 20, 2, 415, 417, 11, 2, 2, 2, 416, 414, 3, 2, 2, 2, 416, 415, 3, 2, 2, 2, 417, 420, 3, 2, 2, 2, 418, 419, 3, 2, 2, 2, 418, 416, 3, 2, 2, 2, 419, 421, 3, 2, 2, 2, 420, 418, 3, 2, 2, 2, 421, 422, 7, 44, 2, 2, 422, 423, 7, 49, 2, 2, 423, 424, 3, 2, 2, 2, 424, 425, 8, 20, 6, 2, 425, 44, 3, 2, 2, 2, 426, 428, 9, 3, 2, 2, 427, 426, 3, 2, 2, 2, 428, 429, 3, 2, 2, 2, 429, 427, 3, 2, 2, 2, 429, 430, 3, 2, 2, 2, 430, 431, 3, 2, 2, 2, 431, 432, 8, 21, 6, 2, 432, 46, 3, 2, 2, 2, 433, 434, 7, 93, 2, 2, 434, 435, 3, 2, 2, 2, 435, 436, 8, 22, 7, 2, 436, 437, 8, 22, 8, 2, 437, 48, 3, 2, 2, 2, 438, 439, 7, 126, 2, 2, 439, 440, 3, 2, 2, 2, 440, 441, 8, 23, 9, 2, 441, 442, 8, 23, 10, 2, 442, 50, 3, 2, 2, 2, 443, 444, 5, 45, 21, 2, 444, 445, 3, 2, 2, 2, 445, 446, 8, 24, 6, 2, 446, 52, 3, 2, 2, 2, 447, 448, 5, 41, 19, 2, 448, 449, 3, 2, 2, 2, 449, 450, 8, 25, 6, 2, 450, 54, 3, 2, 2, 2, 451, 452, 5, 43, 20, 2, 452, 453, 3, 2, 2, 2, 453, 454, 8, 26, 6, 2, 454, 56, 3, 2, 2, 2, 455, 456, 7, 126, 2, 2, 456, 457, 3, 2, 2, 2, 457, 458, 8, 27, 10, 2, 458, 58, 3, 2, 2, 2, 459, 460, 9, 4, 2, 2, 460, 60, 3, 2, 2, 2, 461, 462, 9, 5, 2, 2, 462, 62, 3, 2, 2, 2, 463, 464, 7, 94, 2, 2, 464, 465, 9, 6, 2, 2, 465, 64, 3, 2, 2, 2, 466, 467, 10, 7, 2, 2, 467, 66, 3, 2, 2, 2, 468, 470, 9, 8, 2, 2, 469, 471, 9, 9, 2, 2, 470, 469, 3, 2, 2, 2, 470, 471, 3, 2, 2, 2, 471, 473, 3, 2, 2, 2, 472, 474, 5, 59, 28, 2, 473, 472, 3, 2, 2, 2, 474, 475, 3, 2, 2, 2, 475, 473, 3, 2, 2, 2, 475, 476, 3, 2, 2, 2, 476, 68, 3, 2, 2, 2, 477, 482, 7, 36, 2, 2, 478, 481, 5, 63, 30, 2, 479, 481, 5, 65, 31, 2, 480, 478, 3, 2, 2, 2, 480, 479, 3, 2, 2, 2, 481, 484, 3, 2, 2, 2, 482, 480, 3, 2, 2, 2, 482, 483, 3, 2, 2, 2, 483, 485, 3, 2, 2, 2, 484, 482, 3, 2, 2, 2, 485, 507, 7, 36, 2, 2, 486, 487, 7, 36, 2, 2, 487, 488, 7, 36, 2, 2, 488, 489, 7, 36, 2, 2, 489, 493, 3, 2, 2, 2, 490, 492, 10, 2, 2, 2, 491, 490, 3, 2, 2, 2, 492, 495, 3, 2, 2, 2, 493, 494, 3, 2, 2, 2, 493, 491, 3, 2, 2, 2, 494, 496, 3, 2, 2, 2, 495, 493, 3, 2, 2, 2, 496, 497, 7, 36, 2, 2, 497, 498, 7, 36, 2, 2, 498, 499, 7, 36, 2, 2, 499, 501, 3, 2, 2, 2, 500, 502, 7, 36, 2, 2, 501, 500, 3, 2, 2, 2, 501, 502, 3, 2, 2, 2, 502, 504, 3, 2, 2, 2, 503, 505, 7, 36, 2, 2, 504, 503, 3, 2, 2, 2, 504, 505, 3, 2, 2, 2, 505, 507, 3, 2, 2, 2, 506, 477, 3, 2, 2, 2, 506, 486, 3, 2, 2, 2, 507, 70, 3, 2, 2, 2, 508, 510, 5, 59, 28, 2, 509, 508, 3, 2, 2, 2, 510, 511, 3, 2, 2, 2, 511, 509, 3, 2, 2, 2, 511, 512, 3, 2, 2, 2, 512, 72, 3, 2, 2, 2, 513, 515, 5, 59, 28, 2, 514, 513, 3, 2, 2, 2, 515, 516, 3, 2, 2, 2, 516, 514, 3, 2, 2, 2, 516, 517, 3, 2, 2, 2, 517, 518, 3, 2, 2, 2, 518, 522, 5, 85, 41, 2, 519, 521, 5, 59, 28, 2, 520, 519, 3, 2, 2, 2, 521, 524, 3, 2, 2, 2, 522, 520, 3, 2, 2, 2, 522, 523, 3, 2, 2, 2, 523, 556, 3, 2, 2, 2, 524, 522, 3, 2, 2, 2, 525, 527, 5, 85, 41, 2, 526, 528, 5, 59, 28, 2, 527, 526, 3, 2, 2, 2, 528, 529, 3, 2, 2, 2, 529, 527, 3, 2, 2, 2, 529, 530, 3, 2, 2, 2, 530, 556, 3, 2, 2, 2, 531, 533, 5, 59, 28, 2, 532, 531, 3, 2, 2, 2, 533, 534, 3, 2, 2, 2, 534, 532, 3, 2, 2, 2, 534, 535, 3, 2, 2, 2, 535, 543, 3, 2, 2, 2, 536, 540, 5, 85, 41, 2, 537, 539, 5, 59, 28, 2, 538, 537, 3, 2, 2, 2, 539, 542, 3, 2, 2, 2, 540, 538, 3, 2, 2, 2, 540, 541, 3, 2, 2, 2, 541, 544, 3, 2, 2, 2, 542, 540, 3, 2, 2, 2, 543, 536, 3, 2, 2, 2, 543, 544, 3, 2, 2, 2, 544, 545, 3, 2, 2, 2, 545, 546, 5, 67, 32, 2, 546, 556, 3, 2, 2, 2, 547, 549, 5, 85, 41, 2, 548, 550, 5, 59, 28, 2, 549, 548, 3, 2, 2, 2, 550, 551, 3, 2, 2, 2, 551, 549, 3, 2, 2, 2, 551, 552, 3, 2, 2, 2, 552, 553, 3, 2, 2, 2, 553, 554, 5, 67, 32, 2, 554, 556, 3, 2, 2, 2, 555, 514, 3, 2, 2, 2, 555, 525, 3, 2, 2, 2, 555, 532, 3, 2, 2, 2, 555, 547, 3, 2, 2, 2, 556, 74, 3, 2, 2, 2, 557, 558, 7, 100, 2, 2, 558, 559, 7, 123, 2, 2, 559, 76, 3, 2, 2, 2, 560, 561, 7, 123, 2, 2, 561, 562, 7, 103, 2, 2, 562, 563, 7, 99, 2, 2, 563, 655, 7, 116, 2, 2, 564, 565, 7, 111, 2, 2, 565, 566, 7, 113, 2, 2, 566, 567, 7, 112, 2, 2, 567, 568, 7, 118, 2, 2, 568, 655, 7, 106, 2, 2, 569, 570, 7, 102, 2, 2, 570, 571, 7, 99, 2, 2, 571, 655, 7, 123, 2, 2, 572, 573, 7, 117, 2, 2, 573, 574, 7, 103, 2, 2, 574, 575, 7, 101, 2, 2, 575, 576, 7, 113, 2, 2, 576, 577, 7, 112, 2, 2, 577, 655, 7, 102, 2, 2, 578, 579, 7, 111, 2, 2, 579, 580, 7, 107, 2, 2, 580, 581, 7, 112, 2, 2, 581, 582, 7, 119, 2, 2, 582, 583, 7, 118, 2, 2, 583, 655, 7, 103, 2, 2, 584, 585, 7, 106, 2, 2, 585, 586, 7, 113, 2, 2, 586, 587, 7, 119, 2, 2, 587, 655, 7, 116, 2, 2, 588, 589, 7, 121, 2, 2, 589, 590, 7, 103, 2, 2, 590, 591, 7, 103, 2, 2, 591, 655, 7, 109, 2, 2, 592, 593, 7, 111, 2, 2, 593, 594, 7, 107, 2, 2, 594, 595, 7, 110, 2, 2, 595, 596, 7, 110, 2, 2, 596, 597, 7, 107, 2, 2, 597, 598, 7, 117, 2, 2, 598, 599, 7, 103, 2, 2, 599, 600, 7, 101, 2, 2, 600, 601, 7, 113, 2, 2, 601, 602, 7, 112, 2, 2, 602, 655, 7, 102, 2, 2, 603, 604, 7, 123, 2, 2, 604, 605, 7, 103, 2, 2, 605, 606, 7, 99, 2, 2, 606, 607, 7, 116, 2, 2, 607, 655, 7, 117, 2, 2, 608, 609, 7, 111, 2, 2, 609, 610, 7, 113, 2, 2, 610, 611, 7, 112, 2, 2, 611, 612, 7, 118, 2, 2, 612, 613, 7, 106, 2, 2, 613, 655, 7, 117, 2, 2, 614, 615, 7, 102, 2, 2, 615, 616, 7, 99, 2, 2, 616, 617, 7, 123, 2, 2, 617, 655, 7, 117, 2, 2, 618, 619, 7, 117, 2, 2, 619, 620, 7, 103, 2, 2, 620, 621, 7, 101, 2, 2, 621, 622, 7, 113, 2, 2, 622, 623, 7, 112, 2, 2, 623, 624, 7, 102, 2, 2, 624, 655, 7, 117, 2, 2, 625, 626, 7, 111, 2, 2, 626, 627, 7, 107, 2, 2, 627, 628, 7, 112, 2, 2, 628, 629, 7, 119, 2, 2, 629, 630, 7, 118, 2, 2, 630, 631, 7, 103, 2, 2, 631, 655, 7, 117, 2, 2, 632, 633, 7, 106, 2, 2, 633, 634, 7, 113, 2, 2, 634, 635, 7, 119, 2, 2, 635, 636, 7, 116, 2, 2, 636, 655, 7, 117, 2, 2, 637, 638, 7, 121, 2, 2, 638, 639, 7, 103, 2, 2, 639, 640, 7, 103, 2, 2, 640, 641, 7, 109, 2, 2, 641, 655, 7, 117, 2, 2, 642, 643, 7, 111, 2, 2, 643, 644, 7, 107, 2, 2, 644, 645, 7, 110, 2, 2, 645, 646, 7, 110, 2, 2, 646, 647, 7, 107, 2, 2, 647, 648, 7, 117, 2, 2, 648, 649, 7, 103, 2, 2, 649, 650, 7, 101, 2, 2, 650, 651, 7, 113, 2, 2, 651, 652, 7, 112, 2, 2, 652, 653, 7, 102, 2, 2, 653, 655, 7, 117, 2, 2, 654, 560, 3, 2, 2, 2, 654, 564, 3, 2, 2, 2, 654, 569, 3, 2, 2, 2, 654, 572, 3, 2, 2, 2, 654, 578, 3, 2, 2, 2, 654, 584, 3, 2, 2, 2, 654, 588, 3, 2, 2, 2, 654, 592, 3, 2, 2, 2, 654, 603, 3, 2, 2, 2, 654, 608, 3, 2, 2, 2, 654, 614, 3, 2, 2, 2, 654, 618, 3, 2, 2, 2, 654, 625, 3, 2, 2, 2, 654, 632, 3, 2, 2, 2, 654, 637, 3, 2, 2, 2, 654, 642, 3, 2, 2, 2, 655, 78, 3, 2, 2, 2, 656, 657, 7, 99, 2, 2, 657, 658, 7, 112, 2, 2, 658, 659, 7, 102, 2, 2, 659, 80, 3, 2, 2, 2, 660, 661, 7, 63, 2, 2, 661, 82, 3, 2, 2, 2, 662, 663, 7, 46, 2, 2, 663, 84, 3, 2, 2, 2, 664, 665, 7, 48, 2, 2, 665, 86, 3, 2, 2, 2, 666, 667, 7, 42, 2, 2, 667, 88, 3, 2, 2, 2, 668, 669, 7, 93, 2, 2, 669, 670, 3, 2, 2, 2, 670, 671, 8, 43, 2, 2, 671, 672, 8, 43, 2, 2, 672, 90, 3, 2, 2, 2, 673, 674, 7, 95, 2, 2, 674, 675, 3, 2, 2, 2, 675, 676, 8, 44, 10, 2, 676, 677, 8, 44, 10, 2, 677, 92, 3, 2, 2, 2, 678, 679, 5, 229, 113, 2, 679, 680, 5, 231, 114, 2, 680, 681, 5, 241, 119, 2, 681, 94, 3, 2, 2, 2, 682, 683, 5, 225, 111, 2, 683, 684, 5, 219, 108, 2, 684, 685, 5, 223, 110, 2, 685, 686, 5, 211, 104, 2, 686, 96, 3, 2, 2, 2, 687, 688, 5, 237, 117, 2, 688, 689, 5, 225, 111, 2, 689, 690, 5, 219, 108, 2, 690, 691, 5, 223, 110, 2, 691, 692, 5, 211, 104, 2, 692, 98, 3, 2, 2, 2, 693, 694, 5, 219, 108, 2, 694, 695, 5, 229, 113, 2, 695, 100, 3, 2, 2, 2, 696, 697, 5, 219, 108, 2, 697, 698, 5, 239, 118, 2, 698, 102, 3, 2, 2, 2, 699, 700, 5, 203, 100, 2, 700, 701, 5, 239, 118, 2, 701, 104, 3, 2, 2, 2, 702, 703, 5, 229, 113, 2, 703, 704, 5, 243, 120, 2, 704, 705, 5, 225, 111, 2, 705, 706, 5, 225, 111, 2, 706, 106, 3, 2, 2, 2, 707, 708, 7, 113, 2, 2, 708, 709, 7, 116, 2, 2, 709, 108, 3, 2, 2, 2, 710, 711, 7, 43, 2, 2, 711, 110, 3, 2, 2, 2, 712, 713, 7, 97, 2, 2, 713, 112, 3, 2, 2, 2, 714, 715, 7, 107, 2, 2, 715, 716, 7, 112, 2, 2, 716, 717, 7, 104, 2, 2, 717, 718, 7, 113, 2, 2, 718, 114, 3, 2, 2, 2, 719, 720, 7, 104, 2, 2, 720, 721, 7, 119, 2, 2, 721, 722, 7, 112, 2, 2, 722, 723, 7, 101, 2, 2, 723, 724, 7, 118, 2, 2, 724, 725, 7, 107, 2, 2, 725, 726, 7, 113, 2, 2, 726, 727, 7, 112, 2, 2, 727, 728, 7, 117, 2, 2, 728, 116, 3, 2, 2, 2, 729, 730, 7, 118, 2, 2, 730, 731, 7, 116, 2, 2, 731, 732, 7, 119, 2, 2, 732, 739, 7, 103, 2, 2, 733, 734, 7, 104, 2, 2, 734, 735, 7, 99, 2, 2, 735, 736, 7, 110, 2, 2, 736, 737, 7, 117, 2, 2, 737, 739, 7, 103, 2, 2, 738, 729, 3, 2, 2, 2, 738, 733, 3, 2, 2, 2, 739, 118, 3, 2, 2, 2, 740, 741, 7, 63, 2, 2, 741, 751, 7, 63, 2, 2, 742, 743, 7, 35, 2, 2, 743, 751, 7, 63, 2, 2, 744, 751, 7, 62, 2, 2, 745, 746, 7, 62, 2, 2, 746, 751, 7, 63, 2, 2, 747, 751, 7, 64, 2, 2, 748, 749, 7, 64, 2, 2, 749, 751, 7, 63, 2, 2, 750, 740, 3, 2, 2, 2, 750, 742, 3, 2, 2, 2, 750, 744, 3, 2, 2, 2, 750, 745, 3, 2, 2, 2, 750, 747, 3, 2, 2, 2, 750, 748, 3, 2, 2, 2, 751, 120, 3, 2, 2, 2, 752, 753, 7, 45, 2, 2, 753, 122, 3, 2, 2, 2, 754, 755, 7, 47, 2, 2, 755, 124, 3, 2, 2, 2, 756, 757, 7, 44, 2, 2, 757, 126, 3, 2, 2, 2, 758, 759, 7, 49, 2, 2, 759, 128, 3, 2, 2, 2, 760, 761, 7, 39, 2, 2, 761, 130, 3, 2, 2, 2, 762, 763, 7, 51, 2, 2, 763, 764, 7, 50, 2, 2, 764, 132, 3, 2, 2, 2, 765, 766, 7, 99, 2, 2, 766, 767, 7, 117, 2, 2, 767, 773, 7, 101, 2, 2, 768, 769, 7, 102, 2, 2, 769, 770, 7, 103, 2, 2, 770, 771, 7, 117, 2, 2, 771, 773, 7, 101, 2, 2, 772, 765, 3, 2, 2, 2, 772, 768, 3, 2, 2, 2, 773, 134, 3, 2, 2, 2, 774, 775, 7, 112, 2, 2, 775, 776, 7, 119, 2, 2, 776, 777, 7, 110, 2, 2, 777, 778, 7, 110, 2, 2, 778, 779, 7, 117, 2, 2, 779, 136, 3, 2, 2, 2, 780, 781, 7, 104, 2, 2, 781, 782, 7, 107, 2, 2, 782, 783, 7, 116, 2, 2, 783, 784, 7, 117, 2, 2, 784, 790, 7, 118, 2, 2, 785, 786, 7, 110, 2, 2, 786, 787, 7, 99, 2, 2, 787, 788, 7, 117, 2, 2, 788, 790, 7, 118, 2, 2, 789, 780, 3, 2, 2, 2, 789, 785, 3, 2, 2, 2, 790, 138, 3, 2, 2, 2, 791, 792, 5, 237, 117, 2, 792, 793, 5, 231, 114, 2, 793, 794, 5, 243, 120, 2, 794, 795, 5, 229, 113, 2, 795, 796, 5, 209, 103, 2, 796, 1222, 3, 2, 2, 2, 797, 798, 5, 203, 100, 2, 798, 799, 5, 205, 101, 2, 799, 800, 5, 239, 118, 2, 800, 1222, 3, 2, 2, 2, 801, 802, 5, 233, 115, 2, 802, 803, 5, 231, 114, 2, 803, 804, 5, 247, 122, 2, 804, 1222, 3, 2, 2, 2, 805, 806, 5, 225, 111, 2, 806, 807, 5, 231, 114, 2, 807, 808, 5, 215, 106, 2, 808, 809, 5, 131, 64, 2, 809, 1222, 3, 2, 2, 2, 810, 811, 5, 233, 115, 2, 811, 812, 5, 219, 108, 2, 812, 1222, 3, 2, 2, 2, 813, 814, 5, 241, 119, 2, 814, 815, 5, 203, 100, 2, 815, 816, 5, 243, 120, 2, 816, 1222, 3, 2, 2, 2, 817, 1222, 5, 211, 104, 2, 818, 819, 5, 239, 118, 2, 819, 820, 5, 243, 120, 2, 820, 821, 5, 205, 101, 2, 821, 822, 5, 239, 118, 2, 822, 823, 5, 241, 119, 2, 823, 824, 5, 237, 117, 2, 824, 825, 5, 219, 108, 2, 825, 826, 5, 229, 113, 2, 826, 827, 5, 215, 106, 2, 827, 1222, 3, 2, 2, 2, 828, 829, 5, 241, 119, 2, 829, 830, 5, 237, 117, 2, 830, 831, 5, 219, 108, 2, 831, 832, 5, 227, 112, 2, 832, 1222, 3, 2, 2, 2, 833, 834, 5, 207, 102, 2, 834, 835, 5, 231, 114, 2, 835, 836, 5, 229, 113, 2, 836, 837, 5, 207, 102, 2, 837, 838, 5, 203, 100, 2, 838, 839, 5, 241, 119, 2, 839, 1222, 3, 2, 2, 2, 840, 841, 5, 207, 102, 2, 841, 842, 5, 231, 114, 2, 842, 843, 5, 203, 100, 2, 843, 844, 5, 225, 111, 2, 844, 845, 5, 211, 104, 2, 845, 846, 5, 239, 118, 2, 846, 847, 5, 207, 102, 2, 847, 848, 5, 211, 104, 2, 848, 1222, 3, 2, 2, 2, 849, 850, 5, 215, 106, 2, 850, 851, 5, 237, 117, 2, 851, 852, 5, 211, 104, 2, 852, 853, 5, 203, 100, 2, 853, 854, 5, 241, 119, 2, 854, 855, 5, 211, 104, 2, 855, 856, 5, 239, 118, 2, 856, 857, 5, 241, 119, 2, 857, 1222, 3, 2, 2, 2, 858, 859, 5, 225, 111, 2, 859, 860, 5, 211, 104, 2, 860, 861, 5, 213, 105, 2, 861, 862, 5, 241, 119, 2, 862, 1222, 3, 2, 2, 2, 863, 864, 5, 229, 113, 2, 864, 865, 5, 231, 114, 2, 865, 866, 5, 247, 122, 2, 866, 1222, 3, 2, 2, 2, 867, 868, 5, 237, 117, 2, 868, 869, 5, 219, 108, 2, 869, 870, 5, 215, 106, 2, 870, 871, 5, 217, 107, 2, 871, 872, 5, 241, 119, 2, 872, 1222, 3, 2, 2, 2, 873, 874, 5, 239, 118, 2, 874, 875, 5, 241, 119, 2, 875, 876, 5, 203, 100, 2, 876, 877, 5, 237, 117, 2, 877, 878, 5, 241, 119, 2, 878, 879, 5, 239, 118, 2, 879, 880, 5, 111, 54, 2, 880, 881, 5, 247, 122, 2, 881, 882, 5, 219, 108, 2, 882, 883, 5, 241, 119, 2, 883, 884, 5, 217, 107, 2, 884, 1222, 3, 2, 2, 2, 885, 886, 5, 209, 103, 2, 886, 887, 5, 203, 100, 2, 887, 888, 5, 241, 119, 2, 888, 889, 5, 211, 104, 2, 889, 890, 5, 111, 54, 2, 890, 891, 5, 213, 105, 2, 891, 892, 5, 231, 114, 2, 892, 893, 5, 237, 117, 2, 893, 894, 5, 227, 112, 2, 894, 895, 5, 203, 100, 2, 895, 896, 5, 241, 119, 2, 896, 1222, 3, 2, 2, 2, 897, 898, 5, 209, 103, 2, 898, 899, 5, 203, 100, 2, 899, 900, 5, 241, 119, 2, 900, 901, 5, 211, 104, 2, 901, 902, 5, 111, 54, 2, 902, 903, 5, 241, 119, 2, 903, 904, 5, 237, 117, 2, 904, 905, 5, 243, 120, 2, 905, 906, 5, 229, 113, 2, 906, 907, 5, 207, 102, 2, 907, 1222, 3, 2, 2, 2, 908, 909, 5, 209, 103, 2, 909, 910, 5, 203, 100, 2, 910, 911, 5, 241, 119, 2, 911, 912, 5, 211, 104, 2, 912, 913, 5, 111, 54, 2, 913, 914, 5, 233, 115, 2, 914, 915, 5, 203, 100, 2, 915, 916, 5, 237, 117, 2, 916, 917, 5, 239, 118, 2, 917, 918, 5, 211, 104, 2, 918, 1222, 3, 2, 2, 2, 919, 920, 5, 203, 100, 2, 920, 921, 5, 243, 120, 2, 921, 922, 5, 241, 119, 2, 922, 923, 5, 231, 114, 2, 923, 924, 5, 111, 54, 2, 924, 925, 5, 205, 101, 2, 925, 926, 5, 243, 120, 2, 926, 927, 5, 207, 102, 2, 927, 928, 5, 223, 110, 2, 928, 929, 5, 211, 104, 2, 929, 930, 5, 241, 119, 2, 930, 1222, 3, 2, 2, 2, 931, 932, 5, 209, 103, 2, 932, 933, 5, 203, 100, 2, 933, 934, 5, 241, 119, 2, 934, 935, 5, 211, 104, 2, 935, 936, 5, 111, 54, 2, 936, 937, 5, 211, 104, 2, 937, 938, 5, 249, 123, 2, 938, 939, 5, 241, 119, 2, 939, 940, 5, 237, 117, 2, 940, 941, 5, 203, 100, 2, 941, 942, 5, 207, 102, 2, 942, 943, 5, 241, 119, 2, 943, 1222, 3, 2, 2, 2, 944, 945, 5, 219, 108, 2, 945, 946, 5, 239, 118, 2, 946, 947, 5, 111, 54, 2, 947, 948, 5, 213, 105, 2, 948, 949, 5, 219, 108, 2, 949, 950, 5, 229, 113, 2, 950, 951, 5, 219, 108, 2, 951, 952, 5, 241, 119, 2, 952, 953, 5, 211, 104, 2, 953, 1222, 3, 2, 2, 2, 954, 955, 5, 219, 108, 2, 955, 956, 5, 239, 118, 2, 956, 957, 5, 111, 54, 2, 957, 958, 5, 219, 108, 2, 958, 959, 5, 229, 113, 2, 959, 960, 5, 213, 105, 2, 960, 961, 5, 219, 108, 2, 961, 962, 5, 229, 113, 2, 962, 963, 5, 219, 108, 2, 963, 964, 5, 241, 119, 2, 964, 965, 5, 211, 104, 2, 965, 1222, 3, 2, 2, 2, 966, 967, 5, 207, 102, 2, 967, 968, 5, 203, 100, 2, 968, 969, 5, 239, 118, 2, 969, 970, 5, 211, 104, 2, 970, 1222, 3, 2, 2, 2, 971, 972, 5, 225, 111, 2, 972, 973, 5, 211, 104, 2, 973, 974, 5, 229, 113, 2, 974, 975, 5, 215, 106, 2, 975, 976, 5, 241, 119, 2, 976, 977, 5, 217, 107, 2, 977, 1222, 3, 2, 2, 2, 978, 979, 5, 227, 112, 2, 979, 980, 5, 245, 121, 2, 980, 981, 5, 111, 54, 2, 981, 982, 5, 227, 112, 2, 982, 983, 5, 203, 100, 2, 983, 984, 5, 249, 123, 2, 984, 1222, 3, 2, 2, 2, 985, 986, 5, 227, 112, 2, 986, 987, 5, 245, 121, 2, 987, 988, 5, 111, 54, 2, 988, 989, 5, 227, 112, 2, 989, 990, 5, 219, 108, 2, 990, 991, 5, 229, 113, 2, 991, 1222, 3, 2, 2, 2, 992, 993, 5, 227, 112, 2, 993, 994, 5, 245, 121, 2, 994, 995, 5, 111, 54, 2, 995, 996, 5, 203, 100, 2, 996, 997, 5, 245, 121, 2, 997, 998, 5, 215, 106, 2, 998, 1222, 3, 2, 2, 2, 999, 1000, 5, 227, 112, 2, 1000, 1001, 5, 245, 121, 2, 1001, 1002, 5, 111, 54, 2, 1002, 1003, 5, 239, 118, 2, 1003, 1004, 5, 243, 120, 2, 1004, 1005, 5, 227, 112, 2, 1005, 1222, 3, 2, 2, 2, 1006, 1007, 5, 227, 112, 2, 1007, 1008, 5, 245, 121, 2, 1008, 1009, 5, 111, 54, 2, 1009, 1010, 5, 207, 102, 2, 1010, 1011, 5, 231, 114, 2, 1011, 1012, 5, 243, 120, 2, 1012, 1013, 5, 229, 113, 2, 1013, 1014, 5, 241, 119, 2, 1014, 1222, 3, 2, 2, 2, 1015, 1016, 5, 227, 112, 2, 1016, 1017, 5, 245, 121, 2, 1017, 1018, 5, 111, 54, 2, 1018, 1019, 5, 207, 102, 2, 1019, 1020, 5, 231, 114, 2, 1020, 1021, 5, 229, 113, 2, 1021, 1022, 5, 207, 102, 2, 1022, 1023, 5, 203, 100, 2, 1023, 1024, 5, 241, 119, 2, 1024, 1222, 3, 2, 2, 2, 1025, 1026, 5, 227, 112, 2, 1026, 1027, 5, 245, 121, 2, 1027, 1028, 5, 111, 54, 2, 1028, 1029, 5, 221, 109, 2, 1029, 1030, 5, 231, 114, 2, 1030, 1031, 5, 219, 108, 2, 1031, 1032, 5, 229, 113, 2, 1032, 1222, 3, 2, 2, 2, 1033, 1034, 5, 227, 112, 2, 1034, 1035, 5, 245, 121, 2, 1035, 1036, 5, 111, 54, 2, 1036, 1037, 5, 227, 112, 2, 1037, 1038, 5, 211, 104, 2, 1038, 1039, 5, 209, 103, 2, 1039, 1040, 5, 219, 108, 2, 1040, 1041, 5, 203, 100, 2, 1041, 1042, 5, 229, 113, 2, 1042, 1222, 3, 2, 2, 2, 1043, 1044, 5, 227, 112, 2, 1044, 1045, 5, 245, 121, 2, 1045, 1046, 5, 111, 54, 2, 1046, 1047, 5, 209, 103, 2, 1047, 1048, 5, 211, 104, 2, 1048, 1049, 5, 209, 103, 2, 1049, 1050, 5, 243, 120, 2, 1050, 1051, 5, 233, 115, 2, 1051, 1052, 5, 211, 104, 2, 1052, 1222, 3, 2, 2, 2, 1053, 1054, 5, 227, 112, 2, 1054, 1055, 5, 211, 104, 2, 1055, 1056, 5, 241, 119, 2, 1056, 1057, 5, 203, 100, 2, 1057, 1058, 5, 209, 103, 2, 1058, 1059, 5, 203, 100, 2, 1059, 1060, 5, 241, 119, 2, 1060, 1061, 5, 203, 100, 2, 1061, 1222, 3, 2, 2, 2, 1062, 1063, 5, 239, 118, 2, 1063, 1064, 5, 233, 115, 2, 1064, 1065, 5, 225, 111, 2, 1065, 1066, 5, 219, 108, 2, 1066, 1067, 5, 241, 119, 2, 1067, 1222, 3, 2, 2, 2, 1068, 1069, 5, 241, 119, 2, 1069, 1070, 5, 231, 114, 2, 1070, 1071, 5, 111, 54, 2, 1071, 1072, 5, 239, 118, 2, 1072, 1073, 5, 241, 119, 2, 1073, 1074, 5, 237, 117, 2, 1074, 1075, 5, 219, 108, 2, 1075, 1076, 5, 229, 113, 2, 1076, 1077, 5, 215, 106, 2, 1077, 1222, 3, 2, 2, 2, 1078, 1079, 5, 241, 119, 2, 1079, 1080, 5, 231, 114, 2, 1080, 1081, 5, 111, 54, 2, 1081, 1082, 5, 239, 118, 2, 1082, 1083, 5, 241, 119, 2, 1083, 1084, 5, 237, 117, 2, 1084, 1222, 3, 2, 2, 2, 1085, 1086, 5, 241, 119, 2, 1086, 1087, 5, 231, 114, 2, 1087, 1088, 5, 111, 54, 2, 1088, 1089, 5, 205, 101, 2, 1089, 1090, 5, 231, 114, 2, 1090, 1091, 5, 231, 114, 2, 1091, 1092, 5, 225, 111, 2, 1092, 1222, 3, 2, 2, 2, 1093, 1094, 5, 241, 119, 2, 1094, 1095, 5, 231, 114, 2, 1095, 1096, 5, 111, 54, 2, 1096, 1097, 5, 205, 101, 2, 1097, 1098, 5, 231, 114, 2, 1098, 1099, 5, 231, 114, 2, 1099, 1100, 5, 225, 111, 2, 1100, 1101, 5, 211, 104, 2, 1101, 1102, 5, 203, 100, 2, 1102, 1103, 5, 229, 113, 2, 1103, 1222, 3, 2, 2, 2, 1104, 1105, 5, 241, 119, 2, 1105, 1106, 5, 231, 114, 2, 1106, 1107, 5, 111, 54, 2, 1107, 1108, 5, 209, 103, 2, 1108, 1109, 5, 203, 100, 2, 1109, 1110, 5, 241, 119, 2, 1110, 1111, 5, 211, 104, 2, 1111, 1112, 5, 241, 119, 2, 1112, 1113, 5, 219, 108, 2, 1113, 1114, 5, 227, 112, 2, 1114, 1115, 5, 211, 104, 2, 1115, 1222, 3, 2, 2, 2, 1116, 1117, 5, 241, 119, 2, 1117, 1118, 5, 231, 114, 2, 1118, 1119, 5, 111, 54, 2, 1119, 1120, 5, 209, 103, 2, 1120, 1121, 5, 241, 119, 2, 1121, 1222, 3, 2, 2, 2, 1122, 1123, 5, 241, 119, 2, 1123, 1124, 5, 231, 114, 2, 1124, 1125, 5, 111, 54, 2, 1125, 1126, 5, 209, 103, 2, 1126, 1127, 5, 205, 101, 2, 1127, 1128, 5, 225, 111, 2, 1128, 1222, 3, 2, 2, 2, 1129, 1130, 5, 241, 119, 2, 1130, 1131, 5, 231, 114, 2, 1131, 1132, 5, 111, 54, 2, 1132, 1133, 5, 209, 103, 2, 1133, 1134, 5, 231, 114, 2, 1134, 1135, 5, 243, 120, 2, 1135, 1136, 5, 205, 101, 2, 1136, 1137, 5, 225, 111, 2, 1137, 1138, 5, 211, 104, 2, 1138, 1222, 3, 2, 2, 2, 1139, 1140, 5, 241, 119, 2, 1140, 1141, 5, 231, 114, 2, 1141, 1142, 5, 111, 54, 2, 1142, 1143, 5, 209, 103, 2, 1143, 1144, 5, 211, 104, 2, 1144, 1145, 5, 215, 106, 2, 1145, 1146, 5, 237, 117, 2, 1146, 1147, 5, 211, 104, 2, 1147, 1148, 5, 211, 104, 2, 1148, 1149, 5, 239, 118, 2, 1149, 1222, 3, 2, 2, 2, 1150, 1151, 5, 241, 119, 2, 1151, 1152, 5, 231, 114, 2, 1152, 1153, 5, 111, 54, 2, 1153, 1154, 5, 219, 108, 2, 1154, 1155, 5, 229, 113, 2, 1155, 1156, 5, 241, 119, 2, 1156, 1222, 3, 2, 2, 2, 1157, 1158, 5, 241, 119, 2, 1158, 1159, 5, 231, 114, 2, 1159, 1160, 5, 111, 54, 2, 1160, 1161, 5, 219, 108, 2, 1161, 1162, 5, 229, 113, 2, 1162, 1163, 5, 241, 119, 2, 1163, 1164, 5, 211, 104, 2, 1164, 1165, 5, 215, 106, 2, 1165, 1166, 5, 211, 104, 2, 1166, 1167, 5, 237, 117, 2, 1167, 1222, 3, 2, 2, 2, 1168, 1169, 5, 241, 119, 2, 1169, 1170, 5, 231, 114, 2, 1170, 1171, 5, 111, 54, 2, 1171, 1172, 5, 219, 108, 2, 1172, 1173, 5, 233, 115, 2, 1173, 1222, 3, 2, 2, 2, 1174, 1175, 5, 241, 119, 2, 1175, 1176, 5, 231, 114, 2, 1176, 1177, 5, 111, 54, 2, 1177, 1178, 5, 225, 111, 2, 1178, 1179, 5, 231, 114, 2, 1179, 1180, 5, 229, 113, 2, 1180, 1181, 5, 215, 106, 2, 1181, 1222, 3, 2, 2, 2, 1182, 1183, 5, 241, 119, 2, 1183, 1184, 5, 231, 114, 2, 1184, 1185, 5, 111, 54, 2, 1185, 1186, 5, 237, 117, 2, 1186, 1187, 5, 203, 100, 2, 1187, 1188, 5, 209, 103, 2, 1188, 1189, 5, 219, 108, 2, 1189, 1190, 5, 203, 100, 2, 1190, 1191, 5, 229, 113, 2, 1191, 1192, 5, 239, 118, 2, 1192, 1222, 3, 2, 2, 2, 1193, 1194, 5, 241, 119, 2, 1194, 1195, 5, 231, 114, 2, 1195, 1196, 5, 111, 54, 2, 1196, 1197, 5, 245, 121, 2, 1197, 1198, 5, 211, 104, 2, 1198, 1199, 5, 237, 117, 2, 1199, 1200, 5, 239, 118, 2, 1200, 1201, 5, 219, 108, 2, 1201, 1202, 5, 231, 114, 2, 1202, 1203, 5, 229, 113, 2, 1203, 1222, 3, 2, 2, 2, 1204, 1205, 5, 241, 119, 2, 1205, 1206, 5, 231, 114, 2, 1206, 1207, 5, 111, 54, 2, 1207, 1208, 5, 243, 120, 2, 1208, 1209, 5, 229, 113, 2, 1209, 1210, 5, 239, 118, 2, 1210, 1211, 5, 219, 108, 2, 1211, 1212, 5, 215, 106, 2, 1212, 1213, 5, 229, 113, 2, 1213, 1214, 5, 211, 104, 2, 1214, 1215, 5, 209, 103, 2, 1215, 1216, 5, 111, 54, 2, 1216, 1217, 5, 225, 111, 2, 1217, 1218, 5, 231, 114, 2, 1218, 1219, 5, 229, 113, 2, 1219, 1220, 5, 215, 106, 2, 1220, 1222, 3, 2, 2, 2, 1221, 791, 3, 2, 2, 2, 1221, 797, 3, 2, 2, 2, 1221, 801, 3, 2, 2, 2, 1221, 805, 3, 2, 2, 2, 1221, 810, 3, 2, 2, 2, 1221, 813, 3, 2, 2, 2, 1221, 817, 3, 2, 2, 2, 1221, 818, 3, 2, 2, 2, 1221, 828, 3, 2, 2, 2, 1221, 833, 3, 2, 2, 2, 1221, 840, 3, 2, 2, 2, 1221, 849, 3, 2, 2, 2, 1221, 858, 3, 2, 2, 2, 1221, 863, 3, 2, 2, 2, 1221, 867, 3, 2, 2, 2, 1221, 873, 3, 2, 2, 2, 1221, 885, 3, 2, 2, 2, 1221, 897, 3, 2, 2, 2, 1221, 908, 3, 2, 2, 2, 1221, 919, 3, 2, 2, 2, 1221, 931, 3, 2, 2, 2, 1221, 944, 3, 2, 2, 2, 1221, 954, 3, 2, 2, 2, 1221, 966, 3, 2, 2, 2, 1221, 971, 3, 2, 2, 2, 1221, 978, 3, 2, 2, 2, 1221, 985, 3, 2, 2, 2, 1221, 992, 3, 2, 2, 2, 1221, 999, 3, 2, 2, 2, 1221, 1006, 3, 2, 2, 2, 1221, 1015, 3, 2, 2, 2, 1221, 1025, 3, 2, 2, 2, 1221, 1033, 3, 2, 2, 2, 1221, 1043, 3, 2, 2, 2, 1221, 1053, 3, 2, 2, 2, 1221, 1062, 3, 2, 2, 2, 1221, 1068, 3, 2, 2, 2, 1221, 1078, 3, 2, 2, 2, 1221, 1085, 3, 2, 2, 2, 1221, 1093, 3, 2, 2, 2, 1221, 1104, 3, 2, 2, 2, 1221, 1116, 3, 2, 2, 2, 1221, 1122, 3, 2, 2, 2, 1221, 1129, 3, 2, 2, 2, 1221, 1139, 3, 2, 2, 2, 1221, 1150, 3, 2, 2, 2, 1221, 1157, 3, 2, 2, 2, 1221, 1168, 3, 2, 2, 2, 1221, 1174, 3, 2, 2, 2, 1221, 1182, 3, 2, 2, 2, 1221, 1193, 3, 2, 2, 2, 1221, 1204, 3, 2, 2, 2, 1222, 140, 3, 2, 2, 2, 1223, 1224, 5, 203, 100, 2, 1224, 1225, 5, 245, 121, 2, 1225, 1226, 5, 215, 106, 2, 1226, 1375, 3, 2, 2, 2, 1227, 1228, 5, 227, 112, 2, 1228, 1229, 5, 219, 108, 2, 1229, 1230, 5, 229, 113, 2, 1230, 1375, 3, 2, 2, 2, 1231, 1232, 5, 227, 112, 2, 1232, 1233, 5, 203, 100, 2, 1233, 1234, 5, 249, 123, 2, 1234, 1375, 3, 2, 2, 2, 1235, 1236, 5, 239, 118, 2, 1236, 1237, 5, 243, 120, 2, 1237, 1238, 5, 227, 112, 2, 1238, 1375, 3, 2, 2, 2, 1239, 1240, 5, 207, 102, 2, 1240, 1241, 5, 231, 114, 2, 1241, 1242, 5, 243, 120, 2, 1242, 1243, 5, 229, 113, 2, 1243, 1244, 5, 241, 119, 2, 1244, 1375, 3, 2, 2, 2, 1245, 1246, 5, 207, 102, 2, 1246, 1247, 5, 231, 114, 2, 1247, 1248, 5, 243, 120, 2, 1248, 1249, 5, 229, 113, 2, 1249, 1250, 5, 241, 119, 2, 1250, 1251, 5, 111, 54, 2, 1251, 1252, 5, 209, 103, 2, 1252, 1253, 5, 219, 108, 2, 1253, 1254, 5, 239, 118, 2, 1254, 1255, 5, 241, 119, 2, 1255, 1256, 5, 219, 108, 2, 1256, 1257, 5, 229, 113, 2, 1257, 1258, 5, 207, 102, 2, 1258, 1259, 5, 241, 119, 2, 1259, 1375, 3, 2, 2, 2, 1260, 1261, 5, 233, 115, 2, 1261, 1262, 5, 211, 104, 2, 1262, 1263, 5, 237, 117, 2, 1263, 1264, 5, 207, 102, 2, 1264, 1265, 5, 211, 104, 2, 1265, 1266, 5, 229, 113, 2, 1266, 1267, 5, 241, 119, 2, 1267, 1268, 5, 219, 108, 2, 1268, 1269, 5, 225, 111, 2, 1269, 1270, 5, 211, 104, 2, 1270, 1375, 3, 2, 2, 2, 1271, 1272, 5, 227, 112, 2, 1272, 1273, 5, 211, 104, 2, 1273, 1274, 5, 209, 103, 2, 1274, 1275, 5, 219, 108, 2, 1275, 1276, 5, 203, 100, 2, 1276, 1277, 5, 229, 113, 2, 1277, 1375, 3, 2, 2, 2, 1278, 1279, 5, 227, 112, 2, 1279, 1280, 5, 211, 104, 2, 1280, 1281, 5, 209, 103, 2, 1281, 1282, 5, 219, 108, 2, 1282, 1283, 5, 203, 100, 2, 1283, 1284, 5, 229, 113, 2, 1284, 1285, 5, 111, 54, 2, 1285, 1286, 5, 203, 100, 2, 1286, 1287, 5, 205, 101, 2, 1287, 1288, 5, 239, 118, 2, 1288, 1289, 5, 231, 114, 2, 1289, 1290, 5, 225, 111, 2, 1290, 1291, 5, 243, 120, 2, 1291, 1292, 5, 241, 119, 2, 1292, 1293, 5, 211, 104, 2, 1293, 1294, 5, 111, 54, 2, 1294, 1295, 5, 209, 103, 2, 1295, 1296, 5, 211, 104, 2, 1296, 1297, 5, 245, 121, 2, 1297, 1298, 5, 219, 108, 2, 1298, 1299, 5, 203, 100, 2, 1299, 1300, 5, 241, 119, 2, 1300, 1301, 5, 219, 108, 2, 1301, 1302, 5, 231, 114, 2, 1302, 1303, 5, 229, 113, 2, 1303, 1375, 3, 2, 2, 2, 1304, 1305, 5, 203, 100, 2, 1305, 1306, 5, 207, 102, 2, 1306, 1307, 5, 231, 114, 2, 1307, 1308, 5, 239, 118, 2, 1308, 1375, 3, 2, 2, 2, 1309, 1310, 5, 203, 100, 2, 1310, 1311, 5, 239, 118, 2, 1311, 1312, 5, 219, 108, 2, 1312, 1313, 5, 229, 113, 2, 1313, 1375, 3, 2, 2, 2, 1314, 1315, 5, 203, 100, 2, 1315, 1316, 5, 241, 119, 2, 1316, 1317, 5, 203, 100, 2, 1317, 1318, 5, 229, 113, 2, 1318, 1375, 3, 2, 2, 2, 1319, 1320, 5, 203, 100, 2, 1320, 1321, 5, 241, 119, 2, 1321, 1322, 5, 203, 100, 2, 1322, 1323, 5, 229, 113, 2, 1323, 1324, 7, 52, 2, 2, 1324, 1375, 3, 2, 2, 2, 1325, 1326, 5, 207, 102, 2, 1326, 1327, 5, 211, 104, 2, 1327, 1328, 5, 219, 108, 2, 1328, 1329, 5, 225, 111, 2, 1329, 1375, 3, 2, 2, 2, 1330, 1331, 5, 207, 102, 2, 1331, 1332, 5, 231, 114, 2, 1332, 1333, 5, 239, 118, 2, 1333, 1375, 3, 2, 2, 2, 1334, 1335, 5, 207, 102, 2, 1335, 1336, 5, 231, 114, 2, 1336, 1337, 5, 239, 118, 2, 1337, 1338, 5, 217, 107, 2, 1338, 1375, 3, 2, 2, 2, 1339, 1340, 5, 213, 105, 2, 1340, 1341, 5, 225, 111, 2, 1341, 1342, 5, 231, 114, 2, 1342, 1343, 5, 231, 114, 2, 1343, 1344, 5, 237, 117, 2, 1344, 1375, 3, 2, 2, 2, 1345, 1346, 5, 225, 111, 2, 1346, 1347, 5, 241, 119, 2, 1347, 1348, 5, 237, 117, 2, 1348, 1349, 5, 219, 108, 2, 1349, 1350, 5, 227, 112, 2, 1350, 1375, 3, 2, 2, 2, 1351, 1352, 5, 239, 118, 2, 1352, 1353, 5, 219, 108, 2, 1353, 1354, 5, 229, 113, 2, 1354, 1375, 3, 2, 2, 2, 1355, 1356, 5, 239, 118, 2, 1356, 1357, 5, 219, 108, 2, 1357, 1358, 5, 229, 113, 2, 1358, 1359, 5, 217, 107, 2, 1359, 1375, 3, 2, 2, 2, 1360, 1361, 5, 239, 118, 2, 1361, 1362, 5, 235, 116, 2, 1362, 1363, 5, 237, 117, 2, 1363, 1364, 5, 241, 119, 2, 1364, 1375, 3, 2, 2, 2, 1365, 1366, 5, 241, 119, 2, 1366, 1367, 5, 203, 100, 2, 1367, 1368, 5, 229, 113, 2, 1368, 1375, 3, 2, 2, 2, 1369, 1370, 5, 241, 119, 2, 1370, 1371, 5, 203, 100, 2, 1371, 1372, 5, 229, 113, 2, 1372, 1373, 5, 217, 107, 2, 1373, 1375, 3, 2, 2, 2, 1374, 1223, 3, 2, 2, 2, 1374, 1227, 3, 2, 2, 2, 1374, 1231, 3, 2, 2, 2, 1374, 1235, 3, 2, 2, 2, 1374, 1239, 3, 2, 2, 2, 1374, 1245, 3, 2, 2, 2, 1374, 1260, 3, 2, 2, 2, 1374, 1271, 3, 2, 2, 2, 1374, 1278, 3, 2, 2, 2, 1374, 1304, 3, 2, 2, 2, 1374, 1309, 3, 2, 2, 2, 1374, 1314, 3, 2, 2, 2, 1374, 1319, 3, 2, 2, 2, 1374, 1325, 3, 2, 2, 2, 1374, 1330, 3, 2, 2, 2, 1374, 1334, 3, 2, 2, 2, 1374, 1339, 3, 2, 2, 2, 1374, 1345, 3, 2, 2, 2, 1374, 1351, 3, 2, 2, 2, 1374, 1355, 3, 2, 2, 2, 1374, 1360, 3, 2, 2, 2, 1374, 1365, 3, 2, 2, 2, 1374, 1369, 3, 2, 2, 2, 1375, 142, 3, 2, 2, 2, 1376, 1377, 5, 207, 102, 2, 1377, 1378, 5, 219, 108, 2, 1378, 1379, 5, 209, 103, 2, 1379, 1380, 5, 237, 117, 2, 1380, 1381, 5, 111, 54, 2, 1381, 1382, 5, 227, 112, 2, 1382, 1383, 5, 203, 100, 2, 1383, 1384, 5, 241, 119, 2, 1384, 1385, 5, 207, 102, 2, 1385, 1386, 5, 217, 107, 2, 1386, 144, 3, 2, 2, 2, 1387, 1394, 5, 61, 29, 2, 1388, 1393, 5, 61, 29, 2, 1389, 1393, 5, 59, 28, 2, 1390, 1393, 7, 97, 2, 2, 1391, 1393, 5, 125, 61, 2, 1392, 1388, 3, 2, 2, 2, 1392, 1389, 3, 2, 2, 2, 1392, 1390, 3, 2, 2, 2, 1392, 1391, 3, 2, 2, 2, 1393, 1396, 3, 2, 2, 2, 1394, 1392, 3, 2, 2, 2, 1394, 1395, 3, 2, 2, 2, 1395, 1407, 3, 2, 2, 2, 1396, 1394, 3, 2, 2, 2, 1397, 1402, 9, 10, 2, 2, 1398, 1403, 5, 61, 29, 2, 1399, 1403, 5, 59, 28, 2, 1400, 1403, 7, 97, 2, 2, 1401, 1403, 5, 125, 61, 2, 1402, 1398, 3, 2, 2, 2, 1402, 1399, 3, 2, 2, 2, 1402, 1400, 3, 2, 2, 2, 1402, 1401, 3, 2, 2, 2, 1403, 1404, 3, 2, 2, 2, 1404, 1402, 3, 2, 2, 2, 1404, 1405, 3, 2, 2, 2, 1405, 1407, 3, 2, 2, 2, 1406, 1387, 3, 2, 2, 2, 1406, 1397, 3, 2, 2, 2, 1407, 146, 3, 2, 2, 2, 1408, 1414, 7, 98, 2, 2, 1409, 1413, 10, 11, 2, 2, 1410, 1411, 7, 98, 2, 2, 1411, 1413, 7, 98, 2, 2, 1412, 1409, 3, 2, 2, 2, 1412, 1410, 3, 2, 2, 2, 1413, 1416, 3, 2, 2, 2, 1414, 1412, 3, 2, 2, 2, 1414, 1415, 3, 2, 2, 2, 1415, 1417, 3, 2, 2, 2, 1416, 1414, 3, 2, 2, 2, 1417, 1418, 7, 98, 2, 2, 1418, 148, 3, 2, 2, 2, 1419, 1420, 5, 41, 19, 2, 1420, 1421, 3, 2, 2, 2, 1421, 1422, 8, 73, 6, 2, 1422, 150, 3, 2, 2, 2, 1423, 1424, 5, 43, 20, 2, 1424, 1425, 3, 2, 2, 2, 1425, 1426, 8, 74, 6, 2, 1426, 152, 3, 2, 2, 2, 1427, 1428, 5, 45, 21, 2, 1428, 1429, 3, 2, 2, 2, 1429, 1430, 8, 75, 6, 2, 1430, 154, 3, 2, 2, 2, 1431, 1432, 7, 126, 2, 2, 1432, 1433, 3, 2, 2, 2, 1433, 1434, 8, 76, 9, 2, 1434, 1435, 8, 76, 10, 2, 1435, 156, 3, 2, 2, 2, 1436, 1437, 7, 93, 2, 2, 1437, 1438, 3, 2, 2, 2, 1438, 1439, 8, 77, 7, 2, 1439, 1440, 8, 77, 4, 2, 1440, 1441, 8, 77, 4, 2, 1441, 158, 3, 2, 2, 2, 1442, 1443, 7, 95, 2, 2, 1443, 1444, 3, 2, 2, 2, 1444, 1445, 8, 78, 10, 2, 1445, 1446, 8, 78, 10, 2, 1446, 1447, 8, 78, 11, 2, 1447, 160, 3, 2, 2, 2, 1448, 1449, 7, 46, 2, 2, 1449, 1450, 3, 2, 2, 2, 1450, 1451, 8, 79, 12, 2, 1451, 162, 3, 2, 2, 2, 1452, 1453, 7, 63, 2, 2, 1453, 1454, 3, 2, 2, 2, 1454, 1455, 8, 80, 13, 2, 1455, 164, 3, 2, 2, 2, 1456, 1457, 5, 227, 112, 2, 1457, 1458, 5, 211, 104, 2, 1458, 1459, 5, 241, 119, 2, 1459, 1460, 5, 203, 100, 2, 1460, 1461, 5, 209, 103, 2, 1461, 1462, 5, 203, 100, 2, 1462, 1463, 5, 241, 119, 2, 1463, 1464, 5, 203, 100, 2, 1464, 166, 3, 2, 2, 2, 1465, 1467, 5, 169, 83, 2, 1466, 1465, 3, 2, 2, 2, 1467, 1468, 3, 2, 2, 2, 1468, 1466, 3, 2, 2, 2, 1468, 1469, 3, 2, 2, 2, 1469, 168, 3, 2, 2, 2, 1470, 1472, 10, 12, 2, 2, 1471, 1470, 3, 2, 2, 2, 1472, 1473, 3, 2, 2, 2, 1473, 1471, 3, 2, 2, 2, 1473, 1474, 3, 2, 2, 2, 1474, 1478, 3, 2, 2, 2, 1475, 1476, 7, 49, 2, 2, 1476, 1478, 10, 13, 2, 2, 1477, 1471, 3, 2, 2, 2, 1477, 1475, 3, 2, 2, 2, 1478, 170, 3, 2, 2, 2, 1479, 1480, 5, 147, 72, 2, 1480, 172, 3, 2, 2, 2, 1481, 1482, 5, 41, 19, 2, 1482, 1483, 3, 2, 2, 2, 1483, 1484, 8, 85, 6, 2, 1484, 174, 3, 2, 2, 2, 1485, 1486, 5, 43, 20, 2, 1486, 1487, 3, 2, 2, 2, 1487, 1488, 8, 86, 6, 2, 1488, 176, 3, 2, 2, 2, 1489, 1490, 5, 45, 21, 2, 1490, 1491, 3, 2, 2, 2, 1491, 1492, 8, 87, 6, 2, 1492, 178, 3, 2, 2, 2, 1493, 1494, 5, 231, 114, 2, 1494, 1495, 5, 229, 113, 2, 1495, 180, 3, 2, 2, 2, 1496, 1497, 5, 247, 122, 2, 1497, 1498, 5, 219, 108, 2, 1498, 1499, 5, 241, 119, 2, 1499, 1500, 5, 217, 107, 2, 1500, 182, 3, 2, 2, 2, 1501, 1502, 7, 126, 2, 2, 1502, 1503, 3, 2, 2, 2, 1503, 1504, 8, 90, 9, 2, 1504, 1505, 8, 90, 10, 2, 1505, 184, 3, 2, 2, 2, 1506, 1507, 7, 95, 2, 2, 1507, 1508, 3, 2, 2, 2, 1508, 1509, 8, 91, 10, 2, 1509, 1510, 8, 91, 10, 2, 1510, 1511, 8, 91, 11, 2, 1511, 186, 3, 2, 2, 2, 1512, 1513, 7, 46, 2, 2, 1513, 1514, 3, 2, 2, 2, 1514, 1515, 8, 92, 12, 2, 1515, 188, 3, 2, 2, 2, 1516, 1517, 7, 63, 2, 2, 1517, 1518, 3, 2, 2, 2, 1518, 1519, 8, 93, 13, 2, 1519, 190, 3, 2, 2, 2, 1520, 1522, 5, 193, 95, 2, 1521, 1520, 3, 2, 2, 2, 1522, 1523, 3, 2, 2, 2, 1523, 1521, 3, 2, 2, 2, 1523, 1524, 3, 2, 2, 2, 1524, 192, 3, 2, 2, 2, 1525, 1527, 10, 12, 2, 2, 1526, 1525, 3, 2, 2, 2, 1527, 1528, 3, 2, 2, 2, 1528, 1526, 3, 2, 2, 2, 1528, 1529, 3, 2, 2, 2, 1529, 1533, 3, 2, 2, 2, 1530, 1531, 7, 49, 2, 2, 1531, 1533, 10, 13, 2, 2, 1532, 1526, 3, 2, 2, 2, 1532, 1530, 3, 2, 2, 2, 1533, 194, 3, 2, 2, 2, 1534, 1535, 5, 147, 72, 2, 1535, 196, 3, 2, 2, 2, 1536, 1537, 5, 41, 19, 2, 1537, 1538, 3, 2, 2, 2, 1538, 1539, 8, 97, 6, 2, 1539, 198, 3, 2, 2, 2, 1540, 1541, 5, 43, 20, 2, 1541, 1542, 3, 2, 2, 2, 1542, 1543, 8, 98, 6, 2, 1543, 200, 3, 2, 2, 2, 1544, 1545, 5, 45, 21, 2, 1545, 1546, 3, 2, 2, 2, 1546, 1547, 8, 99, 6, 2, 1547, 202, 3, 2, 2, 2, 1548, 1549, 9, 14, 2, 2, 1549, 204, 3, 2, 2, 2, 1550, 1551, 9, 15, 2, 2, 1551, 206, 3, 2, 2, 2, 1552, 1553, 9, 16, 2, 2, 1553, 208, 3, 2, 2, 2, 1554, 1555, 9, 17, 2, 2, 1555, 210, 3, 2, 2, 2, 1556, 1557, 9, 8, 2, 2, 1557, 212, 3, 2, 2, 2, 1558, 1559, 9, 18, 2, 2, 1559, 214, 3, 2, 2, 2, 1560, 1561, 9, 19, 2, 2, 1561, 216, 3, 2, 2, 2, 1562, 1563, 9, 20, 2, 2, 1563, 218, 3, 2, 2, 2, 1564, 1565, 9, 21, 2, 2, 1565, 220, 3, 2, 2, 2, 1566, 1567, 9, 22, 2, 2, 1567, 222, 3, 2, 2, 2, 1568, 1569, 9, 23, 2, 2, 1569, 224, 3, 2, 2, 2, 1570, 1571, 9, 24, 2, 2, 1571, 226, 3, 2, 2, 2, 1572, 1573, 9, 25, 2, 2, 1573, 228, 3, 2, 2, 2, 1574, 1575, 9, 26, 2, 2, 1575, 230, 3, 2, 2, 2, 1576, 1577, 9, 27, 2, 2, 1577, 232, 3, 2, 2, 2, 1578, 1579, 9, 28, 2, 2, 1579, 234, 3, 2, 2, 2, 1580, 1581, 9, 29, 2, 2, 1581, 236, 3, 2, 2, 2, 1582, 1583, 9, 30, 2, 2, 1583, 238, 3, 2, 2, 2, 1584, 1585, 9, 31, 2, 2, 1585, 240, 3, 2, 2, 2, 1586, 1587, 9, 32, 2, 2, 1587, 242, 3, 2, 2, 2, 1588, 1589, 9, 33, 2, 2, 1589, 244, 3, 2, 2, 2, 1590, 1591, 9, 34, 2, 2, 1591, 246, 3, 2, 2, 2, 1592, 1593, 9, 35, 2, 2, 1593, 248, 3, 2, 2, 2, 1594, 1595, 9, 36, 2, 2, 1595, 250, 3, 2, 2, 2, 1596, 1597, 9, 37, 2, 2, 1597, 252, 3, 2, 2, 2, 1598, 1599, 9, 38, 2, 2, 1599, 254, 3, 2, 2, 2, 50, 2, 3, 4, 5, 6, 400, 404, 407, 416, 418, 429, 470, 475, 480, 482, 493, 501, 504, 506, 511, 516, 522, 529, 534, 540, 543, 551, 555, 654, 738, 750, 772, 789, 1221, 1374, 1392, 1394, 1402, 1404, 1406, 1412, 1414, 1468, 1473, 1477, 1523, 1528, 1532, 14, 7, 4, 2, 7, 3, 2, 7, 5, 2, 7, 6, 2, 2, 3, 2, 9, 37, 2, 7, 2, 2, 9, 26, 2, 6, 2, 2, 9, 38, 2, 9, 34, 2, 9, 33, 2] \ No newline at end of file diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens b/packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens index c2dafff2f222c..b72e97b9a2961 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens @@ -1,74 +1,98 @@ -EVAL=1 -EXPLAIN=2 -FROM=3 -ROW=4 -STATS=5 -WHERE=6 -SORT=7 -LIMIT=8 -PROJECT=9 -LINE_COMMENT=10 -MULTILINE_COMMENT=11 -WS=12 -PIPE=13 -STRING=14 -INTEGER_LITERAL=15 -DECIMAL_LITERAL=16 -BY=17 -AND=18 -ASSIGN=19 -COMMA=20 -DOT=21 -LP=22 -OPENING_BRACKET=23 -CLOSING_BRACKET=24 -NOT=25 -NULL=26 -OR=27 -RP=28 -BOOLEAN_VALUE=29 -COMPARISON_OPERATOR=30 -PLUS=31 -MINUS=32 -ASTERISK=33 -SLASH=34 -PERCENT=35 -ORDERING=36 -NULLS_ORDERING=37 -NULLS_ORDERING_DIRECTION=38 -UNARY_FUNCTION=39 -UNQUOTED_IDENTIFIER=40 -QUOTED_IDENTIFIER=41 -EXPR_LINE_COMMENT=42 -EXPR_MULTILINE_COMMENT=43 -EXPR_WS=44 -SRC_UNQUOTED_IDENTIFIER=45 -SRC_QUOTED_IDENTIFIER=46 -SRC_LINE_COMMENT=47 -SRC_MULTILINE_COMMENT=48 -SRC_WS=49 -'eval'=1 -'explain'=2 -'from'=3 -'row'=4 -'stats'=5 -'where'=6 -'sort'=7 -'limit'=8 -'project'=9 -'by'=17 -'and'=18 -'.'=21 -'('=22 -'['=23 -']'=24 -'not'=25 -'null'=26 -'or'=27 -')'=28 -'+'=31 -'-'=32 -'*'=33 -'/'=34 -'%'=35 -'nulls'=37 +DISSECT=1 +GROK=2 +EVAL=3 +EXPLAIN=4 +FROM=5 +ROW=6 +STATS=7 +WHERE=8 +SORT=9 +MV_EXPAND=10 +LIMIT=11 +PROJECT=12 +DROP=13 +RENAME=14 +SHOW=15 +ENRICH=16 +KEEP=17 +LINE_COMMENT=18 +MULTILINE_COMMENT=19 +WS=20 +EXPLAIN_WS=21 +EXPLAIN_LINE_COMMENT=22 +EXPLAIN_MULTILINE_COMMENT=23 +PIPE=24 +STRING=25 +INTEGER_LITERAL=26 +DECIMAL_LITERAL=27 +BY=28 +DATE_LITERAL=29 +AND=30 +ASSIGN=31 +COMMA=32 +DOT=33 +LP=34 +OPENING_BRACKET=35 +CLOSING_BRACKET=36 +NOT=37 +LIKE=38 +RLIKE=39 +IN=40 +IS=41 +AS=42 +NULL=43 +OR=44 +RP=45 +UNDERSCORE=46 +INFO=47 +FUNCTIONS=48 +BOOLEAN_VALUE=49 +COMPARISON_OPERATOR=50 +PLUS=51 +MINUS=52 +ASTERISK=53 +SLASH=54 +PERCENT=55 +TEN=56 +ORDERING=57 +NULLS_ORDERING=58 +NULLS_ORDERING_DIRECTION=59 +MATH_FUNCTION=60 +UNARY_FUNCTION=61 +WHERE_FUNCTIONS=62 +UNQUOTED_IDENTIFIER=63 +QUOTED_IDENTIFIER=64 +EXPR_LINE_COMMENT=65 +EXPR_MULTILINE_COMMENT=66 +EXPR_WS=67 +METADATA=68 +SRC_UNQUOTED_IDENTIFIER=69 +SRC_QUOTED_IDENTIFIER=70 +SRC_LINE_COMMENT=71 +SRC_MULTILINE_COMMENT=72 +SRC_WS=73 +ON=74 +WITH=75 +ENR_UNQUOTED_IDENTIFIER=76 +ENR_QUOTED_IDENTIFIER=77 +ENR_LINE_COMMENT=78 +ENR_MULTILINE_COMMENT=79 +ENR_WS=80 +EXPLAIN_PIPE=81 +'by'=28 +'and'=30 +'.'=33 +'('=34 +']'=36 +'or'=44 +')'=45 +'_'=46 +'info'=47 +'functions'=48 +'+'=51 +'-'=52 +'*'=53 +'/'=54 +'%'=55 +'10'=56 +'nulls'=58 diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.ts b/packages/kbn-monaco/src/esql/antlr/esql_lexer.ts index 064b2fe2c02d1..1c5fc5a918aa4 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_lexer.ts +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.ts @@ -17,57 +17,91 @@ import * as Utils from "antlr4ts/misc/Utils"; export class esql_lexer extends Lexer { - public static readonly EVAL = 1; - public static readonly EXPLAIN = 2; - public static readonly FROM = 3; - public static readonly ROW = 4; - public static readonly STATS = 5; - public static readonly WHERE = 6; - public static readonly SORT = 7; - public static readonly LIMIT = 8; - public static readonly PROJECT = 9; - public static readonly LINE_COMMENT = 10; - public static readonly MULTILINE_COMMENT = 11; - public static readonly WS = 12; - public static readonly PIPE = 13; - public static readonly STRING = 14; - public static readonly INTEGER_LITERAL = 15; - public static readonly DECIMAL_LITERAL = 16; - public static readonly BY = 17; - public static readonly AND = 18; - public static readonly ASSIGN = 19; - public static readonly COMMA = 20; - public static readonly DOT = 21; - public static readonly LP = 22; - public static readonly OPENING_BRACKET = 23; - public static readonly CLOSING_BRACKET = 24; - public static readonly NOT = 25; - public static readonly NULL = 26; - public static readonly OR = 27; - public static readonly RP = 28; - public static readonly BOOLEAN_VALUE = 29; - public static readonly COMPARISON_OPERATOR = 30; - public static readonly PLUS = 31; - public static readonly MINUS = 32; - public static readonly ASTERISK = 33; - public static readonly SLASH = 34; - public static readonly PERCENT = 35; - public static readonly ORDERING = 36; - public static readonly NULLS_ORDERING = 37; - public static readonly NULLS_ORDERING_DIRECTION = 38; - public static readonly UNARY_FUNCTION = 39; - public static readonly UNQUOTED_IDENTIFIER = 40; - public static readonly QUOTED_IDENTIFIER = 41; - public static readonly EXPR_LINE_COMMENT = 42; - public static readonly EXPR_MULTILINE_COMMENT = 43; - public static readonly EXPR_WS = 44; - public static readonly SRC_UNQUOTED_IDENTIFIER = 45; - public static readonly SRC_QUOTED_IDENTIFIER = 46; - public static readonly SRC_LINE_COMMENT = 47; - public static readonly SRC_MULTILINE_COMMENT = 48; - public static readonly SRC_WS = 49; - public static readonly EXPRESSION = 1; - public static readonly SOURCE_IDENTIFIERS = 2; + public static readonly DISSECT = 1; + public static readonly GROK = 2; + public static readonly EVAL = 3; + public static readonly EXPLAIN = 4; + public static readonly FROM = 5; + public static readonly ROW = 6; + public static readonly STATS = 7; + public static readonly WHERE = 8; + public static readonly SORT = 9; + public static readonly MV_EXPAND = 10; + public static readonly LIMIT = 11; + public static readonly PROJECT = 12; + public static readonly DROP = 13; + public static readonly RENAME = 14; + public static readonly SHOW = 15; + public static readonly ENRICH = 16; + public static readonly KEEP = 17; + public static readonly LINE_COMMENT = 18; + public static readonly MULTILINE_COMMENT = 19; + public static readonly WS = 20; + public static readonly EXPLAIN_WS = 21; + public static readonly EXPLAIN_LINE_COMMENT = 22; + public static readonly EXPLAIN_MULTILINE_COMMENT = 23; + public static readonly PIPE = 24; + public static readonly STRING = 25; + public static readonly INTEGER_LITERAL = 26; + public static readonly DECIMAL_LITERAL = 27; + public static readonly BY = 28; + public static readonly DATE_LITERAL = 29; + public static readonly AND = 30; + public static readonly ASSIGN = 31; + public static readonly COMMA = 32; + public static readonly DOT = 33; + public static readonly LP = 34; + public static readonly OPENING_BRACKET = 35; + public static readonly CLOSING_BRACKET = 36; + public static readonly NOT = 37; + public static readonly LIKE = 38; + public static readonly RLIKE = 39; + public static readonly IN = 40; + public static readonly IS = 41; + public static readonly AS = 42; + public static readonly NULL = 43; + public static readonly OR = 44; + public static readonly RP = 45; + public static readonly UNDERSCORE = 46; + public static readonly INFO = 47; + public static readonly FUNCTIONS = 48; + public static readonly BOOLEAN_VALUE = 49; + public static readonly COMPARISON_OPERATOR = 50; + public static readonly PLUS = 51; + public static readonly MINUS = 52; + public static readonly ASTERISK = 53; + public static readonly SLASH = 54; + public static readonly PERCENT = 55; + public static readonly TEN = 56; + public static readonly ORDERING = 57; + public static readonly NULLS_ORDERING = 58; + public static readonly NULLS_ORDERING_DIRECTION = 59; + public static readonly MATH_FUNCTION = 60; + public static readonly UNARY_FUNCTION = 61; + public static readonly WHERE_FUNCTIONS = 62; + public static readonly UNQUOTED_IDENTIFIER = 63; + public static readonly QUOTED_IDENTIFIER = 64; + public static readonly EXPR_LINE_COMMENT = 65; + public static readonly EXPR_MULTILINE_COMMENT = 66; + public static readonly EXPR_WS = 67; + public static readonly METADATA = 68; + public static readonly SRC_UNQUOTED_IDENTIFIER = 69; + public static readonly SRC_QUOTED_IDENTIFIER = 70; + public static readonly SRC_LINE_COMMENT = 71; + public static readonly SRC_MULTILINE_COMMENT = 72; + public static readonly SRC_WS = 73; + public static readonly ON = 74; + public static readonly WITH = 75; + public static readonly ENR_UNQUOTED_IDENTIFIER = 76; + public static readonly ENR_QUOTED_IDENTIFIER = 77; + public static readonly ENR_LINE_COMMENT = 78; + public static readonly ENR_MULTILINE_COMMENT = 79; + public static readonly ENR_WS = 80; + public static readonly EXPLAIN_PIPE = 81; + public static readonly EXPLAIN_MODE = 1; + public static readonly EXPRESSION = 2; + public static readonly SOURCE_IDENTIFIERS = 3; + public static readonly ENRICH_IDENTIFIERS = 4; // tslint:disable:no-trailing-whitespace public static readonly channelNames: string[] = [ @@ -76,40 +110,58 @@ export class esql_lexer extends Lexer { // tslint:disable:no-trailing-whitespace public static readonly modeNames: string[] = [ - "DEFAULT_MODE", "EXPRESSION", "SOURCE_IDENTIFIERS", + "DEFAULT_MODE", "EXPLAIN_MODE", "EXPRESSION", "SOURCE_IDENTIFIERS", "ENRICH_IDENTIFIERS", ]; public static readonly ruleNames: string[] = [ - "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", "WHERE", "SORT", "LIMIT", "PROJECT", - "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", "LETTER", - "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "STRING", "INTEGER_LITERAL", - "DECIMAL_LITERAL", "BY", "AND", "ASSIGN", "COMMA", "DOT", "LP", "OPENING_BRACKET", - "CLOSING_BRACKET", "NOT", "NULL", "OR", "RP", "BOOLEAN_VALUE", "COMPARISON_OPERATOR", - "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "ORDERING", "NULLS_ORDERING", - "NULLS_ORDERING_DIRECTION", "UNARY_FUNCTION", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", - "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "SRC_PIPE", - "SRC_CLOSING_BRACKET", "SRC_COMMA", "SRC_ASSIGN", "SRC_UNQUOTED_IDENTIFIER", - "SRC_UNQUOTED_IDENTIFIER_PART", "SRC_QUOTED_IDENTIFIER", "SRC_LINE_COMMENT", - "SRC_MULTILINE_COMMENT", "SRC_WS", + "DISSECT", "GROK", "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", "WHERE", + "SORT", "MV_EXPAND", "LIMIT", "PROJECT", "DROP", "RENAME", "SHOW", "ENRICH", + "KEEP", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "EXPLAIN_OPENING_BRACKET", + "EXPLAIN_PIPE", "EXPLAIN_WS", "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", + "PIPE", "DIGIT", "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", + "STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "DATE_LITERAL", + "AND", "ASSIGN", "COMMA", "DOT", "LP", "OPENING_BRACKET", "CLOSING_BRACKET", + "NOT", "LIKE", "RLIKE", "IN", "IS", "AS", "NULL", "OR", "RP", "UNDERSCORE", + "INFO", "FUNCTIONS", "BOOLEAN_VALUE", "COMPARISON_OPERATOR", "PLUS", "MINUS", + "ASTERISK", "SLASH", "PERCENT", "TEN", "ORDERING", "NULLS_ORDERING", "NULLS_ORDERING_DIRECTION", + "MATH_FUNCTION", "UNARY_FUNCTION", "WHERE_FUNCTIONS", "UNQUOTED_IDENTIFIER", + "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", + "SRC_PIPE", "SRC_OPENING_BRACKET", "SRC_CLOSING_BRACKET", "SRC_COMMA", + "SRC_ASSIGN", "METADATA", "SRC_UNQUOTED_IDENTIFIER", "SRC_UNQUOTED_IDENTIFIER_PART", + "SRC_QUOTED_IDENTIFIER", "SRC_LINE_COMMENT", "SRC_MULTILINE_COMMENT", + "SRC_WS", "ON", "WITH", "ENR_PIPE", "ENR_CLOSING_BRACKET", "ENR_COMMA", + "ENR_ASSIGN", "ENR_UNQUOTED_IDENTIFIER", "ENR_UNQUOTED_IDENTIFIER_PART", + "ENR_QUOTED_IDENTIFIER", "ENR_LINE_COMMENT", "ENR_MULTILINE_COMMENT", + "ENR_WS", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", + "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", ]; private static readonly _LITERAL_NAMES: Array = [ - undefined, "'eval'", "'explain'", "'from'", "'row'", "'stats'", "'where'", - "'sort'", "'limit'", "'project'", undefined, undefined, undefined, undefined, - undefined, undefined, undefined, "'by'", "'and'", undefined, undefined, - "'.'", "'('", "'['", "']'", "'not'", "'null'", "'or'", "')'", undefined, - undefined, "'+'", "'-'", "'*'", "'/'", "'%'", undefined, "'nulls'", + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + "'by'", undefined, "'and'", undefined, undefined, "'.'", "'('", undefined, + "']'", undefined, undefined, undefined, undefined, undefined, undefined, + undefined, "'or'", "')'", "'_'", "'info'", "'functions'", undefined, undefined, + "'+'", "'-'", "'*'", "'/'", "'%'", "'10'", undefined, "'nulls'", ]; private static readonly _SYMBOLIC_NAMES: Array = [ - undefined, "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", "WHERE", "SORT", - "LIMIT", "PROJECT", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", - "STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASSIGN", - "COMMA", "DOT", "LP", "OPENING_BRACKET", "CLOSING_BRACKET", "NOT", "NULL", - "OR", "RP", "BOOLEAN_VALUE", "COMPARISON_OPERATOR", "PLUS", "MINUS", "ASTERISK", - "SLASH", "PERCENT", "ORDERING", "NULLS_ORDERING", "NULLS_ORDERING_DIRECTION", - "UNARY_FUNCTION", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", - "EXPR_MULTILINE_COMMENT", "EXPR_WS", "SRC_UNQUOTED_IDENTIFIER", "SRC_QUOTED_IDENTIFIER", - "SRC_LINE_COMMENT", "SRC_MULTILINE_COMMENT", "SRC_WS", + undefined, "DISSECT", "GROK", "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", + "WHERE", "SORT", "MV_EXPAND", "LIMIT", "PROJECT", "DROP", "RENAME", "SHOW", + "ENRICH", "KEEP", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "EXPLAIN_WS", + "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", "PIPE", "STRING", + "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "DATE_LITERAL", "AND", "ASSIGN", + "COMMA", "DOT", "LP", "OPENING_BRACKET", "CLOSING_BRACKET", "NOT", "LIKE", + "RLIKE", "IN", "IS", "AS", "NULL", "OR", "RP", "UNDERSCORE", "INFO", "FUNCTIONS", + "BOOLEAN_VALUE", "COMPARISON_OPERATOR", "PLUS", "MINUS", "ASTERISK", "SLASH", + "PERCENT", "TEN", "ORDERING", "NULLS_ORDERING", "NULLS_ORDERING_DIRECTION", + "MATH_FUNCTION", "UNARY_FUNCTION", "WHERE_FUNCTIONS", "UNQUOTED_IDENTIFIER", + "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", + "METADATA", "SRC_UNQUOTED_IDENTIFIER", "SRC_QUOTED_IDENTIFIER", "SRC_LINE_COMMENT", + "SRC_MULTILINE_COMMENT", "SRC_WS", "ON", "WITH", "ENR_UNQUOTED_IDENTIFIER", + "ENR_QUOTED_IDENTIFIER", "ENR_LINE_COMMENT", "ENR_MULTILINE_COMMENT", + "ENR_WS", "EXPLAIN_PIPE", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(esql_lexer._LITERAL_NAMES, esql_lexer._SYMBOLIC_NAMES, []); @@ -141,266 +193,783 @@ export class esql_lexer extends Lexer { // @Override public get modeNames(): string[] { return esql_lexer.modeNames; } - public static readonly _serializedATN: string = - "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x023\u0215\b\x01" + - "\b\x01\b\x01\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04" + - "\x06\t\x06\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f" + - "\t\f\x04\r\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11" + - "\x04\x12\t\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16" + - "\x04\x17\t\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B" + - "\x04\x1C\t\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!" + - "\t!\x04\"\t\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x04)\t" + - ")\x04*\t*\x04+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041\t1\x04" + - "2\t2\x043\t3\x044\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04" + - ";\t;\x04<\t<\x03\x02\x03\x02\x03\x02\x03\x02\x03\x02\x03\x02\x03\x02\x03" + - "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + - "\x03\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x05\x03" + - "\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x06\x03\x06\x03\x06\x03\x06\x03" + - "\x06\x03\x06\x03\x06\x03\x06\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03" + - "\x07\x03\x07\x03\x07\x03\b\x03\b\x03\b\x03\b\x03\b\x03\b\x03\b\x03\t\x03" + - "\t\x03\t\x03\t\x03\t\x03\t\x03\t\x03\t\x03\n\x03\n\x03\n\x03\n\x03\n\x03" + - "\n\x03\n\x03\n\x03\n\x03\n\x03\v\x03\v\x03\v\x03\v\x07\v\xC7\n\v\f\v\x0E" + - "\v\xCA\v\v\x03\v\x05\v\xCD\n\v\x03\v\x05\v\xD0\n\v\x03\v\x03\v\x03\f\x03" + - "\f\x03\f\x03\f\x03\f\x07\f\xD9\n\f\f\f\x0E\f\xDC\v\f\x03\f\x03\f\x03\f" + - "\x03\f\x03\f\x03\r\x06\r\xE4\n\r\r\r\x0E\r\xE5\x03\r\x03\r\x03\x0E\x03" + - "\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x10\x03\x10\x03\x11\x03\x11\x03" + - "\x11\x03\x12\x03\x12\x03\x13\x03\x13\x05\x13\xF9\n\x13\x03\x13\x06\x13" + - "\xFC\n\x13\r\x13\x0E\x13\xFD\x03\x14\x03\x14\x03\x14\x07\x14\u0103\n\x14" + - "\f\x14\x0E\x14\u0106\v\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03" + - "\x14\x07\x14\u010E\n\x14\f\x14\x0E\x14\u0111\v\x14\x03\x14\x03\x14\x03" + - "\x14\x03\x14\x03\x14\x05\x14\u0118\n\x14\x03\x14\x05\x14\u011B\n\x14\x05" + - "\x14\u011D\n\x14\x03\x15\x06\x15\u0120\n\x15\r\x15\x0E\x15\u0121\x03\x16" + - "\x06\x16\u0125\n\x16\r\x16\x0E\x16\u0126\x03\x16\x03\x16\x07\x16\u012B" + - "\n\x16\f\x16\x0E\x16\u012E\v\x16\x03\x16\x03\x16\x06\x16\u0132\n\x16\r" + - "\x16\x0E\x16\u0133\x03\x16\x06\x16\u0137\n\x16\r\x16\x0E\x16\u0138\x03" + - "\x16\x03\x16\x07\x16\u013D\n\x16\f\x16\x0E\x16\u0140\v\x16\x05\x16\u0142" + - "\n\x16\x03\x16\x03\x16\x03\x16\x03\x16\x06\x16\u0148\n\x16\r\x16\x0E\x16" + - "\u0149\x03\x16\x03\x16\x05\x16\u014E\n\x16\x03\x17\x03\x17\x03\x17\x03" + - "\x18\x03\x18\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03" + - "\x1B\x03\x1C\x03\x1C\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03" + - "\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03" + - " \x03 \x03!\x03!\x03!\x03\"\x03\"\x03#\x03#\x03#\x03#\x03#\x03#\x03#\x03" + - "#\x03#\x05#\u017F\n#\x03$\x03$\x03$\x03$\x03$\x03$\x03$\x03$\x03$\x03" + - "$\x05$\u018B\n$\x03%\x03%\x03&\x03&\x03\'\x03\'\x03(\x03(\x03)\x03)\x03" + - "*\x03*\x03*\x03*\x03*\x03*\x03*\x05*\u019E\n*\x03+\x03+\x03+\x03+\x03" + - "+\x03+\x03,\x03,\x03,\x03,\x03,\x03,\x03,\x03,\x03,\x05,\u01AF\n,\x03" + - "-\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x03" + - "-\x03-\x03-\x05-\u01C2\n-\x03.\x03.\x05.\u01C6\n.\x03.\x03.\x03.\x07." + - "\u01CB\n.\f.\x0E.\u01CE\v.\x03/\x03/\x03/\x03/\x07/\u01D4\n/\f/\x0E/\u01D7" + - "\v/\x03/\x03/\x030\x030\x030\x030\x031\x031\x031\x031\x032\x032\x032\x03" + - "2\x033\x033\x033\x033\x033\x034\x034\x034\x034\x034\x034\x035\x035\x03" + - "5\x035\x036\x036\x036\x036\x037\x067\u01FB\n7\r7\x0E7\u01FC\x038\x068" + - "\u0200\n8\r8\x0E8\u0201\x038\x038\x058\u0206\n8\x039\x039\x03:\x03:\x03" + - ":\x03:\x03;\x03;\x03;\x03;\x03<\x03<\x03<\x03<\x04\xDA\u010F\x02\x02=" + - "\x05\x02\x03\x07\x02\x04\t\x02\x05\v\x02\x06\r\x02\x07\x0F\x02\b\x11\x02" + - "\t\x13\x02\n\x15\x02\v\x17\x02\f\x19\x02\r\x1B\x02\x0E\x1D\x02\x0F\x1F" + - "\x02\x02!\x02\x02#\x02\x02%\x02\x02\'\x02\x02)\x02\x10+\x02\x11-\x02\x12" + - "/\x02\x131\x02\x143\x02\x155\x02\x167\x02\x179\x02\x18;\x02\x19=\x02\x1A" + - "?\x02\x1BA\x02\x1CC\x02\x1DE\x02\x1EG\x02\x1FI\x02 K\x02!M\x02\"O\x02" + - "#Q\x02$S\x02%U\x02&W\x02\'Y\x02([\x02)]\x02*_\x02+a\x02,c\x02-e\x02.g" + - "\x02\x02i\x02\x02k\x02\x02m\x02\x02o\x02/q\x02\x02s\x020u\x021w\x022y" + - "\x023\x05\x02\x03\x04\r\x04\x02\f\f\x0F\x0F\x05\x02\v\f\x0F\x0F\"\"\x03" + - "\x022;\x04\x02C\\c|\x07\x02$$^^ppttvv\x06\x02\f\f\x0F\x0F$$^^\x04\x02" + - "GGgg\x04\x02--//\x03\x02bb\f\x02\v\f\x0F\x0F\"\"..11??]]__bb~~\x04\x02" + - ",,11\x02\u023A\x02\x05\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03" + - "\x02\x02\x02\x02\v\x03\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02" + - "\x02\x02\x02\x11\x03\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02" + - "\x02\x02\x02\x17\x03\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02" + - "\x02\x02\x03\x1D\x03\x02\x02\x02\x03)\x03\x02\x02\x02\x03+\x03\x02\x02" + - "\x02\x03-\x03\x02\x02\x02\x03/\x03\x02\x02\x02\x031\x03\x02\x02\x02\x03" + - "3\x03\x02\x02\x02\x035\x03\x02\x02\x02\x037\x03\x02\x02\x02\x039\x03\x02" + - "\x02\x02\x03;\x03\x02\x02\x02\x03=\x03\x02\x02\x02\x03?\x03\x02\x02\x02" + - "\x03A\x03\x02\x02\x02\x03C\x03\x02\x02\x02\x03E\x03\x02\x02\x02\x03G\x03" + - "\x02\x02\x02\x03I\x03\x02\x02\x02\x03K\x03\x02\x02\x02\x03M\x03\x02\x02" + - "\x02\x03O\x03\x02\x02\x02\x03Q\x03\x02\x02\x02\x03S\x03\x02\x02\x02\x03" + - "U\x03\x02\x02\x02\x03W\x03\x02\x02\x02\x03Y\x03\x02\x02\x02\x03[\x03\x02" + - "\x02\x02\x03]\x03\x02\x02\x02\x03_\x03\x02\x02\x02\x03a\x03\x02\x02\x02" + - "\x03c\x03\x02\x02\x02\x03e\x03\x02\x02\x02\x04g\x03\x02\x02\x02\x04i\x03" + + private static readonly _serializedATNSegments: number = 3; + private static readonly _serializedATNSegment0: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02S\u0640\b\x01" + + "\b\x01\b\x01\b\x01\b\x01\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04" + + "\x05\t\x05\x04\x06\t\x06\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04" + + "\v\t\v\x04\f\t\f\x04\r\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04" + + "\x11\t\x11\x04\x12\t\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04" + + "\x16\t\x16\x04\x17\t\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04" + + "\x1B\t\x1B\x04\x1C\t\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04" + + " \t \x04!\t!\x04\"\t\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(" + + "\t(\x04)\t)\x04*\t*\x04+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x04" + + "1\t1\x042\t2\x043\t3\x044\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04" + + ":\t:\x04;\t;\x04<\t<\x04=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x04" + + "C\tC\x04D\tD\x04E\tE\x04F\tF\x04G\tG\x04H\tH\x04I\tI\x04J\tJ\x04K\tK\x04" + + "L\tL\x04M\tM\x04N\tN\x04O\tO\x04P\tP\x04Q\tQ\x04R\tR\x04S\tS\x04T\tT\x04" + + "U\tU\x04V\tV\x04W\tW\x04X\tX\x04Y\tY\x04Z\tZ\x04[\t[\x04\\\t\\\x04]\t" + + "]\x04^\t^\x04_\t_\x04`\t`\x04a\ta\x04b\tb\x04c\tc\x04d\td\x04e\te\x04" + + "f\tf\x04g\tg\x04h\th\x04i\ti\x04j\tj\x04k\tk\x04l\tl\x04m\tm\x04n\tn\x04" + + "o\to\x04p\tp\x04q\tq\x04r\tr\x04s\ts\x04t\tt\x04u\tu\x04v\tv\x04w\tw\x04" + + "x\tx\x04y\ty\x04z\tz\x04{\t{\x04|\t|\x04}\t}\x03\x02\x03\x02\x03\x02\x03" + + "\x02\x03\x02\x03\x02\x03\x02\x03\x02\x03\x02\x03\x02\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x04\x03\x04\x03\x04\x03\x04\x03" + + "\x04\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03" + + "\x05\x03\x05\x03\x05\x03\x05\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03" + + "\x06\x03\x06\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\b\x03" + + "\b\x03\b\x03\b\x03\b\x03\b\x03\b\x03\b\x03\t\x03\t\x03\t\x03\t\x03\t\x03" + + "\t\x03\t\x03\t\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\v\x03\v\x03" + + "\v\x03\v\x03\v\x03\v\x03\v\x03\v\x03\v\x03\v\x03\v\x03\v\x03\f\x03\f\x03" + + "\f\x03\f\x03\f\x03\f\x03\f\x03\f\x03\r\x03\r\x03\r\x03\r\x03\r\x03\r\x03" + + "\r\x03\r\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03" + + "\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03" + + "\x0F\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03" + + "\x11\x03\x11\x03\x11\x03\x11\x03\x11\x03\x11\x03\x11\x03\x11\x03\x12\x03" + + "\x12\x03\x12\x03\x12\x03\x12\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03" + + "\x13\x07\x13\u018F\n\x13\f\x13\x0E\x13\u0192\v\x13\x03\x13\x05\x13\u0195" + + "\n\x13\x03\x13\x05\x13\u0198\n\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03" + + "\x14\x03\x14\x03\x14\x07\x14\u01A1\n\x14\f\x14\x0E\x14\u01A4\v\x14\x03" + + "\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03\x15\x06\x15\u01AC\n\x15\r\x15" + + "\x0E\x15\u01AD\x03\x15\x03\x15\x03\x16\x03\x16\x03\x16\x03\x16\x03\x16" + + "\x03\x17\x03\x17\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18\x03\x18\x03\x18" + + "\x03\x19\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1B" + + "\x03\x1B\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1D\x03\x1D\x03\x1E\x03\x1E" + + "\x03\x1E\x03\x1F\x03\x1F\x03 \x03 \x05 \u01D7\n \x03 \x06 \u01DA\n \r" + + " \x0E \u01DB\x03!\x03!\x03!\x07!\u01E1\n!\f!\x0E!\u01E4\v!\x03!\x03!\x03" + + "!\x03!\x03!\x03!\x07!\u01EC\n!\f!\x0E!\u01EF\v!\x03!\x03!\x03!\x03!\x03" + + "!\x05!\u01F6\n!\x03!\x05!\u01F9\n!\x05!\u01FB\n!\x03\"\x06\"\u01FE\n\"" + + "\r\"\x0E\"\u01FF\x03#\x06#\u0203\n#\r#\x0E#\u0204\x03#\x03#\x07#\u0209" + + "\n#\f#\x0E#\u020C\v#\x03#\x03#\x06#\u0210\n#\r#\x0E#\u0211\x03#\x06#\u0215" + + "\n#\r#\x0E#\u0216\x03#\x03#\x07#\u021B\n#\f#\x0E#\u021E\v#\x05#\u0220" + + "\n#\x03#\x03#\x03#\x03#\x06#\u0226\n#\r#\x0E#\u0227\x03#\x03#\x05#\u022C" + + "\n#\x03$\x03$\x03$\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03" + + "%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03" + + "%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03" + + "%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03" + + "%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03" + + "%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03" + + "%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x03%\x05" + + "%\u028F\n%\x03&\x03&\x03&\x03&\x03\'\x03\'\x03(\x03(\x03)\x03)\x03*\x03" + + "*\x03+\x03+\x03+\x03+\x03+\x03,\x03,\x03,\x03,\x03,\x03-\x03-\x03-\x03" + + "-\x03.\x03.\x03.\x03.\x03.\x03/\x03/\x03/\x03/\x03/\x03/\x030\x030\x03" + + "0\x031\x031\x031\x032\x032\x032\x033\x033\x033\x033\x033\x034\x034\x03" + + "4\x035\x035\x036\x036\x037\x037\x037\x037\x037\x038\x038\x038\x038\x03" + + "8\x038\x038\x038\x038\x038\x039\x039\x039\x039\x039\x039\x039\x039\x03" + + "9\x059\u02E3\n9\x03:\x03:\x03:\x03:\x03:\x03:\x03:\x03:\x03:\x03:\x05" + + ":\u02EF\n:\x03;\x03;\x03<\x03<\x03=\x03=\x03>\x03>\x03?\x03?\x03@\x03" + + "@\x03@\x03A\x03A\x03A\x03A\x03A\x03A\x03A\x05A\u0305\nA\x03B\x03B\x03" + + "B\x03B\x03B\x03B\x03C\x03C\x03C\x03C\x03C\x03C\x03C\x03C\x03C\x05C\u0316" + + "\nC\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03" + + "D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x03D\x05D\u04C6\nD\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03" + + "E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x03E\x05E\u055F\nE\x03" + + "F\x03F\x03F\x03F\x03F\x03F\x03F\x03F\x03F\x03F\x03F\x03G\x03G\x03G\x03" + + "G\x03G\x07G\u0571\nG\fG\x0EG\u0574\vG\x03G\x03G\x03G\x03G\x03G\x06G\u057B" + + "\nG\rG\x0EG\u057C\x05G\u057F\nG\x03H\x03H\x03H\x03H\x07H\u0585\nH\fH\x0E" + + "H\u0588\vH\x03H\x03H\x03I\x03I\x03I\x03I\x03J\x03J\x03J\x03J\x03K\x03" + + "K\x03K\x03K\x03L\x03L\x03L\x03L\x03L\x03M\x03M\x03M\x03M\x03M\x03M\x03" + + "N\x03N\x03N\x03N\x03N\x03N\x03O\x03O\x03O\x03O\x03P\x03P\x03P\x03P\x03" + + "Q\x03Q\x03Q\x03Q\x03Q\x03Q\x03Q\x03Q\x03Q\x03R\x06R\u05BB\nR\rR\x0ER\u05BC" + + "\x03S\x06S\u05C0\nS\rS\x0ES\u05C1\x03S\x03S\x05S\u05C6\nS\x03T\x03T\x03" + + "U\x03U\x03U\x03U\x03V\x03V\x03V\x03V\x03W\x03W\x03W\x03W\x03X\x03X\x03" + + "X\x03Y\x03Y\x03Y\x03Y\x03Y\x03Z\x03Z\x03Z\x03Z\x03Z\x03[\x03[\x03[\x03" + + "[\x03[\x03[\x03\\\x03\\\x03\\\x03\\\x03]\x03]\x03]\x03]\x03^\x06^\u05F2" + + "\n^\r^\x0E^\u05F3\x03_\x06_\u05F7\n_\r_\x0E_\u05F8\x03_\x03_\x05_\u05FD" + + "\n_\x03`\x03`\x03a\x03a\x03a\x03a\x03b\x03b\x03b\x03b\x03c\x03c\x03c\x03" + + "c\x03d\x03d\x03e\x03e\x03f\x03f\x03g\x03g\x03h\x03h\x03i\x03i\x03j\x03" + + "j\x03k\x03k\x03l\x03l\x03m\x03m\x03n\x03n\x03o\x03o\x03p\x03p\x03q\x03" + + "q\x03r\x03r\x03s\x03s\x03t\x03t\x03u\x03u\x03v\x03v\x03w\x03w\x03x\x03" + + "x\x03y\x03y\x03z\x03z\x03{\x03{\x03|\x03|\x03}\x03}\x04\u01A2\u01ED\x02" + + "\x02~\x07\x02\x03\t\x02\x04\v\x02\x05\r\x02\x06\x0F\x02\x07\x11\x02\b" + + "\x13\x02\t\x15\x02\n\x17\x02\v\x19\x02\f\x1B\x02\r\x1D\x02\x0E\x1F\x02" + + "\x0F!\x02\x10#\x02\x11%\x02\x12\'\x02\x13)\x02\x14+\x02\x15-\x02\x16/" + + "\x02\x021\x02S3\x02\x175\x02\x187\x02\x199\x02\x1A;\x02\x02=\x02\x02?" + + "\x02\x02A\x02\x02C\x02\x02E\x02\x1BG\x02\x1CI\x02\x1DK\x02\x1EM\x02\x1F" + + "O\x02 Q\x02!S\x02\"U\x02#W\x02$Y\x02%[\x02&]\x02\'_\x02(a\x02)c\x02*e" + + "\x02+g\x02,i\x02-k\x02.m\x02/o\x020q\x021s\x022u\x023w\x024y\x025{\x02" + + "6}\x027\x7F\x028\x81\x029\x83\x02:\x85\x02;\x87\x02<\x89\x02=\x8B\x02" + + ">\x8D\x02?\x8F\x02@\x91\x02A\x93\x02B\x95\x02C\x97\x02D\x99\x02E\x9B\x02" + + "\x02\x9D\x02\x02\x9F\x02\x02\xA1\x02\x02\xA3\x02\x02\xA5\x02F\xA7\x02" + + "G\xA9\x02\x02\xAB\x02H\xAD\x02I\xAF\x02J\xB1\x02K\xB3\x02L\xB5\x02M\xB7" + + "\x02\x02\xB9\x02\x02\xBB\x02\x02\xBD\x02\x02\xBF\x02N\xC1\x02\x02\xC3" + + "\x02O\xC5\x02P\xC7\x02Q\xC9\x02R\xCB\x02\x02\xCD\x02\x02\xCF\x02\x02\xD1" + + "\x02\x02\xD3\x02\x02\xD5\x02\x02\xD7\x02\x02\xD9\x02\x02\xDB\x02\x02\xDD" + + "\x02\x02\xDF\x02\x02\xE1\x02\x02\xE3\x02\x02\xE5\x02\x02\xE7\x02\x02\xE9" + + "\x02\x02\xEB\x02\x02\xED\x02\x02\xEF\x02\x02\xF1\x02\x02\xF3\x02\x02\xF5" + + "\x02\x02\xF7\x02\x02\xF9\x02\x02\xFB\x02\x02\xFD\x02\x02\x07\x02\x03\x04" + + "\x05\x06\'\x04\x02\f\f\x0F\x0F\x05\x02\v\f\x0F\x0F\"\"\x03\x022;\x04\x02" + + "C\\c|\x07\x02$$^^ppttvv\x06\x02\f\f\x0F\x0F$$^^\x04\x02GGgg\x04\x02--" + + "//\x04\x02BBaa\x03\x02bb\f\x02\v\f\x0F\x0F\"\"..11??]]__bb~~\x04\x02," + + ",11\x04\x02CCcc\x04\x02DDdd\x04\x02EEee\x04\x02FFff\x04\x02HHhh\x04\x02" + + "IIii\x04\x02JJjj\x04\x02KKkk\x04\x02LLll\x04\x02MMmm\x04\x02NNnn\x04\x02" + + "OOoo\x04\x02PPpp\x04\x02QQqq\x04\x02RRrr\x04\x02SSss\x04\x02TTtt\x04\x02" + + "UUuu\x04\x02VVvv\x04\x02WWww\x04\x02XXxx\x04\x02YYyy\x04\x02ZZzz\x04\x02" + + "[[{{\x04\x02\\\\||\x02\u06A4\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02" + + "\x02\x02\v\x03\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02" + + "\x02\x11\x03\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02" + + "\x02\x17\x03\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02" + + "\x02\x1D\x03\x02\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02" + + "\x02#\x03\x02\x02\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)" + + "\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x03/\x03\x02" + + "\x02\x02\x031\x03\x02\x02\x02\x033\x03\x02\x02\x02\x035\x03\x02\x02\x02" + + "\x037\x03\x02\x02\x02\x049\x03\x02\x02\x02\x04E\x03\x02\x02\x02\x04G\x03" + + "\x02\x02\x02\x04I\x03\x02\x02\x02\x04K\x03\x02\x02\x02\x04M\x03\x02\x02" + + "\x02\x04O\x03\x02\x02\x02\x04Q\x03\x02\x02\x02\x04S\x03\x02\x02\x02\x04" + + "U\x03\x02\x02\x02\x04W\x03\x02\x02\x02\x04Y\x03\x02\x02\x02\x04[\x03\x02" + + "\x02\x02\x04]\x03\x02\x02\x02\x04_\x03\x02\x02\x02\x04a\x03\x02\x02\x02" + + "\x04c\x03\x02\x02\x02\x04e\x03\x02\x02\x02\x04g\x03\x02\x02\x02\x04i\x03" + "\x02\x02\x02\x04k\x03\x02\x02\x02\x04m\x03\x02\x02\x02\x04o\x03\x02\x02" + - "\x02\x04s\x03\x02\x02\x02\x04u\x03\x02\x02\x02\x04w\x03\x02\x02\x02\x04" + - "y\x03\x02\x02\x02\x05{\x03\x02\x02\x02\x07\x82\x03\x02\x02\x02\t\x8C\x03" + - "\x02\x02\x02\v\x93\x03\x02\x02\x02\r\x99\x03\x02\x02\x02\x0F\xA1\x03\x02" + - "\x02\x02\x11\xA9\x03\x02\x02\x02\x13\xB0\x03\x02\x02\x02\x15\xB8\x03\x02" + - "\x02\x02\x17\xC2\x03\x02\x02\x02\x19\xD3\x03\x02\x02\x02\x1B\xE3\x03\x02" + - "\x02\x02\x1D\xE9\x03\x02\x02\x02\x1F\xED\x03\x02\x02\x02!\xEF\x03\x02" + - "\x02\x02#\xF1\x03\x02\x02\x02%\xF4\x03\x02\x02\x02\'\xF6\x03\x02\x02\x02" + - ")\u011C\x03\x02\x02\x02+\u011F\x03\x02\x02\x02-\u014D\x03\x02\x02\x02" + - "/\u014F\x03\x02\x02\x021\u0152\x03\x02\x02\x023\u0156\x03\x02\x02\x02" + - "5\u0158\x03\x02\x02\x027\u015A\x03\x02\x02\x029\u015C\x03\x02\x02\x02" + - ";\u015E\x03\x02\x02\x02=\u0162\x03\x02\x02\x02?\u0167\x03\x02\x02\x02" + - "A\u016B\x03\x02\x02\x02C\u0170\x03\x02\x02\x02E\u0173\x03\x02\x02\x02" + - "G\u017E\x03\x02\x02\x02I\u018A\x03\x02\x02\x02K\u018C\x03\x02\x02\x02" + - "M\u018E\x03\x02\x02\x02O\u0190\x03\x02\x02\x02Q\u0192\x03\x02\x02\x02" + - "S\u0194\x03\x02\x02\x02U\u019D\x03\x02\x02\x02W\u019F\x03\x02\x02\x02" + - "Y\u01AE\x03\x02\x02\x02[\u01C1\x03\x02\x02\x02]\u01C5\x03\x02\x02\x02" + - "_\u01CF\x03\x02\x02\x02a\u01DA\x03\x02\x02\x02c\u01DE\x03\x02\x02\x02" + - "e\u01E2\x03\x02\x02\x02g\u01E6\x03\x02\x02\x02i\u01EB\x03\x02\x02\x02" + - "k\u01F1\x03\x02\x02\x02m\u01F5\x03\x02\x02\x02o\u01FA\x03\x02\x02\x02" + - "q\u0205\x03\x02\x02\x02s\u0207\x03\x02\x02\x02u\u0209\x03\x02\x02\x02" + - "w\u020D\x03\x02\x02\x02y\u0211\x03\x02\x02\x02{|\x07g\x02\x02|}\x07x\x02" + - "\x02}~\x07c\x02\x02~\x7F\x07n\x02\x02\x7F\x80\x03\x02\x02\x02\x80\x81" + - "\b\x02\x02\x02\x81\x06\x03\x02\x02\x02\x82\x83\x07g\x02\x02\x83\x84\x07" + - "z\x02\x02\x84\x85\x07r\x02\x02\x85\x86\x07n\x02\x02\x86\x87\x07c\x02\x02" + - "\x87\x88\x07k\x02\x02\x88\x89\x07p\x02\x02\x89\x8A\x03\x02\x02\x02\x8A" + - "\x8B\b\x03\x02\x02\x8B\b\x03\x02\x02\x02\x8C\x8D\x07h\x02\x02\x8D\x8E" + - "\x07t\x02\x02\x8E\x8F\x07q\x02\x02\x8F\x90\x07o\x02\x02\x90\x91\x03\x02" + - "\x02\x02\x91\x92\b\x04\x03\x02\x92\n\x03\x02\x02\x02\x93\x94\x07t\x02" + - "\x02\x94\x95\x07q\x02\x02\x95\x96\x07y\x02\x02\x96\x97\x03\x02\x02\x02" + - "\x97\x98\b\x05\x02\x02\x98\f\x03\x02\x02\x02\x99\x9A\x07u\x02\x02\x9A" + - "\x9B\x07v\x02\x02\x9B\x9C\x07c\x02\x02\x9C\x9D\x07v\x02\x02\x9D\x9E\x07" + - "u\x02\x02\x9E\x9F\x03\x02\x02\x02\x9F\xA0\b\x06\x02\x02\xA0\x0E\x03\x02" + - "\x02\x02\xA1\xA2\x07y\x02\x02\xA2\xA3\x07j\x02\x02\xA3\xA4\x07g\x02\x02" + - "\xA4\xA5\x07t\x02\x02\xA5\xA6\x07g\x02\x02\xA6\xA7\x03\x02\x02\x02\xA7" + - "\xA8\b\x07\x02\x02\xA8\x10\x03\x02\x02\x02\xA9\xAA\x07u\x02\x02\xAA\xAB" + - "\x07q\x02\x02\xAB\xAC\x07t\x02\x02\xAC\xAD\x07v\x02\x02\xAD\xAE\x03\x02" + - "\x02\x02\xAE\xAF\b\b\x02\x02\xAF\x12\x03\x02\x02\x02\xB0\xB1\x07n\x02" + - "\x02\xB1\xB2\x07k\x02\x02\xB2\xB3\x07o\x02\x02\xB3\xB4\x07k\x02\x02\xB4" + - "\xB5\x07v\x02\x02\xB5\xB6\x03\x02\x02\x02\xB6\xB7\b\t\x02\x02\xB7\x14" + - "\x03\x02\x02\x02\xB8\xB9\x07r\x02\x02\xB9\xBA\x07t\x02\x02\xBA\xBB\x07" + - "q\x02\x02\xBB\xBC\x07l\x02\x02\xBC\xBD\x07g\x02\x02\xBD\xBE\x07e\x02\x02" + - "\xBE\xBF\x07v\x02\x02\xBF\xC0\x03\x02\x02\x02\xC0\xC1\b\n\x03\x02\xC1" + - "\x16\x03\x02\x02\x02\xC2\xC3\x071\x02\x02\xC3\xC4\x071\x02\x02\xC4\xC8" + - "\x03\x02\x02\x02\xC5\xC7\n\x02\x02\x02\xC6\xC5\x03\x02\x02\x02\xC7\xCA" + - "\x03\x02\x02\x02\xC8\xC6\x03\x02\x02\x02\xC8\xC9\x03\x02\x02\x02\xC9\xCC" + - "\x03\x02\x02\x02\xCA\xC8\x03\x02\x02\x02\xCB\xCD\x07\x0F\x02\x02\xCC\xCB" + - "\x03\x02\x02\x02\xCC\xCD\x03\x02\x02\x02\xCD\xCF\x03\x02\x02\x02\xCE\xD0" + - "\x07\f\x02\x02\xCF\xCE\x03\x02\x02\x02\xCF\xD0\x03\x02\x02\x02\xD0\xD1" + - "\x03\x02\x02\x02\xD1\xD2\b\v\x04\x02\xD2\x18\x03\x02\x02\x02\xD3\xD4\x07" + - "1\x02\x02\xD4\xD5\x07,\x02\x02\xD5\xDA\x03\x02\x02\x02\xD6\xD9\x05\x19" + - "\f\x02\xD7\xD9\v\x02\x02\x02\xD8\xD6\x03\x02\x02\x02\xD8\xD7\x03\x02\x02" + - "\x02\xD9\xDC\x03\x02\x02\x02\xDA\xDB\x03\x02\x02\x02\xDA\xD8\x03\x02\x02" + - "\x02\xDB\xDD\x03\x02\x02\x02\xDC\xDA\x03\x02\x02\x02\xDD\xDE\x07,\x02" + - "\x02\xDE\xDF\x071\x02\x02\xDF\xE0\x03\x02\x02\x02\xE0\xE1\b\f\x04\x02" + - "\xE1\x1A\x03\x02\x02\x02\xE2\xE4\t\x03\x02\x02\xE3\xE2\x03\x02\x02\x02" + - "\xE4\xE5\x03\x02\x02\x02\xE5\xE3\x03\x02\x02\x02\xE5\xE6\x03\x02\x02\x02" + - "\xE6\xE7\x03\x02\x02\x02\xE7\xE8\b\r\x04\x02\xE8\x1C\x03\x02\x02\x02\xE9" + - "\xEA\x07~\x02\x02\xEA\xEB\x03\x02\x02\x02\xEB\xEC\b\x0E\x05\x02\xEC\x1E" + - "\x03\x02\x02\x02\xED\xEE\t\x04\x02\x02\xEE \x03\x02\x02\x02\xEF\xF0\t" + - "\x05\x02\x02\xF0\"\x03\x02\x02\x02\xF1\xF2\x07^\x02\x02\xF2\xF3\t\x06" + - "\x02\x02\xF3$\x03\x02\x02\x02\xF4\xF5\n\x07\x02\x02\xF5&\x03\x02\x02\x02" + - "\xF6\xF8\t\b\x02\x02\xF7\xF9\t\t\x02\x02\xF8\xF7\x03\x02\x02\x02\xF8\xF9" + - "\x03\x02\x02\x02\xF9\xFB\x03\x02\x02\x02\xFA\xFC\x05\x1F\x0F\x02\xFB\xFA" + - "\x03\x02\x02\x02\xFC\xFD\x03\x02\x02\x02\xFD\xFB\x03\x02\x02\x02\xFD\xFE" + - "\x03\x02\x02\x02\xFE(\x03\x02\x02\x02\xFF\u0104\x07$\x02\x02\u0100\u0103" + - "\x05#\x11\x02\u0101\u0103\x05%\x12\x02\u0102\u0100\x03\x02\x02\x02\u0102" + - "\u0101\x03\x02\x02\x02\u0103\u0106\x03\x02\x02\x02\u0104\u0102\x03\x02" + - "\x02\x02\u0104\u0105\x03\x02\x02\x02\u0105\u0107\x03\x02\x02\x02\u0106" + - "\u0104\x03\x02\x02\x02\u0107\u011D\x07$\x02\x02\u0108\u0109\x07$\x02\x02" + - "\u0109\u010A\x07$\x02\x02\u010A\u010B\x07$\x02\x02\u010B\u010F\x03\x02" + - "\x02\x02\u010C\u010E\n\x02\x02\x02\u010D\u010C\x03\x02\x02\x02\u010E\u0111" + - "\x03\x02\x02\x02\u010F\u0110\x03\x02\x02\x02\u010F\u010D\x03\x02\x02\x02" + - "\u0110\u0112\x03\x02\x02\x02\u0111\u010F\x03\x02\x02\x02\u0112\u0113\x07" + - "$\x02\x02\u0113\u0114\x07$\x02\x02\u0114\u0115\x07$\x02\x02\u0115\u0117" + - "\x03\x02\x02\x02\u0116\u0118\x07$\x02\x02\u0117\u0116\x03\x02\x02\x02" + - "\u0117\u0118\x03\x02\x02\x02\u0118\u011A\x03\x02\x02\x02\u0119\u011B\x07" + - "$\x02\x02\u011A\u0119\x03\x02\x02\x02\u011A\u011B\x03\x02\x02\x02\u011B" + - "\u011D\x03\x02\x02\x02\u011C\xFF\x03\x02\x02\x02\u011C\u0108\x03\x02\x02" + - "\x02\u011D*\x03\x02\x02\x02\u011E\u0120\x05\x1F\x0F\x02\u011F\u011E\x03" + - "\x02\x02\x02\u0120\u0121\x03\x02\x02\x02\u0121\u011F\x03\x02\x02\x02\u0121" + - "\u0122\x03\x02\x02\x02\u0122,\x03\x02\x02\x02\u0123\u0125\x05\x1F\x0F" + - "\x02\u0124\u0123\x03\x02\x02\x02\u0125\u0126\x03\x02\x02\x02\u0126\u0124" + - "\x03\x02\x02\x02\u0126\u0127\x03\x02\x02\x02\u0127\u0128\x03\x02\x02\x02" + - "\u0128\u012C\x057\x1B\x02\u0129\u012B\x05\x1F\x0F\x02\u012A\u0129\x03" + - "\x02\x02\x02\u012B\u012E\x03\x02\x02\x02\u012C\u012A\x03\x02\x02\x02\u012C" + - "\u012D\x03\x02\x02\x02\u012D\u014E\x03\x02\x02\x02\u012E\u012C\x03\x02" + - "\x02\x02\u012F\u0131\x057\x1B\x02\u0130\u0132\x05\x1F\x0F\x02\u0131\u0130" + - "\x03\x02\x02\x02\u0132\u0133\x03\x02\x02\x02\u0133\u0131\x03\x02\x02\x02" + - "\u0133\u0134\x03\x02\x02\x02\u0134\u014E\x03\x02\x02\x02\u0135\u0137\x05" + - "\x1F\x0F\x02\u0136\u0135\x03\x02\x02\x02\u0137\u0138\x03\x02\x02\x02\u0138" + - "\u0136\x03\x02\x02\x02\u0138\u0139\x03\x02\x02\x02\u0139\u0141\x03\x02" + - "\x02\x02\u013A\u013E\x057\x1B\x02\u013B\u013D\x05\x1F\x0F\x02\u013C\u013B" + - "\x03\x02\x02\x02\u013D\u0140\x03\x02\x02\x02\u013E\u013C\x03\x02\x02\x02" + - "\u013E\u013F\x03\x02\x02\x02\u013F\u0142\x03\x02\x02\x02\u0140\u013E\x03" + - "\x02\x02\x02\u0141\u013A\x03\x02\x02\x02\u0141\u0142\x03\x02\x02\x02\u0142" + - "\u0143\x03\x02\x02\x02\u0143\u0144\x05\'\x13\x02\u0144\u014E\x03\x02\x02" + - "\x02\u0145\u0147\x057\x1B\x02\u0146\u0148\x05\x1F\x0F\x02\u0147\u0146" + - "\x03\x02\x02\x02\u0148\u0149\x03\x02\x02\x02\u0149\u0147\x03\x02\x02\x02" + - "\u0149\u014A\x03\x02\x02\x02\u014A\u014B\x03\x02\x02\x02\u014B\u014C\x05" + - "\'\x13\x02\u014C\u014E\x03\x02\x02\x02\u014D\u0124\x03\x02\x02\x02\u014D" + - "\u012F\x03\x02\x02\x02\u014D\u0136\x03\x02\x02\x02\u014D\u0145\x03\x02" + - "\x02\x02\u014E.\x03\x02\x02\x02\u014F\u0150\x07d\x02\x02\u0150\u0151\x07" + - "{\x02\x02\u01510\x03\x02\x02\x02\u0152\u0153\x07c\x02\x02\u0153\u0154" + - "\x07p\x02\x02\u0154\u0155\x07f\x02\x02\u01552\x03\x02\x02\x02\u0156\u0157" + - "\x07?\x02\x02\u01574\x03\x02\x02\x02\u0158\u0159\x07.\x02\x02\u01596\x03" + - "\x02\x02\x02\u015A\u015B\x070\x02\x02\u015B8\x03\x02\x02\x02\u015C\u015D" + - "\x07*\x02\x02\u015D:\x03\x02\x02\x02\u015E\u015F\x07]\x02\x02\u015F\u0160" + - "\x03\x02\x02\x02\u0160\u0161\b\x1D\x06\x02\u0161<\x03\x02\x02\x02\u0162" + - "\u0163\x07_\x02\x02\u0163\u0164\x03\x02\x02\x02\u0164\u0165\b\x1E\x05" + - "\x02\u0165\u0166\b\x1E\x05\x02\u0166>\x03\x02\x02\x02\u0167\u0168\x07" + - "p\x02\x02\u0168\u0169\x07q\x02\x02\u0169\u016A\x07v\x02\x02\u016A@\x03" + - "\x02\x02\x02\u016B\u016C\x07p\x02\x02\u016C\u016D\x07w\x02\x02\u016D\u016E" + - "\x07n\x02\x02\u016E\u016F\x07n\x02\x02\u016FB\x03\x02\x02\x02\u0170\u0171" + - "\x07q\x02\x02\u0171\u0172\x07t\x02\x02\u0172D\x03\x02\x02\x02\u0173\u0174" + - "\x07+\x02\x02\u0174F\x03\x02\x02\x02\u0175\u0176\x07v\x02\x02\u0176\u0177" + - "\x07t\x02\x02\u0177\u0178\x07w\x02\x02\u0178\u017F\x07g\x02\x02\u0179" + - "\u017A\x07h\x02\x02\u017A\u017B\x07c\x02\x02\u017B\u017C\x07n\x02\x02" + - "\u017C\u017D\x07u\x02\x02\u017D\u017F\x07g\x02\x02\u017E\u0175\x03\x02" + - "\x02\x02\u017E\u0179\x03\x02\x02\x02\u017FH\x03\x02\x02\x02\u0180\u0181" + - "\x07?\x02\x02\u0181\u018B\x07?\x02\x02\u0182\u0183\x07#\x02\x02\u0183" + - "\u018B\x07?\x02\x02\u0184\u018B\x07>\x02\x02\u0185\u0186\x07>\x02\x02" + - "\u0186\u018B\x07?\x02\x02\u0187\u018B\x07@\x02\x02\u0188\u0189\x07@\x02" + - "\x02\u0189\u018B\x07?\x02\x02\u018A\u0180\x03\x02\x02\x02\u018A\u0182" + - "\x03\x02\x02\x02\u018A\u0184\x03\x02\x02\x02\u018A\u0185\x03\x02\x02\x02" + - "\u018A\u0187\x03\x02\x02\x02\u018A\u0188\x03\x02\x02\x02\u018BJ\x03\x02" + - "\x02\x02\u018C\u018D\x07-\x02\x02\u018DL\x03\x02\x02\x02\u018E\u018F\x07" + - "/\x02\x02\u018FN\x03\x02\x02\x02\u0190\u0191\x07,\x02\x02\u0191P\x03\x02" + - "\x02\x02\u0192\u0193\x071\x02\x02\u0193R\x03\x02\x02\x02\u0194\u0195\x07" + - "\'\x02\x02\u0195T\x03\x02\x02\x02\u0196\u0197\x07c\x02\x02\u0197\u0198" + - "\x07u\x02\x02\u0198\u019E\x07e\x02\x02\u0199\u019A\x07f\x02\x02\u019A" + - "\u019B\x07g\x02\x02\u019B\u019C\x07u\x02\x02\u019C\u019E\x07e\x02\x02" + - "\u019D\u0196\x03\x02\x02\x02\u019D\u0199\x03\x02\x02\x02\u019EV\x03\x02" + - "\x02\x02\u019F\u01A0\x07p\x02\x02\u01A0\u01A1\x07w\x02\x02\u01A1\u01A2" + - "\x07n\x02\x02\u01A2\u01A3\x07n\x02\x02\u01A3\u01A4\x07u\x02\x02\u01A4" + - "X\x03\x02\x02\x02\u01A5\u01A6\x07h\x02\x02\u01A6\u01A7\x07k\x02\x02\u01A7" + - "\u01A8\x07t\x02\x02\u01A8\u01A9\x07u\x02\x02\u01A9\u01AF\x07v\x02\x02" + - "\u01AA\u01AB\x07n\x02\x02\u01AB\u01AC\x07c\x02\x02\u01AC\u01AD\x07u\x02" + - "\x02\u01AD\u01AF\x07v\x02\x02\u01AE\u01A5\x03\x02\x02\x02\u01AE\u01AA" + - "\x03\x02\x02\x02\u01AFZ\x03\x02\x02\x02\u01B0\u01B1\x07t\x02\x02\u01B1" + - "\u01B2\x07q\x02\x02\u01B2\u01B3\x07w\x02\x02\u01B3\u01B4\x07p\x02\x02" + - "\u01B4\u01C2\x07f\x02\x02\u01B5\u01B6\x07c\x02\x02\u01B6\u01B7\x07x\x02" + - "\x02\u01B7\u01C2\x07i\x02\x02\u01B8\u01B9\x07o\x02\x02\u01B9\u01BA\x07" + - "k\x02\x02\u01BA\u01C2\x07p\x02\x02\u01BB\u01BC\x07o\x02\x02\u01BC\u01BD" + - "\x07c\x02\x02\u01BD\u01C2\x07z\x02\x02\u01BE\u01BF\x07u\x02\x02\u01BF" + - "\u01C0\x07w\x02\x02\u01C0\u01C2\x07o\x02\x02\u01C1\u01B0\x03\x02\x02\x02" + - "\u01C1\u01B5\x03\x02\x02\x02\u01C1\u01B8\x03\x02\x02\x02\u01C1\u01BB\x03" + - "\x02\x02\x02\u01C1\u01BE\x03\x02\x02\x02\u01C2\\\x03\x02\x02\x02\u01C3" + - "\u01C6\x05!\x10\x02\u01C4\u01C6\x07a\x02\x02\u01C5\u01C3\x03\x02\x02\x02" + - "\u01C5\u01C4\x03\x02\x02\x02\u01C6\u01CC\x03\x02\x02\x02\u01C7\u01CB\x05" + - "!\x10\x02\u01C8\u01CB\x05\x1F\x0F\x02\u01C9\u01CB\x07a\x02\x02\u01CA\u01C7" + - "\x03\x02\x02\x02\u01CA\u01C8\x03\x02\x02\x02\u01CA\u01C9\x03\x02\x02\x02" + - "\u01CB\u01CE\x03\x02\x02\x02\u01CC\u01CA\x03\x02\x02\x02\u01CC\u01CD\x03" + - "\x02\x02\x02\u01CD^\x03\x02\x02\x02\u01CE\u01CC\x03\x02\x02\x02\u01CF" + - "\u01D5\x07b\x02\x02\u01D0\u01D4\n\n\x02\x02\u01D1\u01D2\x07b\x02\x02\u01D2" + - "\u01D4\x07b\x02\x02\u01D3\u01D0\x03\x02\x02\x02\u01D3\u01D1\x03\x02\x02" + - "\x02\u01D4\u01D7\x03\x02\x02\x02\u01D5\u01D3\x03\x02\x02\x02\u01D5\u01D6" + - "\x03\x02\x02\x02\u01D6\u01D8\x03\x02\x02\x02\u01D7\u01D5\x03\x02\x02\x02" + - "\u01D8\u01D9\x07b\x02\x02\u01D9`\x03\x02\x02\x02\u01DA\u01DB\x05\x17\v" + - "\x02\u01DB\u01DC\x03\x02\x02\x02\u01DC\u01DD\b0\x04\x02\u01DDb\x03\x02" + - "\x02\x02\u01DE\u01DF\x05\x19\f\x02\u01DF\u01E0\x03\x02\x02\x02\u01E0\u01E1" + - "\b1\x04\x02\u01E1d\x03\x02\x02\x02\u01E2\u01E3\x05\x1B\r\x02\u01E3\u01E4" + - "\x03\x02\x02\x02\u01E4\u01E5\b2\x04\x02\u01E5f\x03\x02\x02\x02\u01E6\u01E7" + - "\x07~\x02\x02\u01E7\u01E8\x03\x02\x02\x02\u01E8\u01E9\b3\x07\x02\u01E9" + - "\u01EA\b3\x05\x02\u01EAh\x03\x02\x02\x02\u01EB\u01EC\x07_\x02\x02\u01EC" + - "\u01ED\x03\x02\x02\x02\u01ED\u01EE\b4\x05\x02\u01EE\u01EF\b4\x05\x02\u01EF" + - "\u01F0\b4\b\x02\u01F0j\x03\x02\x02\x02\u01F1\u01F2\x07.\x02\x02\u01F2" + - "\u01F3\x03\x02\x02\x02\u01F3\u01F4\b5\t\x02\u01F4l\x03\x02\x02\x02\u01F5" + - "\u01F6\x07?\x02\x02\u01F6\u01F7\x03\x02\x02\x02\u01F7\u01F8\b6\n\x02\u01F8" + - "n\x03\x02\x02\x02\u01F9\u01FB\x05q8\x02\u01FA\u01F9\x03\x02\x02\x02\u01FB" + - "\u01FC\x03\x02\x02\x02\u01FC\u01FA\x03\x02\x02\x02\u01FC\u01FD\x03\x02" + - "\x02\x02\u01FDp\x03\x02\x02\x02\u01FE\u0200\n\v\x02\x02\u01FF\u01FE\x03" + - "\x02\x02\x02\u0200\u0201\x03\x02\x02\x02\u0201\u01FF\x03\x02\x02\x02\u0201" + - "\u0202\x03\x02\x02\x02\u0202\u0206\x03\x02\x02\x02\u0203\u0204\x071\x02" + - "\x02\u0204\u0206\n\f\x02\x02\u0205\u01FF\x03\x02\x02\x02\u0205\u0203\x03" + - "\x02\x02\x02\u0206r\x03\x02\x02\x02\u0207\u0208\x05_/\x02\u0208t\x03\x02" + - "\x02\x02\u0209\u020A\x05\x17\v\x02\u020A\u020B\x03\x02\x02\x02\u020B\u020C" + - "\b:\x04\x02\u020Cv\x03\x02\x02\x02\u020D\u020E\x05\x19\f\x02\u020E\u020F" + - "\x03\x02\x02\x02\u020F\u0210\b;\x04\x02\u0210x\x03\x02\x02\x02\u0211\u0212" + - "\x05\x1B\r\x02\u0212\u0213\x03\x02\x02\x02\u0213\u0214\b<\x04\x02\u0214" + - "z\x03\x02\x02\x02)\x02\x03\x04\xC8\xCC\xCF\xD8\xDA\xE5\xF8\xFD\u0102\u0104" + - "\u010F\u0117\u011A\u011C\u0121\u0126\u012C\u0133\u0138\u013E\u0141\u0149" + - "\u014D\u017E\u018A\u019D\u01AE\u01C1\u01C5\u01CA\u01CC\u01D3\u01D5\u01FC" + - "\u0201\u0205\v\x07\x03\x02\x07\x04\x02\x02\x03\x02\x06\x02\x02\x07\x02" + - "\x02\t\x0F\x02\t\x1A\x02\t\x16\x02\t\x15\x02"; + "\x02\x04q\x03\x02\x02\x02\x04s\x03\x02\x02\x02\x04u\x03\x02\x02\x02\x04" + + "w\x03\x02\x02\x02\x04y\x03\x02\x02\x02\x04{\x03\x02\x02\x02\x04}\x03\x02" + + "\x02\x02\x04\x7F\x03\x02\x02\x02\x04\x81\x03\x02\x02\x02\x04\x83\x03\x02" + + "\x02\x02\x04\x85\x03\x02\x02\x02\x04\x87\x03\x02\x02\x02\x04\x89\x03\x02" + + "\x02\x02\x04\x8B\x03\x02\x02\x02\x04\x8D\x03\x02\x02\x02\x04\x8F\x03\x02" + + "\x02\x02\x04\x91\x03\x02\x02\x02\x04\x93\x03\x02\x02\x02\x04\x95\x03\x02" + + "\x02\x02\x04\x97\x03\x02\x02\x02\x04\x99\x03\x02\x02\x02\x05\x9B\x03\x02" + + "\x02\x02\x05\x9D\x03\x02\x02\x02\x05\x9F\x03\x02\x02\x02\x05\xA1\x03\x02" + + "\x02\x02\x05\xA3\x03\x02\x02\x02\x05\xA5\x03\x02\x02\x02\x05\xA7\x03\x02" + + "\x02\x02\x05\xAB\x03\x02\x02\x02\x05\xAD\x03\x02\x02\x02\x05\xAF\x03\x02" + + "\x02\x02\x05\xB1\x03\x02\x02\x02\x06\xB3\x03\x02\x02\x02\x06\xB5\x03\x02" + + "\x02\x02\x06\xB7\x03\x02\x02\x02\x06\xB9\x03\x02\x02\x02\x06\xBB\x03\x02" + + "\x02\x02\x06\xBD\x03\x02\x02\x02\x06\xBF\x03\x02\x02\x02\x06\xC3\x03\x02" + + "\x02\x02\x06\xC5\x03\x02\x02\x02\x06\xC7\x03\x02\x02\x02\x06\xC9\x03\x02" + + "\x02\x02\x07\xFF\x03\x02\x02\x02\t\u0109\x03\x02\x02\x02\v\u0110\x03\x02" + + "\x02\x02\r\u0117\x03\x02\x02\x02\x0F\u0121\x03\x02\x02\x02\x11\u0128\x03" + + "\x02\x02\x02\x13\u012E\x03\x02\x02\x02\x15\u0136\x03\x02\x02\x02\x17\u013E" + + "\x03\x02\x02\x02\x19\u0145\x03\x02\x02\x02\x1B\u0151\x03\x02\x02\x02\x1D" + + "\u0159\x03\x02\x02\x02\x1F\u0163\x03\x02\x02\x02!\u016A\x03\x02\x02\x02" + + "#\u0173\x03\x02\x02\x02%\u017A\x03\x02\x02\x02\'\u0183\x03\x02\x02\x02" + + ")\u018A\x03\x02\x02\x02+\u019B\x03\x02\x02\x02-\u01AB\x03\x02\x02\x02" + + "/\u01B1\x03\x02\x02\x021\u01B6\x03\x02\x02\x023\u01BB\x03\x02\x02\x02" + + "5\u01BF\x03\x02\x02\x027\u01C3\x03\x02\x02\x029\u01C7\x03\x02\x02\x02" + + ";\u01CB\x03\x02\x02\x02=\u01CD\x03\x02\x02\x02?\u01CF\x03\x02\x02\x02" + + "A\u01D2\x03\x02\x02\x02C\u01D4\x03\x02\x02\x02E\u01FA\x03\x02\x02\x02" + + "G\u01FD\x03\x02\x02\x02I\u022B\x03\x02\x02\x02K\u022D\x03\x02\x02\x02" + + "M\u028E\x03\x02\x02\x02O\u0290\x03\x02\x02\x02Q\u0294\x03\x02\x02\x02" + + "S\u0296\x03\x02\x02\x02U\u0298\x03\x02\x02\x02W\u029A\x03\x02\x02\x02" + + "Y\u029C\x03\x02\x02\x02[\u02A1\x03\x02\x02\x02]\u02A6\x03\x02\x02\x02" + + "_\u02AA\x03\x02\x02\x02a\u02AF\x03\x02\x02\x02c\u02B5\x03\x02\x02\x02" + + "e\u02B8\x03\x02\x02\x02g\u02BB\x03\x02\x02\x02i\u02BE\x03\x02\x02\x02" + + "k\u02C3\x03\x02\x02\x02m\u02C6\x03\x02\x02\x02o\u02C8\x03\x02\x02\x02" + + "q\u02CA\x03\x02\x02\x02s\u02CF\x03\x02\x02\x02u\u02E2\x03\x02\x02\x02" + + "w\u02EE\x03\x02\x02\x02y\u02F0\x03\x02\x02\x02{\u02F2\x03\x02\x02\x02" + + "}\u02F4\x03\x02\x02\x02\x7F\u02F6\x03\x02\x02\x02\x81\u02F8\x03\x02\x02" + + "\x02\x83\u02FA\x03\x02\x02\x02\x85\u0304\x03\x02\x02\x02\x87\u0306\x03" + + "\x02\x02\x02\x89\u0315\x03\x02\x02\x02\x8B\u04C5\x03\x02\x02\x02\x8D\u055E" + + "\x03\x02\x02\x02\x8F\u0560\x03\x02\x02\x02\x91\u057E\x03\x02\x02\x02\x93" + + "\u0580\x03\x02\x02\x02\x95\u058B\x03\x02\x02\x02\x97\u058F\x03\x02\x02" + + "\x02\x99\u0593\x03\x02\x02\x02\x9B\u0597\x03\x02\x02\x02\x9D\u059C\x03" + + "\x02\x02\x02\x9F\u05A2\x03\x02\x02\x02\xA1\u05A8\x03\x02\x02\x02\xA3\u05AC" + + "\x03\x02\x02\x02\xA5\u05B0\x03\x02\x02\x02\xA7\u05BA\x03\x02\x02\x02\xA9" + + "\u05C5\x03\x02\x02\x02\xAB\u05C7\x03\x02\x02\x02\xAD\u05C9\x03\x02\x02" + + "\x02\xAF\u05CD\x03\x02\x02\x02\xB1\u05D1\x03\x02\x02\x02\xB3\u05D5\x03" + + "\x02\x02\x02\xB5\u05D8\x03\x02\x02\x02\xB7\u05DD\x03\x02\x02\x02\xB9\u05E2" + + "\x03\x02\x02\x02\xBB\u05E8\x03\x02\x02\x02\xBD\u05EC\x03\x02\x02\x02\xBF" + + "\u05F1\x03"; + private static readonly _serializedATNSegment1: string = + "\x02\x02\x02\xC1\u05FC\x03\x02\x02\x02\xC3\u05FE\x03\x02\x02\x02\xC5\u0600" + + "\x03\x02\x02\x02\xC7\u0604\x03\x02\x02\x02\xC9\u0608\x03\x02\x02\x02\xCB" + + "\u060C\x03\x02\x02\x02\xCD\u060E\x03\x02\x02\x02\xCF\u0610\x03\x02\x02" + + "\x02\xD1\u0612\x03\x02\x02\x02\xD3\u0614\x03\x02\x02\x02\xD5\u0616\x03" + + "\x02\x02\x02\xD7\u0618\x03\x02\x02\x02\xD9\u061A\x03\x02\x02\x02\xDB\u061C" + + "\x03\x02\x02\x02\xDD\u061E\x03\x02\x02\x02\xDF\u0620\x03\x02\x02\x02\xE1" + + "\u0622\x03\x02\x02\x02\xE3\u0624\x03\x02\x02\x02\xE5\u0626\x03\x02\x02" + + "\x02\xE7\u0628\x03\x02\x02\x02\xE9\u062A\x03\x02\x02\x02\xEB\u062C\x03" + + "\x02\x02\x02\xED\u062E\x03\x02\x02\x02\xEF\u0630\x03\x02\x02\x02\xF1\u0632" + + "\x03\x02\x02\x02\xF3\u0634\x03\x02\x02\x02\xF5\u0636\x03\x02\x02\x02\xF7" + + "\u0638\x03\x02\x02\x02\xF9\u063A\x03\x02\x02\x02\xFB\u063C\x03\x02\x02" + + "\x02\xFD\u063E\x03\x02\x02\x02\xFF\u0100\x05\xD1g\x02\u0100\u0101\x05" + + "\xDBl\x02\u0101\u0102\x05\xEFv\x02\u0102\u0103\x05\xEFv\x02\u0103\u0104" + + "\x05\xD3h\x02\u0104\u0105\x05\xCFf\x02\u0105\u0106\x05\xF1w\x02\u0106" + + "\u0107\x03\x02\x02\x02\u0107\u0108\b\x02\x02\x02\u0108\b\x03\x02\x02\x02" + + "\u0109\u010A\x05\xD7j\x02\u010A\u010B\x05\xEDu\x02\u010B\u010C\x05\xE7" + + "r\x02\u010C\u010D\x05\xDFn\x02\u010D\u010E\x03\x02\x02\x02\u010E\u010F" + + "\b\x03\x02\x02\u010F\n\x03\x02\x02\x02\u0110\u0111\x05\xD3h\x02\u0111" + + "\u0112\x05\xF5y\x02\u0112\u0113\x05\xCBd\x02\u0113\u0114\x05\xE1o\x02" + + "\u0114\u0115\x03\x02\x02\x02\u0115\u0116\b\x04\x02\x02\u0116\f\x03\x02" + + "\x02\x02\u0117\u0118\x05\xD3h\x02\u0118\u0119\x05\xF9{\x02\u0119\u011A" + + "\x05\xE9s\x02\u011A\u011B\x05\xE1o\x02\u011B\u011C\x05\xCBd\x02\u011C" + + "\u011D\x05\xDBl\x02\u011D\u011E\x05\xE5q\x02\u011E\u011F\x03\x02\x02\x02" + + "\u011F\u0120\b\x05\x03\x02\u0120\x0E\x03\x02\x02\x02\u0121\u0122\x05\xD5" + + "i\x02\u0122\u0123\x05\xEDu\x02\u0123\u0124\x05\xE7r\x02\u0124\u0125\x05" + + "\xE3p\x02\u0125\u0126\x03\x02\x02\x02\u0126\u0127\b\x06\x04\x02\u0127" + + "\x10\x03\x02\x02\x02\u0128\u0129\x05\xEDu\x02\u0129\u012A\x05\xE7r\x02" + + "\u012A\u012B\x05\xF7z\x02\u012B\u012C\x03\x02\x02\x02\u012C\u012D\b\x07" + + "\x02\x02\u012D\x12\x03\x02\x02\x02\u012E\u012F\x05\xEFv\x02\u012F\u0130" + + "\x05\xF1w\x02\u0130\u0131\x05\xCBd\x02\u0131\u0132\x05\xF1w\x02\u0132" + + "\u0133\x05\xEFv\x02\u0133\u0134\x03\x02\x02\x02\u0134\u0135\b\b\x02\x02" + + "\u0135\x14\x03\x02\x02\x02\u0136\u0137\x05\xF7z\x02\u0137\u0138\x05\xD9" + + "k\x02\u0138\u0139\x05\xD3h\x02\u0139\u013A\x05\xEDu\x02\u013A\u013B\x05" + + "\xD3h\x02\u013B\u013C\x03\x02\x02\x02\u013C\u013D\b\t\x02\x02\u013D\x16" + + "\x03\x02\x02\x02\u013E\u013F\x05\xEFv\x02\u013F\u0140\x05\xE7r\x02\u0140" + + "\u0141\x05\xEDu\x02\u0141\u0142\x05\xF1w\x02\u0142\u0143\x03\x02\x02\x02" + + "\u0143\u0144\b\n\x02\x02\u0144\x18\x03\x02\x02\x02\u0145\u0146\x05\xE3" + + "p\x02\u0146\u0147\x05\xF5y\x02\u0147\u0148\x05o6\x02\u0148\u0149\x05\xD3" + + "h\x02\u0149\u014A\x05\xF9{\x02\u014A\u014B\x05\xE9s\x02\u014B\u014C\x05" + + "\xCBd\x02\u014C\u014D\x05\xE5q\x02\u014D\u014E\x05\xD1g\x02\u014E\u014F" + + "\x03\x02\x02\x02\u014F\u0150\b\v\x02\x02\u0150\x1A\x03\x02\x02\x02\u0151" + + "\u0152\x05\xE1o\x02\u0152\u0153\x05\xDBl\x02\u0153\u0154\x05\xE3p\x02" + + "\u0154\u0155\x05\xDBl\x02\u0155\u0156\x05\xF1w\x02\u0156\u0157\x03\x02" + + "\x02\x02\u0157\u0158\b\f\x02\x02\u0158\x1C\x03\x02\x02\x02\u0159\u015A" + + "\x05\xE9s\x02\u015A\u015B\x05\xEDu\x02\u015B\u015C\x05\xE7r\x02\u015C" + + "\u015D\x05\xDDm\x02\u015D\u015E\x05\xD3h\x02\u015E\u015F\x05\xCFf\x02" + + "\u015F\u0160\x05\xF1w\x02\u0160\u0161\x03\x02\x02\x02\u0161\u0162\b\r" + + "\x02\x02\u0162\x1E\x03\x02\x02\x02\u0163\u0164\x05\xD1g\x02\u0164\u0165" + + "\x05\xEDu\x02\u0165\u0166\x05\xE7r\x02\u0166\u0167\x05\xE9s\x02\u0167" + + "\u0168\x03\x02\x02\x02\u0168\u0169\b\x0E\x02\x02\u0169 \x03\x02\x02\x02" + + "\u016A\u016B\x05\xEDu\x02\u016B\u016C\x05\xD3h\x02\u016C\u016D\x05\xE5" + + "q\x02\u016D\u016E\x05\xCBd\x02\u016E\u016F\x05\xE3p\x02\u016F\u0170\x05" + + "\xD3h\x02\u0170\u0171\x03\x02\x02\x02\u0171\u0172\b\x0F\x02\x02\u0172" + + "\"\x03\x02\x02\x02\u0173\u0174\x05\xEFv\x02\u0174\u0175\x05\xD9k\x02\u0175" + + "\u0176\x05\xE7r\x02\u0176\u0177\x05\xF7z\x02\u0177\u0178\x03\x02\x02\x02" + + "\u0178\u0179\b\x10\x02\x02\u0179$\x03\x02\x02\x02\u017A\u017B\x05\xD3" + + "h\x02\u017B\u017C\x05\xE5q\x02\u017C\u017D\x05\xEDu\x02\u017D\u017E\x05" + + "\xDBl\x02\u017E\u017F\x05\xCFf\x02\u017F\u0180\x05\xD9k\x02\u0180\u0181" + + "\x03\x02\x02\x02\u0181\u0182\b\x11\x05\x02\u0182&\x03\x02\x02\x02\u0183" + + "\u0184\x05\xDFn\x02\u0184\u0185\x05\xD3h\x02\u0185\u0186\x05\xD3h\x02" + + "\u0186\u0187\x05\xE9s\x02\u0187\u0188\x03\x02\x02\x02\u0188\u0189\b\x12" + + "\x02\x02\u0189(\x03\x02\x02\x02\u018A\u018B\x071\x02\x02\u018B\u018C\x07" + + "1\x02\x02\u018C\u0190\x03\x02\x02\x02\u018D\u018F\n\x02\x02\x02\u018E" + + "\u018D\x03\x02\x02\x02\u018F\u0192\x03\x02\x02\x02\u0190\u018E\x03\x02" + + "\x02\x02\u0190\u0191\x03\x02\x02\x02\u0191\u0194\x03\x02\x02\x02\u0192" + + "\u0190\x03\x02\x02\x02\u0193\u0195\x07\x0F\x02\x02\u0194\u0193\x03\x02" + + "\x02\x02\u0194\u0195\x03\x02\x02\x02\u0195\u0197\x03\x02\x02\x02\u0196" + + "\u0198\x07\f\x02\x02\u0197\u0196\x03\x02\x02\x02\u0197\u0198\x03\x02\x02" + + "\x02\u0198\u0199\x03\x02\x02\x02\u0199\u019A\b\x13\x06\x02\u019A*\x03" + + "\x02\x02\x02\u019B\u019C\x071\x02\x02\u019C\u019D\x07,\x02\x02\u019D\u01A2" + + "\x03\x02\x02\x02\u019E\u01A1\x05+\x14\x02\u019F\u01A1\v\x02\x02\x02\u01A0" + + "\u019E\x03\x02\x02\x02\u01A0\u019F\x03\x02\x02\x02\u01A1\u01A4\x03\x02" + + "\x02\x02\u01A2\u01A3\x03\x02\x02\x02\u01A2\u01A0\x03\x02\x02\x02\u01A3" + + "\u01A5\x03\x02\x02\x02\u01A4\u01A2\x03\x02\x02\x02\u01A5\u01A6\x07,\x02" + + "\x02\u01A6\u01A7\x071\x02\x02\u01A7\u01A8\x03\x02\x02\x02\u01A8\u01A9" + + "\b\x14\x06\x02\u01A9,\x03\x02\x02\x02\u01AA\u01AC\t\x03\x02\x02\u01AB" + + "\u01AA\x03\x02\x02\x02\u01AC\u01AD\x03\x02\x02\x02\u01AD\u01AB\x03\x02" + + "\x02\x02\u01AD\u01AE\x03\x02\x02\x02\u01AE\u01AF\x03\x02\x02\x02\u01AF" + + "\u01B0\b\x15\x06\x02\u01B0.\x03\x02\x02\x02\u01B1\u01B2\x07]\x02\x02\u01B2" + + "\u01B3\x03\x02\x02\x02\u01B3\u01B4\b\x16\x07\x02\u01B4\u01B5\b\x16\b\x02" + + "\u01B50\x03\x02\x02\x02\u01B6\u01B7\x07~\x02\x02\u01B7\u01B8\x03\x02\x02" + + "\x02\u01B8\u01B9\b\x17\t\x02\u01B9\u01BA\b\x17\n\x02\u01BA2\x03\x02\x02" + + "\x02\u01BB\u01BC\x05-\x15\x02\u01BC\u01BD\x03\x02\x02\x02\u01BD\u01BE" + + "\b\x18\x06\x02\u01BE4\x03\x02\x02\x02\u01BF\u01C0\x05)\x13\x02\u01C0\u01C1" + + "\x03\x02\x02\x02\u01C1\u01C2\b\x19\x06\x02\u01C26\x03\x02\x02\x02\u01C3" + + "\u01C4\x05+\x14\x02\u01C4\u01C5\x03\x02\x02\x02\u01C5\u01C6\b\x1A\x06" + + "\x02\u01C68\x03\x02\x02\x02\u01C7\u01C8\x07~\x02\x02\u01C8\u01C9\x03\x02" + + "\x02\x02\u01C9\u01CA\b\x1B\n\x02\u01CA:\x03\x02\x02\x02\u01CB\u01CC\t" + + "\x04\x02\x02\u01CC<\x03\x02\x02\x02\u01CD\u01CE\t\x05\x02\x02\u01CE>\x03" + + "\x02\x02\x02\u01CF\u01D0\x07^\x02\x02\u01D0\u01D1\t\x06\x02\x02\u01D1" + + "@\x03\x02\x02\x02\u01D2\u01D3\n\x07\x02\x02\u01D3B\x03\x02\x02\x02\u01D4" + + "\u01D6\t\b\x02\x02\u01D5\u01D7\t\t\x02\x02\u01D6\u01D5\x03\x02\x02\x02" + + "\u01D6\u01D7\x03\x02\x02\x02\u01D7\u01D9\x03\x02\x02\x02\u01D8\u01DA\x05" + + ";\x1C\x02\u01D9\u01D8\x03\x02\x02\x02\u01DA\u01DB\x03\x02\x02\x02\u01DB" + + "\u01D9\x03\x02\x02\x02\u01DB\u01DC\x03\x02\x02\x02\u01DCD\x03\x02\x02" + + "\x02\u01DD\u01E2\x07$\x02\x02\u01DE\u01E1\x05?\x1E\x02\u01DF\u01E1\x05" + + "A\x1F\x02\u01E0\u01DE\x03\x02\x02\x02\u01E0\u01DF\x03\x02\x02\x02\u01E1" + + "\u01E4\x03\x02\x02\x02\u01E2\u01E0\x03\x02\x02\x02\u01E2\u01E3\x03\x02" + + "\x02\x02\u01E3\u01E5\x03\x02\x02\x02\u01E4\u01E2\x03\x02\x02\x02\u01E5" + + "\u01FB\x07$\x02\x02\u01E6\u01E7\x07$\x02\x02\u01E7\u01E8\x07$\x02\x02" + + "\u01E8\u01E9\x07$\x02\x02\u01E9\u01ED\x03\x02\x02\x02\u01EA\u01EC\n\x02" + + "\x02\x02\u01EB\u01EA\x03\x02\x02\x02\u01EC\u01EF\x03\x02\x02\x02\u01ED" + + "\u01EE\x03\x02\x02\x02\u01ED\u01EB\x03\x02\x02\x02\u01EE\u01F0\x03\x02" + + "\x02\x02\u01EF\u01ED\x03\x02\x02\x02\u01F0\u01F1\x07$\x02\x02\u01F1\u01F2" + + "\x07$\x02\x02\u01F2\u01F3\x07$\x02\x02\u01F3\u01F5\x03\x02\x02\x02\u01F4" + + "\u01F6\x07$\x02\x02\u01F5\u01F4\x03\x02\x02\x02\u01F5\u01F6\x03\x02\x02" + + "\x02\u01F6\u01F8\x03\x02\x02\x02\u01F7\u01F9\x07$\x02\x02\u01F8\u01F7" + + "\x03\x02\x02\x02\u01F8\u01F9\x03\x02\x02\x02\u01F9\u01FB\x03\x02\x02\x02" + + "\u01FA\u01DD\x03\x02\x02\x02\u01FA\u01E6\x03\x02\x02\x02\u01FBF\x03\x02" + + "\x02\x02\u01FC\u01FE\x05;\x1C\x02\u01FD\u01FC\x03\x02\x02\x02\u01FE\u01FF" + + "\x03\x02\x02\x02\u01FF\u01FD\x03\x02\x02\x02\u01FF\u0200\x03\x02\x02\x02" + + "\u0200H\x03\x02\x02\x02\u0201\u0203\x05;\x1C\x02\u0202\u0201\x03\x02\x02" + + "\x02\u0203\u0204\x03\x02\x02\x02\u0204\u0202\x03\x02\x02\x02\u0204\u0205" + + "\x03\x02\x02\x02\u0205\u0206\x03\x02\x02\x02\u0206\u020A\x05U)\x02\u0207" + + "\u0209\x05;\x1C\x02\u0208\u0207\x03\x02\x02\x02\u0209\u020C\x03\x02\x02" + + "\x02\u020A\u0208\x03\x02\x02\x02\u020A\u020B\x03\x02\x02\x02\u020B\u022C" + + "\x03\x02\x02\x02\u020C\u020A\x03\x02\x02\x02\u020D\u020F\x05U)\x02\u020E" + + "\u0210\x05;\x1C\x02\u020F\u020E\x03\x02\x02\x02\u0210\u0211\x03\x02\x02" + + "\x02\u0211\u020F\x03\x02\x02\x02\u0211\u0212\x03\x02\x02\x02\u0212\u022C" + + "\x03\x02\x02\x02\u0213\u0215\x05;\x1C\x02\u0214\u0213\x03\x02\x02\x02" + + "\u0215\u0216\x03\x02\x02\x02\u0216\u0214\x03\x02\x02\x02\u0216\u0217\x03" + + "\x02\x02\x02\u0217\u021F\x03\x02\x02\x02\u0218\u021C\x05U)\x02\u0219\u021B" + + "\x05;\x1C\x02\u021A\u0219\x03\x02\x02\x02\u021B\u021E\x03\x02\x02\x02" + + "\u021C\u021A\x03\x02\x02\x02\u021C\u021D\x03\x02\x02\x02\u021D\u0220\x03" + + "\x02\x02\x02\u021E\u021C\x03\x02\x02\x02\u021F\u0218\x03\x02\x02\x02\u021F" + + "\u0220\x03\x02\x02\x02\u0220\u0221\x03\x02\x02\x02\u0221\u0222\x05C \x02" + + "\u0222\u022C\x03\x02\x02\x02\u0223\u0225\x05U)\x02\u0224\u0226\x05;\x1C" + + "\x02\u0225\u0224\x03\x02\x02\x02\u0226\u0227\x03\x02\x02\x02\u0227\u0225" + + "\x03\x02\x02\x02\u0227\u0228\x03\x02\x02\x02\u0228\u0229\x03\x02\x02\x02" + + "\u0229\u022A\x05C \x02\u022A\u022C\x03\x02\x02\x02\u022B\u0202\x03\x02" + + "\x02\x02\u022B\u020D\x03\x02\x02\x02\u022B\u0214\x03\x02\x02\x02\u022B" + + "\u0223\x03\x02\x02\x02\u022CJ\x03\x02\x02\x02\u022D\u022E\x07d\x02\x02" + + "\u022E\u022F\x07{\x02\x02\u022FL\x03\x02\x02\x02\u0230\u0231\x07{\x02" + + "\x02\u0231\u0232\x07g\x02\x02\u0232\u0233\x07c\x02\x02\u0233\u028F\x07" + + "t\x02\x02\u0234\u0235\x07o\x02\x02\u0235\u0236\x07q\x02\x02\u0236\u0237" + + "\x07p\x02\x02\u0237\u0238\x07v\x02\x02\u0238\u028F\x07j\x02\x02\u0239" + + "\u023A\x07f\x02\x02\u023A\u023B\x07c\x02\x02\u023B\u028F\x07{\x02\x02" + + "\u023C\u023D\x07u\x02\x02\u023D\u023E\x07g\x02\x02\u023E\u023F\x07e\x02" + + "\x02\u023F\u0240\x07q\x02\x02\u0240\u0241\x07p\x02\x02\u0241\u028F\x07" + + "f\x02\x02\u0242\u0243\x07o\x02\x02\u0243\u0244\x07k\x02\x02\u0244\u0245" + + "\x07p\x02\x02\u0245\u0246\x07w\x02\x02\u0246\u0247\x07v\x02\x02\u0247" + + "\u028F\x07g\x02\x02\u0248\u0249\x07j\x02\x02\u0249\u024A\x07q\x02\x02" + + "\u024A\u024B\x07w\x02\x02\u024B\u028F\x07t\x02\x02\u024C\u024D\x07y\x02" + + "\x02\u024D\u024E\x07g\x02\x02\u024E\u024F\x07g\x02\x02\u024F\u028F\x07" + + "m\x02\x02\u0250\u0251\x07o\x02\x02\u0251\u0252\x07k\x02\x02\u0252\u0253" + + "\x07n\x02\x02\u0253\u0254\x07n\x02\x02\u0254\u0255\x07k\x02\x02\u0255" + + "\u0256\x07u\x02\x02\u0256\u0257\x07g\x02\x02\u0257\u0258\x07e\x02\x02" + + "\u0258\u0259\x07q\x02\x02\u0259\u025A\x07p\x02\x02\u025A\u028F\x07f\x02" + + "\x02\u025B\u025C\x07{\x02\x02\u025C\u025D\x07g\x02\x02\u025D\u025E\x07" + + "c\x02\x02\u025E\u025F\x07t\x02\x02\u025F\u028F\x07u\x02\x02\u0260\u0261" + + "\x07o\x02\x02\u0261\u0262\x07q\x02\x02\u0262\u0263\x07p\x02\x02\u0263" + + "\u0264\x07v\x02\x02\u0264\u0265\x07j\x02\x02\u0265\u028F\x07u\x02\x02" + + "\u0266\u0267\x07f\x02\x02\u0267\u0268\x07c\x02\x02\u0268\u0269\x07{\x02" + + "\x02\u0269\u028F\x07u\x02\x02\u026A\u026B\x07u\x02\x02\u026B\u026C\x07" + + "g\x02\x02\u026C\u026D\x07e\x02\x02\u026D\u026E\x07q\x02\x02\u026E\u026F" + + "\x07p\x02\x02\u026F\u0270\x07f\x02\x02\u0270\u028F\x07u\x02\x02\u0271" + + "\u0272\x07o\x02\x02\u0272\u0273\x07k\x02\x02\u0273\u0274\x07p\x02\x02" + + "\u0274\u0275\x07w\x02\x02\u0275\u0276\x07v\x02\x02\u0276\u0277\x07g\x02" + + "\x02\u0277\u028F\x07u\x02\x02\u0278\u0279\x07j\x02\x02\u0279\u027A\x07" + + "q\x02\x02\u027A\u027B\x07w\x02\x02\u027B\u027C\x07t\x02\x02\u027C\u028F" + + "\x07u\x02\x02\u027D\u027E\x07y\x02\x02\u027E\u027F\x07g\x02\x02\u027F" + + "\u0280\x07g\x02\x02\u0280\u0281\x07m\x02\x02\u0281\u028F\x07u\x02\x02" + + "\u0282\u0283\x07o\x02\x02\u0283\u0284\x07k\x02\x02\u0284\u0285\x07n\x02" + + "\x02\u0285\u0286\x07n\x02\x02\u0286\u0287\x07k\x02\x02\u0287\u0288\x07" + + "u\x02\x02\u0288\u0289\x07g\x02\x02\u0289\u028A\x07e\x02\x02\u028A\u028B" + + "\x07q\x02\x02\u028B\u028C\x07p\x02\x02\u028C\u028D\x07f\x02\x02\u028D" + + "\u028F\x07u\x02\x02\u028E\u0230\x03\x02\x02\x02\u028E\u0234\x03\x02\x02" + + "\x02\u028E\u0239\x03\x02\x02\x02\u028E\u023C\x03\x02\x02\x02\u028E\u0242" + + "\x03\x02\x02\x02\u028E\u0248\x03\x02\x02\x02\u028E\u024C\x03\x02\x02\x02" + + "\u028E\u0250\x03\x02\x02\x02\u028E\u025B\x03\x02\x02\x02\u028E\u0260\x03" + + "\x02\x02\x02\u028E\u0266\x03\x02\x02\x02\u028E\u026A\x03\x02\x02\x02\u028E" + + "\u0271\x03\x02\x02\x02\u028E\u0278\x03\x02\x02\x02\u028E\u027D\x03\x02" + + "\x02\x02\u028E\u0282\x03\x02\x02\x02\u028FN\x03\x02\x02\x02\u0290\u0291" + + "\x07c\x02\x02\u0291\u0292\x07p\x02\x02\u0292\u0293\x07f\x02\x02\u0293" + + "P\x03\x02\x02\x02\u0294\u0295\x07?\x02\x02\u0295R\x03\x02\x02\x02\u0296" + + "\u0297\x07.\x02\x02\u0297T\x03\x02\x02\x02\u0298\u0299\x070\x02\x02\u0299" + + "V\x03\x02\x02\x02\u029A\u029B\x07*\x02\x02\u029BX\x03\x02\x02\x02\u029C" + + "\u029D\x07]\x02\x02\u029D\u029E\x03\x02\x02\x02\u029E\u029F\b+\x02\x02" + + "\u029F\u02A0\b+\x02\x02\u02A0Z\x03\x02\x02\x02\u02A1\u02A2\x07_\x02\x02" + + "\u02A2\u02A3\x03\x02\x02\x02\u02A3\u02A4\b,\n\x02\u02A4\u02A5\b,\n\x02" + + "\u02A5\\\x03\x02\x02\x02\u02A6\u02A7\x05\xE5q\x02\u02A7\u02A8\x05\xE7" + + "r\x02\u02A8\u02A9\x05\xF1w\x02\u02A9^\x03\x02\x02\x02\u02AA\u02AB\x05" + + "\xE1o\x02\u02AB\u02AC\x05\xDBl\x02\u02AC\u02AD\x05\xDFn\x02\u02AD\u02AE" + + "\x05\xD3h\x02\u02AE`\x03\x02\x02\x02\u02AF\u02B0\x05\xEDu\x02\u02B0\u02B1" + + "\x05\xE1o\x02\u02B1\u02B2\x05\xDBl\x02\u02B2\u02B3\x05\xDFn\x02\u02B3" + + "\u02B4\x05\xD3h\x02\u02B4b\x03\x02\x02\x02\u02B5\u02B6\x05\xDBl\x02\u02B6" + + "\u02B7\x05\xE5q\x02\u02B7d\x03\x02\x02\x02\u02B8\u02B9\x05\xDBl\x02\u02B9" + + "\u02BA\x05\xEFv\x02\u02BAf\x03\x02\x02\x02\u02BB\u02BC\x05\xCBd\x02\u02BC" + + "\u02BD\x05\xEFv\x02\u02BDh\x03\x02\x02\x02\u02BE\u02BF\x05\xE5q\x02\u02BF" + + "\u02C0\x05\xF3x\x02\u02C0\u02C1\x05\xE1o\x02\u02C1\u02C2\x05\xE1o\x02" + + "\u02C2j\x03\x02\x02\x02\u02C3\u02C4\x07q\x02\x02\u02C4\u02C5\x07t\x02" + + "\x02\u02C5l\x03\x02\x02\x02\u02C6\u02C7\x07+\x02\x02\u02C7n\x03\x02\x02" + + "\x02\u02C8\u02C9\x07a\x02\x02\u02C9p\x03\x02\x02\x02\u02CA\u02CB\x07k" + + "\x02\x02\u02CB\u02CC\x07p\x02\x02\u02CC\u02CD\x07h\x02\x02\u02CD\u02CE" + + "\x07q\x02\x02\u02CEr\x03\x02\x02\x02\u02CF\u02D0\x07h\x02\x02\u02D0\u02D1" + + "\x07w\x02\x02\u02D1\u02D2\x07p\x02\x02\u02D2\u02D3\x07e\x02\x02\u02D3" + + "\u02D4\x07v\x02\x02\u02D4\u02D5\x07k\x02\x02\u02D5\u02D6\x07q\x02\x02" + + "\u02D6\u02D7\x07p\x02\x02\u02D7\u02D8\x07u\x02\x02\u02D8t\x03\x02\x02" + + "\x02\u02D9\u02DA\x07v\x02\x02\u02DA\u02DB\x07t\x02\x02\u02DB\u02DC\x07" + + "w\x02\x02\u02DC\u02E3\x07g\x02\x02\u02DD\u02DE\x07h\x02\x02\u02DE\u02DF" + + "\x07c\x02\x02\u02DF\u02E0\x07n\x02\x02\u02E0\u02E1\x07u\x02\x02\u02E1" + + "\u02E3\x07g\x02\x02\u02E2\u02D9\x03\x02\x02\x02\u02E2\u02DD\x03\x02\x02" + + "\x02\u02E3v\x03\x02\x02\x02\u02E4\u02E5\x07?\x02\x02\u02E5\u02EF\x07?" + + "\x02\x02\u02E6\u02E7\x07#\x02\x02\u02E7\u02EF\x07?\x02\x02\u02E8\u02EF" + + "\x07>\x02\x02\u02E9\u02EA\x07>\x02\x02\u02EA\u02EF\x07?\x02\x02\u02EB" + + "\u02EF\x07@\x02\x02\u02EC\u02ED\x07@\x02\x02\u02ED\u02EF\x07?\x02\x02" + + "\u02EE\u02E4\x03\x02\x02\x02\u02EE\u02E6\x03\x02\x02\x02\u02EE\u02E8\x03" + + "\x02\x02\x02\u02EE\u02E9\x03\x02\x02\x02\u02EE\u02EB\x03\x02\x02\x02\u02EE" + + "\u02EC\x03\x02\x02\x02\u02EFx\x03\x02\x02\x02\u02F0\u02F1\x07-\x02\x02" + + "\u02F1z\x03\x02\x02\x02\u02F2\u02F3\x07/\x02\x02\u02F3|\x03\x02\x02\x02" + + "\u02F4\u02F5\x07,\x02\x02\u02F5~\x03\x02\x02\x02\u02F6\u02F7\x071\x02" + + "\x02\u02F7\x80\x03\x02\x02\x02\u02F8\u02F9\x07\'\x02\x02\u02F9\x82\x03" + + "\x02\x02\x02\u02FA\u02FB\x073\x02\x02\u02FB\u02FC\x072\x02\x02\u02FC\x84" + + "\x03\x02\x02\x02\u02FD\u02FE\x07c\x02\x02\u02FE\u02FF\x07u\x02\x02\u02FF" + + "\u0305\x07e\x02\x02\u0300\u0301\x07f\x02\x02\u0301\u0302\x07g\x02\x02" + + "\u0302\u0303\x07u\x02\x02\u0303\u0305\x07e\x02\x02\u0304\u02FD\x03\x02" + + "\x02\x02\u0304\u0300\x03\x02\x02\x02\u0305\x86\x03\x02\x02\x02\u0306\u0307" + + "\x07p\x02\x02\u0307\u0308\x07w\x02\x02\u0308\u0309\x07n\x02\x02\u0309" + + "\u030A\x07n\x02\x02\u030A\u030B\x07u\x02\x02\u030B\x88\x03\x02\x02\x02" + + "\u030C\u030D\x07h\x02\x02\u030D\u030E\x07k\x02\x02\u030E\u030F\x07t\x02" + + "\x02\u030F\u0310\x07u\x02\x02\u0310\u0316\x07v\x02\x02\u0311\u0312\x07" + + "n\x02\x02\u0312\u0313\x07c\x02\x02\u0313\u0314\x07u\x02\x02\u0314\u0316" + + "\x07v\x02\x02\u0315\u030C\x03\x02\x02\x02\u0315\u0311\x03\x02\x02\x02" + + "\u0316\x8A\x03\x02\x02\x02\u0317\u0318\x05\xEDu\x02\u0318\u0319\x05\xE7" + + "r\x02\u0319\u031A\x05\xF3x\x02\u031A\u031B\x05\xE5q\x02\u031B\u031C\x05" + + "\xD1g\x02\u031C\u04C6\x03\x02\x02\x02\u031D\u031E\x05\xCBd\x02\u031E\u031F" + + "\x05\xCDe\x02\u031F\u0320\x05\xEFv\x02\u0320\u04C6\x03\x02\x02\x02\u0321" + + "\u0322\x05\xE9s\x02\u0322\u0323\x05\xE7r\x02\u0323\u0324\x05\xF7z\x02" + + "\u0324\u04C6\x03\x02\x02\x02\u0325\u0326\x05\xE1o\x02\u0326\u0327\x05" + + "\xE7r\x02\u0327\u0328\x05\xD7j\x02\u0328\u0329\x05\x83@\x02\u0329\u04C6" + + "\x03\x02\x02\x02\u032A\u032B\x05\xE9s\x02\u032B\u032C\x05\xDBl\x02\u032C" + + "\u04C6\x03\x02\x02\x02\u032D\u032E\x05\xF1w\x02\u032E\u032F\x05\xCBd\x02" + + "\u032F\u0330\x05\xF3x\x02\u0330\u04C6\x03\x02\x02\x02\u0331\u04C6\x05" + + "\xD3h\x02\u0332\u0333\x05\xEFv\x02\u0333\u0334\x05\xF3x\x02\u0334\u0335" + + "\x05\xCDe\x02\u0335\u0336\x05\xEFv\x02\u0336\u0337\x05\xF1w\x02\u0337" + + "\u0338\x05\xEDu\x02\u0338\u0339\x05\xDBl\x02\u0339\u033A\x05\xE5q\x02" + + "\u033A\u033B\x05\xD7j\x02\u033B\u04C6\x03\x02\x02\x02\u033C\u033D\x05" + + "\xF1w\x02\u033D\u033E\x05\xEDu\x02\u033E\u033F\x05\xDBl\x02\u033F\u0340" + + "\x05\xE3p\x02\u0340\u04C6\x03\x02\x02\x02\u0341\u0342\x05\xCFf\x02\u0342" + + "\u0343\x05\xE7r\x02\u0343\u0344\x05\xE5q\x02\u0344\u0345\x05\xCFf\x02" + + "\u0345\u0346\x05\xCBd\x02\u0346\u0347\x05\xF1w\x02\u0347\u04C6\x03\x02" + + "\x02\x02\u0348\u0349\x05\xCFf\x02\u0349\u034A\x05\xE7r\x02\u034A\u034B" + + "\x05\xCBd\x02\u034B\u034C\x05\xE1o\x02\u034C\u034D\x05\xD3h\x02\u034D" + + "\u034E\x05\xEFv\x02\u034E\u034F\x05\xCFf\x02\u034F\u0350\x05\xD3h\x02" + + "\u0350\u04C6\x03\x02\x02\x02\u0351\u0352\x05\xD7j\x02\u0352\u0353\x05" + + "\xEDu\x02\u0353\u0354\x05\xD3h\x02\u0354\u0355\x05\xCBd\x02\u0355\u0356" + + "\x05\xF1w\x02\u0356\u0357\x05\xD3h\x02\u0357\u0358\x05\xEFv\x02\u0358" + + "\u0359\x05\xF1w\x02\u0359\u04C6\x03\x02\x02\x02\u035A\u035B\x05\xE1o\x02" + + "\u035B\u035C\x05\xD3h\x02\u035C\u035D\x05\xD5i\x02\u035D\u035E\x05\xF1" + + "w\x02\u035E\u04C6\x03\x02\x02\x02\u035F\u0360\x05\xE5q\x02\u0360\u0361" + + "\x05\xE7r\x02\u0361\u0362\x05\xF7z\x02\u0362\u04C6\x03\x02\x02\x02\u0363" + + "\u0364\x05\xEDu\x02\u0364\u0365\x05\xDBl\x02\u0365\u0366\x05\xD7j\x02" + + "\u0366\u0367\x05\xD9k\x02\u0367\u0368\x05\xF1w\x02\u0368\u04C6\x03\x02" + + "\x02\x02\u0369\u036A\x05\xEFv\x02\u036A\u036B\x05\xF1w\x02\u036B\u036C" + + "\x05\xCBd\x02\u036C\u036D\x05\xEDu\x02\u036D\u036E\x05\xF1w\x02\u036E" + + "\u036F\x05\xEFv\x02\u036F\u0370\x05o6\x02\u0370\u0371\x05\xF7z\x02\u0371" + + "\u0372\x05\xDBl\x02\u0372\u0373\x05\xF1w\x02\u0373\u0374\x05\xD9k\x02" + + "\u0374\u04C6\x03\x02\x02\x02\u0375\u0376\x05\xD1g\x02\u0376\u0377\x05" + + "\xCBd\x02\u0377\u0378\x05\xF1w\x02\u0378\u0379\x05\xD3h\x02\u0379\u037A" + + "\x05o6\x02\u037A\u037B\x05\xD5i\x02\u037B\u037C\x05\xE7r\x02\u037C\u037D" + + "\x05\xEDu\x02\u037D\u037E\x05\xE3p\x02\u037E\u037F\x05\xCBd\x02\u037F" + + "\u0380\x05\xF1w\x02\u0380\u04C6\x03\x02\x02\x02\u0381\u0382\x05\xD1g\x02" + + "\u0382\u0383\x05\xCBd\x02\u0383\u0384\x05\xF1w\x02\u0384\u0385\x05\xD3" + + "h\x02\u0385\u0386\x05o6\x02\u0386\u0387\x05\xF1w\x02\u0387\u0388\x05\xED" + + "u\x02\u0388\u0389\x05\xF3x\x02\u0389\u038A\x05\xE5q\x02\u038A\u038B\x05" + + "\xCFf\x02\u038B\u04C6\x03\x02\x02\x02\u038C\u038D\x05\xD1g\x02\u038D\u038E" + + "\x05\xCBd\x02\u038E\u038F\x05\xF1w\x02\u038F\u0390\x05\xD3h\x02\u0390" + + "\u0391\x05o6\x02\u0391\u0392\x05\xE9s\x02\u0392\u0393\x05\xCBd\x02\u0393" + + "\u0394\x05\xEDu\x02\u0394\u0395\x05\xEFv\x02\u0395\u0396\x05\xD3h\x02" + + "\u0396\u04C6\x03\x02\x02\x02\u0397\u0398\x05\xCBd\x02\u0398\u0399\x05" + + "\xF3x\x02\u0399\u039A\x05\xF1w\x02\u039A\u039B\x05\xE7r\x02\u039B\u039C" + + "\x05o6\x02\u039C\u039D\x05\xCDe\x02\u039D\u039E\x05\xF3x\x02\u039E\u039F" + + "\x05\xCFf\x02\u039F\u03A0\x05\xDFn\x02\u03A0\u03A1\x05\xD3h\x02\u03A1" + + "\u03A2\x05\xF1w\x02\u03A2\u04C6\x03\x02\x02\x02\u03A3\u03A4\x05\xD1g\x02" + + "\u03A4\u03A5\x05\xCBd\x02\u03A5\u03A6\x05\xF1w\x02\u03A6\u03A7\x05\xD3" + + "h\x02\u03A7\u03A8\x05o6\x02\u03A8\u03A9\x05\xD3h\x02\u03A9\u03AA\x05\xF9" + + "{\x02\u03AA\u03AB\x05\xF1w\x02\u03AB\u03AC\x05\xEDu\x02\u03AC\u03AD\x05" + + "\xCBd\x02\u03AD\u03AE\x05\xCFf\x02\u03AE\u03AF\x05\xF1w\x02\u03AF\u04C6" + + "\x03\x02\x02\x02\u03B0\u03B1\x05\xDBl\x02\u03B1\u03B2\x05\xEFv\x02\u03B2" + + "\u03B3\x05o6\x02\u03B3\u03B4\x05\xD5i\x02\u03B4\u03B5\x05\xDBl\x02\u03B5" + + "\u03B6\x05\xE5q\x02\u03B6\u03B7\x05\xDBl\x02\u03B7\u03B8\x05\xF1w\x02" + + "\u03B8\u03B9\x05\xD3h\x02\u03B9\u04C6\x03\x02\x02\x02\u03BA\u03BB\x05" + + "\xDBl\x02\u03BB\u03BC\x05\xEFv\x02\u03BC\u03BD\x05o6\x02\u03BD\u03BE\x05" + + "\xDBl\x02\u03BE\u03BF\x05\xE5q\x02\u03BF\u03C0\x05\xD5i\x02\u03C0\u03C1" + + "\x05\xDBl\x02\u03C1\u03C2\x05\xE5q\x02\u03C2\u03C3\x05\xDBl\x02\u03C3" + + "\u03C4\x05\xF1w\x02\u03C4\u03C5\x05\xD3h\x02\u03C5\u04C6\x03\x02\x02\x02" + + "\u03C6\u03C7\x05\xCFf\x02\u03C7\u03C8\x05\xCBd\x02\u03C8\u03C9\x05\xEF" + + "v\x02\u03C9\u03CA\x05\xD3h\x02\u03CA\u04C6\x03\x02\x02\x02\u03CB\u03CC" + + "\x05\xE1o\x02\u03CC\u03CD\x05\xD3h\x02\u03CD\u03CE\x05\xE5q\x02\u03CE" + + "\u03CF\x05\xD7j\x02\u03CF\u03D0\x05\xF1w\x02\u03D0\u03D1\x05\xD9k\x02" + + "\u03D1\u04C6\x03\x02\x02\x02\u03D2\u03D3\x05\xE3p\x02\u03D3\u03D4\x05" + + "\xF5y\x02\u03D4\u03D5\x05o6\x02\u03D5\u03D6\x05\xE3p\x02\u03D6\u03D7\x05" + + "\xCBd\x02\u03D7\u03D8\x05\xF9{\x02\u03D8\u04C6\x03\x02\x02\x02\u03D9\u03DA" + + "\x05\xE3p\x02\u03DA\u03DB\x05\xF5y\x02\u03DB\u03DC\x05o6\x02\u03DC\u03DD" + + "\x05\xE3p\x02\u03DD\u03DE\x05\xDBl\x02\u03DE\u03DF\x05\xE5q\x02\u03DF" + + "\u04C6\x03\x02\x02\x02\u03E0\u03E1\x05\xE3p\x02\u03E1\u03E2\x05\xF5y\x02" + + "\u03E2\u03E3\x05o6\x02\u03E3\u03E4\x05\xCBd\x02\u03E4\u03E5\x05\xF5y\x02" + + "\u03E5\u03E6\x05\xD7j\x02\u03E6\u04C6\x03\x02\x02\x02\u03E7\u03E8\x05" + + "\xE3p\x02\u03E8\u03E9\x05\xF5y\x02\u03E9\u03EA\x05o6\x02\u03EA\u03EB\x05" + + "\xEFv\x02\u03EB\u03EC\x05\xF3x\x02\u03EC\u03ED\x05\xE3p\x02\u03ED\u04C6" + + "\x03\x02\x02\x02\u03EE\u03EF\x05\xE3p\x02\u03EF\u03F0\x05\xF5y\x02\u03F0" + + "\u03F1\x05o6"; + private static readonly _serializedATNSegment2: string = + "\x02\u03F1\u03F2\x05\xCFf\x02\u03F2\u03F3\x05\xE7r\x02\u03F3\u03F4\x05" + + "\xF3x\x02\u03F4\u03F5\x05\xE5q\x02\u03F5\u03F6\x05\xF1w\x02\u03F6\u04C6" + + "\x03\x02\x02\x02\u03F7\u03F8\x05\xE3p\x02\u03F8\u03F9\x05\xF5y\x02\u03F9" + + "\u03FA\x05o6\x02\u03FA\u03FB\x05\xCFf\x02\u03FB\u03FC\x05\xE7r\x02\u03FC" + + "\u03FD\x05\xE5q\x02\u03FD\u03FE\x05\xCFf\x02\u03FE\u03FF\x05\xCBd\x02" + + "\u03FF\u0400\x05\xF1w\x02\u0400\u04C6\x03\x02\x02\x02\u0401\u0402\x05" + + "\xE3p\x02\u0402\u0403\x05\xF5y\x02\u0403\u0404\x05o6\x02\u0404\u0405\x05" + + "\xDDm\x02\u0405\u0406\x05\xE7r\x02\u0406\u0407\x05\xDBl\x02\u0407\u0408" + + "\x05\xE5q\x02\u0408\u04C6\x03\x02\x02\x02\u0409\u040A\x05\xE3p\x02\u040A" + + "\u040B\x05\xF5y\x02\u040B\u040C\x05o6\x02\u040C\u040D\x05\xE3p\x02\u040D" + + "\u040E\x05\xD3h\x02\u040E\u040F\x05\xD1g\x02\u040F\u0410\x05\xDBl\x02" + + "\u0410\u0411\x05\xCBd\x02\u0411\u0412\x05\xE5q\x02\u0412\u04C6\x03\x02" + + "\x02\x02\u0413\u0414\x05\xE3p\x02\u0414\u0415\x05\xF5y\x02\u0415\u0416" + + "\x05o6\x02\u0416\u0417\x05\xD1g\x02\u0417\u0418\x05\xD3h\x02\u0418\u0419" + + "\x05\xD1g\x02\u0419\u041A\x05\xF3x\x02\u041A\u041B\x05\xE9s\x02\u041B" + + "\u041C\x05\xD3h\x02\u041C\u04C6\x03\x02\x02\x02\u041D\u041E\x05\xE3p\x02" + + "\u041E\u041F\x05\xD3h\x02\u041F\u0420\x05\xF1w\x02\u0420\u0421\x05\xCB" + + "d\x02\u0421\u0422\x05\xD1g\x02\u0422\u0423\x05\xCBd\x02\u0423\u0424\x05" + + "\xF1w\x02\u0424\u0425\x05\xCBd\x02\u0425\u04C6\x03\x02\x02\x02\u0426\u0427" + + "\x05\xEFv\x02\u0427\u0428\x05\xE9s\x02\u0428\u0429\x05\xE1o\x02\u0429" + + "\u042A\x05\xDBl\x02\u042A\u042B\x05\xF1w\x02\u042B\u04C6\x03\x02\x02\x02" + + "\u042C\u042D\x05\xF1w\x02\u042D\u042E\x05\xE7r\x02\u042E\u042F\x05o6\x02" + + "\u042F\u0430\x05\xEFv\x02\u0430\u0431\x05\xF1w\x02\u0431\u0432\x05\xED" + + "u\x02\u0432\u0433\x05\xDBl\x02\u0433\u0434\x05\xE5q\x02\u0434\u0435\x05" + + "\xD7j\x02\u0435\u04C6\x03\x02\x02\x02\u0436\u0437\x05\xF1w\x02\u0437\u0438" + + "\x05\xE7r\x02\u0438\u0439\x05o6\x02\u0439\u043A\x05\xEFv\x02\u043A\u043B" + + "\x05\xF1w\x02\u043B\u043C\x05\xEDu\x02\u043C\u04C6\x03\x02\x02\x02\u043D" + + "\u043E\x05\xF1w\x02\u043E\u043F\x05\xE7r\x02\u043F\u0440\x05o6\x02\u0440" + + "\u0441\x05\xCDe\x02\u0441\u0442\x05\xE7r\x02\u0442\u0443\x05\xE7r\x02" + + "\u0443\u0444\x05\xE1o\x02\u0444\u04C6\x03\x02\x02\x02\u0445\u0446\x05" + + "\xF1w\x02\u0446\u0447\x05\xE7r\x02\u0447\u0448\x05o6\x02\u0448\u0449\x05" + + "\xCDe\x02\u0449\u044A\x05\xE7r\x02\u044A\u044B\x05\xE7r\x02\u044B\u044C" + + "\x05\xE1o\x02\u044C\u044D\x05\xD3h\x02\u044D\u044E\x05\xCBd\x02\u044E" + + "\u044F\x05\xE5q\x02\u044F\u04C6\x03\x02\x02\x02\u0450\u0451\x05\xF1w\x02" + + "\u0451\u0452\x05\xE7r\x02\u0452\u0453\x05o6\x02\u0453\u0454\x05\xD1g\x02" + + "\u0454\u0455\x05\xCBd\x02\u0455\u0456\x05\xF1w\x02\u0456\u0457\x05\xD3" + + "h\x02\u0457\u0458\x05\xF1w\x02\u0458\u0459\x05\xDBl\x02\u0459\u045A\x05" + + "\xE3p\x02\u045A\u045B\x05\xD3h\x02\u045B\u04C6\x03\x02\x02\x02\u045C\u045D" + + "\x05\xF1w\x02\u045D\u045E\x05\xE7r\x02\u045E\u045F\x05o6\x02\u045F\u0460" + + "\x05\xD1g\x02\u0460\u0461\x05\xF1w\x02\u0461\u04C6\x03\x02\x02\x02\u0462" + + "\u0463\x05\xF1w\x02\u0463\u0464\x05\xE7r\x02\u0464\u0465\x05o6\x02\u0465" + + "\u0466\x05\xD1g\x02\u0466\u0467\x05\xCDe\x02\u0467\u0468\x05\xE1o\x02" + + "\u0468\u04C6\x03\x02\x02\x02\u0469\u046A\x05\xF1w\x02\u046A\u046B\x05" + + "\xE7r\x02\u046B\u046C\x05o6\x02\u046C\u046D\x05\xD1g\x02\u046D\u046E\x05" + + "\xE7r\x02\u046E\u046F\x05\xF3x\x02\u046F\u0470\x05\xCDe\x02\u0470\u0471" + + "\x05\xE1o\x02\u0471\u0472\x05\xD3h\x02\u0472\u04C6\x03\x02\x02\x02\u0473" + + "\u0474\x05\xF1w\x02\u0474\u0475\x05\xE7r\x02\u0475\u0476\x05o6\x02\u0476" + + "\u0477\x05\xD1g\x02\u0477\u0478\x05\xD3h\x02\u0478\u0479\x05\xD7j\x02" + + "\u0479\u047A\x05\xEDu\x02\u047A\u047B\x05\xD3h\x02\u047B\u047C\x05\xD3" + + "h\x02\u047C\u047D\x05\xEFv\x02\u047D\u04C6\x03\x02\x02\x02\u047E\u047F" + + "\x05\xF1w\x02\u047F\u0480\x05\xE7r\x02\u0480\u0481\x05o6\x02\u0481\u0482" + + "\x05\xDBl\x02\u0482\u0483\x05\xE5q\x02\u0483\u0484\x05\xF1w\x02\u0484" + + "\u04C6\x03\x02\x02\x02\u0485\u0486\x05\xF1w\x02\u0486\u0487\x05\xE7r\x02" + + "\u0487\u0488\x05o6\x02\u0488\u0489\x05\xDBl\x02\u0489\u048A\x05\xE5q\x02" + + "\u048A\u048B\x05\xF1w\x02\u048B\u048C\x05\xD3h\x02\u048C\u048D\x05\xD7" + + "j\x02\u048D\u048E\x05\xD3h\x02\u048E\u048F\x05\xEDu\x02\u048F\u04C6\x03" + + "\x02\x02\x02\u0490\u0491\x05\xF1w\x02\u0491\u0492\x05\xE7r\x02\u0492\u0493" + + "\x05o6\x02\u0493\u0494\x05\xDBl\x02\u0494\u0495\x05\xE9s\x02\u0495\u04C6" + + "\x03\x02\x02\x02\u0496\u0497\x05\xF1w\x02\u0497\u0498\x05\xE7r\x02\u0498" + + "\u0499\x05o6\x02\u0499\u049A\x05\xE1o\x02\u049A\u049B\x05\xE7r\x02\u049B" + + "\u049C\x05\xE5q\x02\u049C\u049D\x05\xD7j\x02\u049D\u04C6\x03\x02\x02\x02" + + "\u049E\u049F\x05\xF1w\x02\u049F\u04A0\x05\xE7r\x02\u04A0\u04A1\x05o6\x02" + + "\u04A1\u04A2\x05\xEDu\x02\u04A2\u04A3\x05\xCBd\x02\u04A3\u04A4\x05\xD1" + + "g\x02\u04A4\u04A5\x05\xDBl\x02\u04A5\u04A6\x05\xCBd\x02\u04A6\u04A7\x05" + + "\xE5q\x02\u04A7\u04A8\x05\xEFv\x02\u04A8\u04C6\x03\x02\x02\x02\u04A9\u04AA" + + "\x05\xF1w\x02\u04AA\u04AB\x05\xE7r\x02\u04AB\u04AC\x05o6\x02\u04AC\u04AD" + + "\x05\xF5y\x02\u04AD\u04AE\x05\xD3h\x02\u04AE\u04AF\x05\xEDu\x02\u04AF" + + "\u04B0\x05\xEFv\x02\u04B0\u04B1\x05\xDBl\x02\u04B1\u04B2\x05\xE7r\x02" + + "\u04B2\u04B3\x05\xE5q\x02\u04B3\u04C6\x03\x02\x02\x02\u04B4\u04B5\x05" + + "\xF1w\x02\u04B5\u04B6\x05\xE7r\x02\u04B6\u04B7\x05o6\x02\u04B7\u04B8\x05" + + "\xF3x\x02\u04B8\u04B9\x05\xE5q\x02\u04B9\u04BA\x05\xEFv\x02\u04BA\u04BB" + + "\x05\xDBl\x02\u04BB\u04BC\x05\xD7j\x02\u04BC\u04BD\x05\xE5q\x02\u04BD" + + "\u04BE\x05\xD3h\x02\u04BE\u04BF\x05\xD1g\x02\u04BF\u04C0\x05o6\x02\u04C0" + + "\u04C1\x05\xE1o\x02\u04C1\u04C2\x05\xE7r\x02\u04C2\u04C3\x05\xE5q\x02" + + "\u04C3\u04C4\x05\xD7j\x02\u04C4\u04C6\x03\x02\x02\x02\u04C5\u0317\x03" + + "\x02\x02\x02\u04C5\u031D\x03\x02\x02\x02\u04C5\u0321\x03\x02\x02\x02\u04C5" + + "\u0325\x03\x02\x02\x02\u04C5\u032A\x03\x02\x02\x02\u04C5\u032D\x03\x02" + + "\x02\x02\u04C5\u0331\x03\x02\x02\x02\u04C5\u0332\x03\x02\x02\x02\u04C5" + + "\u033C\x03\x02\x02\x02\u04C5\u0341\x03\x02\x02\x02\u04C5\u0348\x03\x02" + + "\x02\x02\u04C5\u0351\x03\x02\x02\x02\u04C5\u035A\x03\x02\x02\x02\u04C5" + + "\u035F\x03\x02\x02\x02\u04C5\u0363\x03\x02\x02\x02\u04C5\u0369\x03\x02" + + "\x02\x02\u04C5\u0375\x03\x02\x02\x02\u04C5\u0381\x03\x02\x02\x02\u04C5" + + "\u038C\x03\x02\x02\x02\u04C5\u0397\x03\x02\x02\x02\u04C5\u03A3\x03\x02" + + "\x02\x02\u04C5\u03B0\x03\x02\x02\x02\u04C5\u03BA\x03\x02\x02\x02\u04C5" + + "\u03C6\x03\x02\x02\x02\u04C5\u03CB\x03\x02\x02\x02\u04C5\u03D2\x03\x02" + + "\x02\x02\u04C5\u03D9\x03\x02\x02\x02\u04C5\u03E0\x03\x02\x02\x02\u04C5" + + "\u03E7\x03\x02\x02\x02\u04C5\u03EE\x03\x02\x02\x02\u04C5\u03F7\x03\x02" + + "\x02\x02\u04C5\u0401\x03\x02\x02\x02\u04C5\u0409\x03\x02\x02\x02\u04C5" + + "\u0413\x03\x02\x02\x02\u04C5\u041D\x03\x02\x02\x02\u04C5\u0426\x03\x02" + + "\x02\x02\u04C5\u042C\x03\x02\x02\x02\u04C5\u0436\x03\x02\x02\x02\u04C5" + + "\u043D\x03\x02\x02\x02\u04C5\u0445\x03\x02\x02\x02\u04C5\u0450\x03\x02" + + "\x02\x02\u04C5\u045C\x03\x02\x02\x02\u04C5\u0462\x03\x02\x02\x02\u04C5" + + "\u0469\x03\x02\x02\x02\u04C5\u0473\x03\x02\x02\x02\u04C5\u047E\x03\x02" + + "\x02\x02\u04C5\u0485\x03\x02\x02\x02\u04C5\u0490\x03\x02\x02\x02\u04C5" + + "\u0496\x03\x02\x02\x02\u04C5\u049E\x03\x02\x02\x02\u04C5\u04A9\x03\x02" + + "\x02\x02\u04C5\u04B4\x03\x02\x02\x02\u04C6\x8C\x03\x02\x02\x02\u04C7\u04C8" + + "\x05\xCBd\x02\u04C8\u04C9\x05\xF5y\x02\u04C9\u04CA\x05\xD7j\x02\u04CA" + + "\u055F\x03\x02\x02\x02\u04CB\u04CC\x05\xE3p\x02\u04CC\u04CD\x05\xDBl\x02" + + "\u04CD\u04CE\x05\xE5q\x02\u04CE\u055F\x03\x02\x02\x02\u04CF\u04D0\x05" + + "\xE3p\x02\u04D0\u04D1\x05\xCBd\x02\u04D1\u04D2\x05\xF9{\x02\u04D2\u055F" + + "\x03\x02\x02\x02\u04D3\u04D4\x05\xEFv\x02\u04D4\u04D5\x05\xF3x\x02\u04D5" + + "\u04D6\x05\xE3p\x02\u04D6\u055F\x03\x02\x02\x02\u04D7\u04D8\x05\xCFf\x02" + + "\u04D8\u04D9\x05\xE7r\x02\u04D9\u04DA\x05\xF3x\x02\u04DA\u04DB\x05\xE5" + + "q\x02\u04DB\u04DC\x05\xF1w\x02\u04DC\u055F\x03\x02\x02\x02\u04DD\u04DE" + + "\x05\xCFf\x02\u04DE\u04DF\x05\xE7r\x02\u04DF\u04E0\x05\xF3x\x02\u04E0" + + "\u04E1\x05\xE5q\x02\u04E1\u04E2\x05\xF1w\x02\u04E2\u04E3\x05o6\x02\u04E3" + + "\u04E4\x05\xD1g\x02\u04E4\u04E5\x05\xDBl\x02\u04E5\u04E6\x05\xEFv\x02" + + "\u04E6\u04E7\x05\xF1w\x02\u04E7\u04E8\x05\xDBl\x02\u04E8\u04E9\x05\xE5" + + "q\x02\u04E9\u04EA\x05\xCFf\x02\u04EA\u04EB\x05\xF1w\x02\u04EB\u055F\x03" + + "\x02\x02\x02\u04EC\u04ED\x05\xE9s\x02\u04ED\u04EE\x05\xD3h\x02\u04EE\u04EF" + + "\x05\xEDu\x02\u04EF\u04F0\x05\xCFf\x02\u04F0\u04F1\x05\xD3h\x02\u04F1" + + "\u04F2\x05\xE5q\x02\u04F2\u04F3\x05\xF1w\x02\u04F3\u04F4\x05\xDBl\x02" + + "\u04F4\u04F5\x05\xE1o\x02\u04F5\u04F6\x05\xD3h\x02\u04F6\u055F\x03\x02" + + "\x02\x02\u04F7\u04F8\x05\xE3p\x02\u04F8\u04F9\x05\xD3h\x02\u04F9\u04FA" + + "\x05\xD1g\x02\u04FA\u04FB\x05\xDBl\x02\u04FB\u04FC\x05\xCBd\x02\u04FC" + + "\u04FD\x05\xE5q\x02\u04FD\u055F\x03\x02\x02\x02\u04FE\u04FF\x05\xE3p\x02" + + "\u04FF\u0500\x05\xD3h\x02\u0500\u0501\x05\xD1g\x02\u0501\u0502\x05\xDB" + + "l\x02\u0502\u0503\x05\xCBd\x02\u0503\u0504\x05\xE5q\x02\u0504\u0505\x05" + + "o6\x02\u0505\u0506\x05\xCBd\x02\u0506\u0507\x05\xCDe\x02\u0507\u0508\x05" + + "\xEFv\x02\u0508\u0509\x05\xE7r\x02\u0509\u050A\x05\xE1o\x02\u050A\u050B" + + "\x05\xF3x\x02\u050B\u050C\x05\xF1w\x02\u050C\u050D\x05\xD3h\x02\u050D" + + "\u050E\x05o6\x02\u050E\u050F\x05\xD1g\x02\u050F\u0510\x05\xD3h\x02\u0510" + + "\u0511\x05\xF5y\x02\u0511\u0512\x05\xDBl\x02\u0512\u0513\x05\xCBd\x02" + + "\u0513\u0514\x05\xF1w\x02\u0514\u0515\x05\xDBl\x02\u0515\u0516\x05\xE7" + + "r\x02\u0516\u0517\x05\xE5q\x02\u0517\u055F\x03\x02\x02\x02\u0518\u0519" + + "\x05\xCBd\x02\u0519\u051A\x05\xCFf\x02\u051A\u051B\x05\xE7r\x02\u051B" + + "\u051C\x05\xEFv\x02\u051C\u055F\x03\x02\x02\x02\u051D\u051E\x05\xCBd\x02" + + "\u051E\u051F\x05\xEFv\x02\u051F\u0520\x05\xDBl\x02\u0520\u0521\x05\xE5" + + "q\x02\u0521\u055F\x03\x02\x02\x02\u0522\u0523\x05\xCBd\x02\u0523\u0524" + + "\x05\xF1w\x02\u0524\u0525\x05\xCBd\x02\u0525\u0526\x05\xE5q\x02\u0526" + + "\u055F\x03\x02\x02\x02\u0527\u0528\x05\xCBd\x02\u0528\u0529\x05\xF1w\x02" + + "\u0529\u052A\x05\xCBd\x02\u052A\u052B\x05\xE5q\x02\u052B\u052C\x074\x02" + + "\x02\u052C\u055F\x03\x02\x02\x02\u052D\u052E\x05\xCFf\x02\u052E\u052F" + + "\x05\xD3h\x02\u052F\u0530\x05\xDBl\x02\u0530\u0531\x05\xE1o\x02\u0531" + + "\u055F\x03\x02\x02\x02\u0532\u0533\x05\xCFf\x02\u0533\u0534\x05\xE7r\x02" + + "\u0534\u0535\x05\xEFv\x02\u0535\u055F\x03\x02\x02\x02\u0536\u0537\x05" + + "\xCFf\x02\u0537\u0538\x05\xE7r\x02\u0538\u0539\x05\xEFv\x02\u0539\u053A" + + "\x05\xD9k\x02\u053A\u055F\x03\x02\x02\x02\u053B\u053C\x05\xD5i\x02\u053C" + + "\u053D\x05\xE1o\x02\u053D\u053E\x05\xE7r\x02\u053E\u053F\x05\xE7r\x02" + + "\u053F\u0540\x05\xEDu\x02\u0540\u055F\x03\x02\x02\x02\u0541\u0542\x05" + + "\xE1o\x02\u0542\u0543\x05\xF1w\x02\u0543\u0544\x05\xEDu\x02\u0544\u0545" + + "\x05\xDBl\x02\u0545\u0546\x05\xE3p\x02\u0546\u055F\x03\x02\x02\x02\u0547" + + "\u0548\x05\xEFv\x02\u0548\u0549\x05\xDBl\x02\u0549\u054A\x05\xE5q\x02" + + "\u054A\u055F\x03\x02\x02\x02\u054B\u054C\x05\xEFv\x02\u054C\u054D\x05" + + "\xDBl\x02\u054D\u054E\x05\xE5q\x02\u054E\u054F\x05\xD9k\x02\u054F\u055F" + + "\x03\x02\x02\x02\u0550\u0551\x05\xEFv\x02\u0551\u0552\x05\xEBt\x02\u0552" + + "\u0553\x05\xEDu\x02\u0553\u0554\x05\xF1w\x02\u0554\u055F\x03\x02\x02\x02" + + "\u0555\u0556\x05\xF1w\x02\u0556\u0557\x05\xCBd\x02\u0557\u0558\x05\xE5" + + "q\x02\u0558\u055F\x03\x02\x02\x02\u0559\u055A\x05\xF1w\x02\u055A\u055B" + + "\x05\xCBd\x02\u055B\u055C\x05\xE5q\x02\u055C\u055D\x05\xD9k\x02\u055D" + + "\u055F\x03\x02\x02\x02\u055E\u04C7\x03\x02\x02\x02\u055E\u04CB\x03\x02" + + "\x02\x02\u055E\u04CF\x03\x02\x02\x02\u055E\u04D3\x03\x02\x02\x02\u055E" + + "\u04D7\x03\x02\x02\x02\u055E\u04DD\x03\x02\x02\x02\u055E\u04EC\x03\x02" + + "\x02\x02\u055E\u04F7\x03\x02\x02\x02\u055E\u04FE\x03\x02\x02\x02\u055E" + + "\u0518\x03\x02\x02\x02\u055E\u051D\x03\x02\x02\x02\u055E\u0522\x03\x02" + + "\x02\x02\u055E\u0527\x03\x02\x02\x02\u055E\u052D\x03\x02\x02\x02\u055E" + + "\u0532\x03\x02\x02\x02\u055E\u0536\x03\x02\x02\x02\u055E\u053B\x03\x02" + + "\x02\x02\u055E\u0541\x03\x02\x02\x02\u055E\u0547\x03\x02\x02\x02\u055E" + + "\u054B\x03\x02\x02\x02\u055E\u0550\x03\x02\x02\x02\u055E\u0555\x03\x02" + + "\x02\x02\u055E\u0559\x03\x02\x02\x02\u055F\x8E\x03\x02\x02\x02\u0560\u0561" + + "\x05\xCFf\x02\u0561\u0562\x05\xDBl\x02\u0562\u0563\x05\xD1g\x02\u0563" + + "\u0564\x05\xEDu\x02\u0564\u0565\x05o6\x02\u0565\u0566\x05\xE3p\x02\u0566" + + "\u0567\x05\xCBd\x02\u0567\u0568\x05\xF1w\x02\u0568\u0569\x05\xCFf\x02" + + "\u0569\u056A\x05\xD9k\x02\u056A\x90\x03\x02\x02\x02\u056B\u0572\x05=\x1D" + + "\x02\u056C\u0571\x05=\x1D\x02\u056D\u0571\x05;\x1C\x02\u056E\u0571\x07" + + "a\x02\x02\u056F\u0571\x05}=\x02\u0570\u056C\x03\x02\x02\x02\u0570\u056D" + + "\x03\x02\x02\x02\u0570\u056E\x03\x02\x02\x02\u0570\u056F\x03\x02\x02\x02" + + "\u0571\u0574\x03\x02\x02\x02\u0572\u0570\x03\x02\x02\x02\u0572\u0573\x03" + + "\x02\x02\x02\u0573\u057F\x03\x02\x02\x02\u0574\u0572\x03\x02\x02\x02\u0575" + + "\u057A\t\n\x02\x02\u0576\u057B\x05=\x1D\x02\u0577\u057B\x05;\x1C\x02\u0578" + + "\u057B\x07a\x02\x02\u0579\u057B\x05}=\x02\u057A\u0576\x03\x02\x02\x02" + + "\u057A\u0577\x03\x02\x02\x02\u057A\u0578\x03\x02\x02\x02\u057A\u0579\x03" + + "\x02\x02\x02\u057B\u057C\x03\x02\x02\x02\u057C\u057A\x03\x02\x02\x02\u057C" + + "\u057D\x03\x02\x02\x02\u057D\u057F\x03\x02\x02\x02\u057E\u056B\x03\x02" + + "\x02\x02\u057E\u0575\x03\x02\x02\x02\u057F\x92\x03\x02\x02\x02\u0580\u0586" + + "\x07b\x02\x02\u0581\u0585\n\v\x02\x02\u0582\u0583\x07b\x02\x02\u0583\u0585" + + "\x07b\x02\x02\u0584\u0581\x03\x02\x02\x02\u0584\u0582\x03\x02\x02\x02" + + "\u0585\u0588\x03\x02\x02\x02\u0586\u0584\x03\x02\x02\x02\u0586\u0587\x03" + + "\x02\x02\x02\u0587\u0589\x03\x02\x02\x02\u0588\u0586\x03\x02\x02\x02\u0589" + + "\u058A\x07b\x02\x02\u058A\x94\x03\x02\x02\x02\u058B\u058C\x05)\x13\x02" + + "\u058C\u058D\x03\x02\x02\x02\u058D\u058E\bI\x06\x02\u058E\x96\x03\x02" + + "\x02\x02\u058F\u0590\x05+\x14\x02\u0590\u0591\x03\x02\x02\x02\u0591\u0592" + + "\bJ\x06\x02\u0592\x98\x03\x02\x02\x02\u0593\u0594\x05-\x15\x02\u0594\u0595" + + "\x03\x02\x02\x02\u0595\u0596\bK\x06\x02\u0596\x9A\x03\x02\x02\x02\u0597" + + "\u0598\x07~\x02\x02\u0598\u0599\x03\x02\x02\x02\u0599\u059A\bL\t\x02\u059A" + + "\u059B\bL\n\x02\u059B\x9C\x03\x02\x02\x02\u059C\u059D\x07]\x02\x02\u059D" + + "\u059E\x03\x02\x02\x02\u059E\u059F\bM\x07\x02\u059F\u05A0\bM\x04\x02\u05A0" + + "\u05A1\bM\x04\x02\u05A1\x9E\x03\x02\x02\x02\u05A2\u05A3\x07_\x02\x02\u05A3" + + "\u05A4\x03\x02\x02\x02\u05A4\u05A5\bN\n\x02\u05A5\u05A6\bN\n\x02\u05A6" + + "\u05A7\bN\v\x02\u05A7\xA0\x03\x02\x02\x02\u05A8\u05A9\x07.\x02\x02\u05A9" + + "\u05AA\x03\x02\x02\x02\u05AA\u05AB\bO\f\x02\u05AB\xA2\x03\x02\x02\x02" + + "\u05AC\u05AD\x07?\x02\x02\u05AD\u05AE\x03\x02\x02\x02\u05AE\u05AF\bP\r" + + "\x02\u05AF\xA4\x03\x02\x02\x02\u05B0\u05B1\x05\xE3p\x02\u05B1\u05B2\x05" + + "\xD3h\x02\u05B2\u05B3\x05\xF1w\x02\u05B3\u05B4\x05\xCBd\x02\u05B4\u05B5" + + "\x05\xD1g\x02\u05B5\u05B6\x05\xCBd\x02\u05B6\u05B7\x05\xF1w\x02\u05B7" + + "\u05B8\x05\xCBd\x02\u05B8\xA6\x03\x02\x02\x02\u05B9\u05BB\x05\xA9S\x02" + + "\u05BA\u05B9\x03\x02\x02\x02\u05BB\u05BC\x03\x02\x02\x02\u05BC\u05BA\x03" + + "\x02\x02\x02\u05BC\u05BD\x03\x02\x02\x02\u05BD\xA8\x03\x02\x02\x02\u05BE" + + "\u05C0\n\f\x02\x02\u05BF\u05BE\x03\x02\x02\x02\u05C0\u05C1\x03\x02\x02" + + "\x02\u05C1\u05BF\x03\x02\x02\x02\u05C1\u05C2\x03\x02\x02\x02\u05C2\u05C6" + + "\x03\x02\x02\x02\u05C3\u05C4\x071\x02\x02\u05C4\u05C6\n\r\x02\x02\u05C5" + + "\u05BF\x03\x02\x02\x02\u05C5\u05C3\x03\x02\x02\x02\u05C6\xAA\x03\x02\x02" + + "\x02\u05C7\u05C8\x05\x93H\x02\u05C8\xAC\x03\x02\x02\x02\u05C9\u05CA\x05" + + ")\x13\x02\u05CA\u05CB\x03\x02\x02\x02\u05CB\u05CC\bU\x06\x02\u05CC\xAE" + + "\x03\x02\x02\x02\u05CD\u05CE\x05+\x14\x02\u05CE\u05CF\x03\x02\x02\x02" + + "\u05CF\u05D0\bV\x06\x02\u05D0\xB0\x03\x02\x02\x02\u05D1\u05D2\x05-\x15" + + "\x02\u05D2\u05D3\x03\x02\x02\x02\u05D3\u05D4\bW\x06\x02\u05D4\xB2\x03" + + "\x02\x02\x02\u05D5\u05D6\x05\xE7r\x02\u05D6\u05D7\x05\xE5q\x02\u05D7\xB4" + + "\x03\x02\x02\x02\u05D8\u05D9\x05\xF7z\x02\u05D9\u05DA\x05\xDBl\x02\u05DA" + + "\u05DB\x05\xF1w\x02\u05DB\u05DC\x05\xD9k\x02\u05DC\xB6\x03\x02\x02\x02" + + "\u05DD\u05DE\x07~\x02\x02\u05DE\u05DF\x03\x02\x02\x02\u05DF\u05E0\bZ\t" + + "\x02\u05E0\u05E1\bZ\n\x02\u05E1\xB8\x03\x02\x02\x02\u05E2\u05E3\x07_\x02" + + "\x02\u05E3\u05E4\x03\x02\x02\x02\u05E4\u05E5\b[\n\x02\u05E5\u05E6\b[\n" + + "\x02\u05E6\u05E7\b[\v\x02\u05E7\xBA\x03\x02\x02\x02\u05E8\u05E9\x07.\x02" + + "\x02\u05E9\u05EA\x03\x02\x02\x02\u05EA\u05EB\b\\\f\x02\u05EB\xBC\x03\x02" + + "\x02\x02\u05EC\u05ED\x07?\x02\x02\u05ED\u05EE\x03\x02\x02\x02\u05EE\u05EF" + + "\b]\r\x02\u05EF\xBE\x03\x02\x02\x02\u05F0\u05F2\x05\xC1_\x02\u05F1\u05F0" + + "\x03\x02\x02\x02\u05F2\u05F3\x03\x02\x02\x02\u05F3\u05F1\x03\x02\x02\x02" + + "\u05F3\u05F4\x03\x02\x02\x02\u05F4\xC0\x03\x02\x02\x02\u05F5\u05F7\n\f" + + "\x02\x02\u05F6\u05F5\x03\x02\x02\x02\u05F7\u05F8\x03\x02\x02\x02\u05F8" + + "\u05F6\x03\x02\x02\x02\u05F8\u05F9\x03\x02\x02\x02\u05F9\u05FD\x03\x02" + + "\x02\x02\u05FA\u05FB\x071\x02\x02\u05FB\u05FD\n\r\x02\x02\u05FC\u05F6" + + "\x03\x02\x02\x02\u05FC\u05FA\x03\x02\x02\x02\u05FD\xC2\x03\x02\x02\x02" + + "\u05FE\u05FF\x05\x93H\x02\u05FF\xC4\x03\x02\x02\x02\u0600\u0601\x05)\x13" + + "\x02\u0601\u0602\x03\x02\x02\x02\u0602\u0603\ba\x06\x02\u0603\xC6\x03" + + "\x02\x02\x02\u0604\u0605\x05+\x14\x02\u0605\u0606\x03\x02\x02\x02\u0606" + + "\u0607\bb\x06\x02\u0607\xC8\x03\x02\x02\x02\u0608\u0609\x05-\x15\x02\u0609" + + "\u060A\x03\x02\x02\x02\u060A\u060B\bc\x06\x02\u060B\xCA\x03\x02\x02\x02" + + "\u060C\u060D\t\x0E\x02\x02\u060D\xCC\x03\x02\x02\x02\u060E\u060F\t\x0F" + + "\x02\x02\u060F\xCE\x03\x02\x02\x02\u0610\u0611\t\x10\x02\x02\u0611\xD0" + + "\x03\x02\x02\x02\u0612\u0613\t\x11\x02\x02\u0613\xD2\x03\x02\x02\x02\u0614" + + "\u0615\t\b\x02\x02\u0615\xD4\x03\x02\x02\x02\u0616\u0617\t\x12\x02\x02" + + "\u0617\xD6\x03\x02\x02\x02\u0618\u0619\t\x13\x02\x02\u0619\xD8\x03\x02" + + "\x02\x02\u061A\u061B\t\x14\x02\x02\u061B\xDA\x03\x02\x02\x02\u061C\u061D" + + "\t\x15\x02\x02\u061D\xDC\x03\x02\x02\x02\u061E\u061F\t\x16\x02\x02\u061F" + + "\xDE\x03\x02\x02\x02\u0620\u0621\t\x17\x02\x02\u0621\xE0\x03\x02\x02\x02" + + "\u0622\u0623\t\x18\x02\x02\u0623\xE2\x03\x02\x02\x02\u0624\u0625\t\x19" + + "\x02\x02\u0625\xE4\x03\x02\x02\x02\u0626\u0627\t\x1A\x02\x02\u0627\xE6" + + "\x03\x02\x02\x02\u0628\u0629\t\x1B\x02\x02\u0629\xE8\x03\x02\x02\x02\u062A" + + "\u062B\t\x1C\x02\x02\u062B\xEA\x03\x02\x02\x02\u062C\u062D\t\x1D\x02\x02" + + "\u062D\xEC\x03\x02\x02\x02\u062E\u062F\t\x1E\x02\x02\u062F\xEE\x03\x02" + + "\x02\x02\u0630\u0631\t\x1F\x02\x02\u0631\xF0\x03\x02\x02\x02\u0632\u0633" + + "\t \x02\x02\u0633\xF2\x03\x02\x02\x02\u0634\u0635\t!\x02\x02\u0635\xF4" + + "\x03\x02\x02\x02\u0636\u0637\t\"\x02\x02\u0637\xF6\x03\x02\x02\x02\u0638" + + "\u0639\t#\x02\x02\u0639\xF8\x03\x02\x02\x02\u063A\u063B\t$\x02\x02\u063B" + + "\xFA\x03\x02\x02\x02\u063C\u063D\t%\x02\x02\u063D\xFC\x03\x02\x02\x02" + + "\u063E\u063F\t&\x02\x02\u063F\xFE\x03\x02\x02\x022\x02\x03\x04\x05\x06" + + "\u0190\u0194\u0197\u01A0\u01A2\u01AD\u01D6\u01DB\u01E0\u01E2\u01ED\u01F5" + + "\u01F8\u01FA\u01FF\u0204\u020A\u0211\u0216\u021C\u021F\u0227\u022B\u028E" + + "\u02E2\u02EE\u0304\u0315\u04C5\u055E\u0570\u0572\u057A\u057C\u057E\u0584" + + "\u0586\u05BC\u05C1\u05C5\u05F3\u05F8\u05FC\x0E\x07\x04\x02\x07\x03\x02" + + "\x07\x05\x02\x07\x06\x02\x02\x03\x02\t%\x02\x07\x02\x02\t\x1A\x02\x06" + + "\x02\x02\t&\x02\t\"\x02\t!\x02"; + public static readonly _serializedATN: string = Utils.join( + [ + esql_lexer._serializedATNSegment0, + esql_lexer._serializedATNSegment1, + esql_lexer._serializedATNSegment2, + ], + "", + ); public static __ATN: ATN; public static get _ATN(): ATN { if (!esql_lexer.__ATN) { diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.g4 b/packages/kbn-monaco/src/esql/antlr/esql_parser.g4 index 6196874af91bd..af48024e56cc9 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_parser.g4 +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.g4 @@ -23,19 +23,50 @@ sourceCommand : explainCommand | fromCommand | rowCommand + | showCommand ; processingCommand : evalCommand | limitCommand | projectCommand + | keepCommand + | renameCommand + | dropCommand + | dissectCommand + | grokCommand | sortCommand | statsCommand | whereCommand + | mvExpandCommand + | enrichCommand + ; + +enrichCommand + : ENRICH policyName=enrichIdentifier (ON matchField=enrichFieldIdentifier)? (WITH enrichWithClause (COMMA enrichWithClause)*)? + ; + +enrichWithClause + : (newName=enrichFieldIdentifier ASSIGN)? enrichField=enrichFieldIdentifier + ; + +mvExpandCommand + : MV_EXPAND qualifiedNames ; whereCommand - : WHERE booleanExpression + : WHERE whereBooleanExpression + ; + +whereBooleanExpression + : NOT whereBooleanExpression + | valueExpression + | regexBooleanExpression + | left=whereBooleanExpression operator=AND right=whereBooleanExpression + | left=whereBooleanExpression operator=OR right=whereBooleanExpression + | valueExpression (NOT)? IN LP valueExpression (COMMA valueExpression)* RP + | (NOT)? WHERE_FUNCTIONS LP qualifiedName ((COMMA functionExpressionArgument)*)? RP + | valueExpression IS NOT? NULL ; booleanExpression @@ -45,6 +76,11 @@ booleanExpression | left=booleanExpression operator=OR right=booleanExpression ; +regexBooleanExpression + : valueExpression (NOT)? kind=LIKE pattern=string + | valueExpression (NOT)? kind=RLIKE pattern=string + ; + valueExpression : operatorExpression | comparison @@ -58,9 +94,18 @@ mathFn : functionIdentifier LP (functionExpressionArgument (COMMA functionExpressionArgument)*)? RP ; +mathEvalFn + : mathFunctionIdentifier LP (mathFunctionExpressionArgument (COMMA mathFunctionExpressionArgument)*)? RP + ; + +dateExpression + : quantifier=number DATE_LITERAL + ; + operatorExpression : primaryExpression | mathFn + | mathEvalFn | operator=(MINUS | PLUS) operatorExpression | left=operatorExpression operator=(ASTERISK | SLASH | PERCENT) right=operatorExpression | left=operatorExpression operator=(PLUS | MINUS) right=operatorExpression @@ -69,6 +114,7 @@ operatorExpression primaryExpression : constant | qualifiedName + | dateExpression | LP booleanExpression RP | identifier LP (booleanExpression (COMMA booleanExpression)*)? RP ; @@ -86,12 +132,21 @@ field | userVariable ASSIGN booleanExpression ; +enrichFieldIdentifier + : ENR_UNQUOTED_IDENTIFIER + | ENR_QUOTED_IDENTIFIER + ; + userVariable : identifier ; fromCommand - : FROM sourceIdentifier (COMMA sourceIdentifier)* + : FROM sourceIdentifier (COMMA sourceIdentifier)* metadata? + ; + +metadata + : OPENING_BRACKET METADATA sourceIdentifier (COMMA sourceIdentifier)* CLOSING_BRACKET ; evalCommand @@ -99,7 +154,7 @@ evalCommand ; statsCommand - : STATS fields (BY qualifiedNames)? + : STATS fields? (BY qualifiedNames)? ; sourceIdentifier @@ -107,9 +162,24 @@ sourceIdentifier | SRC_QUOTED_IDENTIFIER ; +enrichIdentifier + : ENR_UNQUOTED_IDENTIFIER + | ENR_QUOTED_IDENTIFIER + ; + functionExpressionArgument : qualifiedName | string + | number + ; + +mathFunctionExpressionArgument + : qualifiedName + | string + | number + | operatorExpression + | dateExpression + | comparison ; qualifiedName @@ -123,6 +193,11 @@ qualifiedNames identifier : UNQUOTED_IDENTIFIER | QUOTED_IDENTIFIER + | ASTERISK + ; + +mathFunctionIdentifier + : MATH_FUNCTION ; functionIdentifier @@ -130,10 +205,18 @@ functionIdentifier ; constant - : NULL #nullLiteral - | number #numericLiteral - | booleanValue #booleanLiteral - | string #stringLiteral + : NULL + | numericValue + | booleanValue + | string + | OPENING_BRACKET numericValue (COMMA numericValue)* CLOSING_BRACKET + | OPENING_BRACKET booleanValue (COMMA booleanValue)* CLOSING_BRACKET + | OPENING_BRACKET string (COMMA string)* CLOSING_BRACKET + ; + +numericValue + : decimalValue + | integerValue ; limitCommand @@ -149,12 +232,44 @@ orderExpression ; projectCommand - : PROJECT projectClause (COMMA projectClause)* + : PROJECT qualifiedNames + ; + +keepCommand + : KEEP qualifiedNames + ; + + +dropCommand + : DROP qualifiedNames + ; + +renameVariable + : identifier (DOT identifier)* + ; + +renameCommand + : RENAME renameClause (COMMA renameClause)* + ; + +renameClause + : qualifiedName AS renameVariable + ; + +dissectCommand + : DISSECT qualifiedNames string commandOptions? + ; + +grokCommand + : GROK qualifiedNames string + ; + +commandOptions + : commandOption (COMMA commandOption)* ; -projectClause - : sourceIdentifier - | newName=sourceIdentifier ASSIGN oldName=sourceIdentifier +commandOption + : identifier ASSIGN constant ; booleanValue @@ -166,6 +281,14 @@ number | INTEGER_LITERAL #integerLiteral ; +decimalValue + : DECIMAL_LITERAL + ; + +integerValue + : INTEGER_LITERAL + ; + string : STRING ; @@ -181,3 +304,8 @@ explainCommand subqueryExpression : OPENING_BRACKET query CLOSING_BRACKET ; + +showCommand + : SHOW INFO + | SHOW FUNCTIONS + ; diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.interp b/packages/kbn-monaco/src/esql/antlr/esql_parser.interp index 39dc1a09fb8ba..378d85247dc13 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_parser.interp +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.interp @@ -1,14 +1,25 @@ token literal names: null -'eval' -'explain' -'from' -'row' -'stats' -'where' -'sort' -'limit' -'project' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null null null null @@ -17,17 +28,26 @@ null null null 'by' +null 'and' null null '.' '(' -'[' +null ']' -'not' -'null' +null +null +null +null +null +null +null 'or' ')' +'_' +'info' +'functions' null null '+' @@ -35,6 +55,7 @@ null '*' '/' '%' +'10' null 'nulls' null @@ -49,9 +70,22 @@ null null null null +null +null +null +null +null +null +null +null +null +null +null token symbolic names: null +DISSECT +GROK EVAL EXPLAIN FROM @@ -59,16 +93,26 @@ ROW STATS WHERE SORT +MV_EXPAND LIMIT PROJECT +DROP +RENAME +SHOW +ENRICH +KEEP LINE_COMMENT MULTILINE_COMMENT WS +EXPLAIN_WS +EXPLAIN_LINE_COMMENT +EXPLAIN_MULTILINE_COMMENT PIPE STRING INTEGER_LITERAL DECIMAL_LITERAL BY +DATE_LITERAL AND ASSIGN COMMA @@ -77,9 +121,17 @@ LP OPENING_BRACKET CLOSING_BRACKET NOT +LIKE +RLIKE +IN +IS +AS NULL OR RP +UNDERSCORE +INFO +FUNCTIONS BOOLEAN_VALUE COMPARISON_OPERATOR PLUS @@ -87,59 +139,95 @@ MINUS ASTERISK SLASH PERCENT +TEN ORDERING NULLS_ORDERING NULLS_ORDERING_DIRECTION +MATH_FUNCTION UNARY_FUNCTION +WHERE_FUNCTIONS UNQUOTED_IDENTIFIER QUOTED_IDENTIFIER EXPR_LINE_COMMENT EXPR_MULTILINE_COMMENT EXPR_WS +METADATA SRC_UNQUOTED_IDENTIFIER SRC_QUOTED_IDENTIFIER SRC_LINE_COMMENT SRC_MULTILINE_COMMENT SRC_WS +ON +WITH +ENR_UNQUOTED_IDENTIFIER +ENR_QUOTED_IDENTIFIER +ENR_LINE_COMMENT +ENR_MULTILINE_COMMENT +ENR_WS +EXPLAIN_PIPE rule names: singleStatement query sourceCommand processingCommand +enrichCommand +enrichWithClause +mvExpandCommand whereCommand +whereBooleanExpression booleanExpression +regexBooleanExpression valueExpression comparison mathFn +mathEvalFn +dateExpression operatorExpression primaryExpression rowCommand fields field +enrichFieldIdentifier userVariable fromCommand +metadata evalCommand statsCommand sourceIdentifier +enrichIdentifier functionExpressionArgument +mathFunctionExpressionArgument qualifiedName qualifiedNames identifier +mathFunctionIdentifier functionIdentifier constant +numericValue limitCommand sortCommand orderExpression projectCommand -projectClause +keepCommand +dropCommand +renameVariable +renameCommand +renameClause +dissectCommand +grokCommand +commandOptions +commandOption booleanValue number +decimalValue +integerValue string comparisonOperator explainCommand subqueryExpression +showCommand atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 51, 307, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 84, 10, 3, 12, 3, 14, 3, 87, 11, 3, 3, 4, 3, 4, 3, 4, 5, 4, 92, 10, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 100, 10, 5, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 109, 10, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 7, 7, 117, 10, 7, 12, 7, 14, 7, 120, 11, 7, 3, 8, 3, 8, 5, 8, 124, 10, 8, 3, 9, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 7, 10, 135, 10, 10, 12, 10, 14, 10, 138, 11, 10, 5, 10, 140, 10, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 5, 11, 149, 10, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 7, 11, 157, 10, 11, 12, 11, 14, 11, 160, 11, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 173, 10, 12, 12, 12, 14, 12, 176, 11, 12, 5, 12, 178, 10, 12, 3, 12, 3, 12, 5, 12, 182, 10, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 7, 14, 190, 10, 14, 12, 14, 14, 14, 193, 11, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 200, 10, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 7, 17, 208, 10, 17, 12, 17, 14, 17, 211, 11, 17, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19, 5, 19, 220, 10, 19, 3, 20, 3, 20, 3, 21, 3, 21, 5, 21, 226, 10, 21, 3, 22, 3, 22, 3, 22, 7, 22, 231, 10, 22, 12, 22, 14, 22, 234, 11, 22, 3, 23, 3, 23, 3, 23, 7, 23, 239, 10, 23, 12, 23, 14, 23, 242, 11, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 5, 26, 252, 10, 26, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 7, 28, 261, 10, 28, 12, 28, 14, 28, 264, 11, 28, 3, 29, 3, 29, 5, 29, 268, 10, 29, 3, 29, 3, 29, 5, 29, 272, 10, 29, 3, 30, 3, 30, 3, 30, 3, 30, 7, 30, 278, 10, 30, 12, 30, 14, 30, 281, 11, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 5, 31, 288, 10, 31, 3, 32, 3, 32, 3, 33, 3, 33, 5, 33, 294, 10, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 2, 2, 5, 4, 12, 20, 38, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 22, 2, 24, 2, 26, 2, 28, 2, 30, 2, 32, 2, 34, 2, 36, 2, 38, 2, 40, 2, 42, 2, 44, 2, 46, 2, 48, 2, 50, 2, 52, 2, 54, 2, 56, 2, 58, 2, 60, 2, 62, 2, 64, 2, 66, 2, 68, 2, 70, 2, 72, 2, 2, 6, 3, 2, 33, 34, 3, 2, 35, 37, 3, 2, 47, 48, 3, 2, 42, 43, 2, 309, 2, 74, 3, 2, 2, 2, 4, 77, 3, 2, 2, 2, 6, 91, 3, 2, 2, 2, 8, 99, 3, 2, 2, 2, 10, 101, 3, 2, 2, 2, 12, 108, 3, 2, 2, 2, 14, 123, 3, 2, 2, 2, 16, 125, 3, 2, 2, 2, 18, 129, 3, 2, 2, 2, 20, 148, 3, 2, 2, 2, 22, 181, 3, 2, 2, 2, 24, 183, 3, 2, 2, 2, 26, 186, 3, 2, 2, 2, 28, 199, 3, 2, 2, 2, 30, 201, 3, 2, 2, 2, 32, 203, 3, 2, 2, 2, 34, 212, 3, 2, 2, 2, 36, 215, 3, 2, 2, 2, 38, 221, 3, 2, 2, 2, 40, 225, 3, 2, 2, 2, 42, 227, 3, 2, 2, 2, 44, 235, 3, 2, 2, 2, 46, 243, 3, 2, 2, 2, 48, 245, 3, 2, 2, 2, 50, 251, 3, 2, 2, 2, 52, 253, 3, 2, 2, 2, 54, 256, 3, 2, 2, 2, 56, 265, 3, 2, 2, 2, 58, 273, 3, 2, 2, 2, 60, 287, 3, 2, 2, 2, 62, 289, 3, 2, 2, 2, 64, 293, 3, 2, 2, 2, 66, 295, 3, 2, 2, 2, 68, 297, 3, 2, 2, 2, 70, 299, 3, 2, 2, 2, 72, 302, 3, 2, 2, 2, 74, 75, 5, 4, 3, 2, 75, 76, 7, 2, 2, 3, 76, 3, 3, 2, 2, 2, 77, 78, 8, 3, 1, 2, 78, 79, 5, 6, 4, 2, 79, 85, 3, 2, 2, 2, 80, 81, 12, 3, 2, 2, 81, 82, 7, 15, 2, 2, 82, 84, 5, 8, 5, 2, 83, 80, 3, 2, 2, 2, 84, 87, 3, 2, 2, 2, 85, 83, 3, 2, 2, 2, 85, 86, 3, 2, 2, 2, 86, 5, 3, 2, 2, 2, 87, 85, 3, 2, 2, 2, 88, 92, 5, 70, 36, 2, 89, 92, 5, 32, 17, 2, 90, 92, 5, 24, 13, 2, 91, 88, 3, 2, 2, 2, 91, 89, 3, 2, 2, 2, 91, 90, 3, 2, 2, 2, 92, 7, 3, 2, 2, 2, 93, 100, 5, 34, 18, 2, 94, 100, 5, 52, 27, 2, 95, 100, 5, 58, 30, 2, 96, 100, 5, 54, 28, 2, 97, 100, 5, 36, 19, 2, 98, 100, 5, 10, 6, 2, 99, 93, 3, 2, 2, 2, 99, 94, 3, 2, 2, 2, 99, 95, 3, 2, 2, 2, 99, 96, 3, 2, 2, 2, 99, 97, 3, 2, 2, 2, 99, 98, 3, 2, 2, 2, 100, 9, 3, 2, 2, 2, 101, 102, 7, 8, 2, 2, 102, 103, 5, 12, 7, 2, 103, 11, 3, 2, 2, 2, 104, 105, 8, 7, 1, 2, 105, 106, 7, 27, 2, 2, 106, 109, 5, 12, 7, 6, 107, 109, 5, 14, 8, 2, 108, 104, 3, 2, 2, 2, 108, 107, 3, 2, 2, 2, 109, 118, 3, 2, 2, 2, 110, 111, 12, 4, 2, 2, 111, 112, 7, 20, 2, 2, 112, 117, 5, 12, 7, 5, 113, 114, 12, 3, 2, 2, 114, 115, 7, 29, 2, 2, 115, 117, 5, 12, 7, 4, 116, 110, 3, 2, 2, 2, 116, 113, 3, 2, 2, 2, 117, 120, 3, 2, 2, 2, 118, 116, 3, 2, 2, 2, 118, 119, 3, 2, 2, 2, 119, 13, 3, 2, 2, 2, 120, 118, 3, 2, 2, 2, 121, 124, 5, 20, 11, 2, 122, 124, 5, 16, 9, 2, 123, 121, 3, 2, 2, 2, 123, 122, 3, 2, 2, 2, 124, 15, 3, 2, 2, 2, 125, 126, 5, 20, 11, 2, 126, 127, 5, 68, 35, 2, 127, 128, 5, 20, 11, 2, 128, 17, 3, 2, 2, 2, 129, 130, 5, 48, 25, 2, 130, 139, 7, 24, 2, 2, 131, 136, 5, 40, 21, 2, 132, 133, 7, 22, 2, 2, 133, 135, 5, 40, 21, 2, 134, 132, 3, 2, 2, 2, 135, 138, 3, 2, 2, 2, 136, 134, 3, 2, 2, 2, 136, 137, 3, 2, 2, 2, 137, 140, 3, 2, 2, 2, 138, 136, 3, 2, 2, 2, 139, 131, 3, 2, 2, 2, 139, 140, 3, 2, 2, 2, 140, 141, 3, 2, 2, 2, 141, 142, 7, 30, 2, 2, 142, 19, 3, 2, 2, 2, 143, 144, 8, 11, 1, 2, 144, 149, 5, 22, 12, 2, 145, 149, 5, 18, 10, 2, 146, 147, 9, 2, 2, 2, 147, 149, 5, 20, 11, 5, 148, 143, 3, 2, 2, 2, 148, 145, 3, 2, 2, 2, 148, 146, 3, 2, 2, 2, 149, 158, 3, 2, 2, 2, 150, 151, 12, 4, 2, 2, 151, 152, 9, 3, 2, 2, 152, 157, 5, 20, 11, 5, 153, 154, 12, 3, 2, 2, 154, 155, 9, 2, 2, 2, 155, 157, 5, 20, 11, 4, 156, 150, 3, 2, 2, 2, 156, 153, 3, 2, 2, 2, 157, 160, 3, 2, 2, 2, 158, 156, 3, 2, 2, 2, 158, 159, 3, 2, 2, 2, 159, 21, 3, 2, 2, 2, 160, 158, 3, 2, 2, 2, 161, 182, 5, 50, 26, 2, 162, 182, 5, 42, 22, 2, 163, 164, 7, 24, 2, 2, 164, 165, 5, 12, 7, 2, 165, 166, 7, 30, 2, 2, 166, 182, 3, 2, 2, 2, 167, 168, 5, 46, 24, 2, 168, 177, 7, 24, 2, 2, 169, 174, 5, 12, 7, 2, 170, 171, 7, 22, 2, 2, 171, 173, 5, 12, 7, 2, 172, 170, 3, 2, 2, 2, 173, 176, 3, 2, 2, 2, 174, 172, 3, 2, 2, 2, 174, 175, 3, 2, 2, 2, 175, 178, 3, 2, 2, 2, 176, 174, 3, 2, 2, 2, 177, 169, 3, 2, 2, 2, 177, 178, 3, 2, 2, 2, 178, 179, 3, 2, 2, 2, 179, 180, 7, 30, 2, 2, 180, 182, 3, 2, 2, 2, 181, 161, 3, 2, 2, 2, 181, 162, 3, 2, 2, 2, 181, 163, 3, 2, 2, 2, 181, 167, 3, 2, 2, 2, 182, 23, 3, 2, 2, 2, 183, 184, 7, 6, 2, 2, 184, 185, 5, 26, 14, 2, 185, 25, 3, 2, 2, 2, 186, 191, 5, 28, 15, 2, 187, 188, 7, 22, 2, 2, 188, 190, 5, 28, 15, 2, 189, 187, 3, 2, 2, 2, 190, 193, 3, 2, 2, 2, 191, 189, 3, 2, 2, 2, 191, 192, 3, 2, 2, 2, 192, 27, 3, 2, 2, 2, 193, 191, 3, 2, 2, 2, 194, 200, 5, 12, 7, 2, 195, 196, 5, 30, 16, 2, 196, 197, 7, 21, 2, 2, 197, 198, 5, 12, 7, 2, 198, 200, 3, 2, 2, 2, 199, 194, 3, 2, 2, 2, 199, 195, 3, 2, 2, 2, 200, 29, 3, 2, 2, 2, 201, 202, 5, 46, 24, 2, 202, 31, 3, 2, 2, 2, 203, 204, 7, 5, 2, 2, 204, 209, 5, 38, 20, 2, 205, 206, 7, 22, 2, 2, 206, 208, 5, 38, 20, 2, 207, 205, 3, 2, 2, 2, 208, 211, 3, 2, 2, 2, 209, 207, 3, 2, 2, 2, 209, 210, 3, 2, 2, 2, 210, 33, 3, 2, 2, 2, 211, 209, 3, 2, 2, 2, 212, 213, 7, 3, 2, 2, 213, 214, 5, 26, 14, 2, 214, 35, 3, 2, 2, 2, 215, 216, 7, 7, 2, 2, 216, 219, 5, 26, 14, 2, 217, 218, 7, 19, 2, 2, 218, 220, 5, 44, 23, 2, 219, 217, 3, 2, 2, 2, 219, 220, 3, 2, 2, 2, 220, 37, 3, 2, 2, 2, 221, 222, 9, 4, 2, 2, 222, 39, 3, 2, 2, 2, 223, 226, 5, 42, 22, 2, 224, 226, 5, 66, 34, 2, 225, 223, 3, 2, 2, 2, 225, 224, 3, 2, 2, 2, 226, 41, 3, 2, 2, 2, 227, 232, 5, 46, 24, 2, 228, 229, 7, 23, 2, 2, 229, 231, 5, 46, 24, 2, 230, 228, 3, 2, 2, 2, 231, 234, 3, 2, 2, 2, 232, 230, 3, 2, 2, 2, 232, 233, 3, 2, 2, 2, 233, 43, 3, 2, 2, 2, 234, 232, 3, 2, 2, 2, 235, 240, 5, 42, 22, 2, 236, 237, 7, 22, 2, 2, 237, 239, 5, 42, 22, 2, 238, 236, 3, 2, 2, 2, 239, 242, 3, 2, 2, 2, 240, 238, 3, 2, 2, 2, 240, 241, 3, 2, 2, 2, 241, 45, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 243, 244, 9, 5, 2, 2, 244, 47, 3, 2, 2, 2, 245, 246, 7, 41, 2, 2, 246, 49, 3, 2, 2, 2, 247, 252, 7, 28, 2, 2, 248, 252, 5, 64, 33, 2, 249, 252, 5, 62, 32, 2, 250, 252, 5, 66, 34, 2, 251, 247, 3, 2, 2, 2, 251, 248, 3, 2, 2, 2, 251, 249, 3, 2, 2, 2, 251, 250, 3, 2, 2, 2, 252, 51, 3, 2, 2, 2, 253, 254, 7, 10, 2, 2, 254, 255, 7, 17, 2, 2, 255, 53, 3, 2, 2, 2, 256, 257, 7, 9, 2, 2, 257, 262, 5, 56, 29, 2, 258, 259, 7, 22, 2, 2, 259, 261, 5, 56, 29, 2, 260, 258, 3, 2, 2, 2, 261, 264, 3, 2, 2, 2, 262, 260, 3, 2, 2, 2, 262, 263, 3, 2, 2, 2, 263, 55, 3, 2, 2, 2, 264, 262, 3, 2, 2, 2, 265, 267, 5, 12, 7, 2, 266, 268, 7, 38, 2, 2, 267, 266, 3, 2, 2, 2, 267, 268, 3, 2, 2, 2, 268, 271, 3, 2, 2, 2, 269, 270, 7, 39, 2, 2, 270, 272, 7, 40, 2, 2, 271, 269, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 57, 3, 2, 2, 2, 273, 274, 7, 11, 2, 2, 274, 279, 5, 60, 31, 2, 275, 276, 7, 22, 2, 2, 276, 278, 5, 60, 31, 2, 277, 275, 3, 2, 2, 2, 278, 281, 3, 2, 2, 2, 279, 277, 3, 2, 2, 2, 279, 280, 3, 2, 2, 2, 280, 59, 3, 2, 2, 2, 281, 279, 3, 2, 2, 2, 282, 288, 5, 38, 20, 2, 283, 284, 5, 38, 20, 2, 284, 285, 7, 21, 2, 2, 285, 286, 5, 38, 20, 2, 286, 288, 3, 2, 2, 2, 287, 282, 3, 2, 2, 2, 287, 283, 3, 2, 2, 2, 288, 61, 3, 2, 2, 2, 289, 290, 7, 31, 2, 2, 290, 63, 3, 2, 2, 2, 291, 294, 7, 18, 2, 2, 292, 294, 7, 17, 2, 2, 293, 291, 3, 2, 2, 2, 293, 292, 3, 2, 2, 2, 294, 65, 3, 2, 2, 2, 295, 296, 7, 16, 2, 2, 296, 67, 3, 2, 2, 2, 297, 298, 7, 32, 2, 2, 298, 69, 3, 2, 2, 2, 299, 300, 7, 4, 2, 2, 300, 301, 5, 72, 37, 2, 301, 71, 3, 2, 2, 2, 302, 303, 7, 25, 2, 2, 303, 304, 5, 4, 3, 2, 304, 305, 7, 26, 2, 2, 305, 73, 3, 2, 2, 2, 31, 85, 91, 99, 108, 116, 118, 123, 136, 139, 148, 156, 158, 174, 177, 181, 191, 199, 209, 219, 225, 232, 240, 251, 262, 267, 271, 279, 287, 293] \ No newline at end of file +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 83, 598, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 132, 10, 3, 12, 3, 14, 3, 135, 11, 3, 3, 4, 3, 4, 3, 4, 3, 4, 5, 4, 141, 10, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 156, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 162, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 168, 10, 6, 12, 6, 14, 6, 171, 11, 6, 5, 6, 173, 10, 6, 3, 7, 3, 7, 3, 7, 5, 7, 178, 10, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 195, 10, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 7, 10, 202, 10, 10, 12, 10, 14, 10, 205, 11, 10, 3, 10, 3, 10, 3, 10, 5, 10, 210, 10, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 7, 10, 217, 10, 10, 12, 10, 14, 10, 220, 11, 10, 5, 10, 222, 10, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 229, 10, 10, 3, 10, 3, 10, 5, 10, 233, 10, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 7, 10, 241, 10, 10, 12, 10, 14, 10, 244, 11, 10, 3, 11, 3, 11, 3, 11, 3, 11, 5, 11, 250, 10, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 7, 11, 258, 10, 11, 12, 11, 14, 11, 261, 11, 11, 3, 12, 3, 12, 5, 12, 265, 10, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 272, 10, 12, 3, 12, 3, 12, 3, 12, 5, 12, 277, 10, 12, 3, 13, 3, 13, 5, 13, 281, 10, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 7, 15, 292, 10, 15, 12, 15, 14, 15, 295, 11, 15, 5, 15, 297, 10, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 7, 16, 306, 10, 16, 12, 16, 14, 16, 309, 11, 16, 5, 16, 311, 10, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 5, 18, 324, 10, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 7, 18, 332, 10, 18, 12, 18, 14, 18, 335, 11, 18, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 7, 19, 349, 10, 19, 12, 19, 14, 19, 352, 11, 19, 5, 19, 354, 10, 19, 3, 19, 3, 19, 5, 19, 358, 10, 19, 3, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 7, 21, 366, 10, 21, 12, 21, 14, 21, 369, 11, 21, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 5, 22, 376, 10, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 7, 25, 386, 10, 25, 12, 25, 14, 25, 389, 11, 25, 3, 25, 5, 25, 392, 10, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 7, 26, 399, 10, 26, 12, 26, 14, 26, 402, 11, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 5, 28, 411, 10, 28, 3, 28, 3, 28, 5, 28, 415, 10, 28, 3, 29, 3, 29, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 5, 31, 424, 10, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 5, 32, 432, 10, 32, 3, 33, 3, 33, 3, 33, 7, 33, 437, 10, 33, 12, 33, 14, 33, 440, 11, 33, 3, 34, 3, 34, 3, 34, 7, 34, 445, 10, 34, 12, 34, 14, 34, 448, 11, 34, 3, 35, 3, 35, 3, 36, 3, 36, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 464, 10, 38, 12, 38, 14, 38, 467, 11, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 475, 10, 38, 12, 38, 14, 38, 478, 11, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 486, 10, 38, 12, 38, 14, 38, 489, 11, 38, 3, 38, 3, 38, 5, 38, 493, 10, 38, 3, 39, 3, 39, 5, 39, 497, 10, 39, 3, 40, 3, 40, 3, 40, 3, 41, 3, 41, 3, 41, 3, 41, 7, 41, 506, 10, 41, 12, 41, 14, 41, 509, 11, 41, 3, 42, 3, 42, 5, 42, 513, 10, 42, 3, 42, 3, 42, 5, 42, 517, 10, 42, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 45, 3, 45, 3, 45, 3, 46, 3, 46, 3, 46, 7, 46, 531, 10, 46, 12, 46, 14, 46, 534, 11, 46, 3, 47, 3, 47, 3, 47, 3, 47, 7, 47, 540, 10, 47, 12, 47, 14, 47, 543, 11, 47, 3, 48, 3, 48, 3, 48, 3, 48, 3, 49, 3, 49, 3, 49, 3, 49, 5, 49, 553, 10, 49, 3, 50, 3, 50, 3, 50, 3, 50, 3, 51, 3, 51, 3, 51, 7, 51, 562, 10, 51, 12, 51, 14, 51, 565, 11, 51, 3, 52, 3, 52, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 5, 54, 575, 10, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 59, 3, 60, 3, 60, 3, 60, 3, 60, 3, 61, 3, 61, 3, 61, 3, 61, 5, 61, 596, 10, 61, 3, 61, 2, 2, 6, 4, 18, 20, 34, 62, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 22, 2, 24, 2, 26, 2, 28, 2, 30, 2, 32, 2, 34, 2, 36, 2, 38, 2, 40, 2, 42, 2, 44, 2, 46, 2, 48, 2, 50, 2, 52, 2, 54, 2, 56, 2, 58, 2, 60, 2, 62, 2, 64, 2, 66, 2, 68, 2, 70, 2, 72, 2, 74, 2, 76, 2, 78, 2, 80, 2, 82, 2, 84, 2, 86, 2, 88, 2, 90, 2, 92, 2, 94, 2, 96, 2, 98, 2, 100, 2, 102, 2, 104, 2, 106, 2, 108, 2, 110, 2, 112, 2, 114, 2, 116, 2, 118, 2, 120, 2, 2, 7, 3, 2, 53, 54, 3, 2, 55, 57, 3, 2, 78, 79, 3, 2, 71, 72, 4, 2, 55, 55, 65, 66, 2, 627, 2, 122, 3, 2, 2, 2, 4, 125, 3, 2, 2, 2, 6, 140, 3, 2, 2, 2, 8, 155, 3, 2, 2, 2, 10, 157, 3, 2, 2, 2, 12, 177, 3, 2, 2, 2, 14, 181, 3, 2, 2, 2, 16, 184, 3, 2, 2, 2, 18, 232, 3, 2, 2, 2, 20, 249, 3, 2, 2, 2, 22, 276, 3, 2, 2, 2, 24, 280, 3, 2, 2, 2, 26, 282, 3, 2, 2, 2, 28, 286, 3, 2, 2, 2, 30, 300, 3, 2, 2, 2, 32, 314, 3, 2, 2, 2, 34, 323, 3, 2, 2, 2, 36, 357, 3, 2, 2, 2, 38, 359, 3, 2, 2, 2, 40, 362, 3, 2, 2, 2, 42, 375, 3, 2, 2, 2, 44, 377, 3, 2, 2, 2, 46, 379, 3, 2, 2, 2, 48, 381, 3, 2, 2, 2, 50, 393, 3, 2, 2, 2, 52, 405, 3, 2, 2, 2, 54, 408, 3, 2, 2, 2, 56, 416, 3, 2, 2, 2, 58, 418, 3, 2, 2, 2, 60, 423, 3, 2, 2, 2, 62, 431, 3, 2, 2, 2, 64, 433, 3, 2, 2, 2, 66, 441, 3, 2, 2, 2, 68, 449, 3, 2, 2, 2, 70, 451, 3, 2, 2, 2, 72, 453, 3, 2, 2, 2, 74, 492, 3, 2, 2, 2, 76, 496, 3, 2, 2, 2, 78, 498, 3, 2, 2, 2, 80, 501, 3, 2, 2, 2, 82, 510, 3, 2, 2, 2, 84, 518, 3, 2, 2, 2, 86, 521, 3, 2, 2, 2, 88, 524, 3, 2, 2, 2, 90, 527, 3, 2, 2, 2, 92, 535, 3, 2, 2, 2, 94, 544, 3, 2, 2, 2, 96, 548, 3, 2, 2, 2, 98, 554, 3, 2, 2, 2, 100, 558, 3, 2, 2, 2, 102, 566, 3, 2, 2, 2, 104, 570, 3, 2, 2, 2, 106, 574, 3, 2, 2, 2, 108, 576, 3, 2, 2, 2, 110, 578, 3, 2, 2, 2, 112, 580, 3, 2, 2, 2, 114, 582, 3, 2, 2, 2, 116, 584, 3, 2, 2, 2, 118, 587, 3, 2, 2, 2, 120, 595, 3, 2, 2, 2, 122, 123, 5, 4, 3, 2, 123, 124, 7, 2, 2, 3, 124, 3, 3, 2, 2, 2, 125, 126, 8, 3, 1, 2, 126, 127, 5, 6, 4, 2, 127, 133, 3, 2, 2, 2, 128, 129, 12, 3, 2, 2, 129, 130, 7, 26, 2, 2, 130, 132, 5, 8, 5, 2, 131, 128, 3, 2, 2, 2, 132, 135, 3, 2, 2, 2, 133, 131, 3, 2, 2, 2, 133, 134, 3, 2, 2, 2, 134, 5, 3, 2, 2, 2, 135, 133, 3, 2, 2, 2, 136, 141, 5, 116, 59, 2, 137, 141, 5, 48, 25, 2, 138, 141, 5, 38, 20, 2, 139, 141, 5, 120, 61, 2, 140, 136, 3, 2, 2, 2, 140, 137, 3, 2, 2, 2, 140, 138, 3, 2, 2, 2, 140, 139, 3, 2, 2, 2, 141, 7, 3, 2, 2, 2, 142, 156, 5, 52, 27, 2, 143, 156, 5, 78, 40, 2, 144, 156, 5, 84, 43, 2, 145, 156, 5, 86, 44, 2, 146, 156, 5, 92, 47, 2, 147, 156, 5, 88, 45, 2, 148, 156, 5, 96, 49, 2, 149, 156, 5, 98, 50, 2, 150, 156, 5, 80, 41, 2, 151, 156, 5, 54, 28, 2, 152, 156, 5, 16, 9, 2, 153, 156, 5, 14, 8, 2, 154, 156, 5, 10, 6, 2, 155, 142, 3, 2, 2, 2, 155, 143, 3, 2, 2, 2, 155, 144, 3, 2, 2, 2, 155, 145, 3, 2, 2, 2, 155, 146, 3, 2, 2, 2, 155, 147, 3, 2, 2, 2, 155, 148, 3, 2, 2, 2, 155, 149, 3, 2, 2, 2, 155, 150, 3, 2, 2, 2, 155, 151, 3, 2, 2, 2, 155, 152, 3, 2, 2, 2, 155, 153, 3, 2, 2, 2, 155, 154, 3, 2, 2, 2, 156, 9, 3, 2, 2, 2, 157, 158, 7, 18, 2, 2, 158, 161, 5, 58, 30, 2, 159, 160, 7, 76, 2, 2, 160, 162, 5, 44, 23, 2, 161, 159, 3, 2, 2, 2, 161, 162, 3, 2, 2, 2, 162, 172, 3, 2, 2, 2, 163, 164, 7, 77, 2, 2, 164, 169, 5, 12, 7, 2, 165, 166, 7, 34, 2, 2, 166, 168, 5, 12, 7, 2, 167, 165, 3, 2, 2, 2, 168, 171, 3, 2, 2, 2, 169, 167, 3, 2, 2, 2, 169, 170, 3, 2, 2, 2, 170, 173, 3, 2, 2, 2, 171, 169, 3, 2, 2, 2, 172, 163, 3, 2, 2, 2, 172, 173, 3, 2, 2, 2, 173, 11, 3, 2, 2, 2, 174, 175, 5, 44, 23, 2, 175, 176, 7, 33, 2, 2, 176, 178, 3, 2, 2, 2, 177, 174, 3, 2, 2, 2, 177, 178, 3, 2, 2, 2, 178, 179, 3, 2, 2, 2, 179, 180, 5, 44, 23, 2, 180, 13, 3, 2, 2, 2, 181, 182, 7, 12, 2, 2, 182, 183, 5, 66, 34, 2, 183, 15, 3, 2, 2, 2, 184, 185, 7, 10, 2, 2, 185, 186, 5, 18, 10, 2, 186, 17, 3, 2, 2, 2, 187, 188, 8, 10, 1, 2, 188, 189, 7, 39, 2, 2, 189, 233, 5, 18, 10, 10, 190, 233, 5, 24, 13, 2, 191, 233, 5, 22, 12, 2, 192, 194, 5, 24, 13, 2, 193, 195, 7, 39, 2, 2, 194, 193, 3, 2, 2, 2, 194, 195, 3, 2, 2, 2, 195, 196, 3, 2, 2, 2, 196, 197, 7, 42, 2, 2, 197, 198, 7, 36, 2, 2, 198, 203, 5, 24, 13, 2, 199, 200, 7, 34, 2, 2, 200, 202, 5, 24, 13, 2, 201, 199, 3, 2, 2, 2, 202, 205, 3, 2, 2, 2, 203, 201, 3, 2, 2, 2, 203, 204, 3, 2, 2, 2, 204, 206, 3, 2, 2, 2, 205, 203, 3, 2, 2, 2, 206, 207, 7, 47, 2, 2, 207, 233, 3, 2, 2, 2, 208, 210, 7, 39, 2, 2, 209, 208, 3, 2, 2, 2, 209, 210, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2, 211, 212, 7, 64, 2, 2, 212, 213, 7, 36, 2, 2, 213, 221, 5, 64, 33, 2, 214, 215, 7, 34, 2, 2, 215, 217, 5, 60, 31, 2, 216, 214, 3, 2, 2, 2, 217, 220, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 222, 3, 2, 2, 2, 220, 218, 3, 2, 2, 2, 221, 218, 3, 2, 2, 2, 221, 222, 3, 2, 2, 2, 222, 223, 3, 2, 2, 2, 223, 224, 7, 47, 2, 2, 224, 233, 3, 2, 2, 2, 225, 226, 5, 24, 13, 2, 226, 228, 7, 43, 2, 2, 227, 229, 7, 39, 2, 2, 228, 227, 3, 2, 2, 2, 228, 229, 3, 2, 2, 2, 229, 230, 3, 2, 2, 2, 230, 231, 7, 45, 2, 2, 231, 233, 3, 2, 2, 2, 232, 187, 3, 2, 2, 2, 232, 190, 3, 2, 2, 2, 232, 191, 3, 2, 2, 2, 232, 192, 3, 2, 2, 2, 232, 209, 3, 2, 2, 2, 232, 225, 3, 2, 2, 2, 233, 242, 3, 2, 2, 2, 234, 235, 12, 7, 2, 2, 235, 236, 7, 32, 2, 2, 236, 241, 5, 18, 10, 8, 237, 238, 12, 6, 2, 2, 238, 239, 7, 46, 2, 2, 239, 241, 5, 18, 10, 7, 240, 234, 3, 2, 2, 2, 240, 237, 3, 2, 2, 2, 241, 244, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 243, 3, 2, 2, 2, 243, 19, 3, 2, 2, 2, 244, 242, 3, 2, 2, 2, 245, 246, 8, 11, 1, 2, 246, 247, 7, 39, 2, 2, 247, 250, 5, 20, 11, 6, 248, 250, 5, 24, 13, 2, 249, 245, 3, 2, 2, 2, 249, 248, 3, 2, 2, 2, 250, 259, 3, 2, 2, 2, 251, 252, 12, 4, 2, 2, 252, 253, 7, 32, 2, 2, 253, 258, 5, 20, 11, 5, 254, 255, 12, 3, 2, 2, 255, 256, 7, 46, 2, 2, 256, 258, 5, 20, 11, 4, 257, 251, 3, 2, 2, 2, 257, 254, 3, 2, 2, 2, 258, 261, 3, 2, 2, 2, 259, 257, 3, 2, 2, 2, 259, 260, 3, 2, 2, 2, 260, 21, 3, 2, 2, 2, 261, 259, 3, 2, 2, 2, 262, 264, 5, 24, 13, 2, 263, 265, 7, 39, 2, 2, 264, 263, 3, 2, 2, 2, 264, 265, 3, 2, 2, 2, 265, 266, 3, 2, 2, 2, 266, 267, 7, 40, 2, 2, 267, 268, 5, 112, 57, 2, 268, 277, 3, 2, 2, 2, 269, 271, 5, 24, 13, 2, 270, 272, 7, 39, 2, 2, 271, 270, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 273, 3, 2, 2, 2, 273, 274, 7, 41, 2, 2, 274, 275, 5, 112, 57, 2, 275, 277, 3, 2, 2, 2, 276, 262, 3, 2, 2, 2, 276, 269, 3, 2, 2, 2, 277, 23, 3, 2, 2, 2, 278, 281, 5, 34, 18, 2, 279, 281, 5, 26, 14, 2, 280, 278, 3, 2, 2, 2, 280, 279, 3, 2, 2, 2, 281, 25, 3, 2, 2, 2, 282, 283, 5, 34, 18, 2, 283, 284, 5, 114, 58, 2, 284, 285, 5, 34, 18, 2, 285, 27, 3, 2, 2, 2, 286, 287, 5, 72, 37, 2, 287, 296, 7, 36, 2, 2, 288, 293, 5, 60, 31, 2, 289, 290, 7, 34, 2, 2, 290, 292, 5, 60, 31, 2, 291, 289, 3, 2, 2, 2, 292, 295, 3, 2, 2, 2, 293, 291, 3, 2, 2, 2, 293, 294, 3, 2, 2, 2, 294, 297, 3, 2, 2, 2, 295, 293, 3, 2, 2, 2, 296, 288, 3, 2, 2, 2, 296, 297, 3, 2, 2, 2, 297, 298, 3, 2, 2, 2, 298, 299, 7, 47, 2, 2, 299, 29, 3, 2, 2, 2, 300, 301, 5, 70, 36, 2, 301, 310, 7, 36, 2, 2, 302, 307, 5, 62, 32, 2, 303, 304, 7, 34, 2, 2, 304, 306, 5, 62, 32, 2, 305, 303, 3, 2, 2, 2, 306, 309, 3, 2, 2, 2, 307, 305, 3, 2, 2, 2, 307, 308, 3, 2, 2, 2, 308, 311, 3, 2, 2, 2, 309, 307, 3, 2, 2, 2, 310, 302, 3, 2, 2, 2, 310, 311, 3, 2, 2, 2, 311, 312, 3, 2, 2, 2, 312, 313, 7, 47, 2, 2, 313, 31, 3, 2, 2, 2, 314, 315, 5, 106, 54, 2, 315, 316, 7, 31, 2, 2, 316, 33, 3, 2, 2, 2, 317, 318, 8, 18, 1, 2, 318, 324, 5, 36, 19, 2, 319, 324, 5, 28, 15, 2, 320, 324, 5, 30, 16, 2, 321, 322, 9, 2, 2, 2, 322, 324, 5, 34, 18, 5, 323, 317, 3, 2, 2, 2, 323, 319, 3, 2, 2, 2, 323, 320, 3, 2, 2, 2, 323, 321, 3, 2, 2, 2, 324, 333, 3, 2, 2, 2, 325, 326, 12, 4, 2, 2, 326, 327, 9, 3, 2, 2, 327, 332, 5, 34, 18, 5, 328, 329, 12, 3, 2, 2, 329, 330, 9, 2, 2, 2, 330, 332, 5, 34, 18, 4, 331, 325, 3, 2, 2, 2, 331, 328, 3, 2, 2, 2, 332, 335, 3, 2, 2, 2, 333, 331, 3, 2, 2, 2, 333, 334, 3, 2, 2, 2, 334, 35, 3, 2, 2, 2, 335, 333, 3, 2, 2, 2, 336, 358, 5, 74, 38, 2, 337, 358, 5, 64, 33, 2, 338, 358, 5, 32, 17, 2, 339, 340, 7, 36, 2, 2, 340, 341, 5, 20, 11, 2, 341, 342, 7, 47, 2, 2, 342, 358, 3, 2, 2, 2, 343, 344, 5, 68, 35, 2, 344, 353, 7, 36, 2, 2, 345, 350, 5, 20, 11, 2, 346, 347, 7, 34, 2, 2, 347, 349, 5, 20, 11, 2, 348, 346, 3, 2, 2, 2, 349, 352, 3, 2, 2, 2, 350, 348, 3, 2, 2, 2, 350, 351, 3, 2, 2, 2, 351, 354, 3, 2, 2, 2, 352, 350, 3, 2, 2, 2, 353, 345, 3, 2, 2, 2, 353, 354, 3, 2, 2, 2, 354, 355, 3, 2, 2, 2, 355, 356, 7, 47, 2, 2, 356, 358, 3, 2, 2, 2, 357, 336, 3, 2, 2, 2, 357, 337, 3, 2, 2, 2, 357, 338, 3, 2, 2, 2, 357, 339, 3, 2, 2, 2, 357, 343, 3, 2, 2, 2, 358, 37, 3, 2, 2, 2, 359, 360, 7, 8, 2, 2, 360, 361, 5, 40, 21, 2, 361, 39, 3, 2, 2, 2, 362, 367, 5, 42, 22, 2, 363, 364, 7, 34, 2, 2, 364, 366, 5, 42, 22, 2, 365, 363, 3, 2, 2, 2, 366, 369, 3, 2, 2, 2, 367, 365, 3, 2, 2, 2, 367, 368, 3, 2, 2, 2, 368, 41, 3, 2, 2, 2, 369, 367, 3, 2, 2, 2, 370, 376, 5, 20, 11, 2, 371, 372, 5, 46, 24, 2, 372, 373, 7, 33, 2, 2, 373, 374, 5, 20, 11, 2, 374, 376, 3, 2, 2, 2, 375, 370, 3, 2, 2, 2, 375, 371, 3, 2, 2, 2, 376, 43, 3, 2, 2, 2, 377, 378, 9, 4, 2, 2, 378, 45, 3, 2, 2, 2, 379, 380, 5, 68, 35, 2, 380, 47, 3, 2, 2, 2, 381, 382, 7, 7, 2, 2, 382, 387, 5, 56, 29, 2, 383, 384, 7, 34, 2, 2, 384, 386, 5, 56, 29, 2, 385, 383, 3, 2, 2, 2, 386, 389, 3, 2, 2, 2, 387, 385, 3, 2, 2, 2, 387, 388, 3, 2, 2, 2, 388, 391, 3, 2, 2, 2, 389, 387, 3, 2, 2, 2, 390, 392, 5, 50, 26, 2, 391, 390, 3, 2, 2, 2, 391, 392, 3, 2, 2, 2, 392, 49, 3, 2, 2, 2, 393, 394, 7, 37, 2, 2, 394, 395, 7, 70, 2, 2, 395, 400, 5, 56, 29, 2, 396, 397, 7, 34, 2, 2, 397, 399, 5, 56, 29, 2, 398, 396, 3, 2, 2, 2, 399, 402, 3, 2, 2, 2, 400, 398, 3, 2, 2, 2, 400, 401, 3, 2, 2, 2, 401, 403, 3, 2, 2, 2, 402, 400, 3, 2, 2, 2, 403, 404, 7, 38, 2, 2, 404, 51, 3, 2, 2, 2, 405, 406, 7, 5, 2, 2, 406, 407, 5, 40, 21, 2, 407, 53, 3, 2, 2, 2, 408, 410, 7, 9, 2, 2, 409, 411, 5, 40, 21, 2, 410, 409, 3, 2, 2, 2, 410, 411, 3, 2, 2, 2, 411, 414, 3, 2, 2, 2, 412, 413, 7, 30, 2, 2, 413, 415, 5, 66, 34, 2, 414, 412, 3, 2, 2, 2, 414, 415, 3, 2, 2, 2, 415, 55, 3, 2, 2, 2, 416, 417, 9, 5, 2, 2, 417, 57, 3, 2, 2, 2, 418, 419, 9, 4, 2, 2, 419, 59, 3, 2, 2, 2, 420, 424, 5, 64, 33, 2, 421, 424, 5, 112, 57, 2, 422, 424, 5, 106, 54, 2, 423, 420, 3, 2, 2, 2, 423, 421, 3, 2, 2, 2, 423, 422, 3, 2, 2, 2, 424, 61, 3, 2, 2, 2, 425, 432, 5, 64, 33, 2, 426, 432, 5, 112, 57, 2, 427, 432, 5, 106, 54, 2, 428, 432, 5, 34, 18, 2, 429, 432, 5, 32, 17, 2, 430, 432, 5, 26, 14, 2, 431, 425, 3, 2, 2, 2, 431, 426, 3, 2, 2, 2, 431, 427, 3, 2, 2, 2, 431, 428, 3, 2, 2, 2, 431, 429, 3, 2, 2, 2, 431, 430, 3, 2, 2, 2, 432, 63, 3, 2, 2, 2, 433, 438, 5, 68, 35, 2, 434, 435, 7, 35, 2, 2, 435, 437, 5, 68, 35, 2, 436, 434, 3, 2, 2, 2, 437, 440, 3, 2, 2, 2, 438, 436, 3, 2, 2, 2, 438, 439, 3, 2, 2, 2, 439, 65, 3, 2, 2, 2, 440, 438, 3, 2, 2, 2, 441, 446, 5, 64, 33, 2, 442, 443, 7, 34, 2, 2, 443, 445, 5, 64, 33, 2, 444, 442, 3, 2, 2, 2, 445, 448, 3, 2, 2, 2, 446, 444, 3, 2, 2, 2, 446, 447, 3, 2, 2, 2, 447, 67, 3, 2, 2, 2, 448, 446, 3, 2, 2, 2, 449, 450, 9, 6, 2, 2, 450, 69, 3, 2, 2, 2, 451, 452, 7, 62, 2, 2, 452, 71, 3, 2, 2, 2, 453, 454, 7, 63, 2, 2, 454, 73, 3, 2, 2, 2, 455, 493, 7, 45, 2, 2, 456, 493, 5, 76, 39, 2, 457, 493, 5, 104, 53, 2, 458, 493, 5, 112, 57, 2, 459, 460, 7, 37, 2, 2, 460, 465, 5, 76, 39, 2, 461, 462, 7, 34, 2, 2, 462, 464, 5, 76, 39, 2, 463, 461, 3, 2, 2, 2, 464, 467, 3, 2, 2, 2, 465, 463, 3, 2, 2, 2, 465, 466, 3, 2, 2, 2, 466, 468, 3, 2, 2, 2, 467, 465, 3, 2, 2, 2, 468, 469, 7, 38, 2, 2, 469, 493, 3, 2, 2, 2, 470, 471, 7, 37, 2, 2, 471, 476, 5, 104, 53, 2, 472, 473, 7, 34, 2, 2, 473, 475, 5, 104, 53, 2, 474, 472, 3, 2, 2, 2, 475, 478, 3, 2, 2, 2, 476, 474, 3, 2, 2, 2, 476, 477, 3, 2, 2, 2, 477, 479, 3, 2, 2, 2, 478, 476, 3, 2, 2, 2, 479, 480, 7, 38, 2, 2, 480, 493, 3, 2, 2, 2, 481, 482, 7, 37, 2, 2, 482, 487, 5, 112, 57, 2, 483, 484, 7, 34, 2, 2, 484, 486, 5, 112, 57, 2, 485, 483, 3, 2, 2, 2, 486, 489, 3, 2, 2, 2, 487, 485, 3, 2, 2, 2, 487, 488, 3, 2, 2, 2, 488, 490, 3, 2, 2, 2, 489, 487, 3, 2, 2, 2, 490, 491, 7, 38, 2, 2, 491, 493, 3, 2, 2, 2, 492, 455, 3, 2, 2, 2, 492, 456, 3, 2, 2, 2, 492, 457, 3, 2, 2, 2, 492, 458, 3, 2, 2, 2, 492, 459, 3, 2, 2, 2, 492, 470, 3, 2, 2, 2, 492, 481, 3, 2, 2, 2, 493, 75, 3, 2, 2, 2, 494, 497, 5, 108, 55, 2, 495, 497, 5, 110, 56, 2, 496, 494, 3, 2, 2, 2, 496, 495, 3, 2, 2, 2, 497, 77, 3, 2, 2, 2, 498, 499, 7, 13, 2, 2, 499, 500, 7, 28, 2, 2, 500, 79, 3, 2, 2, 2, 501, 502, 7, 11, 2, 2, 502, 507, 5, 82, 42, 2, 503, 504, 7, 34, 2, 2, 504, 506, 5, 82, 42, 2, 505, 503, 3, 2, 2, 2, 506, 509, 3, 2, 2, 2, 507, 505, 3, 2, 2, 2, 507, 508, 3, 2, 2, 2, 508, 81, 3, 2, 2, 2, 509, 507, 3, 2, 2, 2, 510, 512, 5, 20, 11, 2, 511, 513, 7, 59, 2, 2, 512, 511, 3, 2, 2, 2, 512, 513, 3, 2, 2, 2, 513, 516, 3, 2, 2, 2, 514, 515, 7, 60, 2, 2, 515, 517, 7, 61, 2, 2, 516, 514, 3, 2, 2, 2, 516, 517, 3, 2, 2, 2, 517, 83, 3, 2, 2, 2, 518, 519, 7, 14, 2, 2, 519, 520, 5, 66, 34, 2, 520, 85, 3, 2, 2, 2, 521, 522, 7, 19, 2, 2, 522, 523, 5, 66, 34, 2, 523, 87, 3, 2, 2, 2, 524, 525, 7, 15, 2, 2, 525, 526, 5, 66, 34, 2, 526, 89, 3, 2, 2, 2, 527, 532, 5, 68, 35, 2, 528, 529, 7, 35, 2, 2, 529, 531, 5, 68, 35, 2, 530, 528, 3, 2, 2, 2, 531, 534, 3, 2, 2, 2, 532, 530, 3, 2, 2, 2, 532, 533, 3, 2, 2, 2, 533, 91, 3, 2, 2, 2, 534, 532, 3, 2, 2, 2, 535, 536, 7, 16, 2, 2, 536, 541, 5, 94, 48, 2, 537, 538, 7, 34, 2, 2, 538, 540, 5, 94, 48, 2, 539, 537, 3, 2, 2, 2, 540, 543, 3, 2, 2, 2, 541, 539, 3, 2, 2, 2, 541, 542, 3, 2, 2, 2, 542, 93, 3, 2, 2, 2, 543, 541, 3, 2, 2, 2, 544, 545, 5, 64, 33, 2, 545, 546, 7, 44, 2, 2, 546, 547, 5, 90, 46, 2, 547, 95, 3, 2, 2, 2, 548, 549, 7, 3, 2, 2, 549, 550, 5, 66, 34, 2, 550, 552, 5, 112, 57, 2, 551, 553, 5, 100, 51, 2, 552, 551, 3, 2, 2, 2, 552, 553, 3, 2, 2, 2, 553, 97, 3, 2, 2, 2, 554, 555, 7, 4, 2, 2, 555, 556, 5, 66, 34, 2, 556, 557, 5, 112, 57, 2, 557, 99, 3, 2, 2, 2, 558, 563, 5, 102, 52, 2, 559, 560, 7, 34, 2, 2, 560, 562, 5, 102, 52, 2, 561, 559, 3, 2, 2, 2, 562, 565, 3, 2, 2, 2, 563, 561, 3, 2, 2, 2, 563, 564, 3, 2, 2, 2, 564, 101, 3, 2, 2, 2, 565, 563, 3, 2, 2, 2, 566, 567, 5, 68, 35, 2, 567, 568, 7, 33, 2, 2, 568, 569, 5, 74, 38, 2, 569, 103, 3, 2, 2, 2, 570, 571, 7, 51, 2, 2, 571, 105, 3, 2, 2, 2, 572, 575, 7, 29, 2, 2, 573, 575, 7, 28, 2, 2, 574, 572, 3, 2, 2, 2, 574, 573, 3, 2, 2, 2, 575, 107, 3, 2, 2, 2, 576, 577, 7, 29, 2, 2, 577, 109, 3, 2, 2, 2, 578, 579, 7, 28, 2, 2, 579, 111, 3, 2, 2, 2, 580, 581, 7, 27, 2, 2, 581, 113, 3, 2, 2, 2, 582, 583, 7, 52, 2, 2, 583, 115, 3, 2, 2, 2, 584, 585, 7, 6, 2, 2, 585, 586, 5, 118, 60, 2, 586, 117, 3, 2, 2, 2, 587, 588, 7, 37, 2, 2, 588, 589, 5, 4, 3, 2, 589, 590, 7, 38, 2, 2, 590, 119, 3, 2, 2, 2, 591, 592, 7, 17, 2, 2, 592, 596, 7, 49, 2, 2, 593, 594, 7, 17, 2, 2, 594, 596, 7, 50, 2, 2, 595, 591, 3, 2, 2, 2, 595, 593, 3, 2, 2, 2, 596, 121, 3, 2, 2, 2, 60, 133, 140, 155, 161, 169, 172, 177, 194, 203, 209, 218, 221, 228, 232, 240, 242, 249, 257, 259, 264, 271, 276, 280, 293, 296, 307, 310, 323, 331, 333, 350, 353, 357, 367, 375, 387, 391, 400, 410, 414, 423, 431, 438, 446, 465, 476, 487, 492, 496, 507, 512, 516, 532, 541, 552, 563, 574, 595] \ No newline at end of file diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.tokens b/packages/kbn-monaco/src/esql/antlr/esql_parser.tokens index c2dafff2f222c..b72e97b9a2961 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_parser.tokens +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.tokens @@ -1,74 +1,98 @@ -EVAL=1 -EXPLAIN=2 -FROM=3 -ROW=4 -STATS=5 -WHERE=6 -SORT=7 -LIMIT=8 -PROJECT=9 -LINE_COMMENT=10 -MULTILINE_COMMENT=11 -WS=12 -PIPE=13 -STRING=14 -INTEGER_LITERAL=15 -DECIMAL_LITERAL=16 -BY=17 -AND=18 -ASSIGN=19 -COMMA=20 -DOT=21 -LP=22 -OPENING_BRACKET=23 -CLOSING_BRACKET=24 -NOT=25 -NULL=26 -OR=27 -RP=28 -BOOLEAN_VALUE=29 -COMPARISON_OPERATOR=30 -PLUS=31 -MINUS=32 -ASTERISK=33 -SLASH=34 -PERCENT=35 -ORDERING=36 -NULLS_ORDERING=37 -NULLS_ORDERING_DIRECTION=38 -UNARY_FUNCTION=39 -UNQUOTED_IDENTIFIER=40 -QUOTED_IDENTIFIER=41 -EXPR_LINE_COMMENT=42 -EXPR_MULTILINE_COMMENT=43 -EXPR_WS=44 -SRC_UNQUOTED_IDENTIFIER=45 -SRC_QUOTED_IDENTIFIER=46 -SRC_LINE_COMMENT=47 -SRC_MULTILINE_COMMENT=48 -SRC_WS=49 -'eval'=1 -'explain'=2 -'from'=3 -'row'=4 -'stats'=5 -'where'=6 -'sort'=7 -'limit'=8 -'project'=9 -'by'=17 -'and'=18 -'.'=21 -'('=22 -'['=23 -']'=24 -'not'=25 -'null'=26 -'or'=27 -')'=28 -'+'=31 -'-'=32 -'*'=33 -'/'=34 -'%'=35 -'nulls'=37 +DISSECT=1 +GROK=2 +EVAL=3 +EXPLAIN=4 +FROM=5 +ROW=6 +STATS=7 +WHERE=8 +SORT=9 +MV_EXPAND=10 +LIMIT=11 +PROJECT=12 +DROP=13 +RENAME=14 +SHOW=15 +ENRICH=16 +KEEP=17 +LINE_COMMENT=18 +MULTILINE_COMMENT=19 +WS=20 +EXPLAIN_WS=21 +EXPLAIN_LINE_COMMENT=22 +EXPLAIN_MULTILINE_COMMENT=23 +PIPE=24 +STRING=25 +INTEGER_LITERAL=26 +DECIMAL_LITERAL=27 +BY=28 +DATE_LITERAL=29 +AND=30 +ASSIGN=31 +COMMA=32 +DOT=33 +LP=34 +OPENING_BRACKET=35 +CLOSING_BRACKET=36 +NOT=37 +LIKE=38 +RLIKE=39 +IN=40 +IS=41 +AS=42 +NULL=43 +OR=44 +RP=45 +UNDERSCORE=46 +INFO=47 +FUNCTIONS=48 +BOOLEAN_VALUE=49 +COMPARISON_OPERATOR=50 +PLUS=51 +MINUS=52 +ASTERISK=53 +SLASH=54 +PERCENT=55 +TEN=56 +ORDERING=57 +NULLS_ORDERING=58 +NULLS_ORDERING_DIRECTION=59 +MATH_FUNCTION=60 +UNARY_FUNCTION=61 +WHERE_FUNCTIONS=62 +UNQUOTED_IDENTIFIER=63 +QUOTED_IDENTIFIER=64 +EXPR_LINE_COMMENT=65 +EXPR_MULTILINE_COMMENT=66 +EXPR_WS=67 +METADATA=68 +SRC_UNQUOTED_IDENTIFIER=69 +SRC_QUOTED_IDENTIFIER=70 +SRC_LINE_COMMENT=71 +SRC_MULTILINE_COMMENT=72 +SRC_WS=73 +ON=74 +WITH=75 +ENR_UNQUOTED_IDENTIFIER=76 +ENR_QUOTED_IDENTIFIER=77 +ENR_LINE_COMMENT=78 +ENR_MULTILINE_COMMENT=79 +ENR_WS=80 +EXPLAIN_PIPE=81 +'by'=28 +'and'=30 +'.'=33 +'('=34 +']'=36 +'or'=44 +')'=45 +'_'=46 +'info'=47 +'functions'=48 +'+'=51 +'-'=52 +'*'=53 +'/'=54 +'%'=55 +'10'=56 +'nulls'=58 diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.ts b/packages/kbn-monaco/src/esql/antlr/esql_parser.ts index de825a0b3698e..494ffadd8aad9 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_parser.ts +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.ts @@ -24,123 +24,194 @@ import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; import * as Utils from "antlr4ts/misc/Utils"; -import { esql_parserListener } from "./esql_parser_listener"; +import { esql_parserListener } from "./esql_parserListener"; export class esql_parser extends Parser { - public static readonly EVAL = 1; - public static readonly EXPLAIN = 2; - public static readonly FROM = 3; - public static readonly ROW = 4; - public static readonly STATS = 5; - public static readonly WHERE = 6; - public static readonly SORT = 7; - public static readonly LIMIT = 8; - public static readonly PROJECT = 9; - public static readonly LINE_COMMENT = 10; - public static readonly MULTILINE_COMMENT = 11; - public static readonly WS = 12; - public static readonly PIPE = 13; - public static readonly STRING = 14; - public static readonly INTEGER_LITERAL = 15; - public static readonly DECIMAL_LITERAL = 16; - public static readonly BY = 17; - public static readonly AND = 18; - public static readonly ASSIGN = 19; - public static readonly COMMA = 20; - public static readonly DOT = 21; - public static readonly LP = 22; - public static readonly OPENING_BRACKET = 23; - public static readonly CLOSING_BRACKET = 24; - public static readonly NOT = 25; - public static readonly NULL = 26; - public static readonly OR = 27; - public static readonly RP = 28; - public static readonly BOOLEAN_VALUE = 29; - public static readonly COMPARISON_OPERATOR = 30; - public static readonly PLUS = 31; - public static readonly MINUS = 32; - public static readonly ASTERISK = 33; - public static readonly SLASH = 34; - public static readonly PERCENT = 35; - public static readonly ORDERING = 36; - public static readonly NULLS_ORDERING = 37; - public static readonly NULLS_ORDERING_DIRECTION = 38; - public static readonly UNARY_FUNCTION = 39; - public static readonly UNQUOTED_IDENTIFIER = 40; - public static readonly QUOTED_IDENTIFIER = 41; - public static readonly EXPR_LINE_COMMENT = 42; - public static readonly EXPR_MULTILINE_COMMENT = 43; - public static readonly EXPR_WS = 44; - public static readonly SRC_UNQUOTED_IDENTIFIER = 45; - public static readonly SRC_QUOTED_IDENTIFIER = 46; - public static readonly SRC_LINE_COMMENT = 47; - public static readonly SRC_MULTILINE_COMMENT = 48; - public static readonly SRC_WS = 49; + public static readonly DISSECT = 1; + public static readonly GROK = 2; + public static readonly EVAL = 3; + public static readonly EXPLAIN = 4; + public static readonly FROM = 5; + public static readonly ROW = 6; + public static readonly STATS = 7; + public static readonly WHERE = 8; + public static readonly SORT = 9; + public static readonly MV_EXPAND = 10; + public static readonly LIMIT = 11; + public static readonly PROJECT = 12; + public static readonly DROP = 13; + public static readonly RENAME = 14; + public static readonly SHOW = 15; + public static readonly ENRICH = 16; + public static readonly KEEP = 17; + public static readonly LINE_COMMENT = 18; + public static readonly MULTILINE_COMMENT = 19; + public static readonly WS = 20; + public static readonly EXPLAIN_WS = 21; + public static readonly EXPLAIN_LINE_COMMENT = 22; + public static readonly EXPLAIN_MULTILINE_COMMENT = 23; + public static readonly PIPE = 24; + public static readonly STRING = 25; + public static readonly INTEGER_LITERAL = 26; + public static readonly DECIMAL_LITERAL = 27; + public static readonly BY = 28; + public static readonly DATE_LITERAL = 29; + public static readonly AND = 30; + public static readonly ASSIGN = 31; + public static readonly COMMA = 32; + public static readonly DOT = 33; + public static readonly LP = 34; + public static readonly OPENING_BRACKET = 35; + public static readonly CLOSING_BRACKET = 36; + public static readonly NOT = 37; + public static readonly LIKE = 38; + public static readonly RLIKE = 39; + public static readonly IN = 40; + public static readonly IS = 41; + public static readonly AS = 42; + public static readonly NULL = 43; + public static readonly OR = 44; + public static readonly RP = 45; + public static readonly UNDERSCORE = 46; + public static readonly INFO = 47; + public static readonly FUNCTIONS = 48; + public static readonly BOOLEAN_VALUE = 49; + public static readonly COMPARISON_OPERATOR = 50; + public static readonly PLUS = 51; + public static readonly MINUS = 52; + public static readonly ASTERISK = 53; + public static readonly SLASH = 54; + public static readonly PERCENT = 55; + public static readonly TEN = 56; + public static readonly ORDERING = 57; + public static readonly NULLS_ORDERING = 58; + public static readonly NULLS_ORDERING_DIRECTION = 59; + public static readonly MATH_FUNCTION = 60; + public static readonly UNARY_FUNCTION = 61; + public static readonly WHERE_FUNCTIONS = 62; + public static readonly UNQUOTED_IDENTIFIER = 63; + public static readonly QUOTED_IDENTIFIER = 64; + public static readonly EXPR_LINE_COMMENT = 65; + public static readonly EXPR_MULTILINE_COMMENT = 66; + public static readonly EXPR_WS = 67; + public static readonly METADATA = 68; + public static readonly SRC_UNQUOTED_IDENTIFIER = 69; + public static readonly SRC_QUOTED_IDENTIFIER = 70; + public static readonly SRC_LINE_COMMENT = 71; + public static readonly SRC_MULTILINE_COMMENT = 72; + public static readonly SRC_WS = 73; + public static readonly ON = 74; + public static readonly WITH = 75; + public static readonly ENR_UNQUOTED_IDENTIFIER = 76; + public static readonly ENR_QUOTED_IDENTIFIER = 77; + public static readonly ENR_LINE_COMMENT = 78; + public static readonly ENR_MULTILINE_COMMENT = 79; + public static readonly ENR_WS = 80; + public static readonly EXPLAIN_PIPE = 81; public static readonly RULE_singleStatement = 0; public static readonly RULE_query = 1; public static readonly RULE_sourceCommand = 2; public static readonly RULE_processingCommand = 3; - public static readonly RULE_whereCommand = 4; - public static readonly RULE_booleanExpression = 5; - public static readonly RULE_valueExpression = 6; - public static readonly RULE_comparison = 7; - public static readonly RULE_mathFn = 8; - public static readonly RULE_operatorExpression = 9; - public static readonly RULE_primaryExpression = 10; - public static readonly RULE_rowCommand = 11; - public static readonly RULE_fields = 12; - public static readonly RULE_field = 13; - public static readonly RULE_userVariable = 14; - public static readonly RULE_fromCommand = 15; - public static readonly RULE_evalCommand = 16; - public static readonly RULE_statsCommand = 17; - public static readonly RULE_sourceIdentifier = 18; - public static readonly RULE_functionExpressionArgument = 19; - public static readonly RULE_qualifiedName = 20; - public static readonly RULE_qualifiedNames = 21; - public static readonly RULE_identifier = 22; - public static readonly RULE_functionIdentifier = 23; - public static readonly RULE_constant = 24; - public static readonly RULE_limitCommand = 25; - public static readonly RULE_sortCommand = 26; - public static readonly RULE_orderExpression = 27; - public static readonly RULE_projectCommand = 28; - public static readonly RULE_projectClause = 29; - public static readonly RULE_booleanValue = 30; - public static readonly RULE_number = 31; - public static readonly RULE_string = 32; - public static readonly RULE_comparisonOperator = 33; - public static readonly RULE_explainCommand = 34; - public static readonly RULE_subqueryExpression = 35; + public static readonly RULE_enrichCommand = 4; + public static readonly RULE_enrichWithClause = 5; + public static readonly RULE_mvExpandCommand = 6; + public static readonly RULE_whereCommand = 7; + public static readonly RULE_whereBooleanExpression = 8; + public static readonly RULE_booleanExpression = 9; + public static readonly RULE_regexBooleanExpression = 10; + public static readonly RULE_valueExpression = 11; + public static readonly RULE_comparison = 12; + public static readonly RULE_mathFn = 13; + public static readonly RULE_mathEvalFn = 14; + public static readonly RULE_dateExpression = 15; + public static readonly RULE_operatorExpression = 16; + public static readonly RULE_primaryExpression = 17; + public static readonly RULE_rowCommand = 18; + public static readonly RULE_fields = 19; + public static readonly RULE_field = 20; + public static readonly RULE_enrichFieldIdentifier = 21; + public static readonly RULE_userVariable = 22; + public static readonly RULE_fromCommand = 23; + public static readonly RULE_metadata = 24; + public static readonly RULE_evalCommand = 25; + public static readonly RULE_statsCommand = 26; + public static readonly RULE_sourceIdentifier = 27; + public static readonly RULE_enrichIdentifier = 28; + public static readonly RULE_functionExpressionArgument = 29; + public static readonly RULE_mathFunctionExpressionArgument = 30; + public static readonly RULE_qualifiedName = 31; + public static readonly RULE_qualifiedNames = 32; + public static readonly RULE_identifier = 33; + public static readonly RULE_mathFunctionIdentifier = 34; + public static readonly RULE_functionIdentifier = 35; + public static readonly RULE_constant = 36; + public static readonly RULE_numericValue = 37; + public static readonly RULE_limitCommand = 38; + public static readonly RULE_sortCommand = 39; + public static readonly RULE_orderExpression = 40; + public static readonly RULE_projectCommand = 41; + public static readonly RULE_keepCommand = 42; + public static readonly RULE_dropCommand = 43; + public static readonly RULE_renameVariable = 44; + public static readonly RULE_renameCommand = 45; + public static readonly RULE_renameClause = 46; + public static readonly RULE_dissectCommand = 47; + public static readonly RULE_grokCommand = 48; + public static readonly RULE_commandOptions = 49; + public static readonly RULE_commandOption = 50; + public static readonly RULE_booleanValue = 51; + public static readonly RULE_number = 52; + public static readonly RULE_decimalValue = 53; + public static readonly RULE_integerValue = 54; + public static readonly RULE_string = 55; + public static readonly RULE_comparisonOperator = 56; + public static readonly RULE_explainCommand = 57; + public static readonly RULE_subqueryExpression = 58; + public static readonly RULE_showCommand = 59; // tslint:disable:no-trailing-whitespace public static readonly ruleNames: string[] = [ - "singleStatement", "query", "sourceCommand", "processingCommand", "whereCommand", - "booleanExpression", "valueExpression", "comparison", "mathFn", "operatorExpression", - "primaryExpression", "rowCommand", "fields", "field", "userVariable", - "fromCommand", "evalCommand", "statsCommand", "sourceIdentifier", "functionExpressionArgument", - "qualifiedName", "qualifiedNames", "identifier", "functionIdentifier", - "constant", "limitCommand", "sortCommand", "orderExpression", "projectCommand", - "projectClause", "booleanValue", "number", "string", "comparisonOperator", - "explainCommand", "subqueryExpression", + "singleStatement", "query", "sourceCommand", "processingCommand", "enrichCommand", + "enrichWithClause", "mvExpandCommand", "whereCommand", "whereBooleanExpression", + "booleanExpression", "regexBooleanExpression", "valueExpression", "comparison", + "mathFn", "mathEvalFn", "dateExpression", "operatorExpression", "primaryExpression", + "rowCommand", "fields", "field", "enrichFieldIdentifier", "userVariable", + "fromCommand", "metadata", "evalCommand", "statsCommand", "sourceIdentifier", + "enrichIdentifier", "functionExpressionArgument", "mathFunctionExpressionArgument", + "qualifiedName", "qualifiedNames", "identifier", "mathFunctionIdentifier", + "functionIdentifier", "constant", "numericValue", "limitCommand", "sortCommand", + "orderExpression", "projectCommand", "keepCommand", "dropCommand", "renameVariable", + "renameCommand", "renameClause", "dissectCommand", "grokCommand", "commandOptions", + "commandOption", "booleanValue", "number", "decimalValue", "integerValue", + "string", "comparisonOperator", "explainCommand", "subqueryExpression", + "showCommand", ]; private static readonly _LITERAL_NAMES: Array = [ - undefined, "'eval'", "'explain'", "'from'", "'row'", "'stats'", "'where'", - "'sort'", "'limit'", "'project'", undefined, undefined, undefined, undefined, - undefined, undefined, undefined, "'by'", "'and'", undefined, undefined, - "'.'", "'('", "'['", "']'", "'not'", "'null'", "'or'", "')'", undefined, - undefined, "'+'", "'-'", "'*'", "'/'", "'%'", undefined, "'nulls'", + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + "'by'", undefined, "'and'", undefined, undefined, "'.'", "'('", undefined, + "']'", undefined, undefined, undefined, undefined, undefined, undefined, + undefined, "'or'", "')'", "'_'", "'info'", "'functions'", undefined, undefined, + "'+'", "'-'", "'*'", "'/'", "'%'", "'10'", undefined, "'nulls'", ]; private static readonly _SYMBOLIC_NAMES: Array = [ - undefined, "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", "WHERE", "SORT", - "LIMIT", "PROJECT", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", - "STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASSIGN", - "COMMA", "DOT", "LP", "OPENING_BRACKET", "CLOSING_BRACKET", "NOT", "NULL", - "OR", "RP", "BOOLEAN_VALUE", "COMPARISON_OPERATOR", "PLUS", "MINUS", "ASTERISK", - "SLASH", "PERCENT", "ORDERING", "NULLS_ORDERING", "NULLS_ORDERING_DIRECTION", - "UNARY_FUNCTION", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", - "EXPR_MULTILINE_COMMENT", "EXPR_WS", "SRC_UNQUOTED_IDENTIFIER", "SRC_QUOTED_IDENTIFIER", - "SRC_LINE_COMMENT", "SRC_MULTILINE_COMMENT", "SRC_WS", + undefined, "DISSECT", "GROK", "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", + "WHERE", "SORT", "MV_EXPAND", "LIMIT", "PROJECT", "DROP", "RENAME", "SHOW", + "ENRICH", "KEEP", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "EXPLAIN_WS", + "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", "PIPE", "STRING", + "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "DATE_LITERAL", "AND", "ASSIGN", + "COMMA", "DOT", "LP", "OPENING_BRACKET", "CLOSING_BRACKET", "NOT", "LIKE", + "RLIKE", "IN", "IS", "AS", "NULL", "OR", "RP", "UNDERSCORE", "INFO", "FUNCTIONS", + "BOOLEAN_VALUE", "COMPARISON_OPERATOR", "PLUS", "MINUS", "ASTERISK", "SLASH", + "PERCENT", "TEN", "ORDERING", "NULLS_ORDERING", "NULLS_ORDERING_DIRECTION", + "MATH_FUNCTION", "UNARY_FUNCTION", "WHERE_FUNCTIONS", "UNQUOTED_IDENTIFIER", + "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", + "METADATA", "SRC_UNQUOTED_IDENTIFIER", "SRC_QUOTED_IDENTIFIER", "SRC_LINE_COMMENT", + "SRC_MULTILINE_COMMENT", "SRC_WS", "ON", "WITH", "ENR_UNQUOTED_IDENTIFIER", + "ENR_QUOTED_IDENTIFIER", "ENR_LINE_COMMENT", "ENR_MULTILINE_COMMENT", + "ENR_WS", "EXPLAIN_PIPE", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(esql_parser._LITERAL_NAMES, esql_parser._SYMBOLIC_NAMES, []); @@ -171,9 +242,9 @@ export class esql_parser extends Parser { try { this.enterOuterAlt(_localctx, 1); { - this.state = 72; + this.state = 120; this.query(0); - this.state = 73; + this.state = 121; this.match(esql_parser.EOF); } } @@ -215,11 +286,11 @@ export class esql_parser extends Parser { this._ctx = _localctx; _prevctx = _localctx; - this.state = 76; + this.state = 124; this.sourceCommand(); } this._ctx._stop = this._input.tryLT(-1); - this.state = 83; + this.state = 131; this._errHandler.sync(this); _alt = this.interpreter.adaptivePredict(this._input, 0, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -232,18 +303,18 @@ export class esql_parser extends Parser { { _localctx = new CompositeQueryContext(new QueryContext(_parentctx, _parentState)); this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_query); - this.state = 78; + this.state = 126; if (!(this.precpred(this._ctx, 1))) { throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); } - this.state = 79; + this.state = 127; this.match(esql_parser.PIPE); - this.state = 80; + this.state = 128; this.processingCommand(); } } } - this.state = 85; + this.state = 133; this._errHandler.sync(this); _alt = this.interpreter.adaptivePredict(this._input, 0, this._ctx); } @@ -268,30 +339,37 @@ export class esql_parser extends Parser { let _localctx: SourceCommandContext = new SourceCommandContext(this._ctx, this.state); this.enterRule(_localctx, 4, esql_parser.RULE_sourceCommand); try { - this.state = 89; + this.state = 138; this._errHandler.sync(this); switch (this._input.LA(1)) { case esql_parser.EXPLAIN: this.enterOuterAlt(_localctx, 1); { - this.state = 86; + this.state = 134; this.explainCommand(); } break; case esql_parser.FROM: this.enterOuterAlt(_localctx, 2); { - this.state = 87; + this.state = 135; this.fromCommand(); } break; case esql_parser.ROW: this.enterOuterAlt(_localctx, 3); { - this.state = 88; + this.state = 136; this.rowCommand(); } break; + case esql_parser.SHOW: + this.enterOuterAlt(_localctx, 4); + { + this.state = 137; + this.showCommand(); + } + break; default: throw new NoViableAltException(this); } @@ -315,51 +393,100 @@ export class esql_parser extends Parser { let _localctx: ProcessingCommandContext = new ProcessingCommandContext(this._ctx, this.state); this.enterRule(_localctx, 6, esql_parser.RULE_processingCommand); try { - this.state = 97; + this.state = 153; this._errHandler.sync(this); switch (this._input.LA(1)) { case esql_parser.EVAL: this.enterOuterAlt(_localctx, 1); { - this.state = 91; + this.state = 140; this.evalCommand(); } break; case esql_parser.LIMIT: this.enterOuterAlt(_localctx, 2); { - this.state = 92; + this.state = 141; this.limitCommand(); } break; case esql_parser.PROJECT: this.enterOuterAlt(_localctx, 3); { - this.state = 93; + this.state = 142; this.projectCommand(); } break; - case esql_parser.SORT: + case esql_parser.KEEP: this.enterOuterAlt(_localctx, 4); { - this.state = 94; + this.state = 143; + this.keepCommand(); + } + break; + case esql_parser.RENAME: + this.enterOuterAlt(_localctx, 5); + { + this.state = 144; + this.renameCommand(); + } + break; + case esql_parser.DROP: + this.enterOuterAlt(_localctx, 6); + { + this.state = 145; + this.dropCommand(); + } + break; + case esql_parser.DISSECT: + this.enterOuterAlt(_localctx, 7); + { + this.state = 146; + this.dissectCommand(); + } + break; + case esql_parser.GROK: + this.enterOuterAlt(_localctx, 8); + { + this.state = 147; + this.grokCommand(); + } + break; + case esql_parser.SORT: + this.enterOuterAlt(_localctx, 9); + { + this.state = 148; this.sortCommand(); } break; case esql_parser.STATS: - this.enterOuterAlt(_localctx, 5); + this.enterOuterAlt(_localctx, 10); { - this.state = 95; + this.state = 149; this.statsCommand(); } break; case esql_parser.WHERE: - this.enterOuterAlt(_localctx, 6); + this.enterOuterAlt(_localctx, 11); { - this.state = 96; + this.state = 150; this.whereCommand(); } break; + case esql_parser.MV_EXPAND: + this.enterOuterAlt(_localctx, 12); + { + this.state = 151; + this.mvExpandCommand(); + } + break; + case esql_parser.ENRICH: + this.enterOuterAlt(_localctx, 13); + { + this.state = 152; + this.enrichCommand(); + } + break; default: throw new NoViableAltException(this); } @@ -379,132 +506,58 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public whereCommand(): WhereCommandContext { - let _localctx: WhereCommandContext = new WhereCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 8, esql_parser.RULE_whereCommand); - try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 99; - this.match(esql_parser.WHERE); - this.state = 100; - this.booleanExpression(0); - } - } - catch (re) { - if (re instanceof RecognitionException) { - _localctx.exception = re; - this._errHandler.reportError(this, re); - this._errHandler.recover(this, re); - } else { - throw re; - } - } - finally { - this.exitRule(); - } - return _localctx; - } - - public booleanExpression(): BooleanExpressionContext; - public booleanExpression(_p: number): BooleanExpressionContext; - // @RuleVersion(0) - public booleanExpression(_p?: number): BooleanExpressionContext { - if (_p === undefined) { - _p = 0; - } - - let _parentctx: ParserRuleContext = this._ctx; - let _parentState: number = this.state; - let _localctx: BooleanExpressionContext = new BooleanExpressionContext(this._ctx, _parentState); - let _prevctx: BooleanExpressionContext = _localctx; - let _startState: number = 10; - this.enterRecursionRule(_localctx, 10, esql_parser.RULE_booleanExpression, _p); + public enrichCommand(): EnrichCommandContext { + let _localctx: EnrichCommandContext = new EnrichCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 8, esql_parser.RULE_enrichCommand); try { let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 106; + this.state = 155; + this.match(esql_parser.ENRICH); + this.state = 156; + _localctx._policyName = this.enrichIdentifier(); + this.state = 159; this._errHandler.sync(this); - switch (this._input.LA(1)) { - case esql_parser.NOT: - { - this.state = 103; - this.match(esql_parser.NOT); - this.state = 104; - this.booleanExpression(4); - } - break; - case esql_parser.STRING: - case esql_parser.INTEGER_LITERAL: - case esql_parser.DECIMAL_LITERAL: - case esql_parser.LP: - case esql_parser.NULL: - case esql_parser.BOOLEAN_VALUE: - case esql_parser.PLUS: - case esql_parser.MINUS: - case esql_parser.UNARY_FUNCTION: - case esql_parser.UNQUOTED_IDENTIFIER: - case esql_parser.QUOTED_IDENTIFIER: + switch ( this.interpreter.adaptivePredict(this._input, 3, this._ctx) ) { + case 1: { - this.state = 105; - this.valueExpression(); + this.state = 157; + this.match(esql_parser.ON); + this.state = 158; + _localctx._matchField = this.enrichFieldIdentifier(); } break; - default: - throw new NoViableAltException(this); } - this._ctx._stop = this._input.tryLT(-1); - this.state = 116; + this.state = 170; this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 5, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { - if (this._parseListeners != null) { - this.triggerExitRuleEvent(); - } - _prevctx = _localctx; - { - this.state = 114; - this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 4, this._ctx) ) { - case 1: + switch ( this.interpreter.adaptivePredict(this._input, 5, this._ctx) ) { + case 1: + { + this.state = 161; + this.match(esql_parser.WITH); + this.state = 162; + this.enrichWithClause(); + this.state = 167; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 4, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { { - _localctx = new BooleanExpressionContext(_parentctx, _parentState); - _localctx._left = _prevctx; - this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 108; - if (!(this.precpred(this._ctx, 2))) { - throw new FailedPredicateException(this, "this.precpred(this._ctx, 2)"); - } - this.state = 109; - _localctx._operator = this.match(esql_parser.AND); - this.state = 110; - _localctx._right = this.booleanExpression(3); - } - break; - - case 2: { - _localctx = new BooleanExpressionContext(_parentctx, _parentState); - _localctx._left = _prevctx; - this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 111; - if (!(this.precpred(this._ctx, 1))) { - throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); + this.state = 163; + this.match(esql_parser.COMMA); + this.state = 164; + this.enrichWithClause(); } - this.state = 112; - _localctx._operator = this.match(esql_parser.OR); - this.state = 113; - _localctx._right = this.booleanExpression(2); } - break; - } } + this.state = 169; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 4, this._ctx); } - this.state = 118; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 5, this._ctx); + } + break; } } } @@ -518,34 +571,32 @@ export class esql_parser extends Parser { } } finally { - this.unrollRecursionContexts(_parentctx); + this.exitRule(); } return _localctx; } // @RuleVersion(0) - public valueExpression(): ValueExpressionContext { - let _localctx: ValueExpressionContext = new ValueExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 12, esql_parser.RULE_valueExpression); + public enrichWithClause(): EnrichWithClauseContext { + let _localctx: EnrichWithClauseContext = new EnrichWithClauseContext(this._ctx, this.state); + this.enterRule(_localctx, 10, esql_parser.RULE_enrichWithClause); try { - this.state = 121; + this.enterOuterAlt(_localctx, 1); + { + this.state = 175; this._errHandler.sync(this); switch ( this.interpreter.adaptivePredict(this._input, 6, this._ctx) ) { case 1: - this.enterOuterAlt(_localctx, 1); - { - this.state = 119; - this.operatorExpression(0); - } - break; - - case 2: - this.enterOuterAlt(_localctx, 2); { - this.state = 120; - this.comparison(); + this.state = 172; + _localctx._newName = this.enrichFieldIdentifier(); + this.state = 173; + this.match(esql_parser.ASSIGN); } break; } + this.state = 177; + _localctx._enrichField = this.enrichFieldIdentifier(); + } } catch (re) { if (re instanceof RecognitionException) { @@ -562,18 +613,16 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public comparison(): ComparisonContext { - let _localctx: ComparisonContext = new ComparisonContext(this._ctx, this.state); - this.enterRule(_localctx, 14, esql_parser.RULE_comparison); + public mvExpandCommand(): MvExpandCommandContext { + let _localctx: MvExpandCommandContext = new MvExpandCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 12, esql_parser.RULE_mvExpandCommand); try { this.enterOuterAlt(_localctx, 1); { - this.state = 123; - _localctx._left = this.operatorExpression(0); - this.state = 124; - this.comparisonOperator(); - this.state = 125; - _localctx._right = this.operatorExpression(0); + this.state = 179; + this.match(esql_parser.MV_EXPAND); + this.state = 180; + this.qualifiedNames(); } } catch (re) { @@ -591,45 +640,16 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public mathFn(): MathFnContext { - let _localctx: MathFnContext = new MathFnContext(this._ctx, this.state); - this.enterRule(_localctx, 16, esql_parser.RULE_mathFn); - let _la: number; + public whereCommand(): WhereCommandContext { + let _localctx: WhereCommandContext = new WhereCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 14, esql_parser.RULE_whereCommand); try { this.enterOuterAlt(_localctx, 1); { - this.state = 127; - this.functionIdentifier(); - this.state = 128; - this.match(esql_parser.LP); - this.state = 137; - this._errHandler.sync(this); - _la = this._input.LA(1); - if (((((_la - 14)) & ~0x1F) === 0 && ((1 << (_la - 14)) & ((1 << (esql_parser.STRING - 14)) | (1 << (esql_parser.UNQUOTED_IDENTIFIER - 14)) | (1 << (esql_parser.QUOTED_IDENTIFIER - 14)))) !== 0)) { - { - this.state = 129; - this.functionExpressionArgument(); - this.state = 134; - this._errHandler.sync(this); - _la = this._input.LA(1); - while (_la === esql_parser.COMMA) { - { - { - this.state = 130; - this.match(esql_parser.COMMA); - this.state = 131; - this.functionExpressionArgument(); - } - } - this.state = 136; - this._errHandler.sync(this); - _la = this._input.LA(1); - } - } - } - - this.state = 139; - this.match(esql_parser.RP); + this.state = 182; + this.match(esql_parser.WHERE); + this.state = 183; + this.whereBooleanExpression(0); } } catch (re) { @@ -647,143 +667,211 @@ export class esql_parser extends Parser { return _localctx; } - public operatorExpression(): OperatorExpressionContext; - public operatorExpression(_p: number): OperatorExpressionContext; + public whereBooleanExpression(): WhereBooleanExpressionContext; + public whereBooleanExpression(_p: number): WhereBooleanExpressionContext; // @RuleVersion(0) - public operatorExpression(_p?: number): OperatorExpressionContext { + public whereBooleanExpression(_p?: number): WhereBooleanExpressionContext { if (_p === undefined) { _p = 0; } let _parentctx: ParserRuleContext = this._ctx; let _parentState: number = this.state; - let _localctx: OperatorExpressionContext = new OperatorExpressionContext(this._ctx, _parentState); - let _prevctx: OperatorExpressionContext = _localctx; - let _startState: number = 18; - this.enterRecursionRule(_localctx, 18, esql_parser.RULE_operatorExpression, _p); + let _localctx: WhereBooleanExpressionContext = new WhereBooleanExpressionContext(this._ctx, _parentState); + let _prevctx: WhereBooleanExpressionContext = _localctx; + let _startState: number = 16; + this.enterRecursionRule(_localctx, 16, esql_parser.RULE_whereBooleanExpression, _p); let _la: number; try { let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 146; + this.state = 230; this._errHandler.sync(this); - switch (this._input.LA(1)) { - case esql_parser.STRING: - case esql_parser.INTEGER_LITERAL: - case esql_parser.DECIMAL_LITERAL: - case esql_parser.LP: - case esql_parser.NULL: - case esql_parser.BOOLEAN_VALUE: - case esql_parser.UNQUOTED_IDENTIFIER: - case esql_parser.QUOTED_IDENTIFIER: + switch ( this.interpreter.adaptivePredict(this._input, 13, this._ctx) ) { + case 1: { - this.state = 142; - this.primaryExpression(); + this.state = 186; + this.match(esql_parser.NOT); + this.state = 187; + this.whereBooleanExpression(8); } break; - case esql_parser.UNARY_FUNCTION: + + case 2: { - this.state = 143; - this.mathFn(); + this.state = 188; + this.valueExpression(); } break; - case esql_parser.PLUS: - case esql_parser.MINUS: + + case 3: { - this.state = 144; - _localctx._operator = this._input.LT(1); + this.state = 189; + this.regexBooleanExpression(); + } + break; + + case 4: + { + this.state = 190; + this.valueExpression(); + this.state = 192; + this._errHandler.sync(this); _la = this._input.LA(1); - if (!(_la === esql_parser.PLUS || _la === esql_parser.MINUS)) { - _localctx._operator = this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; + if (_la === esql_parser.NOT) { + { + this.state = 191; + this.match(esql_parser.NOT); } - - this._errHandler.reportMatch(this); - this.consume(); - } - this.state = 145; - this.operatorExpression(3); } - break; - default: - throw new NoViableAltException(this); - } - this._ctx._stop = this._input.tryLT(-1); - this.state = 156; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 11, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { - if (this._parseListeners != null) { + + this.state = 194; + this.match(esql_parser.IN); + this.state = 195; + this.match(esql_parser.LP); + this.state = 196; + this.valueExpression(); + this.state = 201; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 197; + this.match(esql_parser.COMMA); + this.state = 198; + this.valueExpression(); + } + } + this.state = 203; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 204; + this.match(esql_parser.RP); + } + break; + + case 5: + { + this.state = 207; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === esql_parser.NOT) { + { + this.state = 206; + this.match(esql_parser.NOT); + } + } + + this.state = 209; + this.match(esql_parser.WHERE_FUNCTIONS); + this.state = 210; + this.match(esql_parser.LP); + this.state = 211; + this.qualifiedName(); + this.state = 219; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 11, this._ctx) ) { + case 1: + { + this.state = 216; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 212; + this.match(esql_parser.COMMA); + this.state = 213; + this.functionExpressionArgument(); + } + } + this.state = 218; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + break; + } + this.state = 221; + this.match(esql_parser.RP); + } + break; + + case 6: + { + this.state = 223; + this.valueExpression(); + this.state = 224; + this.match(esql_parser.IS); + this.state = 226; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === esql_parser.NOT) { + { + this.state = 225; + this.match(esql_parser.NOT); + } + } + + this.state = 228; + this.match(esql_parser.NULL); + } + break; + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 240; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 15, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { this.triggerExitRuleEvent(); } _prevctx = _localctx; { - this.state = 154; + this.state = 238; this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 10, this._ctx) ) { + switch ( this.interpreter.adaptivePredict(this._input, 14, this._ctx) ) { case 1: { - _localctx = new OperatorExpressionContext(_parentctx, _parentState); + _localctx = new WhereBooleanExpressionContext(_parentctx, _parentState); _localctx._left = _prevctx; - this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 148; - if (!(this.precpred(this._ctx, 2))) { - throw new FailedPredicateException(this, "this.precpred(this._ctx, 2)"); - } - this.state = 149; - _localctx._operator = this._input.LT(1); - _la = this._input.LA(1); - if (!(((((_la - 33)) & ~0x1F) === 0 && ((1 << (_la - 33)) & ((1 << (esql_parser.ASTERISK - 33)) | (1 << (esql_parser.SLASH - 33)) | (1 << (esql_parser.PERCENT - 33)))) !== 0))) { - _localctx._operator = this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_whereBooleanExpression); + this.state = 232; + if (!(this.precpred(this._ctx, 5))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 5)"); } - this.state = 150; - _localctx._right = this.operatorExpression(3); + this.state = 233; + _localctx._operator = this.match(esql_parser.AND); + this.state = 234; + _localctx._right = this.whereBooleanExpression(6); } break; case 2: { - _localctx = new OperatorExpressionContext(_parentctx, _parentState); + _localctx = new WhereBooleanExpressionContext(_parentctx, _parentState); _localctx._left = _prevctx; - this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 151; - if (!(this.precpred(this._ctx, 1))) { - throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); - } - this.state = 152; - _localctx._operator = this._input.LT(1); - _la = this._input.LA(1); - if (!(_la === esql_parser.PLUS || _la === esql_parser.MINUS)) { - _localctx._operator = this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_whereBooleanExpression); + this.state = 235; + if (!(this.precpred(this._ctx, 4))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 4)"); } - this.state = 153; - _localctx._right = this.operatorExpression(2); + this.state = 236; + _localctx._operator = this.match(esql_parser.OR); + this.state = 237; + _localctx._right = this.whereBooleanExpression(5); } break; } } } - this.state = 158; + this.state = 242; this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 11, this._ctx); + _alt = this.interpreter.adaptivePredict(this._input, 15, this._ctx); } } } @@ -801,80 +889,110 @@ export class esql_parser extends Parser { } return _localctx; } + + public booleanExpression(): BooleanExpressionContext; + public booleanExpression(_p: number): BooleanExpressionContext; // @RuleVersion(0) - public primaryExpression(): PrimaryExpressionContext { - let _localctx: PrimaryExpressionContext = new PrimaryExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 20, esql_parser.RULE_primaryExpression); - let _la: number; + public booleanExpression(_p?: number): BooleanExpressionContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: BooleanExpressionContext = new BooleanExpressionContext(this._ctx, _parentState); + let _prevctx: BooleanExpressionContext = _localctx; + let _startState: number = 18; + this.enterRecursionRule(_localctx, 18, esql_parser.RULE_booleanExpression, _p); try { - this.state = 179; + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 247; this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 14, this._ctx) ) { - case 1: - this.enterOuterAlt(_localctx, 1); - { - this.state = 159; - this.constant(); - } - break; - - case 2: - this.enterOuterAlt(_localctx, 2); + switch (this._input.LA(1)) { + case esql_parser.NOT: { - this.state = 160; - this.qualifiedName(); + this.state = 244; + this.match(esql_parser.NOT); + this.state = 245; + this.booleanExpression(4); } break; - - case 3: - this.enterOuterAlt(_localctx, 3); + case esql_parser.STRING: + case esql_parser.INTEGER_LITERAL: + case esql_parser.DECIMAL_LITERAL: + case esql_parser.LP: + case esql_parser.OPENING_BRACKET: + case esql_parser.NULL: + case esql_parser.BOOLEAN_VALUE: + case esql_parser.PLUS: + case esql_parser.MINUS: + case esql_parser.ASTERISK: + case esql_parser.MATH_FUNCTION: + case esql_parser.UNARY_FUNCTION: + case esql_parser.UNQUOTED_IDENTIFIER: + case esql_parser.QUOTED_IDENTIFIER: { - this.state = 161; - this.match(esql_parser.LP); - this.state = 162; - this.booleanExpression(0); - this.state = 163; - this.match(esql_parser.RP); + this.state = 246; + this.valueExpression(); } break; - - case 4: - this.enterOuterAlt(_localctx, 4); - { - this.state = 165; - this.identifier(); - this.state = 166; - this.match(esql_parser.LP); - this.state = 175; - this._errHandler.sync(this); - _la = this._input.LA(1); - if (((((_la - 14)) & ~0x1F) === 0 && ((1 << (_la - 14)) & ((1 << (esql_parser.STRING - 14)) | (1 << (esql_parser.INTEGER_LITERAL - 14)) | (1 << (esql_parser.DECIMAL_LITERAL - 14)) | (1 << (esql_parser.LP - 14)) | (1 << (esql_parser.NOT - 14)) | (1 << (esql_parser.NULL - 14)) | (1 << (esql_parser.BOOLEAN_VALUE - 14)) | (1 << (esql_parser.PLUS - 14)) | (1 << (esql_parser.MINUS - 14)) | (1 << (esql_parser.UNARY_FUNCTION - 14)) | (1 << (esql_parser.UNQUOTED_IDENTIFIER - 14)) | (1 << (esql_parser.QUOTED_IDENTIFIER - 14)))) !== 0)) { + default: + throw new NoViableAltException(this); + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 257; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 18, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; { - this.state = 167; - this.booleanExpression(0); - this.state = 172; + this.state = 255; this._errHandler.sync(this); - _la = this._input.LA(1); - while (_la === esql_parser.COMMA) { + switch ( this.interpreter.adaptivePredict(this._input, 17, this._ctx) ) { + case 1: { + _localctx = new BooleanExpressionContext(_parentctx, _parentState); + _localctx._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_booleanExpression); + this.state = 249; + if (!(this.precpred(this._ctx, 2))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 2)"); + } + this.state = 250; + _localctx._operator = this.match(esql_parser.AND); + this.state = 251; + _localctx._right = this.booleanExpression(3); + } + break; + + case 2: { - this.state = 168; - this.match(esql_parser.COMMA); - this.state = 169; - this.booleanExpression(0); + _localctx = new BooleanExpressionContext(_parentctx, _parentState); + _localctx._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_booleanExpression); + this.state = 252; + if (!(this.precpred(this._ctx, 1))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); } + this.state = 253; + _localctx._operator = this.match(esql_parser.OR); + this.state = 254; + _localctx._right = this.booleanExpression(2); } - this.state = 174; - this._errHandler.sync(this); - _la = this._input.LA(1); + break; } } } - - this.state = 177; - this.match(esql_parser.RP); - } - break; + this.state = 259; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 18, this._ctx); + } } } catch (re) { @@ -887,21 +1005,62 @@ export class esql_parser extends Parser { } } finally { - this.exitRule(); + this.unrollRecursionContexts(_parentctx); } return _localctx; } // @RuleVersion(0) - public rowCommand(): RowCommandContext { - let _localctx: RowCommandContext = new RowCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 22, esql_parser.RULE_rowCommand); + public regexBooleanExpression(): RegexBooleanExpressionContext { + let _localctx: RegexBooleanExpressionContext = new RegexBooleanExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 20, esql_parser.RULE_regexBooleanExpression); + let _la: number; try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 181; - this.match(esql_parser.ROW); - this.state = 182; - this.fields(); + this.state = 274; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 21, this._ctx) ) { + case 1: + this.enterOuterAlt(_localctx, 1); + { + this.state = 260; + this.valueExpression(); + this.state = 262; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === esql_parser.NOT) { + { + this.state = 261; + this.match(esql_parser.NOT); + } + } + + this.state = 264; + _localctx._kind = this.match(esql_parser.LIKE); + this.state = 265; + _localctx._pattern = this.string(); + } + break; + + case 2: + this.enterOuterAlt(_localctx, 2); + { + this.state = 267; + this.valueExpression(); + this.state = 269; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === esql_parser.NOT) { + { + this.state = 268; + this.match(esql_parser.NOT); + } + } + + this.state = 271; + _localctx._kind = this.match(esql_parser.RLIKE); + this.state = 272; + _localctx._pattern = this.string(); + } + break; } } catch (re) { @@ -919,74 +1078,26 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public fields(): FieldsContext { - let _localctx: FieldsContext = new FieldsContext(this._ctx, this.state); - this.enterRule(_localctx, 24, esql_parser.RULE_fields); - try { - let _alt: number; - this.enterOuterAlt(_localctx, 1); - { - this.state = 184; - this.field(); - this.state = 189; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 15, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { - { - { - this.state = 185; - this.match(esql_parser.COMMA); - this.state = 186; - this.field(); - } - } - } - this.state = 191; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 15, this._ctx); - } - } - } - catch (re) { - if (re instanceof RecognitionException) { - _localctx.exception = re; - this._errHandler.reportError(this, re); - this._errHandler.recover(this, re); - } else { - throw re; - } - } - finally { - this.exitRule(); - } - return _localctx; - } - // @RuleVersion(0) - public field(): FieldContext { - let _localctx: FieldContext = new FieldContext(this._ctx, this.state); - this.enterRule(_localctx, 26, esql_parser.RULE_field); + public valueExpression(): ValueExpressionContext { + let _localctx: ValueExpressionContext = new ValueExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 22, esql_parser.RULE_valueExpression); try { - this.state = 197; + this.state = 278; this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 16, this._ctx) ) { + switch ( this.interpreter.adaptivePredict(this._input, 22, this._ctx) ) { case 1: this.enterOuterAlt(_localctx, 1); { - this.state = 192; - this.booleanExpression(0); + this.state = 276; + this.operatorExpression(0); } break; case 2: this.enterOuterAlt(_localctx, 2); { - this.state = 193; - this.userVariable(); - this.state = 194; - this.match(esql_parser.ASSIGN); - this.state = 195; - this.booleanExpression(0); + this.state = 277; + this.comparison(); } break; } @@ -1006,14 +1117,18 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public userVariable(): UserVariableContext { - let _localctx: UserVariableContext = new UserVariableContext(this._ctx, this.state); - this.enterRule(_localctx, 28, esql_parser.RULE_userVariable); + public comparison(): ComparisonContext { + let _localctx: ComparisonContext = new ComparisonContext(this._ctx, this.state); + this.enterRule(_localctx, 24, esql_parser.RULE_comparison); try { this.enterOuterAlt(_localctx, 1); { - this.state = 199; - this.identifier(); + this.state = 280; + _localctx._left = this.operatorExpression(0); + this.state = 281; + this.comparisonOperator(); + this.state = 282; + _localctx._right = this.operatorExpression(0); } } catch (re) { @@ -1031,35 +1146,45 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public fromCommand(): FromCommandContext { - let _localctx: FromCommandContext = new FromCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 30, esql_parser.RULE_fromCommand); + public mathFn(): MathFnContext { + let _localctx: MathFnContext = new MathFnContext(this._ctx, this.state); + this.enterRule(_localctx, 26, esql_parser.RULE_mathFn); + let _la: number; try { - let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 201; - this.match(esql_parser.FROM); - this.state = 202; - this.sourceIdentifier(); - this.state = 207; + this.state = 284; + this.functionIdentifier(); + this.state = 285; + this.match(esql_parser.LP); + this.state = 294; this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 17, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { + _la = this._input.LA(1); + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << esql_parser.STRING) | (1 << esql_parser.INTEGER_LITERAL) | (1 << esql_parser.DECIMAL_LITERAL))) !== 0) || ((((_la - 53)) & ~0x1F) === 0 && ((1 << (_la - 53)) & ((1 << (esql_parser.ASTERISK - 53)) | (1 << (esql_parser.UNQUOTED_IDENTIFIER - 53)) | (1 << (esql_parser.QUOTED_IDENTIFIER - 53)))) !== 0)) { + { + this.state = 286; + this.functionExpressionArgument(); + this.state = 291; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { { { - this.state = 203; + this.state = 287; this.match(esql_parser.COMMA); - this.state = 204; - this.sourceIdentifier(); + this.state = 288; + this.functionExpressionArgument(); } } + this.state = 293; + this._errHandler.sync(this); + _la = this._input.LA(1); + } } - this.state = 209; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 17, this._ctx); } + + this.state = 296; + this.match(esql_parser.RP); } } catch (re) { @@ -1077,16 +1202,45 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public evalCommand(): EvalCommandContext { - let _localctx: EvalCommandContext = new EvalCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 32, esql_parser.RULE_evalCommand); + public mathEvalFn(): MathEvalFnContext { + let _localctx: MathEvalFnContext = new MathEvalFnContext(this._ctx, this.state); + this.enterRule(_localctx, 28, esql_parser.RULE_mathEvalFn); + let _la: number; try { this.enterOuterAlt(_localctx, 1); { - this.state = 210; - this.match(esql_parser.EVAL); - this.state = 211; - this.fields(); + this.state = 298; + this.mathFunctionIdentifier(); + this.state = 299; + this.match(esql_parser.LP); + this.state = 308; + this._errHandler.sync(this); + _la = this._input.LA(1); + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << esql_parser.STRING) | (1 << esql_parser.INTEGER_LITERAL) | (1 << esql_parser.DECIMAL_LITERAL))) !== 0) || ((((_la - 34)) & ~0x1F) === 0 && ((1 << (_la - 34)) & ((1 << (esql_parser.LP - 34)) | (1 << (esql_parser.OPENING_BRACKET - 34)) | (1 << (esql_parser.NULL - 34)) | (1 << (esql_parser.BOOLEAN_VALUE - 34)) | (1 << (esql_parser.PLUS - 34)) | (1 << (esql_parser.MINUS - 34)) | (1 << (esql_parser.ASTERISK - 34)) | (1 << (esql_parser.MATH_FUNCTION - 34)) | (1 << (esql_parser.UNARY_FUNCTION - 34)) | (1 << (esql_parser.UNQUOTED_IDENTIFIER - 34)) | (1 << (esql_parser.QUOTED_IDENTIFIER - 34)))) !== 0)) { + { + this.state = 300; + this.mathFunctionExpressionArgument(); + this.state = 305; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 301; + this.match(esql_parser.COMMA); + this.state = 302; + this.mathFunctionExpressionArgument(); + } + } + this.state = 307; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + + this.state = 310; + this.match(esql_parser.RP); } } catch (re) { @@ -1104,28 +1258,16 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public statsCommand(): StatsCommandContext { - let _localctx: StatsCommandContext = new StatsCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 34, esql_parser.RULE_statsCommand); + public dateExpression(): DateExpressionContext { + let _localctx: DateExpressionContext = new DateExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 30, esql_parser.RULE_dateExpression); try { this.enterOuterAlt(_localctx, 1); { - this.state = 213; - this.match(esql_parser.STATS); - this.state = 214; - this.fields(); - this.state = 217; - this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 18, this._ctx) ) { - case 1: - { - this.state = 215; - this.match(esql_parser.BY); - this.state = 216; - this.qualifiedNames(); - } - break; - } + this.state = 312; + _localctx._quantifier = this.number(); + this.state = 313; + this.match(esql_parser.DATE_LITERAL); } } catch (re) { @@ -1142,25 +1284,152 @@ export class esql_parser extends Parser { } return _localctx; } + + public operatorExpression(): OperatorExpressionContext; + public operatorExpression(_p: number): OperatorExpressionContext; // @RuleVersion(0) - public sourceIdentifier(): SourceIdentifierContext { - let _localctx: SourceIdentifierContext = new SourceIdentifierContext(this._ctx, this.state); - this.enterRule(_localctx, 36, esql_parser.RULE_sourceIdentifier); + public operatorExpression(_p?: number): OperatorExpressionContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: OperatorExpressionContext = new OperatorExpressionContext(this._ctx, _parentState); + let _prevctx: OperatorExpressionContext = _localctx; + let _startState: number = 32; + this.enterRecursionRule(_localctx, 32, esql_parser.RULE_operatorExpression, _p); let _la: number; try { + let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 219; - _la = this._input.LA(1); - if (!(_la === esql_parser.SRC_UNQUOTED_IDENTIFIER || _la === esql_parser.SRC_QUOTED_IDENTIFIER)) { - this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; + this.state = 321; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.STRING: + case esql_parser.INTEGER_LITERAL: + case esql_parser.DECIMAL_LITERAL: + case esql_parser.LP: + case esql_parser.OPENING_BRACKET: + case esql_parser.NULL: + case esql_parser.BOOLEAN_VALUE: + case esql_parser.ASTERISK: + case esql_parser.UNQUOTED_IDENTIFIER: + case esql_parser.QUOTED_IDENTIFIER: + { + this.state = 316; + this.primaryExpression(); + } + break; + case esql_parser.UNARY_FUNCTION: + { + this.state = 317; + this.mathFn(); + } + break; + case esql_parser.MATH_FUNCTION: + { + this.state = 318; + this.mathEvalFn(); } + break; + case esql_parser.PLUS: + case esql_parser.MINUS: + { + this.state = 319; + _localctx._operator = this._input.LT(1); + _la = this._input.LA(1); + if (!(_la === esql_parser.PLUS || _la === esql_parser.MINUS)) { + _localctx._operator = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } - this._errHandler.reportMatch(this); - this.consume(); + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 320; + this.operatorExpression(3); + } + break; + default: + throw new NoViableAltException(this); + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 331; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 29, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; + { + this.state = 329; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 28, this._ctx) ) { + case 1: + { + _localctx = new OperatorExpressionContext(_parentctx, _parentState); + _localctx._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_operatorExpression); + this.state = 323; + if (!(this.precpred(this._ctx, 2))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 2)"); + } + this.state = 324; + _localctx._operator = this._input.LT(1); + _la = this._input.LA(1); + if (!(((((_la - 53)) & ~0x1F) === 0 && ((1 << (_la - 53)) & ((1 << (esql_parser.ASTERISK - 53)) | (1 << (esql_parser.SLASH - 53)) | (1 << (esql_parser.PERCENT - 53)))) !== 0))) { + _localctx._operator = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 325; + _localctx._right = this.operatorExpression(3); + } + break; + + case 2: + { + _localctx = new OperatorExpressionContext(_parentctx, _parentState); + _localctx._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_operatorExpression); + this.state = 326; + if (!(this.precpred(this._ctx, 1))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); + } + this.state = 327; + _localctx._operator = this._input.LT(1); + _la = this._input.LA(1); + if (!(_la === esql_parser.PLUS || _la === esql_parser.MINUS)) { + _localctx._operator = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 328; + _localctx._right = this.operatorExpression(2); + } + break; + } + } + } + this.state = 333; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 29, this._ctx); } } } @@ -1174,35 +1443,92 @@ export class esql_parser extends Parser { } } finally { - this.exitRule(); + this.unrollRecursionContexts(_parentctx); } return _localctx; } // @RuleVersion(0) - public functionExpressionArgument(): FunctionExpressionArgumentContext { - let _localctx: FunctionExpressionArgumentContext = new FunctionExpressionArgumentContext(this._ctx, this.state); - this.enterRule(_localctx, 38, esql_parser.RULE_functionExpressionArgument); + public primaryExpression(): PrimaryExpressionContext { + let _localctx: PrimaryExpressionContext = new PrimaryExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 34, esql_parser.RULE_primaryExpression); + let _la: number; try { - this.state = 223; + this.state = 355; this._errHandler.sync(this); - switch (this._input.LA(1)) { - case esql_parser.UNQUOTED_IDENTIFIER: - case esql_parser.QUOTED_IDENTIFIER: + switch ( this.interpreter.adaptivePredict(this._input, 32, this._ctx) ) { + case 1: this.enterOuterAlt(_localctx, 1); { - this.state = 221; - this.qualifiedName(); + this.state = 334; + this.constant(); } break; - case esql_parser.STRING: + + case 2: this.enterOuterAlt(_localctx, 2); { - this.state = 222; - this.string(); + this.state = 335; + this.qualifiedName(); + } + break; + + case 3: + this.enterOuterAlt(_localctx, 3); + { + this.state = 336; + this.dateExpression(); + } + break; + + case 4: + this.enterOuterAlt(_localctx, 4); + { + this.state = 337; + this.match(esql_parser.LP); + this.state = 338; + this.booleanExpression(0); + this.state = 339; + this.match(esql_parser.RP); + } + break; + + case 5: + this.enterOuterAlt(_localctx, 5); + { + this.state = 341; + this.identifier(); + this.state = 342; + this.match(esql_parser.LP); + this.state = 351; + this._errHandler.sync(this); + _la = this._input.LA(1); + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << esql_parser.STRING) | (1 << esql_parser.INTEGER_LITERAL) | (1 << esql_parser.DECIMAL_LITERAL))) !== 0) || ((((_la - 34)) & ~0x1F) === 0 && ((1 << (_la - 34)) & ((1 << (esql_parser.LP - 34)) | (1 << (esql_parser.OPENING_BRACKET - 34)) | (1 << (esql_parser.NOT - 34)) | (1 << (esql_parser.NULL - 34)) | (1 << (esql_parser.BOOLEAN_VALUE - 34)) | (1 << (esql_parser.PLUS - 34)) | (1 << (esql_parser.MINUS - 34)) | (1 << (esql_parser.ASTERISK - 34)) | (1 << (esql_parser.MATH_FUNCTION - 34)) | (1 << (esql_parser.UNARY_FUNCTION - 34)) | (1 << (esql_parser.UNQUOTED_IDENTIFIER - 34)) | (1 << (esql_parser.QUOTED_IDENTIFIER - 34)))) !== 0)) { + { + this.state = 343; + this.booleanExpression(0); + this.state = 348; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 344; + this.match(esql_parser.COMMA); + this.state = 345; + this.booleanExpression(0); + } + } + this.state = 350; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + + this.state = 353; + this.match(esql_parser.RP); } break; - default: - throw new NoViableAltException(this); } } catch (re) { @@ -1220,33 +1546,16 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public qualifiedName(): QualifiedNameContext { - let _localctx: QualifiedNameContext = new QualifiedNameContext(this._ctx, this.state); - this.enterRule(_localctx, 40, esql_parser.RULE_qualifiedName); + public rowCommand(): RowCommandContext { + let _localctx: RowCommandContext = new RowCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 36, esql_parser.RULE_rowCommand); try { - let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 225; - this.identifier(); - this.state = 230; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 20, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { - { - { - this.state = 226; - this.match(esql_parser.DOT); - this.state = 227; - this.identifier(); - } - } - } - this.state = 232; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 20, this._ctx); - } + this.state = 357; + this.match(esql_parser.ROW); + this.state = 358; + this.fields(); } } catch (re) { @@ -1264,32 +1573,32 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public qualifiedNames(): QualifiedNamesContext { - let _localctx: QualifiedNamesContext = new QualifiedNamesContext(this._ctx, this.state); - this.enterRule(_localctx, 42, esql_parser.RULE_qualifiedNames); + public fields(): FieldsContext { + let _localctx: FieldsContext = new FieldsContext(this._ctx, this.state); + this.enterRule(_localctx, 38, esql_parser.RULE_fields); try { let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 233; - this.qualifiedName(); - this.state = 238; + this.state = 360; + this.field(); + this.state = 365; this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 21, this._ctx); + _alt = this.interpreter.adaptivePredict(this._input, 33, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 234; + this.state = 361; this.match(esql_parser.COMMA); - this.state = 235; - this.qualifiedName(); + this.state = 362; + this.field(); } } } - this.state = 240; + this.state = 367; this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 21, this._ctx); + _alt = this.interpreter.adaptivePredict(this._input, 33, this._ctx); } } } @@ -1308,16 +1617,59 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public identifier(): IdentifierContext { - let _localctx: IdentifierContext = new IdentifierContext(this._ctx, this.state); - this.enterRule(_localctx, 44, esql_parser.RULE_identifier); + public field(): FieldContext { + let _localctx: FieldContext = new FieldContext(this._ctx, this.state); + this.enterRule(_localctx, 40, esql_parser.RULE_field); + try { + this.state = 373; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 34, this._ctx) ) { + case 1: + this.enterOuterAlt(_localctx, 1); + { + this.state = 368; + this.booleanExpression(0); + } + break; + + case 2: + this.enterOuterAlt(_localctx, 2); + { + this.state = 369; + this.userVariable(); + this.state = 370; + this.match(esql_parser.ASSIGN); + this.state = 371; + this.booleanExpression(0); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public enrichFieldIdentifier(): EnrichFieldIdentifierContext { + let _localctx: EnrichFieldIdentifierContext = new EnrichFieldIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 42, esql_parser.RULE_enrichFieldIdentifier); let _la: number; try { this.enterOuterAlt(_localctx, 1); { - this.state = 241; + this.state = 375; _la = this._input.LA(1); - if (!(_la === esql_parser.UNQUOTED_IDENTIFIER || _la === esql_parser.QUOTED_IDENTIFIER)) { + if (!(_la === esql_parser.ENR_UNQUOTED_IDENTIFIER || _la === esql_parser.ENR_QUOTED_IDENTIFIER)) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -1344,14 +1696,14 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public functionIdentifier(): FunctionIdentifierContext { - let _localctx: FunctionIdentifierContext = new FunctionIdentifierContext(this._ctx, this.state); - this.enterRule(_localctx, 46, esql_parser.RULE_functionIdentifier); + public userVariable(): UserVariableContext { + let _localctx: UserVariableContext = new UserVariableContext(this._ctx, this.state); + this.enterRule(_localctx, 44, esql_parser.RULE_userVariable); try { this.enterOuterAlt(_localctx, 1); { - this.state = 243; - this.match(esql_parser.UNARY_FUNCTION); + this.state = 377; + this.identifier(); } } catch (re) { @@ -1369,48 +1721,45 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public constant(): ConstantContext { - let _localctx: ConstantContext = new ConstantContext(this._ctx, this.state); - this.enterRule(_localctx, 48, esql_parser.RULE_constant); + public fromCommand(): FromCommandContext { + let _localctx: FromCommandContext = new FromCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 46, esql_parser.RULE_fromCommand); try { - this.state = 249; + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 379; + this.match(esql_parser.FROM); + this.state = 380; + this.sourceIdentifier(); + this.state = 385; this._errHandler.sync(this); - switch (this._input.LA(1)) { - case esql_parser.NULL: - _localctx = new NullLiteralContext(_localctx); - this.enterOuterAlt(_localctx, 1); - { - this.state = 245; - this.match(esql_parser.NULL); - } - break; - case esql_parser.INTEGER_LITERAL: - case esql_parser.DECIMAL_LITERAL: - _localctx = new NumericLiteralContext(_localctx); - this.enterOuterAlt(_localctx, 2); - { - this.state = 246; - this.number(); - } - break; - case esql_parser.BOOLEAN_VALUE: - _localctx = new BooleanLiteralContext(_localctx); - this.enterOuterAlt(_localctx, 3); - { - this.state = 247; - this.booleanValue(); + _alt = this.interpreter.adaptivePredict(this._input, 35, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 381; + this.match(esql_parser.COMMA); + this.state = 382; + this.sourceIdentifier(); + } + } } - break; - case esql_parser.STRING: - _localctx = new StringLiteralContext(_localctx); - this.enterOuterAlt(_localctx, 4); + this.state = 387; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 35, this._ctx); + } + this.state = 389; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 36, this._ctx) ) { + case 1: { - this.state = 248; - this.string(); + this.state = 388; + this.metadata(); } break; - default: - throw new NoViableAltException(this); + } } } catch (re) { @@ -1428,16 +1777,37 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public limitCommand(): LimitCommandContext { - let _localctx: LimitCommandContext = new LimitCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 50, esql_parser.RULE_limitCommand); + public metadata(): MetadataContext { + let _localctx: MetadataContext = new MetadataContext(this._ctx, this.state); + this.enterRule(_localctx, 48, esql_parser.RULE_metadata); + let _la: number; try { this.enterOuterAlt(_localctx, 1); { - this.state = 251; - this.match(esql_parser.LIMIT); - this.state = 252; - this.match(esql_parser.INTEGER_LITERAL); + this.state = 391; + this.match(esql_parser.OPENING_BRACKET); + this.state = 392; + this.match(esql_parser.METADATA); + this.state = 393; + this.sourceIdentifier(); + this.state = 398; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 394; + this.match(esql_parser.COMMA); + this.state = 395; + this.sourceIdentifier(); + } + } + this.state = 400; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 401; + this.match(esql_parser.CLOSING_BRACKET); } } catch (re) { @@ -1455,35 +1825,16 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public sortCommand(): SortCommandContext { - let _localctx: SortCommandContext = new SortCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 52, esql_parser.RULE_sortCommand); + public evalCommand(): EvalCommandContext { + let _localctx: EvalCommandContext = new EvalCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 50, esql_parser.RULE_evalCommand); try { - let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 254; - this.match(esql_parser.SORT); - this.state = 255; - this.orderExpression(); - this.state = 260; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 23, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { - { - { - this.state = 256; - this.match(esql_parser.COMMA); - this.state = 257; - this.orderExpression(); - } - } - } - this.state = 262; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 23, this._ctx); - } + this.state = 403; + this.match(esql_parser.EVAL); + this.state = 404; + this.fields(); } } catch (re) { @@ -1501,35 +1852,33 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public orderExpression(): OrderExpressionContext { - let _localctx: OrderExpressionContext = new OrderExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 54, esql_parser.RULE_orderExpression); + public statsCommand(): StatsCommandContext { + let _localctx: StatsCommandContext = new StatsCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 52, esql_parser.RULE_statsCommand); try { this.enterOuterAlt(_localctx, 1); { - this.state = 263; - this.booleanExpression(0); - this.state = 265; + this.state = 406; + this.match(esql_parser.STATS); + this.state = 408; this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 24, this._ctx) ) { + switch ( this.interpreter.adaptivePredict(this._input, 38, this._ctx) ) { case 1: { - this.state = 264; - this.match(esql_parser.ORDERING); + this.state = 407; + this.fields(); } break; } - this.state = 269; + this.state = 412; this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 25, this._ctx) ) { + switch ( this.interpreter.adaptivePredict(this._input, 39, this._ctx) ) { case 1: { - this.state = 267; - this.match(esql_parser.NULLS_ORDERING); - { - this.state = 268; - this.match(esql_parser.NULLS_ORDERING_DIRECTION); - } + this.state = 410; + this.match(esql_parser.BY); + this.state = 411; + this.qualifiedNames(); } break; } @@ -1550,34 +1899,24 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public projectCommand(): ProjectCommandContext { - let _localctx: ProjectCommandContext = new ProjectCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 56, esql_parser.RULE_projectCommand); + public sourceIdentifier(): SourceIdentifierContext { + let _localctx: SourceIdentifierContext = new SourceIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 54, esql_parser.RULE_sourceIdentifier); + let _la: number; try { - let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 271; - this.match(esql_parser.PROJECT); - this.state = 272; - this.projectClause(); - this.state = 277; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 26, this._ctx); - while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { - if (_alt === 1) { - { - { - this.state = 273; - this.match(esql_parser.COMMA); - this.state = 274; - this.projectClause(); - } - } + this.state = 414; + _la = this._input.LA(1); + if (!(_la === esql_parser.SRC_UNQUOTED_IDENTIFIER || _la === esql_parser.SRC_QUOTED_IDENTIFIER)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; } - this.state = 279; - this._errHandler.sync(this); - _alt = this.interpreter.adaptivePredict(this._input, 26, this._ctx); + + this._errHandler.reportMatch(this); + this.consume(); } } } @@ -1596,32 +1935,25 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public projectClause(): ProjectClauseContext { - let _localctx: ProjectClauseContext = new ProjectClauseContext(this._ctx, this.state); - this.enterRule(_localctx, 58, esql_parser.RULE_projectClause); + public enrichIdentifier(): EnrichIdentifierContext { + let _localctx: EnrichIdentifierContext = new EnrichIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 56, esql_parser.RULE_enrichIdentifier); + let _la: number; try { - this.state = 285; - this._errHandler.sync(this); - switch ( this.interpreter.adaptivePredict(this._input, 27, this._ctx) ) { - case 1: - this.enterOuterAlt(_localctx, 1); - { - this.state = 280; - this.sourceIdentifier(); + this.enterOuterAlt(_localctx, 1); + { + this.state = 416; + _la = this._input.LA(1); + if (!(_la === esql_parser.ENR_UNQUOTED_IDENTIFIER || _la === esql_parser.ENR_QUOTED_IDENTIFIER)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; } - break; - case 2: - this.enterOuterAlt(_localctx, 2); - { - this.state = 281; - _localctx._newName = this.sourceIdentifier(); - this.state = 282; - this.match(esql_parser.ASSIGN); - this.state = 283; - _localctx._oldName = this.sourceIdentifier(); - } - break; + this._errHandler.reportMatch(this); + this.consume(); + } } } catch (re) { @@ -1639,14 +1971,39 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public booleanValue(): BooleanValueContext { - let _localctx: BooleanValueContext = new BooleanValueContext(this._ctx, this.state); - this.enterRule(_localctx, 60, esql_parser.RULE_booleanValue); + public functionExpressionArgument(): FunctionExpressionArgumentContext { + let _localctx: FunctionExpressionArgumentContext = new FunctionExpressionArgumentContext(this._ctx, this.state); + this.enterRule(_localctx, 58, esql_parser.RULE_functionExpressionArgument); try { - this.enterOuterAlt(_localctx, 1); - { - this.state = 287; - this.match(esql_parser.BOOLEAN_VALUE); + this.state = 421; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.ASTERISK: + case esql_parser.UNQUOTED_IDENTIFIER: + case esql_parser.QUOTED_IDENTIFIER: + this.enterOuterAlt(_localctx, 1); + { + this.state = 418; + this.qualifiedName(); + } + break; + case esql_parser.STRING: + this.enterOuterAlt(_localctx, 2); + { + this.state = 419; + this.string(); + } + break; + case esql_parser.INTEGER_LITERAL: + case esql_parser.DECIMAL_LITERAL: + this.enterOuterAlt(_localctx, 3); + { + this.state = 420; + this.number(); + } + break; + default: + throw new NoViableAltException(this); } } catch (re) { @@ -1664,31 +2021,60 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public number(): NumberContext { - let _localctx: NumberContext = new NumberContext(this._ctx, this.state); - this.enterRule(_localctx, 62, esql_parser.RULE_number); + public mathFunctionExpressionArgument(): MathFunctionExpressionArgumentContext { + let _localctx: MathFunctionExpressionArgumentContext = new MathFunctionExpressionArgumentContext(this._ctx, this.state); + this.enterRule(_localctx, 60, esql_parser.RULE_mathFunctionExpressionArgument); try { - this.state = 291; + this.state = 429; this._errHandler.sync(this); - switch (this._input.LA(1)) { - case esql_parser.DECIMAL_LITERAL: - _localctx = new DecimalLiteralContext(_localctx); + switch ( this.interpreter.adaptivePredict(this._input, 41, this._ctx) ) { + case 1: this.enterOuterAlt(_localctx, 1); { - this.state = 289; - this.match(esql_parser.DECIMAL_LITERAL); + this.state = 423; + this.qualifiedName(); } break; - case esql_parser.INTEGER_LITERAL: - _localctx = new IntegerLiteralContext(_localctx); + + case 2: this.enterOuterAlt(_localctx, 2); { - this.state = 290; - this.match(esql_parser.INTEGER_LITERAL); + this.state = 424; + this.string(); + } + break; + + case 3: + this.enterOuterAlt(_localctx, 3); + { + this.state = 425; + this.number(); + } + break; + + case 4: + this.enterOuterAlt(_localctx, 4); + { + this.state = 426; + this.operatorExpression(0); + } + break; + + case 5: + this.enterOuterAlt(_localctx, 5); + { + this.state = 427; + this.dateExpression(); + } + break; + + case 6: + this.enterOuterAlt(_localctx, 6); + { + this.state = 428; + this.comparison(); } break; - default: - throw new NoViableAltException(this); } } catch (re) { @@ -1706,14 +2092,33 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public string(): StringContext { - let _localctx: StringContext = new StringContext(this._ctx, this.state); - this.enterRule(_localctx, 64, esql_parser.RULE_string); + public qualifiedName(): QualifiedNameContext { + let _localctx: QualifiedNameContext = new QualifiedNameContext(this._ctx, this.state); + this.enterRule(_localctx, 62, esql_parser.RULE_qualifiedName); try { + let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 293; - this.match(esql_parser.STRING); + this.state = 431; + this.identifier(); + this.state = 436; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 42, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 432; + this.match(esql_parser.DOT); + this.state = 433; + this.identifier(); + } + } + } + this.state = 438; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 42, this._ctx); + } } } catch (re) { @@ -1731,14 +2136,33 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public comparisonOperator(): ComparisonOperatorContext { - let _localctx: ComparisonOperatorContext = new ComparisonOperatorContext(this._ctx, this.state); - this.enterRule(_localctx, 66, esql_parser.RULE_comparisonOperator); + public qualifiedNames(): QualifiedNamesContext { + let _localctx: QualifiedNamesContext = new QualifiedNamesContext(this._ctx, this.state); + this.enterRule(_localctx, 64, esql_parser.RULE_qualifiedNames); try { + let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 295; - this.match(esql_parser.COMPARISON_OPERATOR); + this.state = 439; + this.qualifiedName(); + this.state = 444; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 43, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 440; + this.match(esql_parser.COMMA); + this.state = 441; + this.qualifiedName(); + } + } + } + this.state = 446; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 43, this._ctx); + } } } catch (re) { @@ -1756,16 +2180,25 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public explainCommand(): ExplainCommandContext { - let _localctx: ExplainCommandContext = new ExplainCommandContext(this._ctx, this.state); - this.enterRule(_localctx, 68, esql_parser.RULE_explainCommand); + public identifier(): IdentifierContext { + let _localctx: IdentifierContext = new IdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 66, esql_parser.RULE_identifier); + let _la: number; try { this.enterOuterAlt(_localctx, 1); { - this.state = 297; - this.match(esql_parser.EXPLAIN); - this.state = 298; - this.subqueryExpression(); + this.state = 447; + _la = this._input.LA(1); + if (!(((((_la - 53)) & ~0x1F) === 0 && ((1 << (_la - 53)) & ((1 << (esql_parser.ASTERISK - 53)) | (1 << (esql_parser.UNQUOTED_IDENTIFIER - 53)) | (1 << (esql_parser.QUOTED_IDENTIFIER - 53)))) !== 0))) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } } } catch (re) { @@ -1783,18 +2216,14 @@ export class esql_parser extends Parser { return _localctx; } // @RuleVersion(0) - public subqueryExpression(): SubqueryExpressionContext { - let _localctx: SubqueryExpressionContext = new SubqueryExpressionContext(this._ctx, this.state); - this.enterRule(_localctx, 70, esql_parser.RULE_subqueryExpression); + public mathFunctionIdentifier(): MathFunctionIdentifierContext { + let _localctx: MathFunctionIdentifierContext = new MathFunctionIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 68, esql_parser.RULE_mathFunctionIdentifier); try { this.enterOuterAlt(_localctx, 1); { - this.state = 300; - this.match(esql_parser.OPENING_BRACKET); - this.state = 301; - this.query(0); - this.state = 302; - this.match(esql_parser.CLOSING_BRACKET); + this.state = 449; + this.match(esql_parser.MATH_FUNCTION); } } catch (re) { @@ -1811,488 +2240,2229 @@ export class esql_parser extends Parser { } return _localctx; } - - public sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { - switch (ruleIndex) { - case 1: - return this.query_sempred(_localctx as QueryContext, predIndex); - - case 5: - return this.booleanExpression_sempred(_localctx as BooleanExpressionContext, predIndex); - - case 9: - return this.operatorExpression_sempred(_localctx as OperatorExpressionContext, predIndex); - } - return true; - } - private query_sempred(_localctx: QueryContext, predIndex: number): boolean { - switch (predIndex) { - case 0: - return this.precpred(this._ctx, 1); + // @RuleVersion(0) + public functionIdentifier(): FunctionIdentifierContext { + let _localctx: FunctionIdentifierContext = new FunctionIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 70, esql_parser.RULE_functionIdentifier); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 451; + this.match(esql_parser.UNARY_FUNCTION); + } } - return true; - } - private booleanExpression_sempred(_localctx: BooleanExpressionContext, predIndex: number): boolean { - switch (predIndex) { - case 1: - return this.precpred(this._ctx, 2); - - case 2: - return this.precpred(this._ctx, 1); + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } } - return true; - } - private operatorExpression_sempred(_localctx: OperatorExpressionContext, predIndex: number): boolean { - switch (predIndex) { - case 3: - return this.precpred(this._ctx, 2); - - case 4: - return this.precpred(this._ctx, 1); + finally { + this.exitRule(); } - return true; + return _localctx; } + // @RuleVersion(0) + public constant(): ConstantContext { + let _localctx: ConstantContext = new ConstantContext(this._ctx, this.state); + this.enterRule(_localctx, 72, esql_parser.RULE_constant); + let _la: number; + try { + this.state = 490; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 47, this._ctx) ) { + case 1: + this.enterOuterAlt(_localctx, 1); + { + this.state = 453; + this.match(esql_parser.NULL); + } + break; - public static readonly _serializedATN: string = - "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x033\u0133\x04\x02" + - "\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04\x07" + - "\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r\t\r\x04" + - "\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12\x04" + - "\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17\x04" + - "\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C\x04" + - "\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t\"\x04#" + - "\t#\x04$\t$\x04%\t%\x03\x02\x03\x02\x03\x02\x03\x03\x03\x03\x03\x03\x03" + - "\x03\x03\x03\x03\x03\x07\x03T\n\x03\f\x03\x0E\x03W\v\x03\x03\x04\x03\x04" + - "\x03\x04\x05\x04\\\n\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05" + - "\x05\x05d\n\x05\x03\x06\x03\x06\x03\x06\x03\x07\x03\x07\x03\x07\x03\x07" + - "\x05\x07m\n\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x07\x07" + - "u\n\x07\f\x07\x0E\x07x\v\x07\x03\b\x03\b\x05\b|\n\b\x03\t\x03\t\x03\t" + - "\x03\t\x03\n\x03\n\x03\n\x03\n\x03\n\x07\n\x87\n\n\f\n\x0E\n\x8A\v\n\x05" + - "\n\x8C\n\n\x03\n\x03\n\x03\v\x03\v\x03\v\x03\v\x03\v\x05\v\x95\n\v\x03" + - "\v\x03\v\x03\v\x03\v\x03\v\x03\v\x07\v\x9D\n\v\f\v\x0E\v\xA0\v\v\x03\f" + - "\x03\f\x03\f\x03\f\x03\f\x03\f\x03\f\x03\f\x03\f\x03\f\x03\f\x07\f\xAD" + - "\n\f\f\f\x0E\f\xB0\v\f\x05\f\xB2\n\f\x03\f\x03\f\x05\f\xB6\n\f\x03\r\x03" + - "\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x07\x0E\xBE\n\x0E\f\x0E\x0E\x0E\xC1\v" + - "\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x05\x0F\xC8\n\x0F\x03\x10" + - "\x03\x10\x03\x11\x03\x11\x03\x11\x03\x11\x07\x11\xD0\n\x11\f\x11\x0E\x11" + - "\xD3\v\x11\x03\x12\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03\x13\x05" + - "\x13\xDC\n\x13\x03\x14\x03\x14\x03\x15\x03\x15\x05\x15\xE2\n\x15\x03\x16" + - "\x03\x16\x03\x16\x07\x16\xE7\n\x16\f\x16\x0E\x16\xEA\v\x16\x03\x17\x03" + - "\x17\x03\x17\x07\x17\xEF\n\x17\f\x17\x0E\x17\xF2\v\x17\x03\x18\x03\x18" + - "\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x05\x1A\xFC\n\x1A\x03" + - "\x1B\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x07\x1C\u0105\n\x1C" + - "\f\x1C\x0E\x1C\u0108\v\x1C\x03\x1D\x03\x1D\x05\x1D\u010C\n\x1D\x03\x1D" + - "\x03\x1D\x05\x1D\u0110\n\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E\x07\x1E\u0116" + - "\n\x1E\f\x1E\x0E\x1E\u0119\v\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F" + - "\x05\x1F\u0120\n\x1F\x03 \x03 \x03!\x03!\x05!\u0126\n!\x03\"\x03\"\x03" + - "#\x03#\x03$\x03$\x03$\x03%\x03%\x03%\x03%\x03%\x02\x02\x05\x04\f\x14&" + - "\x02\x02\x04\x02\x06\x02\b\x02\n\x02\f\x02\x0E\x02\x10\x02\x12\x02\x14" + - "\x02\x16\x02\x18\x02\x1A\x02\x1C\x02\x1E\x02 \x02\"\x02$\x02&\x02(\x02" + - "*\x02,\x02.\x020\x022\x024\x026\x028\x02:\x02<\x02>\x02@\x02B\x02D\x02" + - "F\x02H\x02\x02\x06\x03\x02!\"\x03\x02#%\x03\x02/0\x03\x02*+\x02\u0135" + - "\x02J\x03\x02\x02\x02\x04M\x03\x02\x02\x02\x06[\x03\x02\x02\x02\bc\x03" + - "\x02\x02\x02\ne\x03\x02\x02\x02\fl\x03\x02\x02\x02\x0E{\x03\x02\x02\x02" + - "\x10}\x03\x02\x02\x02\x12\x81\x03\x02\x02\x02\x14\x94\x03\x02\x02\x02" + - "\x16\xB5\x03\x02\x02\x02\x18\xB7\x03\x02\x02\x02\x1A\xBA\x03\x02\x02\x02" + - "\x1C\xC7\x03\x02\x02\x02\x1E\xC9\x03\x02\x02\x02 \xCB\x03\x02\x02\x02" + - "\"\xD4\x03\x02\x02\x02$\xD7\x03\x02\x02\x02&\xDD\x03\x02\x02\x02(\xE1" + - "\x03\x02\x02\x02*\xE3\x03\x02\x02\x02,\xEB\x03\x02\x02\x02.\xF3\x03\x02" + - "\x02\x020\xF5\x03\x02\x02\x022\xFB\x03\x02\x02\x024\xFD\x03\x02\x02\x02" + - "6\u0100\x03\x02\x02\x028\u0109\x03\x02\x02\x02:\u0111\x03\x02\x02\x02" + - "<\u011F\x03\x02\x02\x02>\u0121\x03\x02\x02\x02@\u0125\x03\x02\x02\x02" + - "B\u0127\x03\x02\x02\x02D\u0129\x03\x02\x02\x02F\u012B\x03\x02\x02\x02" + - "H\u012E\x03\x02\x02\x02JK\x05\x04\x03\x02KL\x07\x02\x02\x03L\x03\x03\x02" + - "\x02\x02MN\b\x03\x01\x02NO\x05\x06\x04\x02OU\x03\x02\x02\x02PQ\f\x03\x02" + - "\x02QR\x07\x0F\x02\x02RT\x05\b\x05\x02SP\x03\x02\x02\x02TW\x03\x02\x02" + - "\x02US\x03\x02\x02\x02UV\x03\x02\x02\x02V\x05\x03\x02\x02\x02WU\x03\x02" + - "\x02\x02X\\\x05F$\x02Y\\\x05 \x11\x02Z\\\x05\x18\r\x02[X\x03\x02\x02\x02" + - "[Y\x03\x02\x02\x02[Z\x03\x02\x02\x02\\\x07\x03\x02\x02\x02]d\x05\"\x12" + - "\x02^d\x054\x1B\x02_d\x05:\x1E\x02`d\x056\x1C\x02ad\x05$\x13\x02bd\x05" + - "\n\x06\x02c]\x03\x02\x02\x02c^\x03\x02\x02\x02c_\x03\x02\x02\x02c`\x03" + - "\x02\x02\x02ca\x03\x02\x02\x02cb\x03\x02\x02\x02d\t\x03\x02\x02\x02ef" + - "\x07\b\x02\x02fg\x05\f\x07\x02g\v\x03\x02\x02\x02hi\b\x07\x01\x02ij\x07" + - "\x1B\x02\x02jm\x05\f\x07\x06km\x05\x0E\b\x02lh\x03\x02\x02\x02lk\x03\x02" + - "\x02\x02mv\x03\x02\x02\x02no\f\x04\x02\x02op\x07\x14\x02\x02pu\x05\f\x07" + - "\x05qr\f\x03\x02\x02rs\x07\x1D\x02\x02su\x05\f\x07\x04tn\x03\x02\x02\x02" + - "tq\x03\x02\x02\x02ux\x03\x02\x02\x02vt\x03\x02\x02\x02vw\x03\x02\x02\x02" + - "w\r\x03\x02\x02\x02xv\x03\x02\x02\x02y|\x05\x14\v\x02z|\x05\x10\t\x02" + - "{y\x03\x02\x02\x02{z\x03\x02\x02\x02|\x0F\x03\x02\x02\x02}~\x05\x14\v" + - "\x02~\x7F\x05D#\x02\x7F\x80\x05\x14\v\x02\x80\x11\x03\x02\x02\x02\x81" + - "\x82\x050\x19\x02\x82\x8B\x07\x18\x02\x02\x83\x88\x05(\x15\x02\x84\x85" + - "\x07\x16\x02\x02\x85\x87\x05(\x15\x02\x86\x84\x03\x02\x02\x02\x87\x8A" + - "\x03\x02\x02\x02\x88\x86\x03\x02\x02\x02\x88\x89\x03\x02\x02\x02\x89\x8C" + - "\x03\x02\x02\x02\x8A\x88\x03\x02\x02\x02\x8B\x83\x03\x02\x02\x02\x8B\x8C" + - "\x03\x02\x02\x02\x8C\x8D\x03\x02\x02\x02\x8D\x8E\x07\x1E\x02\x02\x8E\x13" + - "\x03\x02\x02\x02\x8F\x90\b\v\x01\x02\x90\x95\x05\x16\f\x02\x91\x95\x05" + - "\x12\n\x02\x92\x93\t\x02\x02\x02\x93\x95\x05\x14\v\x05\x94\x8F\x03\x02" + - "\x02\x02\x94\x91\x03\x02\x02\x02\x94\x92\x03\x02\x02\x02\x95\x9E\x03\x02" + - "\x02\x02\x96\x97\f\x04\x02\x02\x97\x98\t\x03\x02\x02\x98\x9D\x05\x14\v" + - "\x05\x99\x9A\f\x03\x02\x02\x9A\x9B\t\x02\x02\x02\x9B\x9D\x05\x14\v\x04" + - "\x9C\x96\x03\x02\x02\x02\x9C\x99\x03\x02\x02\x02\x9D\xA0\x03\x02\x02\x02" + - "\x9E\x9C\x03\x02\x02\x02\x9E\x9F\x03\x02\x02\x02\x9F\x15\x03\x02\x02\x02" + - "\xA0\x9E\x03\x02\x02\x02\xA1\xB6\x052\x1A\x02\xA2\xB6\x05*\x16\x02\xA3" + - "\xA4\x07\x18\x02\x02\xA4\xA5\x05\f\x07\x02\xA5\xA6\x07\x1E\x02\x02\xA6" + - "\xB6\x03\x02\x02\x02\xA7\xA8\x05.\x18\x02\xA8\xB1\x07\x18\x02\x02\xA9" + - "\xAE\x05\f\x07\x02\xAA\xAB\x07\x16\x02\x02\xAB\xAD\x05\f\x07\x02\xAC\xAA" + - "\x03\x02\x02\x02\xAD\xB0\x03\x02\x02\x02\xAE\xAC\x03\x02\x02\x02\xAE\xAF" + - "\x03\x02\x02\x02\xAF\xB2\x03\x02\x02\x02\xB0\xAE\x03\x02\x02\x02\xB1\xA9" + - "\x03\x02\x02\x02\xB1\xB2\x03\x02\x02\x02\xB2\xB3\x03\x02\x02\x02\xB3\xB4" + - "\x07\x1E\x02\x02\xB4\xB6\x03\x02\x02\x02\xB5\xA1\x03\x02\x02\x02\xB5\xA2" + - "\x03\x02\x02\x02\xB5\xA3\x03\x02\x02\x02\xB5\xA7\x03\x02\x02\x02\xB6\x17" + - "\x03\x02\x02\x02\xB7\xB8\x07\x06\x02\x02\xB8\xB9\x05\x1A\x0E\x02\xB9\x19" + - "\x03\x02\x02\x02\xBA\xBF\x05\x1C\x0F\x02\xBB\xBC\x07\x16\x02\x02\xBC\xBE" + - "\x05\x1C\x0F\x02\xBD\xBB\x03\x02\x02\x02\xBE\xC1\x03\x02\x02\x02\xBF\xBD" + - "\x03\x02\x02\x02\xBF\xC0\x03\x02\x02\x02\xC0\x1B\x03\x02\x02\x02\xC1\xBF" + - "\x03\x02\x02\x02\xC2\xC8\x05\f\x07\x02\xC3\xC4\x05\x1E\x10\x02\xC4\xC5" + - "\x07\x15\x02\x02\xC5\xC6\x05\f\x07\x02\xC6\xC8\x03\x02\x02\x02\xC7\xC2" + - "\x03\x02\x02\x02\xC7\xC3\x03\x02\x02\x02\xC8\x1D\x03\x02\x02\x02\xC9\xCA" + - "\x05.\x18\x02\xCA\x1F\x03\x02\x02\x02\xCB\xCC\x07\x05\x02\x02\xCC\xD1" + - "\x05&\x14\x02\xCD\xCE\x07\x16\x02\x02\xCE\xD0\x05&\x14\x02\xCF\xCD\x03" + - "\x02\x02\x02\xD0\xD3\x03\x02\x02\x02\xD1\xCF\x03\x02\x02\x02\xD1\xD2\x03" + - "\x02\x02\x02\xD2!\x03\x02\x02\x02\xD3\xD1\x03\x02\x02\x02\xD4\xD5\x07" + - "\x03\x02\x02\xD5\xD6\x05\x1A\x0E\x02\xD6#\x03\x02\x02\x02\xD7\xD8\x07" + - "\x07\x02\x02\xD8\xDB\x05\x1A\x0E\x02\xD9\xDA\x07\x13\x02\x02\xDA\xDC\x05" + - ",\x17\x02\xDB\xD9\x03\x02\x02\x02\xDB\xDC\x03\x02\x02\x02\xDC%\x03\x02" + - "\x02\x02\xDD\xDE\t\x04\x02\x02\xDE\'\x03\x02\x02\x02\xDF\xE2\x05*\x16" + - "\x02\xE0\xE2\x05B\"\x02\xE1\xDF\x03\x02\x02\x02\xE1\xE0\x03\x02\x02\x02" + - "\xE2)\x03\x02\x02\x02\xE3\xE8\x05.\x18\x02\xE4\xE5\x07\x17\x02\x02\xE5" + - "\xE7\x05.\x18\x02\xE6\xE4\x03\x02\x02\x02\xE7\xEA\x03\x02\x02\x02\xE8" + - "\xE6\x03\x02\x02\x02\xE8\xE9\x03\x02\x02\x02\xE9+\x03\x02\x02\x02\xEA" + - "\xE8\x03\x02\x02\x02\xEB\xF0\x05*\x16\x02\xEC\xED\x07\x16\x02\x02\xED" + - "\xEF\x05*\x16\x02\xEE\xEC\x03\x02\x02\x02\xEF\xF2\x03\x02\x02\x02\xF0" + - "\xEE\x03\x02\x02\x02\xF0\xF1\x03\x02\x02\x02\xF1-\x03\x02\x02\x02\xF2" + - "\xF0\x03\x02\x02\x02\xF3\xF4\t\x05\x02\x02\xF4/\x03\x02\x02\x02\xF5\xF6" + - "\x07)\x02\x02\xF61\x03\x02\x02\x02\xF7\xFC\x07\x1C\x02\x02\xF8\xFC\x05" + - "@!\x02\xF9\xFC\x05> \x02\xFA\xFC\x05B\"\x02\xFB\xF7\x03\x02\x02\x02\xFB" + - "\xF8\x03\x02\x02\x02\xFB\xF9\x03\x02\x02\x02\xFB\xFA\x03\x02\x02\x02\xFC" + - "3\x03\x02\x02\x02\xFD\xFE\x07\n\x02\x02\xFE\xFF\x07\x11\x02\x02\xFF5\x03" + - "\x02\x02\x02\u0100\u0101\x07\t\x02\x02\u0101\u0106\x058\x1D\x02\u0102" + - "\u0103\x07\x16\x02\x02\u0103\u0105\x058\x1D\x02\u0104\u0102\x03\x02\x02" + - "\x02\u0105\u0108\x03\x02\x02\x02\u0106\u0104\x03\x02\x02\x02\u0106\u0107" + - "\x03\x02\x02\x02\u01077\x03\x02\x02\x02\u0108\u0106\x03\x02\x02\x02\u0109" + - "\u010B\x05\f\x07\x02\u010A\u010C\x07&\x02\x02\u010B\u010A\x03\x02\x02" + - "\x02\u010B\u010C\x03\x02\x02\x02\u010C\u010F\x03\x02\x02\x02\u010D\u010E" + - "\x07\'\x02\x02\u010E\u0110\x07(\x02\x02\u010F\u010D\x03\x02\x02\x02\u010F" + - "\u0110\x03\x02\x02\x02\u01109\x03\x02\x02\x02\u0111\u0112\x07\v\x02\x02" + - "\u0112\u0117\x05<\x1F\x02\u0113\u0114\x07\x16\x02\x02\u0114\u0116\x05" + - "<\x1F\x02\u0115\u0113\x03\x02\x02\x02\u0116\u0119\x03\x02\x02\x02\u0117" + - "\u0115\x03\x02\x02\x02\u0117\u0118\x03\x02\x02\x02\u0118;\x03\x02\x02" + - "\x02\u0119\u0117\x03\x02\x02\x02\u011A\u0120\x05&\x14\x02\u011B\u011C" + - "\x05&\x14\x02\u011C\u011D\x07\x15\x02\x02\u011D\u011E\x05&\x14\x02\u011E" + - "\u0120\x03\x02\x02\x02\u011F\u011A\x03\x02\x02\x02\u011F\u011B\x03\x02" + - "\x02\x02\u0120=\x03\x02\x02\x02\u0121\u0122\x07\x1F\x02\x02\u0122?\x03" + - "\x02\x02\x02\u0123\u0126\x07\x12\x02\x02\u0124\u0126\x07\x11\x02\x02\u0125" + - "\u0123\x03\x02\x02\x02\u0125\u0124\x03\x02\x02\x02\u0126A\x03\x02\x02" + - "\x02\u0127\u0128\x07\x10\x02\x02\u0128C\x03\x02\x02\x02\u0129\u012A\x07" + - " \x02\x02\u012AE\x03\x02\x02\x02\u012B\u012C\x07\x04\x02\x02\u012C\u012D" + - "\x05H%\x02\u012DG\x03\x02\x02\x02\u012E\u012F\x07\x19\x02\x02\u012F\u0130" + - "\x05\x04\x03\x02\u0130\u0131\x07\x1A\x02\x02\u0131I\x03\x02\x02\x02\x1F" + - "U[cltv{\x88\x8B\x94\x9C\x9E\xAE\xB1\xB5\xBF\xC7\xD1\xDB\xE1\xE8\xF0\xFB" + - "\u0106\u010B\u010F\u0117\u011F\u0125"; - public static __ATN: ATN; - public static get _ATN(): ATN { - if (!esql_parser.__ATN) { - esql_parser.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(esql_parser._serializedATN)); - } + case 2: + this.enterOuterAlt(_localctx, 2); + { + this.state = 454; + this.numericValue(); + } + break; - return esql_parser.__ATN; - } + case 3: + this.enterOuterAlt(_localctx, 3); + { + this.state = 455; + this.booleanValue(); + } + break; -} + case 4: + this.enterOuterAlt(_localctx, 4); + { + this.state = 456; + this.string(); + } + break; -export class SingleStatementContext extends ParserRuleContext { + case 5: + this.enterOuterAlt(_localctx, 5); + { + this.state = 457; + this.match(esql_parser.OPENING_BRACKET); + this.state = 458; + this.numericValue(); + this.state = 463; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 459; + this.match(esql_parser.COMMA); + this.state = 460; + this.numericValue(); + } + } + this.state = 465; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 466; + this.match(esql_parser.CLOSING_BRACKET); + } + break; + + case 6: + this.enterOuterAlt(_localctx, 6); + { + this.state = 468; + this.match(esql_parser.OPENING_BRACKET); + this.state = 469; + this.booleanValue(); + this.state = 474; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 470; + this.match(esql_parser.COMMA); + this.state = 471; + this.booleanValue(); + } + } + this.state = 476; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 477; + this.match(esql_parser.CLOSING_BRACKET); + } + break; + + case 7: + this.enterOuterAlt(_localctx, 7); + { + this.state = 479; + this.match(esql_parser.OPENING_BRACKET); + this.state = 480; + this.string(); + this.state = 485; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 481; + this.match(esql_parser.COMMA); + this.state = 482; + this.string(); + } + } + this.state = 487; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 488; + this.match(esql_parser.CLOSING_BRACKET); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public numericValue(): NumericValueContext { + let _localctx: NumericValueContext = new NumericValueContext(this._ctx, this.state); + this.enterRule(_localctx, 74, esql_parser.RULE_numericValue); + try { + this.state = 494; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.DECIMAL_LITERAL: + this.enterOuterAlt(_localctx, 1); + { + this.state = 492; + this.decimalValue(); + } + break; + case esql_parser.INTEGER_LITERAL: + this.enterOuterAlt(_localctx, 2); + { + this.state = 493; + this.integerValue(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public limitCommand(): LimitCommandContext { + let _localctx: LimitCommandContext = new LimitCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 76, esql_parser.RULE_limitCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 496; + this.match(esql_parser.LIMIT); + this.state = 497; + this.match(esql_parser.INTEGER_LITERAL); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public sortCommand(): SortCommandContext { + let _localctx: SortCommandContext = new SortCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 78, esql_parser.RULE_sortCommand); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 499; + this.match(esql_parser.SORT); + this.state = 500; + this.orderExpression(); + this.state = 505; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 49, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 501; + this.match(esql_parser.COMMA); + this.state = 502; + this.orderExpression(); + } + } + } + this.state = 507; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 49, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public orderExpression(): OrderExpressionContext { + let _localctx: OrderExpressionContext = new OrderExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 80, esql_parser.RULE_orderExpression); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 508; + this.booleanExpression(0); + this.state = 510; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 50, this._ctx) ) { + case 1: + { + this.state = 509; + this.match(esql_parser.ORDERING); + } + break; + } + this.state = 514; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 51, this._ctx) ) { + case 1: + { + this.state = 512; + this.match(esql_parser.NULLS_ORDERING); + { + this.state = 513; + this.match(esql_parser.NULLS_ORDERING_DIRECTION); + } + } + break; + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public projectCommand(): ProjectCommandContext { + let _localctx: ProjectCommandContext = new ProjectCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 82, esql_parser.RULE_projectCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 516; + this.match(esql_parser.PROJECT); + this.state = 517; + this.qualifiedNames(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public keepCommand(): KeepCommandContext { + let _localctx: KeepCommandContext = new KeepCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 84, esql_parser.RULE_keepCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 519; + this.match(esql_parser.KEEP); + this.state = 520; + this.qualifiedNames(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public dropCommand(): DropCommandContext { + let _localctx: DropCommandContext = new DropCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 86, esql_parser.RULE_dropCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 522; + this.match(esql_parser.DROP); + this.state = 523; + this.qualifiedNames(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public renameVariable(): RenameVariableContext { + let _localctx: RenameVariableContext = new RenameVariableContext(this._ctx, this.state); + this.enterRule(_localctx, 88, esql_parser.RULE_renameVariable); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 525; + this.identifier(); + this.state = 530; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 52, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 526; + this.match(esql_parser.DOT); + this.state = 527; + this.identifier(); + } + } + } + this.state = 532; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 52, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public renameCommand(): RenameCommandContext { + let _localctx: RenameCommandContext = new RenameCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 90, esql_parser.RULE_renameCommand); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 533; + this.match(esql_parser.RENAME); + this.state = 534; + this.renameClause(); + this.state = 539; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 53, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 535; + this.match(esql_parser.COMMA); + this.state = 536; + this.renameClause(); + } + } + } + this.state = 541; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 53, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public renameClause(): RenameClauseContext { + let _localctx: RenameClauseContext = new RenameClauseContext(this._ctx, this.state); + this.enterRule(_localctx, 92, esql_parser.RULE_renameClause); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 542; + this.qualifiedName(); + this.state = 543; + this.match(esql_parser.AS); + this.state = 544; + this.renameVariable(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public dissectCommand(): DissectCommandContext { + let _localctx: DissectCommandContext = new DissectCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 94, esql_parser.RULE_dissectCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 546; + this.match(esql_parser.DISSECT); + this.state = 547; + this.qualifiedNames(); + this.state = 548; + this.string(); + this.state = 550; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 54, this._ctx) ) { + case 1: + { + this.state = 549; + this.commandOptions(); + } + break; + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public grokCommand(): GrokCommandContext { + let _localctx: GrokCommandContext = new GrokCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 96, esql_parser.RULE_grokCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 552; + this.match(esql_parser.GROK); + this.state = 553; + this.qualifiedNames(); + this.state = 554; + this.string(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public commandOptions(): CommandOptionsContext { + let _localctx: CommandOptionsContext = new CommandOptionsContext(this._ctx, this.state); + this.enterRule(_localctx, 98, esql_parser.RULE_commandOptions); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 556; + this.commandOption(); + this.state = 561; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 55, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 557; + this.match(esql_parser.COMMA); + this.state = 558; + this.commandOption(); + } + } + } + this.state = 563; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 55, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public commandOption(): CommandOptionContext { + let _localctx: CommandOptionContext = new CommandOptionContext(this._ctx, this.state); + this.enterRule(_localctx, 100, esql_parser.RULE_commandOption); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 564; + this.identifier(); + this.state = 565; + this.match(esql_parser.ASSIGN); + this.state = 566; + this.constant(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public booleanValue(): BooleanValueContext { + let _localctx: BooleanValueContext = new BooleanValueContext(this._ctx, this.state); + this.enterRule(_localctx, 102, esql_parser.RULE_booleanValue); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 568; + this.match(esql_parser.BOOLEAN_VALUE); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public number(): NumberContext { + let _localctx: NumberContext = new NumberContext(this._ctx, this.state); + this.enterRule(_localctx, 104, esql_parser.RULE_number); + try { + this.state = 572; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.DECIMAL_LITERAL: + _localctx = new DecimalLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 570; + this.match(esql_parser.DECIMAL_LITERAL); + } + break; + case esql_parser.INTEGER_LITERAL: + _localctx = new IntegerLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 571; + this.match(esql_parser.INTEGER_LITERAL); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public decimalValue(): DecimalValueContext { + let _localctx: DecimalValueContext = new DecimalValueContext(this._ctx, this.state); + this.enterRule(_localctx, 106, esql_parser.RULE_decimalValue); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 574; + this.match(esql_parser.DECIMAL_LITERAL); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public integerValue(): IntegerValueContext { + let _localctx: IntegerValueContext = new IntegerValueContext(this._ctx, this.state); + this.enterRule(_localctx, 108, esql_parser.RULE_integerValue); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 576; + this.match(esql_parser.INTEGER_LITERAL); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public string(): StringContext { + let _localctx: StringContext = new StringContext(this._ctx, this.state); + this.enterRule(_localctx, 110, esql_parser.RULE_string); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 578; + this.match(esql_parser.STRING); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public comparisonOperator(): ComparisonOperatorContext { + let _localctx: ComparisonOperatorContext = new ComparisonOperatorContext(this._ctx, this.state); + this.enterRule(_localctx, 112, esql_parser.RULE_comparisonOperator); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 580; + this.match(esql_parser.COMPARISON_OPERATOR); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public explainCommand(): ExplainCommandContext { + let _localctx: ExplainCommandContext = new ExplainCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 114, esql_parser.RULE_explainCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 582; + this.match(esql_parser.EXPLAIN); + this.state = 583; + this.subqueryExpression(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public subqueryExpression(): SubqueryExpressionContext { + let _localctx: SubqueryExpressionContext = new SubqueryExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 116, esql_parser.RULE_subqueryExpression); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 585; + this.match(esql_parser.OPENING_BRACKET); + this.state = 586; + this.query(0); + this.state = 587; + this.match(esql_parser.CLOSING_BRACKET); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public showCommand(): ShowCommandContext { + let _localctx: ShowCommandContext = new ShowCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 118, esql_parser.RULE_showCommand); + try { + this.state = 593; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 57, this._ctx) ) { + case 1: + this.enterOuterAlt(_localctx, 1); + { + this.state = 589; + this.match(esql_parser.SHOW); + this.state = 590; + this.match(esql_parser.INFO); + } + break; + + case 2: + this.enterOuterAlt(_localctx, 2); + { + this.state = 591; + this.match(esql_parser.SHOW); + this.state = 592; + this.match(esql_parser.FUNCTIONS); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { + switch (ruleIndex) { + case 1: + return this.query_sempred(_localctx as QueryContext, predIndex); + + case 8: + return this.whereBooleanExpression_sempred(_localctx as WhereBooleanExpressionContext, predIndex); + + case 9: + return this.booleanExpression_sempred(_localctx as BooleanExpressionContext, predIndex); + + case 16: + return this.operatorExpression_sempred(_localctx as OperatorExpressionContext, predIndex); + } + return true; + } + private query_sempred(_localctx: QueryContext, predIndex: number): boolean { + switch (predIndex) { + case 0: + return this.precpred(this._ctx, 1); + } + return true; + } + private whereBooleanExpression_sempred(_localctx: WhereBooleanExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 1: + return this.precpred(this._ctx, 5); + + case 2: + return this.precpred(this._ctx, 4); + } + return true; + } + private booleanExpression_sempred(_localctx: BooleanExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 3: + return this.precpred(this._ctx, 2); + + case 4: + return this.precpred(this._ctx, 1); + } + return true; + } + private operatorExpression_sempred(_localctx: OperatorExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 5: + return this.precpred(this._ctx, 2); + + case 6: + return this.precpred(this._ctx, 1); + } + return true; + } + + private static readonly _serializedATNSegments: number = 2; + private static readonly _serializedATNSegment0: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03S\u0256\x04\x02" + + "\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04\x07" + + "\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r\t\r\x04" + + "\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12\x04" + + "\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17\x04" + + "\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C\x04" + + "\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t\"\x04#" + + "\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x04)\t)\x04*\t*\x04+\t+" + + "\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041\t1\x042\t2\x043\t3\x044" + + "\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04;\t;\x04<\t<\x04" + + "=\t=\x03\x02\x03\x02\x03\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x07\x03\x84\n\x03\f\x03\x0E\x03\x87\v\x03\x03\x04\x03\x04\x03\x04" + + "\x03\x04\x05\x04\x8D\n\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03" + + "\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x05\x05\x9C" + + "\n\x05\x03\x06\x03\x06\x03\x06\x03\x06\x05\x06\xA2\n\x06\x03\x06\x03\x06" + + "\x03\x06\x03\x06\x07\x06\xA8\n\x06\f\x06\x0E\x06\xAB\v\x06\x05\x06\xAD" + + "\n\x06\x03\x07\x03\x07\x03\x07\x05\x07\xB2\n\x07\x03\x07\x03\x07\x03\b" + + "\x03\b\x03\b\x03\t\x03\t\x03\t\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03" + + "\n\x05\n\xC3\n\n\x03\n\x03\n\x03\n\x03\n\x03\n\x07\n\xCA\n\n\f\n\x0E\n" + + "\xCD\v\n\x03\n\x03\n\x03\n\x05\n\xD2\n\n\x03\n\x03\n\x03\n\x03\n\x03\n" + + "\x07\n\xD9\n\n\f\n\x0E\n\xDC\v\n\x05\n\xDE\n\n\x03\n\x03\n\x03\n\x03\n" + + "\x03\n\x05\n\xE5\n\n\x03\n\x03\n\x05\n\xE9\n\n\x03\n\x03\n\x03\n\x03\n" + + "\x03\n\x03\n\x07\n\xF1\n\n\f\n\x0E\n\xF4\v\n\x03\v\x03\v\x03\v\x03\v\x05" + + "\v\xFA\n\v\x03\v\x03\v\x03\v\x03\v\x03\v\x03\v\x07\v\u0102\n\v\f\v\x0E" + + "\v\u0105\v\v\x03\f\x03\f\x05\f\u0109\n\f\x03\f\x03\f\x03\f\x03\f\x03\f" + + "\x05\f\u0110\n\f\x03\f\x03\f\x03\f\x05\f\u0115\n\f\x03\r\x03\r\x05\r\u0119" + + "\n\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03" + + "\x0F\x07\x0F\u0124\n\x0F\f\x0F\x0E\x0F\u0127\v\x0F\x05\x0F\u0129\n\x0F" + + "\x03\x0F\x03\x0F\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x07\x10\u0132" + + "\n\x10\f\x10\x0E\x10\u0135\v\x10\x05\x10\u0137\n\x10\x03\x10\x03\x10\x03" + + "\x11\x03\x11\x03\x11\x03\x12\x03\x12\x03\x12\x03\x12\x03\x12\x03\x12\x05" + + "\x12\u0144\n\x12\x03\x12\x03\x12\x03\x12\x03\x12\x03\x12\x03\x12\x07\x12" + + "\u014C\n\x12\f\x12\x0E\x12\u014F\v\x12\x03\x13\x03\x13\x03\x13\x03\x13" + + "\x03\x13\x03\x13\x03\x13\x03\x13\x03\x13\x03\x13\x03\x13\x03\x13\x07\x13" + + "\u015D\n\x13\f\x13\x0E\x13\u0160\v\x13\x05\x13\u0162\n\x13\x03\x13\x03" + + "\x13\x05\x13\u0166\n\x13\x03\x14\x03\x14\x03\x14\x03\x15\x03\x15\x03\x15" + + "\x07\x15\u016E\n\x15\f\x15\x0E\x15\u0171\v\x15\x03\x16\x03\x16\x03\x16" + + "\x03\x16\x03\x16\x05\x16\u0178\n\x16\x03\x17\x03\x17\x03\x18\x03\x18\x03" + + "\x19\x03\x19\x03\x19\x03\x19\x07\x19\u0182\n\x19\f\x19\x0E\x19\u0185\v" + + "\x19\x03\x19\x05\x19\u0188\n\x19\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A" + + "\x07\x1A\u018F\n\x1A\f\x1A\x0E\x1A\u0192\v\x1A\x03\x1A\x03\x1A\x03\x1B" + + "\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x05\x1C\u019B\n\x1C\x03\x1C\x03\x1C\x05" + + "\x1C\u019F\n\x1C\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F" + + "\x05\x1F\u01A8\n\x1F\x03 \x03 \x03 \x03 \x03 \x03 \x05 \u01B0\n \x03!" + + "\x03!\x03!\x07!\u01B5\n!\f!\x0E!\u01B8\v!\x03\"\x03\"\x03\"\x07\"\u01BD" + + "\n\"\f\"\x0E\"\u01C0\v\"\x03#\x03#\x03$\x03$\x03%\x03%\x03&\x03&\x03&" + + "\x03&\x03&\x03&\x03&\x03&\x07&\u01D0\n&\f&\x0E&\u01D3\v&\x03&\x03&\x03" + + "&\x03&\x03&\x03&\x07&\u01DB\n&\f&\x0E&\u01DE\v&\x03&\x03&\x03&\x03&\x03" + + "&\x03&\x07&\u01E6\n&\f&\x0E&\u01E9\v&\x03&\x03&\x05&\u01ED\n&\x03\'\x03" + + "\'\x05\'\u01F1\n\'\x03(\x03(\x03(\x03)\x03)\x03)\x03)\x07)\u01FA\n)\f" + + ")\x0E)\u01FD\v)\x03*\x03*\x05*\u0201\n*\x03*\x03*\x05*\u0205\n*\x03+\x03" + + "+\x03+\x03,\x03,\x03,\x03-\x03-\x03-\x03.\x03.\x03.\x07.\u0213\n.\f.\x0E" + + ".\u0216\v.\x03/\x03/\x03/\x03/\x07/\u021C\n/\f/\x0E/\u021F\v/\x030\x03" + + "0\x030\x030\x031\x031\x031\x031\x051\u0229\n1\x032\x032\x032\x032\x03" + + "3\x033\x033\x073\u0232\n3\f3\x0E3\u0235\v3\x034\x034\x034\x034\x035\x03" + + "5\x036\x036\x056\u023F\n6\x037\x037\x038\x038\x039\x039\x03:\x03:\x03" + + ";\x03;\x03;\x03<\x03<\x03<\x03<\x03=\x03=\x03=\x03=\x05=\u0254\n=\x03" + + "=\x02\x02\x06\x04\x12\x14\">\x02\x02\x04\x02\x06\x02\b\x02\n\x02\f\x02" + + "\x0E\x02\x10\x02\x12\x02\x14\x02\x16\x02\x18\x02\x1A\x02\x1C\x02\x1E\x02" + + " \x02\"\x02$\x02&\x02(\x02*\x02,\x02.\x020\x022\x024\x026\x028\x02:\x02" + + "<\x02>\x02@\x02B\x02D\x02F\x02H\x02J\x02L\x02N\x02P\x02R\x02T\x02V\x02" + + "X\x02Z\x02\\\x02^\x02`\x02b\x02d\x02f\x02h\x02j\x02l\x02n\x02p\x02r\x02" + + "t\x02v\x02x\x02\x02\x07\x03\x0256\x03\x0279\x03\x02NO\x03\x02GH\x04\x02" + + "77AB\x02\u0273\x02z\x03\x02\x02\x02\x04}\x03\x02\x02\x02\x06\x8C\x03\x02" + + "\x02\x02\b\x9B\x03\x02\x02\x02\n\x9D\x03\x02\x02\x02\f\xB1\x03\x02\x02" + + "\x02\x0E\xB5\x03\x02\x02\x02\x10\xB8\x03\x02\x02\x02\x12\xE8\x03\x02\x02" + + "\x02\x14\xF9\x03\x02\x02\x02\x16\u0114\x03\x02\x02\x02\x18\u0118\x03\x02" + + "\x02\x02\x1A\u011A\x03\x02\x02\x02\x1C\u011E\x03\x02\x02\x02\x1E\u012C" + + "\x03\x02\x02\x02 \u013A\x03\x02\x02\x02\"\u0143\x03\x02\x02\x02$\u0165" + + "\x03\x02\x02\x02&\u0167\x03\x02\x02\x02(\u016A\x03\x02\x02\x02*\u0177" + + "\x03\x02\x02\x02,\u0179\x03\x02\x02\x02.\u017B\x03\x02\x02\x020\u017D" + + "\x03\x02\x02\x022\u0189\x03\x02\x02\x024\u0195\x03\x02\x02\x026\u0198" + + "\x03\x02\x02\x028\u01A0\x03\x02\x02\x02:\u01A2\x03\x02\x02\x02<\u01A7" + + "\x03\x02\x02\x02>\u01AF\x03\x02\x02\x02@\u01B1\x03\x02\x02\x02B\u01B9" + + "\x03\x02\x02\x02D\u01C1\x03\x02\x02\x02F\u01C3\x03\x02\x02\x02H\u01C5" + + "\x03\x02\x02\x02J\u01EC\x03\x02\x02\x02L\u01F0\x03\x02\x02\x02N\u01F2" + + "\x03\x02\x02\x02P\u01F5\x03\x02\x02\x02R\u01FE\x03\x02\x02\x02T\u0206" + + "\x03\x02\x02\x02V\u0209\x03\x02\x02\x02X\u020C\x03\x02\x02\x02Z\u020F" + + "\x03\x02\x02\x02\\\u0217\x03\x02\x02\x02^\u0220\x03\x02\x02\x02`\u0224" + + "\x03\x02\x02\x02b\u022A\x03\x02\x02\x02d\u022E\x03\x02\x02\x02f\u0236" + + "\x03\x02\x02\x02h\u023A\x03\x02\x02\x02j\u023E\x03\x02\x02\x02l\u0240" + + "\x03\x02\x02\x02n\u0242\x03\x02\x02\x02p\u0244\x03\x02\x02\x02r\u0246" + + "\x03\x02\x02\x02t\u0248\x03\x02\x02\x02v\u024B\x03\x02\x02\x02x\u0253" + + "\x03\x02\x02\x02z{\x05\x04\x03\x02{|\x07\x02\x02\x03|\x03\x03\x02\x02" + + "\x02}~\b\x03\x01\x02~\x7F\x05\x06\x04\x02\x7F\x85\x03\x02\x02\x02\x80" + + "\x81\f\x03\x02\x02\x81\x82\x07\x1A\x02\x02\x82\x84\x05\b\x05\x02\x83\x80" + + "\x03\x02\x02\x02\x84\x87\x03\x02\x02\x02\x85\x83\x03\x02\x02\x02\x85\x86" + + "\x03\x02\x02\x02\x86\x05\x03\x02\x02\x02\x87\x85\x03\x02\x02\x02\x88\x8D" + + "\x05t;\x02\x89\x8D\x050\x19\x02\x8A\x8D\x05&\x14\x02\x8B\x8D\x05x=\x02" + + "\x8C\x88\x03\x02\x02\x02\x8C\x89\x03\x02\x02\x02\x8C\x8A\x03\x02\x02\x02" + + "\x8C\x8B\x03\x02\x02\x02\x8D\x07\x03\x02\x02\x02\x8E\x9C\x054\x1B\x02" + + "\x8F\x9C\x05N(\x02\x90\x9C\x05T+\x02\x91\x9C\x05V,\x02\x92\x9C\x05\\/" + + "\x02\x93\x9C\x05X-\x02\x94\x9C\x05`1\x02\x95\x9C\x05b2\x02\x96\x9C\x05" + + "P)\x02\x97\x9C\x056\x1C\x02\x98\x9C\x05\x10\t\x02\x99\x9C\x05\x0E\b\x02" + + "\x9A\x9C\x05\n\x06\x02\x9B\x8E\x03\x02\x02\x02\x9B\x8F\x03\x02\x02\x02" + + "\x9B\x90\x03\x02\x02\x02\x9B\x91\x03\x02\x02\x02\x9B\x92\x03\x02\x02\x02" + + "\x9B\x93\x03\x02\x02\x02\x9B\x94\x03\x02\x02\x02\x9B\x95\x03\x02\x02\x02" + + "\x9B\x96\x03\x02\x02\x02\x9B\x97\x03\x02\x02\x02\x9B\x98\x03\x02\x02\x02" + + "\x9B\x99\x03\x02\x02\x02\x9B\x9A\x03\x02\x02\x02\x9C\t\x03\x02\x02\x02" + + "\x9D\x9E\x07\x12\x02\x02\x9E\xA1\x05:\x1E\x02\x9F\xA0\x07L\x02\x02\xA0" + + "\xA2\x05,\x17\x02\xA1\x9F\x03\x02\x02\x02\xA1\xA2\x03\x02\x02\x02\xA2" + + "\xAC\x03\x02\x02\x02\xA3\xA4\x07M\x02\x02\xA4\xA9\x05\f\x07\x02\xA5\xA6" + + "\x07\"\x02\x02\xA6\xA8\x05\f\x07\x02\xA7\xA5\x03\x02\x02\x02\xA8\xAB\x03" + + "\x02\x02\x02\xA9\xA7\x03\x02\x02\x02\xA9\xAA\x03\x02\x02\x02\xAA\xAD\x03" + + "\x02\x02\x02\xAB\xA9\x03\x02\x02\x02\xAC\xA3\x03\x02\x02\x02\xAC\xAD\x03" + + "\x02\x02\x02\xAD\v\x03\x02\x02\x02\xAE\xAF\x05,\x17\x02\xAF\xB0\x07!\x02" + + "\x02\xB0\xB2\x03\x02\x02\x02\xB1\xAE\x03\x02\x02\x02\xB1\xB2\x03\x02\x02" + + "\x02\xB2\xB3\x03\x02\x02\x02\xB3\xB4\x05,\x17\x02\xB4\r\x03\x02\x02\x02" + + "\xB5\xB6\x07\f\x02\x02\xB6\xB7\x05B\"\x02\xB7\x0F\x03\x02\x02\x02\xB8" + + "\xB9\x07\n\x02\x02\xB9\xBA\x05\x12\n\x02\xBA\x11\x03\x02\x02\x02\xBB\xBC" + + "\b\n\x01\x02\xBC\xBD\x07\'\x02\x02\xBD\xE9\x05\x12\n\n\xBE\xE9\x05\x18" + + "\r\x02\xBF\xE9\x05\x16\f\x02\xC0\xC2\x05\x18\r\x02\xC1\xC3\x07\'\x02\x02" + + "\xC2\xC1\x03\x02\x02\x02\xC2\xC3\x03\x02\x02\x02\xC3\xC4\x03\x02\x02\x02" + + "\xC4\xC5\x07*\x02\x02\xC5\xC6\x07$\x02\x02\xC6\xCB\x05\x18\r\x02\xC7\xC8" + + "\x07\"\x02\x02\xC8\xCA\x05\x18\r\x02\xC9\xC7\x03\x02\x02\x02\xCA\xCD\x03" + + "\x02\x02\x02\xCB\xC9\x03\x02\x02\x02\xCB\xCC\x03\x02\x02\x02\xCC\xCE\x03" + + "\x02\x02\x02\xCD\xCB\x03\x02\x02\x02\xCE\xCF\x07/\x02\x02\xCF\xE9\x03" + + "\x02\x02\x02\xD0\xD2\x07\'\x02\x02\xD1\xD0\x03\x02\x02\x02\xD1\xD2\x03" + + "\x02\x02\x02\xD2\xD3\x03\x02\x02\x02\xD3\xD4\x07@\x02\x02\xD4\xD5\x07" + + "$\x02\x02\xD5\xDD\x05@!\x02\xD6\xD7\x07\"\x02\x02\xD7\xD9\x05<\x1F\x02" + + "\xD8\xD6\x03\x02\x02\x02\xD9\xDC\x03\x02\x02\x02\xDA\xD8\x03\x02\x02\x02" + + "\xDA\xDB\x03\x02\x02\x02\xDB\xDE\x03\x02\x02\x02\xDC\xDA\x03\x02\x02\x02" + + "\xDD\xDA\x03\x02\x02\x02\xDD\xDE\x03\x02\x02\x02\xDE\xDF\x03\x02\x02\x02" + + "\xDF\xE0\x07/\x02\x02\xE0\xE9\x03\x02\x02\x02\xE1\xE2\x05\x18\r\x02\xE2" + + "\xE4\x07+\x02\x02\xE3\xE5\x07\'\x02\x02\xE4\xE3\x03\x02\x02\x02\xE4\xE5" + + "\x03\x02\x02\x02\xE5\xE6\x03\x02\x02\x02\xE6\xE7\x07-\x02\x02\xE7\xE9" + + "\x03\x02\x02\x02\xE8\xBB\x03\x02\x02\x02\xE8\xBE\x03\x02\x02\x02\xE8\xBF" + + "\x03\x02\x02\x02\xE8\xC0\x03\x02\x02\x02\xE8\xD1\x03\x02\x02\x02\xE8\xE1" + + "\x03\x02\x02\x02\xE9\xF2\x03\x02\x02\x02\xEA\xEB\f\x07\x02\x02\xEB\xEC" + + "\x07 \x02\x02\xEC\xF1\x05\x12\n\b\xED\xEE\f\x06\x02\x02\xEE\xEF\x07.\x02" + + "\x02\xEF\xF1\x05\x12\n\x07\xF0\xEA\x03\x02\x02\x02\xF0\xED\x03\x02\x02" + + "\x02\xF1\xF4\x03\x02\x02\x02\xF2\xF0\x03\x02\x02\x02\xF2\xF3\x03\x02\x02" + + "\x02\xF3\x13\x03\x02\x02\x02\xF4\xF2\x03\x02\x02\x02\xF5\xF6\b\v\x01\x02" + + "\xF6\xF7\x07\'\x02\x02\xF7\xFA\x05\x14\v\x06\xF8\xFA\x05\x18\r\x02\xF9" + + "\xF5\x03\x02\x02\x02\xF9\xF8\x03\x02\x02\x02\xFA\u0103\x03\x02\x02\x02" + + "\xFB\xFC\f\x04\x02\x02\xFC\xFD\x07 \x02\x02\xFD\u0102\x05\x14\v\x05\xFE" + + "\xFF\f\x03\x02\x02\xFF\u0100\x07.\x02\x02\u0100\u0102\x05\x14\v\x04\u0101" + + "\xFB\x03\x02\x02\x02\u0101\xFE\x03\x02\x02\x02\u0102\u0105\x03\x02\x02" + + "\x02\u0103\u0101\x03\x02\x02\x02\u0103\u0104\x03\x02\x02\x02\u0104\x15" + + "\x03\x02\x02\x02\u0105\u0103\x03\x02\x02\x02\u0106\u0108\x05\x18\r\x02" + + "\u0107\u0109\x07\'\x02\x02\u0108\u0107\x03\x02\x02\x02\u0108\u0109\x03" + + "\x02\x02\x02\u0109\u010A\x03\x02\x02\x02\u010A\u010B\x07(\x02\x02\u010B" + + "\u010C\x05p9\x02\u010C\u0115\x03\x02\x02\x02\u010D\u010F\x05\x18\r\x02" + + "\u010E\u0110\x07\'\x02\x02\u010F\u010E\x03\x02\x02\x02\u010F\u0110\x03" + + "\x02\x02\x02\u0110\u0111\x03\x02\x02\x02\u0111\u0112\x07)\x02\x02\u0112" + + "\u0113\x05p9\x02\u0113\u0115\x03\x02\x02\x02\u0114\u0106\x03\x02\x02\x02" + + "\u0114\u010D\x03\x02\x02\x02\u0115\x17\x03\x02\x02\x02\u0116\u0119\x05" + + "\"\x12\x02\u0117\u0119\x05\x1A\x0E\x02\u0118\u0116\x03\x02\x02\x02\u0118" + + "\u0117\x03\x02\x02\x02\u0119\x19\x03\x02\x02\x02\u011A\u011B\x05\"\x12" + + "\x02\u011B\u011C\x05r:\x02\u011C\u011D\x05\"\x12\x02\u011D\x1B\x03\x02" + + "\x02\x02\u011E\u011F\x05H%\x02\u011F\u0128\x07$\x02\x02\u0120\u0125\x05" + + "<\x1F\x02\u0121\u0122\x07\"\x02\x02\u0122\u0124\x05<\x1F\x02\u0123\u0121" + + "\x03\x02\x02\x02\u0124\u0127\x03\x02\x02\x02\u0125\u0123\x03\x02\x02\x02" + + "\u0125\u0126\x03\x02\x02\x02\u0126\u0129\x03\x02\x02\x02\u0127\u0125\x03" + + "\x02\x02\x02\u0128\u0120\x03\x02\x02\x02\u0128\u0129\x03\x02\x02\x02\u0129" + + "\u012A\x03\x02\x02\x02\u012A\u012B\x07/\x02\x02\u012B\x1D\x03\x02\x02" + + "\x02\u012C\u012D\x05F$\x02\u012D\u0136\x07$\x02\x02\u012E\u0133\x05> " + + "\x02\u012F\u0130\x07\"\x02\x02\u0130\u0132\x05> \x02\u0131\u012F\x03\x02" + + "\x02\x02\u0132\u0135\x03\x02\x02\x02\u0133\u0131\x03\x02\x02\x02\u0133" + + "\u0134\x03\x02\x02\x02\u0134\u0137\x03\x02\x02\x02\u0135\u0133\x03\x02" + + "\x02\x02\u0136\u012E\x03\x02\x02\x02\u0136\u0137\x03\x02\x02\x02\u0137" + + "\u0138\x03\x02\x02\x02\u0138\u0139\x07/\x02\x02\u0139\x1F\x03\x02\x02" + + "\x02\u013A\u013B\x05j6\x02\u013B\u013C\x07\x1F\x02\x02\u013C!\x03\x02" + + "\x02\x02\u013D\u013E\b\x12\x01\x02\u013E\u0144\x05$\x13\x02\u013F\u0144" + + "\x05\x1C\x0F\x02\u0140\u0144\x05\x1E\x10\x02\u0141\u0142\t\x02\x02\x02" + + "\u0142\u0144\x05\"\x12\x05\u0143\u013D\x03\x02\x02\x02\u0143\u013F\x03" + + "\x02\x02\x02\u0143\u0140\x03\x02\x02\x02\u0143\u0141\x03\x02\x02\x02\u0144" + + "\u014D\x03\x02\x02\x02\u0145\u0146\f\x04\x02\x02\u0146\u0147\t\x03\x02" + + "\x02\u0147\u014C\x05\"\x12\x05\u0148\u0149\f\x03\x02\x02\u0149\u014A\t" + + "\x02\x02\x02\u014A\u014C\x05\"\x12\x04\u014B\u0145\x03\x02\x02\x02\u014B" + + "\u0148\x03\x02\x02\x02\u014C\u014F\x03\x02\x02\x02\u014D\u014B\x03\x02" + + "\x02\x02\u014D\u014E\x03\x02\x02\x02\u014E#\x03\x02\x02\x02\u014F\u014D" + + "\x03\x02\x02\x02\u0150\u0166\x05J&\x02\u0151\u0166\x05@!\x02\u0152\u0166" + + "\x05 \x11\x02\u0153\u0154\x07$\x02\x02\u0154\u0155\x05\x14\v\x02\u0155" + + "\u0156\x07/\x02\x02\u0156\u0166\x03\x02\x02\x02\u0157\u0158\x05D#\x02" + + "\u0158\u0161\x07$\x02\x02\u0159\u015E\x05\x14\v\x02\u015A\u015B\x07\"" + + "\x02\x02\u015B\u015D\x05\x14\v\x02\u015C\u015A\x03\x02\x02\x02\u015D\u0160" + + "\x03\x02\x02\x02\u015E\u015C\x03\x02\x02\x02\u015E\u015F\x03\x02\x02\x02" + + "\u015F\u0162\x03\x02\x02\x02\u0160\u015E\x03\x02\x02\x02\u0161\u0159\x03" + + "\x02\x02\x02\u0161\u0162\x03\x02\x02\x02\u0162\u0163\x03\x02\x02\x02\u0163" + + "\u0164\x07/\x02\x02\u0164\u0166\x03\x02\x02\x02\u0165\u0150\x03\x02\x02" + + "\x02\u0165\u0151\x03\x02\x02\x02\u0165\u0152\x03\x02\x02\x02\u0165\u0153" + + "\x03\x02\x02\x02\u0165\u0157\x03\x02\x02\x02\u0166%\x03\x02\x02\x02\u0167" + + "\u0168\x07\b\x02\x02\u0168\u0169\x05(\x15\x02\u0169\'\x03\x02\x02\x02" + + "\u016A\u016F\x05*\x16\x02\u016B\u016C\x07\"\x02\x02\u016C\u016E\x05*\x16" + + "\x02\u016D\u016B\x03\x02\x02\x02\u016E\u0171\x03\x02\x02\x02\u016F\u016D" + + "\x03\x02\x02\x02\u016F\u0170\x03\x02\x02\x02\u0170)\x03\x02\x02\x02\u0171" + + "\u016F\x03\x02\x02\x02\u0172\u0178\x05\x14\v\x02\u0173\u0174\x05.\x18" + + "\x02\u0174\u0175\x07!\x02\x02\u0175\u0176\x05\x14\v\x02\u0176\u0178\x03" + + "\x02\x02\x02\u0177\u0172\x03\x02\x02\x02\u0177\u0173\x03\x02\x02\x02\u0178" + + "+\x03\x02\x02\x02\u0179\u017A\t\x04\x02\x02\u017A-\x03\x02\x02\x02\u017B" + + "\u017C\x05D#\x02\u017C/\x03\x02\x02\x02\u017D\u017E\x07\x07\x02\x02\u017E" + + "\u0183\x058\x1D\x02\u017F\u0180\x07\"\x02\x02\u0180\u0182\x058\x1D\x02" + + "\u0181\u017F\x03\x02\x02\x02\u0182\u0185\x03\x02\x02\x02\u0183\u0181\x03" + + "\x02\x02\x02\u0183\u0184\x03\x02\x02\x02\u0184\u0187\x03\x02\x02\x02\u0185" + + "\u0183\x03\x02\x02\x02\u0186\u0188\x052\x1A\x02\u0187\u0186\x03\x02\x02" + + "\x02\u0187\u0188\x03\x02\x02\x02\u01881\x03\x02\x02\x02\u0189\u018A\x07" + + "%\x02\x02\u018A\u018B\x07F\x02\x02\u018B\u0190\x058\x1D\x02\u018C\u018D" + + "\x07\"\x02\x02\u018D\u018F\x058\x1D\x02\u018E\u018C\x03\x02\x02\x02\u018F" + + "\u0192\x03\x02\x02\x02\u0190\u018E\x03\x02\x02\x02\u0190\u0191\x03\x02" + + "\x02\x02\u0191\u0193\x03\x02\x02\x02\u0192\u0190\x03\x02\x02\x02\u0193" + + "\u0194\x07&\x02\x02\u01943\x03\x02\x02\x02\u0195\u0196\x07\x05\x02\x02" + + "\u0196\u0197\x05(\x15\x02\u01975\x03\x02\x02\x02\u0198\u019A\x07\t\x02" + + "\x02\u0199\u019B\x05(\x15\x02\u019A\u0199\x03\x02\x02\x02\u019A\u019B" + + "\x03\x02\x02\x02\u019B\u019E\x03\x02\x02\x02\u019C\u019D\x07\x1E\x02\x02" + + "\u019D\u019F\x05B\"\x02\u019E\u019C\x03\x02\x02\x02\u019E\u019F\x03\x02" + + "\x02\x02\u019F7\x03\x02\x02\x02\u01A0\u01A1\t\x05\x02\x02\u01A19\x03\x02" + + "\x02\x02\u01A2\u01A3\t\x04\x02\x02\u01A3;\x03\x02\x02\x02\u01A4\u01A8" + + "\x05@!\x02\u01A5\u01A8\x05p9\x02\u01A6\u01A8\x05j6\x02\u01A7\u01A4\x03" + + "\x02\x02\x02\u01A7\u01A5\x03\x02\x02\x02\u01A7\u01A6\x03\x02\x02\x02\u01A8" + + "=\x03\x02\x02\x02\u01A9\u01B0\x05@!\x02\u01AA\u01B0\x05p9\x02\u01AB\u01B0" + + "\x05j6\x02\u01AC\u01B0\x05\"\x12\x02\u01AD\u01B0\x05 \x11\x02\u01AE\u01B0" + + "\x05\x1A\x0E\x02\u01AF\u01A9\x03\x02\x02\x02\u01AF\u01AA\x03\x02\x02\x02" + + "\u01AF\u01AB\x03\x02\x02\x02\u01AF\u01AC\x03\x02\x02\x02\u01AF\u01AD\x03" + + "\x02\x02\x02\u01AF\u01AE\x03\x02\x02\x02\u01B0?\x03\x02\x02\x02\u01B1" + + "\u01B6\x05D#\x02\u01B2\u01B3\x07#\x02\x02\u01B3\u01B5\x05D#\x02\u01B4" + + "\u01B2\x03\x02\x02\x02\u01B5\u01B8\x03\x02\x02\x02\u01B6\u01B4\x03\x02" + + "\x02\x02\u01B6\u01B7\x03\x02\x02\x02\u01B7A\x03\x02\x02\x02\u01B8\u01B6" + + "\x03\x02\x02\x02\u01B9\u01BE\x05@!\x02\u01BA\u01BB\x07\"\x02\x02\u01BB" + + "\u01BD\x05@!\x02\u01BC\u01BA\x03\x02\x02\x02\u01BD\u01C0\x03\x02\x02\x02" + + "\u01BE\u01BC\x03\x02\x02\x02\u01BE\u01BF\x03\x02\x02\x02\u01BFC\x03\x02" + + "\x02\x02\u01C0\u01BE\x03\x02\x02\x02\u01C1\u01C2\t\x06\x02\x02\u01C2E" + + "\x03\x02\x02\x02\u01C3\u01C4\x07>\x02\x02\u01C4G\x03\x02\x02\x02\u01C5" + + "\u01C6\x07?\x02\x02\u01C6I\x03\x02\x02\x02\u01C7\u01ED\x07-\x02\x02\u01C8" + + "\u01ED\x05L\'\x02\u01C9\u01ED\x05h5\x02\u01CA\u01ED\x05p9\x02\u01CB\u01CC" + + "\x07%\x02\x02\u01CC\u01D1\x05L\'\x02\u01CD\u01CE\x07\"\x02\x02\u01CE\u01D0" + + "\x05L\'\x02\u01CF\u01CD\x03\x02\x02\x02\u01D0\u01D3\x03\x02\x02\x02\u01D1" + + "\u01CF\x03\x02\x02\x02\u01D1\u01D2\x03\x02\x02\x02\u01D2\u01D4\x03\x02" + + "\x02\x02\u01D3\u01D1\x03\x02\x02\x02\u01D4\u01D5\x07&\x02\x02\u01D5\u01ED" + + "\x03\x02\x02\x02\u01D6\u01D7\x07%\x02\x02\u01D7\u01DC\x05h5\x02\u01D8" + + "\u01D9\x07\"\x02\x02\u01D9\u01DB\x05h5\x02\u01DA\u01D8\x03\x02\x02\x02" + + "\u01DB\u01DE\x03\x02\x02\x02\u01DC\u01DA\x03\x02\x02\x02\u01DC\u01DD\x03" + + "\x02\x02\x02\u01DD\u01DF\x03\x02\x02\x02\u01DE\u01DC\x03\x02\x02\x02\u01DF" + + "\u01E0\x07&\x02\x02\u01E0\u01ED\x03\x02\x02\x02\u01E1\u01E2\x07%\x02\x02" + + "\u01E2\u01E7\x05p9\x02\u01E3\u01E4\x07\"\x02\x02\u01E4\u01E6\x05p9\x02" + + "\u01E5\u01E3\x03\x02\x02\x02\u01E6\u01E9\x03\x02\x02\x02\u01E7\u01E5\x03" + + "\x02\x02\x02\u01E7\u01E8\x03\x02\x02\x02\u01E8\u01EA\x03\x02\x02\x02\u01E9" + + "\u01E7\x03\x02\x02\x02\u01EA\u01EB\x07&\x02\x02\u01EB\u01ED\x03\x02\x02" + + "\x02\u01EC\u01C7\x03\x02\x02\x02\u01EC\u01C8\x03\x02\x02\x02\u01EC\u01C9" + + "\x03\x02\x02\x02\u01EC\u01CA\x03\x02\x02\x02\u01EC\u01CB\x03\x02\x02\x02" + + "\u01EC\u01D6\x03\x02\x02\x02\u01EC\u01E1\x03\x02\x02\x02\u01EDK\x03\x02" + + "\x02\x02\u01EE\u01F1\x05l7\x02\u01EF\u01F1\x05n8\x02\u01F0\u01EE\x03\x02" + + "\x02\x02\u01F0\u01EF\x03\x02\x02\x02\u01F1M\x03\x02\x02\x02\u01F2\u01F3" + + "\x07\r\x02\x02\u01F3\u01F4\x07\x1C\x02\x02\u01F4O\x03\x02\x02\x02\u01F5" + + "\u01F6\x07\v\x02\x02\u01F6\u01FB\x05R*\x02\u01F7\u01F8\x07\"\x02\x02\u01F8" + + "\u01FA\x05R*\x02\u01F9\u01F7\x03\x02\x02\x02\u01FA\u01FD\x03\x02\x02\x02" + + "\u01FB\u01F9\x03\x02\x02\x02\u01FB\u01FC\x03\x02\x02\x02\u01FCQ\x03\x02" + + "\x02\x02\u01FD\u01FB\x03\x02\x02\x02\u01FE\u0200\x05\x14\v\x02\u01FF\u0201" + + "\x07;\x02\x02\u0200\u01FF\x03\x02\x02\x02\u0200\u0201\x03\x02\x02\x02" + + "\u0201\u0204\x03\x02\x02\x02\u0202\u0203\x07<\x02\x02\u0203\u0205\x07" + + "=\x02\x02\u0204\u0202\x03\x02\x02\x02\u0204\u0205\x03\x02\x02\x02\u0205" + + "S\x03\x02\x02\x02\u0206\u0207\x07\x0E\x02\x02\u0207\u0208\x05B\"\x02\u0208" + + "U\x03\x02\x02\x02\u0209\u020A\x07\x13\x02\x02\u020A\u020B\x05B\"\x02\u020B" + + "W\x03\x02\x02\x02\u020C\u020D\x07\x0F\x02\x02\u020D\u020E\x05B\"\x02\u020E" + + "Y\x03\x02\x02\x02\u020F\u0214\x05D#\x02\u0210\u0211\x07#\x02\x02\u0211" + + "\u0213\x05D#\x02\u0212\u0210\x03\x02\x02\x02\u0213\u0216\x03\x02\x02\x02" + + "\u0214\u0212\x03\x02\x02\x02\u0214\u0215\x03\x02\x02\x02\u0215[\x03\x02" + + "\x02\x02\u0216\u0214\x03\x02\x02\x02\u0217\u0218\x07\x10\x02\x02\u0218" + + "\u021D\x05^0\x02\u0219\u021A\x07\"\x02\x02\u021A\u021C\x05^0\x02\u021B" + + "\u0219\x03\x02\x02\x02\u021C\u021F\x03\x02\x02\x02\u021D\u021B\x03\x02" + + "\x02\x02\u021D\u021E\x03\x02\x02\x02\u021E]\x03\x02\x02\x02\u021F\u021D" + + "\x03\x02\x02\x02\u0220\u0221\x05@!\x02\u0221\u0222\x07,\x02\x02\u0222" + + "\u0223\x05Z.\x02\u0223_\x03\x02\x02\x02\u0224\u0225\x07\x03\x02\x02\u0225" + + "\u0226\x05B\"\x02\u0226\u0228\x05p9\x02\u0227\u0229\x05d3\x02\u0228\u0227" + + "\x03\x02\x02\x02\u0228\u0229\x03\x02\x02\x02\u0229a\x03\x02\x02\x02\u022A" + + "\u022B\x07\x04\x02\x02\u022B\u022C\x05B\"\x02\u022C\u022D\x05p9\x02\u022D" + + "c\x03\x02\x02\x02\u022E\u0233\x05f4\x02\u022F\u0230\x07\"\x02\x02\u0230" + + "\u0232\x05f4\x02\u0231\u022F\x03\x02\x02\x02\u0232\u0235\x03\x02\x02\x02" + + "\u0233\u0231\x03\x02\x02\x02\u0233\u0234\x03\x02\x02\x02\u0234e\x03\x02" + + "\x02\x02\u0235\u0233\x03\x02\x02\x02\u0236\u0237\x05D#\x02\u0237\u0238" + + "\x07!\x02\x02\u0238"; + private static readonly _serializedATNSegment1: string = + "\u0239\x05J&\x02\u0239g\x03\x02\x02\x02\u023A\u023B\x073\x02\x02\u023B" + + "i\x03\x02\x02\x02\u023C\u023F\x07\x1D\x02\x02\u023D\u023F\x07\x1C\x02" + + "\x02\u023E\u023C\x03\x02\x02\x02\u023E\u023D\x03\x02\x02\x02\u023Fk\x03" + + "\x02\x02\x02\u0240\u0241\x07\x1D\x02\x02\u0241m\x03\x02\x02\x02\u0242" + + "\u0243\x07\x1C\x02\x02\u0243o\x03\x02\x02\x02\u0244\u0245\x07\x1B\x02" + + "\x02\u0245q\x03\x02\x02\x02\u0246\u0247\x074\x02\x02\u0247s\x03\x02\x02" + + "\x02\u0248\u0249\x07\x06\x02\x02\u0249\u024A\x05v<\x02\u024Au\x03\x02" + + "\x02\x02\u024B\u024C\x07%\x02\x02\u024C\u024D\x05\x04\x03\x02\u024D\u024E" + + "\x07&\x02\x02\u024Ew\x03\x02\x02\x02\u024F\u0250\x07\x11\x02\x02\u0250" + + "\u0254\x071\x02\x02\u0251\u0252\x07\x11\x02\x02\u0252\u0254\x072\x02\x02" + + "\u0253\u024F\x03\x02\x02\x02\u0253\u0251\x03\x02\x02\x02\u0254y\x03\x02" + + "\x02\x02<\x85\x8C\x9B\xA1\xA9\xAC\xB1\xC2\xCB\xD1\xDA\xDD\xE4\xE8\xF0" + + "\xF2\xF9\u0101\u0103\u0108\u010F\u0114\u0118\u0125\u0128\u0133\u0136\u0143" + + "\u014B\u014D\u015E\u0161\u0165\u016F\u0177\u0183\u0187\u0190\u019A\u019E" + + "\u01A7\u01AF\u01B6\u01BE\u01D1\u01DC\u01E7\u01EC\u01F0\u01FB\u0200\u0204" + + "\u0214\u021D\u0228\u0233\u023E\u0253"; + public static readonly _serializedATN: string = Utils.join( + [ + esql_parser._serializedATNSegment0, + esql_parser._serializedATNSegment1, + ], + "", + ); + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!esql_parser.__ATN) { + esql_parser.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(esql_parser._serializedATN)); + } + + return esql_parser.__ATN; + } + +} + +export class SingleStatementContext extends ParserRuleContext { + public query(): QueryContext { + return this.getRuleContext(0, QueryContext); + } + public EOF(): TerminalNode { return this.getToken(esql_parser.EOF, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_singleStatement; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSingleStatement) { + listener.enterSingleStatement(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSingleStatement) { + listener.exitSingleStatement(this); + } + } +} + + +export class QueryContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_query; } + public copyFrom(ctx: QueryContext): void { + super.copyFrom(ctx); + } +} +export class SingleCommandQueryContext extends QueryContext { + public sourceCommand(): SourceCommandContext { + return this.getRuleContext(0, SourceCommandContext); + } + constructor(ctx: QueryContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSingleCommandQuery) { + listener.enterSingleCommandQuery(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSingleCommandQuery) { + listener.exitSingleCommandQuery(this); + } + } +} +export class CompositeQueryContext extends QueryContext { public query(): QueryContext { return this.getRuleContext(0, QueryContext); } - public EOF(): TerminalNode { return this.getToken(esql_parser.EOF, 0); } + public PIPE(): TerminalNode { return this.getToken(esql_parser.PIPE, 0); } + public processingCommand(): ProcessingCommandContext { + return this.getRuleContext(0, ProcessingCommandContext); + } + constructor(ctx: QueryContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterCompositeQuery) { + listener.enterCompositeQuery(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitCompositeQuery) { + listener.exitCompositeQuery(this); + } + } +} + + +export class SourceCommandContext extends ParserRuleContext { + public explainCommand(): ExplainCommandContext | undefined { + return this.tryGetRuleContext(0, ExplainCommandContext); + } + public fromCommand(): FromCommandContext | undefined { + return this.tryGetRuleContext(0, FromCommandContext); + } + public rowCommand(): RowCommandContext | undefined { + return this.tryGetRuleContext(0, RowCommandContext); + } + public showCommand(): ShowCommandContext | undefined { + return this.tryGetRuleContext(0, ShowCommandContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_sourceCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSourceCommand) { + listener.enterSourceCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSourceCommand) { + listener.exitSourceCommand(this); + } + } +} + + +export class ProcessingCommandContext extends ParserRuleContext { + public evalCommand(): EvalCommandContext | undefined { + return this.tryGetRuleContext(0, EvalCommandContext); + } + public limitCommand(): LimitCommandContext | undefined { + return this.tryGetRuleContext(0, LimitCommandContext); + } + public projectCommand(): ProjectCommandContext | undefined { + return this.tryGetRuleContext(0, ProjectCommandContext); + } + public keepCommand(): KeepCommandContext | undefined { + return this.tryGetRuleContext(0, KeepCommandContext); + } + public renameCommand(): RenameCommandContext | undefined { + return this.tryGetRuleContext(0, RenameCommandContext); + } + public dropCommand(): DropCommandContext | undefined { + return this.tryGetRuleContext(0, DropCommandContext); + } + public dissectCommand(): DissectCommandContext | undefined { + return this.tryGetRuleContext(0, DissectCommandContext); + } + public grokCommand(): GrokCommandContext | undefined { + return this.tryGetRuleContext(0, GrokCommandContext); + } + public sortCommand(): SortCommandContext | undefined { + return this.tryGetRuleContext(0, SortCommandContext); + } + public statsCommand(): StatsCommandContext | undefined { + return this.tryGetRuleContext(0, StatsCommandContext); + } + public whereCommand(): WhereCommandContext | undefined { + return this.tryGetRuleContext(0, WhereCommandContext); + } + public mvExpandCommand(): MvExpandCommandContext | undefined { + return this.tryGetRuleContext(0, MvExpandCommandContext); + } + public enrichCommand(): EnrichCommandContext | undefined { + return this.tryGetRuleContext(0, EnrichCommandContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_processingCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterProcessingCommand) { + listener.enterProcessingCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitProcessingCommand) { + listener.exitProcessingCommand(this); + } + } +} + + +export class EnrichCommandContext extends ParserRuleContext { + public _policyName: EnrichIdentifierContext; + public _matchField: EnrichFieldIdentifierContext; + public ENRICH(): TerminalNode { return this.getToken(esql_parser.ENRICH, 0); } + public enrichIdentifier(): EnrichIdentifierContext { + return this.getRuleContext(0, EnrichIdentifierContext); + } + public ON(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ON, 0); } + public WITH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.WITH, 0); } + public enrichWithClause(): EnrichWithClauseContext[]; + public enrichWithClause(i: number): EnrichWithClauseContext; + public enrichWithClause(i?: number): EnrichWithClauseContext | EnrichWithClauseContext[] { + if (i === undefined) { + return this.getRuleContexts(EnrichWithClauseContext); + } else { + return this.getRuleContext(i, EnrichWithClauseContext); + } + } + public enrichFieldIdentifier(): EnrichFieldIdentifierContext | undefined { + return this.tryGetRuleContext(0, EnrichFieldIdentifierContext); + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_enrichCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterEnrichCommand) { + listener.enterEnrichCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitEnrichCommand) { + listener.exitEnrichCommand(this); + } + } +} + + +export class EnrichWithClauseContext extends ParserRuleContext { + public _newName: EnrichFieldIdentifierContext; + public _enrichField: EnrichFieldIdentifierContext; + public enrichFieldIdentifier(): EnrichFieldIdentifierContext[]; + public enrichFieldIdentifier(i: number): EnrichFieldIdentifierContext; + public enrichFieldIdentifier(i?: number): EnrichFieldIdentifierContext | EnrichFieldIdentifierContext[] { + if (i === undefined) { + return this.getRuleContexts(EnrichFieldIdentifierContext); + } else { + return this.getRuleContext(i, EnrichFieldIdentifierContext); + } + } + public ASSIGN(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASSIGN, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_enrichWithClause; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterEnrichWithClause) { + listener.enterEnrichWithClause(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitEnrichWithClause) { + listener.exitEnrichWithClause(this); + } + } +} + + +export class MvExpandCommandContext extends ParserRuleContext { + public MV_EXPAND(): TerminalNode { return this.getToken(esql_parser.MV_EXPAND, 0); } + public qualifiedNames(): QualifiedNamesContext { + return this.getRuleContext(0, QualifiedNamesContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_mvExpandCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterMvExpandCommand) { + listener.enterMvExpandCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitMvExpandCommand) { + listener.exitMvExpandCommand(this); + } + } +} + + +export class WhereCommandContext extends ParserRuleContext { + public WHERE(): TerminalNode { return this.getToken(esql_parser.WHERE, 0); } + public whereBooleanExpression(): WhereBooleanExpressionContext { + return this.getRuleContext(0, WhereBooleanExpressionContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_whereCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterWhereCommand) { + listener.enterWhereCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitWhereCommand) { + listener.exitWhereCommand(this); + } + } +} + + +export class WhereBooleanExpressionContext extends ParserRuleContext { + public _left: WhereBooleanExpressionContext; + public _operator: Token; + public _right: WhereBooleanExpressionContext; + public NOT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NOT, 0); } + public whereBooleanExpression(): WhereBooleanExpressionContext[]; + public whereBooleanExpression(i: number): WhereBooleanExpressionContext; + public whereBooleanExpression(i?: number): WhereBooleanExpressionContext | WhereBooleanExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(WhereBooleanExpressionContext); + } else { + return this.getRuleContext(i, WhereBooleanExpressionContext); + } + } + public valueExpression(): ValueExpressionContext[]; + public valueExpression(i: number): ValueExpressionContext; + public valueExpression(i?: number): ValueExpressionContext | ValueExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ValueExpressionContext); + } else { + return this.getRuleContext(i, ValueExpressionContext); + } + } + public regexBooleanExpression(): RegexBooleanExpressionContext | undefined { + return this.tryGetRuleContext(0, RegexBooleanExpressionContext); + } + public AND(): TerminalNode | undefined { return this.tryGetToken(esql_parser.AND, 0); } + public OR(): TerminalNode | undefined { return this.tryGetToken(esql_parser.OR, 0); } + public IN(): TerminalNode | undefined { return this.tryGetToken(esql_parser.IN, 0); } + public LP(): TerminalNode | undefined { return this.tryGetToken(esql_parser.LP, 0); } + public RP(): TerminalNode | undefined { return this.tryGetToken(esql_parser.RP, 0); } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + public WHERE_FUNCTIONS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.WHERE_FUNCTIONS, 0); } + public qualifiedName(): QualifiedNameContext | undefined { + return this.tryGetRuleContext(0, QualifiedNameContext); + } + public functionExpressionArgument(): FunctionExpressionArgumentContext[]; + public functionExpressionArgument(i: number): FunctionExpressionArgumentContext; + public functionExpressionArgument(i?: number): FunctionExpressionArgumentContext | FunctionExpressionArgumentContext[] { + if (i === undefined) { + return this.getRuleContexts(FunctionExpressionArgumentContext); + } else { + return this.getRuleContext(i, FunctionExpressionArgumentContext); + } + } + public IS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.IS, 0); } + public NULL(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NULL, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_whereBooleanExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterWhereBooleanExpression) { + listener.enterWhereBooleanExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitWhereBooleanExpression) { + listener.exitWhereBooleanExpression(this); + } + } +} + + +export class BooleanExpressionContext extends ParserRuleContext { + public _left: BooleanExpressionContext; + public _operator: Token; + public _right: BooleanExpressionContext; + public NOT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NOT, 0); } + public booleanExpression(): BooleanExpressionContext[]; + public booleanExpression(i: number): BooleanExpressionContext; + public booleanExpression(i?: number): BooleanExpressionContext | BooleanExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(BooleanExpressionContext); + } else { + return this.getRuleContext(i, BooleanExpressionContext); + } + } + public valueExpression(): ValueExpressionContext | undefined { + return this.tryGetRuleContext(0, ValueExpressionContext); + } + public AND(): TerminalNode | undefined { return this.tryGetToken(esql_parser.AND, 0); } + public OR(): TerminalNode | undefined { return this.tryGetToken(esql_parser.OR, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_booleanExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterBooleanExpression) { + listener.enterBooleanExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitBooleanExpression) { + listener.exitBooleanExpression(this); + } + } +} + + +export class RegexBooleanExpressionContext extends ParserRuleContext { + public _kind: Token; + public _pattern: StringContext; + public valueExpression(): ValueExpressionContext { + return this.getRuleContext(0, ValueExpressionContext); + } + public LIKE(): TerminalNode | undefined { return this.tryGetToken(esql_parser.LIKE, 0); } + public string(): StringContext { + return this.getRuleContext(0, StringContext); + } + public NOT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NOT, 0); } + public RLIKE(): TerminalNode | undefined { return this.tryGetToken(esql_parser.RLIKE, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_singleStatement; } + public get ruleIndex(): number { return esql_parser.RULE_regexBooleanExpression; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterSingleStatement) { - listener.enterSingleStatement(this); + if (listener.enterRegexBooleanExpression) { + listener.enterRegexBooleanExpression(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitSingleStatement) { - listener.exitSingleStatement(this); + if (listener.exitRegexBooleanExpression) { + listener.exitRegexBooleanExpression(this); } } } -export class QueryContext extends ParserRuleContext { +export class ValueExpressionContext extends ParserRuleContext { + public operatorExpression(): OperatorExpressionContext | undefined { + return this.tryGetRuleContext(0, OperatorExpressionContext); + } + public comparison(): ComparisonContext | undefined { + return this.tryGetRuleContext(0, ComparisonContext); + } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_query; } - public copyFrom(ctx: QueryContext): void { - super.copyFrom(ctx); + public get ruleIndex(): number { return esql_parser.RULE_valueExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterValueExpression) { + listener.enterValueExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitValueExpression) { + listener.exitValueExpression(this); + } } } -export class SingleCommandQueryContext extends QueryContext { - public sourceCommand(): SourceCommandContext { - return this.getRuleContext(0, SourceCommandContext); + + +export class ComparisonContext extends ParserRuleContext { + public _left: OperatorExpressionContext; + public _right: OperatorExpressionContext; + public comparisonOperator(): ComparisonOperatorContext { + return this.getRuleContext(0, ComparisonOperatorContext); } - constructor(ctx: QueryContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); + public operatorExpression(): OperatorExpressionContext[]; + public operatorExpression(i: number): OperatorExpressionContext; + public operatorExpression(i?: number): OperatorExpressionContext | OperatorExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(OperatorExpressionContext); + } else { + return this.getRuleContext(i, OperatorExpressionContext); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); } // @Override + public get ruleIndex(): number { return esql_parser.RULE_comparison; } + // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterSingleCommandQuery) { - listener.enterSingleCommandQuery(this); + if (listener.enterComparison) { + listener.enterComparison(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitSingleCommandQuery) { - listener.exitSingleCommandQuery(this); + if (listener.exitComparison) { + listener.exitComparison(this); } } } -export class CompositeQueryContext extends QueryContext { - public query(): QueryContext { - return this.getRuleContext(0, QueryContext); + + +export class MathFnContext extends ParserRuleContext { + public functionIdentifier(): FunctionIdentifierContext { + return this.getRuleContext(0, FunctionIdentifierContext); } - public PIPE(): TerminalNode { return this.getToken(esql_parser.PIPE, 0); } - public processingCommand(): ProcessingCommandContext { - return this.getRuleContext(0, ProcessingCommandContext); + public LP(): TerminalNode { return this.getToken(esql_parser.LP, 0); } + public RP(): TerminalNode { return this.getToken(esql_parser.RP, 0); } + public functionExpressionArgument(): FunctionExpressionArgumentContext[]; + public functionExpressionArgument(i: number): FunctionExpressionArgumentContext; + public functionExpressionArgument(i?: number): FunctionExpressionArgumentContext | FunctionExpressionArgumentContext[] { + if (i === undefined) { + return this.getRuleContexts(FunctionExpressionArgumentContext); + } else { + return this.getRuleContext(i, FunctionExpressionArgumentContext); + } } - constructor(ctx: QueryContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_mathFn; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterCompositeQuery) { - listener.enterCompositeQuery(this); + if (listener.enterMathFn) { + listener.enterMathFn(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitCompositeQuery) { - listener.exitCompositeQuery(this); + if (listener.exitMathFn) { + listener.exitMathFn(this); + } + } +} + + +export class MathEvalFnContext extends ParserRuleContext { + public mathFunctionIdentifier(): MathFunctionIdentifierContext { + return this.getRuleContext(0, MathFunctionIdentifierContext); + } + public LP(): TerminalNode { return this.getToken(esql_parser.LP, 0); } + public RP(): TerminalNode { return this.getToken(esql_parser.RP, 0); } + public mathFunctionExpressionArgument(): MathFunctionExpressionArgumentContext[]; + public mathFunctionExpressionArgument(i: number): MathFunctionExpressionArgumentContext; + public mathFunctionExpressionArgument(i?: number): MathFunctionExpressionArgumentContext | MathFunctionExpressionArgumentContext[] { + if (i === undefined) { + return this.getRuleContexts(MathFunctionExpressionArgumentContext); + } else { + return this.getRuleContext(i, MathFunctionExpressionArgumentContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_mathEvalFn; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterMathEvalFn) { + listener.enterMathEvalFn(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitMathEvalFn) { + listener.exitMathEvalFn(this); + } + } +} + + +export class DateExpressionContext extends ParserRuleContext { + public _quantifier: NumberContext; + public DATE_LITERAL(): TerminalNode { return this.getToken(esql_parser.DATE_LITERAL, 0); } + public number(): NumberContext { + return this.getRuleContext(0, NumberContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_dateExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterDateExpression) { + listener.enterDateExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitDateExpression) { + listener.exitDateExpression(this); + } + } +} + + +export class OperatorExpressionContext extends ParserRuleContext { + public _left: OperatorExpressionContext; + public _operator: Token; + public _right: OperatorExpressionContext; + public primaryExpression(): PrimaryExpressionContext | undefined { + return this.tryGetRuleContext(0, PrimaryExpressionContext); + } + public mathFn(): MathFnContext | undefined { + return this.tryGetRuleContext(0, MathFnContext); + } + public mathEvalFn(): MathEvalFnContext | undefined { + return this.tryGetRuleContext(0, MathEvalFnContext); + } + public operatorExpression(): OperatorExpressionContext[]; + public operatorExpression(i: number): OperatorExpressionContext; + public operatorExpression(i?: number): OperatorExpressionContext | OperatorExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(OperatorExpressionContext); + } else { + return this.getRuleContext(i, OperatorExpressionContext); + } + } + public MINUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.MINUS, 0); } + public PLUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.PLUS, 0); } + public ASTERISK(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASTERISK, 0); } + public SLASH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SLASH, 0); } + public PERCENT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.PERCENT, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_operatorExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterOperatorExpression) { + listener.enterOperatorExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitOperatorExpression) { + listener.exitOperatorExpression(this); + } + } +} + + +export class PrimaryExpressionContext extends ParserRuleContext { + public constant(): ConstantContext | undefined { + return this.tryGetRuleContext(0, ConstantContext); + } + public qualifiedName(): QualifiedNameContext | undefined { + return this.tryGetRuleContext(0, QualifiedNameContext); + } + public dateExpression(): DateExpressionContext | undefined { + return this.tryGetRuleContext(0, DateExpressionContext); + } + public LP(): TerminalNode | undefined { return this.tryGetToken(esql_parser.LP, 0); } + public booleanExpression(): BooleanExpressionContext[]; + public booleanExpression(i: number): BooleanExpressionContext; + public booleanExpression(i?: number): BooleanExpressionContext | BooleanExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(BooleanExpressionContext); + } else { + return this.getRuleContext(i, BooleanExpressionContext); + } + } + public RP(): TerminalNode | undefined { return this.tryGetToken(esql_parser.RP, 0); } + public identifier(): IdentifierContext | undefined { + return this.tryGetRuleContext(0, IdentifierContext); + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_primaryExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterPrimaryExpression) { + listener.enterPrimaryExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitPrimaryExpression) { + listener.exitPrimaryExpression(this); } } } -export class SourceCommandContext extends ParserRuleContext { - public explainCommand(): ExplainCommandContext | undefined { - return this.tryGetRuleContext(0, ExplainCommandContext); - } - public fromCommand(): FromCommandContext | undefined { - return this.tryGetRuleContext(0, FromCommandContext); - } - public rowCommand(): RowCommandContext | undefined { - return this.tryGetRuleContext(0, RowCommandContext); +export class RowCommandContext extends ParserRuleContext { + public ROW(): TerminalNode { return this.getToken(esql_parser.ROW, 0); } + public fields(): FieldsContext { + return this.getRuleContext(0, FieldsContext); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_sourceCommand; } + public get ruleIndex(): number { return esql_parser.RULE_rowCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterSourceCommand) { - listener.enterSourceCommand(this); + if (listener.enterRowCommand) { + listener.enterRowCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitSourceCommand) { - listener.exitSourceCommand(this); + if (listener.exitRowCommand) { + listener.exitRowCommand(this); } } } -export class ProcessingCommandContext extends ParserRuleContext { - public evalCommand(): EvalCommandContext | undefined { - return this.tryGetRuleContext(0, EvalCommandContext); - } - public limitCommand(): LimitCommandContext | undefined { - return this.tryGetRuleContext(0, LimitCommandContext); - } - public projectCommand(): ProjectCommandContext | undefined { - return this.tryGetRuleContext(0, ProjectCommandContext); - } - public sortCommand(): SortCommandContext | undefined { - return this.tryGetRuleContext(0, SortCommandContext); - } - public statsCommand(): StatsCommandContext | undefined { - return this.tryGetRuleContext(0, StatsCommandContext); +export class FieldsContext extends ParserRuleContext { + public field(): FieldContext[]; + public field(i: number): FieldContext; + public field(i?: number): FieldContext | FieldContext[] { + if (i === undefined) { + return this.getRuleContexts(FieldContext); + } else { + return this.getRuleContext(i, FieldContext); + } } - public whereCommand(): WhereCommandContext | undefined { - return this.tryGetRuleContext(0, WhereCommandContext); + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_processingCommand; } + public get ruleIndex(): number { return esql_parser.RULE_fields; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterProcessingCommand) { - listener.enterProcessingCommand(this); + if (listener.enterFields) { + listener.enterFields(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitProcessingCommand) { - listener.exitProcessingCommand(this); + if (listener.exitFields) { + listener.exitFields(this); } } } -export class WhereCommandContext extends ParserRuleContext { - public WHERE(): TerminalNode { return this.getToken(esql_parser.WHERE, 0); } +export class FieldContext extends ParserRuleContext { public booleanExpression(): BooleanExpressionContext { return this.getRuleContext(0, BooleanExpressionContext); } + public userVariable(): UserVariableContext | undefined { + return this.tryGetRuleContext(0, UserVariableContext); + } + public ASSIGN(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASSIGN, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_whereCommand; } + public get ruleIndex(): number { return esql_parser.RULE_field; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterWhereCommand) { - listener.enterWhereCommand(this); + if (listener.enterField) { + listener.enterField(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitWhereCommand) { - listener.exitWhereCommand(this); + if (listener.exitField) { + listener.exitField(this); } } } -export class BooleanExpressionContext extends ParserRuleContext { - public _left: BooleanExpressionContext; - public _operator: Token; - public _right: BooleanExpressionContext; - public NOT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NOT, 0); } - public booleanExpression(): BooleanExpressionContext[]; - public booleanExpression(i: number): BooleanExpressionContext; - public booleanExpression(i?: number): BooleanExpressionContext | BooleanExpressionContext[] { - if (i === undefined) { - return this.getRuleContexts(BooleanExpressionContext); - } else { - return this.getRuleContext(i, BooleanExpressionContext); - } - } - public valueExpression(): ValueExpressionContext | undefined { - return this.tryGetRuleContext(0, ValueExpressionContext); - } - public AND(): TerminalNode | undefined { return this.tryGetToken(esql_parser.AND, 0); } - public OR(): TerminalNode | undefined { return this.tryGetToken(esql_parser.OR, 0); } +export class EnrichFieldIdentifierContext extends ParserRuleContext { + public ENR_UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ENR_UNQUOTED_IDENTIFIER, 0); } + public ENR_QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ENR_QUOTED_IDENTIFIER, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_booleanExpression; } + public get ruleIndex(): number { return esql_parser.RULE_enrichFieldIdentifier; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterBooleanExpression) { - listener.enterBooleanExpression(this); + if (listener.enterEnrichFieldIdentifier) { + listener.enterEnrichFieldIdentifier(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitBooleanExpression) { - listener.exitBooleanExpression(this); + if (listener.exitEnrichFieldIdentifier) { + listener.exitEnrichFieldIdentifier(this); } } } -export class ValueExpressionContext extends ParserRuleContext { - public operatorExpression(): OperatorExpressionContext | undefined { - return this.tryGetRuleContext(0, OperatorExpressionContext); - } - public comparison(): ComparisonContext | undefined { - return this.tryGetRuleContext(0, ComparisonContext); +export class UserVariableContext extends ParserRuleContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_valueExpression; } + public get ruleIndex(): number { return esql_parser.RULE_userVariable; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterValueExpression) { - listener.enterValueExpression(this); + if (listener.enterUserVariable) { + listener.enterUserVariable(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitValueExpression) { - listener.exitValueExpression(this); + if (listener.exitUserVariable) { + listener.exitUserVariable(this); } } } -export class ComparisonContext extends ParserRuleContext { - public _left: OperatorExpressionContext; - public _right: OperatorExpressionContext; - public comparisonOperator(): ComparisonOperatorContext { - return this.getRuleContext(0, ComparisonOperatorContext); +export class FromCommandContext extends ParserRuleContext { + public FROM(): TerminalNode { return this.getToken(esql_parser.FROM, 0); } + public sourceIdentifier(): SourceIdentifierContext[]; + public sourceIdentifier(i: number): SourceIdentifierContext; + public sourceIdentifier(i?: number): SourceIdentifierContext | SourceIdentifierContext[] { + if (i === undefined) { + return this.getRuleContexts(SourceIdentifierContext); + } else { + return this.getRuleContext(i, SourceIdentifierContext); + } } - public operatorExpression(): OperatorExpressionContext[]; - public operatorExpression(i: number): OperatorExpressionContext; - public operatorExpression(i?: number): OperatorExpressionContext | OperatorExpressionContext[] { + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { if (i === undefined) { - return this.getRuleContexts(OperatorExpressionContext); + return this.getTokens(esql_parser.COMMA); } else { - return this.getRuleContext(i, OperatorExpressionContext); + return this.getToken(esql_parser.COMMA, i); } } + public metadata(): MetadataContext | undefined { + return this.tryGetRuleContext(0, MetadataContext); + } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_comparison; } + public get ruleIndex(): number { return esql_parser.RULE_fromCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterComparison) { - listener.enterComparison(this); + if (listener.enterFromCommand) { + listener.enterFromCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitComparison) { - listener.exitComparison(this); + if (listener.exitFromCommand) { + listener.exitFromCommand(this); } } } -export class MathFnContext extends ParserRuleContext { - public functionIdentifier(): FunctionIdentifierContext { - return this.getRuleContext(0, FunctionIdentifierContext); - } - public LP(): TerminalNode { return this.getToken(esql_parser.LP, 0); } - public RP(): TerminalNode { return this.getToken(esql_parser.RP, 0); } - public functionExpressionArgument(): FunctionExpressionArgumentContext[]; - public functionExpressionArgument(i: number): FunctionExpressionArgumentContext; - public functionExpressionArgument(i?: number): FunctionExpressionArgumentContext | FunctionExpressionArgumentContext[] { +export class MetadataContext extends ParserRuleContext { + public OPENING_BRACKET(): TerminalNode { return this.getToken(esql_parser.OPENING_BRACKET, 0); } + public METADATA(): TerminalNode { return this.getToken(esql_parser.METADATA, 0); } + public sourceIdentifier(): SourceIdentifierContext[]; + public sourceIdentifier(i: number): SourceIdentifierContext; + public sourceIdentifier(i?: number): SourceIdentifierContext | SourceIdentifierContext[] { if (i === undefined) { - return this.getRuleContexts(FunctionExpressionArgumentContext); + return this.getRuleContexts(SourceIdentifierContext); } else { - return this.getRuleContext(i, FunctionExpressionArgumentContext); + return this.getRuleContext(i, SourceIdentifierContext); } } + public CLOSING_BRACKET(): TerminalNode { return this.getToken(esql_parser.CLOSING_BRACKET, 0); } public COMMA(): TerminalNode[]; public COMMA(i: number): TerminalNode; public COMMA(i?: number): TerminalNode | TerminalNode[] { @@ -2306,241 +4476,238 @@ export class MathFnContext extends ParserRuleContext { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_mathFn; } + public get ruleIndex(): number { return esql_parser.RULE_metadata; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterMathFn) { - listener.enterMathFn(this); + if (listener.enterMetadata) { + listener.enterMetadata(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitMathFn) { - listener.exitMathFn(this); + if (listener.exitMetadata) { + listener.exitMetadata(this); } } } -export class OperatorExpressionContext extends ParserRuleContext { - public _left: OperatorExpressionContext; - public _operator: Token; - public _right: OperatorExpressionContext; - public primaryExpression(): PrimaryExpressionContext | undefined { - return this.tryGetRuleContext(0, PrimaryExpressionContext); - } - public mathFn(): MathFnContext | undefined { - return this.tryGetRuleContext(0, MathFnContext); - } - public operatorExpression(): OperatorExpressionContext[]; - public operatorExpression(i: number): OperatorExpressionContext; - public operatorExpression(i?: number): OperatorExpressionContext | OperatorExpressionContext[] { - if (i === undefined) { - return this.getRuleContexts(OperatorExpressionContext); - } else { - return this.getRuleContext(i, OperatorExpressionContext); - } +export class EvalCommandContext extends ParserRuleContext { + public EVAL(): TerminalNode { return this.getToken(esql_parser.EVAL, 0); } + public fields(): FieldsContext { + return this.getRuleContext(0, FieldsContext); } - public MINUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.MINUS, 0); } - public PLUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.PLUS, 0); } - public ASTERISK(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASTERISK, 0); } - public SLASH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SLASH, 0); } - public PERCENT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.PERCENT, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_operatorExpression; } + public get ruleIndex(): number { return esql_parser.RULE_evalCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterOperatorExpression) { - listener.enterOperatorExpression(this); + if (listener.enterEvalCommand) { + listener.enterEvalCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitOperatorExpression) { - listener.exitOperatorExpression(this); + if (listener.exitEvalCommand) { + listener.exitEvalCommand(this); } } } -export class PrimaryExpressionContext extends ParserRuleContext { - public constant(): ConstantContext | undefined { - return this.tryGetRuleContext(0, ConstantContext); +export class StatsCommandContext extends ParserRuleContext { + public STATS(): TerminalNode { return this.getToken(esql_parser.STATS, 0); } + public fields(): FieldsContext | undefined { + return this.tryGetRuleContext(0, FieldsContext); } - public qualifiedName(): QualifiedNameContext | undefined { - return this.tryGetRuleContext(0, QualifiedNameContext); + public BY(): TerminalNode | undefined { return this.tryGetToken(esql_parser.BY, 0); } + public qualifiedNames(): QualifiedNamesContext | undefined { + return this.tryGetRuleContext(0, QualifiedNamesContext); } - public LP(): TerminalNode | undefined { return this.tryGetToken(esql_parser.LP, 0); } - public booleanExpression(): BooleanExpressionContext[]; - public booleanExpression(i: number): BooleanExpressionContext; - public booleanExpression(i?: number): BooleanExpressionContext | BooleanExpressionContext[] { - if (i === undefined) { - return this.getRuleContexts(BooleanExpressionContext); - } else { - return this.getRuleContext(i, BooleanExpressionContext); - } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); } - public RP(): TerminalNode | undefined { return this.tryGetToken(esql_parser.RP, 0); } - public identifier(): IdentifierContext | undefined { - return this.tryGetRuleContext(0, IdentifierContext); + // @Override + public get ruleIndex(): number { return esql_parser.RULE_statsCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterStatsCommand) { + listener.enterStatsCommand(this); + } } - public COMMA(): TerminalNode[]; - public COMMA(i: number): TerminalNode; - public COMMA(i?: number): TerminalNode | TerminalNode[] { - if (i === undefined) { - return this.getTokens(esql_parser.COMMA); - } else { - return this.getToken(esql_parser.COMMA, i); + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitStatsCommand) { + listener.exitStatsCommand(this); } } +} + + +export class SourceIdentifierContext extends ParserRuleContext { + public SRC_UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SRC_UNQUOTED_IDENTIFIER, 0); } + public SRC_QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SRC_QUOTED_IDENTIFIER, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_primaryExpression; } + public get ruleIndex(): number { return esql_parser.RULE_sourceIdentifier; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterPrimaryExpression) { - listener.enterPrimaryExpression(this); + if (listener.enterSourceIdentifier) { + listener.enterSourceIdentifier(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitPrimaryExpression) { - listener.exitPrimaryExpression(this); + if (listener.exitSourceIdentifier) { + listener.exitSourceIdentifier(this); } } } -export class RowCommandContext extends ParserRuleContext { - public ROW(): TerminalNode { return this.getToken(esql_parser.ROW, 0); } - public fields(): FieldsContext { - return this.getRuleContext(0, FieldsContext); - } +export class EnrichIdentifierContext extends ParserRuleContext { + public ENR_UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ENR_UNQUOTED_IDENTIFIER, 0); } + public ENR_QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ENR_QUOTED_IDENTIFIER, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_rowCommand; } + public get ruleIndex(): number { return esql_parser.RULE_enrichIdentifier; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterRowCommand) { - listener.enterRowCommand(this); + if (listener.enterEnrichIdentifier) { + listener.enterEnrichIdentifier(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitRowCommand) { - listener.exitRowCommand(this); + if (listener.exitEnrichIdentifier) { + listener.exitEnrichIdentifier(this); } } } -export class FieldsContext extends ParserRuleContext { - public field(): FieldContext[]; - public field(i: number): FieldContext; - public field(i?: number): FieldContext | FieldContext[] { - if (i === undefined) { - return this.getRuleContexts(FieldContext); - } else { - return this.getRuleContext(i, FieldContext); - } +export class FunctionExpressionArgumentContext extends ParserRuleContext { + public qualifiedName(): QualifiedNameContext | undefined { + return this.tryGetRuleContext(0, QualifiedNameContext); } - public COMMA(): TerminalNode[]; - public COMMA(i: number): TerminalNode; - public COMMA(i?: number): TerminalNode | TerminalNode[] { - if (i === undefined) { - return this.getTokens(esql_parser.COMMA); - } else { - return this.getToken(esql_parser.COMMA, i); - } + public string(): StringContext | undefined { + return this.tryGetRuleContext(0, StringContext); + } + public number(): NumberContext | undefined { + return this.tryGetRuleContext(0, NumberContext); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_fields; } + public get ruleIndex(): number { return esql_parser.RULE_functionExpressionArgument; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterFields) { - listener.enterFields(this); + if (listener.enterFunctionExpressionArgument) { + listener.enterFunctionExpressionArgument(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitFields) { - listener.exitFields(this); + if (listener.exitFunctionExpressionArgument) { + listener.exitFunctionExpressionArgument(this); } } } -export class FieldContext extends ParserRuleContext { - public booleanExpression(): BooleanExpressionContext { - return this.getRuleContext(0, BooleanExpressionContext); +export class MathFunctionExpressionArgumentContext extends ParserRuleContext { + public qualifiedName(): QualifiedNameContext | undefined { + return this.tryGetRuleContext(0, QualifiedNameContext); } - public userVariable(): UserVariableContext | undefined { - return this.tryGetRuleContext(0, UserVariableContext); + public string(): StringContext | undefined { + return this.tryGetRuleContext(0, StringContext); + } + public number(): NumberContext | undefined { + return this.tryGetRuleContext(0, NumberContext); + } + public operatorExpression(): OperatorExpressionContext | undefined { + return this.tryGetRuleContext(0, OperatorExpressionContext); + } + public dateExpression(): DateExpressionContext | undefined { + return this.tryGetRuleContext(0, DateExpressionContext); + } + public comparison(): ComparisonContext | undefined { + return this.tryGetRuleContext(0, ComparisonContext); } - public ASSIGN(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASSIGN, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_field; } + public get ruleIndex(): number { return esql_parser.RULE_mathFunctionExpressionArgument; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterField) { - listener.enterField(this); + if (listener.enterMathFunctionExpressionArgument) { + listener.enterMathFunctionExpressionArgument(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitField) { - listener.exitField(this); + if (listener.exitMathFunctionExpressionArgument) { + listener.exitMathFunctionExpressionArgument(this); } } } -export class UserVariableContext extends ParserRuleContext { - public identifier(): IdentifierContext { - return this.getRuleContext(0, IdentifierContext); +export class QualifiedNameContext extends ParserRuleContext { + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext; + public identifier(i?: number): IdentifierContext | IdentifierContext[] { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } else { + return this.getRuleContext(i, IdentifierContext); + } + } + public DOT(): TerminalNode[]; + public DOT(i: number): TerminalNode; + public DOT(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.DOT); + } else { + return this.getToken(esql_parser.DOT, i); + } } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_userVariable; } + public get ruleIndex(): number { return esql_parser.RULE_qualifiedName; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterUserVariable) { - listener.enterUserVariable(this); + if (listener.enterQualifiedName) { + listener.enterQualifiedName(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitUserVariable) { - listener.exitUserVariable(this); + if (listener.exitQualifiedName) { + listener.exitQualifiedName(this); } } } -export class FromCommandContext extends ParserRuleContext { - public FROM(): TerminalNode { return this.getToken(esql_parser.FROM, 0); } - public sourceIdentifier(): SourceIdentifierContext[]; - public sourceIdentifier(i: number): SourceIdentifierContext; - public sourceIdentifier(i?: number): SourceIdentifierContext | SourceIdentifierContext[] { +export class QualifiedNamesContext extends ParserRuleContext { + public qualifiedName(): QualifiedNameContext[]; + public qualifiedName(i: number): QualifiedNameContext; + public qualifiedName(i?: number): QualifiedNameContext | QualifiedNameContext[] { if (i === undefined) { - return this.getRuleContexts(SourceIdentifierContext); + return this.getRuleContexts(QualifiedNameContext); } else { - return this.getRuleContext(i, SourceIdentifierContext); + return this.getRuleContext(i, QualifiedNameContext); } } public COMMA(): TerminalNode[]; @@ -2556,173 +4723,209 @@ export class FromCommandContext extends ParserRuleContext { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_fromCommand; } + public get ruleIndex(): number { return esql_parser.RULE_qualifiedNames; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterFromCommand) { - listener.enterFromCommand(this); + if (listener.enterQualifiedNames) { + listener.enterQualifiedNames(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitFromCommand) { - listener.exitFromCommand(this); + if (listener.exitQualifiedNames) { + listener.exitQualifiedNames(this); } } } -export class EvalCommandContext extends ParserRuleContext { - public EVAL(): TerminalNode { return this.getToken(esql_parser.EVAL, 0); } - public fields(): FieldsContext { - return this.getRuleContext(0, FieldsContext); - } +export class IdentifierContext extends ParserRuleContext { + public UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.UNQUOTED_IDENTIFIER, 0); } + public QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.QUOTED_IDENTIFIER, 0); } + public ASTERISK(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASTERISK, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_evalCommand; } + public get ruleIndex(): number { return esql_parser.RULE_identifier; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterEvalCommand) { - listener.enterEvalCommand(this); + if (listener.enterIdentifier) { + listener.enterIdentifier(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitEvalCommand) { - listener.exitEvalCommand(this); + if (listener.exitIdentifier) { + listener.exitIdentifier(this); } } } -export class StatsCommandContext extends ParserRuleContext { - public STATS(): TerminalNode { return this.getToken(esql_parser.STATS, 0); } - public fields(): FieldsContext { - return this.getRuleContext(0, FieldsContext); - } - public BY(): TerminalNode | undefined { return this.tryGetToken(esql_parser.BY, 0); } - public qualifiedNames(): QualifiedNamesContext | undefined { - return this.tryGetRuleContext(0, QualifiedNamesContext); - } +export class MathFunctionIdentifierContext extends ParserRuleContext { + public MATH_FUNCTION(): TerminalNode { return this.getToken(esql_parser.MATH_FUNCTION, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_statsCommand; } + public get ruleIndex(): number { return esql_parser.RULE_mathFunctionIdentifier; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterStatsCommand) { - listener.enterStatsCommand(this); + if (listener.enterMathFunctionIdentifier) { + listener.enterMathFunctionIdentifier(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitStatsCommand) { - listener.exitStatsCommand(this); + if (listener.exitMathFunctionIdentifier) { + listener.exitMathFunctionIdentifier(this); } } } -export class SourceIdentifierContext extends ParserRuleContext { - public SRC_UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SRC_UNQUOTED_IDENTIFIER, 0); } - public SRC_QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SRC_QUOTED_IDENTIFIER, 0); } +export class FunctionIdentifierContext extends ParserRuleContext { + public UNARY_FUNCTION(): TerminalNode { return this.getToken(esql_parser.UNARY_FUNCTION, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_sourceIdentifier; } + public get ruleIndex(): number { return esql_parser.RULE_functionIdentifier; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterSourceIdentifier) { - listener.enterSourceIdentifier(this); + if (listener.enterFunctionIdentifier) { + listener.enterFunctionIdentifier(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitSourceIdentifier) { - listener.exitSourceIdentifier(this); + if (listener.exitFunctionIdentifier) { + listener.exitFunctionIdentifier(this); } } } -export class FunctionExpressionArgumentContext extends ParserRuleContext { - public qualifiedName(): QualifiedNameContext | undefined { - return this.tryGetRuleContext(0, QualifiedNameContext); +export class ConstantContext extends ParserRuleContext { + public NULL(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NULL, 0); } + public numericValue(): NumericValueContext[]; + public numericValue(i: number): NumericValueContext; + public numericValue(i?: number): NumericValueContext | NumericValueContext[] { + if (i === undefined) { + return this.getRuleContexts(NumericValueContext); + } else { + return this.getRuleContext(i, NumericValueContext); + } } - public string(): StringContext | undefined { - return this.tryGetRuleContext(0, StringContext); + public booleanValue(): BooleanValueContext[]; + public booleanValue(i: number): BooleanValueContext; + public booleanValue(i?: number): BooleanValueContext | BooleanValueContext[] { + if (i === undefined) { + return this.getRuleContexts(BooleanValueContext); + } else { + return this.getRuleContext(i, BooleanValueContext); + } + } + public string(): StringContext[]; + public string(i: number): StringContext; + public string(i?: number): StringContext | StringContext[] { + if (i === undefined) { + return this.getRuleContexts(StringContext); + } else { + return this.getRuleContext(i, StringContext); + } + } + public OPENING_BRACKET(): TerminalNode | undefined { return this.tryGetToken(esql_parser.OPENING_BRACKET, 0); } + public CLOSING_BRACKET(): TerminalNode | undefined { return this.tryGetToken(esql_parser.CLOSING_BRACKET, 0); } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_functionExpressionArgument; } + public get ruleIndex(): number { return esql_parser.RULE_constant; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterFunctionExpressionArgument) { - listener.enterFunctionExpressionArgument(this); + if (listener.enterConstant) { + listener.enterConstant(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitFunctionExpressionArgument) { - listener.exitFunctionExpressionArgument(this); + if (listener.exitConstant) { + listener.exitConstant(this); } } } -export class QualifiedNameContext extends ParserRuleContext { - public identifier(): IdentifierContext[]; - public identifier(i: number): IdentifierContext; - public identifier(i?: number): IdentifierContext | IdentifierContext[] { - if (i === undefined) { - return this.getRuleContexts(IdentifierContext); - } else { - return this.getRuleContext(i, IdentifierContext); +export class NumericValueContext extends ParserRuleContext { + public decimalValue(): DecimalValueContext | undefined { + return this.tryGetRuleContext(0, DecimalValueContext); + } + public integerValue(): IntegerValueContext | undefined { + return this.tryGetRuleContext(0, IntegerValueContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_numericValue; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterNumericValue) { + listener.enterNumericValue(this); } } - public DOT(): TerminalNode[]; - public DOT(i: number): TerminalNode; - public DOT(i?: number): TerminalNode | TerminalNode[] { - if (i === undefined) { - return this.getTokens(esql_parser.DOT); - } else { - return this.getToken(esql_parser.DOT, i); + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitNumericValue) { + listener.exitNumericValue(this); } } +} + + +export class LimitCommandContext extends ParserRuleContext { + public LIMIT(): TerminalNode { return this.getToken(esql_parser.LIMIT, 0); } + public INTEGER_LITERAL(): TerminalNode { return this.getToken(esql_parser.INTEGER_LITERAL, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_qualifiedName; } + public get ruleIndex(): number { return esql_parser.RULE_limitCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterQualifiedName) { - listener.enterQualifiedName(this); + if (listener.enterLimitCommand) { + listener.enterLimitCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitQualifiedName) { - listener.exitQualifiedName(this); + if (listener.exitLimitCommand) { + listener.exitLimitCommand(this); } } } -export class QualifiedNamesContext extends ParserRuleContext { - public qualifiedName(): QualifiedNameContext[]; - public qualifiedName(i: number): QualifiedNameContext; - public qualifiedName(i?: number): QualifiedNameContext | QualifiedNameContext[] { +export class SortCommandContext extends ParserRuleContext { + public SORT(): TerminalNode { return this.getToken(esql_parser.SORT, 0); } + public orderExpression(): OrderExpressionContext[]; + public orderExpression(i: number): OrderExpressionContext; + public orderExpression(i?: number): OrderExpressionContext | OrderExpressionContext[] { if (i === undefined) { - return this.getRuleContexts(QualifiedNameContext); + return this.getRuleContexts(OrderExpressionContext); } else { - return this.getRuleContext(i, QualifiedNameContext); + return this.getRuleContext(i, OrderExpressionContext); } } public COMMA(): TerminalNode[]; @@ -2738,260 +4941,298 @@ export class QualifiedNamesContext extends ParserRuleContext { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_qualifiedNames; } + public get ruleIndex(): number { return esql_parser.RULE_sortCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterQualifiedNames) { - listener.enterQualifiedNames(this); + if (listener.enterSortCommand) { + listener.enterSortCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitQualifiedNames) { - listener.exitQualifiedNames(this); + if (listener.exitSortCommand) { + listener.exitSortCommand(this); } } } -export class IdentifierContext extends ParserRuleContext { - public UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.UNQUOTED_IDENTIFIER, 0); } - public QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.QUOTED_IDENTIFIER, 0); } +export class OrderExpressionContext extends ParserRuleContext { + public booleanExpression(): BooleanExpressionContext { + return this.getRuleContext(0, BooleanExpressionContext); + } + public ORDERING(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ORDERING, 0); } + public NULLS_ORDERING(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NULLS_ORDERING, 0); } + public NULLS_ORDERING_DIRECTION(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NULLS_ORDERING_DIRECTION, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_identifier; } + public get ruleIndex(): number { return esql_parser.RULE_orderExpression; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterIdentifier) { - listener.enterIdentifier(this); + if (listener.enterOrderExpression) { + listener.enterOrderExpression(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitIdentifier) { - listener.exitIdentifier(this); + if (listener.exitOrderExpression) { + listener.exitOrderExpression(this); } } } -export class FunctionIdentifierContext extends ParserRuleContext { - public UNARY_FUNCTION(): TerminalNode { return this.getToken(esql_parser.UNARY_FUNCTION, 0); } +export class ProjectCommandContext extends ParserRuleContext { + public PROJECT(): TerminalNode { return this.getToken(esql_parser.PROJECT, 0); } + public qualifiedNames(): QualifiedNamesContext { + return this.getRuleContext(0, QualifiedNamesContext); + } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_functionIdentifier; } + public get ruleIndex(): number { return esql_parser.RULE_projectCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterFunctionIdentifier) { - listener.enterFunctionIdentifier(this); + if (listener.enterProjectCommand) { + listener.enterProjectCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitFunctionIdentifier) { - listener.exitFunctionIdentifier(this); + if (listener.exitProjectCommand) { + listener.exitProjectCommand(this); } } } -export class ConstantContext extends ParserRuleContext { +export class KeepCommandContext extends ParserRuleContext { + public KEEP(): TerminalNode { return this.getToken(esql_parser.KEEP, 0); } + public qualifiedNames(): QualifiedNamesContext { + return this.getRuleContext(0, QualifiedNamesContext); + } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_constant; } - public copyFrom(ctx: ConstantContext): void { - super.copyFrom(ctx); - } -} -export class NullLiteralContext extends ConstantContext { - public NULL(): TerminalNode { return this.getToken(esql_parser.NULL, 0); } - constructor(ctx: ConstantContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } + public get ruleIndex(): number { return esql_parser.RULE_keepCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterNullLiteral) { - listener.enterNullLiteral(this); + if (listener.enterKeepCommand) { + listener.enterKeepCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitNullLiteral) { - listener.exitNullLiteral(this); + if (listener.exitKeepCommand) { + listener.exitKeepCommand(this); } } } -export class NumericLiteralContext extends ConstantContext { - public number(): NumberContext { - return this.getRuleContext(0, NumberContext); + + +export class DropCommandContext extends ParserRuleContext { + public DROP(): TerminalNode { return this.getToken(esql_parser.DROP, 0); } + public qualifiedNames(): QualifiedNamesContext { + return this.getRuleContext(0, QualifiedNamesContext); } - constructor(ctx: ConstantContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); } // @Override + public get ruleIndex(): number { return esql_parser.RULE_dropCommand; } + // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterNumericLiteral) { - listener.enterNumericLiteral(this); + if (listener.enterDropCommand) { + listener.enterDropCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitNumericLiteral) { - listener.exitNumericLiteral(this); + if (listener.exitDropCommand) { + listener.exitDropCommand(this); } } } -export class BooleanLiteralContext extends ConstantContext { - public booleanValue(): BooleanValueContext { - return this.getRuleContext(0, BooleanValueContext); + + +export class RenameVariableContext extends ParserRuleContext { + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext; + public identifier(i?: number): IdentifierContext | IdentifierContext[] { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } else { + return this.getRuleContext(i, IdentifierContext); + } } - constructor(ctx: ConstantContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); + public DOT(): TerminalNode[]; + public DOT(i: number): TerminalNode; + public DOT(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.DOT); + } else { + return this.getToken(esql_parser.DOT, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); } // @Override + public get ruleIndex(): number { return esql_parser.RULE_renameVariable; } + // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterBooleanLiteral) { - listener.enterBooleanLiteral(this); + if (listener.enterRenameVariable) { + listener.enterRenameVariable(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitBooleanLiteral) { - listener.exitBooleanLiteral(this); + if (listener.exitRenameVariable) { + listener.exitRenameVariable(this); } } } -export class StringLiteralContext extends ConstantContext { - public string(): StringContext { - return this.getRuleContext(0, StringContext); + + +export class RenameCommandContext extends ParserRuleContext { + public RENAME(): TerminalNode { return this.getToken(esql_parser.RENAME, 0); } + public renameClause(): RenameClauseContext[]; + public renameClause(i: number): RenameClauseContext; + public renameClause(i?: number): RenameClauseContext | RenameClauseContext[] { + if (i === undefined) { + return this.getRuleContexts(RenameClauseContext); + } else { + return this.getRuleContext(i, RenameClauseContext); + } } - constructor(ctx: ConstantContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_renameCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterStringLiteral) { - listener.enterStringLiteral(this); + if (listener.enterRenameCommand) { + listener.enterRenameCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitStringLiteral) { - listener.exitStringLiteral(this); + if (listener.exitRenameCommand) { + listener.exitRenameCommand(this); } } } -export class LimitCommandContext extends ParserRuleContext { - public LIMIT(): TerminalNode { return this.getToken(esql_parser.LIMIT, 0); } - public INTEGER_LITERAL(): TerminalNode { return this.getToken(esql_parser.INTEGER_LITERAL, 0); } +export class RenameClauseContext extends ParserRuleContext { + public qualifiedName(): QualifiedNameContext { + return this.getRuleContext(0, QualifiedNameContext); + } + public AS(): TerminalNode { return this.getToken(esql_parser.AS, 0); } + public renameVariable(): RenameVariableContext { + return this.getRuleContext(0, RenameVariableContext); + } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_limitCommand; } + public get ruleIndex(): number { return esql_parser.RULE_renameClause; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterLimitCommand) { - listener.enterLimitCommand(this); + if (listener.enterRenameClause) { + listener.enterRenameClause(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitLimitCommand) { - listener.exitLimitCommand(this); + if (listener.exitRenameClause) { + listener.exitRenameClause(this); } } } -export class SortCommandContext extends ParserRuleContext { - public SORT(): TerminalNode { return this.getToken(esql_parser.SORT, 0); } - public orderExpression(): OrderExpressionContext[]; - public orderExpression(i: number): OrderExpressionContext; - public orderExpression(i?: number): OrderExpressionContext | OrderExpressionContext[] { - if (i === undefined) { - return this.getRuleContexts(OrderExpressionContext); - } else { - return this.getRuleContext(i, OrderExpressionContext); - } +export class DissectCommandContext extends ParserRuleContext { + public DISSECT(): TerminalNode { return this.getToken(esql_parser.DISSECT, 0); } + public qualifiedNames(): QualifiedNamesContext { + return this.getRuleContext(0, QualifiedNamesContext); } - public COMMA(): TerminalNode[]; - public COMMA(i: number): TerminalNode; - public COMMA(i?: number): TerminalNode | TerminalNode[] { - if (i === undefined) { - return this.getTokens(esql_parser.COMMA); - } else { - return this.getToken(esql_parser.COMMA, i); - } + public string(): StringContext { + return this.getRuleContext(0, StringContext); + } + public commandOptions(): CommandOptionsContext | undefined { + return this.tryGetRuleContext(0, CommandOptionsContext); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_sortCommand; } + public get ruleIndex(): number { return esql_parser.RULE_dissectCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterSortCommand) { - listener.enterSortCommand(this); + if (listener.enterDissectCommand) { + listener.enterDissectCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitSortCommand) { - listener.exitSortCommand(this); + if (listener.exitDissectCommand) { + listener.exitDissectCommand(this); } } } -export class OrderExpressionContext extends ParserRuleContext { - public booleanExpression(): BooleanExpressionContext { - return this.getRuleContext(0, BooleanExpressionContext); +export class GrokCommandContext extends ParserRuleContext { + public GROK(): TerminalNode { return this.getToken(esql_parser.GROK, 0); } + public qualifiedNames(): QualifiedNamesContext { + return this.getRuleContext(0, QualifiedNamesContext); + } + public string(): StringContext { + return this.getRuleContext(0, StringContext); } - public ORDERING(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ORDERING, 0); } - public NULLS_ORDERING(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NULLS_ORDERING, 0); } - public NULLS_ORDERING_DIRECTION(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NULLS_ORDERING_DIRECTION, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_orderExpression; } + public get ruleIndex(): number { return esql_parser.RULE_grokCommand; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterOrderExpression) { - listener.enterOrderExpression(this); + if (listener.enterGrokCommand) { + listener.enterGrokCommand(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitOrderExpression) { - listener.exitOrderExpression(this); + if (listener.exitGrokCommand) { + listener.exitGrokCommand(this); } } } -export class ProjectCommandContext extends ParserRuleContext { - public PROJECT(): TerminalNode { return this.getToken(esql_parser.PROJECT, 0); } - public projectClause(): ProjectClauseContext[]; - public projectClause(i: number): ProjectClauseContext; - public projectClause(i?: number): ProjectClauseContext | ProjectClauseContext[] { +export class CommandOptionsContext extends ParserRuleContext { + public commandOption(): CommandOptionContext[]; + public commandOption(i: number): CommandOptionContext; + public commandOption(i?: number): CommandOptionContext | CommandOptionContext[] { if (i === undefined) { - return this.getRuleContexts(ProjectClauseContext); + return this.getRuleContexts(CommandOptionContext); } else { - return this.getRuleContext(i, ProjectClauseContext); + return this.getRuleContext(i, CommandOptionContext); } } public COMMA(): TerminalNode[]; @@ -3007,50 +5248,45 @@ export class ProjectCommandContext extends ParserRuleContext { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_projectCommand; } + public get ruleIndex(): number { return esql_parser.RULE_commandOptions; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterProjectCommand) { - listener.enterProjectCommand(this); + if (listener.enterCommandOptions) { + listener.enterCommandOptions(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitProjectCommand) { - listener.exitProjectCommand(this); + if (listener.exitCommandOptions) { + listener.exitCommandOptions(this); } } } -export class ProjectClauseContext extends ParserRuleContext { - public _newName: SourceIdentifierContext; - public _oldName: SourceIdentifierContext; - public sourceIdentifier(): SourceIdentifierContext[]; - public sourceIdentifier(i: number): SourceIdentifierContext; - public sourceIdentifier(i?: number): SourceIdentifierContext | SourceIdentifierContext[] { - if (i === undefined) { - return this.getRuleContexts(SourceIdentifierContext); - } else { - return this.getRuleContext(i, SourceIdentifierContext); - } +export class CommandOptionContext extends ParserRuleContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + public ASSIGN(): TerminalNode { return this.getToken(esql_parser.ASSIGN, 0); } + public constant(): ConstantContext { + return this.getRuleContext(0, ConstantContext); } - public ASSIGN(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASSIGN, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { super(parent, invokingState); } // @Override - public get ruleIndex(): number { return esql_parser.RULE_projectClause; } + public get ruleIndex(): number { return esql_parser.RULE_commandOption; } // @Override public enterRule(listener: esql_parserListener): void { - if (listener.enterProjectClause) { - listener.enterProjectClause(this); + if (listener.enterCommandOption) { + listener.enterCommandOption(this); } } // @Override public exitRule(listener: esql_parserListener): void { - if (listener.exitProjectClause) { - listener.exitProjectClause(this); + if (listener.exitCommandOption) { + listener.exitCommandOption(this); } } } @@ -3128,6 +5364,50 @@ export class IntegerLiteralContext extends NumberContext { } +export class DecimalValueContext extends ParserRuleContext { + public DECIMAL_LITERAL(): TerminalNode { return this.getToken(esql_parser.DECIMAL_LITERAL, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_decimalValue; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterDecimalValue) { + listener.enterDecimalValue(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitDecimalValue) { + listener.exitDecimalValue(this); + } + } +} + + +export class IntegerValueContext extends ParserRuleContext { + public INTEGER_LITERAL(): TerminalNode { return this.getToken(esql_parser.INTEGER_LITERAL, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_integerValue; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterIntegerValue) { + listener.enterIntegerValue(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitIntegerValue) { + listener.exitIntegerValue(this); + } + } +} + + export class StringContext extends ParserRuleContext { public STRING(): TerminalNode { return this.getToken(esql_parser.STRING, 0); } constructor(parent: ParserRuleContext | undefined, invokingState: number) { @@ -3223,3 +5503,27 @@ export class SubqueryExpressionContext extends ParserRuleContext { } +export class ShowCommandContext extends ParserRuleContext { + public SHOW(): TerminalNode { return this.getToken(esql_parser.SHOW, 0); } + public INFO(): TerminalNode | undefined { return this.tryGetToken(esql_parser.INFO, 0); } + public FUNCTIONS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.FUNCTIONS, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_showCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterShowCommand) { + listener.enterShowCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitShowCommand) { + listener.exitShowCommand(this); + } + } +} + + diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser_listener.ts b/packages/kbn-monaco/src/esql/antlr/esql_parser_listener.ts index 2b943a8bcff45..f131d4072b773 100644 --- a/packages/kbn-monaco/src/esql/antlr/esql_parser_listener.ts +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser_listener.ts @@ -4,10 +4,6 @@ import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; -import { NullLiteralContext } from "./esql_parser"; -import { NumericLiteralContext } from "./esql_parser"; -import { BooleanLiteralContext } from "./esql_parser"; -import { StringLiteralContext } from "./esql_parser"; import { DecimalLiteralContext } from "./esql_parser"; import { IntegerLiteralContext } from "./esql_parser"; import { SingleCommandQueryContext } from "./esql_parser"; @@ -16,38 +12,62 @@ import { SingleStatementContext } from "./esql_parser"; import { QueryContext } from "./esql_parser"; import { SourceCommandContext } from "./esql_parser"; import { ProcessingCommandContext } from "./esql_parser"; +import { EnrichCommandContext } from "./esql_parser"; +import { EnrichWithClauseContext } from "./esql_parser"; +import { MvExpandCommandContext } from "./esql_parser"; import { WhereCommandContext } from "./esql_parser"; +import { WhereBooleanExpressionContext } from "./esql_parser"; import { BooleanExpressionContext } from "./esql_parser"; +import { RegexBooleanExpressionContext } from "./esql_parser"; import { ValueExpressionContext } from "./esql_parser"; import { ComparisonContext } from "./esql_parser"; import { MathFnContext } from "./esql_parser"; +import { MathEvalFnContext } from "./esql_parser"; +import { DateExpressionContext } from "./esql_parser"; import { OperatorExpressionContext } from "./esql_parser"; import { PrimaryExpressionContext } from "./esql_parser"; import { RowCommandContext } from "./esql_parser"; import { FieldsContext } from "./esql_parser"; import { FieldContext } from "./esql_parser"; +import { EnrichFieldIdentifierContext } from "./esql_parser"; import { UserVariableContext } from "./esql_parser"; import { FromCommandContext } from "./esql_parser"; +import { MetadataContext } from "./esql_parser"; import { EvalCommandContext } from "./esql_parser"; import { StatsCommandContext } from "./esql_parser"; import { SourceIdentifierContext } from "./esql_parser"; +import { EnrichIdentifierContext } from "./esql_parser"; import { FunctionExpressionArgumentContext } from "./esql_parser"; +import { MathFunctionExpressionArgumentContext } from "./esql_parser"; import { QualifiedNameContext } from "./esql_parser"; import { QualifiedNamesContext } from "./esql_parser"; import { IdentifierContext } from "./esql_parser"; +import { MathFunctionIdentifierContext } from "./esql_parser"; import { FunctionIdentifierContext } from "./esql_parser"; import { ConstantContext } from "./esql_parser"; +import { NumericValueContext } from "./esql_parser"; import { LimitCommandContext } from "./esql_parser"; import { SortCommandContext } from "./esql_parser"; import { OrderExpressionContext } from "./esql_parser"; import { ProjectCommandContext } from "./esql_parser"; -import { ProjectClauseContext } from "./esql_parser"; +import { KeepCommandContext } from "./esql_parser"; +import { DropCommandContext } from "./esql_parser"; +import { RenameVariableContext } from "./esql_parser"; +import { RenameCommandContext } from "./esql_parser"; +import { RenameClauseContext } from "./esql_parser"; +import { DissectCommandContext } from "./esql_parser"; +import { GrokCommandContext } from "./esql_parser"; +import { CommandOptionsContext } from "./esql_parser"; +import { CommandOptionContext } from "./esql_parser"; import { BooleanValueContext } from "./esql_parser"; import { NumberContext } from "./esql_parser"; +import { DecimalValueContext } from "./esql_parser"; +import { IntegerValueContext } from "./esql_parser"; import { StringContext } from "./esql_parser"; import { ComparisonOperatorContext } from "./esql_parser"; import { ExplainCommandContext } from "./esql_parser"; import { SubqueryExpressionContext } from "./esql_parser"; +import { ShowCommandContext } from "./esql_parser"; /** @@ -55,58 +75,6 @@ import { SubqueryExpressionContext } from "./esql_parser"; * `esql_parser`. */ export interface esql_parserListener extends ParseTreeListener { - /** - * Enter a parse tree produced by the `nullLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - enterNullLiteral?: (ctx: NullLiteralContext) => void; - /** - * Exit a parse tree produced by the `nullLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - exitNullLiteral?: (ctx: NullLiteralContext) => void; - - /** - * Enter a parse tree produced by the `numericLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - enterNumericLiteral?: (ctx: NumericLiteralContext) => void; - /** - * Exit a parse tree produced by the `numericLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - exitNumericLiteral?: (ctx: NumericLiteralContext) => void; - - /** - * Enter a parse tree produced by the `booleanLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - enterBooleanLiteral?: (ctx: BooleanLiteralContext) => void; - /** - * Exit a parse tree produced by the `booleanLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - exitBooleanLiteral?: (ctx: BooleanLiteralContext) => void; - - /** - * Enter a parse tree produced by the `stringLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - enterStringLiteral?: (ctx: StringLiteralContext) => void; - /** - * Exit a parse tree produced by the `stringLiteral` - * labeled alternative in `esql_parser.constant`. - * @param ctx the parse tree - */ - exitStringLiteral?: (ctx: StringLiteralContext) => void; - /** * Enter a parse tree produced by the `decimalLiteral` * labeled alternative in `esql_parser.number`. @@ -203,6 +171,39 @@ export interface esql_parserListener extends ParseTreeListener { */ exitProcessingCommand?: (ctx: ProcessingCommandContext) => void; + /** + * Enter a parse tree produced by `esql_parser.enrichCommand`. + * @param ctx the parse tree + */ + enterEnrichCommand?: (ctx: EnrichCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.enrichCommand`. + * @param ctx the parse tree + */ + exitEnrichCommand?: (ctx: EnrichCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.enrichWithClause`. + * @param ctx the parse tree + */ + enterEnrichWithClause?: (ctx: EnrichWithClauseContext) => void; + /** + * Exit a parse tree produced by `esql_parser.enrichWithClause`. + * @param ctx the parse tree + */ + exitEnrichWithClause?: (ctx: EnrichWithClauseContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.mvExpandCommand`. + * @param ctx the parse tree + */ + enterMvExpandCommand?: (ctx: MvExpandCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.mvExpandCommand`. + * @param ctx the parse tree + */ + exitMvExpandCommand?: (ctx: MvExpandCommandContext) => void; + /** * Enter a parse tree produced by `esql_parser.whereCommand`. * @param ctx the parse tree @@ -214,6 +215,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitWhereCommand?: (ctx: WhereCommandContext) => void; + /** + * Enter a parse tree produced by `esql_parser.whereBooleanExpression`. + * @param ctx the parse tree + */ + enterWhereBooleanExpression?: (ctx: WhereBooleanExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.whereBooleanExpression`. + * @param ctx the parse tree + */ + exitWhereBooleanExpression?: (ctx: WhereBooleanExpressionContext) => void; + /** * Enter a parse tree produced by `esql_parser.booleanExpression`. * @param ctx the parse tree @@ -225,6 +237,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitBooleanExpression?: (ctx: BooleanExpressionContext) => void; + /** + * Enter a parse tree produced by `esql_parser.regexBooleanExpression`. + * @param ctx the parse tree + */ + enterRegexBooleanExpression?: (ctx: RegexBooleanExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.regexBooleanExpression`. + * @param ctx the parse tree + */ + exitRegexBooleanExpression?: (ctx: RegexBooleanExpressionContext) => void; + /** * Enter a parse tree produced by `esql_parser.valueExpression`. * @param ctx the parse tree @@ -258,6 +281,28 @@ export interface esql_parserListener extends ParseTreeListener { */ exitMathFn?: (ctx: MathFnContext) => void; + /** + * Enter a parse tree produced by `esql_parser.mathEvalFn`. + * @param ctx the parse tree + */ + enterMathEvalFn?: (ctx: MathEvalFnContext) => void; + /** + * Exit a parse tree produced by `esql_parser.mathEvalFn`. + * @param ctx the parse tree + */ + exitMathEvalFn?: (ctx: MathEvalFnContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.dateExpression`. + * @param ctx the parse tree + */ + enterDateExpression?: (ctx: DateExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.dateExpression`. + * @param ctx the parse tree + */ + exitDateExpression?: (ctx: DateExpressionContext) => void; + /** * Enter a parse tree produced by `esql_parser.operatorExpression`. * @param ctx the parse tree @@ -313,6 +358,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitField?: (ctx: FieldContext) => void; + /** + * Enter a parse tree produced by `esql_parser.enrichFieldIdentifier`. + * @param ctx the parse tree + */ + enterEnrichFieldIdentifier?: (ctx: EnrichFieldIdentifierContext) => void; + /** + * Exit a parse tree produced by `esql_parser.enrichFieldIdentifier`. + * @param ctx the parse tree + */ + exitEnrichFieldIdentifier?: (ctx: EnrichFieldIdentifierContext) => void; + /** * Enter a parse tree produced by `esql_parser.userVariable`. * @param ctx the parse tree @@ -335,6 +391,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitFromCommand?: (ctx: FromCommandContext) => void; + /** + * Enter a parse tree produced by `esql_parser.metadata`. + * @param ctx the parse tree + */ + enterMetadata?: (ctx: MetadataContext) => void; + /** + * Exit a parse tree produced by `esql_parser.metadata`. + * @param ctx the parse tree + */ + exitMetadata?: (ctx: MetadataContext) => void; + /** * Enter a parse tree produced by `esql_parser.evalCommand`. * @param ctx the parse tree @@ -368,6 +435,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitSourceIdentifier?: (ctx: SourceIdentifierContext) => void; + /** + * Enter a parse tree produced by `esql_parser.enrichIdentifier`. + * @param ctx the parse tree + */ + enterEnrichIdentifier?: (ctx: EnrichIdentifierContext) => void; + /** + * Exit a parse tree produced by `esql_parser.enrichIdentifier`. + * @param ctx the parse tree + */ + exitEnrichIdentifier?: (ctx: EnrichIdentifierContext) => void; + /** * Enter a parse tree produced by `esql_parser.functionExpressionArgument`. * @param ctx the parse tree @@ -379,6 +457,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitFunctionExpressionArgument?: (ctx: FunctionExpressionArgumentContext) => void; + /** + * Enter a parse tree produced by `esql_parser.mathFunctionExpressionArgument`. + * @param ctx the parse tree + */ + enterMathFunctionExpressionArgument?: (ctx: MathFunctionExpressionArgumentContext) => void; + /** + * Exit a parse tree produced by `esql_parser.mathFunctionExpressionArgument`. + * @param ctx the parse tree + */ + exitMathFunctionExpressionArgument?: (ctx: MathFunctionExpressionArgumentContext) => void; + /** * Enter a parse tree produced by `esql_parser.qualifiedName`. * @param ctx the parse tree @@ -412,6 +501,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitIdentifier?: (ctx: IdentifierContext) => void; + /** + * Enter a parse tree produced by `esql_parser.mathFunctionIdentifier`. + * @param ctx the parse tree + */ + enterMathFunctionIdentifier?: (ctx: MathFunctionIdentifierContext) => void; + /** + * Exit a parse tree produced by `esql_parser.mathFunctionIdentifier`. + * @param ctx the parse tree + */ + exitMathFunctionIdentifier?: (ctx: MathFunctionIdentifierContext) => void; + /** * Enter a parse tree produced by `esql_parser.functionIdentifier`. * @param ctx the parse tree @@ -434,6 +534,17 @@ export interface esql_parserListener extends ParseTreeListener { */ exitConstant?: (ctx: ConstantContext) => void; + /** + * Enter a parse tree produced by `esql_parser.numericValue`. + * @param ctx the parse tree + */ + enterNumericValue?: (ctx: NumericValueContext) => void; + /** + * Exit a parse tree produced by `esql_parser.numericValue`. + * @param ctx the parse tree + */ + exitNumericValue?: (ctx: NumericValueContext) => void; + /** * Enter a parse tree produced by `esql_parser.limitCommand`. * @param ctx the parse tree @@ -479,15 +590,103 @@ export interface esql_parserListener extends ParseTreeListener { exitProjectCommand?: (ctx: ProjectCommandContext) => void; /** - * Enter a parse tree produced by `esql_parser.projectClause`. + * Enter a parse tree produced by `esql_parser.keepCommand`. + * @param ctx the parse tree + */ + enterKeepCommand?: (ctx: KeepCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.keepCommand`. + * @param ctx the parse tree + */ + exitKeepCommand?: (ctx: KeepCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.dropCommand`. + * @param ctx the parse tree + */ + enterDropCommand?: (ctx: DropCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.dropCommand`. + * @param ctx the parse tree + */ + exitDropCommand?: (ctx: DropCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.renameVariable`. + * @param ctx the parse tree + */ + enterRenameVariable?: (ctx: RenameVariableContext) => void; + /** + * Exit a parse tree produced by `esql_parser.renameVariable`. + * @param ctx the parse tree + */ + exitRenameVariable?: (ctx: RenameVariableContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.renameCommand`. + * @param ctx the parse tree + */ + enterRenameCommand?: (ctx: RenameCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.renameCommand`. + * @param ctx the parse tree + */ + exitRenameCommand?: (ctx: RenameCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.renameClause`. * @param ctx the parse tree */ - enterProjectClause?: (ctx: ProjectClauseContext) => void; + enterRenameClause?: (ctx: RenameClauseContext) => void; /** - * Exit a parse tree produced by `esql_parser.projectClause`. + * Exit a parse tree produced by `esql_parser.renameClause`. * @param ctx the parse tree */ - exitProjectClause?: (ctx: ProjectClauseContext) => void; + exitRenameClause?: (ctx: RenameClauseContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.dissectCommand`. + * @param ctx the parse tree + */ + enterDissectCommand?: (ctx: DissectCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.dissectCommand`. + * @param ctx the parse tree + */ + exitDissectCommand?: (ctx: DissectCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.grokCommand`. + * @param ctx the parse tree + */ + enterGrokCommand?: (ctx: GrokCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.grokCommand`. + * @param ctx the parse tree + */ + exitGrokCommand?: (ctx: GrokCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.commandOptions`. + * @param ctx the parse tree + */ + enterCommandOptions?: (ctx: CommandOptionsContext) => void; + /** + * Exit a parse tree produced by `esql_parser.commandOptions`. + * @param ctx the parse tree + */ + exitCommandOptions?: (ctx: CommandOptionsContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.commandOption`. + * @param ctx the parse tree + */ + enterCommandOption?: (ctx: CommandOptionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.commandOption`. + * @param ctx the parse tree + */ + exitCommandOption?: (ctx: CommandOptionContext) => void; /** * Enter a parse tree produced by `esql_parser.booleanValue`. @@ -511,6 +710,28 @@ export interface esql_parserListener extends ParseTreeListener { */ exitNumber?: (ctx: NumberContext) => void; + /** + * Enter a parse tree produced by `esql_parser.decimalValue`. + * @param ctx the parse tree + */ + enterDecimalValue?: (ctx: DecimalValueContext) => void; + /** + * Exit a parse tree produced by `esql_parser.decimalValue`. + * @param ctx the parse tree + */ + exitDecimalValue?: (ctx: DecimalValueContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.integerValue`. + * @param ctx the parse tree + */ + enterIntegerValue?: (ctx: IntegerValueContext) => void; + /** + * Exit a parse tree produced by `esql_parser.integerValue`. + * @param ctx the parse tree + */ + exitIntegerValue?: (ctx: IntegerValueContext) => void; + /** * Enter a parse tree produced by `esql_parser.string`. * @param ctx the parse tree @@ -554,5 +775,16 @@ export interface esql_parserListener extends ParseTreeListener { * @param ctx the parse tree */ exitSubqueryExpression?: (ctx: SubqueryExpressionContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.showCommand`. + * @param ctx the parse tree + */ + enterShowCommand?: (ctx: ShowCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.showCommand`. + * @param ctx the parse tree + */ + exitShowCommand?: (ctx: ShowCommandContext) => void; } diff --git a/packages/kbn-monaco/src/esql/language.ts b/packages/kbn-monaco/src/esql/language.ts index 8ab28106460fb..cca4cb3718f06 100644 --- a/packages/kbn-monaco/src/esql/language.ts +++ b/packages/kbn-monaco/src/esql/language.ts @@ -31,6 +31,19 @@ export const ESQLLang: CustomLangModuleType = { new DiagnosticsAdapter(ESQL_LANG_ID, (...uris) => workerProxyService.getWorker(uris)); }, + languageConfiguration: { + brackets: [['(', ')']], + autoClosingPairs: [ + { open: '(', close: ')' }, + { open: `'`, close: `'` }, + { open: '"', close: '"' }, + ], + surroundingPairs: [ + { open: '(', close: ')' }, + { open: `'`, close: `'` }, + { open: '"', close: '"' }, + ], + }, getSuggestionProvider(callbacks?: ESQLCustomAutocompleteCallbacks) { return new ESQLCompletionAdapter((...uris) => workerProxyService.getWorker(uris), callbacks); diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/comparison_commands.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/comparison_commands.ts index ec8cfe8e596c1..92b2e8f7c31d1 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/comparison_commands.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/comparison_commands.ts @@ -85,4 +85,32 @@ export const comparisonCommandsDefinitions: AutocompleteCommandDefinition[] = [ }), sortText: 'D', }, + { + label: 'like', + insertText: 'like', + kind: 11, + detail: i18n.translate('monaco.esql.autocomplete.likeDoc', { + defaultMessage: 'Filter data based on string patterns', + }), + sortText: 'D', + }, + { + label: 'rlike', + insertText: 'rlike', + kind: 11, + detail: i18n.translate('monaco.esql.autocomplete.rlikeDoc', { + defaultMessage: 'Filter data based on string regular expressions', + }), + sortText: 'D', + }, + { + label: 'in', + insertText: 'in', + kind: 11, + detail: i18n.translate('monaco.esql.autocomplete.inDoc', { + defaultMessage: + 'Tests if the value an expression takes is contained in a list of other expressions', + }), + sortText: 'D', + }, ]; diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/date_math_expressions.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/date_math_expressions.ts new file mode 100644 index 0000000000000..2eb0226914ee9 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/date_math_expressions.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { AutocompleteCommandDefinition } from '../types'; + +export const dateExpressionDefinitions: AutocompleteCommandDefinition[] = [ + { + label: 'year', + insertText: 'year', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.year', { + defaultMessage: 'Year', + }), + sortText: 'D', + }, + { + label: 'years', + insertText: 'years', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.years', { + defaultMessage: 'Years (Plural)', + }), + sortText: 'D', + }, + { + label: 'month', + insertText: 'month', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.month', { + defaultMessage: 'Month', + }), + sortText: 'D', + }, + { + label: 'months', + insertText: 'months', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.months', { + defaultMessage: 'Months (Plural)', + }), + sortText: 'D', + }, + { + label: 'week', + insertText: 'week', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.week', { + defaultMessage: 'Week', + }), + sortText: 'D', + }, + { + label: 'weeks', + insertText: 'weeks', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.weeks', { + defaultMessage: 'Weeks (Plural)', + }), + sortText: 'D', + }, + { + label: 'day', + insertText: 'day', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.day', { + defaultMessage: 'Day', + }), + sortText: 'D', + }, + { + label: 'days', + insertText: 'days', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.days', { + defaultMessage: 'Days (Plural)', + }), + sortText: 'D', + }, + { + label: 'hour', + insertText: 'hour', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.hour', { + defaultMessage: 'Hour', + }), + sortText: 'D', + }, + { + label: 'hours', + insertText: 'hours', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.hours', { + defaultMessage: 'Hours (Plural)', + }), + sortText: 'D', + }, + { + label: 'minute', + insertText: 'minute', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.minute', { + defaultMessage: 'Minute', + }), + sortText: 'D', + }, + { + label: 'minutes', + insertText: 'minutes', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.minutes', { + defaultMessage: 'Minutes (Plural)', + }), + sortText: 'D', + }, + { + label: 'second', + insertText: 'second', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.second', { + defaultMessage: 'Second', + }), + sortText: 'D', + }, + { + label: 'seconds', + insertText: 'seconds', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.seconds', { + defaultMessage: 'Seconds (Plural)', + }), + sortText: 'D', + }, + { + label: 'millisecond', + insertText: 'millisecond', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.millisecond', { + defaultMessage: 'Millisecond', + }), + sortText: 'D', + }, + { + label: 'milliseconds', + insertText: 'milliseconds', + kind: 12, + detail: i18n.translate('monaco.esql.autocomplete.dateDurationDefinition.milliseconds', { + defaultMessage: 'Milliseconds (Plural)', + }), + sortText: 'D', + }, +]; diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts index aa9a9f1777ff3..4a5a147ffcbde 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts @@ -9,6 +9,23 @@ import { i18n } from '@kbn/i18n'; import type { AutocompleteCommandDefinition } from '../types'; +export const buildPoliciesDefinitions = ( + policies: Array<{ name: string; indices: string[] }> +): AutocompleteCommandDefinition[] => + policies.map(({ name: label, indices }) => ({ + label, + insertText: label, + kind: 5, + detail: i18n.translate('monaco.esql.autocomplete.policyDefinition', { + defaultMessage: `Policy defined on {count, plural, one {index} other {indices}}: {indices}`, + values: { + count: indices.length, + indices: indices.join(', '), + }, + }), + sortText: 'D', + })); + export const buildFieldsDefinitions = (fields: string[]): AutocompleteCommandDefinition[] => fields.map((label) => ({ label, @@ -20,6 +37,37 @@ export const buildFieldsDefinitions = (fields: string[]): AutocompleteCommandDef sortText: 'D', })); +export const buildNoPoliciesAvailableDefinition = (): AutocompleteCommandDefinition[] => [ + { + label: i18n.translate('monaco.esql.autocomplete.noPoliciesLabel', { + defaultMessage: 'No available policy', + }), + insertText: '', + kind: 26, + detail: i18n.translate('monaco.esql.autocomplete.noPoliciesLabelsFound', { + defaultMessage: 'No policies found', + }), + sortText: 'D', + }, +]; + +export const buildMatchingFieldsDefinition = ( + matchingField: string, + fields: string[] +): AutocompleteCommandDefinition[] => + fields.map((label) => ({ + label, + insertText: label, + kind: 4, + detail: i18n.translate('monaco.esql.autocomplete.matchingFieldDefinition', { + defaultMessage: `Use to match on {matchingField} on the policy`, + values: { + matchingField, + }, + }), + sortText: 'D', + })); + export const buildNewVarDefinition = (label: string): AutocompleteCommandDefinition => { return { label, diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/functions_commands.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/functions_commands.ts index 119a443c40190..12056ee784695 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/functions_commands.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/functions_commands.ts @@ -7,25 +7,787 @@ */ import { i18n } from '@kbn/i18n'; -import { buildDocumentation } from './utils'; +import { buildDocumentation, buildFunctionDocumentation } from './utils'; import type { AutocompleteCommandDefinition } from '../types'; -export const roundCommandDefinition: AutocompleteCommandDefinition = { - label: 'round', - insertText: 'round', - kind: 1, - detail: i18n.translate('monaco.esql.autocomplete.roundDoc', { - defaultMessage: - 'Returns a number rounded to the decimal, specified by he closest integer value. The default is to round to an integer.', - }), - documentation: { - value: buildDocumentation('round(grouped[T]): aggregated[T]', [ - 'from index where field="value" | eval rounded = round(field)', - ]), - }, - sortText: 'C', -}; +export const whereCommandDefinition: AutocompleteCommandDefinition[] = [ + { + label: 'cidr_match', + insertText: 'cidr_match', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.cidrMatchDoc', { + defaultMessage: + 'The function takes a first parameter of type IP, followed by one or more parameters evaluated to a CIDR specificatione.', + }), + documentation: { + value: buildDocumentation('cidr_match(grouped[T]): aggregated[T]', [ + 'from index | eval cidr="10.0.0.0/8" | where cidr_match(ip_field, "127.0.0.1/30", cidr)', + ]), + }, + sortText: 'C', + }, +]; + +interface FunctionDefinition { + name: string; + description: string; + signatures: Array<{ + params: Array<{ + name: string; + type: string | string[]; + optional?: boolean; + }>; + infiniteParams?: boolean; + returnType: string; + examples?: string[]; + }>; +} + +const mathCommandFullDefinitions: FunctionDefinition[] = [ + { + name: 'round', + description: i18n.translate('monaco.esql.autocomplete.roundDoc', { + defaultMessage: + 'Returns a number rounded to the decimal, specified by he closest integer value. The default is to round to an integer.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval round_value = round(field)`], + }, + ], + }, + { + name: 'abs', + description: i18n.translate('monaco.esql.autocomplete.absDoc', { + defaultMessage: 'Returns the absolute value.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval abs_value = abs(field)`], + }, + ], + }, + { + name: 'log10', + description: i18n.translate('monaco.esql.autocomplete.log10Doc', { + defaultMessage: 'Returns the log base 10.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval log10_value = log10(field)`], + }, + ], + }, + { + name: 'pow', + description: i18n.translate('monaco.esql.autocomplete.powDoc', { + defaultMessage: + 'Returns the the value of a base (first argument) raised to a power (second argument).', + }), + signatures: [ + { + params: [ + { name: 'field', type: 'number' }, + { name: 'exponent', type: 'number' }, + ], + returnType: 'number', + examples: ['from index where field="value" | eval s = POW(field, exponent)'], + }, + ], + }, + { + name: 'concat', + description: i18n.translate('monaco.esql.autocomplete.concatDoc', { + defaultMessage: 'Concatenates two or more strings.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'string' }], + infiniteParams: true, + returnType: 'string', + examples: [ + 'from index where field="value" | eval concatenated = concat(field1, "-", field2)', + ], + }, + ], + }, + { + name: 'substring', + description: i18n.translate('monaco.esql.autocomplete.substringDoc', { + defaultMessage: + 'Returns a substring of a string, specified by a start position and an optional length. This example returns the first three characters of every last name.', + }), + signatures: [ + { + params: [ + { name: 'field', type: 'string' }, + { name: 'startIndex', type: 'number' }, + { name: 'endIndex', type: 'number' }, + ], + returnType: 'string', + examples: ['from index where field="value" | eval new_string = substring(field, 1, 3)'], + }, + ], + }, + { + name: 'trim', + description: i18n.translate('monaco.esql.autocomplete.trimDoc', { + defaultMessage: 'Removes leading and trailing whitespaces from strings.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'string' }], + returnType: 'string', + examples: ['from index where field="value" | eval new_string = trim(field)'], + }, + ], + }, + { + name: 'starts_with', + description: i18n.translate('monaco.esql.autocomplete.startsWithDoc', { + defaultMessage: + 'Returns a boolean that indicates whether a keyword string starts with another string.', + }), + signatures: [ + { + params: [ + { name: 'field', type: 'string' }, + { name: 'prefix', type: 'string' }, + ], + returnType: 'boolean', + examples: ['from index where field="value" | eval new_string = starts_with(field, "a")'], + }, + ], + }, + { + name: 'split', + description: i18n.translate('monaco.esql.autocomplete.splitDoc', { + defaultMessage: 'Splits a single valued string into multiple strings.', + }), + signatures: [ + { + params: [ + { name: 'words', type: 'string' }, + { name: 'separator', type: 'string' }, + ], + returnType: 'string[]', + examples: [`ROW words="foo;bar;baz;qux;quux;corge" | EVAL word = SPLIT(words, ";")`], + }, + ], + }, + { + name: 'to_string', + description: i18n.translate('monaco.esql.autocomplete.toStringDoc', { + defaultMessage: 'Converts to string.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'string', + examples: [`from index where field="value"" | EVAL string = to_string(field)`], + }, + ], + }, + { + name: 'to_boolean', + description: i18n.translate('monaco.esql.autocomplete.toBooleanDoc', { + defaultMessage: 'Converts to boolean.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'boolean', + examples: [`from index where field="value"" | EVAL bool = to_boolean(field)`], + }, + ], + }, + { + name: 'to_datetime', + description: i18n.translate('monaco.esql.autocomplete.toDateTimeDoc', { + defaultMessage: 'Converts to date.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'date', + examples: [`from index where field="value"" | EVAL datetime = to_datetime(field)`], + }, + ], + }, + { + name: 'to_degrees', + description: i18n.translate('monaco.esql.autocomplete.toDegreesDoc', { + defaultMessage: 'Coverts to degrees', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval degrees = to_degrees(field)`], + }, + ], + }, + { + name: 'to_double', + description: i18n.translate('monaco.esql.autocomplete.toDoubleDoc', { + defaultMessage: 'Converts to double.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'number', + examples: [`from index where field="value"" | EVAL double = to_double(field)`], + }, + ], + }, + { + name: 'to_integer', + description: i18n.translate('monaco.esql.autocomplete.toIntegerDoc', { + defaultMessage: 'Converts to integer.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'number', + examples: [`from index where field="value"" | EVAL integer = to_integer(field)`], + }, + ], + }, + { + name: 'to_long', + description: i18n.translate('monaco.esql.autocomplete.toLongDoc', { + defaultMessage: 'Converts to long.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'number', + examples: [`from index where field="value"" | EVAL long = to_long(field)`], + }, + ], + }, + { + name: 'to_radians', + description: i18n.translate('monaco.esql.autocomplete.toRadiansDoc', { + defaultMessage: 'Converts to radians', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval radians = to_radians(field)`], + }, + ], + }, + { + name: 'to_unsigned_long', + description: i18n.translate('monaco.esql.autocomplete.toUnsignedLongDoc', { + defaultMessage: 'Converts to unsigned long.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'number', + examples: [ + `from index where field="value"" | EVAL unsigned_long = to_unsigned_long(field)`, + ], + }, + ], + }, + { + name: 'to_ip', + description: i18n.translate('monaco.esql.autocomplete.toIpDoc', { + defaultMessage: 'Converts to ip.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + returnType: 'string[]', + examples: [`from index where field="value"" | EVAL ip = to_ip(field)`], + }, + ], + }, + { + name: 'to_version', + description: i18n.translate('monaco.esql.autocomplete.toVersionDoc', { + defaultMessage: 'Converts to version.', + }), + signatures: [ + { + params: [{ name: 'field', type: ['string', 'version'] }], + returnType: 'version', + examples: [`from index where field="value"" | EVAL version = to_version(field)`], + }, + ], + }, + { + name: 'date_extract', + description: i18n.translate('monaco.esql.autocomplete.dateExtractDoc', { + defaultMessage: `Extracts parts of a date, like year, month, day, hour. The supported field types are those provided by java.time.temporal.ChronoField`, + }), + signatures: [ + { + params: [ + { name: 'field', type: 'date' }, + { + name: 'date_part', + type: 'string', + }, + ], + returnType: 'number', + examples: [ + `ROW date = DATE_PARSE("2022-05-06", "yyyy-MM-dd") | EVAL year = DATE_EXTRACT(date, "year")`, + ], + }, + ], + }, + { + name: 'date_format', + description: i18n.translate('monaco.esql.autocomplete.dateFormatDoc', { + defaultMessage: `Returns a string representation of a date in the provided format. If no format is specified, the "yyyy-MM-dd'T'HH:mm:ss.SSSZ" format is used.`, + }), + signatures: [ + { + params: [ + { name: 'field', type: 'date' }, + { name: 'format_string', type: 'string', optional: true }, + ], + returnType: 'string', + examples: [ + 'from index where field="value" | eval hired = date_format(hire_date, "YYYY-MM-dd")', + ], + }, + ], + }, + { + name: 'date_trunc', + description: i18n.translate('monaco.esql.autocomplete.dateTruncDoc', { + defaultMessage: `Rounds down a date to the closest interval. Intervals can be expressed using the timespan literal syntax.`, + }), + signatures: [ + { + params: [ + { name: 'time', type: 'time_literal' }, + { name: 'field', type: 'date' }, + ], + returnType: 'date', + examples: [ + `from index where field="value" | eval year_hired = DATE_TRUNC(1 year, hire_date)`, + ], + }, + ], + }, + { + name: 'date_parse', + description: i18n.translate('monaco.esql.autocomplete.dateParseDoc', { + defaultMessage: `Parse dates from strings.`, + }), + signatures: [ + { + params: [ + { name: 'field', type: 'string' }, + { name: 'format_string', type: 'string' }, + ], + returnType: 'date', + examples: [ + `from index where field="value" | eval year_hired = date_parse(hire_date, yyyy-MM-dd'T'HH:mm:ss.SSS'Z')`, + ], + }, + ], + }, + { + name: 'auto_bucket', + description: i18n.translate('monaco.esql.autocomplete.autoBucketDoc', { + defaultMessage: `Automatically bucket dates based on a given range and bucket target.`, + }), + signatures: [ + { + params: [ + { name: 'field', type: 'date' }, + { name: 'buckets', type: 'number' }, + { name: 'startDate', type: 'string' }, + { name: 'endDate', type: 'string' }, + ], + returnType: 'date', + examples: [ + 'from index where field="value" | eval hd = auto_bucket(hire_date, 20, "1985-01-01T00:00:00Z", "1986-01-01T00:00:00Z")', + ], + }, + { + params: [ + { name: 'field', type: 'date' }, + { name: 'buckets', type: 'number' }, + { name: 'startValue', type: 'number' }, + { name: 'endValue', type: 'number' }, + ], + returnType: 'number', + examples: [ + 'from index where field="value" | eval bs = auto_bucket(salary, 20, 25324, 74999)', + ], + }, + ], + }, + { + name: 'is_finite', + description: i18n.translate('monaco.esql.autocomplete.isFiniteDoc', { + defaultMessage: 'Returns a boolean that indicates whether its input is a finite number.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'boolean', + examples: ['from index where field="value" | eval s = is_finite(field/0)'], + }, + ], + }, + { + name: 'is_infinite', + description: i18n.translate('monaco.esql.autocomplete.isInfiniteDoc', { + defaultMessage: 'Returns a boolean that indicates whether its input is infinite.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'boolean', + examples: ['from index where field="value" | eval s = is_infinite(field/0)'], + }, + ], + }, + { + name: 'case', + description: i18n.translate('monaco.esql.autocomplete.caseDoc', { + defaultMessage: + 'Accepts pairs of conditions and values. The function returns the value that belongs to the first condition that evaluates to `true`. If the number of arguments is odd, the last argument is the default value which is returned when no condition matches.', + }), + signatures: [ + { + params: [ + { name: 'condition', type: 'booleanExpression' }, + { name: 'value', type: 'any' }, + ], + infiniteParams: true, + returnType: 'any', + examples: [ + `from index where field="value" | eval type = case(languages <= 1, "monolingual", languages <= 2, "bilingual", "polyglot")`, + ], + }, + ], + }, + { + name: 'length', + description: i18n.translate('monaco.esql.autocomplete.lengthDoc', { + defaultMessage: 'Returns the character length of a string.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'string' }], + returnType: 'number', + examples: [`from index where field="value" | eval fn_length = length(field)`], + }, + ], + }, + { + name: 'acos', + description: i18n.translate('monaco.esql.autocomplete.acosDoc', { + defaultMessage: 'Inverse cosine trigonometric function', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval acos = acos(field)`], + }, + ], + }, + { + name: 'asin', + description: i18n.translate('monaco.esql.autocomplete.asinDoc', { + defaultMessage: 'Inverse sine trigonometric function', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval asin = asin(field)`], + }, + ], + }, + { + name: 'atan', + description: i18n.translate('monaco.esql.autocomplete.atanDoc', { + defaultMessage: 'Inverse tangent trigonometric function', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval atan = atan(field)`], + }, + ], + }, + { + name: 'atan2', + description: i18n.translate('monaco.esql.autocomplete.atan2Doc', { + defaultMessage: + 'The angle between the positive x-axis and the ray from the origin to the point (x , y) in the Cartesian plane', + }), + signatures: [ + { + params: [ + { name: 'x', type: 'number' }, + { name: 'y', type: 'number' }, + ], + returnType: 'number', + examples: [`from index where field="value" | eval atan2 = atan2(x, y)`], + }, + ], + }, + { + name: 'coalesce', + description: i18n.translate('monaco.esql.autocomplete.coalesceDoc', { + defaultMessage: 'Returns the first non-null value.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'any' }], + infiniteParams: true, + returnType: 'any', + examples: [`ROW a=null, b="b" | EVAL COALESCE(a, b)`], + }, + ], + }, + { + name: 'cos', + description: i18n.translate('monaco.esql.autocomplete.cosDoc', { + defaultMessage: 'Cosine trigonometric function', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval cos = cos(field)`], + }, + ], + }, + { + name: 'cosh', + description: i18n.translate('monaco.esql.autocomplete.coshDoc', { + defaultMessage: 'Cosine hyperbolic function', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval cosh = cosh(field)`], + }, + ], + }, + { + name: 'floor', + description: i18n.translate('monaco.esql.autocomplete.floorDoc', { + defaultMessage: 'Round a number down to the nearest integer.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`from index where field="value" | eval a = floor(field)`], + }, + ], + }, + { + name: 'greatest', + description: i18n.translate('monaco.esql.autocomplete.greatestDoc', { + defaultMessage: 'Returns the maximum value from many columns.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + infiniteParams: true, + returnType: 'number', + examples: [`ROW a = 10, b = 20 | EVAL g = GREATEST(a, b)`], + }, + ], + }, + { + name: 'left', + description: i18n.translate('monaco.esql.autocomplete.leftDoc', { + defaultMessage: + 'Return the substring that extracts length chars from the string starting from the left.', + }), + signatures: [ + { + params: [ + { name: 'field', type: 'string' }, + { name: 'length', type: 'number' }, + ], + returnType: 'string', + examples: [`from index where field="value" | eval substr = left(field, 3)`], + }, + ], + }, + { + name: 'ltrim', + description: i18n.translate('monaco.esql.autocomplete.ltrimDoc', { + defaultMessage: 'Removes leading whitespaces from strings.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'string' }], + returnType: 'string', + examples: [`ROW message = " some text "| EVAL message = LTRIM(message)`], + }, + ], + }, + { + name: 'now', + description: i18n.translate('monaco.esql.autocomplete.nowDoc', { + defaultMessage: 'Returns current date and time.', + }), + signatures: [ + { + params: [], + returnType: 'date', + examples: [`ROW current_date = NOW()`], + }, + ], + }, + { + name: 'right', + description: i18n.translate('monaco.esql.autocomplete.rightDoc', { + defaultMessage: + 'Return the substring that extracts length chars from the string starting from the right.', + }), + signatures: [ + { + params: [ + { name: 'field', type: 'string' }, + { name: 'length', type: 'number' }, + ], + returnType: 'string', + examples: [`from index where field="value" | eval string = right(field, 3)`], + }, + ], + }, + { + name: 'rtrim', + description: i18n.translate('monaco.esql.autocomplete.rtrimDoc', { + defaultMessage: 'Removes trailing whitespaces from strings.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'string' }], + returnType: 'string', + examples: [`ROW message = " some text " | EVAL message = RTRIM(message)`], + }, + ], + }, + { + name: 'sin', + description: i18n.translate('monaco.esql.autocomplete.sinDoc', { + defaultMessage: 'Sine trigonometric function.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`ROW a=1.8 | EVAL sin=SIN(a)`], + }, + ], + }, + { + name: 'sinh', + description: i18n.translate('monaco.esql.autocomplete.sinhDoc', { + defaultMessage: 'Sine hyperbolic function.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`ROW a=1.8 | EVAL sinh=SINH(a)`], + }, + ], + }, + { + name: 'sqrt', + description: i18n.translate('monaco.esql.autocomplete.sqrtDoc', { + defaultMessage: 'Returns the square root of a number. ', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`ROW d = 100.0 | EVAL s = SQRT(d)`], + }, + ], + }, + { + name: 'tan', + description: i18n.translate('monaco.esql.autocomplete.tanDoc', { + defaultMessage: 'Tangent trigonometric function.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`ROW a=1.8 | EVAL tan=TAN(a)`], + }, + ], + }, + { + name: 'tanh', + description: i18n.translate('monaco.esql.autocomplete.tanhDoc', { + defaultMessage: 'Tangent hyperbolic function.', + }), + signatures: [ + { + params: [{ name: 'field', type: 'number' }], + returnType: 'number', + examples: [`ROW a=1.8 | EVAL tanh=TANH(a)`], + }, + ], + }, +].sort(({ name: a }, { name: b }) => a.localeCompare(b)); + +function printArguments({ + name, + type, + optional, + reference, +}: { + name: string; + type: string | string[]; + optional?: boolean; + reference?: string; +}): string { + return `${name}${optional ? ':?' : ':'} ${Array.isArray(type) ? type.join(' | ') : type}`; +} + +export const mathCommandDefinition: AutocompleteCommandDefinition[] = + mathCommandFullDefinitions.map(({ name, description, signatures }) => ({ + label: name, + insertText: name, + kind: 1, + detail: description, + documentation: { + value: buildFunctionDocumentation( + signatures.map(({ params, returnType, infiniteParams, examples }) => ({ + declaration: `${name}(${params.map(printArguments).join(', ')}${ + infiniteParams ? ` ,[... ${params.map(printArguments)}]` : '' + }): ${returnType}`, + examples, + })) + ), + }, + sortText: 'C', + })); export const aggregationFunctionsDefinitions: AutocompleteCommandDefinition[] = [ { @@ -84,4 +846,75 @@ export const aggregationFunctionsDefinitions: AutocompleteCommandDefinition[] = }, sortText: 'C', }, + { + label: 'count', + insertText: 'count', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.countDoc', { + defaultMessage: 'Returns the count of the values in a field.', + }), + documentation: { + value: buildDocumentation('count(grouped[T]): aggregated[T]', [ + 'from index | stats count = count(field)', + ]), + }, + sortText: 'C', + }, + { + label: 'count_distinct', + insertText: 'count_distinct', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.countDistinctDoc', { + defaultMessage: 'Returns the count of distinct values in a field.', + }), + documentation: { + value: buildDocumentation('count(grouped[T]): aggregated[T]', [ + 'from index | stats count = count_distinct(field)', + ]), + }, + sortText: 'C', + }, + { + label: 'median', + insertText: 'median', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.medianDoc', { + defaultMessage: 'Returns the 50% percentile.', + }), + documentation: { + value: buildDocumentation('count(grouped[T]): aggregated[T]', [ + 'from index | stats count = median(field)', + ]), + }, + sortText: 'C', + }, + { + label: 'median_absolute_deviation', + insertText: 'median_absolute_deviation', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.medianDeviationDoc', { + defaultMessage: + 'Returns the median of each data point’s deviation from the median of the entire sample.', + }), + documentation: { + value: buildDocumentation('count(grouped[T]): aggregated[T]', [ + 'from index | stats count = median_absolute_deviation(field)', + ]), + }, + sortText: 'C', + }, + { + label: 'percentile', + insertText: 'percentile', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.percentiletDoc', { + defaultMessage: 'Returns the n percentile of a field.', + }), + documentation: { + value: buildDocumentation('percentile(grouped[T]): aggregated[T]', [ + 'from index | stats pct = percentile(field, 90)', + ]), + }, + sortText: 'C', + }, ]; diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/index.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/index.ts index ef096d678acc3..e1fb514cfa4de 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/index.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/index.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ -export { aggregationFunctionsDefinitions, roundCommandDefinition } from './functions_commands'; +export { + aggregationFunctionsDefinitions, + mathCommandDefinition, + whereCommandDefinition, +} from './functions_commands'; export { sourceCommandsDefinitions } from './source_commands'; export { processingCommandsDefinitions, pipeDefinition } from './processing_commands'; @@ -17,6 +21,7 @@ export { export { mathOperatorsCommandsDefinitions, assignOperatorDefinition, + asOperatorDefinition, byOperatorDefinition, openBracketDefinition, closeBracketDefinition, diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/operators_commands.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/operators_commands.ts index 21a5f6260cedd..91ccb74cb9501 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/operators_commands.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/operators_commands.ts @@ -11,7 +11,7 @@ import type { AutocompleteCommandDefinition } from '../types'; export const byOperatorDefinition: AutocompleteCommandDefinition = { label: 'by', - insertText: 'by ', + insertText: 'by', kind: 21, detail: i18n.translate('monaco.esql.autocomplete.byDoc', { defaultMessage: 'By', @@ -19,6 +19,36 @@ export const byOperatorDefinition: AutocompleteCommandDefinition = { sortText: 'D', }; +export const onOperatorDefinition: AutocompleteCommandDefinition = { + label: 'on', + insertText: 'on', + kind: 21, + detail: i18n.translate('monaco.esql.autocomplete.onDoc', { + defaultMessage: 'On', + }), + sortText: 'D', +}; + +export const withOperatorDefinition: AutocompleteCommandDefinition = { + label: 'with', + insertText: 'with', + kind: 21, + detail: i18n.translate('monaco.esql.autocomplete.withDoc', { + defaultMessage: 'With', + }), + sortText: 'D', +}; + +export const asOperatorDefinition: AutocompleteCommandDefinition = { + label: 'as', + insertText: 'as', + kind: 11, + detail: i18n.translate('monaco.esql.autocomplete.asDoc', { + defaultMessage: 'As', + }), + sortText: 'D', +}; + export const assignOperatorDefinition: AutocompleteCommandDefinition = { label: '=', insertText: '=', diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/processing_commands.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/processing_commands.ts index 8dbc1ebe3d9c0..5fe6969f3eddb 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/processing_commands.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/processing_commands.ts @@ -62,6 +62,47 @@ export const processingCommandsDefinitions: AutocompleteCommandDefinition[] = [ documentation: { value: buildDocumentation('eval columns = fieldSpecification ( `,` fieldSpecification )*', [ '… | eval a = b * c', + '… | eval then = 1 year + 2 weeks', + ]), + }, + sortText: 'B', + }, + { + label: 'keep', + insertText: 'keep', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.keepDoc', { + defaultMessage: 'Rearranges fields in the input table by applying the keep clauses in fields', + }), + documentation: { + value: buildDocumentation('keep fieldSpecification `,` fieldSpecification *', [ + '… | keep a,b', + ]), + }, + sortText: 'B', + }, + { + label: 'rename', + insertText: 'rename', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.renameDoc', { + defaultMessage: 'Renames an old column to a new one', + }), + documentation: { + value: buildDocumentation('rename new as old', ['… | rename a as b']), + }, + sortText: 'B', + }, + { + label: 'drop', + insertText: 'drop', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.dropDoc', { + defaultMessage: 'Drops columns', + }), + documentation: { + value: buildDocumentation('drop fieldSpecification `,` fieldSpecification *', [ + '… | drop a,b', ]), }, sortText: 'B', @@ -96,4 +137,61 @@ export const processingCommandsDefinitions: AutocompleteCommandDefinition[] = [ }, sortText: 'B', }, + { + label: 'dissect', + insertText: 'dissect', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.dissectDoc', { + defaultMessage: + 'Extracts multiple string values from a single string input, based on a pattern', + }), + documentation: { + value: buildDocumentation( + 'dissect (append_separator=)?', + ['… | dissect a "%{b} %{c}";'] + ), + }, + sortText: 'B', + }, + { + label: 'grok', + insertText: 'grok', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.grokDoc', { + defaultMessage: + 'Extracts multiple string values from a single string input, based on a pattern', + }), + documentation: { + value: buildDocumentation('grok ', [ + '… | grok a "%{b} %{c}";', + ]), + }, + sortText: 'B', + }, + { + label: 'mv_expand', + insertText: 'mv_expand', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.mvExpandDoc', { + defaultMessage: 'Expands multivalued fields into one row per value, duplicating other fields', + }), + documentation: { + value: buildDocumentation('mv_expand field', [ + 'ROW a=[1,2,3], b="b", j=["a","b"] | MV_EXPAND a', + ]), + }, + sortText: 'B', + }, + { + label: 'enrich', + insertText: 'enrich', + kind: 1, + detail: i18n.translate('monaco.esql.autocomplete.enrichDoc', { + defaultMessage: 'Enrich table with another table', + }), + documentation: { + value: buildDocumentation('enrich policy', ['... | ENRICH a']), + }, + sortText: 'B', + }, ]; diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/utils.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/utils.ts index 87b0c6dc087aa..0166c94210f8d 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/utils.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/utils.ts @@ -16,6 +16,48 @@ const examplesLabel = i18n.translate('monaco.esql.autocomplete.examplesLabel', { defaultMessage: 'Examples:', }); +/** @internal */ +export const buildFunctionDocumentation = ( + signatures: Array<{ + declaration: string; + examples?: string[]; + }> +) => ` +--- +\ +***${declarationLabel}*** +${signatures + .map( + ({ declaration }) => ` +\ + - \`\`${declaration}\`\` +\ +` + ) + .join('\n\n')} +--- +${ + signatures.some((examples) => examples) + ? `\ +***${examplesLabel}*** +\ +${signatures + .filter(({ examples }) => examples) + .map( + ({ examples }) => ` + ${examples! + .map( + (i) => ` + - \`\`${i}\`\`) +` + ) + .join('')} + +` + )}` + : '' +}`; + /** @internal **/ export const buildDocumentation = (declaration: string, examples?: string[]) => ` --- diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.test.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.test.ts index 157d111154f1f..a33c9f99f6f9e 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.test.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.test.ts @@ -13,6 +13,8 @@ import { ANTLREErrorListener } from '../../../common/error_listener'; import { getParser, ROOT_STATEMENT } from '../antlr_facade'; import { isDynamicAutocompleteItem } from './dymanic_item'; +import { getDurationItemsWithQuantifier } from './helpers'; +import { mathCommandDefinition } from './autocomplete_definitions/functions_commands'; describe('autocomplete_listener', () => { const getAutocompleteSuggestions = (text: string) => { @@ -38,12 +40,22 @@ describe('autocomplete_listener', () => { testSuggestions('f', ['from']); testSuggestions('from ', ['SourceIdentifier']); testSuggestions('from a,', ['SourceIdentifier']); - testSuggestions('from a, b ', ['|']); + testSuggestions('from a, b ', ['SourceIdentifier']); }); describe('where', () => { - testSuggestions('from a | where ', ['FieldIdentifier']); - testSuggestions('from a | where "field" ', ['==', '!=', '<', '>', '<=', '>=']); + testSuggestions('from a | where ', ['cidr_match', 'FieldIdentifier']); + testSuggestions('from a | where "field" ', [ + '==', + '!=', + '<', + '>', + '<=', + '>=', + 'like', + 'rlike', + 'in', + ]); testSuggestions('from a | where "field" >= ', ['FieldIdentifier']); testSuggestions('from a | where "field" >= "field1" ', ['or', 'and', '|']); testSuggestions('from a | where "field" >= "field1" and ', ['FieldIdentifier']); @@ -54,9 +66,32 @@ describe('autocomplete_listener', () => { '>', '<=', '>=', + 'like', + 'rlike', + 'in', + ]); + testSuggestions('from a | stats a=avg("field") | where a ', [ + '==', + '!=', + '<', + '>', + '<=', + '>=', + 'like', + 'rlike', + 'in', + ]); + testSuggestions('from a | stats a=avg("b") | where "c" ', [ + '==', + '!=', + '<', + '>', + '<=', + '>=', + 'like', + 'rlike', + 'in', ]); - testSuggestions('from a | stats a=avg("field") | where a ', ['==', '!=', '<', '>', '<=', '>=']); - testSuggestions('from a | stats a=avg("b") | where "c" ', ['==', '!=', '<', '>', '<=', '>=']); testSuggestions('from a | where "field" >= "field1" and "field2 == ', ['FieldIdentifier']); }); @@ -72,11 +107,25 @@ describe('autocomplete_listener', () => { testSuggestions('from a | limit 4 ', ['|']); }); + describe('mv_expand', () => { + testSuggestions('from a | mv_expand ', ['FieldIdentifier']); + testSuggestions('from a | mv_expand a ', ['|']); + }); + describe('stats', () => { testSuggestions('from a | stats ', ['var0']); testSuggestions('from a | stats a ', ['=']); - testSuggestions('from a | stats a=', ['avg', 'max', 'min', 'sum', 'FieldIdentifier']); - testSuggestions('from a | stats a=b', ['|', 'by']); + testSuggestions('from a | stats a=', [ + 'avg', + 'max', + 'min', + 'sum', + 'count', + 'count_distinct', + 'median', + 'median_absolute_deviation', + 'percentile', + ]); testSuggestions('from a | stats a=b by ', ['FieldIdentifier']); testSuggestions('from a | stats a=c by d', ['|']); testSuggestions('from a | stats a=b, ', ['var0']); @@ -90,20 +139,88 @@ describe('autocomplete_listener', () => { testSuggestions('from a | stats a=min(b), b=max(', ['FieldIdentifier']); }); + describe('enrich', () => { + for (const prevCommand of [ + '', + '| enrich other-policy ', + '| enrich other-policy on b ', + '| enrich other-policy with c ', + ]) { + testSuggestions(`from a ${prevCommand}| enrich`, ['PolicyIdentifier']); + testSuggestions(`from a ${prevCommand}| enrich policy `, ['|', 'on', 'with']); + testSuggestions(`from a ${prevCommand}| enrich policy on `, [ + 'PolicyMatchingFieldIdentifier', + ]); + testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['|', 'with']); + testSuggestions(`from a ${prevCommand}| enrich policy on b with `, [ + 'var0', + 'PolicyFieldIdentifier', + ]); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 `, ['=', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = `, [ + 'PolicyFieldIdentifier', + ]); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = c `, ['|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = c, `, [ + 'var1', + 'PolicyFieldIdentifier', + ]); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = c, var1 `, ['=', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = c, var1 = `, [ + 'PolicyFieldIdentifier', + ]); + testSuggestions(`from a ${prevCommand}| enrich policy with `, [ + 'var0', + 'PolicyFieldIdentifier', + ]); + testSuggestions(`from a ${prevCommand}| enrich policy with c`, ['=', '|']); + } + }); + describe('eval', () => { + const functionSuggestions = mathCommandDefinition.map(({ label }) => String(label)); + testSuggestions('from a | eval ', ['var0']); testSuggestions('from a | eval a ', ['=']); - testSuggestions('from a | eval a=', ['round', 'FieldIdentifier']); - testSuggestions('from a | eval a=b', ['|', '+', '-', '/', '*']); + testSuggestions('from a | eval a=', functionSuggestions); testSuggestions('from a | eval a=b, ', ['var0']); testSuggestions('from a | eval a=round', ['(']); testSuggestions('from a | eval a=round(', ['FieldIdentifier']); testSuggestions('from a | eval a=round(b) ', ['|', '+', '-', '/', '*']); testSuggestions('from a | eval a=round(b),', ['var0']); - testSuggestions('from a | eval a=round(b) +', ['FieldIdentifier']); + testSuggestions('from a | eval a=round(b) + ', ['FieldIdentifier', ...functionSuggestions]); + // NOTE: this is handled also partially in the suggestion wrapper with auto-injection of closing brackets testSuggestions('from a | eval a=round(b', [')', 'FieldIdentifier']); testSuggestions('from a | eval a=round(b), b=round(', ['FieldIdentifier']); testSuggestions('from a | stats a=round(b), b=round(', ['FieldIdentifier']); testSuggestions('from a | eval var0=round(b), var1=round(c) | stats ', ['var2']); + + describe('date math', () => { + const dateSuggestions = [ + 'year', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + 'millisecond', + ].flatMap((v) => [v, `${v}s`]); + const dateMathSymbols = ['+', '-']; + testSuggestions('from a | eval a = 1 ', dateMathSymbols.concat(dateSuggestions, ['|'])); + testSuggestions('from a | eval a = 1 year ', dateMathSymbols.concat(dateSuggestions, ['|'])); + testSuggestions( + 'from a | eval a = 1 day + 2 ', + dateMathSymbols.concat(dateSuggestions, ['|']) + ); + testSuggestions( + 'from a | eval var0=date_trunc(', + ['FieldIdentifier'].concat(...getDurationItemsWithQuantifier().map(({ label }) => label)) + ); + testSuggestions( + 'from a | eval var0=date_trunc(2 ', + [')', 'FieldIdentifier'].concat(dateSuggestions) + ); + }); }); }); diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.ts index d3cda17124349..35c75e6ce6021 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_listener.ts @@ -10,7 +10,13 @@ import type { AutocompleteCommandDefinition, UserDefinedVariables } from './type import { DynamicAutocompleteItem } from './dymanic_item'; import { esql_parserListener as ESQLParserListener } from '../../antlr/esql_parser_listener'; -import { esql_parser, esql_parser as ESQLParser } from '../../antlr/esql_parser'; +import { + esql_parser, + esql_parser as ESQLParser, + EnrichCommandContext, + EnrichWithClauseContext, + OperatorExpressionContext, +} from '../../antlr/esql_parser'; import { processingCommandsDefinitions, @@ -26,10 +32,12 @@ import { closeBracketDefinition, mathOperatorsCommandsDefinitions, aggregationFunctionsDefinitions, - roundCommandDefinition, + mathCommandDefinition, + whereCommandDefinition, assignOperatorDefinition, buildConstantsDefinitions, buildNewVarDefinition, + asOperatorDefinition, } from './autocomplete_definitions'; import { @@ -45,22 +53,56 @@ import { SourceIdentifierContext, UserVariableContext, BooleanExpressionContext, + RegexBooleanExpressionContext, + WhereBooleanExpressionContext, LimitCommandContext, ValueExpressionContext, + KeepCommandContext, + DropCommandContext, + RenameCommandContext, + DissectCommandContext, + GrokCommandContext, + MvExpandCommandContext, } from '../../antlr/esql_parser'; +import { + onOperatorDefinition, + withOperatorDefinition, +} from './autocomplete_definitions/operators_commands'; +import { dateExpressionDefinitions } from './autocomplete_definitions/date_math_expressions'; +import { + endsWithOpenBracket, + getDateMathOperation, + getDurationItemsWithQuantifier, + isDateFunction, +} from './helpers'; + +export function nonNullable(v: T): v is NonNullable { + return v != null; +} export class AutocompleteListener implements ESQLParserListener { private suggestions: Array = []; private readonly userDefinedVariables: UserDefinedVariables = { sourceIdentifiers: [], + policyIdentifiers: [], }; private readonly tables: string[][] = []; private parentContext: number | undefined; - private get fields() { - return this.tables.length > 1 - ? buildConstantsDefinitions(this.tables.at(-2)!) - : [DynamicAutocompleteItem.FieldIdentifier]; + private get fields(): [DynamicAutocompleteItem] { + return [DynamicAutocompleteItem.FieldIdentifier]; + } + + private get policies(): [DynamicAutocompleteItem] { + return [DynamicAutocompleteItem.PolicyIdentifier]; + } + + private get policyFields(): [DynamicAutocompleteItem] { + return [DynamicAutocompleteItem.PolicyFieldIdentifier]; + } + + private get policyMatchingField(): [DynamicAutocompleteItem] { + return [DynamicAutocompleteItem.PolicyMatchingFieldIdentifier]; } private get hasSuggestions() { @@ -71,16 +113,54 @@ export class AutocompleteListener implements ESQLParserListener { return node && node.payload?.startIndex >= 0; } - private getEndCommandSuggestions(skipDefinitions: AutocompleteCommandDefinition[] = []) { - const suggestions = [pipeDefinition]; + private inspectOperatorExpressionContext( + context: OperatorExpressionContext | OperatorExpressionContext[] | undefined, + innerScope: 'constant' | 'dateExpression' | 'booleanExpression' + ): boolean { + if (!context) { + return false; + } + if (Array.isArray(context)) { + return context.some((c) => this.inspectOperatorExpressionContext(c, innerScope)); + } + if (context.operatorExpression()?.length) { + return this.inspectOperatorExpressionContext(context.operatorExpression(), innerScope); + } + if (context.primaryExpression()) { + return Boolean(context.primaryExpression()?.[innerScope]()); + } + return false; + } - if ( - !skipDefinitions.find((i) => i === byOperatorDefinition) && - this.parentContext === ESQLParser.STATS - ) { - suggestions.push(byOperatorDefinition); + private hasDateExpressionTerminalNode( + context: OperatorExpressionContext | OperatorExpressionContext[] | undefined + ): boolean { + return this.inspectOperatorExpressionContext(context, 'dateExpression'); + } + + private hasOnlyConstantDefined( + context: OperatorExpressionContext | OperatorExpressionContext[] | undefined + ): boolean { + return this.inspectOperatorExpressionContext(context, 'constant'); + } + + private applyConditionalSuggestion( + skipDefinitions: AutocompleteCommandDefinition[], + targetDefinition: AutocompleteCommandDefinition, + context: number + ) { + if (!skipDefinitions.find((i) => i === targetDefinition) && this.parentContext === context) { + return targetDefinition; } - return suggestions; + } + + private getEndCommandSuggestions(skipDefinitions: AutocompleteCommandDefinition[] = []) { + return [ + pipeDefinition, + this.applyConditionalSuggestion(skipDefinitions, byOperatorDefinition, ESQLParser.STATS), + this.applyConditionalSuggestion(skipDefinitions, onOperatorDefinition, ESQLParser.ENRICH), + this.applyConditionalSuggestion(skipDefinitions, withOperatorDefinition, ESQLParser.ENRICH), + ].filter(nonNullable); } private getNewVarName() { @@ -112,11 +192,13 @@ export class AutocompleteListener implements ESQLParserListener { exitSourceCommand(ctx: SourceCommandContext) { if (ctx.exception) { this.suggestions = sourceCommandsDefinitions; - } else if (!this.hasSuggestions) { - this.suggestions = this.getEndCommandSuggestions(); } } + enterSourceIdentifier(ctx: SourceIdentifierContext) { + this.suggestions = [DynamicAutocompleteItem.SourceIdentifier]; + } + exitSourceIdentifier(ctx: SourceIdentifierContext) { if (!ctx.childCount) { this.suggestions = [DynamicAutocompleteItem.SourceIdentifier]; @@ -141,6 +223,11 @@ export class AutocompleteListener implements ESQLParserListener { enterStatsCommand(ctx: StatsCommandContext) { this.suggestions = []; this.parentContext = ESQLParser.STATS; + const fn = ctx.fields(); + if (!fn) { + this.suggestions = [buildNewVarDefinition(this.getNewVarName())]; + return; + } } enterEvalCommand(ctx: EvalCommandContext) { @@ -155,7 +242,84 @@ export class AutocompleteListener implements ESQLParserListener { } } + exitKeepCommand?(ctx: KeepCommandContext) { + const qn = ctx.qualifiedNames(); + if (qn && qn.text) { + if (qn.text.slice(-1) !== ',') { + this.suggestions = this.getEndCommandSuggestions(); + } + } + } + + exitDropCommand?(ctx: DropCommandContext) { + const qn = ctx.qualifiedNames(); + if (qn && qn.text) { + if (qn.text.slice(-1) !== ',') { + this.suggestions = this.getEndCommandSuggestions(); + } + } + } + + enterRenameCommand(ctx: RenameCommandContext) { + this.parentContext = ESQLParser.RENAME; + } + + exitRenameCommand?(ctx: RenameCommandContext) { + const rc = ctx.renameClause(); + const commaExists = ctx.COMMA(); + if (!rc[0].exception) { + const qn = rc[0].renameVariable(); + const asExists = this.isTerminalNodeExists(rc[0].AS()); + if (asExists && qn && !qn.text) { + this.suggestions = []; + } + if (qn && qn.text) { + if (!commaExists.length) { + this.suggestions = this.getEndCommandSuggestions(); + } + } + } + } + + exitDissectCommand?(ctx: DissectCommandContext) { + const qn = ctx.qualifiedNames(); + const pattern = ctx.string(); + if (qn && qn.text && pattern && pattern.text && pattern.text !== '') { + this.suggestions = this.getEndCommandSuggestions(); + } + } + + exitGrokCommand?(ctx: GrokCommandContext) { + const qn = ctx.qualifiedNames(); + const pattern = ctx.string(); + if (qn && qn.text && pattern && pattern.text && pattern.text !== '') { + this.suggestions = this.getEndCommandSuggestions(); + } + } + + exitMvExpandCommand?(ctx: MvExpandCommandContext) { + const qn = ctx.qualifiedNames(); + if (qn && qn.text) { + this.suggestions = this.getEndCommandSuggestions(); + } + } + exitQualifiedName(ctx: QualifiedNameContext) { + const isInEval = this.parentContext === ESQLParser.EVAL; + const isInStats = this.parentContext === ESQLParser.STATS; + const isInRename = this.parentContext === ESQLParser.RENAME; + if (this.parentContext && isInRename) { + if (!ctx.exception && ctx.text) { + this.suggestions = [asOperatorDefinition]; + } + } + if (this.parentContext && (isInStats || isInEval)) { + this.suggestions = [ + ...this.getEndCommandSuggestions(), + ...(isInEval ? mathOperatorsCommandsDefinitions : []), + ]; + } + if ( ctx .identifier() @@ -205,12 +369,12 @@ export class AutocompleteListener implements ESQLParserListener { const ve = ctx.valueExpression(); if (!ve) { if (this.parentContext === ESQLParser.STATS) { - this.suggestions = [...aggregationFunctionsDefinitions, ...this.fields]; + this.suggestions = [...aggregationFunctionsDefinitions]; return; } if (this.parentContext === ESQLParser.EVAL) { - this.suggestions = [roundCommandDefinition, ...this.fields]; + this.suggestions = [...mathCommandDefinition]; return; } } @@ -221,54 +385,215 @@ export class AutocompleteListener implements ESQLParserListener { const isInStats = this.parentContext === ESQLParser.STATS; const isInEval = this.parentContext === ESQLParser.EVAL; - if (this.parentContext && (isInStats || isInEval)) { - const hasFN = ctx.tryGetToken(esql_parser.UNARY_FUNCTION, 0); + if (isInStats || isInEval) { + const hasFN = + ctx.tryGetToken(esql_parser.UNARY_FUNCTION, 0) || + ctx.tryGetToken(esql_parser.MATH_FUNCTION, 0); const hasLP = ctx.tryGetToken(esql_parser.LP, 0); const hasRP = ctx.tryGetToken(esql_parser.RP, 0); + // TODO: handle also other math signs later on + const hasPlusOrMinus = + ctx.tryGetToken(esql_parser.PLUS, 0) || ctx.tryGetToken(esql_parser.MINUS, 0); + + const hasDateLiteral = ctx.tryGetToken(esql_parser.DATE_LITERAL, 0); + + const isInDurationMode = hasDateLiteral || (hasFN && isDateFunction(hasFN.text)); + if (hasPlusOrMinus && this.isTerminalNodeExists(hasPlusOrMinus)) { + if (isInEval) { + this.suggestions = isInDurationMode + ? // eval a = 1 year + || eval a = date_trunc(1 year, date) - + [ + ...mathCommandDefinition.filter(({ label }) => isDateFunction(String(label))), + ...getDurationItemsWithQuantifier(), + ] + : // eval a = 1 + || eval a = abs(b) - + [...this.fields, ...mathCommandDefinition]; + } else { + this.suggestions = [...this.fields, ...aggregationFunctionsDefinitions]; + } + return; + } + // Monaco will auto close the brackets but the language listener will not pick up yet this auto-change. + // We try to inject it outside but it won't cover all scenarios if (hasFN) { if (!hasLP) { this.suggestions = [openBracketDefinition]; return; } + + this.suggestions = []; + if (!hasRP) { if (ctx.childCount === 3) { - this.suggestions = [closeBracketDefinition, ...this.fields]; - return; + // TODO: improve here to suggest comma if signature has multiple args + this.suggestions.push(closeBracketDefinition); + } + } + this.suggestions.push(...this.fields); + // Need to get the function name from the previous node (current is "(" ) + const fnName = hasFN.text; + const fnsToCheck = isInEval ? mathCommandDefinition : aggregationFunctionsDefinitions; + if (fnName && fnsToCheck.some(({ label }) => label === fnName)) { + // push date suggestions only for date functions + // TODO: improve this checks + if (isInEval && isDateFunction(fnName)) { + if (!ctx.tryGetToken(esql_parser.DATE_LITERAL, 0)) { + this.suggestions.push( + // if it's just after the open bracket, suggest also a number together with a date period, + // otherwise just the date period unit + ...(endsWithOpenBracket(ctx.text) + ? getDurationItemsWithQuantifier() + : dateExpressionDefinitions) + ); + } } } + + return; } else { if (ctx.childCount === 1) { - this.suggestions = [ - ...this.getEndCommandSuggestions(), - ...(isInEval ? mathOperatorsCommandsDefinitions : []), - ]; + if (ctx.text && ctx.text.indexOf('(') === -1) { + this.suggestions = [...mathOperatorsCommandsDefinitions]; + if (isInEval) { + // eval a = 1 || eval a = 1 year + 1 + if ( + this.hasDateExpressionTerminalNode(ctx.operatorExpression()) || + this.hasOnlyConstantDefined(ctx.operatorExpression()) + ) { + this.suggestions = [...getDateMathOperation(), ...dateExpressionDefinitions]; + } + } + + if (isInStats) { + this.suggestions.push(...aggregationFunctionsDefinitions); + } + + this.suggestions.push(...this.getEndCommandSuggestions()); + } return; } } - this.suggestions = this.fields; + this.suggestions = [...this.fields]; + if (ctx.exception && isInEval) { + // case: eval a = x * or / + this.suggestions.push(...mathCommandDefinition); + } + this.suggestions.push(...this.getEndCommandSuggestions()); } } + enterWhereBooleanExpression(ctx: WhereBooleanExpressionContext) { + this.suggestions = []; + } + enterWhereCommand(ctx: WhereCommandContext) { this.suggestions = []; this.parentContext = ESQLParser.WHERE; } + enterEnrichCommand(ctx: EnrichCommandContext) { + this.suggestions = []; + this.parentContext = ESQLParser.ENRICH; + } + + exitEnrichCommand(ctx: EnrichCommandContext) { + const policyName = ctx.enrichIdentifier().text; + if (policyName && !this.userDefinedVariables.policyIdentifiers.includes(policyName)) { + this.userDefinedVariables.policyIdentifiers.push(policyName); + } + + if (this.parentContext === ESQLParser.WITH) { + return; + } + if (!policyName) { + this.suggestions = this.policies; + } + + if (policyName) + if (this.parentContext === ESQLParser.ENRICH) { + const hasOn = this.isTerminalNodeExists(ctx.ON()); + if (hasOn && !ctx._matchField.text) { + this.suggestions = this.policyMatchingField; + } else { + this.suggestions = this.getEndCommandSuggestions( + hasOn ? [onOperatorDefinition] : undefined + ); + } + } + } + + enterEnrichWithClause(ctx: EnrichWithClauseContext) { + this.suggestions = []; + this.parentContext = ESQLParser.WITH; + } + + exitEnrichWithClause(ctx: EnrichWithClauseContext) { + const hasAssign = this.isTerminalNodeExists(ctx.ASSIGN()); + // Note: this gets filled only after the assign operation :( + if (ctx._newName?.text) { + this.tables.at(-1)?.push(ctx._newName.text); + } + + if (!ctx.exception && ctx.enrichFieldIdentifier().length === 1) { + // if it's after the assign operator, then suggest the fields from the policy + // TODO: need to check if the enrichFieldIdentifier given is a policyField or not and decide whether append the assignOperator + this.suggestions = !hasAssign + ? [assignOperatorDefinition, ...this.getEndCommandSuggestions()] + : this.policyFields; + } else { + this.suggestions = []; + if (!hasAssign) { + this.suggestions.push(buildNewVarDefinition(this.getNewVarName())); + } + if (!ctx._enrichField?.text) { + this.suggestions.push(...this.policyFields); + } + if (this.suggestions.length === 0) { + this.suggestions = this.getEndCommandSuggestions([ + onOperatorDefinition, + withOperatorDefinition, + ]); + } + } + } + exitWhereCommand(ctx: WhereCommandContext) { - const booleanExpression = ctx.booleanExpression(); + const booleanExpression = ctx.whereBooleanExpression(); if (booleanExpression.exception) { + if (!booleanExpression.text) { + this.suggestions = [...whereCommandDefinition, ...this.fields]; + return; + } this.suggestions = this.fields; return; } else { - const innerBooleanExpressions = booleanExpression.getRuleContexts(BooleanExpressionContext); + const innerBooleanExpressions = booleanExpression.getRuleContexts( + WhereBooleanExpressionContext + ); + const regexBooleanExpression = booleanExpression.getRuleContexts( + RegexBooleanExpressionContext + ); + + if (booleanExpression.WHERE_FUNCTIONS()) { + if (booleanExpression.COMMA().length) { + this.suggestions = []; + return; + } + } + + if (regexBooleanExpression.length) { + this.suggestions = []; + return; + } + if (innerBooleanExpressions.some((be) => be.exception)) { this.suggestions = this.fields; return; } } - if (!this.hasSuggestions) { + if (!this.hasSuggestions && !booleanExpression.WHERE_FUNCTIONS()) { this.suggestions = comparisonCommandsDefinitions; } } diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/dymanic_item.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/dymanic_item.ts index b819dc34059a1..621c8900447a0 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/dymanic_item.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/dymanic_item.ts @@ -9,10 +9,13 @@ export enum DynamicAutocompleteItem { SourceIdentifier = 'SourceIdentifier', FieldIdentifier = 'FieldIdentifier', + PolicyIdentifier = 'PolicyIdentifier', + PolicyFieldIdentifier = 'PolicyFieldIdentifier', + PolicyMatchingFieldIdentifier = 'PolicyMatchingFieldIdentifier', } +const DynamicAutocompleteItems = Object.values(DynamicAutocompleteItem); + export function isDynamicAutocompleteItem(v: unknown): v is DynamicAutocompleteItem { - return ( - v === DynamicAutocompleteItem.SourceIdentifier || v === DynamicAutocompleteItem.FieldIdentifier - ); + return DynamicAutocompleteItems.some((dai) => dai === v); } diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/helpers.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/helpers.ts new file mode 100644 index 0000000000000..be1392cb11e72 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/helpers.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { mathOperatorsCommandsDefinitions } from './autocomplete_definitions'; +import { dateExpressionDefinitions } from './autocomplete_definitions/date_math_expressions'; + +export function endsWithOpenBracket(text: string) { + return /\($/.test(text); +} + +export function isDateFunction(fnName: string) { + // TODO: improve this and rely in signature in the future + return ['to_datetime', 'date_trunc', 'date_parse'].includes(fnName.toLowerCase()); +} + +export function getDateMathOperation() { + return mathOperatorsCommandsDefinitions.filter(({ label }) => ['+', '-'].includes(String(label))); +} + +export function getDurationItemsWithQuantifier(quantifier: number = 1) { + return dateExpressionDefinitions + .filter(({ label }) => !/s$/.test(label.toString())) + .map(({ label, insertText, ...rest }) => ({ + label: `${quantifier} ${label}`, + insertText: `${quantifier} ${insertText}`, + ...rest, + })); +} diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts index 58438baa298a9..0b64f0871b27a 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts @@ -12,17 +12,21 @@ import { monaco } from '../../../..'; export interface ESQLCustomAutocompleteCallbacks { getSourceIdentifiers?: CallbackFn; getFieldsIdentifiers?: CallbackFn; + getPoliciesIdentifiers?: CallbackFn<{ name: string; indices: string[] }>; + getPolicyFieldsIdentifiers?: CallbackFn; + getPolicyMatchingFieldIdentifiers?: CallbackFn; } /** @internal **/ -type CallbackFn = (ctx: { +type CallbackFn = (ctx: { word: string; userDefinedVariables: UserDefinedVariables; -}) => string[] | Promise; +}) => T[] | Promise; /** @internal **/ export interface UserDefinedVariables { sourceIdentifiers: string[]; + policyIdentifiers: string[]; } /** @internal **/ diff --git a/packages/kbn-monaco/src/esql/lib/monaco/esql_completion_provider.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_completion_provider.ts index 40393fe1b844d..4a407c3519769 100644 --- a/packages/kbn-monaco/src/esql/lib/monaco/esql_completion_provider.ts +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_completion_provider.ts @@ -11,7 +11,11 @@ import { DynamicAutocompleteItem, isDynamicAutocompleteItem } from '../autocompl import { buildFieldsDefinitions, buildSourcesDefinitions, + buildPoliciesDefinitions, + buildNoPoliciesAvailableDefinition, + buildMatchingFieldsDefinition, } from '../autocomplete/autocomplete_definitions/dynamic_commands'; +import { pipeDefinition } from '../autocomplete/autocomplete_definitions'; import type { AutocompleteCommandDefinition, @@ -20,11 +24,6 @@ import type { } from '../autocomplete/types'; import type { ESQLWorker } from '../../worker/esql_worker'; -const emptyCompletionList: monaco.languages.CompletionList = { - incomplete: false, - suggestions: [], -}; - export class ESQLCompletionAdapter implements monaco.languages.CompletionItemProvider { constructor( private worker: (...uris: monaco.Uri[]) => Promise, @@ -40,16 +39,20 @@ export class ESQLCompletionAdapter implements monaco.languages.CompletionItemPro userDefinedVariables: UserDefinedVariables; } ): Promise { - let result: AutocompleteCommandDefinition[] = []; - - for (const suggestion of suggestions) { - if (isDynamicAutocompleteItem(suggestion)) { + const allSuggestions: AutocompleteCommandDefinition[][] = await Promise.all( + suggestions.map(async (suggestion) => { + if (!isDynamicAutocompleteItem(suggestion)) { + return [suggestion]; + } let dynamicItems: AutocompleteCommandDefinition[] = []; if (suggestion === DynamicAutocompleteItem.SourceIdentifier) { dynamicItems = buildSourcesDefinitions( (await this.callbacks?.getSourceIdentifiers?.(ctx)) ?? [] ); + if (!ctx.word && ctx.userDefinedVariables.sourceIdentifiers.length) { + dynamicItems = [pipeDefinition]; + } } if (suggestion === DynamicAutocompleteItem.FieldIdentifier) { @@ -57,13 +60,34 @@ export class ESQLCompletionAdapter implements monaco.languages.CompletionItemPro (await this.callbacks?.getFieldsIdentifiers?.(ctx)) ?? [] ); } - result = [...result, ...dynamicItems]; - } else { - result = [...result, suggestion]; - } - } - return result; + if (suggestion === DynamicAutocompleteItem.PolicyIdentifier) { + const results = await this.callbacks?.getPoliciesIdentifiers?.(ctx); + dynamicItems = results?.length + ? buildPoliciesDefinitions(results) + : buildNoPoliciesAvailableDefinition(); + } + + if (suggestion === DynamicAutocompleteItem.PolicyFieldIdentifier) { + dynamicItems = buildFieldsDefinitions( + (await this.callbacks?.getPolicyFieldsIdentifiers?.(ctx)) || [] + ); + } + + if (suggestion === DynamicAutocompleteItem.PolicyMatchingFieldIdentifier) { + const [fields = [], matchingField] = await Promise.all([ + this.callbacks?.getFieldsIdentifiers?.(ctx), + this.callbacks?.getPolicyMatchingFieldIdentifiers?.(ctx), + ]); + dynamicItems = matchingField?.length + ? buildMatchingFieldsDefinition(matchingField[0], fields) + : buildFieldsDefinitions(fields); + } + return dynamicItems; + }) + ); + + return allSuggestions.flat(); } async provideCompletionItems( @@ -72,21 +96,23 @@ export class ESQLCompletionAdapter implements monaco.languages.CompletionItemPro ): Promise { const lines = model.getLineCount(); - if ( + const currentLineChars = model.getValueInRange({ + startLineNumber: 0, + startColumn: 0, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + const wordInfo = model.getWordUntilPosition(position); + const worker = await this.worker(model.uri); + const providedSuggestions = lines !== position.lineNumber || model.getLineContent(position.lineNumber).trimEnd().length >= position.column - ) { - return emptyCompletionList; - } - - const worker = await this.worker(model.uri); - const wordInfo = model.getWordUntilPosition(position); - - const providedSuggestions = await worker.provideAutocompleteSuggestions(model.uri.toString(), { - word: wordInfo.word, - line: position.lineNumber, - index: position.column, - }); + ? await worker.provideAutocompleteSuggestionsFromString(currentLineChars) + : await worker.provideAutocompleteSuggestions(model.uri.toString(), { + word: wordInfo.word, + line: position.lineNumber, + index: position.column, + }); const withDynamicItems = providedSuggestions ? await this.injectDynamicAutocompleteItems(providedSuggestions.suggestions, { @@ -96,7 +122,6 @@ export class ESQLCompletionAdapter implements monaco.languages.CompletionItemPro : []; return { - incomplete: true, suggestions: withDynamicItems.map((i) => ({ ...i, range: { diff --git a/packages/kbn-monaco/src/esql/lib/monaco/esql_theme.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_theme.ts index 94c3c6bbe6897..6fc6caee2886f 100644 --- a/packages/kbn-monaco/src/esql/lib/monaco/esql_theme.ts +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_theme.ts @@ -27,7 +27,6 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'explain', 'row', 'limit', - 'project', 'ws', 'assign', 'comma', @@ -55,25 +54,48 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ ...buildRuleGroup( [ 'from', + 'metadata', + 'mv_expand', 'stats', + 'dissect', + 'grok', + 'project', + 'keep', + 'rename', + 'drop', 'eval', 'sort', 'by', 'where', + 'not', + 'is', + 'like', + 'rlike', + 'in', + 'as', 'expr_ws', 'row', + 'show', 'limit', + 'cidr_match', 'nulls_ordering_direction', 'nulls_ordering', 'null', 'boolean_value', 'comparison_operator', + 'enrich', + 'on', + 'with', ], euiThemeVars.euiColorPrimaryText ), - // math functions + // aggregation functions ...buildRuleGroup(['unary_function'], euiThemeVars.euiColorPrimaryText), + // is null functions + ...buildRuleGroup(['where_functions'], euiThemeVars.euiColorPrimaryText), + // math functions + ...buildRuleGroup(['math_function'], euiThemeVars.euiColorPrimaryText), // operators ...buildRuleGroup( diff --git a/packages/kbn-monaco/src/esql/worker/esql_worker.ts b/packages/kbn-monaco/src/esql/worker/esql_worker.ts index 4d52c2b1094cb..4656ac9e9db7c 100644 --- a/packages/kbn-monaco/src/esql/worker/esql_worker.ts +++ b/packages/kbn-monaco/src/esql/worker/esql_worker.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { CharStreams } from 'antlr4ts'; +import { CharStreams, type CodePointCharStream } from 'antlr4ts'; import { monaco } from '../../monaco_imports'; import { AutocompleteListener } from '../lib/autocomplete/autocomplete_listener'; import type { BaseWorkerDefinition } from '../../types'; @@ -43,16 +43,9 @@ export class ESQLWorker implements BaseWorkerDefinition { return []; } - public async provideAutocompleteSuggestions( - modelUri: string, - meta: { - word: string; - line: number; - index: number; - } + private async provideAutocompleteSuggestionFromRawString( + inputStream: CodePointCharStream | undefined ) { - const inputStream = this.getModelCharStream(modelUri); - if (inputStream) { const errorListener = new ANTLREErrorListener(); const parseListener = new AutocompleteListener(); @@ -63,4 +56,19 @@ export class ESQLWorker implements BaseWorkerDefinition { return parseListener.getAutocompleteSuggestions(); } } + + public async provideAutocompleteSuggestions( + modelUri: string, + meta: { + word: string; + line: number; + index: number; + } + ) { + return this.provideAutocompleteSuggestionFromRawString(this.getModelCharStream(modelUri)); + } + + public async provideAutocompleteSuggestionsFromString(text: string) { + return this.provideAutocompleteSuggestionFromRawString(CharStreams.fromString(text)); + } } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 67d6be54cf004..bde4397c30424 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -31,7 +31,7 @@ pageLoadAssetSize: dataViewEditor: 28082 dataViewFieldEditor: 27000 dataViewManagement: 5000 - dataViews: 47000 + dataViews: 47300 dataVisualizer: 27530 devTools: 38637 discover: 99999 @@ -40,7 +40,8 @@ pageLoadAssetSize: embeddableEnhanced: 22107 enterpriseSearch: 50858 esUiShared: 326654 - eventAnnotation: 48565 + eventAnnotation: 30000 + eventAnnotationListing: 25841 exploratoryView: 74673 expressionError: 22127 expressionGauge: 25000 @@ -95,7 +96,7 @@ pageLoadAssetSize: ml: 82187 monitoring: 80000 navigation: 37269 - navigationEmbeddable: 17892 + navigationEmbeddable: 44490 newsfeed: 42228 noDataPage: 5000 observability: 115443 diff --git a/packages/kbn-profiling-utils/common/__fixtures__/README.md b/packages/kbn-profiling-utils/common/__fixtures__/README.md new file mode 100644 index 0000000000000..1a26bca590668 --- /dev/null +++ b/packages/kbn-profiling-utils/common/__fixtures__/README.md @@ -0,0 +1,17 @@ +The stacktrace fixtures in this directory are originally from Elasticsearch's +`POST /_profiling/stacktraces` endpoint. They were subsequently filtered +through the `shrink_stacktrace_response.js` command in `x-pack/plugins/profiling/scripts/` +to reduce the size without losing sampling fidelity (see the script for further +details). + +The naming convention for each stacktrace fixture follows this pattern: + +``` +stacktraces_{seconds}s_{upsampling rate}x.json +``` + +where `seconds` is the time span of the original query and `upsampling rate` is +the reciprocal of the sampling rate returned from the original query. + +To add a new stacktrace fixture to the test suite, update `stacktraces.ts` +appropriately. \ No newline at end of file diff --git a/packages/kbn-profiling-utils/common/__fixtures__/stacktraces.ts b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces.ts new file mode 100644 index 0000000000000..105132ec15941 --- /dev/null +++ b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StackTraceResponse } from '../stack_traces'; + +import stackTraces1x from './stacktraces_60s_1x.json'; +import stackTraces5x from './stacktraces_3600s_5x.json'; +import stackTraces125x from './stacktraces_86400s_125x.json'; +import stackTraces625x from './stacktraces_604800s_625x.json'; + +export const stackTraceFixtures: Array<{ + response: StackTraceResponse; + seconds: number; + upsampledBy: number; +}> = [ + { response: stackTraces1x, seconds: 60, upsampledBy: 1 }, + { response: stackTraces5x, seconds: 3600, upsampledBy: 5 }, + { response: stackTraces125x, seconds: 86400, upsampledBy: 125 }, + { response: stackTraces625x, seconds: 604800, upsampledBy: 625 }, +]; diff --git a/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_3600s_5x.json b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_3600s_5x.json new file mode 100644 index 0000000000000..cad5ac24c7a7e --- /dev/null +++ b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_3600s_5x.json @@ -0,0 +1 @@ +{"stack_trace_events":{"-njmbjRUBOZR5EgXpUQdRw":42,"ztDY3GPoIfO7CjHQxmyZ-Q":115,"Y8CwPu4zFwOz0m86XYzkGw":256,"QiwsJA6NJ0Q3f2M4DT-dxA":1192,"9_06LL00QkYIeiFNCWu0XQ":1033,"GApi1ybrprUZdnGMiSfUPA":675,"QpRRwD9tRNNrUmJ_2oOuSg":385,"43tbk4XHS6h_eSSkozr2lQ":480,"nORl1I4BGh3mzZiFR21ijQ":342,"ONNtRKFUjSc8lLm64B4nVQ":604,"IgUYn71JvS5hV0IssAqJCA":415,"u31aX9a6CI2OuomWQHSx1Q":486,"ZBYtP3yTV5OAbePvOl3arg":500,"ztbi9NfSFBK5AxpIlylSew":478,"-s21TvA-EsTWbfCutQG83Q":402,"APcbPjShNMH1PkL1e22JYg":381,"sGdKDAzt2D3ZK2brqGj4vQ":551,"hecRkAhRG62NML7wI512zA":225,"yqosCJmye4YNNxuB2s8zdQ":181,"JEl8c8qrwRMDRhl_VlTpFQ":234,"TFvQpP8OVc3AdHSKmIUBAA":218,"eUMH9Wf36CVzdkAZsN9itA":242,"57NvBalQc9mIcBwC1lPObg":229,"qaTBBEzEjIyGmsWUYfCBpA":189,"y7Mdo_ee9-4XsWhpA4MB0g":271,"vODIlh-kDOyM2hWSJhdfpA":235,"QKuCwkwTUdmVpouD1TSb6g":167,"zQ3yVnMIXoz1yUFx6SaSlA":146,"PfGJvpI_t-0Eiwgl8k31BA":148,"P-lVr6eiwDBuO8eZBdsdMQ":144,"KxQngfXsErVAsVuASxix6w":138,"NDxOvbKIocbTk6FkHrLlqQ":107,"2GP6bCEH-XkrLdH6ox0E3Q":95,"NYEjWS7muJ8dsj9z5lNehg":52,"Nr5XZDDmb-nXg0BzTFzdFA":44,"JVvUxIunvr6V68Rt99rK9w":38,"tagsGmBta7BnDHBzEbH9eQ":28,"CjP83pplY09FGl9PBMeqCg":13,"SQ6jhz-Ee7WHXLMOHOsDcQ":18,"eM1ATYEKUIN4nyPylmr13A":20,"9vNu8RjYClbqhYYGUiWI7A":12,"CU-T9AvnxmWd1TTRjgV01Q":17,"hoJT-ObO7MDFTgt9UeFJfg":9,"us5XzJaFA8Y8a8Jhq7VWzQ":34,"tWPDa1sBMePW-YFiahrHBA":9,"KKjaO47Ew4fmVCY-lBFkLg":6,"zxyQebekMWvnWWEuWSzR9Q":8,"UI-7Z494NKAWuv1FuNlxoQ":4,"6yHX0lcyWmly8MshBzd78Q":7,"uEL43HtanLRCO2rLB4ttzQ":3,"mXgK2ekWZ4qH-uHB8QaLtA":7,"1twYzjHR6hCfJqQLvJ81XA":5,"f-LRF9Sfj675yc68DOXczw":2,"p24lyWOwFjGMsQaWybQUMA":1,"KHat1RLkyP8wPwwR1uD04A":4,"B-OQjwP7KzSb4f6cXUL1bA":2,"kOWftL0Ttias8Z1isZi9oA":4,"JzGylmBPluUmIML9XnagKw":3,"tTw0tfSnPtZhbcyzyVHHpg":2,"E_F-N51BcZ4iQ9oPaHFKXw":2,"d04G8ZHV3kYQ0ekQBw1VYQ":3,"I-DofAMUQgh7q14tBJcZlA":3,"tGGi0acvAmmxOR5DbuF3dg":4,"Ws9TqFMz-kHv_-7zrBFdKw":3,"nBHRVpYV5wUL_UAb5ff6Zg":1,"vfw5EN0FEHQCAj0w-N2avQ":1,"lyeLQDjWsQDYEJbcY4aFJA":3,"cqzgaW0F-6gZ8uHz_Pf3hQ":1,"b89Eo7vMfG4HsPSBVvjiKQ":5,"5_-zAnLDYAi4FySmVgS6iw":2,"zOI_cRK31hVrh4Typ0-Fxg":5,"4U9ayDnwvWmqJPhn_AOKew":8,"Jt6CexOHLEwUl4IeTgASBQ":4,"8Rif7kuKG2cfhEYF2fJXmA":4,"cCjn5miDmyezrnBAe2jDww":12,"f8AFYpSQOpjCNbhqUuR3Rg":9,"dGMvgpGXk-ajX6PRi92qdg":9,"OxrG9ZVAzX9GwGtxUtIQNg":3,"QoW8uF5K3OBNL2DXI66leA":9,"zV-93oQDbZK9zB7UMAcCmw":5,"9CQVJEfCfL1rSnUaxlAfqg":3,"mGGvLNOYB74ofk9FRrMxxQ":2,"pnLCuJVNeqGwwFeJQIrkPw":2,"R77Zz6fBvENVXyt4GVb9dQ":1,"tgL-t2GJJjItpLjnwjc4zQ":1,"XNCSlgkv_bOXDIYn6zwekw":5,"jPN_jNGPJguImYjakYlBcA":1,"4K-SlZ4j8NjsVBpqyPj2dw":1,"W8IRlEZMfFJdYSgUQXDnMg":2,"qytuJG9brvKSB9NJCHV9fQ":1,"b116myovN7_XXb1AVLPH0g":1,"dNwgDmnCM1dIIF5EZm4ZgA":1,"KEdXtWOmrUdpIHsjndtg_A":1,"V2K_ZjA6rol7KyINtV45_A":1},"stack_traces":{"-njmbjRUBOZR5EgXpUQdRw":{"address_or_lines":[1277056],"file_ids":["G68hjsyagwq6LpWrMjDdng"],"frame_ids":["G68hjsyagwq6LpWrMjDdngAAAAAAE3yA"],"type_ids":[3]},"ztDY3GPoIfO7CjHQxmyZ-Q":{"address_or_lines":[4643458,4456960],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARtqC","B8JRxL079xbhqQBqGvksAgAAAAAARAIA"],"type_ids":[3,3]},"Y8CwPu4zFwOz0m86XYzkGw":{"address_or_lines":[4597989,4390116,4390542],"file_ids":["6kzBY4yj-1Fh1NCTZA3z0w","6kzBY4yj-1Fh1NCTZA3z0w","6kzBY4yj-1Fh1NCTZA3z0w"],"frame_ids":["6kzBY4yj-1Fh1NCTZA3z0wAAAAAARijl","6kzBY4yj-1Fh1NCTZA3z0wAAAAAAQvzk","6kzBY4yj-1Fh1NCTZA3z0wAAAAAAQv6O"],"type_ids":[3,3,3]},"QiwsJA6NJ0Q3f2M4DT-dxA":{"address_or_lines":[4597989,4307812,4320019,4321918],"file_ids":["6kzBY4yj-1Fh1NCTZA3z0w","6kzBY4yj-1Fh1NCTZA3z0w","6kzBY4yj-1Fh1NCTZA3z0w","6kzBY4yj-1Fh1NCTZA3z0w"],"frame_ids":["6kzBY4yj-1Fh1NCTZA3z0wAAAAAARijl","6kzBY4yj-1Fh1NCTZA3z0wAAAAAAQbtk","6kzBY4yj-1Fh1NCTZA3z0wAAAAAAQesT","6kzBY4yj-1Fh1NCTZA3z0wAAAAAAQfJ-"],"type_ids":[3,3,3,3]},"9_06LL00QkYIeiFNCWu0XQ":{"address_or_lines":[4643592,4325284,4339923,4341903,4293837],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARtsI","B8JRxL079xbhqQBqGvksAgAAAAAAQf-k","B8JRxL079xbhqQBqGvksAgAAAAAAQjjT","B8JRxL079xbhqQBqGvksAgAAAAAAQkCP","B8JRxL079xbhqQBqGvksAgAAAAAAQYTN"],"type_ids":[3,3,3,3,3]},"GApi1ybrprUZdnGMiSfUPA":{"address_or_lines":[18434496,18109958,18105083,18107109,18183090,18183229],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABFFYG","j8DVIOTu7Btj9lgFefJ84AAAAAABFEL7","j8DVIOTu7Btj9lgFefJ84AAAAAABFErl","j8DVIOTu7Btj9lgFefJ84AAAAAABFXOy","j8DVIOTu7Btj9lgFefJ84AAAAAABFXQ9"],"type_ids":[3,3,3,3,3,3]},"QpRRwD9tRNNrUmJ_2oOuSg":{"address_or_lines":[4644672,40444780,40465086,40468873,40476239,4250662,4249714],"file_ids":["B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A"],"frame_ids":["B56YkhsK1JwqD-8F8sjS3AAAAAAARt9A","B56YkhsK1JwqD-8F8sjS3AAAAAACaSNs","B56YkhsK1JwqD-8F8sjS3AAAAAACaXK-","B56YkhsK1JwqD-8F8sjS3AAAAAACaYGJ","B56YkhsK1JwqD-8F8sjS3AAAAAACaZ5P","B56YkhsK1JwqD-8F8sjS3AAAAAAAQNwm","B56YkhsK1JwqD-8F8sjS3AAAAAAAQNhy"],"type_ids":[3,3,3,3,3,3,3]},"43tbk4XHS6h_eSSkozr2lQ":{"address_or_lines":[18515232,22597677,22574090,22556393,22530363,22106663,22101077,22107662],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHQK","v6HIzNa4K6G4nRP9032RIAAAAAABWC7p","v6HIzNa4K6G4nRP9032RIAAAAAABV8k7","v6HIzNa4K6G4nRP9032RIAAAAAABUVIn","v6HIzNa4K6G4nRP9032RIAAAAAABUTxV","v6HIzNa4K6G4nRP9032RIAAAAAABUVYO"],"type_ids":[3,3,3,3,3,3,3,3]},"nORl1I4BGh3mzZiFR21ijQ":{"address_or_lines":[4654944,15291206,14341928,15275435,15271908,4256166,4255110,4288975,4287865],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6Qfk","FWZ9q3TQKZZok58ua1HDsgAAAAAAQPGm","FWZ9q3TQKZZok58ua1HDsgAAAAAAQO2G","FWZ9q3TQKZZok58ua1HDsgAAAAAAQXHP","FWZ9q3TQKZZok58ua1HDsgAAAAAAQW15"],"type_ids":[3,3,3,3,3,3,3,3,3]},"ONNtRKFUjSc8lLm64B4nVQ":{"address_or_lines":[4641312,7081613,7060969,4425906,7064267,7057968,6093476,6025643,4305623,4278829],"file_ids":["gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w","gNW12BepH17pXwK-ZuYt3w"],"frame_ids":["gNW12BepH17pXwK-ZuYt3wAAAAAARtIg","gNW12BepH17pXwK-ZuYt3wAAAAAAbA6N","gNW12BepH17pXwK-ZuYt3wAAAAAAa73p","gNW12BepH17pXwK-ZuYt3wAAAAAAQ4iy","gNW12BepH17pXwK-ZuYt3wAAAAAAa8rL","gNW12BepH17pXwK-ZuYt3wAAAAAAa7Iw","gNW12BepH17pXwK-ZuYt3wAAAAAAXPqk","gNW12BepH17pXwK-ZuYt3wAAAAAAW_Gr","gNW12BepH17pXwK-ZuYt3wAAAAAAQbLX","gNW12BepH17pXwK-ZuYt3wAAAAAAQUot"],"type_ids":[3,3,3,3,3,3,3,3,3,3]},"IgUYn71JvS5hV0IssAqJCA":{"address_or_lines":[4636100,4452920,4453106,4487396,4487396,4651100,10485923,16743,1136873,1113241,4849252],"file_ids":["B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["B56YkhsK1JwqD-8F8sjS3AAAAAAARr3E","B56YkhsK1JwqD-8F8sjS3AAAAAAAQ_I4","B56YkhsK1JwqD-8F8sjS3AAAAAAAQ_Ly","B56YkhsK1JwqD-8F8sjS3AAAAAAARHjk","B56YkhsK1JwqD-8F8sjS3AAAAAAARHjk","B56YkhsK1JwqD-8F8sjS3AAAAAAARvhc","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAEVjp","piWSMQrh4r040D0BPNaJvwAAAAAAEPyZ","piWSMQrh4r040D0BPNaJvwAAAAAASf5k"],"type_ids":[3,3,3,3,3,3,4,4,4,4,4]},"u31aX9a6CI2OuomWQHSx1Q":{"address_or_lines":[4652224,22357367,22385134,22366798,57080079,58879477,58676957,58636100,58650141,31265796,7372663,7364083],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZvkP","B8JRxL079xbhqQBqGvksAgAAAAADgm31","B8JRxL079xbhqQBqGvksAgAAAAADf1bd","B8JRxL079xbhqQBqGvksAgAAAAADfrdE","B8JRxL079xbhqQBqGvksAgAAAAADfu4d","B8JRxL079xbhqQBqGvksAgAAAAAB3RQE","B8JRxL079xbhqQBqGvksAgAAAAAAcH93","B8JRxL079xbhqQBqGvksAgAAAAAAcF3z"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3]},"ZBYtP3yTV5OAbePvOl3arg":{"address_or_lines":[4636226,4469356,4468068,4466980,4460377,4459271,4243432,4415957,4652642,10485923,16743,1221731,1219038],"file_ids":["B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","B56YkhsK1JwqD-8F8sjS3A","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["B56YkhsK1JwqD-8F8sjS3AAAAAAARr5C","B56YkhsK1JwqD-8F8sjS3AAAAAAARDJs","B56YkhsK1JwqD-8F8sjS3AAAAAAARC1k","B56YkhsK1JwqD-8F8sjS3AAAAAAARCkk","B56YkhsK1JwqD-8F8sjS3AAAAAAARA9Z","B56YkhsK1JwqD-8F8sjS3AAAAAAARAsH","B56YkhsK1JwqD-8F8sjS3AAAAAAAQL_o","B56YkhsK1JwqD-8F8sjS3AAAAAAAQ2HV","B56YkhsK1JwqD-8F8sjS3AAAAAAARv5i","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAEqRj","piWSMQrh4r040D0BPNaJvwAAAAAAEpne"],"type_ids":[3,3,3,3,3,3,3,3,3,4,4,4,4]},"ztbi9NfSFBK5AxpIlylSew":{"address_or_lines":[4594466,4444524,4443160,4438546,4391572,4609107,10485923,16807,2756288,2755416,2744627,2792698,4867725,4855327],"file_ids":["kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["kajOqZqz7V1y0BdYQLFQrwAAAAAARhsi","kajOqZqz7V1y0BdYQLFQrwAAAAAAQ9Fs","kajOqZqz7V1y0BdYQLFQrwAAAAAAQ8wY","kajOqZqz7V1y0BdYQLFQrwAAAAAAQ7oS","kajOqZqz7V1y0BdYQLFQrwAAAAAAQwKU","kajOqZqz7V1y0BdYQLFQrwAAAAAARlRT","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A","A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY","A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz","A2oiHVwisByxRn5RDT4LjAAAAAAAKpz6","A2oiHVwisByxRn5RDT4LjAAAAAAASkaN","A2oiHVwisByxRn5RDT4LjAAAAAAAShYf"],"type_ids":[3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"-s21TvA-EsTWbfCutQG83Q":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10733159,10733818,10618404,10387225,4547736,4658752],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Zn","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8j6","FWZ9q3TQKZZok58ua1HDsgAAAAAAogYk","FWZ9q3TQKZZok58ua1HDsgAAAAAAnn8Z","FWZ9q3TQKZZok58ua1HDsgAAAAAARWSY","FWZ9q3TQKZZok58ua1HDsgAAAAAARxZA"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"APcbPjShNMH1PkL1e22JYg":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54557252,54545733,54546893,54560984,44458726,43610833,43327941,43735894],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHpE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQE1F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQFHN","MNBJ5seVz_ocW6tcr1HSmwAAAAADQIjY","MNBJ5seVz_ocW6tcr1HSmwAAAAACpmLm","MNBJ5seVz_ocW6tcr1HSmwAAAAACmXLR","MNBJ5seVz_ocW6tcr1HSmwAAAAAClSHF","MNBJ5seVz_ocW6tcr1HSmwAAAAACm1tW"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"sGdKDAzt2D3ZK2brqGj4vQ":{"address_or_lines":[4652224,22354871,22382638,22364302,56672751,58471189,58268669,58227812,58241853,31197476,7372151,7373114,7373997,4536145,4264900,4265340,4655641],"file_ids":["-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ"],"frame_ids":["-pk6w5puGcp-wKnQ61BZzQAAAAAARvzA","-pk6w5puGcp-wKnQ61BZzQAAAAABVRu3","-pk6w5puGcp-wKnQ61BZzQAAAAABVYgu","-pk6w5puGcp-wKnQ61BZzQAAAAABVUCO","-pk6w5puGcp-wKnQ61BZzQAAAAADYMHv","-pk6w5puGcp-wKnQ61BZzQAAAAADfDMV","-pk6w5puGcp-wKnQ61BZzQAAAAADeRv9","-pk6w5puGcp-wKnQ61BZzQAAAAADeHxk","-pk6w5puGcp-wKnQ61BZzQAAAAADeLM9","-pk6w5puGcp-wKnQ61BZzQAAAAAB3Akk","-pk6w5puGcp-wKnQ61BZzQAAAAAAcH13","-pk6w5puGcp-wKnQ61BZzQAAAAAAcIE6","-pk6w5puGcp-wKnQ61BZzQAAAAAAcISt","-pk6w5puGcp-wKnQ61BZzQAAAAAARTdR","-pk6w5puGcp-wKnQ61BZzQAAAAAAQRPE","-pk6w5puGcp-wKnQ61BZzQAAAAAAQRV8","-pk6w5puGcp-wKnQ61BZzQAAAAAARwoZ"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"hecRkAhRG62NML7wI512zA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000356,39998369,27959205,27961373,27940684],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYltk","v6HIzNa4K6G4nRP9032RIAAAAAACYlOh","v6HIzNa4K6G4nRP9032RIAAAAAABqp-l","v6HIzNa4K6G4nRP9032RIAAAAAABqqgd","v6HIzNa4K6G4nRP9032RIAAAAAABqldM"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"yqosCJmye4YNNxuB2s8zdQ":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000356,39998369,27959205,27961653,27949894,18928855],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYltk","v6HIzNa4K6G4nRP9032RIAAAAAACYlOh","v6HIzNa4K6G4nRP9032RIAAAAAABqp-l","v6HIzNa4K6G4nRP9032RIAAAAAABqqk1","v6HIzNa4K6G4nRP9032RIAAAAAABqntG","v6HIzNa4K6G4nRP9032RIAAAAAABINTX"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"JEl8c8qrwRMDRhl_VlTpFQ":{"address_or_lines":[4652224,59362286,59048854,59078134,59085018,59181690,58121321,58026161,58173220,58175116,7294148,7295421,7297245,7300762,7297188,7304836,7297413,7309604,7298328,5114154],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAADicvu","B8JRxL079xbhqQBqGvksAgAAAAADhQOW","B8JRxL079xbhqQBqGvksAgAAAAADhXX2","B8JRxL079xbhqQBqGvksAgAAAAADhZDa","B8JRxL079xbhqQBqGvksAgAAAAADhwp6","B8JRxL079xbhqQBqGvksAgAAAAADdtxp","B8JRxL079xbhqQBqGvksAgAAAAADdWix","B8JRxL079xbhqQBqGvksAgAAAAADd6ck","B8JRxL079xbhqQBqGvksAgAAAAADd66M","B8JRxL079xbhqQBqGvksAgAAAAAAb0zE","B8JRxL079xbhqQBqGvksAgAAAAAAb1G9","B8JRxL079xbhqQBqGvksAgAAAAAAb1jd","B8JRxL079xbhqQBqGvksAgAAAAAAb2aa","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1mF","B8JRxL079xbhqQBqGvksAgAAAAAAb4kk","B8JRxL079xbhqQBqGvksAgAAAAAAb10Y","B8JRxL079xbhqQBqGvksAgAAAAAATgkq"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"TFvQpP8OVc3AdHSKmIUBAA":{"address_or_lines":[4652224,22357367,22385134,22366798,57092143,58893857,58677085,58641545,58657509,31313785,7372944,7295421,7297245,7300762,7297188,7304836,7297188,7306724,5132868,4625639,4289536],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZygv","B8JRxL079xbhqQBqGvksAgAAAAADgqYh","B8JRxL079xbhqQBqGvksAgAAAAADf1dd","B8JRxL079xbhqQBqGvksAgAAAAADfsyJ","B8JRxL079xbhqQBqGvksAgAAAAADfwrl","B8JRxL079xbhqQBqGvksAgAAAAAB3c95","B8JRxL079xbhqQBqGvksAgAAAAAAcICQ","B8JRxL079xbhqQBqGvksAgAAAAAAb1G9","B8JRxL079xbhqQBqGvksAgAAAAAAb1jd","B8JRxL079xbhqQBqGvksAgAAAAAAb2aa","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb33k","B8JRxL079xbhqQBqGvksAgAAAAAATlJE","B8JRxL079xbhqQBqGvksAgAAAAAARpTn","B8JRxL079xbhqQBqGvksAgAAAAAAQXQA"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"eUMH9Wf36CVzdkAZsN9itA":{"address_or_lines":[32443680,43151402,43152149,43153397,41329281,41441892,41443480,41222389,41225442,41240900,40679166,40714972,40707458,40707880,40710748,40690621,40679204,40688196,40679204,40688166,40644014,41210644],"file_ids":["QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q"],"frame_ids":["QvG8QEGAld88D676NL_Y2QAAAAAB7w0g","QvG8QEGAld88D676NL_Y2QAAAAACknAq","QvG8QEGAld88D676NL_Y2QAAAAACknMV","QvG8QEGAld88D676NL_Y2QAAAAACknf1","QvG8QEGAld88D676NL_Y2QAAAAACdqKB","QvG8QEGAld88D676NL_Y2QAAAAACeFpk","QvG8QEGAld88D676NL_Y2QAAAAACeGCY","QvG8QEGAld88D676NL_Y2QAAAAACdQD1","QvG8QEGAld88D676NL_Y2QAAAAACdQzi","QvG8QEGAld88D676NL_Y2QAAAAACdUlE","QvG8QEGAld88D676NL_Y2QAAAAACbLb-","QvG8QEGAld88D676NL_Y2QAAAAACbULc","QvG8QEGAld88D676NL_Y2QAAAAACbSWC","QvG8QEGAld88D676NL_Y2QAAAAACbSco","QvG8QEGAld88D676NL_Y2QAAAAACbTJc","QvG8QEGAld88D676NL_Y2QAAAAACbOO9","QvG8QEGAld88D676NL_Y2QAAAAACbLck","QvG8QEGAld88D676NL_Y2QAAAAACbNpE","QvG8QEGAld88D676NL_Y2QAAAAACbLck","QvG8QEGAld88D676NL_Y2QAAAAACbNom","QvG8QEGAld88D676NL_Y2QAAAAACbC2u","QvG8QEGAld88D676NL_Y2QAAAAACdNMU"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"57NvBalQc9mIcBwC1lPObg":{"address_or_lines":[4652224,31040261,31054565,31056612,31058888,31450411,30791748,25539462,25519688,25480413,25483943,25484196,4951332,4960527,4959954,4897957,4893996,4627954,4660663,10485923,16807,3103640,3100879],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAAB2aMF","B8JRxL079xbhqQBqGvksAgAAAAAB2drl","B8JRxL079xbhqQBqGvksAgAAAAAB2eLk","B8JRxL079xbhqQBqGvksAgAAAAAB2evI","B8JRxL079xbhqQBqGvksAgAAAAAB3-Ur","B8JRxL079xbhqQBqGvksAgAAAAAB1dhE","B8JRxL079xbhqQBqGvksAgAAAAABhbOG","B8JRxL079xbhqQBqGvksAgAAAAABhWZI","B8JRxL079xbhqQBqGvksAgAAAAABhMzd","B8JRxL079xbhqQBqGvksAgAAAAABhNqn","B8JRxL079xbhqQBqGvksAgAAAAABhNuk","B8JRxL079xbhqQBqGvksAgAAAAAAS40k","B8JRxL079xbhqQBqGvksAgAAAAAAS7EP","B8JRxL079xbhqQBqGvksAgAAAAAAS67S","B8JRxL079xbhqQBqGvksAgAAAAAASryl","B8JRxL079xbhqQBqGvksAgAAAAAASq0s","B8JRxL079xbhqQBqGvksAgAAAAAARp3y","B8JRxL079xbhqQBqGvksAgAAAAAARx23","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAL1uY","A2oiHVwisByxRn5RDT4LjAAAAAAAL1DP"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4]},"qaTBBEzEjIyGmsWUYfCBpA":{"address_or_lines":[4652224,31040261,31054565,31056612,31058888,31450411,30791748,25539462,25520823,25502704,25503492,25480821,25481061,4953508,4960780,4898318,4893650,4898160,4745321,4757831,4219698,4219725,10485923,16755],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAAB2aMF","B8JRxL079xbhqQBqGvksAgAAAAAB2drl","B8JRxL079xbhqQBqGvksAgAAAAAB2eLk","B8JRxL079xbhqQBqGvksAgAAAAAB2evI","B8JRxL079xbhqQBqGvksAgAAAAAB3-Ur","B8JRxL079xbhqQBqGvksAgAAAAAB1dhE","B8JRxL079xbhqQBqGvksAgAAAAABhbOG","B8JRxL079xbhqQBqGvksAgAAAAABhWq3","B8JRxL079xbhqQBqGvksAgAAAAABhSPw","B8JRxL079xbhqQBqGvksAgAAAAABhScE","B8JRxL079xbhqQBqGvksAgAAAAABhM51","B8JRxL079xbhqQBqGvksAgAAAAABhM9l","B8JRxL079xbhqQBqGvksAgAAAAAAS5Wk","B8JRxL079xbhqQBqGvksAgAAAAAAS7IM","B8JRxL079xbhqQBqGvksAgAAAAAASr4O","B8JRxL079xbhqQBqGvksAgAAAAAASqvS","B8JRxL079xbhqQBqGvksAgAAAAAASr1w","B8JRxL079xbhqQBqGvksAgAAAAAASGhp","B8JRxL079xbhqQBqGvksAgAAAAAASJlH","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEFz"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4]},"y7Mdo_ee9-4XsWhpA4MB0g":{"address_or_lines":[4652224,58223725,10400868,10401064,10401333,10401661,58236869,58227432,58120068,58163344,58184537,58041720,57725674,57726188,57066632,22280836,22281116,22396783,22397566,22398116,5362852,5363370,4271546,4264588,4299069],"file_ids":["6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ"],"frame_ids":["6auiCMWq5cA-hAbqSYvdQQAAAAAARvzA","6auiCMWq5cA-hAbqSYvdQQAAAAADeGxt","6auiCMWq5cA-hAbqSYvdQQAAAAAAnrRk","6auiCMWq5cA-hAbqSYvdQQAAAAAAnrUo","6auiCMWq5cA-hAbqSYvdQQAAAAAAnrY1","6auiCMWq5cA-hAbqSYvdQQAAAAAAnrd9","6auiCMWq5cA-hAbqSYvdQQAAAAADeJ_F","6auiCMWq5cA-hAbqSYvdQQAAAAADeHro","6auiCMWq5cA-hAbqSYvdQQAAAAADdteE","6auiCMWq5cA-hAbqSYvdQQAAAAADd4CQ","6auiCMWq5cA-hAbqSYvdQQAAAAADd9NZ","6auiCMWq5cA-hAbqSYvdQQAAAAADdaV4","6auiCMWq5cA-hAbqSYvdQQAAAAADcNLq","6auiCMWq5cA-hAbqSYvdQQAAAAADcNTs","6auiCMWq5cA-hAbqSYvdQQAAAAADZsSI","6auiCMWq5cA-hAbqSYvdQQAAAAABU_qE","6auiCMWq5cA-hAbqSYvdQQAAAAABU_uc","6auiCMWq5cA-hAbqSYvdQQAAAAABVb9v","6auiCMWq5cA-hAbqSYvdQQAAAAABVcJ-","6auiCMWq5cA-hAbqSYvdQQAAAAABVcSk","6auiCMWq5cA-hAbqSYvdQQAAAAAAUdSk","6auiCMWq5cA-hAbqSYvdQQAAAAAAUdaq","6auiCMWq5cA-hAbqSYvdQQAAAAAAQS26","6auiCMWq5cA-hAbqSYvdQQAAAAAAQRKM","6auiCMWq5cA-hAbqSYvdQQAAAAAAQZk9"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"vODIlh-kDOyM2hWSJhdfpA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429353,40304297,19976893,19927481,19928567,19983876,19943049,19984068,19944276,19984260,19945213,19982696,19937907,19982884,19142858],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeClp","v6HIzNa4K6G4nRP9032RIAAAAAACZv6p","v6HIzNa4K6G4nRP9032RIAAAAAABMNK9","v6HIzNa4K6G4nRP9032RIAAAAAABMBG5","v6HIzNa4K6G4nRP9032RIAAAAAABMBX3","v6HIzNa4K6G4nRP9032RIAAAAAABMO4E","v6HIzNa4K6G4nRP9032RIAAAAAABME6J","v6HIzNa4K6G4nRP9032RIAAAAAABMO7E","v6HIzNa4K6G4nRP9032RIAAAAAABMFNU","v6HIzNa4K6G4nRP9032RIAAAAAABMO-E","v6HIzNa4K6G4nRP9032RIAAAAAABMFb9","v6HIzNa4K6G4nRP9032RIAAAAAABMOlo","v6HIzNa4K6G4nRP9032RIAAAAAABMDpz","v6HIzNa4K6G4nRP9032RIAAAAAABMOok","v6HIzNa4K6G4nRP9032RIAAAAAABJBjK"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"QKuCwkwTUdmVpouD1TSb6g":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226601,40103401,19895453,19846041,19847127,19902436,19861609,19902628,19862836,19902820,19863773,19901256,19856467,19901444,19858562,18659470],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdRFp","j8DVIOTu7Btj9lgFefJ84AAAAAACY-3p","j8DVIOTu7Btj9lgFefJ84AAAAAABL5Sd","j8DVIOTu7Btj9lgFefJ84AAAAAABLtOZ","j8DVIOTu7Btj9lgFefJ84AAAAAABLtfX","j8DVIOTu7Btj9lgFefJ84AAAAAABL6_k","j8DVIOTu7Btj9lgFefJ84AAAAAABLxBp","j8DVIOTu7Btj9lgFefJ84AAAAAABL7Ck","j8DVIOTu7Btj9lgFefJ84AAAAAABLxU0","j8DVIOTu7Btj9lgFefJ84AAAAAABL7Fk","j8DVIOTu7Btj9lgFefJ84AAAAAABLxjd","j8DVIOTu7Btj9lgFefJ84AAAAAABL6tI","j8DVIOTu7Btj9lgFefJ84AAAAAABLvxT","j8DVIOTu7Btj9lgFefJ84AAAAAABL6wE","j8DVIOTu7Btj9lgFefJ84AAAAAABLwSC","j8DVIOTu7Btj9lgFefJ84AAAAAABHLiO"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"zQ3yVnMIXoz1yUFx6SaSlA":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54557252,54545733,54548081,54524484,54525381,54528467,54488242,54489352,54492882,44042020,44050554,43824563,43838109,43282962,43282989,10485923,16807,2741196,2827770,2817934],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHpE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQE1F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQFZx","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_pE","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_3F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQAnT","MNBJ5seVz_ocW6tcr1HSmwAAAAADP2yy","MNBJ5seVz_ocW6tcr1HSmwAAAAADP3EI","MNBJ5seVz_ocW6tcr1HSmwAAAAADP37S","MNBJ5seVz_ocW6tcr1HSmwAAAAACoAck","MNBJ5seVz_ocW6tcr1HSmwAAAAACoCh6","MNBJ5seVz_ocW6tcr1HSmwAAAAACnLWz","MNBJ5seVz_ocW6tcr1HSmwAAAAACnOqd","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIS","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIt","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM","A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6","A2oiHVwisByxRn5RDT4LjAAAAAAAKv-O"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4]},"PfGJvpI_t-0Eiwgl8k31BA":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226539,39801748,39804999,39805475,40019662,39816300,32602256,32687470,24708921,24712242,24698684,24696100,20084020,20086666,20084847,20085083,18040582,18049603],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdREr","j8DVIOTu7Btj9lgFefJ84AAAAAACX1OU","j8DVIOTu7Btj9lgFefJ84AAAAAACX2BH","j8DVIOTu7Btj9lgFefJ84AAAAAACX2Ij","j8DVIOTu7Btj9lgFefJ84AAAAAACYqbO","j8DVIOTu7Btj9lgFefJ84AAAAAACX4xs","j8DVIOTu7Btj9lgFefJ84AAAAAAB8XiQ","j8DVIOTu7Btj9lgFefJ84AAAAAAB8sVu","j8DVIOTu7Btj9lgFefJ84AAAAAABeQc5","j8DVIOTu7Btj9lgFefJ84AAAAAABeRQy","j8DVIOTu7Btj9lgFefJ84AAAAAABeN88","j8DVIOTu7Btj9lgFefJ84AAAAAABeNUk","j8DVIOTu7Btj9lgFefJ84AAAAAABMnU0","j8DVIOTu7Btj9lgFefJ84AAAAAABMn-K","j8DVIOTu7Btj9lgFefJ84AAAAAABMnhv","j8DVIOTu7Btj9lgFefJ84AAAAAABMnlb","j8DVIOTu7Btj9lgFefJ84AAAAAABE0cG","j8DVIOTu7Btj9lgFefJ84AAAAAABE2pD"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"P-lVr6eiwDBuO8eZBdsdMQ":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54557252,54545733,54548081,54524484,54525381,54528745,54499864,54500494,54477482,44044054,44044293,44044676,44051020,43988398,43982642,43988240,43826825,43837959,43282962,43282989,10485923,16755],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHpE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQE1F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQFZx","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_pE","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_3F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQArp","MNBJ5seVz_ocW6tcr1HSmwAAAAADP5oY","MNBJ5seVz_ocW6tcr1HSmwAAAAADP5yO","MNBJ5seVz_ocW6tcr1HSmwAAAAADP0Kq","MNBJ5seVz_ocW6tcr1HSmwAAAAACoA8W","MNBJ5seVz_ocW6tcr1HSmwAAAAACoBAF","MNBJ5seVz_ocW6tcr1HSmwAAAAACoBGE","MNBJ5seVz_ocW6tcr1HSmwAAAAACoCpM","MNBJ5seVz_ocW6tcr1HSmwAAAAACnzWu","MNBJ5seVz_ocW6tcr1HSmwAAAAACnx8y","MNBJ5seVz_ocW6tcr1HSmwAAAAACnzUQ","MNBJ5seVz_ocW6tcr1HSmwAAAAACnL6J","MNBJ5seVz_ocW6tcr1HSmwAAAAACnOoH","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIS","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIt","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEFz"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4]},"KxQngfXsErVAsVuASxix6w":{"address_or_lines":[4652224,11645454,31861537,31858282,31847101,59040776,58304471,58312462,31457395,31076505,31042101,31058818,31448215,30842852,30845380,30848778,30847620,4952886,4953125,4953508,4960780,4898318,4893650,4898125,4628233,4660663,10485923,16807,3104019,8528279,936364],"file_ids":["6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["6auiCMWq5cA-hAbqSYvdQQAAAAAARvzA","6auiCMWq5cA-hAbqSYvdQQAAAAAAsbIO","6auiCMWq5cA-hAbqSYvdQQAAAAAB5ish","6auiCMWq5cA-hAbqSYvdQQAAAAAB5h5q","6auiCMWq5cA-hAbqSYvdQQAAAAAB5fK9","6auiCMWq5cA-hAbqSYvdQQAAAAADhOQI","6auiCMWq5cA-hAbqSYvdQQAAAAADeafX","6auiCMWq5cA-hAbqSYvdQQAAAAADeccO","6auiCMWq5cA-hAbqSYvdQQAAAAAB4ABz","6auiCMWq5cA-hAbqSYvdQQAAAAAB2jCZ","6auiCMWq5cA-hAbqSYvdQQAAAAAB2ao1","6auiCMWq5cA-hAbqSYvdQQAAAAAB2euC","6auiCMWq5cA-hAbqSYvdQQAAAAAB39yX","6auiCMWq5cA-hAbqSYvdQQAAAAAB1p_k","6auiCMWq5cA-hAbqSYvdQQAAAAAB1qnE","6auiCMWq5cA-hAbqSYvdQQAAAAAB1rcK","6auiCMWq5cA-hAbqSYvdQQAAAAAB1rKE","6auiCMWq5cA-hAbqSYvdQQAAAAAAS5M2","6auiCMWq5cA-hAbqSYvdQQAAAAAAS5Ql","6auiCMWq5cA-hAbqSYvdQQAAAAAAS5Wk","6auiCMWq5cA-hAbqSYvdQQAAAAAAS7IM","6auiCMWq5cA-hAbqSYvdQQAAAAAASr4O","6auiCMWq5cA-hAbqSYvdQQAAAAAASqvS","6auiCMWq5cA-hAbqSYvdQQAAAAAASr1N","6auiCMWq5cA-hAbqSYvdQQAAAAAARp8J","6auiCMWq5cA-hAbqSYvdQQAAAAAARx23","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAL10T","ew01Dk0sWZctP-VaEpavqQAAAAAAgiGX","ew01Dk0sWZctP-VaEpavqQAAAAAADkms"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4]},"NDxOvbKIocbTk6FkHrLlqQ":{"address_or_lines":[4652224,58222957,10400868,10401064,10401333,10401661,58236101,58226664,58119300,58162576,58183769,58040952,57724906,57725420,57065864,22280836,22281206,22412958,22408242,22413668,22416921,22341332,22109092,22108612,11325304,11325700,10718668,11154818,57469092,57466065,4552751,4263429],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAADeGlt","B8JRxL079xbhqQBqGvksAgAAAAAAnrRk","B8JRxL079xbhqQBqGvksAgAAAAAAnrUo","B8JRxL079xbhqQBqGvksAgAAAAAAnrY1","B8JRxL079xbhqQBqGvksAgAAAAAAnrd9","B8JRxL079xbhqQBqGvksAgAAAAADeJzF","B8JRxL079xbhqQBqGvksAgAAAAADeHfo","B8JRxL079xbhqQBqGvksAgAAAAADdtSE","B8JRxL079xbhqQBqGvksAgAAAAADd32Q","B8JRxL079xbhqQBqGvksAgAAAAADd9BZ","B8JRxL079xbhqQBqGvksAgAAAAADdaJ4","B8JRxL079xbhqQBqGvksAgAAAAADcM_q","B8JRxL079xbhqQBqGvksAgAAAAADcNHs","B8JRxL079xbhqQBqGvksAgAAAAADZsGI","B8JRxL079xbhqQBqGvksAgAAAAABU_qE","B8JRxL079xbhqQBqGvksAgAAAAABU_v2","B8JRxL079xbhqQBqGvksAgAAAAABVf6e","B8JRxL079xbhqQBqGvksAgAAAAABVewy","B8JRxL079xbhqQBqGvksAgAAAAABVgFk","B8JRxL079xbhqQBqGvksAgAAAAABVg4Z","B8JRxL079xbhqQBqGvksAgAAAAABVObU","B8JRxL079xbhqQBqGvksAgAAAAABUVuk","B8JRxL079xbhqQBqGvksAgAAAAABUVnE","B8JRxL079xbhqQBqGvksAgAAAAAArM94","B8JRxL079xbhqQBqGvksAgAAAAAArNEE","B8JRxL079xbhqQBqGvksAgAAAAAAo43M","B8JRxL079xbhqQBqGvksAgAAAAAAqjWC","B8JRxL079xbhqQBqGvksAgAAAAADbOik","B8JRxL079xbhqQBqGvksAgAAAAADbNzR","B8JRxL079xbhqQBqGvksAgAAAAAARXgv","B8JRxL079xbhqQBqGvksAgAAAAAAQQ4F"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"2GP6bCEH-XkrLdH6ox0E3Q":{"address_or_lines":[4623648,7066994,7068484,7069849,7058446,10002970,10005676,10124500,9016547,11291366,9016547,24500423,24494926,9016547,10689293,10690744,9016547,24494153,24444068,9016547,24526481,9016547,12769368,12762703,6837766,6838366,6839304,5651373,5585348,5510696,4903076,4768780,4778619],"file_ids":["JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA"],"frame_ids":["JsObMPhfT_zO2Q_B1cPLxAAAAAAARo0g","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa9Vy","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa9tE","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa-CZ","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa7QO","JsObMPhfT_zO2Q_B1cPLxAAAAAAAmKIa","JsObMPhfT_zO2Q_B1cPLxAAAAAAAmKys","JsObMPhfT_zO2Q_B1cPLxAAAAAAAmnzU","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAAArErm","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAABddjH","JsObMPhfT_zO2Q_B1cPLxAAAAAABdcNO","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAAAoxsN","JsObMPhfT_zO2Q_B1cPLxAAAAAAAoyC4","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAABdcBJ","JsObMPhfT_zO2Q_B1cPLxAAAAAABdPyk","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAABdj6R","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAAAwthY","JsObMPhfT_zO2Q_B1cPLxAAAAAAAwr5P","JsObMPhfT_zO2Q_B1cPLxAAAAAAAaFYG","JsObMPhfT_zO2Q_B1cPLxAAAAAAAaFhe","JsObMPhfT_zO2Q_B1cPLxAAAAAAAaFwI","JsObMPhfT_zO2Q_B1cPLxAAAAAAAVjut","JsObMPhfT_zO2Q_B1cPLxAAAAAAAVTnE","JsObMPhfT_zO2Q_B1cPLxAAAAAAAVBYo","JsObMPhfT_zO2Q_B1cPLxAAAAAAAStCk","JsObMPhfT_zO2Q_B1cPLxAAAAAAASMQM","JsObMPhfT_zO2Q_B1cPLxAAAAAAASOp7"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"NYEjWS7muJ8dsj9z5lNehg":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791213,24785269,19897796,19899069,19901252,19908516,19901309,19904677,19901252,19908516,19901477,19920683,18932457,18907996,18882195],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekit","v6HIzNa4K6G4nRP9032RIAAAAAABejF1","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6t9","v6HIzNa4K6G4nRP9032RIAAAAAABL7il","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6wl","v6HIzNa4K6G4nRP9032RIAAAAAABL_cr","v6HIzNa4K6G4nRP9032RIAAAAAABIOLp","v6HIzNa4K6G4nRP9032RIAAAAAABIINc","v6HIzNa4K6G4nRP9032RIAAAAAABIB6T"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"Nr5XZDDmb-nXg0BzTFzdFA":{"address_or_lines":[4652224,22354871,22382638,22364302,56669071,58509234,58268669,58227812,58241853,31197553,31197973,31304315,4873273,4873930,4883062,4875761,4874468,8925121,8860356,8860667,8476967,4872825,5688954,8906989,5590020,5506248,4899556,4748900,4757831,4219698,4219725,10485923,16890,16350,1408382],"file_ids":["-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["-pk6w5puGcp-wKnQ61BZzQAAAAAARvzA","-pk6w5puGcp-wKnQ61BZzQAAAAABVRu3","-pk6w5puGcp-wKnQ61BZzQAAAAABVYgu","-pk6w5puGcp-wKnQ61BZzQAAAAABVUCO","-pk6w5puGcp-wKnQ61BZzQAAAAADYLOP","-pk6w5puGcp-wKnQ61BZzQAAAAADfMey","-pk6w5puGcp-wKnQ61BZzQAAAAADeRv9","-pk6w5puGcp-wKnQ61BZzQAAAAADeHxk","-pk6w5puGcp-wKnQ61BZzQAAAAADeLM9","-pk6w5puGcp-wKnQ61BZzQAAAAAB3Alx","-pk6w5puGcp-wKnQ61BZzQAAAAAB3AsV","-pk6w5puGcp-wKnQ61BZzQAAAAAB3ap7","-pk6w5puGcp-wKnQ61BZzQAAAAAASlw5","-pk6w5puGcp-wKnQ61BZzQAAAAAASl7K","-pk6w5puGcp-wKnQ61BZzQAAAAAASoJ2","-pk6w5puGcp-wKnQ61BZzQAAAAAASmXx","-pk6w5puGcp-wKnQ61BZzQAAAAAASmDk","-pk6w5puGcp-wKnQ61BZzQAAAAAAiC_B","-pk6w5puGcp-wKnQ61BZzQAAAAAAhzLE","-pk6w5puGcp-wKnQ61BZzQAAAAAAhzP7","-pk6w5puGcp-wKnQ61BZzQAAAAAAgVkn","-pk6w5puGcp-wKnQ61BZzQAAAAAASlp5","-pk6w5puGcp-wKnQ61BZzQAAAAAAVs56","-pk6w5puGcp-wKnQ61BZzQAAAAAAh-jt","-pk6w5puGcp-wKnQ61BZzQAAAAAAVUwE","-pk6w5puGcp-wKnQ61BZzQAAAAAAVATI","-pk6w5puGcp-wKnQ61BZzQAAAAAASsLk","-pk6w5puGcp-wKnQ61BZzQAAAAAASHZk","-pk6w5puGcp-wKnQ61BZzQAAAAAASJlH","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGMy","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGNN","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEH6","piWSMQrh4r040D0BPNaJvwAAAAAAAD_e","piWSMQrh4r040D0BPNaJvwAAAAAAFX1-"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4]},"JVvUxIunvr6V68Rt99rK9w":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791191,24778097,24778417,19045737,19044484,19054298,18859716,18879913,10485923,16807,2741196,2827770,2817385,2759858,2758809,2558430,2672376],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekiX","v6HIzNa4K6G4nRP9032RIAAAAAABehVx","v6HIzNa4K6G4nRP9032RIAAAAAABehax","v6HIzNa4K6G4nRP9032RIAAAAAABIp1p","v6HIzNa4K6G4nRP9032RIAAAAAABIpiE","v6HIzNa4K6G4nRP9032RIAAAAAABIr7a","v6HIzNa4K6G4nRP9032RIAAAAAABH8bE","v6HIzNa4K6G4nRP9032RIAAAAAABIBWp","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM","A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6","A2oiHVwisByxRn5RDT4LjAAAAAAAKv1p","A2oiHVwisByxRn5RDT4LjAAAAAAAKhyy","A2oiHVwisByxRn5RDT4LjAAAAAAAKhiZ","A2oiHVwisByxRn5RDT4LjAAAAAAAJwne","A2oiHVwisByxRn5RDT4LjAAAAAAAKMb4"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"tagsGmBta7BnDHBzEbH9eQ":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429353,40304297,19977269,22569935,22570653,19208948,22544340,19208919,19208225,22608882,19754692,19668808,19001325,18870508,18879802,10485923,16807,2756848,2756092,2745322,6715782,6715626,7927445,6732427,882422,8542429],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeClp","v6HIzNa4K6G4nRP9032RIAAAAAACZv6p","v6HIzNa4K6G4nRP9032RIAAAAAABMNQ1","v6HIzNa4K6G4nRP9032RIAAAAAABWGPP","v6HIzNa4K6G4nRP9032RIAAAAAABWGad","v6HIzNa4K6G4nRP9032RIAAAAAABJRr0","v6HIzNa4K6G4nRP9032RIAAAAAABV__U","v6HIzNa4K6G4nRP9032RIAAAAAABJRrX","v6HIzNa4K6G4nRP9032RIAAAAAABJRgh","v6HIzNa4K6G4nRP9032RIAAAAAABWPvy","v6HIzNa4K6G4nRP9032RIAAAAAABLW7E","v6HIzNa4K6G4nRP9032RIAAAAAABLB9I","v6HIzNa4K6G4nRP9032RIAAAAAABIe_t","v6HIzNa4K6G4nRP9032RIAAAAAABH_Ds","v6HIzNa4K6G4nRP9032RIAAAAAABIBU6","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKhDw","ew01Dk0sWZctP-VaEpavqQAAAAAAKg38","ew01Dk0sWZctP-VaEpavqQAAAAAAKePq","ew01Dk0sWZctP-VaEpavqQAAAAAAZnmG","ew01Dk0sWZctP-VaEpavqQAAAAAAZnjq","ew01Dk0sWZctP-VaEpavqQAAAAAAePaV","ew01Dk0sWZctP-VaEpavqQAAAAAAZrqL","ew01Dk0sWZctP-VaEpavqQAAAAAADXb2","ew01Dk0sWZctP-VaEpavqQAAAAAAgljd"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"CjP83pplY09FGl9PBMeqCg":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226601,40103401,19895829,22487599,22488317,19128052,22462004,19128023,19127329,22526546,19673252,19587368,18920557,18789740,18799034,10485923,16743,2752800,2752044,2741274,6650246,6650090,7860129,6674998,6706857,2411027,2395208],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdRFp","j8DVIOTu7Btj9lgFefJ84AAAAAACY-3p","j8DVIOTu7Btj9lgFefJ84AAAAAABL5YV","j8DVIOTu7Btj9lgFefJ84AAAAAABVyIv","j8DVIOTu7Btj9lgFefJ84AAAAAABVyT9","j8DVIOTu7Btj9lgFefJ84AAAAAABI970","j8DVIOTu7Btj9lgFefJ84AAAAAABVr40","j8DVIOTu7Btj9lgFefJ84AAAAAABI97X","j8DVIOTu7Btj9lgFefJ84AAAAAABI9wh","j8DVIOTu7Btj9lgFefJ84AAAAAABV7pS","j8DVIOTu7Btj9lgFefJ84AAAAAABLDCk","j8DVIOTu7Btj9lgFefJ84AAAAAABKuEo","j8DVIOTu7Btj9lgFefJ84AAAAAABILRt","j8DVIOTu7Btj9lgFefJ84AAAAAABHrVs","j8DVIOTu7Btj9lgFefJ84AAAAAABHtm6","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKgEg","piWSMQrh4r040D0BPNaJvwAAAAAAKf4s","piWSMQrh4r040D0BPNaJvwAAAAAAKdQa","piWSMQrh4r040D0BPNaJvwAAAAAAZXmG","piWSMQrh4r040D0BPNaJvwAAAAAAZXjq","piWSMQrh4r040D0BPNaJvwAAAAAAd--h","piWSMQrh4r040D0BPNaJvwAAAAAAZdo2","piWSMQrh4r040D0BPNaJvwAAAAAAZlap","piWSMQrh4r040D0BPNaJvwAAAAAAJMoT","piWSMQrh4r040D0BPNaJvwAAAAAAJIxI"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4]},"SQ6jhz-Ee7WHXLMOHOsDcQ":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,6715099,4221812],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAZnbb","ew01Dk0sWZctP-VaEpavqQAAAAAAQGt0"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"eM1ATYEKUIN4nyPylmr13A":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440021,7478164],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYaV","ew01Dk0sWZctP-VaEpavqQAAAAAAchuU"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"9vNu8RjYClbqhYYGUiWI7A":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,51380,55074,37132,20242,23612,47200,14250,1480561,1970211,1481652,1480953,2600004,1079669,52860,1480561,1970211,1481652,1480953,2600004,1079483,6166,60608,20250,65302,10604,14228,1479868,2600004,1079483,29728,14228,1479868,2600004,1069332,47952],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","EFJHOn-GACfHXgae-R1yDA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","ktj-IOmkEpvZJouiJkQjTg","O_h7elJSxPO7SiCsftYRZg","DxQN3aM1Ddn1lUwovx75wQ","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","--q8cwZVXbHL2zOM_p3RlQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAMi0","U4Le8nh-beog_B7jq7uTIAAAAAAAANci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAJEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAE8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAFw8","W8AFtEsepzrJ6AasHrCttwAAAAAAALhg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAADeq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","EFJHOn-GACfHXgae-R1yDAAAAAAAAM58","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","kSaNXrGzSS3BnDNNWezzMAAAAAAAABgW","ne8F__HPIVgxgycJADVSzAAAAAAAAOzA","ktj-IOmkEpvZJouiJkQjTgAAAAAAAE8a","O_h7elJSxPO7SiCsftYRZgAAAAAAAP8W","DxQN3aM1Ddn1lUwovx75wQAAAAAAACls","FqNqtF0e0OG1VJJtWE9clwAAAAAAADeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","GEIvPhvjHWZLHz2BksVgvAAAAAAAAHQg","FqNqtF0e0OG1VJJtWE9clwAAAAAAADeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEFEU","--q8cwZVXbHL2zOM_p3RlQAAAAAAALtQ"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3,1,1,3,3,3,1]},"CU-T9AvnxmWd1TTRjgV01Q":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2755760,2754888,2744099,6711233,7651644,7435512,7508830,6761766,2559050],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw","9LzzIocepYcOjnUsLlgOjgAAAAAAKglI","9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j","9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB","9LzzIocepYcOjnUsLlgOjgAAAAAAdME8","9LzzIocepYcOjnUsLlgOjgAAAAAAcXT4","9LzzIocepYcOjnUsLlgOjgAAAAAAcpNe","9LzzIocepYcOjnUsLlgOjgAAAAAAZy0m","9LzzIocepYcOjnUsLlgOjgAAAAAAJwxK"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"hoJT-ObO7MDFTgt9UeFJfg":{"address_or_lines":[980270,29770,3203438,1526226,1526293,1526410,1522622,1523799,453712,1320069,1900469,1899334,1898707,2062274,2293545,2285857,2284809,2485949,2472275,2784493,2826658,2822585,3001783,2924437,3111967,3095700,156159,136664,1348522,1348436,1345741,1348060,1347558,1345741,1348060,1347558,1344317,1318852,1317318,469350,452199,518055,511351],"file_ids":["Z_CHd3Zjsh2cWE2NSdbiNQ","eOfhJQFIxbIEScd007tROw","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","9HZ7GQCC6G9fZlRD7aGzXQ","9HZ7GQCC6G9fZlRD7aGzXQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ"],"frame_ids":["Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADvUu","eOfhJQFIxbIEScd007tROwAAAAAAAHRK","-p9BlJh9JZMPPNjY_j92ngAAAAAAMOFu","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0nS","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0oV","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0qK","-p9BlJh9JZMPPNjY_j92ngAAAAAAFzu-","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0BX","-p9BlJh9JZMPPNjY_j92ngAAAAAABuxQ","-p9BlJh9JZMPPNjY_j92ngAAAAAAFCSF","-p9BlJh9JZMPPNjY_j92ngAAAAAAHP-1","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPtG","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPjT","-p9BlJh9JZMPPNjY_j92ngAAAAAAH3fC","-p9BlJh9JZMPPNjY_j92ngAAAAAAIv8p","-p9BlJh9JZMPPNjY_j92ngAAAAAAIuEh","-p9BlJh9JZMPPNjY_j92ngAAAAAAIt0J","-p9BlJh9JZMPPNjY_j92ngAAAAAAJe69","-p9BlJh9JZMPPNjY_j92ngAAAAAAJblT","-p9BlJh9JZMPPNjY_j92ngAAAAAAKnzt","-p9BlJh9JZMPPNjY_j92ngAAAAAAKyGi","-p9BlJh9JZMPPNjY_j92ngAAAAAAKxG5","-p9BlJh9JZMPPNjY_j92ngAAAAAALc23","-p9BlJh9JZMPPNjY_j92ngAAAAAALJ-V","-p9BlJh9JZMPPNjY_j92ngAAAAAAL3wf","-p9BlJh9JZMPPNjY_j92ngAAAAAALzyU","9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAmH_","9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAhXY","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJOq","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJNU","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIM9","huWyXZbCBWCe2ZtK9BiokQAAAAAAFB_E","huWyXZbCBWCe2ZtK9BiokQAAAAAAFBnG","huWyXZbCBWCe2ZtK9BiokQAAAAAABylm","huWyXZbCBWCe2ZtK9BiokQAAAAAABuZn","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB-en","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB813"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"us5XzJaFA8Y8a8Jhq7VWzQ":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7439971,6798378,6797926,6797556,2726254,449444],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYZj","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7wq","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7pm","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7j0","ew01Dk0sWZctP-VaEpavqQAAAAAAKZlu","ew01Dk0sWZctP-VaEpavqQAAAAAABtuk"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"tWPDa1sBMePW-YFiahrHBA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10489481,12583132,6878809,6871998,6871380,7366427,7371724,7390232,7379824,6863646,7218707,7217709,6862495,13713],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","5OhlekN4HU3KaqhG_GtinA"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoA6J","9LzzIocepYcOjnUsLlgOjgAAAAAAwADc","9LzzIocepYcOjnUsLlgOjgAAAAAAaPZZ","9LzzIocepYcOjnUsLlgOjgAAAAAAaNu-","9LzzIocepYcOjnUsLlgOjgAAAAAAaNlU","9LzzIocepYcOjnUsLlgOjgAAAAAAcGcb","9LzzIocepYcOjnUsLlgOjgAAAAAAcHvM","9LzzIocepYcOjnUsLlgOjgAAAAAAcMQY","9LzzIocepYcOjnUsLlgOjgAAAAAAcJtw","9LzzIocepYcOjnUsLlgOjgAAAAAAaLse","9LzzIocepYcOjnUsLlgOjgAAAAAAbiYT","9LzzIocepYcOjnUsLlgOjgAAAAAAbiIt","9LzzIocepYcOjnUsLlgOjgAAAAAAaLaf","5OhlekN4HU3KaqhG_GtinAAAAAAAADWR"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"KKjaO47Ew4fmVCY-lBFkLg":{"address_or_lines":[980270,29770,3203438,1526226,1526293,1526410,1522622,1523799,453712,1320069,1900469,1899334,1898707,2062274,2293545,2285857,2284809,2485949,2472275,2784493,2826658,2823003,3007344,3001783,2924437,3112045,3104142,1417998,1456694,1456323,1393341,1348522,1348436,1345741,1348060,1347558,1345741,1348060,1347558,1344317,1318852,1317297,1335062,1334886,452199,517552],"file_ids":["Z_CHd3Zjsh2cWE2NSdbiNQ","eOfhJQFIxbIEScd007tROw","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","Z_CHd3Zjsh2cWE2NSdbiNQ"],"frame_ids":["Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADvUu","eOfhJQFIxbIEScd007tROwAAAAAAAHRK","-p9BlJh9JZMPPNjY_j92ngAAAAAAMOFu","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0nS","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0oV","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0qK","-p9BlJh9JZMPPNjY_j92ngAAAAAAFzu-","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0BX","-p9BlJh9JZMPPNjY_j92ngAAAAAABuxQ","-p9BlJh9JZMPPNjY_j92ngAAAAAAFCSF","-p9BlJh9JZMPPNjY_j92ngAAAAAAHP-1","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPtG","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPjT","-p9BlJh9JZMPPNjY_j92ngAAAAAAH3fC","-p9BlJh9JZMPPNjY_j92ngAAAAAAIv8p","-p9BlJh9JZMPPNjY_j92ngAAAAAAIuEh","-p9BlJh9JZMPPNjY_j92ngAAAAAAIt0J","-p9BlJh9JZMPPNjY_j92ngAAAAAAJe69","-p9BlJh9JZMPPNjY_j92ngAAAAAAJblT","-p9BlJh9JZMPPNjY_j92ngAAAAAAKnzt","-p9BlJh9JZMPPNjY_j92ngAAAAAAKyGi","-p9BlJh9JZMPPNjY_j92ngAAAAAAKxNb","-p9BlJh9JZMPPNjY_j92ngAAAAAALeNw","-p9BlJh9JZMPPNjY_j92ngAAAAAALc23","-p9BlJh9JZMPPNjY_j92ngAAAAAALJ-V","-p9BlJh9JZMPPNjY_j92ngAAAAAAL3xt","-p9BlJh9JZMPPNjY_j92ngAAAAAAL12O","huWyXZbCBWCe2ZtK9BiokQAAAAAAFaMO","huWyXZbCBWCe2ZtK9BiokQAAAAAAFjo2","huWyXZbCBWCe2ZtK9BiokQAAAAAAFjjD","huWyXZbCBWCe2ZtK9BiokQAAAAAAFUK9","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJOq","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJNU","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIM9","huWyXZbCBWCe2ZtK9BiokQAAAAAAFB_E","huWyXZbCBWCe2ZtK9BiokQAAAAAAFBmx","huWyXZbCBWCe2ZtK9BiokQAAAAAAFF8W","huWyXZbCBWCe2ZtK9BiokQAAAAAAFF5m","huWyXZbCBWCe2ZtK9BiokQAAAAAABuZn","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB-Ww"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"zxyQebekMWvnWWEuWSzR9Q":{"address_or_lines":[4652224,22357367,22385134,22366798,57080079,58879477,58676957,58636100,58650141,31265873,31266293,31372635,4873273,4873930,4883062,4875761,4874468,8927681,8862916,8863227,8479623,4872825,5688954,8909549,5590020,5506248,4899556,4748900,4757831,4219698,4219725,10485923,16807,2756288,2755416,2744627,6715329,7926130,7925524,6772762,6770749,6770671,7937674,6744271,7917830,882422,8541549],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZvkP","B8JRxL079xbhqQBqGvksAgAAAAADgm31","B8JRxL079xbhqQBqGvksAgAAAAADf1bd","B8JRxL079xbhqQBqGvksAgAAAAADfrdE","B8JRxL079xbhqQBqGvksAgAAAAADfu4d","B8JRxL079xbhqQBqGvksAgAAAAAB3RRR","B8JRxL079xbhqQBqGvksAgAAAAAB3RX1","B8JRxL079xbhqQBqGvksAgAAAAAB3rVb","B8JRxL079xbhqQBqGvksAgAAAAAASlw5","B8JRxL079xbhqQBqGvksAgAAAAAASl7K","B8JRxL079xbhqQBqGvksAgAAAAAASoJ2","B8JRxL079xbhqQBqGvksAgAAAAAASmXx","B8JRxL079xbhqQBqGvksAgAAAAAASmDk","B8JRxL079xbhqQBqGvksAgAAAAAAiDnB","B8JRxL079xbhqQBqGvksAgAAAAAAhzzE","B8JRxL079xbhqQBqGvksAgAAAAAAhz37","B8JRxL079xbhqQBqGvksAgAAAAAAgWOH","B8JRxL079xbhqQBqGvksAgAAAAAASlp5","B8JRxL079xbhqQBqGvksAgAAAAAAVs56","B8JRxL079xbhqQBqGvksAgAAAAAAh_Lt","B8JRxL079xbhqQBqGvksAgAAAAAAVUwE","B8JRxL079xbhqQBqGvksAgAAAAAAVATI","B8JRxL079xbhqQBqGvksAgAAAAAASsLk","B8JRxL079xbhqQBqGvksAgAAAAAASHZk","B8JRxL079xbhqQBqGvksAgAAAAAASJlH","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A","A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY","A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz","A2oiHVwisByxRn5RDT4LjAAAAAAAZnfB","A2oiHVwisByxRn5RDT4LjAAAAAAAePFy","A2oiHVwisByxRn5RDT4LjAAAAAAAeO8U","A2oiHVwisByxRn5RDT4LjAAAAAAAZ1ga","A2oiHVwisByxRn5RDT4LjAAAAAAAZ1A9","A2oiHVwisByxRn5RDT4LjAAAAAAAZ0_v","A2oiHVwisByxRn5RDT4LjAAAAAAAeR6K","A2oiHVwisByxRn5RDT4LjAAAAAAAZujP","A2oiHVwisByxRn5RDT4LjAAAAAAAeNEG","A2oiHVwisByxRn5RDT4LjAAAAAAADXb2","A2oiHVwisByxRn5RDT4LjAAAAAAAglVt"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"UI-7Z494NKAWuv1FuNlxoQ":{"address_or_lines":[4652224,59049454,56939078,10401064,10401333,10401661,56939173,56937529,56937108,38310942,29802677,29803353,29746360,8752265,4268420,4265510,4264588,4297532,10488398,10493154,585663,12583132,6882905,21536,6881628,6877992,6877443,6876950,7370944,7369391,7367054,7370328,7370195,7369770,7552115,7547124,7496717,7491196,7486785,7507864,7393057,7394424,7384016,6867742,7222899,7221901,6866591,13650],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","R3YNZBiWt7Z3ZpFfTh6XyQ","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","R3YNZBiWt7Z3ZpFfTh6XyQ"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAADhQXu","B8JRxL079xbhqQBqGvksAgAAAAADZNJG","B8JRxL079xbhqQBqGvksAgAAAAAAnrUo","B8JRxL079xbhqQBqGvksAgAAAAAAnrY1","B8JRxL079xbhqQBqGvksAgAAAAAAnrd9","B8JRxL079xbhqQBqGvksAgAAAAADZNKl","B8JRxL079xbhqQBqGvksAgAAAAADZMw5","B8JRxL079xbhqQBqGvksAgAAAAADZMqU","B8JRxL079xbhqQBqGvksAgAAAAACSJQe","B8JRxL079xbhqQBqGvksAgAAAAABxsC1","B8JRxL079xbhqQBqGvksAgAAAAABxsNZ","B8JRxL079xbhqQBqGvksAgAAAAABxeS4","B8JRxL079xbhqQBqGvksAgAAAAAAhYyJ","B8JRxL079xbhqQBqGvksAgAAAAAAQSGE","B8JRxL079xbhqQBqGvksAgAAAAAAQRYm","B8JRxL079xbhqQBqGvksAgAAAAAAQRKM","B8JRxL079xbhqQBqGvksAgAAAAAAQZM8","A2oiHVwisByxRn5RDT4LjAAAAAAAoApO","A2oiHVwisByxRn5RDT4LjAAAAAAAoBzi","A2oiHVwisByxRn5RDT4LjAAAAAAACO-_","A2oiHVwisByxRn5RDT4LjAAAAAAAwADc","A2oiHVwisByxRn5RDT4LjAAAAAAAaQZZ","R3YNZBiWt7Z3ZpFfTh6XyQAAAAAAAFQg","A2oiHVwisByxRn5RDT4LjAAAAAAAaQFc","A2oiHVwisByxRn5RDT4LjAAAAAAAaPMo","A2oiHVwisByxRn5RDT4LjAAAAAAAaPED","A2oiHVwisByxRn5RDT4LjAAAAAAAaO8W","A2oiHVwisByxRn5RDT4LjAAAAAAAcHjA","A2oiHVwisByxRn5RDT4LjAAAAAAAcHKv","A2oiHVwisByxRn5RDT4LjAAAAAAAcGmO","A2oiHVwisByxRn5RDT4LjAAAAAAAcHZY","A2oiHVwisByxRn5RDT4LjAAAAAAAcHXT","A2oiHVwisByxRn5RDT4LjAAAAAAAcHQq","A2oiHVwisByxRn5RDT4LjAAAAAAAczxz","A2oiHVwisByxRn5RDT4LjAAAAAAAcyj0","A2oiHVwisByxRn5RDT4LjAAAAAAAcmQN","A2oiHVwisByxRn5RDT4LjAAAAAAAck58","A2oiHVwisByxRn5RDT4LjAAAAAAAcj1B","A2oiHVwisByxRn5RDT4LjAAAAAAAco-Y","A2oiHVwisByxRn5RDT4LjAAAAAAAcM8h","A2oiHVwisByxRn5RDT4LjAAAAAAAcNR4","A2oiHVwisByxRn5RDT4LjAAAAAAAcKvQ","A2oiHVwisByxRn5RDT4LjAAAAAAAaMse","A2oiHVwisByxRn5RDT4LjAAAAAAAbjZz","A2oiHVwisByxRn5RDT4LjAAAAAAAbjKN","A2oiHVwisByxRn5RDT4LjAAAAAAAaMaf","R3YNZBiWt7Z3ZpFfTh6XyQAAAAAAADVS"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"6yHX0lcyWmly8MshBzd78Q":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,34996,38690,20748,3858,3132,30816,59306,1480561,1970211,1481652,1480953,2600004,1079483,36350,56142,27276,48820,6316,1479960,1494280,2600004,1079483,31058,15346,1479960,2600004,1079483,44156,54044,53948,63380,1479868,2600004,1079483,8496,63380,1479868,2600004,1056891,26970,28876,2143205,2040020],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","EFJHOn-GACfHXgae-R1yDA","GdaBUD9IUEkKxIBryNqV2w","QU8QLoFK6ojrywKrBFfTzA","V558DAsp4yi8bwa8eYwk5Q","tuTnMBfyc9UiPsI0QyvErA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","cHp4MwXaY5FCuFRuAA6tWw","-9oyoP4Jj2iRkwEezqId-g","3FRCbvQLPuJyn2B-2wELGw","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","--q8cwZVXbHL2zOM_p3RlQ","yaTrLhUSIq2WitrTHLBy3Q","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAIi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAJci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAFEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAA8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAAw8","W8AFtEsepzrJ6AasHrCttwAAAAAAAHhg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAOeq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","EFJHOn-GACfHXgae-R1yDAAAAAAAAI3-","GdaBUD9IUEkKxIBryNqV2wAAAAAAANtO","QU8QLoFK6ojrywKrBFfTzAAAAAAAAGqM","V558DAsp4yi8bwa8eYwk5QAAAAAAAL60","tuTnMBfyc9UiPsI0QyvErAAAAAAAABis","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","oERZXsH8EPeoSRxNNaSWfQAAAAAAAHlS","gMhgHDYSMmyInNJ15VwYFgAAAAAAADvy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","cHp4MwXaY5FCuFRuAA6tWwAAAAAAAKx8","-9oyoP4Jj2iRkwEezqId-gAAAAAAANMc","3FRCbvQLPuJyn2B-2wELGwAAAAAAANK8","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","GEIvPhvjHWZLHz2BksVgvAAAAAAAACEw","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAECB7","--q8cwZVXbHL2zOM_p3RlQAAAAAAAGla","yaTrLhUSIq2WitrTHLBy3QAAAAAAAHDM","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAILPl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHyDU"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,1,1,1,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,1,3,3]},"uEL43HtanLRCO2rLB4ttzQ":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,64358,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,11986,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,51652,2573747,2594708,1091475,13186,2790352,1482889,1482415,2595076,1069851,33394,1493754,2595076,1049998,50014,45950,2995046,2994923,3072326,3072096,3066615,1917744],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","8EY5iPD5-FtlXFBTyb6lkw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","dCCKy6JoX0PADOFic8hRNQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","7RLN3PNgotUSmdQVMRTSvA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","43vJVfBcAahhLMzDSC-H0g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","RRFdsCrJw1U2erb6qtrrzQ","_zH-ed4x-42m0B4z2RmcdQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","8EY5iPD5-FtlXFBTyb6lkwAAAAAAAPtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","dCCKy6JoX0PADOFic8hRNQAAAAAAAC7S","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","7RLN3PNgotUSmdQVMRTSvAAAAAAAAMnE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","43vJVfBcAahhLMzDSC-H0gAAAAAAADOC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEFMb","ik6PIX946fW_erE7uBJlVQAAAAAAAIJy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsr6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEAWO","RRFdsCrJw1U2erb6qtrrzQAAAAAAAMNe","_zH-ed4x-42m0B4z2RmcdQAAAAAAALN-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALbNm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALbLr","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuFG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuBg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALsr3","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUMw"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,1,1,3,3,3,3,3,3]},"mXgK2ekWZ4qH-uHB8QaLtA":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,824,116,12,8,54,12,46,22,1091612,1804498,665668,663668,1112453,1232178,833111,2265137,2264574,2258679],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","IlUL618nbeW5Kz4uyGZLrQ","U7DZUwH_4YU5DSkoQhGJWw","bmb3nSRfimrjfhanpjR1rQ","oN7OWDJeuc8DmI2f_earDQ","Yj7P3-Rt3nirG6apRl4A7A","pz3Evn9laHNJFMwOKIXbsw","7aaw2O1Vn7-6eR8XuUWQZQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAAM4","IlUL618nbeW5Kz4uyGZLrQAAAAAAAAB0","U7DZUwH_4YU5DSkoQhGJWwAAAAAAAAAM","bmb3nSRfimrjfhanpjR1rQAAAAAAAAAI","oN7OWDJeuc8DmI2f_earDQAAAAAAAAA2","Yj7P3-Rt3nirG6apRl4A7AAAAAAAAAAM","pz3Evn9laHNJFMwOKIXbswAAAAAAAAAu","7aaw2O1Vn7-6eR8XuUWQZQAAAAAAAAAW","G68hjsyagwq6LpWrMjDdngAAAAAAEKgc","G68hjsyagwq6LpWrMjDdngAAAAAAG4jS","G68hjsyagwq6LpWrMjDdngAAAAAACihE","G68hjsyagwq6LpWrMjDdngAAAAAACiB0","G68hjsyagwq6LpWrMjDdngAAAAAAEPmF","G68hjsyagwq6LpWrMjDdngAAAAAAEs0y","G68hjsyagwq6LpWrMjDdngAAAAAADLZX","G68hjsyagwq6LpWrMjDdngAAAAAAIpAx","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAInb3"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3]},"1twYzjHR6hCfJqQLvJ81XA":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,33156,1058,33388,19218,50892,43744,57354,1480209,1969795,1481300,1480601,2595076,1079144,34636,1480209,1969795,1481300,1480601,2595076,1075570,17430,40768,26744,7590,63980,23014,47110,19666,47110,34306,44426,44426,44426,44426,44426,44426,44426,44334,47110,46588,46966,1670488,3072326,3072096,3066777,1745028],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","CwUjPVV5_7q7c0GhtW0aPw","O_h7elJSxPO7SiCsftYRZg","ZLTqiSLOmv4Ej_7d8yKLmw","qLiwuFhv6DIyQ0OgaSMXCg","ka2IKJhpWbD6PA3J3v624w","e8Lb_MV93AH-OkvHPPDitg","ka2IKJhpWbD6PA3J3v624w","1vivUE5hL65442lQ9a_ylg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","ka2IKJhpWbD6PA3J3v624w","fCsVLBj60GK9Hf8VtnMcgA","ka2IKJhpWbD6PA3J3v624w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAMbM","W8AFtEsepzrJ6AasHrCttwAAAAAAAKrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAOAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAIdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGly","kSaNXrGzSS3BnDNNWezzMAAAAAAAAEQW","ne8F__HPIVgxgycJADVSzAAAAAAAAJ9A","CwUjPVV5_7q7c0GhtW0aPwAAAAAAAGh4","O_h7elJSxPO7SiCsftYRZgAAAAAAAB2m","ZLTqiSLOmv4Ej_7d8yKLmwAAAAAAAPns","qLiwuFhv6DIyQ0OgaSMXCgAAAAAAAFnm","ka2IKJhpWbD6PA3J3v624wAAAAAAALgG","e8Lb_MV93AH-OkvHPPDitgAAAAAAAEzS","ka2IKJhpWbD6PA3J3v624wAAAAAAALgG","1vivUE5hL65442lQ9a_ylgAAAAAAAIYC","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK0u","ka2IKJhpWbD6PA3J3v624wAAAAAAALgG","fCsVLBj60GK9Hf8VtnMcgAAAAAAAALX8","ka2IKJhpWbD6PA3J3v624wAAAAAAALd2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGX1Y","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuFG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuBg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALsuZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGqCE"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3]},"f-LRF9Sfj675yc68DOXczw":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,56302,2790352,1482889,1482415,2595076,1079144,25326,27384,368,1760,1481694,1828960,2573747,2594708,1091475,16910,2790352,1482889,1482415,2595076,1079144,25326,27384,368,1760,1481694,1828960,2573747,2594708,1073425,16424,24340,2572553,2928589,1108138,1105869,1310238,1245752,1200236,1192099,1183786,1104144,1103499,2268402,1775000,1761295,1048342],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","cfc92_adXFZraMPGbgbcDg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","WLefmNR3IpykzCX3WWNnMw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","IvJrzqPEgeoowZySdwFq3w","vkeP2ntYyoFN0A16x9eliw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","cfc92_adXFZraMPGbgbcDgAAAAAAANvu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","WLefmNR3IpykzCX3WWNnMwAAAAAAAEIO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGER","IvJrzqPEgeoowZySdwFq3wAAAAAAAEAo","vkeP2ntYyoFN0A16x9eliwAAAAAAAF8U","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0EJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALK_N","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEOiq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEN_N","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAE_4e","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEwI4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAElBs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEjCj","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEhAq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAENkQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAENaL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIpzy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGxWY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGuAP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAD_8W"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"p24lyWOwFjGMsQaWybQUMA":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,36384,21728,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,0,2789627,1482889,1482415,2595076,1079485,54384,2918,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1079144,0,1481694,1828960,2581397,1480601,1480209,1940568,1986447,1982493,1959028,1099442],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MXHCWLuAJw7Gg6T7hdrPHA","ecHSwk0KAG7gFkiYdAgIZw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MXHCWLuAJw7Gg6T7hdrPHAAAAAAAAI4g","ecHSwk0KAG7gFkiYdAgIZwAAAAAAAFTg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAANRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAAtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHk-P","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHkAd","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHeR0","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEMay"],"type_ids":[3,3,3,3,3,3,1,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3]},"KHat1RLkyP8wPwwR1uD04A":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,40,38,174,104,68,80,38,174,104,68,60,38,174,104,68,382,38,174,104,68,24,38,174,104,68,28,38,174,104,68,0,1090933,1814182,788459,788130,1197048,1243240,1238413,1212345,1033898,429638],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","0cqvso24v07beLsmyC0nMw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","3WU6MO1xF7O0NmrHFj4y4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","x617yDiAG2Sqq3cLDkX4aA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ZTmztUywGW_uHXPqWVr76w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ZPAF8mJO2n0azNbxzkJ2rA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_____________________w","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAo","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","0cqvso24v07beLsmyC0nMwAAAAAAAABQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","3WU6MO1xF7O0NmrHFj4y4AAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","x617yDiAG2Sqq3cLDkX4aAAAAAAAAAF-","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ZTmztUywGW_uHXPqWVr76wAAAAAAAAAY","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ZPAF8mJO2n0azNbxzkJ2rAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_____________________wAAAAAAAAAA","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","G68hjsyagwq6LpWrMjDdngAAAAAAG66m","G68hjsyagwq6LpWrMjDdngAAAAAADAfr","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkP4","G68hjsyagwq6LpWrMjDdngAAAAAAEvho","G68hjsyagwq6LpWrMjDdngAAAAAAEuWN","G68hjsyagwq6LpWrMjDdngAAAAAAEn-5","G68hjsyagwq6LpWrMjDdngAAAAAAD8aq","G68hjsyagwq6LpWrMjDdngAAAAAABo5G"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3]},"B-OQjwP7KzSb4f6cXUL1bA":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,3616,42208,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,0,2789627,1482889,1482415,2595076,1079485,50288,64358,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1079144,0,1481694,1828960,2581397,1480601,1480209,1940568,1986405,1946637,1538878,2269465,2268402,1774938,1011120],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MXHCWLuAJw7Gg6T7hdrPHA","ecHSwk0KAG7gFkiYdAgIZw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MXHCWLuAJw7Gg6T7hdrPHAAAAAAAAA4g","ecHSwk0KAG7gFkiYdAgIZwAAAAAAAKTg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAMRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAPtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHk9l","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHbQN","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAF3s-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIqEZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIpzy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGxVa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAD22w"],"type_ids":[3,3,3,3,3,3,1,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3]},"kOWftL0Ttias8Z1isZi9oA":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,49540,17442,49772,35602,58710,61916,19828,27444,26096,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,12482,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,49534,2790352,1482889,1482415,2595076,1097615,37614,39672,12656,17976,49494,2722496,3251876,3237020,1748920],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","SOSrvCNmbstVFKAcqHNCvA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAMJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAIsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAOVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAPHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAE10","xwuAPHgc12-8PZB3i-320gAAAAAAAGs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAADDC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","SOSrvCNmbstVFKAcqHNCvAAAAAAAAMF-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEL-P","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAMFW","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKYrA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMZ6k","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMWSc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGq-4"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,3,3,3,3]},"JzGylmBPluUmIML9XnagKw":{"address_or_lines":[2599636,1079669,2228,5922,53516,36626,36806,45836,18932,13860,58864,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,56398,58456,31408,16708,2578675,2599636,1091600,36298,2795776,1483241,1482767,2600004,1074397,56398,58456,31408,16708,2578675,2599636,1091600,46582,2795776,1483241,1482767,2600004,1073803,56398,58456,31408,16492,49494,45794,2852079,2851771,2849353,2846190,2849353,2846190,2847233,2838792],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","SD7uzoegJjRT3jYNpuQ5wQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0","U4Le8nh-beog_B7jq7uTIAAAAAAAABci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAI_G","LF6DFcGHEMqhhhlptO_M_QAAAAAAALMM","Af6E3BeG383JVVbu67NJ0QAAAAAAAEn0","xwuAPHgc12-8PZB3i-320gAAAAAAADYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAOXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAANxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAORY","J1eggTwSzYdi9OsSu1q37gAAAAAAAHqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAEFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAI3K","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAANxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAORY","J1eggTwSzYdi9OsSu1q37gAAAAAAAHqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAEFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","SD7uzoegJjRT3jYNpuQ5wQAAAAAAALX2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAANxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAORY","J1eggTwSzYdi9OsSu1q37gAAAAAAAHqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAEBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAMFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAALLi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3IB","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK1EI"],"type_ids":[3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3]},"tTw0tfSnPtZhbcyzyVHHpg":{"address_or_lines":[4622976,4423302,48950246,48930003,48929418,48931768,15219528,15219797,15220198,48932134,15224283,15224488,15224631,15220795,15220538,48932900,48934534,48924362,21171091,15443915,15441240,6695879,6686586,6688471,15292865,6927608,7025423,9353786,9296758,9312446,9317924,5671585,9381613,9295438,6263620,6258992,6257863,6068365,6003908,5935528,5054445,4702860,4711258,10485923,16743,2752800,2752044,2741274,6650246,6650083,7384662,7382442,7451553,7447772,7441688,7327025,7328392,7317984,6802313,6799580,6799223,6797958],"file_ids":["-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","-SVIyCZG9IbFKK-fe2Wh4g","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["-SVIyCZG9IbFKK-fe2Wh4gAAAAAARoqA","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAQ36G","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6uvm","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6pzT","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6pqK","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6qO4","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6DtI","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6DxV","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6D3m","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6qUm","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6E3b","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6E6o","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6E83","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6EA7","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6D86","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6qgk","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6q6G","-SVIyCZG9IbFKK-fe2Wh4gAAAAAC6obK","-SVIyCZG9IbFKK-fe2Wh4gAAAAABQwuT","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA66fL","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA651Y","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAZivH","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAZgd6","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAZg7X","-SVIyCZG9IbFKK-fe2Wh4gAAAAAA6VnB","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAabT4","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAazMP","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAjro6","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAjdt2","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAjhi-","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAji4k","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAVoqh","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAjybt","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAjdZO","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAX5NE","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAX4Ew","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAX3zH","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAXJiN","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAW5zE","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAWpGo","-SVIyCZG9IbFKK-fe2Wh4gAAAAAATR_t","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAR8KM","-SVIyCZG9IbFKK-fe2Wh4gAAAAAAR-Na","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKgEg","piWSMQrh4r040D0BPNaJvwAAAAAAKf4s","piWSMQrh4r040D0BPNaJvwAAAAAAKdQa","piWSMQrh4r040D0BPNaJvwAAAAAAZXmG","piWSMQrh4r040D0BPNaJvwAAAAAAZXjj","piWSMQrh4r040D0BPNaJvwAAAAAAcK5W","piWSMQrh4r040D0BPNaJvwAAAAAAcKWq","piWSMQrh4r040D0BPNaJvwAAAAAAcbOh","piWSMQrh4r040D0BPNaJvwAAAAAAcaTc","piWSMQrh4r040D0BPNaJvwAAAAAAcY0Y","piWSMQrh4r040D0BPNaJvwAAAAAAb80x","piWSMQrh4r040D0BPNaJvwAAAAAAb9KI","piWSMQrh4r040D0BPNaJvwAAAAAAb6ng","piWSMQrh4r040D0BPNaJvwAAAAAAZ8uJ","piWSMQrh4r040D0BPNaJvwAAAAAAZ8Dc","piWSMQrh4r040D0BPNaJvwAAAAAAZ793","piWSMQrh4r040D0BPNaJvwAAAAAAZ7qG"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"E_F-N51BcZ4iQ9oPaHFKXw":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,400,38,174,104,68,20,38,174,104,68,88,38,174,104,14,32,190,1091944,2047231,2046923,2044755,2041537,2044780,2041460,1171829,2265239,2264574,2258463,1015963,2256180],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","c-eM3dWacIPzBmA_7-OWBw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","w9AQfBE7-1YeE4mOMirPBg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAAGQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","c-eM3dWacIPzBmA_7-OWBwAAAAAAAAAU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","w9AQfBE7-1YeE4mOMirPBgAAAAAAAABY","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAAC-","G68hjsyagwq6LpWrMjDdngAAAAAAEKlo","G68hjsyagwq6LpWrMjDdngAAAAAAHzz_","G68hjsyagwq6LpWrMjDdngAAAAAAHzvL","G68hjsyagwq6LpWrMjDdngAAAAAAHzNT","G68hjsyagwq6LpWrMjDdngAAAAAAHybB","G68hjsyagwq6LpWrMjDdngAAAAAAHzNs","G68hjsyagwq6LpWrMjDdngAAAAAAHyZ0","G68hjsyagwq6LpWrMjDdngAAAAAAEeF1","G68hjsyagwq6LpWrMjDdngAAAAAAIpCX","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAInYf","G68hjsyagwq6LpWrMjDdngAAAAAAD4Cb","G68hjsyagwq6LpWrMjDdngAAAAAAIm00"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3]},"d04G8ZHV3kYQ0ekQBw1VYQ":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,388,33826,620,51986,62806,476,36212,43828,42480,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,17614,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,41518,2790352,1482889,1482415,2595076,1076587,25326,27384,368,1592,16726,55682,2846655,2846347,2843929,2840766,2843929,2840766,2843929,2840692,1912597,3072400],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uo8E5My6tupMEt-pfV-uhA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAPVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAAHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAI10","xwuAPHgc12-8PZB3i-320gAAAAAAAKs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAETO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","uo8E5My6tupMEt-pfV-uhAAAAAAAAKIu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAANmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1h0","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHS8V","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuGQ"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3]},"I-DofAMUQgh7q14tBJcZlA":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,33156,1058,33388,19218,30412,43744,6426,1480209,1969795,1481300,1480601,2595076,1079144,34636,1480209,1969795,1481300,1480601,2595076,1062336,60522,1844695,1847563,1481567,2595076,1079485,19388,48282,27404,1479608,1493928,2595076,1079485,63084,1479608,1493928,2595076,1079485,63346,48114,1479608,2595076,1079485,5750,41842,34364,63380,1479516,2595076,1079485,14544,63380,1479516,2595076,1056995,11370,55184,2188039,2032414,1865128],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","xNMiNBkMujk7ZnRv0OEjrQ","MYrgKQIxdDhr1gdpucfc-Q","un9fLDZOLvDMO52ltZtueg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","grikUXlisBLUbeL_OWixIw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","rTFMSHhLRlj86vHPR06zoQ","oArGmvsy3VNtTf_V9EHNeQ","7v-k2b21f_Xuf-3329jFyw","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","--q8cwZVXbHL2zOM_p3RlQ","yaTrLhUSIq2WitrTHLBy3Q","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAHbM","W8AFtEsepzrJ6AasHrCttwAAAAAAAKrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAABka","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAIdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEDXA","kSaNXrGzSS3BnDNNWezzMAAAAAAAAOxq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDEL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","xNMiNBkMujk7ZnRv0OEjrQAAAAAAAEu8","MYrgKQIxdDhr1gdpucfc-QAAAAAAALya","un9fLDZOLvDMO52ltZtuegAAAAAAAGsM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","grikUXlisBLUbeL_OWixIwAAAAAAAPZs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","oERZXsH8EPeoSRxNNaSWfQAAAAAAAPdy","gMhgHDYSMmyInNJ15VwYFgAAAAAAALvy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","rTFMSHhLRlj86vHPR06zoQAAAAAAABZ2","oArGmvsy3VNtTf_V9EHNeQAAAAAAAKNy","7v-k2b21f_Xuf-3329jFywAAAAAAAIY8","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","GEIvPhvjHWZLHz2BksVgvAAAAAAAADjQ","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAECDj","--q8cwZVXbHL2zOM_p3RlQAAAAAAACxq","yaTrLhUSIq2WitrTHLBy3QAAAAAAANeQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIWMH","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHwMe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHWo"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,1,3,3,3]},"tGGi0acvAmmxOR5DbuF3dg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,49488,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,20126,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,12078,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1079144,65228,1481694,1828960,2581397,1480843,1480209,1940568,1917258,1481300,1480601,2595076,1079144,28888,1480209,1827586,1940195,1986405,1946664,1775467,1749899,1745572,1865128],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MU3fJpOZe9TA4mzeo52wZg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N0GNsPaCLYzoFsPJWnIJtQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fq0ezjB8ddCA6Pk0BY9arQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","r1l-BTVp1g6dSvPPoOY_cg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","DTRaillMS4wmG2CDEfm9rQAAAAAAAMFQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MU3fJpOZe9TA4mzeo52wZgAAAAAAAE6e","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N0GNsPaCLYzoFsPJWnIJtQAAAAAAAC8u","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","fq0ezjB8ddCA6Pk0BY9arQAAAAAAAP7M","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpiL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUFK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","r1l-BTVp1g6dSvPPoOY_cgAAAAAAAHDY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-MC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZrj","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHk9l","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHbQo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGxdr","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGrOL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGqKk","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHWo"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3]},"Ws9TqFMz-kHv_-7zrBFdKw":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,212,38,174,104,68,188,38,174,104,68,60,38,174,104,68,98,38,174,104,68,8,38,174,104,68,36,38,174,104,14,32,166,1090933,19429,41240,50286],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","bAXCoU3-CU0WlRxl5l1tmw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","IcegEVkl4JzbMBhUeMqp0Q","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","tz0ps4QDYR1clO_q5ziJUQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","M0gS5SrmklEEjlV4jbSIBA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","k5C4r96b77lEZ_fHFwCYkQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","G68hjsyagwq6LpWrMjDdng","EX9l-cE0x8X9W8uz4iKUfw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAADU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","bAXCoU3-CU0WlRxl5l1tmwAAAAAAAAC8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","IcegEVkl4JzbMBhUeMqp0QAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","tz0ps4QDYR1clO_q5ziJUQAAAAAAAABi","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","M0gS5SrmklEEjlV4jbSIBAAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","k5C4r96b77lEZ_fHFwCYkQAAAAAAAAAk","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAACm","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","EX9l-cE0x8X9W8uz4iKUfwAAAAAAAEvl","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMRu"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3]},"nBHRVpYV5wUL_UAb5ff6Zg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,49540,33826,49772,35602,22316,60128,28682,1480209,1969795,1481300,1480601,2595076,1079144,51020,1480209,1969795,1481300,1480601,2595076,1062336,53402,1844695,1847563,1481567,2595076,1079485,35772,40874,43788,1479608,1493928,2595076,1079485,13932,1479608,1493928,2595076,1079485,63346,48114,1479608,2595076,1079485,1990,41842,34364,63380,1479516,2595076,1079485,8256,63380,1479516,2595076,1073749,4896,39178,32948,3149429,3144768,1903783,1765444,1761295,1048797],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","xNMiNBkMujk7ZnRv0OEjrQ","MYrgKQIxdDhr1gdpucfc-Q","un9fLDZOLvDMO52ltZtueg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","grikUXlisBLUbeL_OWixIw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","rTFMSHhLRlj86vHPR06zoQ","oArGmvsy3VNtTf_V9EHNeQ","7v-k2b21f_Xuf-3329jFyw","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","--q8cwZVXbHL2zOM_p3RlQ","wXOyVgf5_nNg6CUH5kFBbg","zEgDK4qMawUAQZjg5YHyww","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAMJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAIsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAFcs","W8AFtEsepzrJ6AasHrCttwAAAAAAAOrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAHAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAMdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEDXA","kSaNXrGzSS3BnDNNWezzMAAAAAAAANCa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDEL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","xNMiNBkMujk7ZnRv0OEjrQAAAAAAAIu8","MYrgKQIxdDhr1gdpucfc-QAAAAAAAJ-q","un9fLDZOLvDMO52ltZtuegAAAAAAAKsM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","grikUXlisBLUbeL_OWixIwAAAAAAADZs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","oERZXsH8EPeoSRxNNaSWfQAAAAAAAPdy","gMhgHDYSMmyInNJ15VwYFgAAAAAAALvy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","rTFMSHhLRlj86vHPR06zoQAAAAAAAAfG","oArGmvsy3VNtTf_V9EHNeQAAAAAAAKNy","7v-k2b21f_Xuf-3329jFywAAAAAAAIY8","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","GEIvPhvjHWZLHz2BksVgvAAAAAAAACBA","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","--q8cwZVXbHL2zOM_p3RlQAAAAAAABMg","wXOyVgf5_nNg6CUH5kFBbgAAAAAAAJkK","zEgDK4qMawUAQZjg5YHywwAAAAAAAIC0","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMA51","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAL_xA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHQyn","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGvBE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGuAP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEADd"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,1,1,3,3,3,3,3,3]},"vfw5EN0FEHQCAj0w-N2avQ":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,16772,50210,17004,2834,5462,8668,3444,60212,9712,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,24902,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,21798,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,62098,2789627,1482889,1482415,2595076,1073425,9228,2567913,1848405,1837592,1848017,2712905,2221838,2208668,2039344],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","780bLUPADqfQ3x1T5lnVOg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","X0TUmWpd8saA6nnPGQi3nQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAEGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAMQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAEJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAAsS","grZNsSElR5ITq8H2yHCNSwAAAAAAABVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAACHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAA10","xwuAPHgc12-8PZB3i-320gAAAAAAAOs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAGFG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","780bLUPADqfQ3x1T5lnVOgAAAAAAAFUm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","X0TUmWpd8saA6nnPGQi3nQAAAAAAAPKS","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGER","Npep8JfxWDWZ3roJSD7jPgAAAAAAACQM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy7p","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDRV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHAoY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDLR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKWVJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIecO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIbOc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHx4w"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,3,3,3,3,3]},"lyeLQDjWsQDYEJbcY4aFJA":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,51380,55074,37132,20242,15420,47200,6058,1480561,1970211,1481652,1480953,2600004,1079669,52860,1480561,1970211,1481652,1480953,2600004,1062448,62522,1845095,1847963,1481919,2600004,1079483,44204,61562,19788,1479960,1494280,2600004,1079483,22700,1479960,1494280,2600004,1079483,31058,15346,1479960,2600004,1079483,54374,42194,5116,30612,1479868,2600004,1079483,16608,30612,1479868,2600004,1074397,28580,3123760,766784,10485923,16807,2741468,2828042,2817657,2760130,2759130,4216293],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","EFJHOn-GACfHXgae-R1yDA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","kSaNXrGzSS3BnDNNWezzMA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xNMiNBkMujk7ZnRv0OEjrQ","MYrgKQIxdDhr1gdpucfc-Q","un9fLDZOLvDMO52ltZtueg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","tuTnMBfyc9UiPsI0QyvErA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","rTFMSHhLRlj86vHPR06zoQ","oArGmvsy3VNtTf_V9EHNeQ","-T5rZCijT5TDJjmoEi8Kxg","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","--q8cwZVXbHL2zOM_p3RlQ","xLxcEbwnZ5oNrk99ZsxcSQ","Z_CHd3Zjsh2cWE2NSdbiNQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAMi0","U4Le8nh-beog_B7jq7uTIAAAAAAAANci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAJEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAE8S","grZNsSElR5ITq8H2yHCNSwAAAAAAADw8","W8AFtEsepzrJ6AasHrCttwAAAAAAALhg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAABeq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","EFJHOn-GACfHXgae-R1yDAAAAAAAAM58","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEDYw","kSaNXrGzSS3BnDNNWezzMAAAAAAAAPQ6","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHCdn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDKb","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpy_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","xNMiNBkMujk7ZnRv0OEjrQAAAAAAAKys","MYrgKQIxdDhr1gdpucfc-QAAAAAAAPB6","un9fLDZOLvDMO52ltZtuegAAAAAAAE1M","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","tuTnMBfyc9UiPsI0QyvErAAAAAAAAFis","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","oERZXsH8EPeoSRxNNaSWfQAAAAAAAHlS","gMhgHDYSMmyInNJ15VwYFgAAAAAAADvy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","rTFMSHhLRlj86vHPR06zoQAAAAAAANRm","oArGmvsy3VNtTf_V9EHNeQAAAAAAAKTS","-T5rZCijT5TDJjmoEi8KxgAAAAAAABP8","FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","GEIvPhvjHWZLHz2BksVgvAAAAAAAAEDg","FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","--q8cwZVXbHL2zOM_p3RlQAAAAAAAG-k","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAL6ow","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAC7NA","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKdTc","ew01Dk0sWZctP-VaEpavqQAAAAAAKycK","ew01Dk0sWZctP-VaEpavqQAAAAAAKv55","ew01Dk0sWZctP-VaEpavqQAAAAAAKh3C","ew01Dk0sWZctP-VaEpavqQAAAAAAKhna","ew01Dk0sWZctP-VaEpavqQAAAAAAQFXl"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,3,3,4,4,4,4,4,4,4,4]},"cqzgaW0F-6gZ8uHz_Pf3hQ":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,212,38,174,104,68,188,38,174,104,68,60,38,174,104,68,86,38,174,104,68,4,38,174,104,68,0,38,174,104,68,0,714,34,1115045,1179023,833111,2265137,2264574,2261229,1175338],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","bAXCoU3-CU0WlRxl5l1tmw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","IcegEVkl4JzbMBhUeMqp0Q","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","tz0ps4QDYR1clO_q5ziJUQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","O2RGJIowquMzuET0HYQ6aQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_____________________w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_____________________w","Ht79I_xqXv3bOgaClTNQ4w","T8-enlAkCZXqinPHW4B8sw","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAADU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","bAXCoU3-CU0WlRxl5l1tmwAAAAAAAAC8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","IcegEVkl4JzbMBhUeMqp0QAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","tz0ps4QDYR1clO_q5ziJUQAAAAAAAABW","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","O2RGJIowquMzuET0HYQ6aQAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_____________________wAAAAAAAAAA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_____________________wAAAAAAAAAA","Ht79I_xqXv3bOgaClTNQ4wAAAAAAAALK","T8-enlAkCZXqinPHW4B8swAAAAAAAAAi","G68hjsyagwq6LpWrMjDdngAAAAAAEQOl","G68hjsyagwq6LpWrMjDdngAAAAAAEf2P","G68hjsyagwq6LpWrMjDdngAAAAAADLZX","G68hjsyagwq6LpWrMjDdngAAAAAAIpAx","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAIoDt","G68hjsyagwq6LpWrMjDdngAAAAAAEe8q"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3]},"b89Eo7vMfG4HsPSBVvjiKQ":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,34996,38690,20748,3858,31334,49372,51700,46628,9712,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,12612,2578675,2599636,1091600,32150,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,12612,2578675,2599636,1091600,7938,2795051,1483241,1482767,2600004,1079483,28112,42150,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,12612,2578675,2599636,1079669,40672,1482046,1829360,2586325,1480953,1480561,1940968,1986911,1983192],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","3HhVgGD2yvuFLpoZq7RfKw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fDiQPd_MeGeyY9ZBOSU1Gg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAIi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAJci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAFEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAA8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAHpm","LF6DFcGHEMqhhhlptO_M_QAAAAAAAMDc","Af6E3BeG383JVVbu67NJ0QAAAAAAAMn0","xwuAPHgc12-8PZB3i-320gAAAAAAALYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAH2W","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","3HhVgGD2yvuFLpoZq7RfKwAAAAAAAB8C","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAG3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAKSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","fDiQPd_MeGeyY9ZBOSU1GgAAAAAAAJ7g","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3bV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHlFf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHkLY"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3]},"5_-zAnLDYAi4FySmVgS6iw":{"address_or_lines":[2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,61666,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,9122,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,8610,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,11838,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1079144,61238,1481694,1828960,2581297,2595076,1072525,49410,1646337,3072295,1865241,10489950,422647],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","mP9Tk3T74fjOyYWKUaqdMQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","I4X8AC1-B0GuL4JyYemPzw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","b-3iFnlA7BmzAxDEzxShdA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","8jcOoolAg5RmmHop7NqzWQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2LABj1asXFICsosP2OrbVQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N1ZmsCOKFJHNThnHfFYo6Q","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","mP9Tk3T74fjOyYWKUaqdMQAAAAAAAPDi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","I4X8AC1-B0GuL4JyYemPzwAAAAAAACOi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","b-3iFnlA7BmzAxDEzxShdAAAAAAAACGi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","8jcOoolAg5RmmHop7NqzWQAAAAAAAC4-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","2LABj1asXFICsosP2OrbVQAAAAAAAO82","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2Mx","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEF2N","N1ZmsCOKFJHNThnHfFYo6QAAAAAAAMEC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGR8B","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuEn","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHYZ","A2oiHVwisByxRn5RDT4LjAAAAAAAoBBe","A2oiHVwisByxRn5RDT4LjAAAAAAABnL3"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,4,4]},"zOI_cRK31hVrh4Typ0-Fxg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,16720,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,60990,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,44846,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,40354,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1079144,48884,1481694,1828960,2581397,1480601,1480209,1940568,1986405,1948474,1768216,1756070,1865241,10490014,423063,2283967,2281647,2098628,2098378,8541549],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MU3fJpOZe9TA4mzeo52wZg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N0GNsPaCLYzoFsPJWnIJtQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fq0ezjB8ddCA6Pk0BY9arQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-gDCCFjiBc58_iqAxti3Kw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","DTRaillMS4wmG2CDEfm9rQAAAAAAAEFQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MU3fJpOZe9TA4mzeo52wZgAAAAAAAO4-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N0GNsPaCLYzoFsPJWnIJtQAAAAAAAK8u","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","fq0ezjB8ddCA6Pk0BY9arQAAAAAAAJ2i","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","-gDCCFjiBc58_iqAxti3KwAAAAAAAL70","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHk9l","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHbs6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGvsY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGsum","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHYZ","A2oiHVwisByxRn5RDT4LjAAAAAAAoBCe","A2oiHVwisByxRn5RDT4LjAAAAAAABnSX","A2oiHVwisByxRn5RDT4LjAAAAAAAItm_","A2oiHVwisByxRn5RDT4LjAAAAAAAItCv","A2oiHVwisByxRn5RDT4LjAAAAAAAIAXE","A2oiHVwisByxRn5RDT4LjAAAAAAAIATK","A2oiHVwisByxRn5RDT4LjAAAAAAAglVt"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4]},"4U9ayDnwvWmqJPhn_AOKew":{"address_or_lines":[38782,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,50350,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,10266,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,31478,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,4998,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1079144,0,1481694,1828960,2581397,1480601,1480209,1940568,1986447,1982493,1959065,1765336,1761295,1048381],"file_ids":["GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","d4jl580PLMUwu5s3I4wcXg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","tKago5vqLnwIkezk_wTBpQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","rpq4cV1KPyFZcnKfWjKdZw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uFElJcsK9my-kA6ZYzT1uw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["GP7h96O0_ppGVtc-UpQQIQAAAAAAAJd-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","d4jl580PLMUwu5s3I4wcXgAAAAAAAMSu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","tKago5vqLnwIkezk_wTBpQAAAAAAACga","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","rpq4cV1KPyFZcnKfWjKdZwAAAAAAAHr2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","uFElJcsK9my-kA6ZYzT1uwAAAAAAABOG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHk-P","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHkAd","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHeSZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGu_Y","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGuAP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAD_89"],"type_ids":[1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3]},"Jt6CexOHLEwUl4IeTgASBQ":{"address_or_lines":[2795051,1483241,1482767,2600004,1079483,64976,13478,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,16708,2578675,2599636,1091600,57670,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,16708,2578675,2599636,1091600,51706,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,16708,2578675,2599636,1091600,59680,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,16708,2578675,2599636,1079669,0,1482046,1829360,2586325,1481195,1480561,1940968,1917658,1481652,1480953,2600004,1079483,41394,1480124,1827986,1940595,1986911,1983184],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","yp8MidCGMe4czbl-NigsYQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","2noK4QoWxdzASRHkjOFwVA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","yO-OCNRiISNdCb_iVi4E_w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","_____________________w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","mBpjyQvq6ftE7Wm1BUpcFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAP3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAADSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAEFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","yp8MidCGMe4czbl-NigsYQAAAAAAAOFG","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAEFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","2noK4QoWxdzASRHkjOFwVAAAAAAAAMn6","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAEFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","yO-OCNRiISNdCb_iVi4E_wAAAAAAAOkg","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAEFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","_____________________wAAAAAAAAAA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3bV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpnr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHULa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","mBpjyQvq6ftE7Wm1BUpcFgAAAAAAAKGy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpW8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-SS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZxz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHlFf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHkLQ"],"type_ids":[3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3]},"8Rif7kuKG2cfhEYF2fJXmA":{"address_or_lines":[2790352,1482889,1482415,2595076,1073749,45806,47864,20848,34524,2573747,2594708,1091475,18066,2790352,1482889,1482415,2595076,1073749,45806,47864,20848,34524,2573747,2594708,1091475,53890,2789627,1482889,1482415,2595076,1073425,41996,2567913,1848405,1837592,1847724,1483518,1482415,2595076,1079144,6526,35438,63996,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,45806,47864,20848,34524,2573747,2594708,1091475,48638,2790352,1482889,1482415,2595076,1079485,45806,47864,20848,32520,56166,1479516,1828960,2573747,2594708,1091475,0,2789548,1848405,1837592,1848026,1002720],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2L4SW1rQgEVXRj3pZAI3nQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","OlTvyWQFXjOweJcs3kiGyg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","bcwppGWOjTWw86zVNJE_Jg","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","NiCfOMPggzUjx-usqlmxvg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","Vot4T3F5OpUj8rbXhgpMDg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAALLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAALr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAFFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2L4SW1rQgEVXRj3pZAI3nQAAAAAAAEaS","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAALLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAALr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAFFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","OlTvyWQFXjOweJcs3kiGygAAAAAAANKC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGER","Npep8JfxWDWZ3roJSD7jPgAAAAAAAKQM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy7p","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDRV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHAoY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDGs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqL-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","bcwppGWOjTWw86zVNJE_JgAAAAAAABl-","TBeSzkyqIwKL8td602zDjAAAAAAAAIpu","NH3zvSjFAfTSy6bEocpNyQAAAAAAAPn8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAALLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAALr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAFFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","NiCfOMPggzUjx-usqlmxvgAAAAAAAL3-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAALLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAALr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAFFw","Vot4T3F5OpUj8rbXhgpMDgAAAAAAAH8I","eV_m28NnKeeTL60KO2H3SAAAAAAAANtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpCs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDRV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHAoY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDLa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAD0zg"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,3,3,3,3,3,1,3,3,3,3,3]},"cCjn5miDmyezrnBAe2jDww":{"address_or_lines":[1483241,1482767,2600004,1074397,35918,37976,10928,61764,2578675,2599636,1091600,46938,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,61764,2578675,2599636,1091600,15022,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,61764,2578675,2599636,1091600,57678,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,61764,2578675,2599636,1091600,1870,2795051,1483241,1482767,2600004,1079483,32208,46246,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,61764,2578675,2599636,1079669,19486,1482046,1829360,2586325,1480953,1480561,1940968,1986928],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","5nuRo5ZVtij8bTLlri7QXA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","hi5mlwAHRj-Yl1GNV_UEZQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","uSWUCgHgLPG4OFtPdUp0rg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","-BjW54fwMksXBor9R-YN9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","wuSmWRANn3Cl-syjEtxMoQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","5nuRo5ZVtij8bTLlri7QXAAAAAAAALda","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","hi5mlwAHRj-Yl1GNV_UEZQAAAAAAADqu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","uSWUCgHgLPG4OFtPdUp0rgAAAAAAAOFO","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","-BjW54fwMksXBor9R-YN9wAAAAAAAAdO","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAALSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","wuSmWRANn3Cl-syjEtxMoQAAAAAAAEwe","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3bV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHlFw"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3]},"f8AFYpSQOpjCNbhqUuR3Rg":{"address_or_lines":[2578675,2599636,1091600,13686,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,49476,2578675,2599636,1091600,50302,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,49476,2578675,2599636,1091600,31414,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,49476,2578675,2599636,1091600,43062,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,49476,2578675,2599636,1091600,38710,2795776,1483241,1482767,2600004,1079483,31822,33880,6648,14264,54464,42150,1479868,1829983,2783616,2800188,3063028,4240,5748,1213299,4101,76200,1213299,77886,46784,40082,37650],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","pv4wAezdMMO0SVuGgaEMTg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","qns5vQ3LMi6QrIMOgD_TwQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","J_Lkq1OzUHxWQhnTgF6FwA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","XkOSW26Xa6_lkqHv5givKg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","rEbhXoMLMee0rf6bwU9RPw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","0S3htaCNkzxOYeavDR1GTQ","rBzW547V0L_mH4nnWK1FUQ","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","PVZV2uq5ZRt-FFaczL10BA","PVZV2uq5ZRt-FFaczL10BA","Z_CHd3Zjsh2cWE2NSdbiNQ","PVZV2uq5ZRt-FFaczL10BA","3nN3bymnZ8E42aLEtgglmA","Z_CHd3Zjsh2cWE2NSdbiNQ","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","pv4wAezdMMO0SVuGgaEMTgAAAAAAADV2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","qns5vQ3LMi6QrIMOgD_TwQAAAAAAAMR-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","J_Lkq1OzUHxWQhnTgF6FwAAAAAAAAHq2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","XkOSW26Xa6_lkqHv5givKgAAAAAAAKg2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","rEbhXoMLMee0rf6bwU9RPwAAAAAAAJc2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABn4","0S3htaCNkzxOYeavDR1GTQAAAAAAADe4","rBzW547V0L_mH4nnWK1FUQAAAAAAANTA","eV_m28NnKeeTL60KO2H3SAAAAAAAAKSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-xf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKnmA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKro8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALrz0","PVZV2uq5ZRt-FFaczL10BAAAAAAAABCQ","PVZV2uq5ZRt-FFaczL10BAAAAAAAABZ0","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","PVZV2uq5ZRt-FFaczL10BAAAAAAAABAF","3nN3bymnZ8E42aLEtgglmAAAAAAAASmo","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","3nN3bymnZ8E42aLEtgglmAAAAAAAATA-","3nN3bymnZ8E42aLEtgglmAAAAAAAALbA","3nN3bymnZ8E42aLEtgglmAAAAAAAAJyS","3nN3bymnZ8E42aLEtgglmAAAAAAAAJMS"],"type_ids":[3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"dGMvgpGXk-ajX6PRi92qdg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,33156,17442,33388,19218,62806,476,52596,11060,9712,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,16746,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,23102,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,0,2789627,1482889,1482415,2595076,1079485,13424,27494,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1592,33110,55262,3227220,1488310,1480209,1940568,3236384],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","z1-LQiSwGmfJHZm7Q223fQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAPVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAAHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAM10","xwuAPHgc12-8PZB3i-320gAAAAAAACs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAEFq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","z1-LQiSwGmfJHZm7Q223fQAAAAAAAFo-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAADRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAGtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAANfe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMT5U","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFrW2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMWIg"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3]},"OxrG9ZVAzX9GwGtxUtIQNg":{"address_or_lines":[51762,2795051,1483241,1482767,2600004,1079483,36304,50342,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,40014,42072,15024,49476,2578675,2599636,1091600,64822,2795051,1483241,1482767,2600004,1079483,36304,50342,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,40014,42072,15024,49476,2578675,2599636,1091600,45750,2795776,1483241,1482767,2600004,1074397,40014,42072,15024,49476,2578675,2599636,1091600,58410,2795776,1483241,1482767,2600004,1073803,40014,42072,15024,49260,33110,13026,2852079,2851771,2849353,2846190,2849353,2846190,2849408,2846190,2848321,2268450,1775400,1761695,1048471],"file_ids":["xDXQtI2vA5YySwpx7QFiwA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fSQ747oLNh0c0zFQjsVRWg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","yp8MidCGMe4czbl-NigsYQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","2noK4QoWxdzASRHkjOFwVA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xDXQtI2vA5YySwpx7QFiwAAAAAAAAMoy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAI3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAMSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","fSQ747oLNh0c0zFQjsVRWgAAAAAAAP02","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAI3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAMSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","yp8MidCGMe4czbl-NigsYQAAAAAAALK2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","2noK4QoWxdzASRHkjOFwVAAAAAAAAOQq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAADLi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3qA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3ZB","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAIp0i","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAGxco","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAGuGf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAD_-X"],"type_ids":[1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3]},"QoW8uF5K3OBNL2DXI66leA":{"address_or_lines":[1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,44118,2789627,1482889,1482415,2595076,1079485,54384,2918,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,32266,2789627,1482889,1482415,2595076,1079485,54384,2918,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,0,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1079144,0,1481694,1828960,2581397,1480601,1480209,1940568,1986447,1982493,1959065,1765320],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Z-J8GEZK5aE8XNQ-3sO-Fg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","H-OlnUNurKAlPjkWfV0hTg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","Z-J8GEZK5aE8XNQ-3sO-FgAAAAAAAKxW","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAANRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAAtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","H-OlnUNurKAlPjkWfV0hTgAAAAAAAH4K","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAANRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAAtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHk-P","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHkAd","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHeSZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGu_I"],"type_ids":[3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3]},"zV-93oQDbZK9zB7UMAcCmw":{"address_or_lines":[1482889,1482415,2595076,1073749,41710,43768,16752,18140,2573747,2594708,1091475,38166,2790352,1482889,1482415,2595076,1073749,41710,43768,16752,18140,2573747,2594708,1091475,63374,2790352,1482889,1482415,2595076,1073749,41710,43768,16752,18140,2573747,2594708,1091475,12690,2790352,1482889,1482415,2595076,1073749,41710,43768,16752,18140,2573747,2594708,1062336,11500,1844695,1837592,1847724,1483518,1482415,2595076,1079144,40398,15390,8700,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1079485,41710,43252,52070,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1072909,41710,43768,16752,18098,34934,1898256],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","pv4wAezdMMO0SVuGgaEMTg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","qns5vQ3LMi6QrIMOgD_TwQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","J_Lkq1OzUHxWQhnTgF6FwA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","hrIwGgdEFsOBluJKOOs8Zg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","jhRfowFriqBKJWhZSTe7kg","B0e_Spx899MeGx2KSvzzow","v1UMuiFodNtdRCNi4iF0Rg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","yzJdtc2TQHpJ_IY5QdUQKA","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAKLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAEFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","pv4wAezdMMO0SVuGgaEMTgAAAAAAAJUW","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAKLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAEFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","qns5vQ3LMi6QrIMOgD_TwQAAAAAAAPeO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAKLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAEFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","J_Lkq1OzUHxWQhnTgF6FwAAAAAAAADGS","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAKLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAEFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEDXA","hrIwGgdEFsOBluJKOOs8ZgAAAAAAACzs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHAoY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDGs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqL-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","jhRfowFriqBKJWhZSTe7kgAAAAAAAJ3O","B0e_Spx899MeGx2KSvzzowAAAAAAADwe","v1UMuiFodNtdRCNi4iF0RgAAAAAAACH8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAKLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKj0","eV_m28NnKeeTL60KO2H3SAAAAAAAAMtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEF8N","ik6PIX946fW_erE7uBJlVQAAAAAAAKLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAEFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEay","yzJdtc2TQHpJ_IY5QdUQKAAAAAAAAIh2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHPcQ"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,3]},"9CQVJEfCfL1rSnUaxlAfqg":{"address_or_lines":[1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,27398,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,2830,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,16862,2789627,1482889,1482415,2595076,1079485,9328,23398,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1079144,7050,1481694,1828960,2581297,2595076,1079144,21502,39750,29852,29250,6740,37336,26240,24712,1480209,1940568,1934986,1933934,3072096,3066615,1918105,1787434,3064390],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","VuJFonCXevADcEDW6NVbKg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","VFBd9VqCaQu0ZzjQ2K3pjg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","PUSucJs4FC_WdMzOyH3QYw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","q_M8ZB6aihtZKYZfHGkluQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MAFaasFcVIeoQsejXrnp0w","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","zpgqltXEgKujOhJUj-jAhg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","VuJFonCXevADcEDW6NVbKgAAAAAAAGsG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","VFBd9VqCaQu0ZzjQ2K3pjgAAAAAAAAsO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","PUSucJs4FC_WdMzOyH3QYwAAAAAAAEHe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAACRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAFtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","q_M8ZB6aihtZKYZfHGkluQAAAAAAABuK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2Mx","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","MAFaasFcVIeoQsejXrnp0wAAAAAAAFP-","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAHSc","_lF8o5tJDcePvza_IYtgSQAAAAAAAHJC","TRd7r6mvdzYdjMdTtebtwwAAAAAAABpU","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAJHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAGaA","zpgqltXEgKujOhJUj-jAhgAAAAAAAGCI","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHYaK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHYJu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuBg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALsr3","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUSZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG0Yq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALsJG"],"type_ids":[3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3]},"mGGvLNOYB74ofk9FRrMxxQ":{"address_or_lines":[2795776,1483241,1482767,2600004,1074397,35918,37976,10928,49476,2578675,2599636,1091600,17196,2795051,1483241,1482767,2600004,1079483,32208,46246,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,49476,2578675,2599636,1091600,38014,2795051,1483241,1482767,2600004,1079483,32208,46246,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,49476,2578675,2599636,1091600,62622,2795051,1483241,1482767,2600004,1079483,32208,46246,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1073803,35918,37976,10928,49260,33110,13026,2852079,2851771,2849353,2846190,2849443,2846638,1439925,1865540],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","ihsoi5zicXHpPrWRA9bTnA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","HbU9j_4D3UaJfjASj-JljA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","awUBhCYYZvWyN4rrVw-u5A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","ihsoi5zicXHpPrWRA9bTnAAAAAAAAEMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAALSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","HbU9j_4D3UaJfjASj-JljAAAAAAAAJR-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAALSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","awUBhCYYZvWyN4rrVw-u5AAAAAAAAPSe","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAALSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAMBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAADLi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3qj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK2-u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFfi1","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHHdE"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3]},"pnLCuJVNeqGwwFeJQIrkPw":{"address_or_lines":[2795776,1483241,1482767,2600004,1079483,52302,53844,62630,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1079483,52302,53844,62630,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,24900,2578675,2599636,1091600,63066,2795051,1483241,1482767,2600004,1079483,48592,62630,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,24900,2578675,2599636,1091600,62622,2795051,1483241,1482767,2600004,1079483,48592,62630,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,24900,2578675,2599636,1079669,27496,1482046,1829360,2586325,1480953,1480561,1940968,1986911,1982943],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","akZOzI9XwsEixvkTDGeDPw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","d1LNRHMzWQ5PvB10hYiN3g","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","PmkUsVBZlaSEgaFwCOKZlg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAPSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAPSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAGFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","akZOzI9XwsEixvkTDGeDPwAAAAAAAPZa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAL3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAPSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAGFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","d1LNRHMzWQ5PvB10hYiN3gAAAAAAAPSe","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAL3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAPSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAGFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","PmkUsVBZlaSEgaFwCOKZlgAAAAAAAGto","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3bV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHlFf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHkHf"],"type_ids":[3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3]},"R77Zz6fBvENVXyt4GVb9dQ":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,138,138,16,100,12,4,6,4,38,174,104,68,8,38,174,104,68,32,38,174,104,68,94,6,108,36,24,4,28,693765,935741],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","JaHOMfnX0DG4ZnNTpPORVA","MepUYc0jU0AjPrrjuvTgGg","yWt46REABLfKH6PXLAE18A","VQs3Erq77xz92EfpT8sTKw","n7IiY_TlCWEfi47-QpeCLw","Ua3frjTXWBuWpTsQD8aKeA","GtyMRLq4aaDvuQ4C3N95mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","clFhkTaiph2aOjCNuZDWKA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","DLEY7W0VXWLE5Ol-plW-_w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","RY-vzTa9LfseI7kmcIcbgQ","VIK6i3XoO6nxn9WkNabugA","SGPpASrxkViIc4Sq7x-WYQ","9xG1GRY3A4PQMfXDNvrOxQ","4xH83ZXxs_KV95Ur8Z59WQ","PWlQ4X4jsNu5q7FFJqlo_Q","LSxiso_u1cO_pWDBw25Egg","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK","JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK","MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ","yWt46REABLfKH6PXLAE18AAAAAAAAABk","VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM","n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE","Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG","GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","RY-vzTa9LfseI7kmcIcbgQAAAAAAAABe","VIK6i3XoO6nxn9WkNabugAAAAAAAAAAG","SGPpASrxkViIc4Sq7x-WYQAAAAAAAABs","9xG1GRY3A4PQMfXDNvrOxQAAAAAAAAAk","4xH83ZXxs_KV95Ur8Z59WQAAAAAAAAAY","PWlQ4X4jsNu5q7FFJqlo_QAAAAAAAAAE","LSxiso_u1cO_pWDBw25EggAAAAAAAAAc","G68hjsyagwq6LpWrMjDdngAAAAAACpYF","G68hjsyagwq6LpWrMjDdngAAAAAADkc9"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3]},"tgL-t2GJJjItpLjnwjc4zQ":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,34996,38690,20748,3858,40902,49932,35316,46628,9712,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,20804,2578675,2599636,1091600,40322,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,20804,2578675,2599636,1091600,6862,2795051,1483241,1482767,2600004,1079483,15824,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,20804,2578675,2599636,1091600,45714,2795051,1483241,1482767,2600004,1079483,15824,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,20588,33110,49802,19187,41240,51007],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","780bLUPADqfQ3x1T5lnVOg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","f3fxdcTCg7rbloZ6VtA0_Q","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAIi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAJci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAFEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAA8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAJ_G","LF6DFcGHEMqhhhlptO_M_QAAAAAAAMMM","Af6E3BeG383JVVbu67NJ0QAAAAAAAIn0","xwuAPHgc12-8PZB3i-320gAAAAAAALYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAJ2C","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","780bLUPADqfQ3x1T5lnVOgAAAAAAABrO","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","f3fxdcTCg7rbloZ6VtA0_QAAAAAAALKS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAFBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAMKK","ASi9f26ltguiwFajNwOaZwAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMc_"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"XNCSlgkv_bOXDIYn6zwekw":{"address_or_lines":[2578675,2599636,1091600,10822,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,45380,2578675,2599636,1091600,40982,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,45380,2578675,2599636,1091600,6678,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,45380,2578675,2599636,1074067,39072,35338,13252,2577481,2934013,1108250,1105981,1310350,1245864,1200348,1190613,1198830,1177316,1176308,1173405,1172711,1172023,1171335,1170723,1169827,1169015,1167328,1166449,1165561,1146206,1245475,1198830,1177316,1176308,1173405,1172711,1172023,1171335,1170723,1169827,1169015,1167328,1166449,1165783,1162744,1226823,1225457,1224431,1198830,1177316,1176308,1173405,1172510,1172373,1102592],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","uU7rISh8R_xr6YYB3RgLuA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","vQQdLrWHLywJs9twt3EH2Q","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","PUIH740KQXWx70DXM4ZvgQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","dsOcslker2-lnNTIC5yERA","zUlsQG278t98_u2KV_JLSQ","vkeP2ntYyoFN0A16x9eliw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","uU7rISh8R_xr6YYB3RgLuAAAAAAAACpG","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAALFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","vQQdLrWHLywJs9twt3EH2QAAAAAAAKAW","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAALFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","PUIH740KQXWx70DXM4ZvgQAAAAAAABoW","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAALFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGOT","dsOcslker2-lnNTIC5yERAAAAAAAAJig","zUlsQG278t98_u2KV_JLSQAAAAAAAIoK","vkeP2ntYyoFN0A16x9eliwAAAAAAADPE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1RJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALMT9","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEOka","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEOA9","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAE_6O","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEwKo","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAElDc","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEirV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEkru","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfbk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfL0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeed","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeTn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeI3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd-H","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd0j","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdmj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdZ3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEc_g","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcxx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEX1e","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEwEj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEkru","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfbk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfL0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeed","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeTn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeI3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd-H","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd0j","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdmj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdZ3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEc_g","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcxx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcnX","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEb34","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAErhH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAErLx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEq7v","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEkru","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfbk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfL0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeed","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeQe","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeOV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAENMA"],"type_ids":[3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"jPN_jNGPJguImYjakYlBcA":{"address_or_lines":[19534,21592,60080,53572,2578675,2599636,1091600,12394,2795051,1483241,1482767,2600004,1079483,15824,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,53572,2578675,2599636,1091600,39546,2795776,1483241,1482767,2600004,1079669,19534,21418,26368,41208,8202,42532,1482046,1829983,2572841,1848805,1978934,1481919,1494280,2600004,1079669,55198,34238,39164,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,53572,2578675,2599636,1091600,33554,2795776,1483241,1482767,2600004,1073803,19534,21592,60080,53356,33110,17122,2852079,2851771,2849353,2846190,2849353,2846190,2849353,2846190,2845695,2033924,2033070,1865524],"file_ids":["LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","2L4SW1rQgEVXRj3pZAI3nQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","7bd6QJSfWZZfOOpDMHqLMA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","ZPxtkRXufuVf4tqV5k5k2Q","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fj70ljef7nDHOqVJGSIoEQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","2L4SW1rQgEVXRj3pZAI3nQAAAAAAADBq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","7bd6QJSfWZZfOOpDMHqLMAAAAAAAAJp6","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFOq","ZPxtkRXufuVf4tqV5k5k2QAAAAAAAGcA","8R2Lkqe-tYqq-plJ22QNzAAAAAAAAKD4","h0l-9tGi18mC40qpcJbyDwAAAAAAACAK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAAKYk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-xf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0Ip","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDXl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHjI2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpy_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","705jmHYNd7I4Z4L4c0vfiAAAAAAAANee","TBeSzkyqIwKL8td602zDjAAAAAAAAIW-","NH3zvSjFAfTSy6bEocpNyQAAAAAAAJj8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","fj70ljef7nDHOqVJGSIoEQAAAAAAAIMS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAELi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK2v_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHwkE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHwWu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHHc0"],"type_ids":[1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3]},"4K-SlZ4j8NjsVBpqyPj2dw":{"address_or_lines":[1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,6714,2790352,1482889,1482415,2595076,1079144,29422,31306,36256,31544,18122,5412,1481694,1829583,2567913,1848405,1978470,1481567,1493928,2595076,1079144,54286,19054,47612,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,60034,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,64446,2790352,1482889,1482415,2595076,1079485,29422,31480,4280,11896,52064,39782,1479516,1829583,2778192,2794764,3057572,4240,5748,1213299,4101,76200,1213299,77886,46784,40082,37750],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","7bd6QJSfWZZfOOpDMHqLMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","lOUbi56SanKTCh9Y7fIwDw","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","J3wpF3Lf_vPkis4aNGKFbw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zo4mnjDJ1PlZka7jS9k2BA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","0S3htaCNkzxOYeavDR1GTQ","rBzW547V0L_mH4nnWK1FUQ","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","PVZV2uq5ZRt-FFaczL10BA","PVZV2uq5ZRt-FFaczL10BA","Z_CHd3Zjsh2cWE2NSdbiNQ","PVZV2uq5ZRt-FFaczL10BA","3nN3bymnZ8E42aLEtgglmA","Z_CHd3Zjsh2cWE2NSdbiNQ","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","7bd6QJSfWZZfOOpDMHqLMAAAAAAAABo6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHpK","lOUbi56SanKTCh9Y7fIwDwAAAAAAAI2g","8R2Lkqe-tYqq-plJ22QNzAAAAAAAAHs4","h0l-9tGi18mC40qpcJbyDwAAAAAAAEbK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAABUk","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-rP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy7p","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDRV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHjBm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","705jmHYNd7I4Z4L4c0vfiAAAAAAAANQO","TBeSzkyqIwKL8td602zDjAAAAAAAAEpu","NH3zvSjFAfTSy6bEocpNyQAAAAAAALn8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","J3wpF3Lf_vPkis4aNGKFbwAAAAAAAOqC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zo4mnjDJ1PlZka7jS9k2BAAAAAAAAPu-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABC4","0S3htaCNkzxOYeavDR1GTQAAAAAAAC54","rBzW547V0L_mH4nnWK1FUQAAAAAAAMtg","eV_m28NnKeeTL60KO2H3SAAAAAAAAJtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-rP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKmRQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKqUM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALqek","PVZV2uq5ZRt-FFaczL10BAAAAAAAABCQ","PVZV2uq5ZRt-FFaczL10BAAAAAAAABZ0","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","PVZV2uq5ZRt-FFaczL10BAAAAAAAABAF","3nN3bymnZ8E42aLEtgglmAAAAAAAASmo","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","3nN3bymnZ8E42aLEtgglmAAAAAAAATA-","3nN3bymnZ8E42aLEtgglmAAAAAAAALbA","3nN3bymnZ8E42aLEtgglmAAAAAAAAJyS","3nN3bymnZ8E42aLEtgglmAAAAAAAAJN2"],"type_ids":[3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"W8IRlEZMfFJdYSgUQXDnMg":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,72,38,174,104,68,88,38,174,104,68,124,38,38,10,38,174,104,68,72,38,174,104,68,120,38,174,104,68,276,6,108,20,50,50,2970,50,2970,50,1360,24,788130,1197115,1222867,1212996,1212720],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","qkYSh95E1urNTie_gKbr7w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","V8ldXm9NGXsJ182jEHEsUw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","xVaa0cBWNcFeS-8zFezQgA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","UBINlIxj95Sa_x2_k5IddA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gRRk0W_9P4SGZLXFJ5KU8Q","VIK6i3XoO6nxn9WkNabugA","SGPpASrxkViIc4Sq7x-WYQ","9xG1GRY3A4PQMfXDNvrOxQ","cbxfeE2AkqKne6oKUxdB6g","aEZUIXI_cV9kZCa4-U1NsQ","MebnOxK5WOhP29sl19Jefw","aEZUIXI_cV9kZCa4-U1NsQ","MebnOxK5WOhP29sl19Jefw","aEZUIXI_cV9kZCa4-U1NsQ","MebnOxK5WOhP29sl19Jefw","iLW1ehST1pGQ3S8RoqM9Qg","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAABI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","qkYSh95E1urNTie_gKbr7wAAAAAAAABY","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","V8ldXm9NGXsJ182jEHEsUwAAAAAAAAB8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","xVaa0cBWNcFeS-8zFezQgAAAAAAAAABI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","UBINlIxj95Sa_x2_k5IddAAAAAAAAAB4","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gRRk0W_9P4SGZLXFJ5KU8QAAAAAAAAEU","VIK6i3XoO6nxn9WkNabugAAAAAAAAAAG","SGPpASrxkViIc4Sq7x-WYQAAAAAAAABs","9xG1GRY3A4PQMfXDNvrOxQAAAAAAAAAU","cbxfeE2AkqKne6oKUxdB6gAAAAAAAAAy","aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy","MebnOxK5WOhP29sl19JefwAAAAAAAAua","aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy","MebnOxK5WOhP29sl19JefwAAAAAAAAua","aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy","MebnOxK5WOhP29sl19JefwAAAAAAAAVQ","iLW1ehST1pGQ3S8RoqM9QgAAAAAAAAAY","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkQ7","G68hjsyagwq6LpWrMjDdngAAAAAAEqjT","G68hjsyagwq6LpWrMjDdngAAAAAAEoJE","G68hjsyagwq6LpWrMjDdngAAAAAAEoEw"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3]},"qytuJG9brvKSB9NJCHV9fQ":{"address_or_lines":[1483241,1482767,2600004,1079483,19920,33958,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,53572,2578675,2599636,1091600,45506,2795051,1483241,1482767,2600004,1079483,19920,33958,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,53572,2578675,2599636,1091600,10626,2795051,1483241,1482767,2600004,1079483,19920,33958,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,53572,2578675,2599636,1091600,54118,2795776,1483241,1482767,2600004,1073803,23630,25688,64176,53356,16726,17122,2852079,2851771,2849353,2846190,2849353,2846190,2849762,2846638,1439925,1865641,10490014,423063,2284223,2281903,2098884,2098647,2097658],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","9NWoah56eYULAP_zGE9Puw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","IKrIDHd5n47PpDQsRXxvvg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","oG7568kMJujZxPJfj7VMjA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAE3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAISm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","9NWoah56eYULAP_zGE9PuwAAAAAAALHC","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAE3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAISm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","IKrIDHd5n47PpDQsRXxvvgAAAAAAACmC","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAE3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAISm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","oG7568kMJujZxPJfj7VMjAAAAAAAANNm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAANBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAELi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3vi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK2-u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFfi1","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHHep","ew01Dk0sWZctP-VaEpavqQAAAAAAoBCe","ew01Dk0sWZctP-VaEpavqQAAAAAABnSX","ew01Dk0sWZctP-VaEpavqQAAAAAAItq_","ew01Dk0sWZctP-VaEpavqQAAAAAAItGv","ew01Dk0sWZctP-VaEpavqQAAAAAAIAbE","ew01Dk0sWZctP-VaEpavqQAAAAAAIAXX","ew01Dk0sWZctP-VaEpavqQAAAAAAIAH6"],"type_ids":[3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4]},"b116myovN7_XXb1AVLPH0g":{"address_or_lines":[1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,21010,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,32886,2790352,1482889,1482415,2595076,1079485,25326,26868,35686,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,8770,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,52386,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1097633,38284,39750,58524,57922,35412,472,59182,472,59182,472,59182,472,59182,472,55416,2915906,959782,10485923,16807,2315878,2315735,2315122,2305825,2551628],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","OlTvyWQFXjOweJcs3kiGyg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N2mxDWkAZe8CHgZMQpxZ7A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","1eW8DnM19kiBGqMWGVkHPA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2kgk5qEgdkkSXT9cIdjqxQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MsEmysGbXhMvgdbwhcZDCg","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","-Z7SlEXhuy5tL2BF-xmy3g","Z_CHd3Zjsh2cWE2NSdbiNQ","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","OlTvyWQFXjOweJcs3kiGygAAAAAAAFIS","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAIB2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGj0","eV_m28NnKeeTL60KO2H3SAAAAAAAAItm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","1eW8DnM19kiBGqMWGVkHPAAAAAAAACJC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2kgk5qEgdkkSXT9cIdjqxQAAAAAAAMyi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEL-h","MsEmysGbXhMvgdbwhcZDCgAAAAAAAJWM","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAOSc","_lF8o5tJDcePvza_IYtgSQAAAAAAAOJC","TRd7r6mvdzYdjMdTtebtwwAAAAAAAIpU","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAAHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAOcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAAHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAOcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAAHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAOcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAAHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAOcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAAHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAANh4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALH5C","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADqUm","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAI1Zm","A2oiHVwisByxRn5RDT4LjAAAAAAAI1XX","A2oiHVwisByxRn5RDT4LjAAAAAAAI1Ny","A2oiHVwisByxRn5RDT4LjAAAAAAAIy8h","A2oiHVwisByxRn5RDT4LjAAAAAAAJu9M"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,4,4,4,4,4,4,4]},"dNwgDmnCM1dIIF5EZm4ZgA":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,132,38,174,104,68,16,38,38,10,38,174,104,68,4,38,174,104,68,8,38,38,10,38,38,10,38,174,104,68,16,140,10,38,174,104,68,20,140,10,38,174,104,68,92,1090933,1814182,788459,788130,1197048,1243240,1238413,1212345,1033898,428752],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","iwnHqwtnoHjA-XW01rxhpw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","53nvYhJfd2eJh-qREaeFBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zwRZ32H5_95LpRJHzXkqVA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","JJab8JrsPDK66yfOtCG3zQ","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1XUiDryPjyncBxkTlbVecg","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","OIy8IFqaTWz5UoN3FSH-wQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","iwnHqwtnoHjA-XW01rxhpwAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","53nvYhJfd2eJh-qREaeFBQAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zwRZ32H5_95LpRJHzXkqVAAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","JJab8JrsPDK66yfOtCG3zQAAAAAAAAAQ","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1XUiDryPjyncBxkTlbVecgAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","OIy8IFqaTWz5UoN3FSH-wQAAAAAAAABc","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","G68hjsyagwq6LpWrMjDdngAAAAAAG66m","G68hjsyagwq6LpWrMjDdngAAAAAADAfr","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkP4","G68hjsyagwq6LpWrMjDdngAAAAAAEvho","G68hjsyagwq6LpWrMjDdngAAAAAAEuWN","G68hjsyagwq6LpWrMjDdngAAAAAAEn-5","G68hjsyagwq6LpWrMjDdngAAAAAAD8aq","G68hjsyagwq6LpWrMjDdngAAAAAABorQ"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3]},"KEdXtWOmrUdpIHsjndtg_A":{"address_or_lines":[13038,15096,53616,1756,2573747,2594708,1091475,37514,2789627,1482889,1482415,2595076,1079485,9328,23398,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,33834,2790352,1482889,1482415,2595076,1079144,13038,14922,19872,15160,1738,54564,1481694,1829583,2567913,1848405,1978470,1481567,1493928,2595076,1079144,37902,2670,31228,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,20530,2790352,1482889,1482415,2595076,1076587,13038,15096,53616,1592,16726,2434,2846655,2846347,2843929,2840766,2843929,2840766,2844278,2841214,1439429,1865241,10489950,423063,2283967,2281306,2510155,2414579,2398792,2385273,8471622],"file_ids":["ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2L4SW1rQgEVXRj3pZAI3nQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","7bd6QJSfWZZfOOpDMHqLMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","lOUbi56SanKTCh9Y7fIwDw","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","J3wpF3Lf_vPkis4aNGKFbw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2L4SW1rQgEVXRj3pZAI3nQAAAAAAAJKK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAACRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAFtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","7bd6QJSfWZZfOOpDMHqLMAAAAAAAAIQq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADpK","lOUbi56SanKTCh9Y7fIwDwAAAAAAAE2g","8R2Lkqe-tYqq-plJ22QNzAAAAAAAADs4","h0l-9tGi18mC40qpcJbyDwAAAAAAAAbK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAANUk","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-rP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy7p","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDRV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHjBm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","705jmHYNd7I4Z4L4c0vfiAAAAAAAAJQO","TBeSzkyqIwKL8td602zDjAAAAAAAAApu","NH3zvSjFAfTSy6bEocpNyQAAAAAAAHn8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","J3wpF3Lf_vPkis4aNGKFbwAAAAAAAFAy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAAmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2Z2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1p-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFfbF","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHYZ","A2oiHVwisByxRn5RDT4LjAAAAAAAoBBe","A2oiHVwisByxRn5RDT4LjAAAAAAABnSX","A2oiHVwisByxRn5RDT4LjAAAAAAAItm_","A2oiHVwisByxRn5RDT4LjAAAAAAAIs9a","A2oiHVwisByxRn5RDT4LjAAAAAAAJk1L","A2oiHVwisByxRn5RDT4LjAAAAAAAJNfz","A2oiHVwisByxRn5RDT4LjAAAAAAAJJpI","A2oiHVwisByxRn5RDT4LjAAAAAAAJGV5","A2oiHVwisByxRn5RDT4LjAAAAAAAgURG"],"type_ids":[1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"V2K_ZjA6rol7KyINtV45_A":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,138,138,16,100,12,4,6,4,38,174,104,68,8,38,174,104,68,32,38,174,104,68,24,140,10,38,174,104,68,178,1090933,1814182,788459,788130,1197048,1243204,1201241,1245991,1245236,1171829,2265239,2264574,2258463,922614,2256180],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","JaHOMfnX0DG4ZnNTpPORVA","MepUYc0jU0AjPrrjuvTgGg","yWt46REABLfKH6PXLAE18A","VQs3Erq77xz92EfpT8sTKw","n7IiY_TlCWEfi47-QpeCLw","Ua3frjTXWBuWpTsQD8aKeA","GtyMRLq4aaDvuQ4C3N95mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","clFhkTaiph2aOjCNuZDWKA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","DLEY7W0VXWLE5Ol-plW-_w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","RY-vzTa9LfseI7kmcIcbgQ","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","-gq3a70QOgdn9HetYyf2Og","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK","JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK","MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ","yWt46REABLfKH6PXLAE18AAAAAAAAABk","VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM","n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE","Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG","GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAY","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","-gq3a70QOgdn9HetYyf2OgAAAAAAAACy","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","G68hjsyagwq6LpWrMjDdngAAAAAAG66m","G68hjsyagwq6LpWrMjDdngAAAAAADAfr","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkP4","G68hjsyagwq6LpWrMjDdngAAAAAAEvhE","G68hjsyagwq6LpWrMjDdngAAAAAAElRZ","G68hjsyagwq6LpWrMjDdngAAAAAAEwMn","G68hjsyagwq6LpWrMjDdngAAAAAAEwA0","G68hjsyagwq6LpWrMjDdngAAAAAAEeF1","G68hjsyagwq6LpWrMjDdngAAAAAAIpCX","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAInYf","G68hjsyagwq6LpWrMjDdngAAAAAADhP2","G68hjsyagwq6LpWrMjDdngAAAAAAIm00"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]}},"stack_frames":{"piWSMQrh4r040D0BPNaJvwAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAAEFn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEVjp":{"file_name":[],"function_name":["__x64_sys_nanosleep"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEPyZ":{"file_name":[],"function_name":["get_timespec64"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAASf5k":{"file_name":[],"function_name":["_copy_from_user"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEqRj":{"file_name":[],"function_name":["__x64_sys_futex"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEpne":{"file_name":[],"function_name":["do_futex"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKpz6":{"file_name":[],"function_name":["pipe_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAASkaN":{"file_name":[],"function_name":["copy_page_to_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAShYf":{"file_name":[],"function_name":["copyout"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAL1uY":{"file_name":[],"function_name":["__x64_sys_epoll_ctl"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAL1DP":{"file_name":[],"function_name":["ep_insert"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAAEFz":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKv-O":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAL10T":{"file_name":[],"function_name":["__x64_sys_epoll_ctl"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAgiGX":{"file_name":[],"function_name":["__mutex_lock.isra.7"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAADkms":{"file_name":[],"function_name":["mutex_spin_on_owner"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAAEH6":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAAD_e":{"file_name":[],"function_name":["syscall_slow_exit_work"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAFX1-":{"file_name":[],"function_name":["__audit_syscall_exit"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKv1p":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKhyy":{"file_name":[],"function_name":["alloc_empty_file"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKhiZ":{"file_name":[],"function_name":["__alloc_file"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJwne":{"file_name":[],"function_name":["kmem_cache_alloc"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKMb4":{"file_name":[],"function_name":["memcg_kmem_get_cache"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKhDw":{"file_name":[],"function_name":["ksys_write"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKg38":{"file_name":[],"function_name":["vfs_write"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKePq":{"file_name":[],"function_name":["new_sync_write"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnmG":{"file_name":[],"function_name":["sock_write_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnjq":{"file_name":[],"function_name":["sock_sendmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAePaV":{"file_name":[],"function_name":["unix_stream_sendmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZrqL":{"file_name":[],"function_name":["sock_def_readable"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAADXb2":{"file_name":[],"function_name":["__wake_up_common_lock"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAgljd":{"file_name":[],"function_name":["__lock_text_start"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKgEg":{"file_name":[],"function_name":["ksys_write"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKf4s":{"file_name":[],"function_name":["vfs_write"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKdQa":{"file_name":[],"function_name":["new_sync_write"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXmG":{"file_name":[],"function_name":["sock_write_iter"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXjq":{"file_name":[],"function_name":["sock_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAd--h":{"file_name":[],"function_name":["unix_stream_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZdo2":{"file_name":[],"function_name":["sock_alloc_send_pskb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZlap":{"file_name":[],"function_name":["alloc_skb_with_frags"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJMoT":{"file_name":[],"function_name":["__alloc_pages_nodemask"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJIxI":{"file_name":[],"function_name":["get_page_from_freelist"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnbb":{"file_name":[],"function_name":["sock_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAQGt0":{"file_name":[],"function_name":["security_socket_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM":{"file_name":[],"function_name":["inet_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYaV":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAchuU":{"file_name":[],"function_name":["tcp_rcv_space_adjust"],"function_offset":[],"line_number":[]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5":{"file_name":["../csu/libc-start.c"],"function_name":["__libc_start_main"],"function_offset":[],"line_number":[308]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAANci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAJEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAE8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAFw8":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAALhg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAADeq":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAM58":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAABgW":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"ne8F__HPIVgxgycJADVSzAAAAAAAAOzA":{"file_name":["clidriver.py"],"function_name":["invoke"],"function_offset":[29],"line_number":[930]},"ktj-IOmkEpvZJouiJkQjTgAAAAAAAE8a":{"file_name":["session.py"],"function_name":["create_client"],"function_offset":[117],"line_number":[854]},"O_h7elJSxPO7SiCsftYRZgAAAAAAAP8W":{"file_name":["client.py"],"function_name":["create_client"],"function_offset":[52],"line_number":[142]},"DxQN3aM1Ddn1lUwovx75wQAAAAAAACls":{"file_name":["client.py"],"function_name":["_load_service_endpoints_ruleset"],"function_offset":[1],"line_number":[193]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAADeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAAHQg":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAALtQ":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"9LzzIocepYcOjnUsLlgOjgAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKglI":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAdME8":{"file_name":[],"function_name":["inet_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcXT4":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcpNe":{"file_name":[],"function_name":["__tcp_send_ack.part.47"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZy0m":{"file_name":[],"function_name":["__alloc_skb"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAJwxK":{"file_name":[],"function_name":["kmem_cache_alloc_node"],"function_offset":[],"line_number":[]},"eOfhJQFIxbIEScd007tROwAAAAAAAHRK":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/nptl/pthread_create.c"],"function_name":["start_thread"],"function_offset":[],"line_number":[465]},"9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAmH_":{"file_name":["/usr/src/debug/openssl-1.0.2k/ssl/s3_clnt.c"],"function_name":["ssl3_connect"],"function_offset":[],"line_number":[345]},"9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAhXY":{"file_name":["/usr/src/debug/openssl-1.0.2k/ssl/s3_clnt.c"],"function_name":["ssl3_get_server_certificate"],"function_offset":[],"line_number":[1234]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJOq":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["ASN1_item_d2i"],"function_offset":[],"line_number":[154]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJNU":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["ASN1_item_ex_d2i"],"function_offset":[],"line_number":[553]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_item_ex_d2i"],"function_offset":[],"line_number":[478]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_template_ex_d2i"],"function_offset":[],"line_number":[623]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_template_noexp_d2i"],"function_offset":[],"line_number":[735]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFIM9":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_item_ex_d2i"],"function_offset":[],"line_number":[266]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFB_E":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/x_name.c"],"function_name":["x509_name_ex_d2i"],"function_offset":[],"line_number":[235]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFBnG":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/x_name.c"],"function_name":["x509_name_canon"],"function_offset":[],"line_number":[380]},"huWyXZbCBWCe2ZtK9BiokQAAAAAABylm":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/objects/obj_lib.c"],"function_name":["OBJ_dup"],"function_offset":[],"line_number":[83]},"huWyXZbCBWCe2ZtK9BiokQAAAAAABuZn":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/mem.c"],"function_name":["CRYPTO_malloc"],"function_offset":[],"line_number":[346]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB-en":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/malloc/malloc.c"],"function_name":["__GI___libc_malloc"],"function_offset":[],"line_number":[3068]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB813":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/malloc/malloc.c"],"function_name":["_int_malloc"],"function_offset":[],"line_number":[3995]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYZj":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7wq":{"file_name":[],"function_name":["skb_copy_datagram_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7pm":{"file_name":[],"function_name":["__skb_datagram_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7j0":{"file_name":[],"function_name":["simple_copy_to_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKZlu":{"file_name":[],"function_name":["__check_object_size"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAABtuk":{"file_name":[],"function_name":["__virt_addr_valid"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAoA6J":{"file_name":[],"function_name":["do_softirq_own_stack"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAwADc":{"file_name":[],"function_name":["__softirqentry_text_start"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaPZZ":{"file_name":[],"function_name":["net_rx_action"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaNu-":{"file_name":[],"function_name":["process_backlog"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaNlU":{"file_name":[],"function_name":["__netif_receive_skb_one_core"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcGcb":{"file_name":[],"function_name":["ip_rcv"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcHvM":{"file_name":[],"function_name":["ip_forward"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcMQY":{"file_name":[],"function_name":["ip_output"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcJtw":{"file_name":[],"function_name":["ip_finish_output2"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaLse":{"file_name":[],"function_name":["__dev_queue_xmit"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAbiYT":{"file_name":[],"function_name":["__qdisc_run"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAbiIt":{"file_name":[],"function_name":["sch_direct_xmit"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaLaf":{"file_name":[],"function_name":["dev_hard_start_xmit"],"function_offset":[],"line_number":[]},"5OhlekN4HU3KaqhG_GtinAAAAAAAADWR":{"file_name":[],"function_name":["ena_start_xmit"],"function_offset":[],"line_number":[]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFaMO":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_d2.c"],"function_name":["X509_STORE_load_locations"],"function_offset":[],"line_number":[94]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFjo2":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/by_file.c"],"function_name":["by_file_ctrl"],"function_offset":[],"line_number":[117]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFjjD":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/by_file.c"],"function_name":["X509_load_cert_crl_file"],"function_offset":[],"line_number":[261]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFUK9":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/pem/pem_info.c"],"function_name":["PEM_X509_INFO_read_bio"],"function_offset":[],"line_number":[248]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFBmx":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/x_name.c"],"function_name":["x509_name_canon"],"function_offset":[],"line_number":[377]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFF8W":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_new.c"],"function_name":["ASN1_item_new"],"function_offset":[],"line_number":[76]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFF5m":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_new.c"],"function_name":["asn1_item_ex_combine_new"],"function_offset":[],"line_number":[179]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB-Ww":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/malloc/malloc.c"],"function_name":["__GI___libc_malloc"],"function_offset":[],"line_number":[3031]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZnfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAePFy":{"file_name":[],"function_name":["unix_stream_recvmsg"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAeO8U":{"file_name":[],"function_name":["unix_stream_read_generic"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ1ga":{"file_name":[],"function_name":["consume_skb"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ1A9":{"file_name":[],"function_name":["skb_release_all"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ0_v":{"file_name":[],"function_name":["skb_release_head_state"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAeR6K":{"file_name":[],"function_name":["unix_destruct_scm"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZujP":{"file_name":[],"function_name":["sock_wfree"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAeNEG":{"file_name":[],"function_name":["unix_write_space"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAADXb2":{"file_name":[],"function_name":["__wake_up_common_lock"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAglVt":{"file_name":[],"function_name":["__lock_text_start"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoApO":{"file_name":[],"function_name":["ret_from_intr"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoBzi":{"file_name":[],"function_name":["do_IRQ"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAACO-_":{"file_name":[],"function_name":["irq_exit"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAwADc":{"file_name":[],"function_name":["__softirqentry_text_start"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAaQZZ":{"file_name":[],"function_name":["net_rx_action"],"function_offset":[],"line_number":[]},"R3YNZBiWt7Z3ZpFfTh6XyQAAAAAAAFQg":{"file_name":[],"function_name":["ena_io_poll"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAaQFc":{"file_name":[],"function_name":["napi_complete_done"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAaPMo":{"file_name":[],"function_name":["gro_normal_list.part.132"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAaPED":{"file_name":[],"function_name":["netif_receive_skb_list_internal"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAaO8W":{"file_name":[],"function_name":["__netif_receive_skb_list_core"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcHjA":{"file_name":[],"function_name":["ip_list_rcv"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcHKv":{"file_name":[],"function_name":["ip_sublist_rcv"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcGmO":{"file_name":[],"function_name":["ip_sublist_rcv_finish"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcHZY":{"file_name":[],"function_name":["ip_local_deliver"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcHXT":{"file_name":[],"function_name":["ip_local_deliver_finish"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcHQq":{"file_name":[],"function_name":["ip_protocol_deliver_rcu"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAczxz":{"file_name":[],"function_name":["tcp_v4_rcv"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcyj0":{"file_name":[],"function_name":["tcp_v4_do_rcv"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcmQN":{"file_name":[],"function_name":["tcp_rcv_state_process"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAck58":{"file_name":[],"function_name":["tcp_data_queue"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcj1B":{"file_name":[],"function_name":["tcp_fin"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAco-Y":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcM8h":{"file_name":[],"function_name":["__ip_queue_xmit"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcNR4":{"file_name":[],"function_name":["ip_output"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAcKvQ":{"file_name":[],"function_name":["ip_finish_output2"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAaMse":{"file_name":[],"function_name":["__dev_queue_xmit"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAbjZz":{"file_name":[],"function_name":["__qdisc_run"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAbjKN":{"file_name":[],"function_name":["sch_direct_xmit"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAaMaf":{"file_name":[],"function_name":["dev_hard_start_xmit"],"function_offset":[],"line_number":[]},"R3YNZBiWt7Z3ZpFfTh6XyQAAAAAAADVS":{"file_name":[],"function_name":["ena_start_xmit"],"function_offset":[],"line_number":[]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAIi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAJci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAFEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAA8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAAw8":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAHhg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAOeq":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAI3-":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"GdaBUD9IUEkKxIBryNqV2wAAAAAAANtO":{"file_name":["clidriver.py"],"function_name":["create_parser"],"function_offset":[4],"line_number":[635]},"QU8QLoFK6ojrywKrBFfTzAAAAAAAAGqM":{"file_name":["clidriver.py"],"function_name":["_get_command_table"],"function_offset":[3],"line_number":[580]},"V558DAsp4yi8bwa8eYwk5QAAAAAAAL60":{"file_name":["clidriver.py"],"function_name":["_create_command_table"],"function_offset":[18],"line_number":[615]},"tuTnMBfyc9UiPsI0QyvErAAAAAAAABis":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[700]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAAHlS":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"gMhgHDYSMmyInNJ15VwYFgAAAAAAADvy":{"file_name":["hooks.py"],"function_name":["_emit"],"function_offset":[38],"line_number":[215]},"cHp4MwXaY5FCuFRuAA6tWwAAAAAAAKx8":{"file_name":["waiters.py"],"function_name":["add_waiters"],"function_offset":[11],"line_number":[36]},"-9oyoP4Jj2iRkwEezqId-gAAAAAAANMc":{"file_name":["waiters.py"],"function_name":["get_waiter_model_from_service_model"],"function_offset":[5],"line_number":[48]},"3FRCbvQLPuJyn2B-2wELGwAAAAAAANK8":{"file_name":["session.py"],"function_name":["get_waiter_model"],"function_offset":[4],"line_number":[527]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAACEw":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAAGla":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"yaTrLhUSIq2WitrTHLBy3QAAAAAAAHDM":{"file_name":["posixpath.py"],"function_name":["join"],"function_offset":[21],"line_number":[92]},"8EY5iPD5-FtlXFBTyb6lkwAAAAAAAPtm":{"file_name":["pyi_rth_pkgutil.py"],"function_name":[""],"function_offset":[33],"line_number":[34]},"ik6PIX946fW_erE7uBJlVQAAAAAAAILu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAACFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"dCCKy6JoX0PADOFic8hRNQAAAAAAAC7S":{"file_name":["pkgutil.py"],"function_name":[""],"function_offset":[315],"line_number":[316]},"7RLN3PNgotUSmdQVMRTSvAAAAAAAAMnE":{"file_name":["_bootstrap.py"],"function_name":["exec_module"],"function_offset":[5],"line_number":[982]},"43vJVfBcAahhLMzDSC-H0gAAAAAAADOC":{"file_name":["util.py"],"function_name":[""],"function_offset":[266],"line_number":[267]},"ik6PIX946fW_erE7uBJlVQAAAAAAAIJy":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"RRFdsCrJw1U2erb6qtrrzQAAAAAAAMNe":{"file_name":["_bootstrap.py"],"function_name":["__enter__"],"function_offset":[2],"line_number":[171]},"_zH-ed4x-42m0B4z2RmcdQAAAAAAALN-":{"file_name":["_bootstrap.py"],"function_name":["_get_module_lock"],"function_offset":[34],"line_number":[213]},"a5aMcPOeWx28QSVng73nBQAAAAAAAAAw":{"file_name":["aws"],"function_name":[""],"function_offset":[5],"line_number":[19]},"OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[5],"line_number":[1007]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[19],"line_number":[986]},"XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[21],"line_number":[680]},"4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[499]},"79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[49],"line_number":[62]},"gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc":{"file_name":["core.py"],"function_name":[""],"function_offset":[3],"line_number":[16]},"LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs":{"file_name":["prompttoolkit.py"],"function_name":[""],"function_offset":[5],"line_number":[18]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[5],"line_number":[972]},"zP58DjIs7uq1cghmzykyNAAAAAAAAAAK":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[228]},"9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAAM4":{"file_name":["application.py"],"function_name":[""],"function_offset":[114],"line_number":[115]},"IlUL618nbeW5Kz4uyGZLrQAAAAAAAAB0":{"file_name":["application.py"],"function_name":["Application"],"function_offset":[91],"line_number":[206]},"U7DZUwH_4YU5DSkoQhGJWwAAAAAAAAAM":{"file_name":["typing.py"],"function_name":["inner"],"function_offset":[3],"line_number":[274]},"bmb3nSRfimrjfhanpjR1rQAAAAAAAAAI":{"file_name":["typing.py"],"function_name":["__getitem__"],"function_offset":[2],"line_number":[354]},"oN7OWDJeuc8DmI2f_earDQAAAAAAAAA2":{"file_name":["typing.py"],"function_name":["Union"],"function_offset":[32],"line_number":[466]},"Yj7P3-Rt3nirG6apRl4A7AAAAAAAAAAM":{"file_name":["typing.py"],"function_name":[""],"function_offset":[0],"line_number":[466]},"pz3Evn9laHNJFMwOKIXbswAAAAAAAAAu":{"file_name":["typing.py"],"function_name":["_type_check"],"function_offset":[18],"line_number":[155]},"7aaw2O1Vn7-6eR8XuUWQZQAAAAAAAAAW":{"file_name":["typing.py"],"function_name":["_type_convert"],"function_offset":[4],"line_number":[132]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAMbM":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAKrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAOAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAIdM":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAEQW":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"ne8F__HPIVgxgycJADVSzAAAAAAAAJ9A":{"file_name":["clidriver.py"],"function_name":["invoke"],"function_offset":[29],"line_number":[930]},"CwUjPVV5_7q7c0GhtW0aPwAAAAAAAGh4":{"file_name":["session.py"],"function_name":["create_client"],"function_offset":[112],"line_number":[848]},"O_h7elJSxPO7SiCsftYRZgAAAAAAAB2m":{"file_name":["client.py"],"function_name":["create_client"],"function_offset":[52],"line_number":[142]},"ZLTqiSLOmv4Ej_7d8yKLmwAAAAAAAPns":{"file_name":["client.py"],"function_name":["_get_client_args"],"function_offset":[15],"line_number":[295]},"qLiwuFhv6DIyQ0OgaSMXCgAAAAAAAFnm":{"file_name":["args.py"],"function_name":["get_client_args"],"function_offset":[72],"line_number":[118]},"ka2IKJhpWbD6PA3J3v624wAAAAAAALgG":{"file_name":["copy.py"],"function_name":["copy"],"function_offset":[35],"line_number":[101]},"e8Lb_MV93AH-OkvHPPDitgAAAAAAAEzS":{"file_name":["hooks.py"],"function_name":["__copy__"],"function_offset":[6],"line_number":[344]},"1vivUE5hL65442lQ9a_ylgAAAAAAAIYC":{"file_name":["hooks.py"],"function_name":["__copy__"],"function_offset":[8],"line_number":[486]},"fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK2K":{"file_name":["hooks.py"],"function_name":["_recursive_copy"],"function_offset":[12],"line_number":[500]},"fh_7rTxpgngJ2cX2lBjVdgAAAAAAAK0u":{"file_name":["hooks.py"],"function_name":["_recursive_copy"],"function_offset":[12],"line_number":[500]},"fCsVLBj60GK9Hf8VtnMcgAAAAAAAALX8":{"file_name":["hooks.py"],"function_name":["__copy__"],"function_offset":[5],"line_number":[35]},"ka2IKJhpWbD6PA3J3v624wAAAAAAALd2":{"file_name":["copy.py"],"function_name":["copy"],"function_offset":[35],"line_number":[101]},"cfc92_adXFZraMPGbgbcDgAAAAAAANvu":{"file_name":["pyi_rth_inspect.py"],"function_name":[""],"function_offset":[43],"line_number":[44]},"ik6PIX946fW_erE7uBJlVQAAAAAAAGLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbg":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"WLefmNR3IpykzCX3WWNnMwAAAAAAAEIO":{"file_name":["inspect.py"],"function_name":[""],"function_offset":[1707],"line_number":[1708]},"IvJrzqPEgeoowZySdwFq3wAAAAAAAEAo":{"file_name":["dis.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"vkeP2ntYyoFN0A16x9eliwAAAAAAAF8U":{"file_name":["__init__.py"],"function_name":["namedtuple"],"function_offset":[164],"line_number":[512]},"MXHCWLuAJw7Gg6T7hdrPHAAAAAAAAI4g":{"file_name":["pyi_rth_multiprocessing.py"],"function_name":[""],"function_offset":[13],"line_number":[14]},"ecHSwk0KAG7gFkiYdAgIZwAAAAAAAFTg":{"file_name":["pyi_rth_multiprocessing.py"],"function_name":["_pyi_rth_multiprocessing"],"function_offset":[94],"line_number":[107]},"ik6PIX946fW_erE7uBJlVQAAAAAAAOLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAANRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAAtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[8],"line_number":[21]},"mHiYHSEggclUi1ELZIxq4AAAAAAAAABA":{"file_name":["session.py"],"function_name":[""],"function_offset":[13],"line_number":[27]},"_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAo":{"file_name":["client.py"],"function_name":[""],"function_offset":[4],"line_number":[17]},"0cqvso24v07beLsmyC0nMwAAAAAAAABQ":{"file_name":["args.py"],"function_name":[""],"function_offset":[15],"line_number":[28]},"3WU6MO1xF7O0NmrHFj4y4AAAAAAAAAA8":{"file_name":["regions.py"],"function_name":[""],"function_offset":[12],"line_number":[25]},"x617yDiAG2Sqq3cLDkX4aAAAAAAAAAF-":{"file_name":["auth.py"],"function_name":[""],"function_offset":[660],"line_number":[674]},"ZTmztUywGW_uHXPqWVr76wAAAAAAAAAY":{"file_name":["auth.py"],"function_name":[""],"function_offset":[3],"line_number":[17]},"ZPAF8mJO2n0azNbxzkJ2rAAAAAAAAAAc":{"file_name":["auth.py"],"function_name":[""],"function_offset":[9],"line_number":[10]},"MXHCWLuAJw7Gg6T7hdrPHAAAAAAAAA4g":{"file_name":["pyi_rth_multiprocessing.py"],"function_name":[""],"function_offset":[13],"line_number":[14]},"ecHSwk0KAG7gFkiYdAgIZwAAAAAAAKTg":{"file_name":["pyi_rth_multiprocessing.py"],"function_name":["_pyi_rth_multiprocessing"],"function_offset":[94],"line_number":[107]},"ik6PIX946fW_erE7uBJlVQAAAAAAANLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAAMRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAPtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAMJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAIsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAOVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAPHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAE10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAGs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"ik6PIX946fW_erE7uBJlVQAAAAAAAJLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAADFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAADDC":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"SOSrvCNmbstVFKAcqHNCvAAAAAAAAMF-":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[89],"line_number":[90]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAMFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAABci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAI_G":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAALMM":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAEn0":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAADYk":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAOXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"LEy-wm0GIvRoYVAga55HiwAAAAAAANxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAORY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAHqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAEFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAI3K":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"SD7uzoegJjRT3jYNpuQ5wQAAAAAAALX2":{"file_name":["configure.py"],"function_name":[""],"function_offset":[56],"line_number":[57]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAEBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAMFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAALLi":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXjj":{"file_name":[],"function_name":["sock_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcK5W":{"file_name":[],"function_name":["tcp_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcKWq":{"file_name":[],"function_name":["tcp_sendmsg_locked"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcbOh":{"file_name":[],"function_name":["__tcp_push_pending_frames"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcaTc":{"file_name":[],"function_name":["tcp_write_xmit"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcY0Y":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAb80x":{"file_name":[],"function_name":["__ip_queue_xmit"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAb9KI":{"file_name":[],"function_name":["ip_output"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAb6ng":{"file_name":[],"function_name":["ip_finish_output2"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZ8uJ":{"file_name":[],"function_name":["__dev_queue_xmit"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZ8Dc":{"file_name":[],"function_name":["validate_xmit_skb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZ793":{"file_name":[],"function_name":["netif_skb_features"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZ7qG":{"file_name":[],"function_name":["skb_network_protocol"],"function_offset":[],"line_number":[]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAAGQ":{"file_name":["application.py"],"function_name":[""],"function_offset":[58],"line_number":[59]},"c-eM3dWacIPzBmA_7-OWBwAAAAAAAAAU":{"file_name":["defaults.py"],"function_name":[""],"function_offset":[7],"line_number":[8]},"w9AQfBE7-1YeE4mOMirPBgAAAAAAAABY":{"file_name":["basic.py"],"function_name":[""],"function_offset":[13],"line_number":[15]},"4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[13],"line_number":[482]},"NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[14],"line_number":[298]},"coeZ_4yf5sOePIKKlm8FNQAAAAAAAAC-":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[18],"line_number":[304]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAPVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAAHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAI10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAKs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAETO":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"uo8E5My6tupMEt-pfV-uhAAAAAAAAKIu":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAEFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAANmC":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAHbM":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAABka":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAOxq":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"xNMiNBkMujk7ZnRv0OEjrQAAAAAAAEu8":{"file_name":["clidriver.py"],"function_name":["arg_table"],"function_offset":[4],"line_number":[733]},"MYrgKQIxdDhr1gdpucfc-QAAAAAAALya":{"file_name":["clidriver.py"],"function_name":["_create_argument_table"],"function_offset":[26],"line_number":[867]},"un9fLDZOLvDMO52ltZtuegAAAAAAAGsM":{"file_name":["clidriver.py"],"function_name":["_emit"],"function_offset":[1],"line_number":[874]},"grikUXlisBLUbeL_OWixIwAAAAAAAPZs":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[699]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAAPdy":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"gMhgHDYSMmyInNJ15VwYFgAAAAAAALvy":{"file_name":["hooks.py"],"function_name":["_emit"],"function_offset":[38],"line_number":[215]},"rTFMSHhLRlj86vHPR06zoQAAAAAAABZ2":{"file_name":["paginate.py"],"function_name":["unify_paging_params"],"function_offset":[51],"line_number":[175]},"oArGmvsy3VNtTf_V9EHNeQAAAAAAAKNy":{"file_name":["paginate.py"],"function_name":["get_paginator_config"],"function_offset":[10],"line_number":[92]},"7v-k2b21f_Xuf-3329jFywAAAAAAAIY8":{"file_name":["session.py"],"function_name":["get_paginator_model"],"function_offset":[4],"line_number":[532]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAADjQ":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAACxq":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"yaTrLhUSIq2WitrTHLBy3QAAAAAAANeQ":{"file_name":["posixpath.py"],"function_name":["join"],"function_offset":[21],"line_number":[92]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMFQ":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"MU3fJpOZe9TA4mzeo52wZgAAAAAAAE6e":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[297],"line_number":[298]},"N0GNsPaCLYzoFsPJWnIJtQAAAAAAAC8u":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[53],"line_number":[54]},"fq0ezjB8ddCA6Pk0BY9arQAAAAAAAP7M":{"file_name":["distro.py"],"function_name":[""],"function_offset":[608],"line_number":[609]},"r1l-BTVp1g6dSvPPoOY_cgAAAAAAAHDY":{"file_name":["typing.py"],"function_name":["__new__"],"function_offset":[55],"line_number":[2965]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAADU":{"file_name":["application.py"],"function_name":[""],"function_offset":[40],"line_number":[41]},"bAXCoU3-CU0WlRxl5l1tmwAAAAAAAAC8":{"file_name":["buffer.py"],"function_name":[""],"function_offset":[32],"line_number":[33]},"IcegEVkl4JzbMBhUeMqp0QAAAAAAAAA8":{"file_name":["auto_suggest.py"],"function_name":[""],"function_offset":[18],"line_number":[19]},"tz0ps4QDYR1clO_q5ziJUQAAAAAAAABi":{"file_name":["document.py"],"function_name":[""],"function_offset":[20],"line_number":[21]},"M0gS5SrmklEEjlV4jbSIBAAAAAAAAAAI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[18],"line_number":[19]},"k5C4r96b77lEZ_fHFwCYkQAAAAAAAAAk":{"file_name":["app.py"],"function_name":[""],"function_offset":[6],"line_number":[7]},"coeZ_4yf5sOePIKKlm8FNQAAAAAAAACm":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[16],"line_number":[302]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAFcs":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAOrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAHAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAMdM":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAANCa":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"xNMiNBkMujk7ZnRv0OEjrQAAAAAAAIu8":{"file_name":["clidriver.py"],"function_name":["arg_table"],"function_offset":[4],"line_number":[733]},"MYrgKQIxdDhr1gdpucfc-QAAAAAAAJ-q":{"file_name":["clidriver.py"],"function_name":["_create_argument_table"],"function_offset":[26],"line_number":[867]},"un9fLDZOLvDMO52ltZtuegAAAAAAAKsM":{"file_name":["clidriver.py"],"function_name":["_emit"],"function_offset":[1],"line_number":[874]},"grikUXlisBLUbeL_OWixIwAAAAAAADZs":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[699]},"rTFMSHhLRlj86vHPR06zoQAAAAAAAAfG":{"file_name":["paginate.py"],"function_name":["unify_paging_params"],"function_offset":[51],"line_number":[175]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAACBA":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAABMg":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"wXOyVgf5_nNg6CUH5kFBbgAAAAAAAJkK":{"file_name":["loaders.py"],"function_name":[""],"function_offset":[0],"line_number":[273]},"zEgDK4qMawUAQZjg5YHywwAAAAAAAIC0":{"file_name":["genericpath.py"],"function_name":["isdir"],"function_offset":[6],"line_number":[45]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAEGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAMQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAEJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAAsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAABVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAACHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAA10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAOs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"ik6PIX946fW_erE7uBJlVQAAAAAAADLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAANFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAGFG":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"780bLUPADqfQ3x1T5lnVOgAAAAAAAFUm":{"file_name":["emr.py"],"function_name":[""],"function_offset":[42],"line_number":[43]},"X0TUmWpd8saA6nnPGQi3nQAAAAAAAPKS":{"file_name":["addsteps.py"],"function_name":[""],"function_offset":[20],"line_number":[21]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAACQM":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"grZNsSElR5ITq8H2yHCNSwAAAAAAADw8":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAABeq":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAPQ6":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"xNMiNBkMujk7ZnRv0OEjrQAAAAAAAKys":{"file_name":["clidriver.py"],"function_name":["arg_table"],"function_offset":[4],"line_number":[733]},"MYrgKQIxdDhr1gdpucfc-QAAAAAAAPB6":{"file_name":["clidriver.py"],"function_name":["_create_argument_table"],"function_offset":[26],"line_number":[867]},"un9fLDZOLvDMO52ltZtuegAAAAAAAE1M":{"file_name":["clidriver.py"],"function_name":["_emit"],"function_offset":[1],"line_number":[874]},"tuTnMBfyc9UiPsI0QyvErAAAAAAAAFis":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[700]},"rTFMSHhLRlj86vHPR06zoQAAAAAAANRm":{"file_name":["paginate.py"],"function_name":["unify_paging_params"],"function_offset":[51],"line_number":[175]},"oArGmvsy3VNtTf_V9EHNeQAAAAAAAKTS":{"file_name":["paginate.py"],"function_name":["get_paginator_config"],"function_offset":[10],"line_number":[92]},"-T5rZCijT5TDJjmoEi8KxgAAAAAAABP8":{"file_name":["session.py"],"function_name":["get_paginator_model"],"function_offset":[4],"line_number":[533]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAAEDg":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAAG-k":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKdTc":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKycK":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKv55":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKh3C":{"file_name":[],"function_name":["alloc_empty_file"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKhna":{"file_name":[],"function_name":["__alloc_file"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAQFXl":{"file_name":[],"function_name":["security_file_alloc"],"function_offset":[],"line_number":[]},"tz0ps4QDYR1clO_q5ziJUQAAAAAAAABW":{"file_name":["document.py"],"function_name":[""],"function_offset":[19],"line_number":[20]},"O2RGJIowquMzuET0HYQ6aQAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"Ht79I_xqXv3bOgaClTNQ4wAAAAAAAALK":{"file_name":["enum.py"],"function_name":["__new__"],"function_offset":[131],"line_number":[310]},"T8-enlAkCZXqinPHW4B8swAAAAAAAAAi":{"file_name":["enum.py"],"function_name":["__setattr__"],"function_offset":[11],"line_number":[473]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAHpm":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAMDc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAMn0":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAALYk":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAABqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAH2W":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"3HhVgGD2yvuFLpoZq7RfKwAAAAAAAB8C":{"file_name":["cloudfront.py"],"function_name":[""],"function_offset":[179],"line_number":[180]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAG3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAKSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"fDiQPd_MeGeyY9ZBOSU1GgAAAAAAAJ7g":{"file_name":["hashes.py"],"function_name":[""],"function_offset":[245],"line_number":[246]},"mP9Tk3T74fjOyYWKUaqdMQAAAAAAAPDi":{"file_name":["client.py"],"function_name":[""],"function_offset":[119],"line_number":[120]},"I4X8AC1-B0GuL4JyYemPzwAAAAAAACOi":{"file_name":["args.py"],"function_name":[""],"function_offset":[35],"line_number":[36]},"b-3iFnlA7BmzAxDEzxShdAAAAAAAACGi":{"file_name":["config.py"],"function_name":[""],"function_offset":[24],"line_number":[25]},"8jcOoolAg5RmmHop7NqzWQAAAAAAAC4-":{"file_name":["endpoint.py"],"function_name":[""],"function_offset":[47],"line_number":[48]},"2LABj1asXFICsosP2OrbVQAAAAAAAO82":{"file_name":["hooks.py"],"function_name":["httpchecksum"],"function_offset":[67],"line_number":[68]},"N1ZmsCOKFJHNThnHfFYo6QAAAAAAAMEC":{"file_name":["hooks.py"],"function_name":["HierarchicalEmitter"],"function_offset":[155],"line_number":[321]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoBBe":{"file_name":[],"function_name":["page_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAABnL3":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAEFQ":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"MU3fJpOZe9TA4mzeo52wZgAAAAAAAO4-":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[297],"line_number":[298]},"N0GNsPaCLYzoFsPJWnIJtQAAAAAAAK8u":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[53],"line_number":[54]},"fq0ezjB8ddCA6Pk0BY9arQAAAAAAAJ2i":{"file_name":["distro.py"],"function_name":[""],"function_offset":[608],"line_number":[609]},"-gDCCFjiBc58_iqAxti3KwAAAAAAAL70":{"file_name":["argparse.py"],"function_name":[""],"function_offset":[817],"line_number":[818]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoBCe":{"file_name":[],"function_name":["async_page_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAABnSX":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAItm_":{"file_name":[],"function_name":["handle_mm_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAItCv":{"file_name":[],"function_name":["__handle_mm_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAIAXE":{"file_name":[],"function_name":["__lru_cache_add"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAIATK":{"file_name":[],"function_name":["pagevec_lru_move_fn"],"function_offset":[],"line_number":[]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAJd-":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"d4jl580PLMUwu5s3I4wcXgAAAAAAAMSu":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[16],"line_number":[17]},"tKago5vqLnwIkezk_wTBpQAAAAAAACga":{"file_name":["package.py"],"function_name":[""],"function_offset":[31],"line_number":[32]},"rpq4cV1KPyFZcnKfWjKdZwAAAAAAAHr2":{"file_name":["s3uploader.py"],"function_name":[""],"function_offset":[42],"line_number":[43]},"uFElJcsK9my-kA6ZYzT1uwAAAAAAABOG":{"file_name":["manager.py"],"function_name":[""],"function_offset":[46],"line_number":[47]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAP3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAADSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"yp8MidCGMe4czbl-NigsYQAAAAAAAOFG":{"file_name":["connection.py"],"function_name":[""],"function_offset":[524],"line_number":[525]},"2noK4QoWxdzASRHkjOFwVAAAAAAAAMn6":{"file_name":["tempfile.py"],"function_name":[""],"function_offset":[547],"line_number":[548]},"yO-OCNRiISNdCb_iVi4E_wAAAAAAAOkg":{"file_name":["shutil.py"],"function_name":[""],"function_offset":[2003],"line_number":[2004]},"mBpjyQvq6ftE7Wm1BUpcFgAAAAAAAKGy":{"file_name":["abc.py"],"function_name":["__new__"],"function_offset":[3],"line_number":[108]},"ik6PIX946fW_erE7uBJlVQAAAAAAALLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAALr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAFFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"2L4SW1rQgEVXRj3pZAI3nQAAAAAAAEaS":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[97],"line_number":[98]},"OlTvyWQFXjOweJcs3kiGygAAAAAAANKC":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[155],"line_number":[156]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAAKQM":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"bcwppGWOjTWw86zVNJE_JgAAAAAAABl-":{"file_name":["six.py"],"function_name":["__get__"],"function_offset":[9],"line_number":[104]},"TBeSzkyqIwKL8td602zDjAAAAAAAAIpu":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAAPn8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"NiCfOMPggzUjx-usqlmxvgAAAAAAAL3-":{"file_name":["queue.py"],"function_name":[""],"function_offset":[62],"line_number":[63]},"Vot4T3F5OpUj8rbXhgpMDgAAAAAAAH8I":{"file_name":["_bootstrap_external.py"],"function_name":["exec_module"],"function_offset":[4],"line_number":[938]},"eV_m28NnKeeTL60KO2H3SAAAAAAAANtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAACqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"5nuRo5ZVtij8bTLlri7QXAAAAAAAALda":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[29],"line_number":[30]},"hi5mlwAHRj-Yl1GNV_UEZQAAAAAAADqu":{"file_name":["ssh.py"],"function_name":[""],"function_offset":[30],"line_number":[31]},"uSWUCgHgLPG4OFtPdUp0rgAAAAAAAOFO":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[27],"line_number":[28]},"-BjW54fwMksXBor9R-YN9wAAAAAAAAdO":{"file_name":["ssh.py"],"function_name":[""],"function_offset":[575],"line_number":[576]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAALSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"wuSmWRANn3Cl-syjEtxMoQAAAAAAAEwe":{"file_name":["ec.py"],"function_name":[""],"function_offset":[339],"line_number":[340]},"pv4wAezdMMO0SVuGgaEMTgAAAAAAADV2":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[17],"line_number":[18]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAMFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"qns5vQ3LMi6QrIMOgD_TwQAAAAAAAMR-":{"file_name":["service.py"],"function_name":[""],"function_offset":[20],"line_number":[21]},"J_Lkq1OzUHxWQhnTgF6FwAAAAAAAAHq2":{"file_name":["restdoc.py"],"function_name":[""],"function_offset":[22],"line_number":[23]},"XkOSW26Xa6_lkqHv5givKgAAAAAAAKg2":{"file_name":["compat.py"],"function_name":[""],"function_offset":[231],"line_number":[232]},"rEbhXoMLMee0rf6bwU9RPwAAAAAAAJc2":{"file_name":["hashlib.py"],"function_name":[""],"function_offset":[300],"line_number":[301]},"J1eggTwSzYdi9OsSu1q37gAAAAAAABn4":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"0S3htaCNkzxOYeavDR1GTQAAAAAAADe4":{"file_name":["_bootstrap.py"],"function_name":["module_from_spec"],"function_offset":[14],"line_number":[580]},"rBzW547V0L_mH4nnWK1FUQAAAAAAANTA":{"file_name":["_bootstrap_external.py"],"function_name":["create_module"],"function_offset":[6],"line_number":[1237]},"PVZV2uq5ZRt-FFaczL10BAAAAAAAABCQ":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/dlfcn/dlopen.c"],"function_name":["__dlopen"],"function_offset":[],"line_number":[87]},"PVZV2uq5ZRt-FFaczL10BAAAAAAAABZ0":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/dlfcn/dlerror.c"],"function_name":["_dlerror_run"],"function_offset":[],"line_number":[163]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-error-skeleton.c"],"function_name":["__GI__dl_catch_error"],"function_offset":[],"line_number":[198]},"PVZV2uq5ZRt-FFaczL10BAAAAAAAABAF":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/dlfcn/dlopen.c"],"function_name":["dlopen_doit"],"function_offset":[],"line_number":[66]},"3nN3bymnZ8E42aLEtgglmAAAAAAAASmo":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-open.c"],"function_name":["_dl_open"],"function_offset":[],"line_number":[649]},"3nN3bymnZ8E42aLEtgglmAAAAAAAATA-":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-open.c"],"function_name":["dl_open_worker"],"function_offset":[],"line_number":[424]},"3nN3bymnZ8E42aLEtgglmAAAAAAAALbA":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-reloc.c"],"function_name":["_dl_relocate_object"],"function_offset":[],"line_number":[160]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAJyS":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-lookup.c"],"function_name":["_dl_lookup_symbol_x"],"function_offset":[],"line_number":[833]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAJMS":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-lookup.c"],"function_name":["do_lookup_x"],"function_offset":[],"line_number":[413]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAM10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAACs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"ik6PIX946fW_erE7uBJlVQAAAAAAAELu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAEFq":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"z1-LQiSwGmfJHZm7Q223fQAAAAAAAFo-":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[18],"line_number":[19]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAADRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAGtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAANfe":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"xDXQtI2vA5YySwpx7QFiwAAAAAAAAMoy":{"file_name":["popen_forkserver.py"],"function_name":[""],"function_offset":[27],"line_number":[28]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAI3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAMSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAADqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"fSQ747oLNh0c0zFQjsVRWgAAAAAAAP02":{"file_name":["forkserver.py"],"function_name":[""],"function_offset":[80],"line_number":[81]},"yp8MidCGMe4czbl-NigsYQAAAAAAALK2":{"file_name":["connection.py"],"function_name":[""],"function_offset":[524],"line_number":[525]},"2noK4QoWxdzASRHkjOFwVAAAAAAAAOQq":{"file_name":["tempfile.py"],"function_name":[""],"function_offset":[547],"line_number":[548]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAMBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAADLi":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"Z-J8GEZK5aE8XNQ-3sO-FgAAAAAAAKxW":{"file_name":["adaptive.py"],"function_name":[""],"function_offset":[34],"line_number":[35]},"H-OlnUNurKAlPjkWfV0hTgAAAAAAAH4K":{"file_name":["standard.py"],"function_name":[""],"function_offset":[279],"line_number":[280]},"ik6PIX946fW_erE7uBJlVQAAAAAAAKLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAEFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"pv4wAezdMMO0SVuGgaEMTgAAAAAAAJUW":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[17],"line_number":[18]},"qns5vQ3LMi6QrIMOgD_TwQAAAAAAAPeO":{"file_name":["service.py"],"function_name":[""],"function_offset":[20],"line_number":[21]},"J_Lkq1OzUHxWQhnTgF6FwAAAAAAAADGS":{"file_name":["restdoc.py"],"function_name":[""],"function_offset":[22],"line_number":[23]},"hrIwGgdEFsOBluJKOOs8ZgAAAAAAACzs":{"file_name":["docstringparser.py"],"function_name":[""],"function_offset":[172],"line_number":[173]},"jhRfowFriqBKJWhZSTe7kgAAAAAAAJ3O":{"file_name":["six.py"],"function_name":["__get__"],"function_offset":[9],"line_number":[100]},"B0e_Spx899MeGx2KSvzzowAAAAAAADwe":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[115]},"v1UMuiFodNtdRCNi4iF0RgAAAAAAACH8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[83]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAKj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAMtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAEay":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"yzJdtc2TQHpJ_IY5QdUQKAAAAAAAAIh2":{"file_name":["posixpath.py"],"function_name":["dirname"],"function_offset":[8],"line_number":[158]},"VuJFonCXevADcEDW6NVbKgAAAAAAAGsG":{"file_name":["devcommands.py"],"function_name":[""],"function_offset":[49],"line_number":[50]},"VFBd9VqCaQu0ZzjQ2K3pjgAAAAAAAAsO":{"file_name":["factory.py"],"function_name":[""],"function_offset":[57],"line_number":[58]},"PUSucJs4FC_WdMzOyH3QYwAAAAAAAEHe":{"file_name":["layout.py"],"function_name":[""],"function_offset":[130],"line_number":[131]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAACRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAFtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"q_M8ZB6aihtZKYZfHGkluQAAAAAAABuK":{"file_name":["core.py"],"function_name":[""],"function_offset":[331],"line_number":[332]},"MAFaasFcVIeoQsejXrnp0wAAAAAAAFP-":{"file_name":["core.py"],"function_name":["TemplateStep"],"function_offset":[40],"line_number":[240]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAHSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAHJC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAABpU":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAAJHY":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAGaA":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"zpgqltXEgKujOhJUj-jAhgAAAAAAAGCI":{"file_name":["_parser.py"],"function_name":["__getitem__"],"function_offset":[3],"line_number":[165]},"ihsoi5zicXHpPrWRA9bTnAAAAAAAAEMs":{"file_name":["base_events.py"],"function_name":[""],"function_offset":[190],"line_number":[191]},"HbU9j_4D3UaJfjASj-JljAAAAAAAAJR-":{"file_name":["staggered.py"],"function_name":[""],"function_offset":[1],"line_number":[2]},"awUBhCYYZvWyN4rrVw-u5AAAAAAAAPSe":{"file_name":["locks.py"],"function_name":[""],"function_offset":[114],"line_number":[115]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAANJU":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAPSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAGFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"akZOzI9XwsEixvkTDGeDPwAAAAAAAPZa":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[2],"line_number":[3]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAL3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"d1LNRHMzWQ5PvB10hYiN3gAAAAAAAPSe":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[9],"line_number":[10]},"PmkUsVBZlaSEgaFwCOKZlgAAAAAAAGto":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[166],"line_number":[167]},"_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU":{"file_name":["client.py"],"function_name":[""],"function_offset":[3],"line_number":[16]},"fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[25],"line_number":[1058]},"CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc":{"file_name":["waiter.py"],"function_name":[""],"function_offset":[4],"line_number":[17]},"5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[2],"line_number":[15]},"z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE":{"file_name":["service.py"],"function_name":[""],"function_offset":[0],"line_number":[13]},"1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM":{"file_name":["restdoc.py"],"function_name":[""],"function_offset":[2],"line_number":[15]},"rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc":{"file_name":["compat.py"],"function_name":[""],"function_offset":[17],"line_number":[31]},"zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[10],"line_number":[11]},"r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[2],"line_number":[3]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[15],"line_number":[982]},"JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[24],"line_number":[925]},"MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[2],"line_number":[192]},"yWt46REABLfKH6PXLAE18AAAAAAAAABk":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[16],"line_number":[431]},"VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[1],"line_number":[121]},"Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[2],"line_number":[87]},"clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI":{"file_name":["client.py"],"function_name":[""],"function_offset":[70],"line_number":[71]},"DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg":{"file_name":["parser.py"],"function_name":[""],"function_offset":[7],"line_number":[12]},"RY-vzTa9LfseI7kmcIcbgQAAAAAAAABe":{"file_name":["feedparser.py"],"function_name":[""],"function_offset":[28],"line_number":[33]},"VIK6i3XoO6nxn9WkNabugAAAAAAAAAAG":{"file_name":["re.py"],"function_name":["compile"],"function_offset":[2],"line_number":[252]},"SGPpASrxkViIc4Sq7x-WYQAAAAAAAABs":{"file_name":["re.py"],"function_name":["_compile"],"function_offset":[15],"line_number":[304]},"9xG1GRY3A4PQMfXDNvrOxQAAAAAAAAAk":{"file_name":["sre_compile.py"],"function_name":["compile"],"function_offset":[9],"line_number":[768]},"4xH83ZXxs_KV95Ur8Z59WQAAAAAAAAAY":{"file_name":["sre_compile.py"],"function_name":["_code"],"function_offset":[6],"line_number":[604]},"PWlQ4X4jsNu5q7FFJqlo_QAAAAAAAAAE":{"file_name":["sre_compile.py"],"function_name":["_compile_info"],"function_offset":[4],"line_number":[540]},"LSxiso_u1cO_pWDBw25EggAAAAAAAAAc":{"file_name":["sre_parse.py"],"function_name":["getwidth"],"function_offset":[5],"line_number":[179]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAJ_G":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAMMM":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAIn0":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAExO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAJ2C":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"780bLUPADqfQ3x1T5lnVOgAAAAAAABrO":{"file_name":["emr.py"],"function_name":[""],"function_offset":[42],"line_number":[43]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"f3fxdcTCg7rbloZ6VtA0_QAAAAAAALKS":{"file_name":["hbase.py"],"function_name":[""],"function_offset":[96],"line_number":[97]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAFBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAMKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"uU7rISh8R_xr6YYB3RgLuAAAAAAAACpG":{"file_name":["s3.py"],"function_name":[""],"function_offset":[38],"line_number":[39]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAALFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"vQQdLrWHLywJs9twt3EH2QAAAAAAAKAW":{"file_name":["subcommands.py"],"function_name":[""],"function_offset":[833],"line_number":[834]},"PUIH740KQXWx70DXM4ZvgQAAAAAAABoW":{"file_name":["s3handler.py"],"function_name":[""],"function_offset":[273],"line_number":[274]},"dsOcslker2-lnNTIC5yERAAAAAAAAJig":{"file_name":["results.py"],"function_name":[""],"function_offset":[550],"line_number":[551]},"zUlsQG278t98_u2KV_JLSQAAAAAAAIoK":{"file_name":["results.py"],"function_name":["_create_new_result_cls"],"function_offset":[10],"line_number":[48]},"vkeP2ntYyoFN0A16x9eliwAAAAAAADPE":{"file_name":["__init__.py"],"function_name":["namedtuple"],"function_offset":[164],"line_number":[512]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAANFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"2L4SW1rQgEVXRj3pZAI3nQAAAAAAADBq":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[97],"line_number":[98]},"7bd6QJSfWZZfOOpDMHqLMAAAAAAAAJp6":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[319],"line_number":[320]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFOq":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"ZPxtkRXufuVf4tqV5k5k2QAAAAAAAGcA":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1097]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAAKD4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAACAK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAAKYk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAANee":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAIW-":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAAJj8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"fj70ljef7nDHOqVJGSIoEQAAAAAAAIMS":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAANBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAELi":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ik6PIX946fW_erE7uBJlVQAAAAAAAHLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAABFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"7bd6QJSfWZZfOOpDMHqLMAAAAAAAABo6":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[319],"line_number":[320]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHpK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"lOUbi56SanKTCh9Y7fIwDwAAAAAAAI2g":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1099]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAAHs4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAAEbK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAABUk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAANQO":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAEpu":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAALn8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"J3wpF3Lf_vPkis4aNGKFbwAAAAAAAOqC":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"zo4mnjDJ1PlZka7jS9k2BAAAAAAAAPu-":{"file_name":["ssl.py"],"function_name":[""],"function_offset":[780],"line_number":[781]},"J1eggTwSzYdi9OsSu1q37gAAAAAAABC4":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"0S3htaCNkzxOYeavDR1GTQAAAAAAAC54":{"file_name":["_bootstrap.py"],"function_name":["module_from_spec"],"function_offset":[14],"line_number":[580]},"rBzW547V0L_mH4nnWK1FUQAAAAAAAMtg":{"file_name":["_bootstrap_external.py"],"function_name":["create_module"],"function_offset":[6],"line_number":[1237]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAJtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAJN2":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-lookup.c"],"function_name":["do_lookup_x"],"function_offset":[],"line_number":[420]},"zjk1GYHhesH1oTuILj3ToAAAAAAAAABI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[12],"line_number":[13]},"qkYSh95E1urNTie_gKbr7wAAAAAAAABY":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[11],"line_number":[12]},"V8ldXm9NGXsJ182jEHEsUwAAAAAAAAB8":{"file_name":["connection.py"],"function_name":[""],"function_offset":[14],"line_number":[15]},"xVaa0cBWNcFeS-8zFezQgAAAAAAAAABI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[7],"line_number":[8]},"UBINlIxj95Sa_x2_k5IddAAAAAAAAAB4":{"file_name":["ssl_.py"],"function_name":[""],"function_offset":[16],"line_number":[17]},"gRRk0W_9P4SGZLXFJ5KU8QAAAAAAAAEU":{"file_name":["url.py"],"function_name":[""],"function_offset":[61],"line_number":[62]},"9xG1GRY3A4PQMfXDNvrOxQAAAAAAAAAU":{"file_name":["sre_compile.py"],"function_name":["compile"],"function_offset":[5],"line_number":[764]},"cbxfeE2AkqKne6oKUxdB6gAAAAAAAAAy":{"file_name":["sre_parse.py"],"function_name":["parse"],"function_offset":[11],"line_number":[948]},"aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy":{"file_name":["sre_parse.py"],"function_name":["_parse_sub"],"function_offset":[8],"line_number":[443]},"MebnOxK5WOhP29sl19JefwAAAAAAAAua":{"file_name":["sre_parse.py"],"function_name":["_parse"],"function_offset":[341],"line_number":[834]},"MebnOxK5WOhP29sl19JefwAAAAAAAAVQ":{"file_name":["sre_parse.py"],"function_name":["_parse"],"function_offset":[171],"line_number":[664]},"iLW1ehST1pGQ3S8RoqM9QgAAAAAAAAAY":{"file_name":["sre_parse.py"],"function_name":["__getitem__"],"function_offset":[2],"line_number":[166]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAE3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAISm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"9NWoah56eYULAP_zGE9PuwAAAAAAALHC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[101],"line_number":[102]},"IKrIDHd5n47PpDQsRXxvvgAAAAAAACmC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[81],"line_number":[82]},"oG7568kMJujZxPJfj7VMjAAAAAAAANNm":{"file_name":["frontend.py"],"function_name":[""],"function_offset":[390],"line_number":[391]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAEFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"ew01Dk0sWZctP-VaEpavqQAAAAAAoBCe":{"file_name":[],"function_name":["async_page_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAABnSX":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAItq_":{"file_name":[],"function_name":["handle_mm_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAItGv":{"file_name":[],"function_name":["__handle_mm_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAIAbE":{"file_name":[],"function_name":["__lru_cache_add"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAIAXX":{"file_name":[],"function_name":["pagevec_lru_move_fn"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAIAH6":{"file_name":[],"function_name":["release_pages"],"function_offset":[],"line_number":[]},"OlTvyWQFXjOweJcs3kiGygAAAAAAAFIS":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[155],"line_number":[156]},"N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAIB2":{"file_name":["connection.py"],"function_name":[""],"function_offset":[87],"line_number":[88]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAItm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"1eW8DnM19kiBGqMWGVkHPAAAAAAAACJC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[23],"line_number":[24]},"2kgk5qEgdkkSXT9cIdjqxQAAAAAAAMyi":{"file_name":["ssl_.py"],"function_name":[""],"function_offset":[258],"line_number":[259]},"MsEmysGbXhMvgdbwhcZDCgAAAAAAAJWM":{"file_name":["url.py"],"function_name":[""],"function_offset":[238],"line_number":[239]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAOSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAOJC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAAIpU":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAAAHY":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAOcu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAANh4":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"A2oiHVwisByxRn5RDT4LjAAAAAAAI1Zm":{"file_name":[],"function_name":["__x64_sys_munmap"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAI1XX":{"file_name":[],"function_name":["__vm_munmap"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAI1Ny":{"file_name":[],"function_name":["__do_munmap"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAIy8h":{"file_name":[],"function_name":["remove_vma"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJu9M":{"file_name":[],"function_name":["kmem_cache_free"],"function_offset":[],"line_number":[]},"rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACE":{"file_name":["compat.py"],"function_name":[""],"function_offset":[15],"line_number":[29]},"iwnHqwtnoHjA-XW01rxhpwAAAAAAAAAQ":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[2],"line_number":[16]},"53nvYhJfd2eJh-qREaeFBQAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[7]},"zwRZ32H5_95LpRJHzXkqVAAAAAAAAAAI":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[7],"line_number":[10]},"JJab8JrsPDK66yfOtCG3zQAAAAAAAAAQ":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[2],"line_number":[3]},"1XUiDryPjyncBxkTlbVecgAAAAAAAAAU":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[9],"line_number":[10]},"OIy8IFqaTWz5UoN3FSH-wQAAAAAAAABc":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[37],"line_number":[41]},"2L4SW1rQgEVXRj3pZAI3nQAAAAAAAJKK":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[97],"line_number":[98]},"7bd6QJSfWZZfOOpDMHqLMAAAAAAAAIQq":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[319],"line_number":[320]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADpK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"lOUbi56SanKTCh9Y7fIwDwAAAAAAAE2g":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1099]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAADs4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAAAbK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAANUk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAAJQO":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAApu":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAAHn8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"J3wpF3Lf_vPkis4aNGKFbwAAAAAAAFAy":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAAmC":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"A2oiHVwisByxRn5RDT4LjAAAAAAAIs9a":{"file_name":[],"function_name":["__handle_mm_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJk1L":{"file_name":[],"function_name":["alloc_pages_vma"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJNfz":{"file_name":[],"function_name":["__alloc_pages_nodemask"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJJpI":{"file_name":[],"function_name":["get_page_from_freelist"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJGV5":{"file_name":[],"function_name":["prep_new_page"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAgURG":{"file_name":[],"function_name":["clear_page_erms"],"function_offset":[],"line_number":[]},"RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAY":{"file_name":["feedparser.py"],"function_name":[""],"function_offset":[21],"line_number":[26]},"-gq3a70QOgdn9HetYyf2OgAAAAAAAACy":{"file_name":["errors.py"],"function_name":[""],"function_offset":[45],"line_number":[50]}},"executables":{"G68hjsyagwq6LpWrMjDdng":"libpython3.9.so.1.0","B8JRxL079xbhqQBqGvksAg":"kubelet","6kzBY4yj-1Fh1NCTZA3z0w":"aws-k8s-agent","j8DVIOTu7Btj9lgFefJ84A":"dockerd","B56YkhsK1JwqD-8F8sjS3A":"prometheus","v6HIzNa4K6G4nRP9032RIA":"dockerd","FWZ9q3TQKZZok58ua1HDsg":"pf-debug-metadata-service","gNW12BepH17pXwK-ZuYt3w":"node_exporter","piWSMQrh4r040D0BPNaJvw":"vmlinux","kajOqZqz7V1y0BdYQLFQrw":"containerd-shim-runc-v2","A2oiHVwisByxRn5RDT4LjA":"vmlinux","MNBJ5seVz_ocW6tcr1HSmw":"metricbeat","-pk6w5puGcp-wKnQ61BZzQ":"kubelet","QvG8QEGAld88D676NL_Y2Q":"filebeat","6auiCMWq5cA-hAbqSYvdQQ":"kubelet","ew01Dk0sWZctP-VaEpavqQ":"vmlinux","JsObMPhfT_zO2Q_B1cPLxA":"coredns","SbPwzb_Kog2bWn8uc7xhDQ":"aws","Z_CHd3Zjsh2cWE2NSdbiNQ":"libc-2.26.so","xLxcEbwnZ5oNrk99ZsxcSQ":"libpython3.11.so.1.0","9LzzIocepYcOjnUsLlgOjg":"vmlinux","eOfhJQFIxbIEScd007tROw":"libpthread-2.26.so","-p9BlJh9JZMPPNjY_j92ng":"awsagent","9HZ7GQCC6G9fZlRD7aGzXQ":"libssl.so.1.0.2k","huWyXZbCBWCe2ZtK9BiokQ":"libcrypto.so.1.0.2k","5OhlekN4HU3KaqhG_GtinA":"ena","R3YNZBiWt7Z3ZpFfTh6XyQ":"ena","WpYcHtr4qx88B8CBJZ2GTw":"aws","-Z7SlEXhuy5tL2BF-xmy3g":"libpython3.11.so.1.0","-SVIyCZG9IbFKK-fe2Wh4g":"cluster-autoscaler","EX9l-cE0x8X9W8uz4iKUfw":"zlib.cpython-39-x86_64-linux-gnu.so","jaBVtokSUzfS97d-XKjijg":"libz.so.1","PVZV2uq5ZRt-FFaczL10BA":"libdl-2.26.so","3nN3bymnZ8E42aLEtgglmA":"ld-2.26.so","ASi9f26ltguiwFajNwOaZw":"zlib.cpython-311-x86_64-linux-gnu.so"},"total_frames":172380,"sampling_rate":0.2} diff --git a/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_604800s_625x.json b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_604800s_625x.json new file mode 100644 index 0000000000000..75ad39e9298d2 --- /dev/null +++ b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_604800s_625x.json @@ -0,0 +1 @@ +{"stack_trace_events":{"oxpVfjjIF44Ceg6SK1UUdQ":43,"JTDxAdxqnTYIS6qzFXvK3g":100,"5tZzmji29IcMEbLCg170Tw":294,"0CNUMdOdpmKJxWeUmvWvXg":1343,"9_06LL00QkYIeiFNCWu0XQ":1109,"OtKh8npcfHhiQ7ynFMPOeQ":622,"TCJ8_VmEK5hAZOYdmPHyug":487,"OCdksb_5DbnTD8RB0r1Hmw":460,"2Ov4wSepfExdnFvsJSSjog":411,"668oRSTLMVtOeHPjJ80fWg":574,"VmRA1Zd-R_saxzv9stOlrw":519,"u31aX9a6CI2OuomWQHSx1Q":614,"oHTQoPZFXrc9eFjCRWW_BA":570,"tIRMz0rwuOf8rRZlytIuAQ":481,"-s21TvA-EsTWbfCutQG83Q":528,"LuHRiiYB6iq-QXoGUFYVXA":457,"5oh0023XVeE3U9ZP60NzUA":505,"hecRkAhRG62NML7wI512zA":286,"P-5EQ3lfGgit0Oj6qTKYqw":210,"fRxnoZgNqB73ndCJkUzrxg":263,"iww2NcKTwMO4dUHXUrsfKA":297,"dP8WPiIXitz7dopr2cbyrg":302,"c84Ph1EEsEpt9KFMdSQvtA":307,"DkjcsUWzUMWlzGIG7vWPLA":251,"O7XAt57p5nvwpgeB2KrNbw":312,"Oam9nmQfwQpA_10YTKZCkg":255,"gM71DK9QAb25Em9dhlNNXA":231,"VoyVx3eKZvx3I7o9LV75WA":180,"6MfMhGSHuQ0CLUxktz5OVg":175,"9pWzAEbyffmwRrKvRecyaQ":174,"DK4Iffrk3v05Awun60ygow":152,"4r_hCJ268ciweOwgH0Qwzw":129,"VC42Hg55_L_IfaF_actjIw":104,"7l18-g5emVzljYbZzZJDRA":62,"PkHiro08_uzuUWpeantpNA":42,"9EcGjMrQwznPlnAdDi9Lxw":38,"tagsGmBta7BnDHBzEbH9eQ":27,"euPXE4-KNZJD0T6j_TMfYw":24,"cL14TWzNnz1qK2PUYdE9bg":20,"9wXZUZEeGMQm83C5yXCZ2g":15,"bz1cYNqu8MBH2xCXTMEiAg":16,"fCScXsJaisrZL_JXgS4qQg":33,"V-MDb_Yh073ps9Vw4ypmDQ":17,"wAujHiFN47_oNUI63d6EtA":26,"zMMsPlSW5HOq5bsuVRh3KA":6,"pLdowTKUS5KSwivHyl5AgA":10,"_ef-NJahpYK_FzFC-KdtYQ":11,"omG-i9KffSi3YT8q0rYOiw":3,"XiONbb-veQ1sAuFD6_Fv0A":12,"krdohOL0KiVMtm4q-6fmjg":8,"N2LqhupgLi4T_B9D7JaDDQ":6,"7TvODt8WtQ5KXTmYPsDI3A":5,"u1L6jqeUaTNx1a2aJ9yFwA":2,"8uzy4VW9n0Z8KokUdeadfg":2,"EeUwhr9vbcywMBkIYZRfCw":3,"x443zjuudYI-A7cRu2DIGg":3,"rrrvnakD3SpJqProBGqoCQ":3,"sDfHX0MKzztQSqC8kl_-sg":2,"WmwSnxyphedkasVyGbhNdg":3,"NU5so_CJJJwGJM_hiEcxgQ":1,"A9B6bwuKQl9pC0MIYqtAgg":1,"X86DUuQ7tHAxGBaWu4tZLg":4,"T3fWxJzHMwU-oUs7rgXCcg":2,"vq75CDVua5N-eDXnfyZYMA":2,"oKVObqTWF9QIjxgKf8UkTw":6,"DaDdc6eLo0hc-QxL2XQh5Q":3,"YRZbUV2DChD6dl3Y2xjF8g":1,"EnsO3_jc7LnLdUHQbwkxMg":1,"V2XOOBv96QfYXHIIY7_OLA":6,"FTJM3wsT8Kc-UaiIK2yDMQ":4,"ivbgd9hswtvZ7aTts7HESw":3,"yXsgvY1JyekwdCV5rJdspg":7,"_TjN4epIphuKUiHZJZdqxQ":3,"ZQdwkmvvmLjNzNpTA4PPhw":8,"ssC7MBcE9kfM3yTim7UrNQ":12,"-yH5iqJp4uVN6clNHuFusA":7,"SrSwvDbs2pmPg3SRfXJBCA":13,"n5nFiHsDS01AKuzFKvQXdA":4,"XbtNNAnLtuHwAR-P2ynwqA":4,"Rr1Z3cNxrq9AQiD8wZZ1dA":9,"gESQTq4qRn3wnW-FPfxOfA":7,"CSpdzACT53hVs5DyKY8X5A":5,"AlH3zgnqwh5sdMMzX8AXxg":6,"ysEqok7gFOl9eLMLBwFm1g":3,"7B48NKNivOFEka6-8dK3Qg":1,"OC533YmmMZSw8TjJz41YiQ":1,"X6-W250nbzzPy4NasjncWg":1,"gi6S4ODPtJ-ERYxlMd4WHA":2,"EGm59IOxpyqZq7sEwgZb1g":1,"y7cw8NxReMWOs4KtDlMCFA":1,"L1ZLG1mjktr2Zy0xiQnH0w":1},"stack_traces":{"oxpVfjjIF44Ceg6SK1UUdQ":{"address_or_lines":[2357],"file_ids":["edNJ10OjHiWc5nzuTQdvig"],"frame_ids":["edNJ10OjHiWc5nzuTQdvigAAAAAAAAk1"],"type_ids":[3]},"JTDxAdxqnTYIS6qzFXvK3g":{"address_or_lines":[4636840,4373888],"file_ids":["LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g"],"frame_ids":["LvhLWomlc0dSPYzQ8C620gAAAAAARsCo","LvhLWomlc0dSPYzQ8C620gAAAAAAQr2A"],"type_ids":[3,3]},"5tZzmji29IcMEbLCg170Tw":{"address_or_lines":[18425733,18110445,18122515],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGSeF","j8DVIOTu7Btj9lgFefJ84AAAAAABFFft","j8DVIOTu7Btj9lgFefJ84AAAAAABFIcT"],"type_ids":[3,3,3]},"0CNUMdOdpmKJxWeUmvWvXg":{"address_or_lines":[32434917,32101228,32115955,32118104],"file_ids":["QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q"],"frame_ids":["QvG8QEGAld88D676NL_Y2QAAAAAB7url","QvG8QEGAld88D676NL_Y2QAAAAAB6dNs","QvG8QEGAld88D676NL_Y2QAAAAAB6gzz","QvG8QEGAld88D676NL_Y2QAAAAAB6hVY"],"type_ids":[3,3,3,3]},"9_06LL00QkYIeiFNCWu0XQ":{"address_or_lines":[4643592,4325284,4339923,4341903,4293837],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARtsI","B8JRxL079xbhqQBqGvksAgAAAAAAQf-k","B8JRxL079xbhqQBqGvksAgAAAAAAQjjT","B8JRxL079xbhqQBqGvksAgAAAAAAQkCP","B8JRxL079xbhqQBqGvksAgAAAAAAQYTN"],"type_ids":[3,3,3,3,3]},"OtKh8npcfHhiQ7ynFMPOeQ":{"address_or_lines":[4643458,4477392,4476996,4475762,4469018,4457110],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARtqC","B8JRxL079xbhqQBqGvksAgAAAAAARFHQ","B8JRxL079xbhqQBqGvksAgAAAAAARFBE","B8JRxL079xbhqQBqGvksAgAAAAAAREty","B8JRxL079xbhqQBqGvksAgAAAAAARDEa","B8JRxL079xbhqQBqGvksAgAAAAAARAKW"],"type_ids":[3,3,3,3,3,3]},"TCJ8_VmEK5hAZOYdmPHyug":{"address_or_lines":[4652224,11517676,25223155,25230084,11538500,11501274,4847689],"file_ids":["wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw"],"frame_ids":["wfA2BgwfDNXUWsxkJ083RwAAAAAARvzA","wfA2BgwfDNXUWsxkJ083RwAAAAAAr77s","wfA2BgwfDNXUWsxkJ083RwAAAAABgN_z","wfA2BgwfDNXUWsxkJ083RwAAAAABgPsE","wfA2BgwfDNXUWsxkJ083RwAAAAAAsBBE","wfA2BgwfDNXUWsxkJ083RwAAAAAAr37a","wfA2BgwfDNXUWsxkJ083RwAAAAAASfhJ"],"type_ids":[3,3,3,3,3,3,3]},"OCdksb_5DbnTD8RB0r1Hmw":{"address_or_lines":[18515232,25399653,25432667,25428452,25361060,18103588,18097915,18123257],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABg5Fl","v6HIzNa4K6G4nRP9032RIAAAAAABhBJb","v6HIzNa4K6G4nRP9032RIAAAAAABhAHk","v6HIzNa4K6G4nRP9032RIAAAAAABgvqk","v6HIzNa4K6G4nRP9032RIAAAAAABFD0k","v6HIzNa4K6G4nRP9032RIAAAAAABFCb7","v6HIzNa4K6G4nRP9032RIAAAAAABFIn5"],"type_ids":[3,3,3,3,3,3,3,3]},"2Ov4wSepfExdnFvsJSSjog":{"address_or_lines":[4654944,15291206,14341928,15275435,15271933,15288920,9572292,9504548,5043327],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6Qf9","FWZ9q3TQKZZok58ua1HDsgAAAAAA6UpY","FWZ9q3TQKZZok58ua1HDsgAAAAAAkg_E","FWZ9q3TQKZZok58ua1HDsgAAAAAAkQck","FWZ9q3TQKZZok58ua1HDsgAAAAAATPR_"],"type_ids":[3,3,3,3,3,3,3,3,3]},"668oRSTLMVtOeHPjJ80fWg":{"address_or_lines":[4654944,15291206,14341928,15275435,15271933,15288920,9572292,9506710,10521925,4547584],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6Qf9","FWZ9q3TQKZZok58ua1HDsgAAAAAA6UpY","FWZ9q3TQKZZok58ua1HDsgAAAAAAkg_E","FWZ9q3TQKZZok58ua1HDsgAAAAAAkQ-W","FWZ9q3TQKZZok58ua1HDsgAAAAAAoI1F","FWZ9q3TQKZZok58ua1HDsgAAAAAARWQA"],"type_ids":[3,3,3,3,3,3,3,3,3,3]},"VmRA1Zd-R_saxzv9stOlrw":{"address_or_lines":[4650848,9850853,9880398,9883181,9807044,9827268,9781937,9782483,9784009,9784300,9829781],"file_ids":["QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg"],"frame_ids":["QaIvzvU8UoclQMd_OMt-PgAAAAAARvdg","QaIvzvU8UoclQMd_OMt-PgAAAAAAlk_l","QaIvzvU8UoclQMd_OMt-PgAAAAAAlsNO","QaIvzvU8UoclQMd_OMt-PgAAAAAAls4t","QaIvzvU8UoclQMd_OMt-PgAAAAAAlaTE","QaIvzvU8UoclQMd_OMt-PgAAAAAAlfPE","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUKx","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUTT","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUrJ","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUvs","QaIvzvU8UoclQMd_OMt-PgAAAAAAlf2V"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3]},"u31aX9a6CI2OuomWQHSx1Q":{"address_or_lines":[4652224,22357367,22385134,22366798,57080079,58879477,58676957,58636100,58650141,31265796,7372663,7364083],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZvkP","B8JRxL079xbhqQBqGvksAgAAAAADgm31","B8JRxL079xbhqQBqGvksAgAAAAADf1bd","B8JRxL079xbhqQBqGvksAgAAAAADfrdE","B8JRxL079xbhqQBqGvksAgAAAAADfu4d","B8JRxL079xbhqQBqGvksAgAAAAAB3RQE","B8JRxL079xbhqQBqGvksAgAAAAAAcH93","B8JRxL079xbhqQBqGvksAgAAAAAAcF3z"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3]},"oHTQoPZFXrc9eFjCRWW_BA":{"address_or_lines":[4646312,4475111,4248744,4416245,4662882,10485923,16807,1222099,1219772,1208264,769619,768516,8542429],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARuWo","FWZ9q3TQKZZok58ua1HDsgAAAAAAREjn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQNSo","FWZ9q3TQKZZok58ua1HDsgAAAAAAQ2L1","FWZ9q3TQKZZok58ua1HDsgAAAAAARyZi","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAEqXT","ew01Dk0sWZctP-VaEpavqQAAAAAAEpy8","ew01Dk0sWZctP-VaEpavqQAAAAAAEm_I","ew01Dk0sWZctP-VaEpavqQAAAAAAC75T","ew01Dk0sWZctP-VaEpavqQAAAAAAC7oE","ew01Dk0sWZctP-VaEpavqQAAAAAAgljd"],"type_ids":[3,3,3,3,3,4,4,4,4,4,4,4,4]},"tIRMz0rwuOf8rRZlytIuAQ":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10733159,10734948,4245427,4255110,4288384],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Zn","FWZ9q3TQKZZok58ua1HDsgAAAAAAo81k","FWZ9q3TQKZZok58ua1HDsgAAAAAAQMez","FWZ9q3TQKZZok58ua1HDsgAAAAAAQO2G","FWZ9q3TQKZZok58ua1HDsgAAAAAAQW-A"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"-s21TvA-EsTWbfCutQG83Q":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10733159,10733818,10618404,10387225,4547736,4658752],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Zn","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8j6","FWZ9q3TQKZZok58ua1HDsgAAAAAAogYk","FWZ9q3TQKZZok58ua1HDsgAAAAAAnn8Z","FWZ9q3TQKZZok58ua1HDsgAAAAAARWSY","FWZ9q3TQKZZok58ua1HDsgAAAAAARxZA"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"LuHRiiYB6iq-QXoGUFYVXA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41428636,40303236,22534565,19333914,19319593],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCac","v6HIzNa4K6G4nRP9032RIAAAAAACZvqE","v6HIzNa4K6G4nRP9032RIAAAAAABV9ml","v6HIzNa4K6G4nRP9032RIAAAAAABJwMa","v6HIzNa4K6G4nRP9032RIAAAAAABJssp"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"5oh0023XVeE3U9ZP60NzUA":{"address_or_lines":[4610335,4610076,4612877,4490724,4492388,4499312,4241704,4392309,4610754,10485923,16807,1221667,1219340,1207832,769603,768500,8537181],"file_ids":["kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["kajOqZqz7V1y0BdYQLFQrwAAAAAARlkf","kajOqZqz7V1y0BdYQLFQrwAAAAAARlgc","kajOqZqz7V1y0BdYQLFQrwAAAAAARmMN","kajOqZqz7V1y0BdYQLFQrwAAAAAARIXk","kajOqZqz7V1y0BdYQLFQrwAAAAAARIxk","kajOqZqz7V1y0BdYQLFQrwAAAAAARKdw","kajOqZqz7V1y0BdYQLFQrwAAAAAAQLko","kajOqZqz7V1y0BdYQLFQrwAAAAAAQwV1","kajOqZqz7V1y0BdYQLFQrwAAAAAARlrC","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAEqQj","9LzzIocepYcOjnUsLlgOjgAAAAAAEpsM","9LzzIocepYcOjnUsLlgOjgAAAAAAEm4Y","9LzzIocepYcOjnUsLlgOjgAAAAAAC75D","9LzzIocepYcOjnUsLlgOjgAAAAAAC7n0","9LzzIocepYcOjnUsLlgOjgAAAAAAgkRd"],"type_ids":[3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"hecRkAhRG62NML7wI512zA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000356,39998369,27959205,27961373,27940684],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYltk","v6HIzNa4K6G4nRP9032RIAAAAAACYlOh","v6HIzNa4K6G4nRP9032RIAAAAAABqp-l","v6HIzNa4K6G4nRP9032RIAAAAAABqqgd","v6HIzNa4K6G4nRP9032RIAAAAAABqldM"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"P-5EQ3lfGgit0Oj6qTKYqw":{"address_or_lines":[43732576,69263145,69263545,54339630,54340167,54179273,54179969,54177426,50376971,50377819,50384113,50377819,43742470,43723999,43620502,43619092,43672236,43616946,43623742],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIN8p","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIOC5","MNBJ5seVz_ocW6tcr1HSmwAAAAADPSgu","MNBJ5seVz_ocW6tcr1HSmwAAAAADPSpH","MNBJ5seVz_ocW6tcr1HSmwAAAAADOrXJ","MNBJ5seVz_ocW6tcr1HSmwAAAAADOriB","MNBJ5seVz_ocW6tcr1HSmwAAAAADOq6S","MNBJ5seVz_ocW6tcr1HSmwAAAAADALEL","MNBJ5seVz_ocW6tcr1HSmwAAAAADALRb","MNBJ5seVz_ocW6tcr1HSmwAAAAADAMzx","MNBJ5seVz_ocW6tcr1HSmwAAAAADALRb","MNBJ5seVz_ocW6tcr1HSmwAAAAACm3UG","MNBJ5seVz_ocW6tcr1HSmwAAAAACmyzf","MNBJ5seVz_ocW6tcr1HSmwAAAAACmZiW","MNBJ5seVz_ocW6tcr1HSmwAAAAACmZMU","MNBJ5seVz_ocW6tcr1HSmwAAAAACmmKs","MNBJ5seVz_ocW6tcr1HSmwAAAAACmYqy","MNBJ5seVz_ocW6tcr1HSmwAAAAACmaU-"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"fRxnoZgNqB73ndCJkUzrxg":{"address_or_lines":[4652224,22354871,22382638,22364302,56669071,58509234,58268669,58227812,58241853,31197476,7372432,7294909,7296733,7300250,7296676,7304324,7296733,7300250,7296901,7319678],"file_ids":["-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ"],"frame_ids":["-pk6w5puGcp-wKnQ61BZzQAAAAAARvzA","-pk6w5puGcp-wKnQ61BZzQAAAAABVRu3","-pk6w5puGcp-wKnQ61BZzQAAAAABVYgu","-pk6w5puGcp-wKnQ61BZzQAAAAABVUCO","-pk6w5puGcp-wKnQ61BZzQAAAAADYLOP","-pk6w5puGcp-wKnQ61BZzQAAAAADfMey","-pk6w5puGcp-wKnQ61BZzQAAAAADeRv9","-pk6w5puGcp-wKnQ61BZzQAAAAADeHxk","-pk6w5puGcp-wKnQ61BZzQAAAAADeLM9","-pk6w5puGcp-wKnQ61BZzQAAAAAB3Akk","-pk6w5puGcp-wKnQ61BZzQAAAAAAcH6Q","-pk6w5puGcp-wKnQ61BZzQAAAAAAb0-9","-pk6w5puGcp-wKnQ61BZzQAAAAAAb1bd","-pk6w5puGcp-wKnQ61BZzQAAAAAAb2Sa","-pk6w5puGcp-wKnQ61BZzQAAAAAAb1ak","-pk6w5puGcp-wKnQ61BZzQAAAAAAb3SE","-pk6w5puGcp-wKnQ61BZzQAAAAAAb1bd","-pk6w5puGcp-wKnQ61BZzQAAAAAAb2Sa","-pk6w5puGcp-wKnQ61BZzQAAAAAAb1eF","-pk6w5puGcp-wKnQ61BZzQAAAAAAb7B-"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"iww2NcKTwMO4dUHXUrsfKA":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54557252,54545733,54548081,54524484,54525381,54528745,54499864,54500494,54477482,44043537,44060985,43329158,43326819],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHpE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQE1F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQFZx","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_pE","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_3F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQArp","MNBJ5seVz_ocW6tcr1HSmwAAAAADP5oY","MNBJ5seVz_ocW6tcr1HSmwAAAAADP5yO","MNBJ5seVz_ocW6tcr1HSmwAAAAADP0Kq","MNBJ5seVz_ocW6tcr1HSmwAAAAACoA0R","MNBJ5seVz_ocW6tcr1HSmwAAAAACoFE5","MNBJ5seVz_ocW6tcr1HSmwAAAAAClSaG","MNBJ5seVz_ocW6tcr1HSmwAAAAAClR1j"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"dP8WPiIXitz7dopr2cbyrg":{"address_or_lines":[4652224,59362286,59048854,59078134,59085018,59179681,31752932,6709512,4951332,4960314,4742003,4757981,4219698,4219725,10485923,16807,2741196,2827770,2817684,2805156,3383048,8438368],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAADicvu","B8JRxL079xbhqQBqGvksAgAAAAADhQOW","B8JRxL079xbhqQBqGvksAgAAAAADhXX2","B8JRxL079xbhqQBqGvksAgAAAAADhZDa","B8JRxL079xbhqQBqGvksAgAAAAADhwKh","B8JRxL079xbhqQBqGvksAgAAAAAB5ILk","B8JRxL079xbhqQBqGvksAgAAAAAAZmEI","B8JRxL079xbhqQBqGvksAgAAAAAAS40k","B8JRxL079xbhqQBqGvksAgAAAAAAS7A6","B8JRxL079xbhqQBqGvksAgAAAAAASFtz","B8JRxL079xbhqQBqGvksAgAAAAAASJnd","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM","A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6","A2oiHVwisByxRn5RDT4LjAAAAAAAKv6U","A2oiHVwisByxRn5RDT4LjAAAAAAAKs2k","A2oiHVwisByxRn5RDT4LjAAAAAAAM58I","A2oiHVwisByxRn5RDT4LjAAAAAAAgMJg"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"c84Ph1EEsEpt9KFMdSQvtA":{"address_or_lines":[152249,135481,144741,190122,831754,827742,928935,925466,103752,102294,97206,439344,486674,922914,10485923,16807,2756288,2755416,2924693,3066448,4344,2925966,8437662],"file_ids":["w5zBqPf1_9mIVEf-Rn7EdA","Z_CHd3Zjsh2cWE2NSdbiNQ","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","OTWX4UsOVMrSIF5cD4zUzg","OTWX4UsOVMrSIF5cD4zUzg","OTWX4UsOVMrSIF5cD4zUzg","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","LHNvPtcKBt87cCBX8aTNhQ","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["w5zBqPf1_9mIVEf-Rn7EdAAAAAAAAlK5","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","w5zBqPf1_9mIVEf-Rn7EdAAAAAAAAjVl","w5zBqPf1_9mIVEf-Rn7EdAAAAAAAAuaq","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADLEK","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADKFe","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADiyn","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADh8a","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAZVI","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAY-W","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAXu2","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAABrQw","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB20S","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADhUi","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A","A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY","A2oiHVwisByxRn5RDT4LjAAAAAAALKCV","A2oiHVwisByxRn5RDT4LjAAAAAAALspQ","LHNvPtcKBt87cCBX8aTNhQAAAAAAABD4","A2oiHVwisByxRn5RDT4LjAAAAAAALKWO","A2oiHVwisByxRn5RDT4LjAAAAAAAgL-e"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"DkjcsUWzUMWlzGIG7vWPLA":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54556506,44024036,44026008,44007166,43828228,43837959,43282962,43282989,10485923,16807,2845749,2845580,2841596,3335577,3325166,699747],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHda","MNBJ5seVz_ocW6tcr1HSmwAAAAACn8Dk","MNBJ5seVz_ocW6tcr1HSmwAAAAACn8iY","MNBJ5seVz_ocW6tcr1HSmwAAAAACn37-","MNBJ5seVz_ocW6tcr1HSmwAAAAACnMQE","MNBJ5seVz_ocW6tcr1HSmwAAAAACnOoH","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIS","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIt","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAK2w1","A2oiHVwisByxRn5RDT4LjAAAAAAAK2uM","A2oiHVwisByxRn5RDT4LjAAAAAAAK1v8","A2oiHVwisByxRn5RDT4LjAAAAAAAMuWZ","A2oiHVwisByxRn5RDT4LjAAAAAAAMrzu","A2oiHVwisByxRn5RDT4LjAAAAAAACq1j"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"O7XAt57p5nvwpgeB2KrNbw":{"address_or_lines":[12540096,19004791,19032250,19014236,19907031,31278974,31279321,31305795,31279321,31290406,31279321,31317002,19907351,21668882,21654434,21097575,20766142,16277099,16285669,16307614,16278212,12403428,12120854,12121189,12544111],"file_ids":["67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg"],"frame_ids":["67s2TwiMngM0yin5Y8pvEgAAAAAAv1jA","67s2TwiMngM0yin5Y8pvEgAAAAABIf13","67s2TwiMngM0yin5Y8pvEgAAAAABImi6","67s2TwiMngM0yin5Y8pvEgAAAAABIiJc","67s2TwiMngM0yin5Y8pvEgAAAAABL8HX","67s2TwiMngM0yin5Y8pvEgAAAAAB3Ud-","67s2TwiMngM0yin5Y8pvEgAAAAAB3UjZ","67s2TwiMngM0yin5Y8pvEgAAAAAB3bBD","67s2TwiMngM0yin5Y8pvEgAAAAAB3UjZ","67s2TwiMngM0yin5Y8pvEgAAAAAB3XQm","67s2TwiMngM0yin5Y8pvEgAAAAAB3UjZ","67s2TwiMngM0yin5Y8pvEgAAAAAB3dwK","67s2TwiMngM0yin5Y8pvEgAAAAABL8MX","67s2TwiMngM0yin5Y8pvEgAAAAABSqQS","67s2TwiMngM0yin5Y8pvEgAAAAABSmui","67s2TwiMngM0yin5Y8pvEgAAAAABQexn","67s2TwiMngM0yin5Y8pvEgAAAAABPN2-","67s2TwiMngM0yin5Y8pvEgAAAAAA-F5r","67s2TwiMngM0yin5Y8pvEgAAAAAA-H_l","67s2TwiMngM0yin5Y8pvEgAAAAAA-NWe","67s2TwiMngM0yin5Y8pvEgAAAAAA-GLE","67s2TwiMngM0yin5Y8pvEgAAAAAAvULk","67s2TwiMngM0yin5Y8pvEgAAAAAAuPMW","67s2TwiMngM0yin5Y8pvEgAAAAAAuPRl","67s2TwiMngM0yin5Y8pvEgAAAAAAv2hv"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"Oam9nmQfwQpA_10YTKZCkg":{"address_or_lines":[4652224,58596086,58544235,10401064,10401333,10401661,58561029,58544882,58545860,58550052,58558939,56502167,58377199,58374713,5176491,5212551,5201562,5198538,12589080,12593882,12537260,12591620,12402541,12450679,4552007,4551401],"file_ids":["wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw"],"frame_ids":["wfA2BgwfDNXUWsxkJ083RwAAAAAARvzA","wfA2BgwfDNXUWsxkJ083RwAAAAADfhr2","wfA2BgwfDNXUWsxkJ083RwAAAAADfVBr","wfA2BgwfDNXUWsxkJ083RwAAAAAAnrUo","wfA2BgwfDNXUWsxkJ083RwAAAAAAnrY1","wfA2BgwfDNXUWsxkJ083RwAAAAAAnrd9","wfA2BgwfDNXUWsxkJ083RwAAAAADfZIF","wfA2BgwfDNXUWsxkJ083RwAAAAADfVLy","wfA2BgwfDNXUWsxkJ083RwAAAAADfVbE","wfA2BgwfDNXUWsxkJ083RwAAAAADfWck","wfA2BgwfDNXUWsxkJ083RwAAAAADfYnb","wfA2BgwfDNXUWsxkJ083RwAAAAADXieX","wfA2BgwfDNXUWsxkJ083RwAAAAADesPv","wfA2BgwfDNXUWsxkJ083RwAAAAADero5","wfA2BgwfDNXUWsxkJ083RwAAAAAATvyr","wfA2BgwfDNXUWsxkJ083RwAAAAAAT4mH","wfA2BgwfDNXUWsxkJ083RwAAAAAAT16a","wfA2BgwfDNXUWsxkJ083RwAAAAAAT1LK","wfA2BgwfDNXUWsxkJ083RwAAAAAAwBgY","wfA2BgwfDNXUWsxkJ083RwAAAAAAwCra","wfA2BgwfDNXUWsxkJ083RwAAAAAAv02s","wfA2BgwfDNXUWsxkJ083RwAAAAAAwCIE","wfA2BgwfDNXUWsxkJ083RwAAAAAAvT9t","wfA2BgwfDNXUWsxkJ083RwAAAAAAvft3","wfA2BgwfDNXUWsxkJ083RwAAAAAARXVH","wfA2BgwfDNXUWsxkJ083RwAAAAAARXLp"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"gM71DK9QAb25Em9dhlNNXA":{"address_or_lines":[4602912,7755816,7756100,7759920,7760733,7744869,8376791,8749164,8618561,8132341,8137261,8133828,8067381,8671283,5977431,5085785,5087348,4663256,4670457,4680028,4694485,10485923,16807,2795169,2795020,2794811,2794363],"file_ids":["kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","kajOqZqz7V1y0BdYQLFQrw","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["kajOqZqz7V1y0BdYQLFQrwAAAAAARjwg","kajOqZqz7V1y0BdYQLFQrwAAAAAAdlgo","kajOqZqz7V1y0BdYQLFQrwAAAAAAdllE","kajOqZqz7V1y0BdYQLFQrwAAAAAAdmgw","kajOqZqz7V1y0BdYQLFQrwAAAAAAdmtd","kajOqZqz7V1y0BdYQLFQrwAAAAAAdi1l","kajOqZqz7V1y0BdYQLFQrwAAAAAAf9HX","kajOqZqz7V1y0BdYQLFQrwAAAAAAhYBs","kajOqZqz7V1y0BdYQLFQrwAAAAAAg4JB","kajOqZqz7V1y0BdYQLFQrwAAAAAAfBb1","kajOqZqz7V1y0BdYQLFQrwAAAAAAfCot","kajOqZqz7V1y0BdYQLFQrwAAAAAAfBzE","kajOqZqz7V1y0BdYQLFQrwAAAAAAexk1","kajOqZqz7V1y0BdYQLFQrwAAAAAAhFAz","kajOqZqz7V1y0BdYQLFQrwAAAAAAWzVX","kajOqZqz7V1y0BdYQLFQrwAAAAAATZpZ","kajOqZqz7V1y0BdYQLFQrwAAAAAATaB0","kajOqZqz7V1y0BdYQLFQrwAAAAAARyfY","kajOqZqz7V1y0BdYQLFQrwAAAAAAR0P5","kajOqZqz7V1y0BdYQLFQrwAAAAAAR2lc","kajOqZqz7V1y0BdYQLFQrwAAAAAAR6HV","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKqah","9LzzIocepYcOjnUsLlgOjgAAAAAAKqYM","9LzzIocepYcOjnUsLlgOjgAAAAAAKqU7","9LzzIocepYcOjnUsLlgOjgAAAAAAKqN7"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4]},"VoyVx3eKZvx3I7o9LV75WA":{"address_or_lines":[4652224,22354373,22356417,22043891,9840916,9838765,4872825,5688954,5590020,5506248,4899556,4748900,4757831,4219698,4219725,10485923,16807,2756288,2755416,2744627,6715329,7926130,7924288,7914841,6798266,6797590,6797444,2726038],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVRnF","B8JRxL079xbhqQBqGvksAgAAAAABVSHB","B8JRxL079xbhqQBqGvksAgAAAAABUFzz","B8JRxL079xbhqQBqGvksAgAAAAAAlikU","B8JRxL079xbhqQBqGvksAgAAAAAAliCt","B8JRxL079xbhqQBqGvksAgAAAAAASlp5","B8JRxL079xbhqQBqGvksAgAAAAAAVs56","B8JRxL079xbhqQBqGvksAgAAAAAAVUwE","B8JRxL079xbhqQBqGvksAgAAAAAAVATI","B8JRxL079xbhqQBqGvksAgAAAAAASsLk","B8JRxL079xbhqQBqGvksAgAAAAAASHZk","B8JRxL079xbhqQBqGvksAgAAAAAASJlH","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A","A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY","A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz","A2oiHVwisByxRn5RDT4LjAAAAAAAZnfB","A2oiHVwisByxRn5RDT4LjAAAAAAAePFy","A2oiHVwisByxRn5RDT4LjAAAAAAAeOpA","A2oiHVwisByxRn5RDT4LjAAAAAAAeMVZ","A2oiHVwisByxRn5RDT4LjAAAAAAAZ7u6","A2oiHVwisByxRn5RDT4LjAAAAAAAZ7kW","A2oiHVwisByxRn5RDT4LjAAAAAAAZ7iE","A2oiHVwisByxRn5RDT4LjAAAAAAAKZiW"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"6MfMhGSHuQ0CLUxktz5OVg":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791213,24785269,19897796,19899069,19901252,19908516,19901252,19907431,18154044,18082996],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekit","v6HIzNa4K6G4nRP9032RIAAAAAABejF1","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8Nn","v6HIzNa4K6G4nRP9032RIAAAAAABFQI8","v6HIzNa4K6G4nRP9032RIAAAAAABE-y0"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"9pWzAEbyffmwRrKvRecyaQ":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226601,40103401,19895453,19846041,19847127,19902436,19861609,19902628,19862836,19902820,19863773,19901256,19856467,19901444,19858041,18647118,18648496,18406502,18049625],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdRFp","j8DVIOTu7Btj9lgFefJ84AAAAAACY-3p","j8DVIOTu7Btj9lgFefJ84AAAAAABL5Sd","j8DVIOTu7Btj9lgFefJ84AAAAAABLtOZ","j8DVIOTu7Btj9lgFefJ84AAAAAABLtfX","j8DVIOTu7Btj9lgFefJ84AAAAAABL6_k","j8DVIOTu7Btj9lgFefJ84AAAAAABLxBp","j8DVIOTu7Btj9lgFefJ84AAAAAABL7Ck","j8DVIOTu7Btj9lgFefJ84AAAAAABLxU0","j8DVIOTu7Btj9lgFefJ84AAAAAABL7Fk","j8DVIOTu7Btj9lgFefJ84AAAAAABLxjd","j8DVIOTu7Btj9lgFefJ84AAAAAABL6tI","j8DVIOTu7Btj9lgFefJ84AAAAAABLvxT","j8DVIOTu7Btj9lgFefJ84AAAAAABL6wE","j8DVIOTu7Btj9lgFefJ84AAAAAABLwJ5","j8DVIOTu7Btj9lgFefJ84AAAAAABHIhO","j8DVIOTu7Btj9lgFefJ84AAAAAABHI2w","j8DVIOTu7Btj9lgFefJ84AAAAAABGNxm","j8DVIOTu7Btj9lgFefJ84AAAAAABE2pZ"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"DK4Iffrk3v05Awun60ygow":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41460538,41453510,39933561,34157889,34191237,32888264,25716990,34278084,34202797,25717430,25848062,25843154,25848772,25852175,25783796,25513444,25512912,32939143,32929768,24984119,18131287],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeKM6","v6HIzNa4K6G4nRP9032RIAAAAAACeIfG","v6HIzNa4K6G4nRP9032RIAAAAAACYVZ5","v6HIzNa4K6G4nRP9032RIAAAAAACCTVB","v6HIzNa4K6G4nRP9032RIAAAAAACCbeF","v6HIzNa4K6G4nRP9032RIAAAAAAB9dXI","v6HIzNa4K6G4nRP9032RIAAAAAABiGj-","v6HIzNa4K6G4nRP9032RIAAAAAACCwrE","v6HIzNa4K6G4nRP9032RIAAAAAACCeSt","v6HIzNa4K6G4nRP9032RIAAAAAABiGq2","v6HIzNa4K6G4nRP9032RIAAAAAABimj-","v6HIzNa4K6G4nRP9032RIAAAAAABilXS","v6HIzNa4K6G4nRP9032RIAAAAAABimvE","v6HIzNa4K6G4nRP9032RIAAAAAABinkP","v6HIzNa4K6G4nRP9032RIAAAAAABiW30","v6HIzNa4K6G4nRP9032RIAAAAAABhU3k","v6HIzNa4K6G4nRP9032RIAAAAAABhUvQ","v6HIzNa4K6G4nRP9032RIAAAAAAB9pyH","v6HIzNa4K6G4nRP9032RIAAAAAAB9nfo","v6HIzNa4K6G4nRP9032RIAAAAAABfTo3","v6HIzNa4K6G4nRP9032RIAAAAAABFKlX"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"4r_hCJ268ciweOwgH0Qwzw":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791191,24778097,24778417,19046138,19039453,18993092,18869484,18879802,10485923,16807,2756169,2891746,2888851],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekiX","v6HIzNa4K6G4nRP9032RIAAAAAABehVx","v6HIzNa4K6G4nRP9032RIAAAAAABehax","v6HIzNa4K6G4nRP9032RIAAAAAABIp76","v6HIzNa4K6G4nRP9032RIAAAAAABIoTd","v6HIzNa4K6G4nRP9032RIAAAAAABIc_E","v6HIzNa4K6G4nRP9032RIAAAAAABH-zs","v6HIzNa4K6G4nRP9032RIAAAAAABIBU6","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg5J","A2oiHVwisByxRn5RDT4LjAAAAAAALB_i","A2oiHVwisByxRn5RDT4LjAAAAAAALBST"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4]},"VC42Hg55_L_IfaF_actjIw":{"address_or_lines":[4652224,30971941,30986245,30988292,30990568,31382091,30723428,25540326,25548827,25550707,25503568,25504356,25481468,25481277,25484807,25485060,4951332,4960314,4742003,4757981,4219698,4219725,10485923,16743,2737420,2823946,2813708,2804875,2803431,2801020,2796664,2900191,2900031],"file_ids":["-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["-pk6w5puGcp-wKnQ61BZzQAAAAAARvzA","-pk6w5puGcp-wKnQ61BZzQAAAAAB2Jgl","-pk6w5puGcp-wKnQ61BZzQAAAAAB2NAF","-pk6w5puGcp-wKnQ61BZzQAAAAAB2NgE","-pk6w5puGcp-wKnQ61BZzQAAAAAB2ODo","-pk6w5puGcp-wKnQ61BZzQAAAAAB3tpL","-pk6w5puGcp-wKnQ61BZzQAAAAAB1M1k","-pk6w5puGcp-wKnQ61BZzQAAAAABhbbm","-pk6w5puGcp-wKnQ61BZzQAAAAABhdgb","-pk6w5puGcp-wKnQ61BZzQAAAAABhd9z","-pk6w5puGcp-wKnQ61BZzQAAAAABhSdQ","-pk6w5puGcp-wKnQ61BZzQAAAAABhSpk","-pk6w5puGcp-wKnQ61BZzQAAAAABhND8","-pk6w5puGcp-wKnQ61BZzQAAAAABhNA9","-pk6w5puGcp-wKnQ61BZzQAAAAABhN4H","-pk6w5puGcp-wKnQ61BZzQAAAAABhN8E","-pk6w5puGcp-wKnQ61BZzQAAAAAAS40k","-pk6w5puGcp-wKnQ61BZzQAAAAAAS7A6","-pk6w5puGcp-wKnQ61BZzQAAAAAASFtz","-pk6w5puGcp-wKnQ61BZzQAAAAAASJnd","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGMy","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGNN","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKcUM","piWSMQrh4r040D0BPNaJvwAAAAAAKxcK","piWSMQrh4r040D0BPNaJvwAAAAAAKu8M","piWSMQrh4r040D0BPNaJvwAAAAAAKsyL","piWSMQrh4r040D0BPNaJvwAAAAAAKsbn","piWSMQrh4r040D0BPNaJvwAAAAAAKr18","piWSMQrh4r040D0BPNaJvwAAAAAAKqx4","piWSMQrh4r040D0BPNaJvwAAAAAALEDf","piWSMQrh4r040D0BPNaJvwAAAAAALEA_"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"7l18-g5emVzljYbZzZJDRA":{"address_or_lines":[4652224,57367531,57370109,31789066,31776683,58631656,57895320,57890805,57903406,31388307,31007417,30973013,30989730,30933387,30773764,30777712,30779690,30778532,4952297,4951332,4960314,4742003,4757981,4219698,4219725,10485923,16743,2737420,2823946,2813708,2804913,2798877,3355670,8461220],"file_ids":["-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["-pk6w5puGcp-wKnQ61BZzQAAAAAARvzA","-pk6w5puGcp-wKnQ61BZzQAAAAADa1vr","-pk6w5puGcp-wKnQ61BZzQAAAAADa2X9","-pk6w5puGcp-wKnQ61BZzQAAAAAB5RAK","-pk6w5puGcp-wKnQ61BZzQAAAAAB5N-r","-pk6w5puGcp-wKnQ61BZzQAAAAADfqXo","-pk6w5puGcp-wKnQ61BZzQAAAAADc2mY","-pk6w5puGcp-wKnQ61BZzQAAAAADc1f1","-pk6w5puGcp-wKnQ61BZzQAAAAADc4ku","-pk6w5puGcp-wKnQ61BZzQAAAAAB3vKT","-pk6w5puGcp-wKnQ61BZzQAAAAAB2SK5","-pk6w5puGcp-wKnQ61BZzQAAAAAB2JxV","-pk6w5puGcp-wKnQ61BZzQAAAAAB2N2i","-pk6w5puGcp-wKnQ61BZzQAAAAAB2AGL","-pk6w5puGcp-wKnQ61BZzQAAAAAB1ZIE","-pk6w5puGcp-wKnQ61BZzQAAAAAB1aFw","-pk6w5puGcp-wKnQ61BZzQAAAAAB1akq","-pk6w5puGcp-wKnQ61BZzQAAAAAB1aSk","-pk6w5puGcp-wKnQ61BZzQAAAAAAS5Dp","-pk6w5puGcp-wKnQ61BZzQAAAAAAS40k","-pk6w5puGcp-wKnQ61BZzQAAAAAAS7A6","-pk6w5puGcp-wKnQ61BZzQAAAAAASFtz","-pk6w5puGcp-wKnQ61BZzQAAAAAASJnd","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGMy","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGNN","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKcUM","piWSMQrh4r040D0BPNaJvwAAAAAAKxcK","piWSMQrh4r040D0BPNaJvwAAAAAAKu8M","piWSMQrh4r040D0BPNaJvwAAAAAAKsyx","piWSMQrh4r040D0BPNaJvwAAAAAAKrUd","piWSMQrh4r040D0BPNaJvwAAAAAAMzQW","piWSMQrh4r040D0BPNaJvwAAAAAAgRuk"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"PkHiro08_uzuUWpeantpNA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429353,40304297,19977269,22569935,22570653,19208948,22544340,19208919,19208225,22608882,19754692,19668808,19001325,18870508,18879802,10485923,16807,2756848,2756092,2745322,6715782,6715626,7927405,7924037],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeClp","v6HIzNa4K6G4nRP9032RIAAAAAACZv6p","v6HIzNa4K6G4nRP9032RIAAAAAABMNQ1","v6HIzNa4K6G4nRP9032RIAAAAAABWGPP","v6HIzNa4K6G4nRP9032RIAAAAAABWGad","v6HIzNa4K6G4nRP9032RIAAAAAABJRr0","v6HIzNa4K6G4nRP9032RIAAAAAABV__U","v6HIzNa4K6G4nRP9032RIAAAAAABJRrX","v6HIzNa4K6G4nRP9032RIAAAAAABJRgh","v6HIzNa4K6G4nRP9032RIAAAAAABWPvy","v6HIzNa4K6G4nRP9032RIAAAAAABLW7E","v6HIzNa4K6G4nRP9032RIAAAAAABLB9I","v6HIzNa4K6G4nRP9032RIAAAAAABIe_t","v6HIzNa4K6G4nRP9032RIAAAAAABH_Ds","v6HIzNa4K6G4nRP9032RIAAAAAABIBU6","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKhDw","ew01Dk0sWZctP-VaEpavqQAAAAAAKg38","ew01Dk0sWZctP-VaEpavqQAAAAAAKePq","ew01Dk0sWZctP-VaEpavqQAAAAAAZnmG","ew01Dk0sWZctP-VaEpavqQAAAAAAZnjq","ew01Dk0sWZctP-VaEpavqQAAAAAAePZt","ew01Dk0sWZctP-VaEpavqQAAAAAAeOlF"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"9EcGjMrQwznPlnAdDi9Lxw":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791213,24785269,19897796,19899069,19901252,19908516,19901252,19908516,19901309,19904117,19988362,19897796,19899069,19901309,19904677,19901380,19901069],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekit","v6HIzNa4K6G4nRP9032RIAAAAAABejF1","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6t9","v6HIzNa4K6G4nRP9032RIAAAAAABL7Z1","v6HIzNa4K6G4nRP9032RIAAAAAABMP-K","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6t9","v6HIzNa4K6G4nRP9032RIAAAAAABL7il","v6HIzNa4K6G4nRP9032RIAAAAAABL6vE","v6HIzNa4K6G4nRP9032RIAAAAAABL6qN"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"tagsGmBta7BnDHBzEbH9eQ":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429353,40304297,19977269,22569935,22570653,19208948,22544340,19208919,19208225,22608882,19754692,19668808,19001325,18870508,18879802,10485923,16807,2756848,2756092,2745322,6715782,6715626,7927445,6732427,882422,8542429],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeClp","v6HIzNa4K6G4nRP9032RIAAAAAACZv6p","v6HIzNa4K6G4nRP9032RIAAAAAABMNQ1","v6HIzNa4K6G4nRP9032RIAAAAAABWGPP","v6HIzNa4K6G4nRP9032RIAAAAAABWGad","v6HIzNa4K6G4nRP9032RIAAAAAABJRr0","v6HIzNa4K6G4nRP9032RIAAAAAABV__U","v6HIzNa4K6G4nRP9032RIAAAAAABJRrX","v6HIzNa4K6G4nRP9032RIAAAAAABJRgh","v6HIzNa4K6G4nRP9032RIAAAAAABWPvy","v6HIzNa4K6G4nRP9032RIAAAAAABLW7E","v6HIzNa4K6G4nRP9032RIAAAAAABLB9I","v6HIzNa4K6G4nRP9032RIAAAAAABIe_t","v6HIzNa4K6G4nRP9032RIAAAAAABH_Ds","v6HIzNa4K6G4nRP9032RIAAAAAABIBU6","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKhDw","ew01Dk0sWZctP-VaEpavqQAAAAAAKg38","ew01Dk0sWZctP-VaEpavqQAAAAAAKePq","ew01Dk0sWZctP-VaEpavqQAAAAAAZnmG","ew01Dk0sWZctP-VaEpavqQAAAAAAZnjq","ew01Dk0sWZctP-VaEpavqQAAAAAAePaV","ew01Dk0sWZctP-VaEpavqQAAAAAAZrqL","ew01Dk0sWZctP-VaEpavqQAAAAAADXb2","ew01Dk0sWZctP-VaEpavqQAAAAAAgljd"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"euPXE4-KNZJD0T6j_TMfYw":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1091600,7046,2795776,1483241,1482767,2600004,1074397,11342,13400,51888,12612,2578675,2599636,1091600,7744,52134,33264,2795776,1483241,1482767,2600004,1073803,11342,13400,51888,12396,16726,41698,2852079,2851771,2850043,1501120,1495723],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","lLD39yzd4Cg8F13tcGpzGQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","dCCKy6JoX0PADOFic8hRNQ","9w9lF96vJW7ZhBoZ8ETsBw","xUQuo4OgBaS_Le-fdAwt8A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","lLD39yzd4Cg8F13tcGpzGQAAAAAAABuG","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAACxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAADRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAMqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","dCCKy6JoX0PADOFic8hRNQAAAAAAAB5A","9w9lF96vJW7ZhBoZ8ETsBwAAAAAAAMum","xUQuo4OgBaS_Le-fdAwt8AAAAAAAAIHw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAACxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAADRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAMqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAKLi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3z7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFufA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFtKr"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3]},"cL14TWzNnz1qK2PUYdE9bg":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791289,24794610,24781052,24778417,19046138,19039453,18993092,18869484,18879802,10485923,16807,2756560,2755688,2744899,3827767,3827522,2050302,4868077,4855697,8473771],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekj5","v6HIzNa4K6G4nRP9032RIAAAAAABelXy","v6HIzNa4K6G4nRP9032RIAAAAAABeiD8","v6HIzNa4K6G4nRP9032RIAAAAAABehax","v6HIzNa4K6G4nRP9032RIAAAAAABIp76","v6HIzNa4K6G4nRP9032RIAAAAAABIoTd","v6HIzNa4K6G4nRP9032RIAAAAAABIc_E","v6HIzNa4K6G4nRP9032RIAAAAAABH-zs","v6HIzNa4K6G4nRP9032RIAAAAAABIBU6","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAOmg3","ew01Dk0sWZctP-VaEpavqQAAAAAAOmdC","ew01Dk0sWZctP-VaEpavqQAAAAAAH0j-","ew01Dk0sWZctP-VaEpavqQAAAAAASkft","ew01Dk0sWZctP-VaEpavqQAAAAAASheR","ew01Dk0sWZctP-VaEpavqQAAAAAAgUyr"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"9wXZUZEeGMQm83C5yXCZ2g":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226539,39801748,39804999,39805475,40019662,39816300,32602256,32687470,24708823,24695729,24696049,18965430,18965669,18966052,18973868,18911086,18905330,18910928,18783663,18799034,10485923,16900,15534,703491,2755412,3875596,3765212,3542694,3677893],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdREr","j8DVIOTu7Btj9lgFefJ84AAAAAACX1OU","j8DVIOTu7Btj9lgFefJ84AAAAAACX2BH","j8DVIOTu7Btj9lgFefJ84AAAAAACX2Ij","j8DVIOTu7Btj9lgFefJ84AAAAAACYqbO","j8DVIOTu7Btj9lgFefJ84AAAAAACX4xs","j8DVIOTu7Btj9lgFefJ84AAAAAAB8XiQ","j8DVIOTu7Btj9lgFefJ84AAAAAAB8sVu","j8DVIOTu7Btj9lgFefJ84AAAAAABeQbX","j8DVIOTu7Btj9lgFefJ84AAAAAABeNOx","j8DVIOTu7Btj9lgFefJ84AAAAAABeNTx","j8DVIOTu7Btj9lgFefJ84AAAAAABIWO2","j8DVIOTu7Btj9lgFefJ84AAAAAABIWSl","j8DVIOTu7Btj9lgFefJ84AAAAAABIWYk","j8DVIOTu7Btj9lgFefJ84AAAAAABIYSs","j8DVIOTu7Btj9lgFefJ84AAAAAABII9u","j8DVIOTu7Btj9lgFefJ84AAAAAABIHjy","j8DVIOTu7Btj9lgFefJ84AAAAAABII7Q","j8DVIOTu7Btj9lgFefJ84AAAAAABHp2v","j8DVIOTu7Btj9lgFefJ84AAAAAABHtm6","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEIE","piWSMQrh4r040D0BPNaJvwAAAAAAADyu","piWSMQrh4r040D0BPNaJvwAAAAAACrwD","piWSMQrh4r040D0BPNaJvwAAAAAAKgtU","piWSMQrh4r040D0BPNaJvwAAAAAAOyMM","piWSMQrh4r040D0BPNaJvwAAAAAAOXPc","piWSMQrh4r040D0BPNaJvwAAAAAANg6m","piWSMQrh4r040D0BPNaJvwAAAAAAOB7F"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"bz1cYNqu8MBH2xCXTMEiAg":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440136,7507990,7549300],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI","ew01Dk0sWZctP-VaEpavqQAAAAAAcpAW","ew01Dk0sWZctP-VaEpavqQAAAAAAczF0"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4]},"fCScXsJaisrZL_JXgS4qQg":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2755760,2754888,2744099,6711233,7651644,7436960,6766701,6769642,2098164],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw","9LzzIocepYcOjnUsLlgOjgAAAAAAKglI","9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j","9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB","9LzzIocepYcOjnUsLlgOjgAAAAAAdME8","9LzzIocepYcOjnUsLlgOjgAAAAAAcXqg","9LzzIocepYcOjnUsLlgOjgAAAAAAZ0Bt","9LzzIocepYcOjnUsLlgOjgAAAAAAZ0vq","9LzzIocepYcOjnUsLlgOjgAAAAAAIAP0"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"V-MDb_Yh073ps9Vw4ypmDQ":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7439971,6798378,6797702,6797556,2726148],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYZj","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7wq","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7mG","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7j0","ew01Dk0sWZctP-VaEpavqQAAAAAAKZkE"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4]},"wAujHiFN47_oNUI63d6EtA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440136,7513502,6765905,6759805,2574033,2218596],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI","ew01Dk0sWZctP-VaEpavqQAAAAAAcqWe","ew01Dk0sWZctP-VaEpavqQAAAAAAZz1R","ew01Dk0sWZctP-VaEpavqQAAAAAAZyV9","ew01Dk0sWZctP-VaEpavqQAAAAAAJ0bR","ew01Dk0sWZctP-VaEpavqQAAAAAAIdpk"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"zMMsPlSW5HOq5bsuVRh3KA":{"address_or_lines":[980270,29770,3203438,1526226,1526293,1526410,1522622,1523799,453712,1320069,1900469,1899334,1898707,2062274,2293545,2285857,2284809,2485949,2472275,2784493,2826658,2822585,3001783,2924437,3111967,3095700,156159,136830,285452,1430646,1449979,1447865,1447752,1446446,1188192,1188137,220151,219438,219438,219438,219438,219438,219425,219589,1446206],"file_ids":["Z_CHd3Zjsh2cWE2NSdbiNQ","eOfhJQFIxbIEScd007tROw","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","9HZ7GQCC6G9fZlRD7aGzXQ","9HZ7GQCC6G9fZlRD7aGzXQ","9HZ7GQCC6G9fZlRD7aGzXQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","huWyXZbCBWCe2ZtK9BiokQ"],"frame_ids":["Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADvUu","eOfhJQFIxbIEScd007tROwAAAAAAAHRK","-p9BlJh9JZMPPNjY_j92ngAAAAAAMOFu","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0nS","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0oV","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0qK","-p9BlJh9JZMPPNjY_j92ngAAAAAAFzu-","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0BX","-p9BlJh9JZMPPNjY_j92ngAAAAAABuxQ","-p9BlJh9JZMPPNjY_j92ngAAAAAAFCSF","-p9BlJh9JZMPPNjY_j92ngAAAAAAHP-1","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPtG","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPjT","-p9BlJh9JZMPPNjY_j92ngAAAAAAH3fC","-p9BlJh9JZMPPNjY_j92ngAAAAAAIv8p","-p9BlJh9JZMPPNjY_j92ngAAAAAAIuEh","-p9BlJh9JZMPPNjY_j92ngAAAAAAIt0J","-p9BlJh9JZMPPNjY_j92ngAAAAAAJe69","-p9BlJh9JZMPPNjY_j92ngAAAAAAJblT","-p9BlJh9JZMPPNjY_j92ngAAAAAAKnzt","-p9BlJh9JZMPPNjY_j92ngAAAAAAKyGi","-p9BlJh9JZMPPNjY_j92ngAAAAAAKxG5","-p9BlJh9JZMPPNjY_j92ngAAAAAALc23","-p9BlJh9JZMPPNjY_j92ngAAAAAALJ-V","-p9BlJh9JZMPPNjY_j92ngAAAAAAL3wf","-p9BlJh9JZMPPNjY_j92ngAAAAAALzyU","9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAmH_","9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAhZ-","9HZ7GQCC6G9fZlRD7aGzXQAAAAAABFsM","huWyXZbCBWCe2ZtK9BiokQAAAAAAFdR2","huWyXZbCBWCe2ZtK9BiokQAAAAAAFh_7","huWyXZbCBWCe2ZtK9BiokQAAAAAAFhe5","huWyXZbCBWCe2ZtK9BiokQAAAAAAFhdI","huWyXZbCBWCe2ZtK9BiokQAAAAAAFhIu","huWyXZbCBWCe2ZtK9BiokQAAAAAAEiFg","huWyXZbCBWCe2ZtK9BiokQAAAAAAEiEp","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1v3","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1ku","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1ku","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1ku","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1ku","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1ku","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1kh","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1nF","huWyXZbCBWCe2ZtK9BiokQAAAAAAFhE-"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"pLdowTKUS5KSwivHyl5AgA":{"address_or_lines":[980270,29770,3203438,1526226,1526293,1526410,1522622,1523799,453712,1320069,1900469,1899334,1898707,2062274,2293545,2285857,2284809,2485949,2472275,2784493,2826658,2823003,3007344,3001783,2924437,3112045,3104142,1417998,1456694,1456323,1393341,1348522,1348436,1345741,1348060,1347558,1345741,1348060,1347558,1344317,1318852,1317790,1316548,1337360,1338921,1188023],"file_ids":["Z_CHd3Zjsh2cWE2NSdbiNQ","eOfhJQFIxbIEScd007tROw","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ"],"frame_ids":["Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADvUu","eOfhJQFIxbIEScd007tROwAAAAAAAHRK","-p9BlJh9JZMPPNjY_j92ngAAAAAAMOFu","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0nS","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0oV","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0qK","-p9BlJh9JZMPPNjY_j92ngAAAAAAFzu-","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0BX","-p9BlJh9JZMPPNjY_j92ngAAAAAABuxQ","-p9BlJh9JZMPPNjY_j92ngAAAAAAFCSF","-p9BlJh9JZMPPNjY_j92ngAAAAAAHP-1","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPtG","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPjT","-p9BlJh9JZMPPNjY_j92ngAAAAAAH3fC","-p9BlJh9JZMPPNjY_j92ngAAAAAAIv8p","-p9BlJh9JZMPPNjY_j92ngAAAAAAIuEh","-p9BlJh9JZMPPNjY_j92ngAAAAAAIt0J","-p9BlJh9JZMPPNjY_j92ngAAAAAAJe69","-p9BlJh9JZMPPNjY_j92ngAAAAAAJblT","-p9BlJh9JZMPPNjY_j92ngAAAAAAKnzt","-p9BlJh9JZMPPNjY_j92ngAAAAAAKyGi","-p9BlJh9JZMPPNjY_j92ngAAAAAAKxNb","-p9BlJh9JZMPPNjY_j92ngAAAAAALeNw","-p9BlJh9JZMPPNjY_j92ngAAAAAALc23","-p9BlJh9JZMPPNjY_j92ngAAAAAALJ-V","-p9BlJh9JZMPPNjY_j92ngAAAAAAL3xt","-p9BlJh9JZMPPNjY_j92ngAAAAAAL12O","huWyXZbCBWCe2ZtK9BiokQAAAAAAFaMO","huWyXZbCBWCe2ZtK9BiokQAAAAAAFjo2","huWyXZbCBWCe2ZtK9BiokQAAAAAAFjjD","huWyXZbCBWCe2ZtK9BiokQAAAAAAFUK9","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJOq","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJNU","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIM9","huWyXZbCBWCe2ZtK9BiokQAAAAAAFB_E","huWyXZbCBWCe2ZtK9BiokQAAAAAAFBue","huWyXZbCBWCe2ZtK9BiokQAAAAAAFBbE","huWyXZbCBWCe2ZtK9BiokQAAAAAAFGgQ","huWyXZbCBWCe2ZtK9BiokQAAAAAAFG4p","huWyXZbCBWCe2ZtK9BiokQAAAAAAEiC3"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"_ef-NJahpYK_FzFC-KdtYQ":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,49540,17442,49772,35602,29942,33148,3444,27444,9712,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1072174,33518,35576,8560,17976,49494,22596,3272936,3254825,1481992,1534257,3238809,3051716,67008,10485923,16807,2756288,2755416,2744627,3827463,3827218,2049230,2042319,2040147,2469374],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","eOfhJQFIxbIEScd007tROw","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAMJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAIsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAHT2","LF6DFcGHEMqhhhlptO_M_QAAAAAAAIF8","Af6E3BeG383JVVbu67NJ0QAAAAAAAA10","xwuAPHgc12-8PZB3i-320gAAAAAAAGs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEFwu","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAMFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAFhE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMfDo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMaop","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp0I","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAF2kx","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMWuZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALpDE","eOfhJQFIxbIEScd007tROwAAAAAAAQXA","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A","A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY","A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz","A2oiHVwisByxRn5RDT4LjAAAAAAAOmcH","A2oiHVwisByxRn5RDT4LjAAAAAAAOmYS","A2oiHVwisByxRn5RDT4LjAAAAAAAH0TO","A2oiHVwisByxRn5RDT4LjAAAAAAAHynP","A2oiHVwisByxRn5RDT4LjAAAAAAAHyFT","A2oiHVwisByxRn5RDT4LjAAAAAAAJa3-"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"omG-i9KffSi3YT8q0rYOiw":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440136,7508344,7393457,7394824,7384416,6869315,6866863,2620,6841654,6841533],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","6miIyyucTZf5zXHCk7PT1g","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI","ew01Dk0sWZctP-VaEpavqQAAAAAAcpF4","ew01Dk0sWZctP-VaEpavqQAAAAAAcNCx","ew01Dk0sWZctP-VaEpavqQAAAAAAcNYI","ew01Dk0sWZctP-VaEpavqQAAAAAAcK1g","ew01Dk0sWZctP-VaEpavqQAAAAAAaNFD","ew01Dk0sWZctP-VaEpavqQAAAAAAaMev","6miIyyucTZf5zXHCk7PT1gAAAAAAAAo8","ew01Dk0sWZctP-VaEpavqQAAAAAAaGU2","ew01Dk0sWZctP-VaEpavqQAAAAAAaGS9"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"XiONbb-veQ1sAuFD6_Fv0A":{"address_or_lines":[48,38,174,104,68,200,38,174,104,68,60,38,174,104,68,92,38,174,104,68,4,38,174,104,10,10,38,174,104,68,20,38,174,104,14,32,190,1091944,2047231,2046923,2044755,2041537,2044807,2041460,1171829,2265239,2264574,2258601,1016100],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5sij7Z672VAK_gGoPDPJBg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","PCeTYI0HN2oKNST6e1IaQQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","U4FmFVJMlNKhF1hVl3Xj1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","JR7ekk9KGQJKKPohpdwCLQ","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rpRn_rYC3CgtEgBAUrkZZg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAADI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5sij7Z672VAK_gGoPDPJBgAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","PCeTYI0HN2oKNST6e1IaQQAAAAAAAABc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","U4FmFVJMlNKhF1hVl3Xj1AAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","JR7ekk9KGQJKKPohpdwCLQAAAAAAAAAK","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rpRn_rYC3CgtEgBAUrkZZgAAAAAAAAAU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAAC-","G68hjsyagwq6LpWrMjDdngAAAAAAEKlo","G68hjsyagwq6LpWrMjDdngAAAAAAHzz_","G68hjsyagwq6LpWrMjDdngAAAAAAHzvL","G68hjsyagwq6LpWrMjDdngAAAAAAHzNT","G68hjsyagwq6LpWrMjDdngAAAAAAHybB","G68hjsyagwq6LpWrMjDdngAAAAAAHzOH","G68hjsyagwq6LpWrMjDdngAAAAAAHyZ0","G68hjsyagwq6LpWrMjDdngAAAAAAEeF1","G68hjsyagwq6LpWrMjDdngAAAAAAIpCX","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAInap","G68hjsyagwq6LpWrMjDdngAAAAAAD4Ek"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3]},"krdohOL0KiVMtm4q-6fmjg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,388,33826,620,51986,5836,10976,12298,1480209,1969795,1481300,1480601,2595076,1079144,1868,1480209,1969795,1481300,1480601,2595076,1079144,37910,8000,46852,32076,49840,40252,33434,32730,43978,37948,30428,26428,19370,1480209,1940645,1970099,1481300,1480695,2595076,1079144,20016,37192,1480141,1913750],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","CwUjPVV5_7q7c0GhtW0aPw","okehWevKsEA4q6dk779jgw","-IuadWGT89NVzIyF_Emodw","XXJY7v4esGWnaxtMW3FA0g","FbrXdcA4j750RyQ3q9JXMw","pL34QuyxyP6XYzGDBMK_5w","IoAk4kM-M4DsDPp7ia5QXw","uHLoBslr3h6S7ooNeXzEbw","iRoTPXvR_cRsnzDO-aurpQ","fB79lJck2X90l-j7VqPR-Q","gbMheDI1NZ3NY96J0seddg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GquRfhZBLBKr9rIBPuH3nA","_DA_LSFNMjbu9L2Dcselpw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS","grZNsSElR5ITq8H2yHCNSwAAAAAAABbM","W8AFtEsepzrJ6AasHrCttwAAAAAAACrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAADAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAAdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","kSaNXrGzSS3BnDNNWezzMAAAAAAAAJQW","ne8F__HPIVgxgycJADVSzAAAAAAAAB9A","CwUjPVV5_7q7c0GhtW0aPwAAAAAAALcE","okehWevKsEA4q6dk779jgwAAAAAAAH1M","-IuadWGT89NVzIyF_EmodwAAAAAAAMKw","XXJY7v4esGWnaxtMW3FA0gAAAAAAAJ08","FbrXdcA4j750RyQ3q9JXMwAAAAAAAIKa","pL34QuyxyP6XYzGDBMK_5wAAAAAAAH_a","IoAk4kM-M4DsDPp7ia5QXwAAAAAAAKvK","uHLoBslr3h6S7ooNeXzEbwAAAAAAAJQ8","iRoTPXvR_cRsnzDO-aurpQAAAAAAAHbc","fB79lJck2X90l-j7VqPR-QAAAAAAAGc8","gbMheDI1NZ3NY96J0seddgAAAAAAAEuq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZyl","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg-z","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpf3","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","GquRfhZBLBKr9rIBPuH3nAAAAAAAAE4w","_DA_LSFNMjbu9L2DcselpwAAAAAAAJFI","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpXN","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHTOW"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,1,1,3,3]},"N2LqhupgLi4T_B9D7JaDDQ":{"address_or_lines":[4623648,7066994,7068484,7069849,7058446,10002970,10005676,10124500,9016547,11291366,9016547,24500423,24494926,9016547,10689293,10690744,9016547,24494153,24444068,9016547,24526481,9016547,12769612,10684953,24495408,10128820,7327937,7071629,7072042,7142576,5627718,5631637,5512164,4910105,4760761,4777496,4778618,10485923,16743,6659981,6654519,6650911,6650061,8052504,7525822,7331115,7324128,6674998,6706722,6700261,2539310],"file_ids":["JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","JsObMPhfT_zO2Q_B1cPLxA","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["JsObMPhfT_zO2Q_B1cPLxAAAAAAARo0g","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa9Vy","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa9tE","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa-CZ","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa7QO","JsObMPhfT_zO2Q_B1cPLxAAAAAAAmKIa","JsObMPhfT_zO2Q_B1cPLxAAAAAAAmKys","JsObMPhfT_zO2Q_B1cPLxAAAAAAAmnzU","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAAArErm","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAABddjH","JsObMPhfT_zO2Q_B1cPLxAAAAAABdcNO","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAAAoxsN","JsObMPhfT_zO2Q_B1cPLxAAAAAAAoyC4","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAABdcBJ","JsObMPhfT_zO2Q_B1cPLxAAAAAABdPyk","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAABdj6R","JsObMPhfT_zO2Q_B1cPLxAAAAAAAiZTj","JsObMPhfT_zO2Q_B1cPLxAAAAAAAwtlM","JsObMPhfT_zO2Q_B1cPLxAAAAAAAowoZ","JsObMPhfT_zO2Q_B1cPLxAAAAAABdcUw","JsObMPhfT_zO2Q_B1cPLxAAAAAAAmo20","JsObMPhfT_zO2Q_B1cPLxAAAAAAAb9DB","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa-eN","JsObMPhfT_zO2Q_B1cPLxAAAAAAAa-kq","JsObMPhfT_zO2Q_B1cPLxAAAAAAAbPyw","JsObMPhfT_zO2Q_B1cPLxAAAAAAAVd9G","JsObMPhfT_zO2Q_B1cPLxAAAAAAAVe6V","JsObMPhfT_zO2Q_B1cPLxAAAAAAAVBvk","JsObMPhfT_zO2Q_B1cPLxAAAAAAASuwZ","JsObMPhfT_zO2Q_B1cPLxAAAAAAASKS5","JsObMPhfT_zO2Q_B1cPLxAAAAAAASOYY","JsObMPhfT_zO2Q_B1cPLxAAAAAAASOp6","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAZZ-N","piWSMQrh4r040D0BPNaJvwAAAAAAZYo3","piWSMQrh4r040D0BPNaJvwAAAAAAZXwf","piWSMQrh4r040D0BPNaJvwAAAAAAZXjN","piWSMQrh4r040D0BPNaJvwAAAAAAet8Y","piWSMQrh4r040D0BPNaJvwAAAAAActW-","piWSMQrh4r040D0BPNaJvwAAAAAAb90r","piWSMQrh4r040D0BPNaJvwAAAAAAb8Hg","piWSMQrh4r040D0BPNaJvwAAAAAAZdo2","piWSMQrh4r040D0BPNaJvwAAAAAAZlYi","piWSMQrh4r040D0BPNaJvwAAAAAAZjzl","piWSMQrh4r040D0BPNaJvwAAAAAAJr8u"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"7TvODt8WtQ5KXTmYPsDI3A":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,388,33826,620,51986,54988,10976,61450,1480209,1969795,1481300,1480601,2595076,1079144,1868,1480209,1969795,1481300,1480601,2595076,1079144,21526,8000,30022,59542,29542,18986,21536,54462,53814,11024,12030,61026,21014,45460,42632,1480209,3459845,1479516,2595076,1050939,23882,1371605,2194798,2100556,2032414,1865128],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","CwUjPVV5_7q7c0GhtW0aPw","cBO14nNDW8EW0oaZDaZipw","C64RiOp1JIPwHLB_iHDa0A","xvApUwdY2y4sFaZRNrMv5g","vsalcPHh9qLgsdKtk190IA","QsuqlohtoJfpo6vQ6tHa2A","8ep9l3WIVYErRiHtmAdvew","nPWpQrEmCn54Ou0__aZyJA","-xcELApECIipEESUIWed9w","L_saUsdri-UdXCut6Tdtng","uHLoBslr3h6S7ooNeXzEbw","p19NBQ2pky4eRJM7tgeenw","55ABUc9FqQ0uj-yn-sTq2A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","1msFlmxT18lYvJkx-hfGPg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS","grZNsSElR5ITq8H2yHCNSwAAAAAAANbM","W8AFtEsepzrJ6AasHrCttwAAAAAAACrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAPAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAAdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","kSaNXrGzSS3BnDNNWezzMAAAAAAAAFQW","ne8F__HPIVgxgycJADVSzAAAAAAAAB9A","CwUjPVV5_7q7c0GhtW0aPwAAAAAAAHVG","cBO14nNDW8EW0oaZDaZipwAAAAAAAOiW","C64RiOp1JIPwHLB_iHDa0AAAAAAAAHNm","xvApUwdY2y4sFaZRNrMv5gAAAAAAAEoq","vsalcPHh9qLgsdKtk190IAAAAAAAAFQg","QsuqlohtoJfpo6vQ6tHa2AAAAAAAANS-","8ep9l3WIVYErRiHtmAdvewAAAAAAANI2","nPWpQrEmCn54Ou0__aZyJAAAAAAAACsQ","-xcELApECIipEESUIWed9wAAAAAAAC7-","L_saUsdri-UdXCut6TdtngAAAAAAAO5i","uHLoBslr3h6S7ooNeXzEbwAAAAAAAFIW","p19NBQ2pky4eRJM7tgeenwAAAAAAALGU","55ABUc9FqQ0uj-yn-sTq2AAAAAAAAKaI","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAANMsF","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEAk7","1msFlmxT18lYvJkx-hfGPgAAAAAAAF1K","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFO3V","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIX1u","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIA1M","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHwMe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHWo"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,1,3,3,3,3,3]},"u1L6jqeUaTNx1a2aJ9yFwA":{"address_or_lines":[74,6,18,8,18,80,24,4,84,38,174,104,68,128,38,174,104,68,64,38,174,104,68,84,38,174,104,68,100,140,10,38,174,104,68,60,38,174,104,14,32,38,32,786829,1090933,2561389,794630,788130,1197115,2578326,1109790,1111453,1034624],"file_ids":["a5aMcPOeWx28QSVng73nBQ","inI9W0bfekFTCpu0ceKTHg","RPwdw40HEBL87wRkKV2ozw","pT2bgvKv3bKR6LMAYtKFRw","Rsr7q4vCSh2ppRtyNkwZAA","cKQfWSgZRgu_1Goz5QGSHw","T2fhmP8acUvRZslK7YRDPw","lrxXzNEmAlflj7bCNDjxdA","SMoSw8cr-PdrIATvljOPrQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","xaCec3W8F6xlvd_EISI7vw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","GYpj0RgmHJTfD-_w_Fx69w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","b78FoZPzgl20nGrU0Zu24g","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5ZxW56RI3EOJxqCWjdkdHg","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","7l7IlhF_Z6_Ribw1CW945Q","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","imaY9TOf2pKX0_q1vRTskQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAABK","inI9W0bfekFTCpu0ceKTHgAAAAAAAAAG","RPwdw40HEBL87wRkKV2ozwAAAAAAAAAS","pT2bgvKv3bKR6LMAYtKFRwAAAAAAAAAI","Rsr7q4vCSh2ppRtyNkwZAAAAAAAAAAAS","cKQfWSgZRgu_1Goz5QGSHwAAAAAAAABQ","T2fhmP8acUvRZslK7YRDPwAAAAAAAAAY","lrxXzNEmAlflj7bCNDjxdAAAAAAAAAAE","SMoSw8cr-PdrIATvljOPrQAAAAAAAABU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","xaCec3W8F6xlvd_EISI7vwAAAAAAAACA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","GYpj0RgmHJTfD-_w_Fx69wAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","b78FoZPzgl20nGrU0Zu24gAAAAAAAABU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5ZxW56RI3EOJxqCWjdkdHgAAAAAAAABk","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","7l7IlhF_Z6_Ribw1CW945QAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAAAm","imaY9TOf2pKX0_q1vRTskQAAAAAAAAAg","G68hjsyagwq6LpWrMjDdngAAAAAADAGN","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","G68hjsyagwq6LpWrMjDdngAAAAAAJxVt","G68hjsyagwq6LpWrMjDdngAAAAAADCAG","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkQ7","G68hjsyagwq6LpWrMjDdngAAAAAAJ1eW","G68hjsyagwq6LpWrMjDdngAAAAAAEO8e","G68hjsyagwq6LpWrMjDdngAAAAAAEPWd","G68hjsyagwq6LpWrMjDdngAAAAAAD8mA"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3]},"8uzy4VW9n0Z8KokUdeadfg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,16772,50210,17004,2834,14028,27360,55578,1480209,1969795,1481300,1480601,2595076,1079485,18126,36558,2460,42724,46700,1479608,1493928,2595076,1079485,30578,15346,1479608,2595076,1079485,57180,32508,1276,30612,1479516,2595076,1079485,63696,30612,1479516,2595076,1073749,60436,3118304,766784,10485923,16807,2741196,2827770,2817684,2804657,2869654],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","GdaBUD9IUEkKxIBryNqV2w","QU8QLoFK6ojrywKrBFfTzA","V558DAsp4yi8bwa8eYwk5Q","grikUXlisBLUbeL_OWixIw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","cHp4MwXaY5FCuFRuAA6tWw","-9oyoP4Jj2iRkwEezqId-g","Kq9d0b1CBVEQZUtuJtmlJg","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","--q8cwZVXbHL2zOM_p3RlQ","-Z7SlEXhuy5tL2BF-xmy3g","Z_CHd3Zjsh2cWE2NSdbiNQ","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAEGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAMQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAEJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAAsS","grZNsSElR5ITq8H2yHCNSwAAAAAAADbM","W8AFtEsepzrJ6AasHrCttwAAAAAAAGrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAANka","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","EFJHOn-GACfHXgae-R1yDAAAAAAAAEbO","GdaBUD9IUEkKxIBryNqV2wAAAAAAAI7O","QU8QLoFK6ojrywKrBFfTzAAAAAAAAAmc","V558DAsp4yi8bwa8eYwk5QAAAAAAAKbk","grikUXlisBLUbeL_OWixIwAAAAAAALZs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","oERZXsH8EPeoSRxNNaSWfQAAAAAAAHdy","gMhgHDYSMmyInNJ15VwYFgAAAAAAADvy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","cHp4MwXaY5FCuFRuAA6tWwAAAAAAAN9c","-9oyoP4Jj2iRkwEezqId-gAAAAAAAH78","Kq9d0b1CBVEQZUtuJtmlJgAAAAAAAAT8","FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","GEIvPhvjHWZLHz2BksVgvAAAAAAAAPjQ","FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","--q8cwZVXbHL2zOM_p3RlQAAAAAAAOwU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAL5Tg","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAC7NA","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM","A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6","A2oiHVwisByxRn5RDT4LjAAAAAAAKv6U","A2oiHVwisByxRn5RDT4LjAAAAAAAKsux","A2oiHVwisByxRn5RDT4LjAAAAAAAK8mW"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,1,1,1,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,3,3,4,4,4,4,4,4,4]},"EeUwhr9vbcywMBkIYZRfCw":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,33156,1058,33388,19218,46796,43744,53258,1480209,1969795,1481300,1480601,2595076,1079144,34636,1480209,1969795,1481300,1480601,2595076,1079144,13334,40862,834,1480209,1969795,1481300,1480601,2595076,1069341,58136,12466,1587508,1079485,50582,26272,1479608,1493928,2595076,1079211,60348,34084,42798,54954,4836,40660,62188,43850,13372,5488,20256,1924997],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","wpss7yv4AvkSwbtctTl0JA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","SLUxdgyFrTF3l4NU1VRO_w","ZOgaFnYiv38tVz-8Hafu3w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","u1Za6xFXDX1Ys5Qeh_gy9Q","uq4_q8agTQ0rkhJvygJ3QA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","pK0zxAMiW-X23QjQRVzm5w","OP7EiuTwTtWCf_B7a-Zpig","WyVrojmISSgbkYAxEOnpQw","JdWBEAqhrU7LJg0YDuYO0w","cwZEcJVCN5Q4BJdAS3o8fw","iLNvi1vqLkBP_ehg4QlqeA","guXM5tmjJlv0Ehde0y1DFw","avBEfFKeFSrhKf93SLNe0Q","uHLoBslr3h6S7ooNeXzEbw","iRoTPXvR_cRsnzDO-aurpQ","aAagm2yDcrnYaqBPCwyu8Q","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAALbM","W8AFtEsepzrJ6AasHrCttwAAAAAAAKrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAANAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAIdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","kSaNXrGzSS3BnDNNWezzMAAAAAAAADQW","ne8F__HPIVgxgycJADVSzAAAAAAAAJ-e","wpss7yv4AvkSwbtctTl0JAAAAAAAAANC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEFEd","SLUxdgyFrTF3l4NU1VRO_wAAAAAAAOMY","ZOgaFnYiv38tVz-8Hafu3wAAAAAAADCy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGDk0","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","u1Za6xFXDX1Ys5Qeh_gy9QAAAAAAAMWW","uq4_q8agTQ0rkhJvygJ3QAAAAAAAAGag","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHer","pK0zxAMiW-X23QjQRVzm5wAAAAAAAOu8","OP7EiuTwTtWCf_B7a-ZpigAAAAAAAIUk","WyVrojmISSgbkYAxEOnpQwAAAAAAAKcu","JdWBEAqhrU7LJg0YDuYO0wAAAAAAANaq","cwZEcJVCN5Q4BJdAS3o8fwAAAAAAABLk","iLNvi1vqLkBP_ehg4QlqeAAAAAAAAJ7U","guXM5tmjJlv0Ehde0y1DFwAAAAAAAPLs","avBEfFKeFSrhKf93SLNe0QAAAAAAAKtK","uHLoBslr3h6S7ooNeXzEbwAAAAAAADQ8","iRoTPXvR_cRsnzDO-aurpQAAAAAAABVw","aAagm2yDcrnYaqBPCwyu8QAAAAAAAE8g","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHV-F"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,1,1,3,3,1,1,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,3]},"x443zjuudYI-A7cRu2DIGg":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,2228,5922,53516,36626,49094,58124,2548,13860,42480,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,28996,2578675,2599636,1091600,48574,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,28996,2578675,2599636,1091600,63674,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,28780,342,57994,19187,38198,48990],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","lpUCR1NQj5NOLBg7mvzlqg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0","U4Le8nh-beog_B7jq7uTIAAAAAAAABci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAL_G","LF6DFcGHEMqhhhlptO_M_QAAAAAAAOMM","Af6E3BeG383JVVbu67NJ0QAAAAAAAAn0","xwuAPHgc12-8PZB3i-320gAAAAAAADYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAL2-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","lpUCR1NQj5NOLBg7mvzlqgAAAAAAAPi6","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAAFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAOKK","ASi9f26ltguiwFajNwOaZwAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAJU2","jaBVtokSUzfS97d-XKjijgAAAAAAAL9e"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"rrrvnakD3SpJqProBGqoCQ":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,49540,17442,49772,35602,1270,4476,19828,27444,26096,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,8942,11000,49520,50908,2573747,2594708,1091475,19382,2790352,1482889,1482415,2595076,1073749,8942,11000,49520,50908,2573747,2594708,1091475,60558,2790352,1482889,1482415,2595076,1079144,8942,10826,15776,45470,57908,19178,5946,1481694,1535004,2095808],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","08DBZKRu4nC_Oi_uT40UHw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","lOUbi56SanKTCh9Y7fIwDw","n74P5OxFm1hAo5ZWtgcKHQ","zXbqXCWr0lCbi_b24hNBRQ","AOM_-6oRTyAxK8W79Wo5aQ","yaTrLhUSIq2WitrTHLBy3Q","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAMJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAIsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAAT2","LF6DFcGHEMqhhhlptO_M_QAAAAAAABF8","Af6E3BeG383JVVbu67NJ0QAAAAAAAE10","xwuAPHgc12-8PZB3i-320gAAAAAAAGs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAACLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAMFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAEu2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAACLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAMFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","08DBZKRu4nC_Oi_uT40UHwAAAAAAAOyO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","ik6PIX946fW_erE7uBJlVQAAAAAAACLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACpK","lOUbi56SanKTCh9Y7fIwDwAAAAAAAD2g","n74P5OxFm1hAo5ZWtgcKHQAAAAAAALGe","zXbqXCWr0lCbi_b24hNBRQAAAAAAAOI0","AOM_-6oRTyAxK8W79Wo5aQAAAAAAAErq","yaTrLhUSIq2WitrTHLBy3QAAAAAAABc6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAF2wc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAH_rA"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3]},"sDfHX0MKzztQSqC8kl_-sg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,16720,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50908,2573747,2594708,1091475,52894,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50908,2573747,2594708,1091475,44846,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50908,2573747,2594708,1091475,32258,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50744,16726,2346,19187,41240,50359],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MU3fJpOZe9TA4mzeo52wZg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N0GNsPaCLYzoFsPJWnIJtQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fq0ezjB8ddCA6Pk0BY9arQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","dGWvVtQJJ5wuqNyQVpi8lA","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","DTRaillMS4wmG2CDEfm9rQAAAAAAAEFQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MU3fJpOZe9TA4mzeo52wZgAAAAAAAM6e","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N0GNsPaCLYzoFsPJWnIJtQAAAAAAAK8u","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","fq0ezjB8ddCA6Pk0BY9arQAAAAAAAH4C","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAAkq","dGWvVtQJJ5wuqNyQVpi8lAAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMS3"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"WmwSnxyphedkasVyGbhNdg":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,18612,22306,4364,53010,23142,41180,18932,30244,42480,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,4420,2578675,2599636,1091600,29418,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,4420,2578675,2599636,1091600,58990,2795776,1483241,1482767,2600004,1073803,3150,5208,43696,4204,342,33506,2852079,2851771,2849353,2846190,2846190,2845732],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","l97YFeEKpeLfa-lEAZVNcA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAEi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAFci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAABEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAM8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAFpm","LF6DFcGHEMqhhhlptO_M_QAAAAAAAKDc","Af6E3BeG383JVVbu67NJ0QAAAAAAAEn0","xwuAPHgc12-8PZB3i-320gAAAAAAAHYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAHLq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","l97YFeEKpeLfa-lEAZVNcAAAAAAAAOZu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAAFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAILi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK2wk"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3]},"NU5so_CJJJwGJM_hiEcxgQ":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,4,38,174,104,68,16,38,174,104,68,256,140,10,38,174,104,68,0,12,8,28,12,8,54,12,120,1169291,1109342,1109180],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ZBnr-5IlLVGCdkX_lTNKmw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","RDOEyok4432cuMjL10_tug","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_____________________w","U7DZUwH_4YU5DSkoQhGJWw","bmb3nSRfimrjfhanpjR1rQ","25JFhMXA0rvP5hfyUpf34w","U7DZUwH_4YU5DSkoQhGJWw","bmb3nSRfimrjfhanpjR1rQ","oN7OWDJeuc8DmI2f_earDQ","Yj7P3-Rt3nirG6apRl4A7A","pz3Evn9laHNJFMwOKIXbsw","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ZBnr-5IlLVGCdkX_lTNKmwAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","RDOEyok4432cuMjL10_tugAAAAAAAAEA","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_____________________wAAAAAAAAAA","U7DZUwH_4YU5DSkoQhGJWwAAAAAAAAAM","bmb3nSRfimrjfhanpjR1rQAAAAAAAAAI","25JFhMXA0rvP5hfyUpf34wAAAAAAAAAc","U7DZUwH_4YU5DSkoQhGJWwAAAAAAAAAM","bmb3nSRfimrjfhanpjR1rQAAAAAAAAAI","oN7OWDJeuc8DmI2f_earDQAAAAAAAAA2","Yj7P3-Rt3nirG6apRl4A7AAAAAAAAAAM","pz3Evn9laHNJFMwOKIXbswAAAAAAAAB4","G68hjsyagwq6LpWrMjDdngAAAAAAEdeL","G68hjsyagwq6LpWrMjDdngAAAAAAEO1e","G68hjsyagwq6LpWrMjDdngAAAAAAEOy8"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3]},"A9B6bwuKQl9pC0MIYqtAgg":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,34996,38690,20748,3858,37276,30816,26538,1480561,1970211,1481652,1480953,2600004,1079669,36476,1480561,1970211,1481652,1480953,2600004,1079669,13542,44224,26138,5558,16780,64790,18774,36466,18774,17314,43978,43978,43978,43978,43978,43978,43978,43886,18774,13462,1480561,1940968,1917658,1481652,1480953,2600004,1079669,27396,1480561,1827986,1940595,1909209,1934862,3077552,3072233,1745406,3070488],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","EFJHOn-GACfHXgae-R1yDA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","ktj-IOmkEpvZJouiJkQjTg","O_h7elJSxPO7SiCsftYRZg","ZLTqiSLOmv4Ej_7d8yKLmw","v_WV3HQYVe0q1Ob-1gtx1A","ka2IKJhpWbD6PA3J3v624w","e8Lb_MV93AH-OkvHPPDitg","ka2IKJhpWbD6PA3J3v624w","1vivUE5hL65442lQ9a_ylg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","fh_7rTxpgngJ2cX2lBjVdg","ka2IKJhpWbD6PA3J3v624w","fCsVLBj60GK9Hf8VtnMcgA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","54xjnvwS2UtwpSVJMemggA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAIi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAJci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAFEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAA8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAJGc","W8AFtEsepzrJ6AasHrCttwAAAAAAAHhg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAGeq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","EFJHOn-GACfHXgae-R1yDAAAAAAAAI58","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","kSaNXrGzSS3BnDNNWezzMAAAAAAAADTm","ne8F__HPIVgxgycJADVSzAAAAAAAAKzA","ktj-IOmkEpvZJouiJkQjTgAAAAAAAGYa","O_h7elJSxPO7SiCsftYRZgAAAAAAABW2","ZLTqiSLOmv4Ej_7d8yKLmwAAAAAAAEGM","v_WV3HQYVe0q1Ob-1gtx1AAAAAAAAP0W","ka2IKJhpWbD6PA3J3v624wAAAAAAAElW","e8Lb_MV93AH-OkvHPPDitgAAAAAAAI5y","ka2IKJhpWbD6PA3J3v624wAAAAAAAElW","1vivUE5hL65442lQ9a_ylgAAAAAAAEOi","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK","fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKtu","ka2IKJhpWbD6PA3J3v624wAAAAAAAElW","fCsVLBj60GK9Hf8VtnMcgAAAAAAAADSW","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHULa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","54xjnvwS2UtwpSVJMemggAAAAAAAAGsE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-SS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZxz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHSHZ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHYYO","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALvWw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALuDp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAGqH-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALtoY"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3]},"X86DUuQ7tHAxGBaWu4tZLg":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,2228,5922,53516,36626,19046,37084,2548,13860,26096,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,324,2578675,2599636,1091600,64610,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,324,2578675,2599636,1091600,39726,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,324,2578675,2599636,1091600,0,2794972,1848805,1837992,1848417,2718329,2222078,2208786],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","780bLUPADqfQ3x1T5lnVOg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","_____________________w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0","U4Le8nh-beog_B7jq7uTIAAAAAAAABci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAEpm","LF6DFcGHEMqhhhlptO_M_QAAAAAAAJDc","Af6E3BeG383JVVbu67NJ0QAAAAAAAAn0","xwuAPHgc12-8PZB3i-320gAAAAAAADYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAPxi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","780bLUPADqfQ3x1T5lnVOgAAAAAAAJsu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","_____________________wAAAAAAAAAA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqXc","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDXl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHAuo","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDRh","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKXp5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAIef-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAIbQS"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3]},"T3fWxJzHMwU-oUs7rgXCcg":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,212,38,174,104,68,228,38,174,104,68,4,38,174,104,68,92,38,174,104,68,8,38,174,104,68,172,669638,1091944,956540,2223054,995645,1276144],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","bAXCoU3-CU0WlRxl5l1tmw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","qordvIiilnF7CmkWCAd7eA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","iWpqwwcHV8E8OOnqGCYj9g","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","M61AJsljWf0TM7wD6IJVZw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","okgAOHfDrcA806m5xh4DMA","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAADU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","bAXCoU3-CU0WlRxl5l1tmwAAAAAAAADk","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","qordvIiilnF7CmkWCAd7eAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","iWpqwwcHV8E8OOnqGCYj9gAAAAAAAABc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","M61AJsljWf0TM7wD6IJVZwAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","okgAOHfDrcA806m5xh4DMAAAAAAAAACs","G68hjsyagwq6LpWrMjDdngAAAAAACjfG","G68hjsyagwq6LpWrMjDdngAAAAAAEKlo","G68hjsyagwq6LpWrMjDdngAAAAAADph8","G68hjsyagwq6LpWrMjDdngAAAAAAIevO","G68hjsyagwq6LpWrMjDdngAAAAAADzE9","G68hjsyagwq6LpWrMjDdngAAAAAAE3jw"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3]},"vq75CDVua5N-eDXnfyZYMA":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,16772,50210,620,51986,58710,61916,36212,43828,42480,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,49902,51960,24944,34524,2573747,2594708,1091475,12034,2790352,1482889,1482415,2595076,1073749,49902,51960,24944,34524,2573747,2594708,1091475,38490,2790352,1482889,1482415,2595076,1076587,49902,51960,24944,34360,342,51586,2846655,2846347,2843929,2840766,2843954,2840766,2842897,2268402,1775000,1761295,1048455],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","aRRT4_vBG9Q4nqyirWo5FA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAEGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAMQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAOVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAPHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAI10","xwuAPHgc12-8PZB3i-320gAAAAAAAKs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAC8C","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","aRRT4_vBG9Q4nqyirWo5FAAAAAAAAJZa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAAFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAMmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2Uy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2ER","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIpzy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGxWY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGuAP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAD_-H"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3]},"oKVObqTWF9QIjxgKf8UkTw":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1091600,51328,2795776,1483241,1482767,2600004,1079483,27726,29268,38054,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,27726,29784,2736,41284,2578675,2599636,1091600,50170,2795776,1483241,1482767,2600004,1074397,27726,29784,2736,41284,2578675,2599636,1091600,13752,2795776,1483241,1482767,2600004,1079483,27726,29268,38054,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,27726,29784,2736,41068,49494,4746,19187,41141,49404],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","08Dc0vnMK9C_nl7yQB6ZKQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","zuPG_tF81PcJTwjfBwKlDg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","DTRaillMS4wmG2CDEfm9rQAAAAAAAMiA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAJSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAKFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","08Dc0vnMK9C_nl7yQB6ZKQAAAAAAAMP6","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAKFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","zuPG_tF81PcJTwjfBwKlDgAAAAAAADW4","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAJSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAKBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAMFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAABKK","ASi9f26ltguiwFajNwOaZwAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKC1","jaBVtokSUzfS97d-XKjijgAAAAAAAMD8"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"DaDdc6eLo0hc-QxL2XQh5Q":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,336,2790352,1482889,1482415,2595076,1073749,49902,51960,24944,34524,2573747,2594708,1091475,28326,2790352,1482889,1482415,2595076,1073749,49902,51960,24944,34524,2573747,2594708,1091475,51274,2790352,1482889,1482415,2595076,1073749,49902,51960,24944,34524,2573747,2594708,1091475,43126,2790352,1482889,1482415,2595076,1073749,49902,51960,24944,34524,2573747,2594708,1091475,0,2790352,1482889,1482415,2595076,1071215,49902,51786,56736,43360,44552,32102],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MU3fJpOZe9TA4mzeo52wZg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","auEGiAr7C6IfT0eiHbOlyA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ZyAwfhB8pqBFv6xiDVdvPQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","lOUbi56SanKTCh9Y7fIwDw","9alsKcnSosScCQ3ntwGT5w","xAINw9zPBhJlledr3DAcGA","xVweU0pD8q051c2YgF4PTw"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","DTRaillMS4wmG2CDEfm9rQAAAAAAAAFQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MU3fJpOZe9TA4mzeo52wZgAAAAAAAG6m","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","auEGiAr7C6IfT0eiHbOlyAAAAAAAAMhK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","ZyAwfhB8pqBFv6xiDVdvPQAAAAAAAKh2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEFhv","ik6PIX946fW_erE7uBJlVQAAAAAAAMLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMpK","lOUbi56SanKTCh9Y7fIwDwAAAAAAAN2g","9alsKcnSosScCQ3ntwGT5wAAAAAAAKlg","xAINw9zPBhJlledr3DAcGAAAAAAAAK4I","xVweU0pD8q051c2YgF4PTwAAAAAAAH1m"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1]},"YRZbUV2DChD6dl3Y2xjF8g":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,49540,17442,49772,35602,38230,41436,19828,27444,26096,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,57358,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,33966,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,59370,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,17976,49494,31018,19187,41240,50308],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","d4jl580PLMUwu5s3I4wcXg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","tKago5vqLnwIkezk_wTBpQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","dGWvVtQJJ5wuqNyQVpi8lA","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAMJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAIsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAJVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAKHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAE10","xwuAPHgc12-8PZB3i-320gAAAAAAAGs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAOAO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","d4jl580PLMUwu5s3I4wcXgAAAAAAAISu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","tKago5vqLnwIkezk_wTBpQAAAAAAAOfq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAMFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAHkq","dGWvVtQJJ5wuqNyQVpi8lAAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMSE"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"EnsO3_jc7LnLdUHQbwkxMg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,336,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,24230,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,47162,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,37090,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,41914,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34360,342,39210,19187,41240,51115],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MU3fJpOZe9TA4mzeo52wZg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","auEGiAr7C6IfT0eiHbOlyA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","mP9Tk3T74fjOyYWKUaqdMQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","I4X8AC1-B0GuL4JyYemPzw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","dGWvVtQJJ5wuqNyQVpi8lA","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","DTRaillMS4wmG2CDEfm9rQAAAAAAAAFQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MU3fJpOZe9TA4mzeo52wZgAAAAAAAF6m","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","auEGiAr7C6IfT0eiHbOlyAAAAAAAALg6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","mP9Tk3T74fjOyYWKUaqdMQAAAAAAAJDi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","I4X8AC1-B0GuL4JyYemPzwAAAAAAAKO6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAAFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAJkq","dGWvVtQJJ5wuqNyQVpi8lAAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMer"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"V2XOOBv96QfYXHIIY7_OLA":{"address_or_lines":[3150,5208,43696,12612,2578675,2599636,1091600,42546,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1091600,12274,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1091600,15838,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1091600,37594,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1079669,12698,1482046,1829360,2586225,2600004,1054235,21784,1973936,2600004,1051035,60416,55140,1372101,2194686,2080131],"file_ids":["LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Gp9aOxUrrpSVBx4-ftlTOA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","y9R94bQUxts02WzRWfV7xg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","uI6css-d8SGQRK6a_Ntl-A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","SlnkBp0IIJFLHVOe4KbxwQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","7wBb3xHP1JZHNBpMGh4EdA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","u3fGdgL6eAYjYSRbRUri0g","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","aG0mH34tM6si5c1l397JVQ","GC-VoGaqaEobPzimayHQTQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","Gp9aOxUrrpSVBx4-ftlTOAAAAAAAAKYy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","y9R94bQUxts02WzRWfV7xgAAAAAAAC_y","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","uI6css-d8SGQRK6a_Ntl-AAAAAAAAD3e","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","SlnkBp0IIJFLHVOe4KbxwQAAAAAAAJLa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","7wBb3xHP1JZHNBpMGh4EdAAAAAAAADGa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3Zx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEBYb","u3fGdgL6eAYjYSRbRUri0gAAAAAAAFUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHh6w","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEAmb","aG0mH34tM6si5c1l397JVQAAAAAAAOwA","GC-VoGaqaEobPzimayHQTQAAAAAAANdk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFO_F","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAIXz-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAH72D"],"type_ids":[1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,1,1,3,3,3]},"FTJM3wsT8Kc-UaiIK2yDMQ":{"address_or_lines":[33018,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,32502,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,6654,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,9126,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,27090,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1079144,39334,1481694,1828960,2581397,1480843,1480209,1940568,1917230,1844695,1996687],"file_ids":["PmhxUKv5sePRxhCBONca8g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","UfGck3qA2qF0xFB5gpY4Hg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","G9ShE3ODivDEFyHVdsnZ_g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","6AsJ0dA2BUqaic-ScDJBMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fr52ZDCgnkPZlzTNdLTQ5w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uqoEOAkLp1toolLH0q5LVw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["PmhxUKv5sePRxhCBONca8gAAAAAAAID6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","UfGck3qA2qF0xFB5gpY4HgAAAAAAAH72","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","G9ShE3ODivDEFyHVdsnZ_gAAAAAAABn-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","6AsJ0dA2BUqaic-ScDJBMAAAAAAAACOm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","fr52ZDCgnkPZlzTNdLTQ5wAAAAAAAGnS","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","uqoEOAkLp1toolLH0q5LVwAAAAAAAJmm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpiL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUEu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHneP"],"type_ids":[1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3]},"ivbgd9hswtvZ7aTts7HESw":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,49488,2790352,1482889,1482415,2595076,1073749,8942,11000,49520,50908,2573747,2594708,1091475,40502,2790352,1482889,1482415,2595076,1073749,8942,11000,49520,50908,2573747,2594708,1091475,9946,2790352,1482889,1482415,2595076,1079485,8942,11000,49520,61192,19302,1479516,1828960,2573747,2594708,1091475,51250,2790352,1482889,1482415,2595076,1073749,8942,11000,49520,50908,2573747,2594708,1079144,0,1481694,1828960,2581297,2595076,1087128,0,23366,42140,41576,9542,41540,41016,39548,3072796],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MU3fJpOZe9TA4mzeo52wZg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","WjtMXFj0eujpoknR_rynvA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","Vot4T3F5OpUj8rbXhgpMDg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EPS0ql6FPdCQLe9KByvDQA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","OHQX9IWLaZElAgxGbX3P5g","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","DTRaillMS4wmG2CDEfm9rQAAAAAAAMFQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAACLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAMFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MU3fJpOZe9TA4mzeo52wZgAAAAAAAJ42","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAACLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAMFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","WjtMXFj0eujpoknR_rynvAAAAAAAACba","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAACLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAMFw","Vot4T3F5OpUj8rbXhgpMDgAAAAAAAO8I","eV_m28NnKeeTL60KO2H3SAAAAAAAAEtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","EPS0ql6FPdCQLe9KByvDQAAAAAAAAMgy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAACLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAMFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2Mx","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEJaY","_____________________wAAAAAAAAAA","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAFtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAKSc","_lF8o5tJDcePvza_IYtgSQAAAAAAAKJo","OHQX9IWLaZElAgxGbX3P5gAAAAAAACVG","E2b-mzlh_8261-JxcySn-AAAAAAAAKJE","E2b-mzlh_8261-JxcySn-AAAAAAAAKA4","E2b-mzlh_8261-JxcySn-AAAAAAAAJp8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuMc"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,1,1,3]},"yXsgvY1JyekwdCV5rJdspg":{"address_or_lines":[2573747,2594708,1091475,43746,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,51994,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,18382,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,10738,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1079144,0,1481694,1828960,2581397,1480843,1480209,1940568,1917258,1481300,1480601,2595076,1079485,46582,1479772,1827586,1940195,1986447,1982493,1959065,1765336,1761295,1048494],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","V6gUZHzBRISi-Z25klK5DQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zWNEoAKVTnnzSns045VKhw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","n4Ao4OZE2osF0FygfcWo3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","XVsKc4e32xXUv-3uv2s-8Q","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","_____________________w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uPGvGNXBf1JXGeeDSsmGQA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","V6gUZHzBRISi-Z25klK5DQAAAAAAAKri","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zWNEoAKVTnnzSns045VKhwAAAAAAAMsa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","n4Ao4OZE2osF0FygfcWo3gAAAAAAAEfO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","XVsKc4e32xXUv-3uv2s-8QAAAAAAACny","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","_____________________wAAAAAAAAAA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpiL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUFK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","uPGvGNXBf1JXGeeDSsmGQAAAAAAAALX2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpRc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-MC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZrj","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHk-P","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHkAd","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHeSZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGu_Y","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGuAP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAD_-u"],"type_ids":[3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3]},"_TjN4epIphuKUiHZJZdqxQ":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,138,138,16,100,12,4,6,4,38,38,10,38,174,104,68,30,56,382,1034444],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","JaHOMfnX0DG4ZnNTpPORVA","MepUYc0jU0AjPrrjuvTgGg","yWt46REABLfKH6PXLAE18A","VQs3Erq77xz92EfpT8sTKw","n7IiY_TlCWEfi47-QpeCLw","Ua3frjTXWBuWpTsQD8aKeA","GtyMRLq4aaDvuQ4C3N95mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","OwrnTUowquMzuETYoP67yQ","HmAocvtnsxREZJIec2I5gw","KHDki7BxJPyjGLtvY8M5lQ","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK","JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK","MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ","yWt46REABLfKH6PXLAE18AAAAAAAAABk","VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM","n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE","Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG","GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","OwrnTUowquMzuETYoP67yQAAAAAAAAAe","HmAocvtnsxREZJIec2I5gwAAAAAAAAA4","KHDki7BxJPyjGLtvY8M5lQAAAAAAAAF-","G68hjsyagwq6LpWrMjDdngAAAAAAD8jM"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3]},"ZQdwkmvvmLjNzNpTA4PPhw":{"address_or_lines":[25326,27384,368,1756,2573747,2594708,1091475,48726,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,64878,2789627,1482889,1482415,2595076,1079485,21616,35686,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,27398,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,51982,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,58138,2790352,1482889,1482415,2595076,1067375,25326,27210,32160,46288],"file_ids":["ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","XlQ19HBD_RNa2r3QWOR-nA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","VuJFonCXevADcEDW6NVbKg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","VFBd9VqCaQu0ZzjQ2K3pjg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","PUSucJs4FC_WdMzOyH3QYw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","lOUbi56SanKTCh9Y7fIwDw","it1vvnZdXdzy0fFROnaaOQ"],"frame_ids":["ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAL5W","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","XlQ19HBD_RNa2r3QWOR-nAAAAAAAAP1u","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAFRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAItm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","VuJFonCXevADcEDW6NVbKgAAAAAAAGsG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","VFBd9VqCaQu0ZzjQ2K3pjgAAAAAAAMsO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","PUSucJs4FC_WdMzOyH3QYwAAAAAAAOMa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEElv","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGpK","lOUbi56SanKTCh9Y7fIwDwAAAAAAAH2g","it1vvnZdXdzy0fFROnaaOQAAAAAAALTQ"],"type_ids":[1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1]},"ssC7MBcE9kfM3yTim7UrNQ":{"address_or_lines":[4846,6904,45424,50908,2573747,2594708,1091475,58102,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50908,2573747,2594708,1091475,48494,2789627,1482889,1482415,2595076,1079485,1136,15206,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50908,2573747,2594708,1091475,27398,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50908,2573747,2594708,1091475,2830,2790352,1482889,1482415,2595076,1073749,4846,6904,45424,50908,2573747,2594708,1091475,4586,2790352,1482889,1482415,2595076,1067395,4846,6904,45240,53006,54142],"file_ids":["ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","XlQ19HBD_RNa2r3QWOR-nA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","VuJFonCXevADcEDW6NVbKg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","VFBd9VqCaQu0ZzjQ2K3pjg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","PUSucJs4FC_WdMzOyH3QYw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","0S3htaCNkzxOYeavDR1GTQ","gZooqVYiItnHim-lK4feOg"],"frame_ids":["ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAOL2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","XlQ19HBD_RNa2r3QWOR-nAAAAAAAAL1u","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAARw","eV_m28NnKeeTL60KO2H3SAAAAAAAADtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","VuJFonCXevADcEDW6NVbKgAAAAAAAGsG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","VFBd9VqCaQu0ZzjQ2K3pjgAAAAAAAAsO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","PUSucJs4FC_WdMzOyH3QYwAAAAAAABHq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEEmD","ik6PIX946fW_erE7uBJlVQAAAAAAABLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4","J1eggTwSzYdi9OsSu1q37gAAAAAAALC4","0S3htaCNkzxOYeavDR1GTQAAAAAAAM8O","gZooqVYiItnHim-lK4feOgAAAAAAANN-"],"type_ids":[1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1]},"-yH5iqJp4uVN6clNHuFusA":{"address_or_lines":[2578675,2599636,1091600,5350,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1091600,6974,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1091600,5866,2795776,1483241,1482767,2600004,1079483,3150,4692,13478,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1091600,58134,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12612,2578675,2599636,1091600,10246,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,12396,342,41610,19187,41240,50663],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","UfGck3qA2qF0xFB5gpY4Hg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","G9ShE3ODivDEFyHVdsnZ_g","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","6AsJ0dA2BUqaic-ScDJBMA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","VY0EiAO0DxwLRTE4PfFhdw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","A8AozG5gQfEN24i4IE7w5w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","UfGck3qA2qF0xFB5gpY4HgAAAAAAABTm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","G9ShE3ODivDEFyHVdsnZ_gAAAAAAABs-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","6AsJ0dA2BUqaic-ScDJBMAAAAAAAABbq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABJU","eV_m28NnKeeTL60KO2H3SAAAAAAAADSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","VY0EiAO0DxwLRTE4PfFhdwAAAAAAAOMW","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","A8AozG5gQfEN24i4IE7w5wAAAAAAACgG","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAADBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAAFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAKKK","ASi9f26ltguiwFajNwOaZwAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMXn"],"type_ids":[3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"SrSwvDbs2pmPg3SRfXJBCA":{"address_or_lines":[1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,10978,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,35610,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,11318,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,15678,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,250,2790352,1482889,1482415,2595076,1076587,29422,31480,4464,17976,33110,51586,2846655,2846347,2843929,2840766,2843907,2841214,1439462],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","V6gUZHzBRISi-Z25klK5DQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zWNEoAKVTnnzSns045VKhw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","n4Ao4OZE2osF0FygfcWo3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","NGbZlnLCqeq3LFq89r_SpQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","PmhxUKv5sePRxhCBONca8g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","V6gUZHzBRISi-Z25klK5DQAAAAAAACri","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zWNEoAKVTnnzSns045VKhwAAAAAAAIsa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","n4Ao4OZE2osF0FygfcWo3gAAAAAAACw2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","NGbZlnLCqeq3LFq89r_SpQAAAAAAAD0-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","PmhxUKv5sePRxhCBONca8gAAAAAAAAD6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAMmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UD","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1p-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFfbm"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3]},"n5nFiHsDS01AKuzFKvQXdA":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,138,138,16,100,12,4,6,4,38,174,104,68,302,38,174,104,68,382,120,38,258,658,1111840,1034048],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","JaHOMfnX0DG4ZnNTpPORVA","MepUYc0jU0AjPrrjuvTgGg","yWt46REABLfKH6PXLAE18A","VQs3Erq77xz92EfpT8sTKw","n7IiY_TlCWEfi47-QpeCLw","Ua3frjTXWBuWpTsQD8aKeA","GtyMRLq4aaDvuQ4C3N95mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","clFhkTaiph2aOjCNuZDWKA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","OPpnYj88CDOiKneikdGPHA","ZJjPF65K8mBuISvhCfKfBg","xLxhp_367a_SbgOYuEJjlw","QHotkhNTqx5C4Kjd2F2_6w","Ht79I_xqXv3bOgaClTNQ4w","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK","JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK","MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ","yWt46REABLfKH6PXLAE18AAAAAAAAABk","VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM","n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE","Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG","GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","clFhkTaiph2aOjCNuZDWKAAAAAAAAAEu","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","OPpnYj88CDOiKneikdGPHAAAAAAAAAF-","ZJjPF65K8mBuISvhCfKfBgAAAAAAAAB4","xLxhp_367a_SbgOYuEJjlwAAAAAAAAAm","QHotkhNTqx5C4Kjd2F2_6wAAAAAAAAEC","Ht79I_xqXv3bOgaClTNQ4wAAAAAAAAKS","G68hjsyagwq6LpWrMjDdngAAAAAAEPcg","G68hjsyagwq6LpWrMjDdngAAAAAAD8dA"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3]},"XbtNNAnLtuHwAR-P2ynwqA":{"address_or_lines":[1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1091600,46454,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1091600,17534,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1091600,64182,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1091600,22670,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1079669,35024,1482046,1829360,2586325,1480953,1480561,1940968,1986869,1946031,1991239,1990411,1912997,3078008,3077552,3072071,1641674,3069796],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","pv4wAezdMMO0SVuGgaEMTg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","qns5vQ3LMi6QrIMOgD_TwQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","J_Lkq1OzUHxWQhnTgF6FwA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","XkOSW26Xa6_lkqHv5givKg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","aD-GPAkaW-Swis8ybNgyMQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","pv4wAezdMMO0SVuGgaEMTgAAAAAAALV2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","qns5vQ3LMi6QrIMOgD_TwQAAAAAAAER-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","J_Lkq1OzUHxWQhnTgF6FwAAAAAAAAPq2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","XkOSW26Xa6_lkqHv5givKgAAAAAAAFiO","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","aD-GPAkaW-Swis8ybNgyMQAAAAAAAIjQ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3bV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHlE1","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHbGv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHmJH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHl8L","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHTCl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALvd4","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALvWw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALuBH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAGQzK","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALtdk"],"type_ids":[3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"Rr1Z3cNxrq9AQiD8wZZ1dA":{"address_or_lines":[2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,9150,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,52246,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,48350,2789627,1482889,1482415,2595076,1079485,21616,35686,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1079144,37050,1481694,1828960,2581297,2595076,1079144,2994,1480209,1940645,1970099,1481300,1480601,2595076,1067831,41714,39750,33948,33384,25926,33098,33348,34466,32098,39462],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","HENgRXYeEs7mDD8Gk_MNmg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fFS0upy5lIaT99RhlTN5LQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","lSdGU4igLMOpLhL_6XP15w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","QAp_Nt6XUeNsCXnAUgW7Xg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","20O937106XMbOD0LQR4SPw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","gPzb0fXoBe1225fbKepMRA","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","OHQX9IWLaZElAgxGbX3P5g","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","JrU1PwRIxl_8SXdnTESnog"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","HENgRXYeEs7mDD8Gk_MNmgAAAAAAACO-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","fFS0upy5lIaT99RhlTN5LQAAAAAAAMwW","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","lSdGU4igLMOpLhL_6XP15wAAAAAAALze","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAFRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAItm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","QAp_Nt6XUeNsCXnAUgW7XgAAAAAAAJC6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2Mx","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","20O937106XMbOD0LQR4SPwAAAAAAAAuy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZyl","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg-z","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEEs3","gPzb0fXoBe1225fbKepMRAAAAAAAAKLy","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAISc","_lF8o5tJDcePvza_IYtgSQAAAAAAAIJo","OHQX9IWLaZElAgxGbX3P5gAAAAAAAGVG","E2b-mzlh_8261-JxcySn-AAAAAAAAIFK","E2b-mzlh_8261-JxcySn-AAAAAAAAIJE","E2b-mzlh_8261-JxcySn-AAAAAAAAIai","E2b-mzlh_8261-JxcySn-AAAAAAAAH1i","JrU1PwRIxl_8SXdnTESnogAAAAAAAJom"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1]},"gESQTq4qRn3wnW-FPfxOfA":{"address_or_lines":[2790352,1482889,1482415,2595076,1079485,62190,63732,7014,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,43746,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,2842,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,48542,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1050939,4144,1371605,1977020,2595076,1079485,8954,1479772,3459845,1479516,2595076,1072525,58674,1646337,3072295,1865241,10490014,423063,2283967,2281306,2510155,2414579,2398792,2385273,8471624],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","V6gUZHzBRISi-Z25klK5DQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zWNEoAKVTnnzSns045VKhw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","n4Ao4OZE2osF0FygfcWo3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","lTFhQHSZwvS4-s94KVv5mA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","IcJVDEq52FRv22q0yHVMaw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","BDtQyw375W96A0PA_Z7SDQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPj0","eV_m28NnKeeTL60KO2H3SAAAAAAAABtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","V6gUZHzBRISi-Z25klK5DQAAAAAAAKri","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zWNEoAKVTnnzSns045VKhwAAAAAAAAsa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","n4Ao4OZE2osF0FygfcWo3gAAAAAAAL2e","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEAk7","lTFhQHSZwvS4-s94KVv5mAAAAAAAABAw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFO3V","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHiq8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","IcJVDEq52FRv22q0yHVMawAAAAAAACL6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpRc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAANMsF","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEF2N","BDtQyw375W96A0PA_Z7SDQAAAAAAAOUy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGR8B","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuEn","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHYZ","A2oiHVwisByxRn5RDT4LjAAAAAAAoBCe","A2oiHVwisByxRn5RDT4LjAAAAAAABnSX","A2oiHVwisByxRn5RDT4LjAAAAAAAItm_","A2oiHVwisByxRn5RDT4LjAAAAAAAIs9a","A2oiHVwisByxRn5RDT4LjAAAAAAAJk1L","A2oiHVwisByxRn5RDT4LjAAAAAAAJNfz","A2oiHVwisByxRn5RDT4LjAAAAAAAJJpI","A2oiHVwisByxRn5RDT4LjAAAAAAAJGV5","A2oiHVwisByxRn5RDT4LjAAAAAAAgURI"],"type_ids":[3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,1,3,3,3,3,3,1,3,3,3,4,4,4,4,4,4,4,4,4]},"CSpdzACT53hVs5DyKY8X5A":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,33156,1058,33388,19218,13654,16860,52596,11060,58864,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,36842,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,30778,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,47130,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,51886,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1592,33110,6110,3227324,1844695,1847563,1702665,1680736,1865128],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","skFt9oVHBFfMDC1On4IJhg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","g5zhfSuJlGbmNqPl5Qb2wg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","UoMth5MLnZ-vUHeTplwEvA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAADVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAEHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAM10","xwuAPHgc12-8PZB3i-320gAAAAAAACs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAOXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAI_q","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","skFt9oVHBFfMDC1On4IJhgAAAAAAAHg6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","g5zhfSuJlGbmNqPl5Qb2wgAAAAAAALga","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","UoMth5MLnZ-vUHeTplwEvAAAAAAAAMqu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAABfe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMT68","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDEL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGfsJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGaVg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHWo"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3]},"AlH3zgnqwh5sdMMzX8AXxg":{"address_or_lines":[1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,52130,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,61558,2790352,1482889,1482415,2595076,1079485,25326,26868,35686,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,8770,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,17970,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1066158,3868,39750,21660,21058,64084,29144,22318,29144,18030,1840882,1970521,2595076,1049850,1910],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","OlTvyWQFXjOweJcs3kiGyg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N2mxDWkAZe8CHgZMQpxZ7A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","1eW8DnM19kiBGqMWGVkHPA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2kgk5qEgdkkSXT9cIdjqxQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MsEmysGbXhMvgdbwhcZDCg","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Gxt7_MN7XgUOe9547JcHVQ"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","OlTvyWQFXjOweJcs3kiGygAAAAAAAMui","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAPB2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGj0","eV_m28NnKeeTL60KO2H3SAAAAAAAAItm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","1eW8DnM19kiBGqMWGVkHPAAAAAAAACJC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2kgk5qEgdkkSXT9cIdjqxQAAAAAAAEYy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEESu","MsEmysGbXhMvgdbwhcZDCgAAAAAAAA8c","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAFSc","_lF8o5tJDcePvza_IYtgSQAAAAAAAFJC","TRd7r6mvdzYdjMdTtebtwwAAAAAAAPpU","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAHHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAFcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAHHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAEZu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHBby","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHhFZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEAT6","Gxt7_MN7XgUOe9547JcHVQAAAAAAAAd2"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,1]},"ysEqok7gFOl9eLMLBwFm1g":{"address_or_lines":[29422,31480,4464,18140,2573747,2594708,1091475,64774,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,18042,2789627,1482889,1482415,2595076,1079485,25712,39782,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,2618,2790352,1482889,1482415,2595076,1079144,29422,31306,36256,31544,18122,5412,1481694,1829583,2567913,1848405,1978470,1481567,1493928,2595076,1079144,54286,19054,47612,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1073749,55752,56134,25756,25504,3350479,3072521,1865128],"file_ids":["ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","XkOSW26Xa6_lkqHv5givKg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2L4SW1rQgEVXRj3pZAI3nQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","7bd6QJSfWZZfOOpDMHqLMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","lOUbi56SanKTCh9Y7fIwDw","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","J3wpF3Lf_vPkis4aNGKFbw","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","XkOSW26Xa6_lkqHv5givKgAAAAAAAP0G","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2L4SW1rQgEVXRj3pZAI3nQAAAAAAAEZ6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAGRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAJtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","7bd6QJSfWZZfOOpDMHqLMAAAAAAAAAo6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHpK","lOUbi56SanKTCh9Y7fIwDwAAAAAAAI2g","8R2Lkqe-tYqq-plJ22QNzAAAAAAAAHs4","h0l-9tGi18mC40qpcJbyDwAAAAAAAEbK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAABUk","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-rP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy7p","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDRV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHjBm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","705jmHYNd7I4Z4L4c0vfiAAAAAAAANQO","TBeSzkyqIwKL8td602zDjAAAAAAAAEpu","NH3zvSjFAfTSy6bEocpNyQAAAAAAALn8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","J3wpF3Lf_vPkis4aNGKFbwAAAAAAANnI","jtp3NDFNJGnK6sK5oOFo8QAAAAAAANtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAGSc","_lF8o5tJDcePvza_IYtgSQAAAAAAAGOg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMx_P","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuIJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHWo"],"type_ids":[1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,1,3,3,3]},"7B48NKNivOFEka6-8dK3Qg":{"address_or_lines":[2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,8722,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,20598,2790352,1482889,1482415,2595076,1079485,33518,35060,43878,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,41538,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,40098,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1074318,25764,6982,46236,45634,23124,53720,46894,53720,46894,53720,46894,53720,47420,41028,1347096],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","OlTvyWQFXjOweJcs3kiGyg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N2mxDWkAZe8CHgZMQpxZ7A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","1eW8DnM19kiBGqMWGVkHPA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2kgk5qEgdkkSXT9cIdjqxQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MsEmysGbXhMvgdbwhcZDCg","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","zpgqltXEgKujOhJUj-jAhg","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","OlTvyWQFXjOweJcs3kiGygAAAAAAACIS","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAFB2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIj0","eV_m28NnKeeTL60KO2H3SAAAAAAAAKtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","1eW8DnM19kiBGqMWGVkHPAAAAAAAAKJC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2kgk5qEgdkkSXT9cIdjqxQAAAAAAAJyi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGSO","MsEmysGbXhMvgdbwhcZDCgAAAAAAAGSk","jtp3NDFNJGnK6sK5oOFo8QAAAAAAABtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAALSc","_lF8o5tJDcePvza_IYtgSQAAAAAAALJC","TRd7r6mvdzYdjMdTtebtwwAAAAAAAFpU","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALk8","zpgqltXEgKujOhJUj-jAhgAAAAAAAKBE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFI4Y"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3]},"OC533YmmMZSw8TjJz41YiQ":{"address_or_lines":[19534,21076,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,33092,2578675,2599636,1091600,27150,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,33092,2578675,2599636,1091600,42322,2795776,1483241,1482767,2600004,1079483,19534,21076,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1079483,19534,21076,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,33092,2578675,2599636,1091600,30298,2795051,1483241,1482767,2600004,1079483,15824,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,32876,16726,62090,20547,1659254,1860268],"file_ids":["LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","6GGFIt18C0VByIn0h-PdeQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","SA64oIT_DC3uHXf7ZjFqkw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","akZOzI9XwsEixvkTDGeDPw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","6GGFIt18C0VByIn0h-PdeQAAAAAAAGoO","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","SA64oIT_DC3uHXf7ZjFqkwAAAAAAAKVS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","akZOzI9XwsEixvkTDGeDPwAAAAAAAHZa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAPKK","ASi9f26ltguiwFajNwOaZwAAAAAAAFBD","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAGVF2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHGKs"],"type_ids":[1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"X6-W250nbzzPy4NasjncWg":{"address_or_lines":[23630,25514,30464,8440,12298,26148,1482046,1829983,2572841,1848805,1978934,1481919,1494280,2600004,1079669,38814,1470,22780,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,324,2578675,2599636,1091600,51026,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,324,2578675,2599636,1091600,47386,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,324,2578675,2599636,1091600,19506,2795051,1483241,1482767,2600004,1079483,19920,33958,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1073803,23630,25688,64176,108,16726,29410,2852079,2851771,2849353,2846190,2849331,2846638,1439925,1865566,1029925,10490014,422731,937148],"file_ids":["LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","ZPxtkRXufuVf4tqV5k5k2Q","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fj70ljef7nDHOqVJGSIoEQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","AtF9VdLKnFQvB9H1lsFPjA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Pf1McBfrZjVj1CxRZBq6Yw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGOq","ZPxtkRXufuVf4tqV5k5k2QAAAAAAAHcA","8R2Lkqe-tYqq-plJ22QNzAAAAAAAACD4","h0l-9tGi18mC40qpcJbyDwAAAAAAADAK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAAGYk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-xf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0Ip","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDXl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHjI2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpy_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","705jmHYNd7I4Z4L4c0vfiAAAAAAAAJee","TBeSzkyqIwKL8td602zDjAAAAAAAAAW-","NH3zvSjFAfTSy6bEocpNyQAAAAAAAFj8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","fj70ljef7nDHOqVJGSIoEQAAAAAAAMdS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","AtF9VdLKnFQvB9H1lsFPjAAAAAAAALka","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","Pf1McBfrZjVj1CxRZBq6YwAAAAAAAEwy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAE3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAISm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAABs","p5XvqZgoydjTl8thPo5KGwAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAHLi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3oz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK2-u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFfi1","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHHde","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAD7cl","ew01Dk0sWZctP-VaEpavqQAAAAAAoBCe","ew01Dk0sWZctP-VaEpavqQAAAAAABnNL","ew01Dk0sWZctP-VaEpavqQAAAAAADky8"],"type_ids":[1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,4,4,4]},"gi6S4ODPtJ-ERYxlMd4WHA":{"address_or_lines":[2795776,1483241,1482767,2600004,1074397,60494,62552,35504,61764,2578675,2599636,1091600,55462,2795776,1483241,1482767,2600004,1074397,60494,62552,35504,61764,2578675,2599636,1091600,63874,2795776,1483241,1482767,2600004,1074397,60494,62552,35504,61764,2578675,2599636,1074067,0,29636,2577481,2934013,1108250,1105981,1310350,1245864,1200348,1190613,1198830,1177316,1176308,1173405,1172711,1172023,1171335,1170723,1169827,1169015,1167328,1166449,1165561,1146206,1245475,1198830,1177316,1176308,1173405,1172711,1172023,1171335,1170723,1169827,1169015,1167328,1166449,1165783,1162744,1226823,1225457,1224431,1198830,1177316,1176308,1173405,1172711,1172023,1171335,1170723,1169827,1169015,1167328,1166449,1165323,1165909],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","XkOSW26Xa6_lkqHv5givKg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","2L4SW1rQgEVXRj3pZAI3nQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","_____________________w","vkeP2ntYyoFN0A16x9eliw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAOxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAPRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAIqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","XkOSW26Xa6_lkqHv5givKgAAAAAAANim","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAOxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAPRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAIqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","2L4SW1rQgEVXRj3pZAI3nQAAAAAAAPmC","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAOxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAPRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAIqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGOT","_____________________wAAAAAAAAAA","vkeP2ntYyoFN0A16x9eliwAAAAAAAHPE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1RJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALMT9","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEOka","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEOA9","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAE_6O","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEwKo","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAElDc","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEirV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEkru","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfbk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfL0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeed","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeTn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeI3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd-H","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd0j","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdmj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdZ3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEc_g","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcxx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEX1e","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEwEj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEkru","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfbk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfL0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeed","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeTn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeI3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd-H","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd0j","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdmj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdZ3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEc_g","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcxx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcnX","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEb34","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAErhH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAErLx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEq7v","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEkru","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfbk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEfL0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeed","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeTn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEeI3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd-H","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEd0j","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdmj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEdZ3","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEc_g","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcxx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcgL","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEcpV"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"EGm59IOxpyqZq7sEwgZb1g":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,138,138,16,100,12,4,6,4,38,174,104,68,8,38,174,104,68,32,38,174,104,68,36,38,174,104,68,16,140,10,38,174,104,68,48,1992440,1112453,1098694,1112047],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","JaHOMfnX0DG4ZnNTpPORVA","MepUYc0jU0AjPrrjuvTgGg","yWt46REABLfKH6PXLAE18A","VQs3Erq77xz92EfpT8sTKw","n7IiY_TlCWEfi47-QpeCLw","Ua3frjTXWBuWpTsQD8aKeA","GtyMRLq4aaDvuQ4C3N95mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","clFhkTaiph2aOjCNuZDWKA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","DLEY7W0VXWLE5Ol-plW-_w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","RY-vzTa9LfseI7kmcIcbgQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","H5LY_MytOVgyAawi8TymCg","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","kUJz0cDHgh-y1O5Hi8equA","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK","JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK","MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ","yWt46REABLfKH6PXLAE18AAAAAAAAABk","VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM","n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE","Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG","GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAk","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","H5LY_MytOVgyAawi8TymCgAAAAAAAAAQ","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","kUJz0cDHgh-y1O5Hi8equAAAAAAAAAAw","G68hjsyagwq6LpWrMjDdngAAAAAAHmb4","G68hjsyagwq6LpWrMjDdngAAAAAAEPmF","G68hjsyagwq6LpWrMjDdngAAAAAAEMPG","G68hjsyagwq6LpWrMjDdngAAAAAAEPfv"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3]},"y7cw8NxReMWOs4KtDlMCFA":{"address_or_lines":[40014,41898,46848,24824,28682,42532,1482046,1829983,2572841,1848805,1978934,1481919,1494280,2600004,1079669,55198,17854,39164,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,40014,42072,15024,28996,2578675,2599636,1091600,11362,2795776,1483241,1482767,2600004,1074397,40014,42072,15024,28996,2578675,2599636,1091600,14618,2795776,1483241,1482767,2600004,1074397,40014,42072,15024,28996,2578675,2599636,1091600,22130,2795051,1483241,1482767,2600004,1079483,36304,50342,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1079669,40014,42072,15024,28780,33110,57790,1480561,1827950,3236393,1482344,1535086,3273255,1482344,1535086,3245980,67155,10485923,16964,15598,703171,2759460,3901948,3791884,3567755],"file_ids":["LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","ZPxtkRXufuVf4tqV5k5k2Q","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fj70ljef7nDHOqVJGSIoEQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","AtF9VdLKnFQvB9H1lsFPjA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Pf1McBfrZjVj1CxRZBq6Yw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","eOfhJQFIxbIEScd007tROw","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKOq","ZPxtkRXufuVf4tqV5k5k2QAAAAAAALcA","8R2Lkqe-tYqq-plJ22QNzAAAAAAAAGD4","h0l-9tGi18mC40qpcJbyDwAAAAAAAHAK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAAKYk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-xf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0Ip","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDXl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHjI2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpy_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","705jmHYNd7I4Z4L4c0vfiAAAAAAAANee","TBeSzkyqIwKL8td602zDjAAAAAAAAEW-","NH3zvSjFAfTSy6bEocpNyQAAAAAAAJj8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","fj70ljef7nDHOqVJGSIoEQAAAAAAACxi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","AtF9VdLKnFQvB9H1lsFPjAAAAAAAADka","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","Pf1McBfrZjVj1CxRZBq6YwAAAAAAAFZy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAI3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAMSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY","J1eggTwSzYdi9OsSu1q37gAAAAAAADqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAOG-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-Ru","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAMWIp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp5o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAF2xu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAMfIn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp5o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAF2xu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAMYec","eOfhJQFIxbIEScd007tROwAAAAAAAQZT","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEJE","ew01Dk0sWZctP-VaEpavqQAAAAAAADzu","ew01Dk0sWZctP-VaEpavqQAAAAAACrrD","ew01Dk0sWZctP-VaEpavqQAAAAAAKhsk","ew01Dk0sWZctP-VaEpavqQAAAAAAO4n8","ew01Dk0sWZctP-VaEpavqQAAAAAAOdwM","ew01Dk0sWZctP-VaEpavqQAAAAAANnCL"],"type_ids":[1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"L1ZLG1mjktr2Zy0xiQnH0w":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,138,138,16,100,12,4,6,4,38,174,104,68,8,38,174,104,68,32,38,174,104,68,24,140,10,38,174,104,68,178,1090933,1814182,788459,788130,1197048,1243204,1201241,1245991,1245236,1171829,2265239,2264574,2258463,1169067],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","JaHOMfnX0DG4ZnNTpPORVA","MepUYc0jU0AjPrrjuvTgGg","yWt46REABLfKH6PXLAE18A","VQs3Erq77xz92EfpT8sTKw","n7IiY_TlCWEfi47-QpeCLw","Ua3frjTXWBuWpTsQD8aKeA","GtyMRLq4aaDvuQ4C3N95mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","clFhkTaiph2aOjCNuZDWKA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","DLEY7W0VXWLE5Ol-plW-_w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","RY-vzTa9LfseI7kmcIcbgQ","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","-gq3a70QOgdn9HetYyf2Og","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK","JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK","MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ","yWt46REABLfKH6PXLAE18AAAAAAAAABk","VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM","n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE","Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG","GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAY","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","-gq3a70QOgdn9HetYyf2OgAAAAAAAACy","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","G68hjsyagwq6LpWrMjDdngAAAAAAG66m","G68hjsyagwq6LpWrMjDdngAAAAAADAfr","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkP4","G68hjsyagwq6LpWrMjDdngAAAAAAEvhE","G68hjsyagwq6LpWrMjDdngAAAAAAElRZ","G68hjsyagwq6LpWrMjDdngAAAAAAEwMn","G68hjsyagwq6LpWrMjDdngAAAAAAEwA0","G68hjsyagwq6LpWrMjDdngAAAAAAEeF1","G68hjsyagwq6LpWrMjDdngAAAAAAIpCX","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAInYf","G68hjsyagwq6LpWrMjDdngAAAAAAEdar"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3]}},"stack_frames":{"ew01Dk0sWZctP-VaEpavqQAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEqXT":{"file_name":[],"function_name":["__x64_sys_futex"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEpy8":{"file_name":[],"function_name":["do_futex"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEm_I":{"file_name":[],"function_name":["futex_wake"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAC75T":{"file_name":[],"function_name":["wake_up_q"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAC7oE":{"file_name":[],"function_name":["try_to_wake_up"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAgljd":{"file_name":[],"function_name":["__lock_text_start"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAEqQj":{"file_name":[],"function_name":["__x64_sys_futex"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAEpsM":{"file_name":[],"function_name":["do_futex"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAEm4Y":{"file_name":[],"function_name":["futex_wake"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAC75D":{"file_name":[],"function_name":["wake_up_q"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAC7n0":{"file_name":[],"function_name":["try_to_wake_up"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAgkRd":{"file_name":[],"function_name":["__lock_text_start"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKv6U":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKs2k":{"file_name":[],"function_name":["lookup_fast"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAM58I":{"file_name":[],"function_name":["kernfs_dop_revalidate"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAgMJg":{"file_name":[],"function_name":["strcmp"],"function_offset":[],"line_number":[]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5":{"file_name":["../csu/libc-start.c"],"function_name":["__libc_start_main"],"function_offset":[],"line_number":[308]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAZVI":{"file_name":["libmount/src/tab_parse.c"],"function_name":["__mnt_table_parse_mtab"],"function_offset":[],"line_number":[1102]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAY-W":{"file_name":["libmount/src/tab_parse.c"],"function_name":["mnt_table_parse_file"],"function_offset":[],"line_number":[707]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAXu2":{"file_name":["libmount/src/tab_parse.c","libmount/src/tab_parse.c","/usr/include/bits/stdio.h"],"function_name":["mnt_table_parse_stream","mnt_table_parse_next","getline"],"function_offset":[],"line_number":[643,453,117]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAABrQw":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/libio/iogetdelim.c"],"function_name":["_IO_getdelim"],"function_offset":[],"line_number":[114]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB20S":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/libio/fileops.c"],"function_name":["_IO_new_file_underflow"],"function_offset":[],"line_number":[584]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALKCV":{"file_name":[],"function_name":["seq_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALspQ":{"file_name":[],"function_name":["show_mountinfo"],"function_offset":[],"line_number":[]},"LHNvPtcKBt87cCBX8aTNhQAAAAAAABD4":{"file_name":[],"function_name":["ovl_show_options"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALKWO":{"file_name":[],"function_name":["seq_escape"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAgL-e":{"file_name":[],"function_name":["strlen"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK2w1":{"file_name":[],"function_name":["__x64_sys_getdents64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK2uM":{"file_name":[],"function_name":["ksys_getdents64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK1v8":{"file_name":[],"function_name":["iterate_dir"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAMuWZ":{"file_name":[],"function_name":["proc_pid_readdir"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAMrzu":{"file_name":[],"function_name":["next_tgid"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAACq1j":{"file_name":[],"function_name":["pid_nr_ns"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqah":{"file_name":[],"function_name":["__x64_sys_pipe2"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqYM":{"file_name":[],"function_name":["do_pipe2"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqU7":{"file_name":[],"function_name":["__do_pipe_flags"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqN7":{"file_name":[],"function_name":["create_pipe_files"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZnfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAePFy":{"file_name":[],"function_name":["unix_stream_recvmsg"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAeOpA":{"file_name":[],"function_name":["unix_stream_read_generic"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAeMVZ":{"file_name":[],"function_name":["unix_stream_read_actor"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ7u6":{"file_name":[],"function_name":["skb_copy_datagram_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ7kW":{"file_name":[],"function_name":["__skb_datagram_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ7iE":{"file_name":[],"function_name":["simple_copy_to_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKZiW":{"file_name":[],"function_name":["__check_object_size"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKg5J":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALB_i":{"file_name":[],"function_name":["__fdget_pos"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALBST":{"file_name":[],"function_name":["__fget_light"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAAEFn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKcUM":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKxcK":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKu8M":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKsyL":{"file_name":[],"function_name":["link_path_walk.part.33"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKsbn":{"file_name":[],"function_name":["walk_component"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKr18":{"file_name":[],"function_name":["lookup_fast"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKqx4":{"file_name":[],"function_name":["follow_managed"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAALEDf":{"file_name":[],"function_name":["lookup_mnt"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAALEA_":{"file_name":[],"function_name":["__lookup_mnt"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKsyx":{"file_name":[],"function_name":["link_path_walk.part.33"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKrUd":{"file_name":[],"function_name":["inode_permission"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAMzQW":{"file_name":[],"function_name":["kernfs_iop_permission"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAgRuk":{"file_name":[],"function_name":["mutex_lock"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKhDw":{"file_name":[],"function_name":["ksys_write"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKg38":{"file_name":[],"function_name":["vfs_write"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKePq":{"file_name":[],"function_name":["new_sync_write"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnmG":{"file_name":[],"function_name":["sock_write_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnjq":{"file_name":[],"function_name":["sock_sendmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAePZt":{"file_name":[],"function_name":["unix_stream_sendmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAeOlF":{"file_name":[],"function_name":["maybe_add_creds"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAePaV":{"file_name":[],"function_name":["unix_stream_sendmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZrqL":{"file_name":[],"function_name":["sock_def_readable"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAADXb2":{"file_name":[],"function_name":["__wake_up_common_lock"],"function_offset":[],"line_number":[]},"lLD39yzd4Cg8F13tcGpzGQAAAAAAABuG":{"file_name":["pyi_rth_pkgutil.py"],"function_name":[""],"function_offset":[33],"line_number":[34]},"LEy-wm0GIvRoYVAga55HiwAAAAAAACxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAADRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAMqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAADFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"dCCKy6JoX0PADOFic8hRNQAAAAAAAB5A":{"file_name":["pkgutil.py"],"function_name":[""],"function_offset":[315],"line_number":[316]},"9w9lF96vJW7ZhBoZ8ETsBwAAAAAAAMum":{"file_name":["functools.py"],"function_name":["register"],"function_offset":[50],"line_number":[902]},"xUQuo4OgBaS_Le-fdAwt8AAAAAAAAIHw":{"file_name":["functools.py"],"function_name":["_is_union_type"],"function_offset":[2],"line_number":[843]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAADBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAEFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAKLi":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAOmg3":{"file_name":[],"function_name":["xfs_file_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAOmdC":{"file_name":[],"function_name":["xfs_file_buffered_aio_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAH0j-":{"file_name":[],"function_name":["generic_file_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAASkft":{"file_name":[],"function_name":["copy_page_to_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAASheR":{"file_name":[],"function_name":["copyout"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAgUyr":{"file_name":[],"function_name":["copy_user_generic_string"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAAEIE":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAADyu":{"file_name":[],"function_name":["exit_to_usermode_loop"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAACrwD":{"file_name":[],"function_name":["task_work_run"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKgtU":{"file_name":[],"function_name":["__fput"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAOyMM":{"file_name":[],"function_name":["xfs_release"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAOXPc":{"file_name":[],"function_name":["xfs_free_eofblocks"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAANg6m":{"file_name":[],"function_name":["xfs_bmapi_read"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAOB7F":{"file_name":[],"function_name":["xfs_iext_lookup_extent"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM":{"file_name":[],"function_name":["inet_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcpAW":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAczF0":{"file_name":[],"function_name":["tcp_v4_send_check"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKglI":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAdME8":{"file_name":[],"function_name":["inet_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcXqg":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZ0Bt":{"file_name":[],"function_name":["__kfree_skb"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZ0vq":{"file_name":[],"function_name":["skb_release_data"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAIAP0":{"file_name":[],"function_name":["__put_page"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYZj":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7wq":{"file_name":[],"function_name":["skb_copy_datagram_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7mG":{"file_name":[],"function_name":["__skb_datagram_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7j0":{"file_name":[],"function_name":["simple_copy_to_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKZkE":{"file_name":[],"function_name":["__check_object_size"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcqWe":{"file_name":[],"function_name":["__tcp_send_ack.part.47"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZz1R":{"file_name":[],"function_name":["__alloc_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZyV9":{"file_name":[],"function_name":["__kmalloc_reserve.isra.57"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAJ0bR":{"file_name":[],"function_name":["__kmalloc_node_track_caller"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAIdpk":{"file_name":[],"function_name":["kmalloc_slab"],"function_offset":[],"line_number":[]},"eOfhJQFIxbIEScd007tROwAAAAAAAHRK":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/nptl/pthread_create.c"],"function_name":["start_thread"],"function_offset":[],"line_number":[465]},"9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAmH_":{"file_name":["/usr/src/debug/openssl-1.0.2k/ssl/s3_clnt.c"],"function_name":["ssl3_connect"],"function_offset":[],"line_number":[345]},"9HZ7GQCC6G9fZlRD7aGzXQAAAAAAAhZ-":{"file_name":["/usr/src/debug/openssl-1.0.2k/ssl/s3_clnt.c"],"function_name":["ssl3_get_server_certificate"],"function_offset":[],"line_number":[1255]},"9HZ7GQCC6G9fZlRD7aGzXQAAAAAABFsM":{"file_name":["/usr/src/debug/openssl-1.0.2k/ssl/ssl_cert.c"],"function_name":["ssl_verify_cert_chain"],"function_offset":[],"line_number":[759]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFdR2":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_vfy.c"],"function_name":["X509_verify_cert"],"function_offset":[],"line_number":[261]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFh_7":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_lu.c"],"function_name":["X509_STORE_CTX_get1_issuer"],"function_offset":[],"line_number":[617]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFhe5":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_lu.c"],"function_name":["X509_STORE_get_by_subject"],"function_offset":[],"line_number":[306]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFhdI":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_lu.c"],"function_name":["X509_OBJECT_retrieve_by_subject"],"function_offset":[],"line_number":[480]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFhIu":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_lu.c"],"function_name":["x509_object_idx_cnt"],"function_offset":[],"line_number":[454]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAEiFg":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/stack/stack.c"],"function_name":["internal_find"],"function_offset":[],"line_number":[261]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAEiEp":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/stack/stack.c"],"function_name":["sk_sort"],"function_offset":[],"line_number":[374]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1v3":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/stdlib/msort.c","/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/stdlib/msort.c"],"function_name":["__GI___qsort_r","msort_with_tmp"],"function_offset":[],"line_number":[297,45]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1ku":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/stdlib/msort.c","/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/stdlib/msort.c"],"function_name":["msort_with_tmp","msort_with_tmp"],"function_offset":[],"line_number":[53,159]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1kh":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/stdlib/msort.c","/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/stdlib/msort.c"],"function_name":["msort_with_tmp","msort_with_tmp"],"function_offset":[],"line_number":[54,159]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAA1nF":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/stdlib/msort.c"],"function_name":["msort_with_tmp"],"function_offset":[],"line_number":[83]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFhE-":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_lu.c"],"function_name":["x509_object_cmp"],"function_offset":[],"line_number":[168]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFaMO":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_d2.c"],"function_name":["X509_STORE_load_locations"],"function_offset":[],"line_number":[94]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFjo2":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/by_file.c"],"function_name":["by_file_ctrl"],"function_offset":[],"line_number":[117]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFjjD":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/by_file.c"],"function_name":["X509_load_cert_crl_file"],"function_offset":[],"line_number":[261]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFUK9":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/pem/pem_info.c"],"function_name":["PEM_X509_INFO_read_bio"],"function_offset":[],"line_number":[248]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJOq":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["ASN1_item_d2i"],"function_offset":[],"line_number":[154]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJNU":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["ASN1_item_ex_d2i"],"function_offset":[],"line_number":[553]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_item_ex_d2i"],"function_offset":[],"line_number":[478]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_template_ex_d2i"],"function_offset":[],"line_number":[623]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_template_noexp_d2i"],"function_offset":[],"line_number":[735]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFIM9":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_item_ex_d2i"],"function_offset":[],"line_number":[266]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFB_E":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/x_name.c"],"function_name":["x509_name_ex_d2i"],"function_offset":[],"line_number":[235]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFBue":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/x_name.c"],"function_name":["x509_name_canon"],"function_offset":[],"line_number":[390]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFBbE":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/x_name.c"],"function_name":["i2d_name_canon"],"function_offset":[],"line_number":[508]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFGgQ":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_enc.c"],"function_name":["ASN1_item_ex_i2d"],"function_offset":[],"line_number":[148]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFG4p":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_enc.c"],"function_name":["asn1_template_ex_i2d"],"function_offset":[],"line_number":[360]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAEiC3":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/stack/stack.c"],"function_name":["sk_num"],"function_offset":[],"line_number":[344]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAMJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAIsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAHT2":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAIF8":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAA10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAGs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"ik6PIX946fW_erE7uBJlVQAAAAAAAILu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAACFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAMFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAFhE":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"A2oiHVwisByxRn5RDT4LjAAAAAAAOmcH":{"file_name":[],"function_name":["xfs_file_read_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAOmYS":{"file_name":[],"function_name":["xfs_file_buffered_aio_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAH0TO":{"file_name":[],"function_name":["generic_file_read_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAHynP":{"file_name":[],"function_name":["pagecache_get_page"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAHyFT":{"file_name":[],"function_name":["find_get_entry"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJa3-":{"file_name":[],"function_name":["PageHuge"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcpF4":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcNCx":{"file_name":[],"function_name":["__ip_queue_xmit"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcNYI":{"file_name":[],"function_name":["ip_output"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcK1g":{"file_name":[],"function_name":["ip_finish_output2"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaNFD":{"file_name":[],"function_name":["__dev_queue_xmit"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaMev":{"file_name":[],"function_name":["dev_hard_start_xmit"],"function_offset":[],"line_number":[]},"6miIyyucTZf5zXHCk7PT1gAAAAAAAAo8":{"file_name":[],"function_name":["veth_xmit"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaGU2":{"file_name":[],"function_name":["netif_rx"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaGS9":{"file_name":[],"function_name":["netif_rx_internal"],"function_offset":[],"line_number":[]},"a5aMcPOeWx28QSVng73nBQAAAAAAAAAw":{"file_name":["aws"],"function_name":[""],"function_offset":[5],"line_number":[19]},"OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[5],"line_number":[1007]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[19],"line_number":[986]},"XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[21],"line_number":[680]},"4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[499]},"79pMuEW6_o55K0jHDJ-2dQAAAAAAAADI":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[22],"line_number":[35]},"5sij7Z672VAK_gGoPDPJBgAAAAAAAAA8":{"file_name":["formatter.py"],"function_name":[""],"function_offset":[6],"line_number":[19]},"PCeTYI0HN2oKNST6e1IaQQAAAAAAAABc":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[50],"line_number":[51]},"U4FmFVJMlNKhF1hVl3Xj1AAAAAAAAAAE":{"file_name":["cyaml.py"],"function_name":[""],"function_offset":[0],"line_number":[3]},"JR7ekk9KGQJKKPohpdwCLQAAAAAAAAAK":{"file_name":["_bootstrap_external.py"],"function_name":["exec_module"],"function_offset":[2],"line_number":[1181]},"zP58DjIs7uq1cghmzykyNAAAAAAAAAAK":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[228]},"rpRn_rYC3CgtEgBAUrkZZgAAAAAAAAAU":{"file_name":["error.py"],"function_name":[""],"function_offset":[3],"line_number":[6]},"4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[13],"line_number":[482]},"NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[14],"line_number":[298]},"coeZ_4yf5sOePIKKlm8FNQAAAAAAAAC-":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[18],"line_number":[304]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAABbM":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAACrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAADAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAAdM":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAJQW":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"ne8F__HPIVgxgycJADVSzAAAAAAAAB9A":{"file_name":["clidriver.py"],"function_name":["invoke"],"function_offset":[29],"line_number":[930]},"CwUjPVV5_7q7c0GhtW0aPwAAAAAAALcE":{"file_name":["session.py"],"function_name":["create_client"],"function_offset":[112],"line_number":[848]},"okehWevKsEA4q6dk779jgwAAAAAAAH1M":{"file_name":["session.py"],"function_name":["get_credentials"],"function_offset":[12],"line_number":[445]},"-IuadWGT89NVzIyF_EmodwAAAAAAAMKw":{"file_name":["credentials.py"],"function_name":["load_credentials"],"function_offset":[18],"line_number":[1953]},"XXJY7v4esGWnaxtMW3FA0gAAAAAAAJ08":{"file_name":["credentials.py"],"function_name":["load"],"function_offset":[18],"line_number":[1009]},"FbrXdcA4j750RyQ3q9JXMwAAAAAAAIKa":{"file_name":["utils.py"],"function_name":["retrieve_iam_role_credentials"],"function_offset":[30],"line_number":[517]},"pL34QuyxyP6XYzGDBMK_5wAAAAAAAH_a":{"file_name":["utils.py"],"function_name":["_get_iam_role"],"function_offset":[1],"line_number":[524]},"IoAk4kM-M4DsDPp7ia5QXwAAAAAAAKvK":{"file_name":["utils.py"],"function_name":["_get_request"],"function_offset":[32],"line_number":[435]},"uHLoBslr3h6S7ooNeXzEbwAAAAAAAJQ8":{"file_name":["httpsession.py"],"function_name":["send"],"function_offset":[56],"line_number":[487]},"iRoTPXvR_cRsnzDO-aurpQAAAAAAAHbc":{"file_name":["connectionpool.py"],"function_name":["urlopen"],"function_offset":[361],"line_number":[894]},"fB79lJck2X90l-j7VqPR-QAAAAAAAGc8":{"file_name":["connectionpool.py"],"function_name":["_make_request"],"function_offset":[116],"line_number":[494]},"gbMheDI1NZ3NY96J0seddgAAAAAAAEuq":{"file_name":["client.py"],"function_name":["getresponse"],"function_offset":[58],"line_number":[1389]},"GquRfhZBLBKr9rIBPuH3nAAAAAAAAE4w":{"file_name":["client.py"],"function_name":["__init__"],"function_offset":[28],"line_number":[276]},"_DA_LSFNMjbu9L2DcselpwAAAAAAAJFI":{"file_name":["socket.py"],"function_name":["makefile"],"function_offset":[40],"line_number":[343]},"piWSMQrh4r040D0BPNaJvwAAAAAAZZ-N":{"file_name":[],"function_name":["__sys_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZYo3":{"file_name":[],"function_name":["___sys_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXwf":{"file_name":[],"function_name":["____sys_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXjN":{"file_name":[],"function_name":["sock_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAet8Y":{"file_name":[],"function_name":["udpv6_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAActW-":{"file_name":[],"function_name":["udp_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAb90r":{"file_name":[],"function_name":["ip_make_skb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAb8Hg":{"file_name":[],"function_name":["__ip_append_data.isra.50"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZdo2":{"file_name":[],"function_name":["sock_alloc_send_pskb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZlYi":{"file_name":[],"function_name":["alloc_skb_with_frags"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZjzl":{"file_name":[],"function_name":["__alloc_skb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJr8u":{"file_name":[],"function_name":["__ksize"],"function_offset":[],"line_number":[]},"grZNsSElR5ITq8H2yHCNSwAAAAAAANbM":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAPAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAFQW":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"CwUjPVV5_7q7c0GhtW0aPwAAAAAAAHVG":{"file_name":["session.py"],"function_name":["create_client"],"function_offset":[112],"line_number":[848]},"cBO14nNDW8EW0oaZDaZipwAAAAAAAOiW":{"file_name":["session.py"],"function_name":["_resolve_region_name"],"function_offset":[20],"line_number":[876]},"C64RiOp1JIPwHLB_iHDa0AAAAAAAAHNm":{"file_name":["session.py"],"function_name":["get_config_variable"],"function_offset":[4],"line_number":[253]},"xvApUwdY2y4sFaZRNrMv5gAAAAAAAEoq":{"file_name":["configprovider.py"],"function_name":["get_config_variable"],"function_offset":[19],"line_number":[316]},"vsalcPHh9qLgsdKtk190IAAAAAAAAFQg":{"file_name":["configprovider.py"],"function_name":["provide"],"function_offset":[11],"line_number":[416]},"QsuqlohtoJfpo6vQ6tHa2AAAAAAAANS-":{"file_name":["utils.py"],"function_name":["provide"],"function_offset":[3],"line_number":[116]},"8ep9l3WIVYErRiHtmAdvewAAAAAAANI2":{"file_name":["utils.py"],"function_name":["_get_instance_metadata_region"],"function_offset":[3],"line_number":[121]},"nPWpQrEmCn54Ou0__aZyJAAAAAAAACsQ":{"file_name":["utils.py"],"function_name":["retrieve_region"],"function_offset":[19],"line_number":[172]},"-xcELApECIipEESUIWed9wAAAAAAAC7-":{"file_name":["utils.py"],"function_name":["_get_region"],"function_offset":[9],"line_number":[185]},"L_saUsdri-UdXCut6TdtngAAAAAAAO5i":{"file_name":["utils.py"],"function_name":["_fetch_metadata_token"],"function_offset":[28],"line_number":[400]},"uHLoBslr3h6S7ooNeXzEbwAAAAAAAFIW":{"file_name":["httpsession.py"],"function_name":["send"],"function_offset":[56],"line_number":[487]},"p19NBQ2pky4eRJM7tgeenwAAAAAAALGU":{"file_name":["httpsession.py"],"function_name":["proxy_url_for"],"function_offset":[6],"line_number":[222]},"55ABUc9FqQ0uj-yn-sTq2AAAAAAAAKaI":{"file_name":["parse.py"],"function_name":["urlparse"],"function_offset":[28],"line_number":[393]},"1msFlmxT18lYvJkx-hfGPgAAAAAAAF1K":{"file_name":["parse.py"],"function_name":["urlsplit"],"function_offset":[49],"line_number":[481]},"a5aMcPOeWx28QSVng73nBQAAAAAAAABK":{"file_name":["aws"],"function_name":[""],"function_offset":[13],"line_number":[27]},"inI9W0bfekFTCpu0ceKTHgAAAAAAAAAG":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"RPwdw40HEBL87wRkKV2ozwAAAAAAAAAS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"pT2bgvKv3bKR6LMAYtKFRwAAAAAAAAAI":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[2],"line_number":[166]},"Rsr7q4vCSh2ppRtyNkwZAAAAAAAAAAAS":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[3],"line_number":[185]},"cKQfWSgZRgu_1Goz5QGSHwAAAAAAAABQ":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[8],"line_number":[97]},"T2fhmP8acUvRZslK7YRDPwAAAAAAAAAY":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[23],"line_number":[48]},"lrxXzNEmAlflj7bCNDjxdAAAAAAAAAAE":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[1],"line_number":[62]},"SMoSw8cr-PdrIATvljOPrQAAAAAAAABU":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[8],"line_number":[76]},"xaCec3W8F6xlvd_EISI7vwAAAAAAAACA":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[16],"line_number":[29]},"GYpj0RgmHJTfD-_w_Fx69wAAAAAAAABA":{"file_name":["cloudfront.py"],"function_name":[""],"function_offset":[7],"line_number":[20]},"b78FoZPzgl20nGrU0Zu24gAAAAAAAABU":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[17],"line_number":[22]},"5ZxW56RI3EOJxqCWjdkdHgAAAAAAAABk":{"file_name":["ssh.py"],"function_name":[""],"function_offset":[12],"line_number":[17]},"fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[25],"line_number":[1058]},"7l7IlhF_Z6_Ribw1CW945QAAAAAAAAA8":{"file_name":["ec.py"],"function_name":[""],"function_offset":[8],"line_number":[13]},"coeZ_4yf5sOePIKKlm8FNQAAAAAAAAAm":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[5],"line_number":[291]},"imaY9TOf2pKX0_q1vRTskQAAAAAAAAAg":{"file_name":["pyimod01_archive.py"],"function_name":["__enter__"],"function_offset":[8],"line_number":[87]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAEGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAMQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAEJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAAsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAADbM":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAGrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAANka":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAEbO":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"GdaBUD9IUEkKxIBryNqV2wAAAAAAAI7O":{"file_name":["clidriver.py"],"function_name":["create_parser"],"function_offset":[4],"line_number":[635]},"QU8QLoFK6ojrywKrBFfTzAAAAAAAAAmc":{"file_name":["clidriver.py"],"function_name":["_get_command_table"],"function_offset":[3],"line_number":[580]},"V558DAsp4yi8bwa8eYwk5QAAAAAAAKbk":{"file_name":["clidriver.py"],"function_name":["_create_command_table"],"function_offset":[18],"line_number":[615]},"grikUXlisBLUbeL_OWixIwAAAAAAALZs":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[699]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAAHdy":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"gMhgHDYSMmyInNJ15VwYFgAAAAAAADvy":{"file_name":["hooks.py"],"function_name":["_emit"],"function_offset":[38],"line_number":[215]},"cHp4MwXaY5FCuFRuAA6tWwAAAAAAAN9c":{"file_name":["waiters.py"],"function_name":["add_waiters"],"function_offset":[11],"line_number":[36]},"-9oyoP4Jj2iRkwEezqId-gAAAAAAAH78":{"file_name":["waiters.py"],"function_name":["get_waiter_model_from_service_model"],"function_offset":[5],"line_number":[48]},"Kq9d0b1CBVEQZUtuJtmlJgAAAAAAAAT8":{"file_name":["session.py"],"function_name":["get_waiter_model"],"function_offset":[4],"line_number":[526]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAAPjQ":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAAOwU":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKsux":{"file_name":[],"function_name":["lookup_fast"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK8mW":{"file_name":[],"function_name":["__d_lookup_rcu"],"function_offset":[],"line_number":[]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAALbM":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAKrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAANAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAIdM":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAADQW":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"ne8F__HPIVgxgycJADVSzAAAAAAAAJ-e":{"file_name":["clidriver.py"],"function_name":["invoke"],"function_offset":[29],"line_number":[930]},"wpss7yv4AvkSwbtctTl0JAAAAAAAAANC":{"file_name":["clidriver.py"],"function_name":["_display_response"],"function_offset":[7],"line_number":[952]},"SLUxdgyFrTF3l4NU1VRO_wAAAAAAAOMY":{"file_name":["formatter.py"],"function_name":["__call__"],"function_offset":[23],"line_number":[91]},"ZOgaFnYiv38tVz-8Hafu3wAAAAAAADCy":{"file_name":["paginate.py"],"function_name":["build_full_result"],"function_offset":[43],"line_number":[487]},"u1Za6xFXDX1Ys5Qeh_gy9QAAAAAAAMWW":{"file_name":["paginate.py"],"function_name":["__iter__"],"function_offset":[16],"line_number":[251]},"uq4_q8agTQ0rkhJvygJ3QAAAAAAAAGag":{"file_name":["paginate.py"],"function_name":["_make_request"],"function_offset":[1],"line_number":[329]},"pK0zxAMiW-X23QjQRVzm5wAAAAAAAOu8":{"file_name":["client.py"],"function_name":["_api_call"],"function_offset":[4],"line_number":[337]},"OP7EiuTwTtWCf_B7a-ZpigAAAAAAAIUk":{"file_name":["client.py"],"function_name":["_make_api_call"],"function_offset":[58],"line_number":[699]},"WyVrojmISSgbkYAxEOnpQwAAAAAAAKcu":{"file_name":["client.py"],"function_name":["_make_request"],"function_offset":[3],"line_number":[704]},"JdWBEAqhrU7LJg0YDuYO0wAAAAAAANaq":{"file_name":["endpoint.py"],"function_name":["make_request"],"function_offset":[3],"line_number":[101]},"cwZEcJVCN5Q4BJdAS3o8fwAAAAAAABLk":{"file_name":["endpoint.py"],"function_name":["_send_request"],"function_offset":[28],"line_number":[157]},"iLNvi1vqLkBP_ehg4QlqeAAAAAAAAJ7U":{"file_name":["endpoint.py"],"function_name":["_get_response"],"function_offset":[18],"line_number":[177]},"guXM5tmjJlv0Ehde0y1DFwAAAAAAAPLs":{"file_name":["endpoint.py"],"function_name":["_do_get_response"],"function_offset":[48],"line_number":[232]},"avBEfFKeFSrhKf93SLNe0QAAAAAAAKtK":{"file_name":["endpoint.py"],"function_name":["_send"],"function_offset":[1],"line_number":[271]},"uHLoBslr3h6S7ooNeXzEbwAAAAAAADQ8":{"file_name":["httpsession.py"],"function_name":["send"],"function_offset":[56],"line_number":[487]},"iRoTPXvR_cRsnzDO-aurpQAAAAAAABVw":{"file_name":["connectionpool.py"],"function_name":["urlopen"],"function_offset":[361],"line_number":[894]},"aAagm2yDcrnYaqBPCwyu8QAAAAAAAE8g":{"file_name":["awsrequest.py"],"function_name":["copy"],"function_offset":[1],"line_number":[605]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAABci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAL_G":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAOMM":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAAn0":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAADYk":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAL2-":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"lpUCR1NQj5NOLBg7mvzlqgAAAAAAAPi6":{"file_name":["generatecliskeleton.py"],"function_name":[""],"function_offset":[47],"line_number":[48]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAHBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAAFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAOKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAAT2":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAABF8":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAE10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"ik6PIX946fW_erE7uBJlVQAAAAAAACLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAMFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAEu2":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"08DBZKRu4nC_Oi_uT40UHwAAAAAAAOyO":{"file_name":["codecommit.py"],"function_name":[""],"function_offset":[156],"line_number":[157]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAACpK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"lOUbi56SanKTCh9Y7fIwDwAAAAAAAD2g":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1099]},"n74P5OxFm1hAo5ZWtgcKHQAAAAAAALGe":{"file_name":["__init__.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[93]},"zXbqXCWr0lCbi_b24hNBRQAAAAAAAOI0":{"file_name":["pyimod02_importers.py"],"function_name":["find_spec"],"function_offset":[87],"line_number":[302]},"AOM_-6oRTyAxK8W79Wo5aQAAAAAAAErq":{"file_name":["pyimod02_importers.py"],"function_name":["get_filename"],"function_offset":[12],"line_number":[212]},"yaTrLhUSIq2WitrTHLBy3QAAAAAAABc6":{"file_name":["posixpath.py"],"function_name":["join"],"function_offset":[21],"line_number":[92]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAEFQ":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"ik6PIX946fW_erE7uBJlVQAAAAAAABLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAABr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAALFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"MU3fJpOZe9TA4mzeo52wZgAAAAAAAM6e":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[297],"line_number":[298]},"N0GNsPaCLYzoFsPJWnIJtQAAAAAAAK8u":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[53],"line_number":[54]},"fq0ezjB8ddCA6Pk0BY9arQAAAAAAAH4C":{"file_name":["distro.py"],"function_name":[""],"function_offset":[608],"line_number":[609]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAMY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAEFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAAkq":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAEi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAFci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAABEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAM8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAFpm":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAKDc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAEn0":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAHYk":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAHLq":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"l97YFeEKpeLfa-lEAZVNcAAAAAAAAOZu":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[16],"line_number":[17]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAABBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAILi":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[49],"line_number":[62]},"gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc":{"file_name":["core.py"],"function_name":[""],"function_offset":[3],"line_number":[16]},"LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs":{"file_name":["prompttoolkit.py"],"function_name":[""],"function_offset":[5],"line_number":[18]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[5],"line_number":[972]},"9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAAAE":{"file_name":["application.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"ZBnr-5IlLVGCdkX_lTNKmwAAAAAAAAAQ":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[7],"line_number":[8]},"RDOEyok4432cuMjL10_tugAAAAAAAAEA":{"file_name":["base_events.py"],"function_name":[""],"function_offset":[44],"line_number":[45]},"U7DZUwH_4YU5DSkoQhGJWwAAAAAAAAAM":{"file_name":["typing.py"],"function_name":["inner"],"function_offset":[3],"line_number":[274]},"bmb3nSRfimrjfhanpjR1rQAAAAAAAAAI":{"file_name":["typing.py"],"function_name":["__getitem__"],"function_offset":[2],"line_number":[354]},"25JFhMXA0rvP5hfyUpf34wAAAAAAAAAc":{"file_name":["typing.py"],"function_name":["Optional"],"function_offset":[7],"line_number":[479]},"oN7OWDJeuc8DmI2f_earDQAAAAAAAAA2":{"file_name":["typing.py"],"function_name":["Union"],"function_offset":[32],"line_number":[466]},"Yj7P3-Rt3nirG6apRl4A7AAAAAAAAAAM":{"file_name":["typing.py"],"function_name":[""],"function_offset":[0],"line_number":[466]},"pz3Evn9laHNJFMwOKIXbswAAAAAAAAB4":{"file_name":["typing.py"],"function_name":["_type_check"],"function_offset":[24],"line_number":[161]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAIi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAJci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAFEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAA8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAJGc":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAHhg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAGeq":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAI58":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAADTm":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"ne8F__HPIVgxgycJADVSzAAAAAAAAKzA":{"file_name":["clidriver.py"],"function_name":["invoke"],"function_offset":[29],"line_number":[930]},"ktj-IOmkEpvZJouiJkQjTgAAAAAAAGYa":{"file_name":["session.py"],"function_name":["create_client"],"function_offset":[117],"line_number":[854]},"O_h7elJSxPO7SiCsftYRZgAAAAAAABW2":{"file_name":["client.py"],"function_name":["create_client"],"function_offset":[52],"line_number":[142]},"ZLTqiSLOmv4Ej_7d8yKLmwAAAAAAAEGM":{"file_name":["client.py"],"function_name":["_get_client_args"],"function_offset":[15],"line_number":[295]},"v_WV3HQYVe0q1Ob-1gtx1AAAAAAAAP0W":{"file_name":["args.py"],"function_name":["get_client_args"],"function_offset":[72],"line_number":[118]},"ka2IKJhpWbD6PA3J3v624wAAAAAAAElW":{"file_name":["copy.py"],"function_name":["copy"],"function_offset":[35],"line_number":[101]},"e8Lb_MV93AH-OkvHPPDitgAAAAAAAI5y":{"file_name":["hooks.py"],"function_name":["__copy__"],"function_offset":[6],"line_number":[344]},"1vivUE5hL65442lQ9a_ylgAAAAAAAEOi":{"file_name":["hooks.py"],"function_name":["__copy__"],"function_offset":[8],"line_number":[486]},"fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKvK":{"file_name":["hooks.py"],"function_name":["_recursive_copy"],"function_offset":[12],"line_number":[500]},"fh_7rTxpgngJ2cX2lBjVdgAAAAAAAKtu":{"file_name":["hooks.py"],"function_name":["_recursive_copy"],"function_offset":[12],"line_number":[500]},"fCsVLBj60GK9Hf8VtnMcgAAAAAAAADSW":{"file_name":["hooks.py"],"function_name":["__copy__"],"function_offset":[5],"line_number":[35]},"54xjnvwS2UtwpSVJMemggAAAAAAAAGsE":{"file_name":[""],"function_name":[""],"function_offset":[0],"line_number":[1]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAEpm":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAJDc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAPxi":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"780bLUPADqfQ3x1T5lnVOgAAAAAAAJsu":{"file_name":["emr.py"],"function_name":[""],"function_offset":[42],"line_number":[43]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAADU":{"file_name":["application.py"],"function_name":[""],"function_offset":[40],"line_number":[41]},"bAXCoU3-CU0WlRxl5l1tmwAAAAAAAADk":{"file_name":["buffer.py"],"function_name":[""],"function_offset":[35],"line_number":[36]},"qordvIiilnF7CmkWCAd7eAAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"iWpqwwcHV8E8OOnqGCYj9gAAAAAAAABc":{"file_name":["base.py"],"function_name":[""],"function_offset":[8],"line_number":[9]},"M61AJsljWf0TM7wD6IJVZwAAAAAAAAAI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[12],"line_number":[13]},"okgAOHfDrcA806m5xh4DMAAAAAAAAACs":{"file_name":["ansi.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAOVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAPHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAI10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAKs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"ik6PIX946fW_erE7uBJlVQAAAAAAAMLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAGFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAC8C":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"aRRT4_vBG9Q4nqyirWo5FAAAAAAAAJZa":{"file_name":["codedeploy.py"],"function_name":[""],"function_offset":[49],"line_number":[50]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAIY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAAFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAMmC":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMiA":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHJU":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAJSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAKFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"08Dc0vnMK9C_nl7yQB6ZKQAAAAAAAMP6":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[47],"line_number":[48]},"zuPG_tF81PcJTwjfBwKlDgAAAAAAADW4":{"file_name":["abc.py"],"function_name":[""],"function_offset":[267],"line_number":[268]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAKBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAMFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAABKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAAFQ":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"MU3fJpOZe9TA4mzeo52wZgAAAAAAAG6m":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[297],"line_number":[298]},"auEGiAr7C6IfT0eiHbOlyAAAAAAAAMhK":{"file_name":["session.py"],"function_name":[""],"function_offset":[184],"line_number":[185]},"ZyAwfhB8pqBFv6xiDVdvPQAAAAAAAKh2":{"file_name":["credentials.py"],"function_name":[""],"function_offset":[553],"line_number":[554]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAMpK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"lOUbi56SanKTCh9Y7fIwDwAAAAAAAN2g":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1099]},"9alsKcnSosScCQ3ntwGT5wAAAAAAAKlg":{"file_name":["_bootstrap_external.py"],"function_name":["find_spec"],"function_offset":[22],"line_number":[1518]},"xAINw9zPBhJlledr3DAcGAAAAAAAAK4I":{"file_name":["_bootstrap_external.py"],"function_name":["_get_spec"],"function_offset":[29],"line_number":[1493]},"xVweU0pD8q051c2YgF4PTwAAAAAAAH1m":{"file_name":["_bootstrap_external.py"],"function_name":["find_spec"],"function_offset":[43],"line_number":[1647]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAJVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAKHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"ik6PIX946fW_erE7uBJlVQAAAAAAAJLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAADFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAOAO":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"d4jl580PLMUwu5s3I4wcXgAAAAAAAISu":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[16],"line_number":[17]},"tKago5vqLnwIkezk_wTBpQAAAAAAAOfq":{"file_name":["package.py"],"function_name":[""],"function_offset":[31],"line_number":[32]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAHkq":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ik6PIX946fW_erE7uBJlVQAAAAAAANLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"MU3fJpOZe9TA4mzeo52wZgAAAAAAAF6m":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[297],"line_number":[298]},"auEGiAr7C6IfT0eiHbOlyAAAAAAAALg6":{"file_name":["session.py"],"function_name":[""],"function_offset":[184],"line_number":[185]},"mP9Tk3T74fjOyYWKUaqdMQAAAAAAAJDi":{"file_name":["client.py"],"function_name":[""],"function_offset":[119],"line_number":[120]},"I4X8AC1-B0GuL4JyYemPzwAAAAAAAKO6":{"file_name":["args.py"],"function_name":[""],"function_offset":[35],"line_number":[36]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAJkq":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"Gp9aOxUrrpSVBx4-ftlTOAAAAAAAAKYy":{"file_name":["auth.py"],"function_name":[""],"function_offset":[603],"line_number":[604]},"y9R94bQUxts02WzRWfV7xgAAAAAAAC_y":{"file_name":["auth.py"],"function_name":[""],"function_offset":[316],"line_number":[317]},"uI6css-d8SGQRK6a_Ntl-AAAAAAAAD3e":{"file_name":["auth.py"],"function_name":[""],"function_offset":[336],"line_number":[337]},"SlnkBp0IIJFLHVOe4KbxwQAAAAAAAJLa":{"file_name":["http.py"],"function_name":[""],"function_offset":[231],"line_number":[232]},"7wBb3xHP1JZHNBpMGh4EdAAAAAAAADGa":{"file_name":["io.py"],"function_name":[""],"function_offset":[408],"line_number":[409]},"u3fGdgL6eAYjYSRbRUri0gAAAAAAAFUY":{"file_name":["io.py"],"function_name":["SocketDomain"],"function_offset":[3],"line_number":[194]},"aG0mH34tM6si5c1l397JVQAAAAAAAOwA":{"file_name":["enum.py"],"function_name":["__setitem__"],"function_offset":[93],"line_number":[457]},"GC-VoGaqaEobPzimayHQTQAAAAAAANdk":{"file_name":["enum.py"],"function_name":["_is_sunder"],"function_offset":[4],"line_number":[62]},"PmhxUKv5sePRxhCBONca8gAAAAAAAID6":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[19],"line_number":[20]},"ik6PIX946fW_erE7uBJlVQAAAAAAAPLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"UfGck3qA2qF0xFB5gpY4HgAAAAAAAH72":{"file_name":["base.py"],"function_name":[""],"function_offset":[191],"line_number":[192]},"G9ShE3ODivDEFyHVdsnZ_gAAAAAAABn-":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[34],"line_number":[35]},"6AsJ0dA2BUqaic-ScDJBMAAAAAAAACOm":{"file_name":["ansi.py"],"function_name":[""],"function_offset":[38],"line_number":[39]},"fr52ZDCgnkPZlzTNdLTQ5wAAAAAAAGnS":{"file_name":["base.py"],"function_name":[""],"function_offset":[167],"line_number":[168]},"uqoEOAkLp1toolLH0q5LVwAAAAAAAJmm":{"file_name":["mouse_events.py"],"function_name":[""],"function_offset":[63],"line_number":[64]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMFQ":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"MU3fJpOZe9TA4mzeo52wZgAAAAAAAJ42":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[297],"line_number":[298]},"WjtMXFj0eujpoknR_rynvAAAAAAAACba":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[800],"line_number":[801]},"Vot4T3F5OpUj8rbXhgpMDgAAAAAAAO8I":{"file_name":["_bootstrap_external.py"],"function_name":["exec_module"],"function_offset":[4],"line_number":[938]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAEtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"EPS0ql6FPdCQLe9KByvDQAAAAAAAAMgy":{"file_name":["traceback.py"],"function_name":[""],"function_offset":[328],"line_number":[329]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAAFtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAKSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAKJo":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"OHQX9IWLaZElAgxGbX3P5gAAAAAAACVG":{"file_name":["_compiler.py"],"function_name":["_code"],"function_offset":[13],"line_number":[584]},"E2b-mzlh_8261-JxcySn-AAAAAAAAKJE":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAAKA4":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAAJp8":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"V6gUZHzBRISi-Z25klK5DQAAAAAAAKri":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[37],"line_number":[38]},"ik6PIX946fW_erE7uBJlVQAAAAAAAELu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"zWNEoAKVTnnzSns045VKhwAAAAAAAMsa":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"n4Ao4OZE2osF0FygfcWo3gAAAAAAAEfO":{"file_name":["application.py"],"function_name":[""],"function_offset":[237],"line_number":[238]},"XVsKc4e32xXUv-3uv2s-8QAAAAAAACny":{"file_name":["defaults.py"],"function_name":["emacs_state"],"function_offset":[32],"line_number":[33]},"uPGvGNXBf1JXGeeDSsmGQAAAAAAAALX2":{"file_name":["enum.py"],"function_name":["__new__"],"function_offset":[194],"line_number":[679]},"79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[8],"line_number":[21]},"mHiYHSEggclUi1ELZIxq4AAAAAAAAABA":{"file_name":["session.py"],"function_name":[""],"function_offset":[13],"line_number":[27]},"_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU":{"file_name":["client.py"],"function_name":[""],"function_offset":[3],"line_number":[16]},"CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc":{"file_name":["waiter.py"],"function_name":[""],"function_offset":[4],"line_number":[17]},"5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[2],"line_number":[15]},"z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE":{"file_name":["service.py"],"function_name":[""],"function_offset":[0],"line_number":[13]},"1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM":{"file_name":["restdoc.py"],"function_name":[""],"function_offset":[2],"line_number":[15]},"rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc":{"file_name":["compat.py"],"function_name":[""],"function_offset":[17],"line_number":[31]},"zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[10],"line_number":[11]},"r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[2],"line_number":[3]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[15],"line_number":[982]},"JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[24],"line_number":[925]},"MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[2],"line_number":[192]},"yWt46REABLfKH6PXLAE18AAAAAAAAABk":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[16],"line_number":[431]},"VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[1],"line_number":[121]},"Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[2],"line_number":[87]},"OwrnTUowquMzuETYoP67yQAAAAAAAAAe":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[4],"line_number":[5]},"HmAocvtnsxREZJIec2I5gwAAAAAAAAA4":{"file_name":["__init__.py"],"function_name":["HTTPStatus"],"function_offset":[41],"line_number":[46]},"KHDki7BxJPyjGLtvY8M5lQAAAAAAAAF-":{"file_name":["enum.py"],"function_name":["__setitem__"],"function_offset":[64],"line_number":[152]},"ik6PIX946fW_erE7uBJlVQAAAAAAAGLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAL5W":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"XlQ19HBD_RNa2r3QWOR-nAAAAAAAAP1u":{"file_name":["commands.py"],"function_name":[""],"function_offset":[127],"line_number":[128]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAAFRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAItm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"VuJFonCXevADcEDW6NVbKgAAAAAAAGsG":{"file_name":["devcommands.py"],"function_name":[""],"function_offset":[49],"line_number":[50]},"VFBd9VqCaQu0ZzjQ2K3pjgAAAAAAAMsO":{"file_name":["factory.py"],"function_name":[""],"function_offset":[57],"line_number":[58]},"PUSucJs4FC_WdMzOyH3QYwAAAAAAAOMa":{"file_name":["layout.py"],"function_name":[""],"function_offset":[130],"line_number":[131]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGpK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"lOUbi56SanKTCh9Y7fIwDwAAAAAAAH2g":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1099]},"it1vvnZdXdzy0fFROnaaOQAAAAAAALTQ":{"file_name":["_bootstrap.py"],"function_name":["find_spec"],"function_offset":[28],"line_number":[950]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAOL2":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"XlQ19HBD_RNa2r3QWOR-nAAAAAAAAL1u":{"file_name":["commands.py"],"function_name":[""],"function_offset":[127],"line_number":[128]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAAARw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAADtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"VFBd9VqCaQu0ZzjQ2K3pjgAAAAAAAAsO":{"file_name":["factory.py"],"function_name":[""],"function_offset":[57],"line_number":[58]},"PUSucJs4FC_WdMzOyH3QYwAAAAAAABHq":{"file_name":["layout.py"],"function_name":[""],"function_offset":[130],"line_number":[131]},"J1eggTwSzYdi9OsSu1q37gAAAAAAALC4":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"0S3htaCNkzxOYeavDR1GTQAAAAAAAM8O":{"file_name":["_bootstrap.py"],"function_name":["module_from_spec"],"function_offset":[14],"line_number":[580]},"gZooqVYiItnHim-lK4feOgAAAAAAANN-":{"file_name":["_bootstrap.py"],"function_name":["_init_module_attrs"],"function_offset":[70],"line_number":[563]},"UfGck3qA2qF0xFB5gpY4HgAAAAAAABTm":{"file_name":["base.py"],"function_name":[""],"function_offset":[191],"line_number":[192]},"G9ShE3ODivDEFyHVdsnZ_gAAAAAAABs-":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[34],"line_number":[35]},"6AsJ0dA2BUqaic-ScDJBMAAAAAAAABbq":{"file_name":["ansi.py"],"function_name":[""],"function_offset":[38],"line_number":[39]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAABJU":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"eV_m28NnKeeTL60KO2H3SAAAAAAAADSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"VY0EiAO0DxwLRTE4PfFhdwAAAAAAAOMW":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[6],"line_number":[7]},"A8AozG5gQfEN24i4IE7w5wAAAAAAACgG":{"file_name":["defaults.py"],"function_name":[""],"function_offset":[21],"line_number":[22]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAKKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ik6PIX946fW_erE7uBJlVQAAAAAAAHLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAABFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"V6gUZHzBRISi-Z25klK5DQAAAAAAACri":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[37],"line_number":[38]},"zWNEoAKVTnnzSns045VKhwAAAAAAAIsa":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"n4Ao4OZE2osF0FygfcWo3gAAAAAAACw2":{"file_name":["application.py"],"function_name":[""],"function_offset":[237],"line_number":[238]},"NGbZlnLCqeq3LFq89r_SpQAAAAAAAD0-":{"file_name":["buffer.py"],"function_name":[""],"function_offset":[191],"line_number":[192]},"PmhxUKv5sePRxhCBONca8gAAAAAAAAD6":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[19],"line_number":[20]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"clFhkTaiph2aOjCNuZDWKAAAAAAAAAEu":{"file_name":["client.py"],"function_name":[""],"function_offset":[1396],"line_number":[1397]},"OPpnYj88CDOiKneikdGPHAAAAAAAAAF-":{"file_name":["ssl.py"],"function_name":[""],"function_offset":[138],"line_number":[142]},"ZJjPF65K8mBuISvhCfKfBgAAAAAAAAB4":{"file_name":["enum.py"],"function_name":["_convert_"],"function_offset":[27],"line_number":[555]},"xLxhp_367a_SbgOYuEJjlwAAAAAAAAAm":{"file_name":["enum.py"],"function_name":["__call__"],"function_offset":[28],"line_number":[386]},"QHotkhNTqx5C4Kjd2F2_6wAAAAAAAAEC":{"file_name":["enum.py"],"function_name":["_create_"],"function_offset":[35],"line_number":[510]},"Ht79I_xqXv3bOgaClTNQ4wAAAAAAAAKS":{"file_name":["enum.py"],"function_name":["__new__"],"function_offset":[122],"line_number":[301]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"pv4wAezdMMO0SVuGgaEMTgAAAAAAALV2":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[17],"line_number":[18]},"qns5vQ3LMi6QrIMOgD_TwQAAAAAAAER-":{"file_name":["service.py"],"function_name":[""],"function_offset":[20],"line_number":[21]},"J_Lkq1OzUHxWQhnTgF6FwAAAAAAAAPq2":{"file_name":["restdoc.py"],"function_name":[""],"function_offset":[22],"line_number":[23]},"XkOSW26Xa6_lkqHv5givKgAAAAAAAFiO":{"file_name":["compat.py"],"function_name":[""],"function_offset":[231],"line_number":[232]},"aD-GPAkaW-Swis8ybNgyMQAAAAAAAIjQ":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[455],"line_number":[456]},"HENgRXYeEs7mDD8Gk_MNmgAAAAAAACO-":{"file_name":["help.py"],"function_name":[""],"function_offset":[202],"line_number":[203]},"fFS0upy5lIaT99RhlTN5LQAAAAAAAMwW":{"file_name":["clidocs.py"],"function_name":[""],"function_offset":[399],"line_number":[400]},"lSdGU4igLMOpLhL_6XP15wAAAAAAALze":{"file_name":["argprocess.py"],"function_name":[""],"function_offset":[278],"line_number":[279]},"QAp_Nt6XUeNsCXnAUgW7XgAAAAAAAJC6":{"file_name":["shorthand.py"],"function_name":[""],"function_offset":[132],"line_number":[133]},"20O937106XMbOD0LQR4SPwAAAAAAAAuy":{"file_name":["shorthand.py"],"function_name":["ShorthandParser"],"function_offset":[257],"line_number":[379]},"gPzb0fXoBe1225fbKepMRAAAAAAAAKLy":{"file_name":["shorthand.py"],"function_name":["__init__"],"function_offset":[2],"line_number":[53]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAISc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAIJo":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"OHQX9IWLaZElAgxGbX3P5gAAAAAAAGVG":{"file_name":["_compiler.py"],"function_name":["_code"],"function_offset":[13],"line_number":[584]},"E2b-mzlh_8261-JxcySn-AAAAAAAAIFK":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAAIJE":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAAIai":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAAH1i":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"JrU1PwRIxl_8SXdnTESnogAAAAAAAJom":{"file_name":["_compiler.py"],"function_name":["_optimize_charset"],"function_offset":[138],"line_number":[379]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAABtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"zWNEoAKVTnnzSns045VKhwAAAAAAAAsa":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"n4Ao4OZE2osF0FygfcWo3gAAAAAAAL2e":{"file_name":["application.py"],"function_name":[""],"function_offset":[237],"line_number":[238]},"lTFhQHSZwvS4-s94KVv5mAAAAAAAABAw":{"file_name":["renderer.py"],"function_name":[""],"function_offset":[85],"line_number":[86]},"IcJVDEq52FRv22q0yHVMawAAAAAAACL6":{"file_name":["typing.py"],"function_name":["inner"],"function_offset":[6],"line_number":[351]},"BDtQyw375W96A0PA_Z7SDQAAAAAAAOUy":{"file_name":["typing.py"],"function_name":["__getitem__"],"function_offset":[7],"line_number":[1557]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoBCe":{"file_name":[],"function_name":["async_page_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAABnSX":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAItm_":{"file_name":[],"function_name":["handle_mm_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAIs9a":{"file_name":[],"function_name":["__handle_mm_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJk1L":{"file_name":[],"function_name":["alloc_pages_vma"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJNfz":{"file_name":[],"function_name":["__alloc_pages_nodemask"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJJpI":{"file_name":[],"function_name":["get_page_from_freelist"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAJGV5":{"file_name":[],"function_name":["prep_new_page"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAgURI":{"file_name":[],"function_name":["clear_page_erms"],"function_offset":[],"line_number":[]},"grZNsSElR5ITq8H2yHCNSwAAAAAAADVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAEHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAM10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAACs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAOXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAI_q":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"skFt9oVHBFfMDC1On4IJhgAAAAAAAHg6":{"file_name":["ddb.py"],"function_name":[""],"function_offset":[26],"line_number":[27]},"g5zhfSuJlGbmNqPl5Qb2wgAAAAAAALga":{"file_name":["subcommands.py"],"function_name":[""],"function_offset":[64],"line_number":[65]},"UoMth5MLnZ-vUHeTplwEvAAAAAAAAMqu":{"file_name":["params.py"],"function_name":[""],"function_offset":[226],"line_number":[227]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAABfe":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"OlTvyWQFXjOweJcs3kiGygAAAAAAAMui":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[155],"line_number":[156]},"N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAPB2":{"file_name":["connection.py"],"function_name":[""],"function_offset":[87],"line_number":[88]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"1eW8DnM19kiBGqMWGVkHPAAAAAAAACJC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[23],"line_number":[24]},"2kgk5qEgdkkSXT9cIdjqxQAAAAAAAEYy":{"file_name":["ssl_.py"],"function_name":[""],"function_offset":[258],"line_number":[259]},"MsEmysGbXhMvgdbwhcZDCgAAAAAAAA8c":{"file_name":["url.py"],"function_name":[""],"function_offset":[238],"line_number":[239]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAFSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAFJC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAAPpU":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAAHHY":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAFcu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAEZu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"Gxt7_MN7XgUOe9547JcHVQAAAAAAAAd2":{"file_name":["_parser.py"],"function_name":["__len__"],"function_offset":[1],"line_number":[159]},"XkOSW26Xa6_lkqHv5givKgAAAAAAAP0G":{"file_name":["compat.py"],"function_name":[""],"function_offset":[231],"line_number":[232]},"2L4SW1rQgEVXRj3pZAI3nQAAAAAAAEZ6":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[97],"line_number":[98]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAAGRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAJtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"7bd6QJSfWZZfOOpDMHqLMAAAAAAAAAo6":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[319],"line_number":[320]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHpK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"lOUbi56SanKTCh9Y7fIwDwAAAAAAAI2g":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1099]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAAHs4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAAEbK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAABUk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAANQO":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAEpu":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAALn8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"J3wpF3Lf_vPkis4aNGKFbwAAAAAAANnI":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAANtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAGSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAGOg":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"OlTvyWQFXjOweJcs3kiGygAAAAAAACIS":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[155],"line_number":[156]},"N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAFB2":{"file_name":["connection.py"],"function_name":[""],"function_offset":[87],"line_number":[88]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAKtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"1eW8DnM19kiBGqMWGVkHPAAAAAAAAKJC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[23],"line_number":[24]},"2kgk5qEgdkkSXT9cIdjqxQAAAAAAAJyi":{"file_name":["ssl_.py"],"function_name":[""],"function_offset":[258],"line_number":[259]},"MsEmysGbXhMvgdbwhcZDCgAAAAAAAGSk":{"file_name":["url.py"],"function_name":[""],"function_offset":[238],"line_number":[239]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAABtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAALSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAALJC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAAFpU":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAALk8":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"zpgqltXEgKujOhJUj-jAhgAAAAAAAKBE":{"file_name":["_parser.py"],"function_name":["__getitem__"],"function_offset":[3],"line_number":[165]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAExO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFJU":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"6GGFIt18C0VByIn0h-PdeQAAAAAAAGoO":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[6],"line_number":[7]},"SA64oIT_DC3uHXf7ZjFqkwAAAAAAAKVS":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[48],"line_number":[49]},"akZOzI9XwsEixvkTDGeDPwAAAAAAAHZa":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[2],"line_number":[3]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAIBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAPKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGOq":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"ZPxtkRXufuVf4tqV5k5k2QAAAAAAAHcA":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1097]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAACD4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAADAK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAAGYk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAAJee":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAAW-":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAAFj8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"fj70ljef7nDHOqVJGSIoEQAAAAAAAMdS":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"AtF9VdLKnFQvB9H1lsFPjAAAAAAAALka":{"file_name":["parser.py"],"function_name":[""],"function_offset":[70],"line_number":[71]},"Pf1McBfrZjVj1CxRZBq6YwAAAAAAAEwy":{"file_name":["feedparser.py"],"function_name":[""],"function_offset":[443],"line_number":[444]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAE3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAISm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAABs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAHLi":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ew01Dk0sWZctP-VaEpavqQAAAAAAoBCe":{"file_name":[],"function_name":["async_page_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAABnNL":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAADky8":{"file_name":[],"function_name":["down_read_trylock"],"function_offset":[],"line_number":[]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAOxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAPRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAIqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAPFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"XkOSW26Xa6_lkqHv5givKgAAAAAAANim":{"file_name":["compat.py"],"function_name":[""],"function_offset":[231],"line_number":[232]},"2L4SW1rQgEVXRj3pZAI3nQAAAAAAAPmC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[97],"line_number":[98]},"vkeP2ntYyoFN0A16x9eliwAAAAAAAHPE":{"file_name":["__init__.py"],"function_name":["namedtuple"],"function_offset":[164],"line_number":[512]},"clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI":{"file_name":["client.py"],"function_name":[""],"function_offset":[70],"line_number":[71]},"DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg":{"file_name":["parser.py"],"function_name":[""],"function_offset":[7],"line_number":[12]},"RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAk":{"file_name":["feedparser.py"],"function_name":[""],"function_offset":[22],"line_number":[27]},"H5LY_MytOVgyAawi8TymCgAAAAAAAAAQ":{"file_name":["_policybase.py"],"function_name":[""],"function_offset":[6],"line_number":[7]},"kUJz0cDHgh-y1O5Hi8equAAAAAAAAAAw":{"file_name":["header.py"],"function_name":[""],"function_offset":[14],"line_number":[19]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAJxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKOq":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"ZPxtkRXufuVf4tqV5k5k2QAAAAAAALcA":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1097]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAAGD4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAAHAK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAAKYk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAANee":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAEW-":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAAJj8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAKRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAADqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"fj70ljef7nDHOqVJGSIoEQAAAAAAACxi":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"AtF9VdLKnFQvB9H1lsFPjAAAAAAAADka":{"file_name":["parser.py"],"function_name":[""],"function_offset":[70],"line_number":[71]},"Pf1McBfrZjVj1CxRZBq6YwAAAAAAAFZy":{"file_name":["feedparser.py"],"function_name":[""],"function_offset":[443],"line_number":[444]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAI3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAMSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAOG-":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ew01Dk0sWZctP-VaEpavqQAAAAAAAEJE":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAADzu":{"file_name":[],"function_name":["exit_to_usermode_loop"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAACrrD":{"file_name":[],"function_name":["task_work_run"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKhsk":{"file_name":[],"function_name":["__fput"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAO4n8":{"file_name":[],"function_name":["xfs_release"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAOdwM":{"file_name":[],"function_name":["xfs_free_eofblocks"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAANnCL":{"file_name":[],"function_name":["xfs_bmapi_read"],"function_offset":[],"line_number":[]},"RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAY":{"file_name":["feedparser.py"],"function_name":[""],"function_offset":[21],"line_number":[26]},"-gq3a70QOgdn9HetYyf2OgAAAAAAAACy":{"file_name":["errors.py"],"function_name":[""],"function_offset":[45],"line_number":[50]}},"executables":{"edNJ10OjHiWc5nzuTQdvig":"linux-vdso.so.1","LvhLWomlc0dSPYzQ8C620g":"controller","j8DVIOTu7Btj9lgFefJ84A":"dockerd","QvG8QEGAld88D676NL_Y2Q":"filebeat","B8JRxL079xbhqQBqGvksAg":"kubelet","wfA2BgwfDNXUWsxkJ083Rw":"kubelet","v6HIzNa4K6G4nRP9032RIA":"dockerd","FWZ9q3TQKZZok58ua1HDsg":"pf-debug-metadata-service","QaIvzvU8UoclQMd_OMt-Pg":"elastic-operator","ew01Dk0sWZctP-VaEpavqQ":"vmlinux","kajOqZqz7V1y0BdYQLFQrw":"containerd-shim-runc-v2","9LzzIocepYcOjnUsLlgOjg":"vmlinux","MNBJ5seVz_ocW6tcr1HSmw":"metricbeat","-pk6w5puGcp-wKnQ61BZzQ":"kubelet","A2oiHVwisByxRn5RDT4LjA":"vmlinux","w5zBqPf1_9mIVEf-Rn7EdA":"systemd","Z_CHd3Zjsh2cWE2NSdbiNQ":"libc-2.26.so","OTWX4UsOVMrSIF5cD4zUzg":"libmount.so.1.1.0","LHNvPtcKBt87cCBX8aTNhQ":"overlay","67s2TwiMngM0yin5Y8pvEg":"containerd","piWSMQrh4r040D0BPNaJvw":"vmlinux","SbPwzb_Kog2bWn8uc7xhDQ":"aws","xLxcEbwnZ5oNrk99ZsxcSQ":"libpython3.11.so.1.0","eOfhJQFIxbIEScd007tROw":"libpthread-2.26.so","-p9BlJh9JZMPPNjY_j92ng":"awsagent","9HZ7GQCC6G9fZlRD7aGzXQ":"libssl.so.1.0.2k","huWyXZbCBWCe2ZtK9BiokQ":"libcrypto.so.1.0.2k","WpYcHtr4qx88B8CBJZ2GTw":"aws","-Z7SlEXhuy5tL2BF-xmy3g":"libpython3.11.so.1.0","6miIyyucTZf5zXHCk7PT1g":"veth","G68hjsyagwq6LpWrMjDdng":"libpython3.9.so.1.0","JsObMPhfT_zO2Q_B1cPLxA":"coredns","ASi9f26ltguiwFajNwOaZw":"zlib.cpython-311-x86_64-linux-gnu.so","jaBVtokSUzfS97d-XKjijg":"libz.so.1","dGWvVtQJJ5wuqNyQVpi8lA":"zlib.cpython-311-x86_64-linux-gnu.so"},"total_frames":198526,"sampling_rate":0.0016000000000000003} diff --git a/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_60s_1x.json b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_60s_1x.json new file mode 100644 index 0000000000000..8a5c1acf7f93d --- /dev/null +++ b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_60s_1x.json @@ -0,0 +1 @@ +{"stack_trace_events":{"YdDJxgmO4Qwjr0AEbbpw5g":3,"ARUlXLnccHmzguHUjXRt-A":7,"fsUmzqifyqwKCmzKO1INZQ":24,"z_Kbu_3KsKjzL49rf-CSTA":94,"RpSSZ069-ac11a4PUFolMA":101,"H4U5LLhN4L_4fDVbcrz30A":57,"8jSwzubV-3-vgAsXwII0kA":26,"43tbk4XHS6h_eSSkozr2lQ":33,"1Hf53oSb-zH-2QD2FYxgyA":27,"ER-x6xVv257WtFQAI5qb9g":47,"Hr1OSWigQhS4BD9n1H0fVw":40,"g1qDjUCVlmghGHVDrjeDvw":44,"XU0AYWfaWEgxn6HS3Npe0Q":42,"Xi_OuuwxmtjxVLfRnOKl-w":43,"j3pRZrJva_6zVfPpTrRgMQ":34,"B0rzVoKcdftibP3e40EU_g":39,"jHWwY4al2R105ljWitJf8Q":51,"JFrKrVm1b8YVyjTALHwFPQ":17,"UkNqUaLVbzZ-0N4mRSSfPA":7,"EH1ElzcXDEuDqu7McdrBdQ":17,"VyF1fKBkXgRmNRnKNEu8Fw":23,"naNkvUaKAyxw8L7AmrJp_A":25,"INCPC3idrKxHgrRrb5yK7w":18,"4-XWrzbKLiMzMN29SCKUhA":18,"oazzZOrFVKPzoEMEINIH2g":14,"bgW4z1P_qeyGZ-BNg-EtzA":21,"_7muG2H-TTX5D3mi3LROgw":17,"nKCqWW03DZONEM_Nq2LvwQ":6,"08TjeY9jNFfBuPDWZvzcGA":16,"41gF_giRSTRZMXWPVpvLYA":10,"CCCw9Z7XCAUBXfzhCKjvyQ":12,"RK2MfkyDuA83Ote1DRpnig":9,"E9YrFLZE6ytYTLr5nOdeqA":8,"OaI2ikXPfU9oPJVr7qHqRA":6,"BeervgrHDOwHnECUdx-R1Q":1,"_E7kI3XeP50ndUGgLwozRw":1,"PiAbunsxsTWIrlVv5AJCxQ":2,"gcylfs4yiiRtiY_AHc1fkQ":2,"2J6chKI2om9Kbvwi1SgqlA":1,"YX2R7C2iz4FGt5q5Tnk6TA":1,"--7TGRswVMtk5qWYdGBDUw":1,"iVZ81pgajC_4cYBykPWgBg":1,"dg33Fg5TLDtB9bOuPSPREA":1},"stack_traces":{"YdDJxgmO4Qwjr0AEbbpw5g":{"address_or_lines":[2371],"file_ids":["Ij7mO1SCteAnvtNe95RpEg"],"frame_ids":["Ij7mO1SCteAnvtNe95RpEgAAAAAAAAlD"],"type_ids":[3]},"ARUlXLnccHmzguHUjXRt-A":{"address_or_lines":[4651602,2352],"file_ids":["B56YkhsK1JwqD-8F8sjS3A","Ij7mO1SCteAnvtNe95RpEg"],"frame_ids":["B56YkhsK1JwqD-8F8sjS3AAAAAAARvpS","Ij7mO1SCteAnvtNe95RpEgAAAAAAAAkw"],"type_ids":[3,3]},"fsUmzqifyqwKCmzKO1INZQ":{"address_or_lines":[32434917,32101228,32118123],"file_ids":["QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q"],"frame_ids":["QvG8QEGAld88D676NL_Y2QAAAAAB7url","QvG8QEGAld88D676NL_Y2QAAAAAB6dNs","QvG8QEGAld88D676NL_Y2QAAAAAB6hVr"],"type_ids":[3,3,3]},"z_Kbu_3KsKjzL49rf-CSTA":{"address_or_lines":[4646312,4318297,4332979,4334816],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARuWo","FWZ9q3TQKZZok58ua1HDsgAAAAAAQeRZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAQh2z","FWZ9q3TQKZZok58ua1HDsgAAAAAAQiTg"],"type_ids":[3,3,3,3]},"RpSSZ069-ac11a4PUFolMA":{"address_or_lines":[4646178,4471372,4470064,4464366,4415263],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARuUi","FWZ9q3TQKZZok58ua1HDsgAAAAAARDpM","FWZ9q3TQKZZok58ua1HDsgAAAAAARDUw","FWZ9q3TQKZZok58ua1HDsgAAAAAARB7u","FWZ9q3TQKZZok58ua1HDsgAAAAAAQ18f"],"type_ids":[3,3,3,3,3]},"H4U5LLhN4L_4fDVbcrz30A":{"address_or_lines":[12531204,12361900,12360536,12355924,12307483,12548548],"file_ids":["67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg"],"frame_ids":["67s2TwiMngM0yin5Y8pvEgAAAAAAvzYE","67s2TwiMngM0yin5Y8pvEgAAAAAAvKCs","67s2TwiMngM0yin5Y8pvEgAAAAAAvJtY","67s2TwiMngM0yin5Y8pvEgAAAAAAvIlU","67s2TwiMngM0yin5Y8pvEgAAAAAAu8wb","67s2TwiMngM0yin5Y8pvEgAAAAAAv3nE"],"type_ids":[3,3,3,3,3,3]},"8jSwzubV-3-vgAsXwII0kA":{"address_or_lines":[4635624,4317996,4333118,4324708,4325572,4330137,4587439],"file_ids":["-1kQFVGzdQWpzLSZ9TRmnw","-1kQFVGzdQWpzLSZ9TRmnw","-1kQFVGzdQWpzLSZ9TRmnw","-1kQFVGzdQWpzLSZ9TRmnw","-1kQFVGzdQWpzLSZ9TRmnw","-1kQFVGzdQWpzLSZ9TRmnw","-1kQFVGzdQWpzLSZ9TRmnw"],"frame_ids":["-1kQFVGzdQWpzLSZ9TRmnwAAAAAARrvo","-1kQFVGzdQWpzLSZ9TRmnwAAAAAAQeMs","-1kQFVGzdQWpzLSZ9TRmnwAAAAAAQh4-","-1kQFVGzdQWpzLSZ9TRmnwAAAAAAQf1k","-1kQFVGzdQWpzLSZ9TRmnwAAAAAAQgDE","-1kQFVGzdQWpzLSZ9TRmnwAAAAAAQhKZ","-1kQFVGzdQWpzLSZ9TRmnwAAAAAARf-v"],"type_ids":[3,3,3,3,3,3,3]},"43tbk4XHS6h_eSSkozr2lQ":{"address_or_lines":[18515232,22597677,22574090,22556393,22530363,22106663,22101077,22107662],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHQK","v6HIzNa4K6G4nRP9032RIAAAAAABWC7p","v6HIzNa4K6G4nRP9032RIAAAAAABV8k7","v6HIzNa4K6G4nRP9032RIAAAAAABUVIn","v6HIzNa4K6G4nRP9032RIAAAAAABUTxV","v6HIzNa4K6G4nRP9032RIAAAAAABUVYO"],"type_ids":[3,3,3,3,3,3,3,3]},"1Hf53oSb-zH-2QD2FYxgyA":{"address_or_lines":[4636706,4469836,4468509,4463096,4465892,4469227,4567193,4567640,5020934],"file_ids":["LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g","LvhLWomlc0dSPYzQ8C620g"],"frame_ids":["LvhLWomlc0dSPYzQ8C620gAAAAAARsAi","LvhLWomlc0dSPYzQ8C620gAAAAAARDRM","LvhLWomlc0dSPYzQ8C620gAAAAAARC8d","LvhLWomlc0dSPYzQ8C620gAAAAAARBn4","LvhLWomlc0dSPYzQ8C620gAAAAAARCTk","LvhLWomlc0dSPYzQ8C620gAAAAAARDHr","LvhLWomlc0dSPYzQ8C620gAAAAAARbCZ","LvhLWomlc0dSPYzQ8C620gAAAAAARbJY","LvhLWomlc0dSPYzQ8C620gAAAAAATJ0G"],"type_ids":[3,3,3,3,3,3,3,3,3]},"ER-x6xVv257WtFQAI5qb9g":{"address_or_lines":[4643592,4325284,4340382,4331972,4332836,4337401,4594856,4566419,4563908,4561911],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARtsI","B8JRxL079xbhqQBqGvksAgAAAAAAQf-k","B8JRxL079xbhqQBqGvksAgAAAAAAQjqe","B8JRxL079xbhqQBqGvksAgAAAAAAQhnE","B8JRxL079xbhqQBqGvksAgAAAAAAQh0k","B8JRxL079xbhqQBqGvksAgAAAAAAQi75","B8JRxL079xbhqQBqGvksAgAAAAAARhyo","B8JRxL079xbhqQBqGvksAgAAAAAARa2T","B8JRxL079xbhqQBqGvksAgAAAAAARaPE","B8JRxL079xbhqQBqGvksAgAAAAAARZv3"],"type_ids":[3,3,3,3,3,3,3,3,3,3]},"Hr1OSWigQhS4BD9n1H0fVw":{"address_or_lines":[4646178,4471372,4470064,4464366,4415320,4209576,4209709,10485923,16807,3096172,3095028],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARuUi","FWZ9q3TQKZZok58ua1HDsgAAAAAARDpM","FWZ9q3TQKZZok58ua1HDsgAAAAAARDUw","FWZ9q3TQKZZok58ua1HDsgAAAAAARB7u","FWZ9q3TQKZZok58ua1HDsgAAAAAAQ19Y","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDuo","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAALz5s","ew01Dk0sWZctP-VaEpavqQAAAAAALzn0"],"type_ids":[3,3,3,3,3,3,3,4,4,4,4]},"g1qDjUCVlmghGHVDrjeDvw":{"address_or_lines":[18425604,18258924,18257560,18253668,18248332,18043494,18206037,18442402,10485923,16743,1221731,1219041],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGScE","j8DVIOTu7Btj9lgFefJ84AAAAAABFpvs","j8DVIOTu7Btj9lgFefJ84AAAAAABFpaY","j8DVIOTu7Btj9lgFefJ84AAAAAABFodk","j8DVIOTu7Btj9lgFefJ84AAAAAABFnKM","j8DVIOTu7Btj9lgFefJ84AAAAAABE1Jm","j8DVIOTu7Btj9lgFefJ84AAAAAABFc1V","j8DVIOTu7Btj9lgFefJ84AAAAAABGWii","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAEqRj","piWSMQrh4r040D0BPNaJvwAAAAAAEpnh"],"type_ids":[3,3,3,3,3,3,3,3,4,4,4,4]},"XU0AYWfaWEgxn6HS3Npe0Q":{"address_or_lines":[18506340,18339660,18338296,18334404,18329068,18124198,18286773,18523138,10485923,16807,1222099,1220257,1210315],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGmJk","v6HIzNa4K6G4nRP9032RIAAAAAABF9dM","v6HIzNa4K6G4nRP9032RIAAAAAABF9H4","v6HIzNa4K6G4nRP9032RIAAAAAABF8LE","v6HIzNa4K6G4nRP9032RIAAAAAABF63s","v6HIzNa4K6G4nRP9032RIAAAAAABFI2m","v6HIzNa4K6G4nRP9032RIAAAAAABFwi1","v6HIzNa4K6G4nRP9032RIAAAAAABGqQC","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAEqXT","ew01Dk0sWZctP-VaEpavqQAAAAAAEp6h","ew01Dk0sWZctP-VaEpavqQAAAAAAEnfL"],"type_ids":[3,3,3,3,3,3,3,3,4,4,4,4,4]},"Xi_OuuwxmtjxVLfRnOKl-w":{"address_or_lines":[4643332,4460312,4460498,4495428,4495848,4496542,4426254,4658837,10485923,16807,633597,633524,633342,631364],"file_ids":["6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["6auiCMWq5cA-hAbqSYvdQQAAAAAARtoE","6auiCMWq5cA-hAbqSYvdQQAAAAAARA8Y","6auiCMWq5cA-hAbqSYvdQQAAAAAARA_S","6auiCMWq5cA-hAbqSYvdQQAAAAAARJhE","6auiCMWq5cA-hAbqSYvdQQAAAAAARJno","6auiCMWq5cA-hAbqSYvdQQAAAAAARJye","6auiCMWq5cA-hAbqSYvdQQAAAAAAQ4oO","6auiCMWq5cA-hAbqSYvdQQAAAAAARxaV","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAACar9","ew01Dk0sWZctP-VaEpavqQAAAAAACaq0","ew01Dk0sWZctP-VaEpavqQAAAAAACan-","ew01Dk0sWZctP-VaEpavqQAAAAAACaJE"],"type_ids":[3,3,3,3,3,3,3,3,4,4,4,4,4,4]},"j3pRZrJva_6zVfPpTrRgMQ":{"address_or_lines":[4435309,4435559,4470649,4243696,4243480,4398678,4639074,10485923,16807,1222099,1220257,1210438,1210021,1207727,1205915],"file_ids":["gfRL5jyxmWedM28UI08hFQ","gfRL5jyxmWedM28UI08hFQ","gfRL5jyxmWedM28UI08hFQ","gfRL5jyxmWedM28UI08hFQ","gfRL5jyxmWedM28UI08hFQ","gfRL5jyxmWedM28UI08hFQ","gfRL5jyxmWedM28UI08hFQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["gfRL5jyxmWedM28UI08hFQAAAAAAQ61t","gfRL5jyxmWedM28UI08hFQAAAAAAQ65n","gfRL5jyxmWedM28UI08hFQAAAAAARDd5","gfRL5jyxmWedM28UI08hFQAAAAAAQMDw","gfRL5jyxmWedM28UI08hFQAAAAAAQMAY","gfRL5jyxmWedM28UI08hFQAAAAAAQx5W","gfRL5jyxmWedM28UI08hFQAAAAAARsli","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAEqXT","ew01Dk0sWZctP-VaEpavqQAAAAAAEp6h","ew01Dk0sWZctP-VaEpavqQAAAAAAEnhG","ew01Dk0sWZctP-VaEpavqQAAAAAAEnal","ew01Dk0sWZctP-VaEpavqQAAAAAAEm2v","ew01Dk0sWZctP-VaEpavqQAAAAAAEmab"],"type_ids":[3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"B0rzVoKcdftibP3e40EU_g":{"address_or_lines":[4594276,4428280,4428466,4462056,4242611,4242276,4392174,4610690,10485923,16743,1221731,1219889,1210331,1133072,1132968,8474365],"file_ids":["1QjX8mEQC0-5qYXzadOESA","1QjX8mEQC0-5qYXzadOESA","1QjX8mEQC0-5qYXzadOESA","1QjX8mEQC0-5qYXzadOESA","1QjX8mEQC0-5qYXzadOESA","1QjX8mEQC0-5qYXzadOESA","1QjX8mEQC0-5qYXzadOESA","1QjX8mEQC0-5qYXzadOESA","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["1QjX8mEQC0-5qYXzadOESAAAAAAARhpk","1QjX8mEQC0-5qYXzadOESAAAAAAAQ5H4","1QjX8mEQC0-5qYXzadOESAAAAAAAQ5Ky","1QjX8mEQC0-5qYXzadOESAAAAAAARBXo","1QjX8mEQC0-5qYXzadOESAAAAAAAQLyz","1QjX8mEQC0-5qYXzadOESAAAAAAAQLtk","1QjX8mEQC0-5qYXzadOESAAAAAAAQwTu","1QjX8mEQC0-5qYXzadOESAAAAAAARlqC","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAEqRj","piWSMQrh4r040D0BPNaJvwAAAAAAEp0x","piWSMQrh4r040D0BPNaJvwAAAAAAEnfb","piWSMQrh4r040D0BPNaJvwAAAAAAEUoQ","piWSMQrh4r040D0BPNaJvwAAAAAAEUmo","piWSMQrh4r040D0BPNaJvwAAAAAAgU79"],"type_ids":[3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"jHWwY4al2R105ljWitJf8Q":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584294],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj6m"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"JFrKrVm1b8YVyjTALHwFPQ":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000312,40003155,27960932,18154776,18503217],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYls4","v6HIzNa4K6G4nRP9032RIAAAAAACYmZT","v6HIzNa4K6G4nRP9032RIAAAAAABqqZk","v6HIzNa4K6G4nRP9032RIAAAAAABFQUY","v6HIzNa4K6G4nRP9032RIAAAAAABGlYx"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"UkNqUaLVbzZ-0N4mRSSfPA":{"address_or_lines":[4652224,31039781,31054085,31056132,31058408,31449931,30791268,25539462,25547885,25549299,25502704,25503492,25480821,25481061,4953508,4960780,4898318,4893650,4898126],"file_ids":["wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw"],"frame_ids":["wfA2BgwfDNXUWsxkJ083RwAAAAAARvzA","wfA2BgwfDNXUWsxkJ083RwAAAAAB2aEl","wfA2BgwfDNXUWsxkJ083RwAAAAAB2dkF","wfA2BgwfDNXUWsxkJ083RwAAAAAB2eEE","wfA2BgwfDNXUWsxkJ083RwAAAAAB2eno","wfA2BgwfDNXUWsxkJ083RwAAAAAB3-NL","wfA2BgwfDNXUWsxkJ083RwAAAAAB1dZk","wfA2BgwfDNXUWsxkJ083RwAAAAABhbOG","wfA2BgwfDNXUWsxkJ083RwAAAAABhdRt","wfA2BgwfDNXUWsxkJ083RwAAAAABhdnz","wfA2BgwfDNXUWsxkJ083RwAAAAABhSPw","wfA2BgwfDNXUWsxkJ083RwAAAAABhScE","wfA2BgwfDNXUWsxkJ083RwAAAAABhM51","wfA2BgwfDNXUWsxkJ083RwAAAAABhM9l","wfA2BgwfDNXUWsxkJ083RwAAAAAAS5Wk","wfA2BgwfDNXUWsxkJ083RwAAAAAAS7IM","wfA2BgwfDNXUWsxkJ083RwAAAAAASr4O","wfA2BgwfDNXUWsxkJ083RwAAAAAASqvS","wfA2BgwfDNXUWsxkJ083RwAAAAAASr1O"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"EH1ElzcXDEuDqu7McdrBdQ":{"address_or_lines":[4652224,22357367,22385134,22366798,57076399,58917522,58676957,58636100,58650141,31265796,7372944,7295421,7297245,7300762,7297188,7304836,7297413,7309604,7297924,5094553],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZuqv","B8JRxL079xbhqQBqGvksAgAAAAADgwKS","B8JRxL079xbhqQBqGvksAgAAAAADf1bd","B8JRxL079xbhqQBqGvksAgAAAAADfrdE","B8JRxL079xbhqQBqGvksAgAAAAADfu4d","B8JRxL079xbhqQBqGvksAgAAAAAB3RQE","B8JRxL079xbhqQBqGvksAgAAAAAAcICQ","B8JRxL079xbhqQBqGvksAgAAAAAAb1G9","B8JRxL079xbhqQBqGvksAgAAAAAAb1jd","B8JRxL079xbhqQBqGvksAgAAAAAAb2aa","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1mF","B8JRxL079xbhqQBqGvksAgAAAAAAb4kk","B8JRxL079xbhqQBqGvksAgAAAAAAb1uE","B8JRxL079xbhqQBqGvksAgAAAAAATbyZ"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"VyF1fKBkXgRmNRnKNEu8Fw":{"address_or_lines":[4652224,59362286,59048854,59078134,59085018,59179681,31752932,6709512,4951332,4960314,4742003,4757981,4219698,4219725,10485923,16807,2741196,2827770,2817684,2805156,3382963],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAADicvu","B8JRxL079xbhqQBqGvksAgAAAAADhQOW","B8JRxL079xbhqQBqGvksAgAAAAADhXX2","B8JRxL079xbhqQBqGvksAgAAAAADhZDa","B8JRxL079xbhqQBqGvksAgAAAAADhwKh","B8JRxL079xbhqQBqGvksAgAAAAAB5ILk","B8JRxL079xbhqQBqGvksAgAAAAAAZmEI","B8JRxL079xbhqQBqGvksAgAAAAAAS40k","B8JRxL079xbhqQBqGvksAgAAAAAAS7A6","B8JRxL079xbhqQBqGvksAgAAAAAASFtz","B8JRxL079xbhqQBqGvksAgAAAAAASJnd","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM","A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6","A2oiHVwisByxRn5RDT4LjAAAAAAAKv6U","A2oiHVwisByxRn5RDT4LjAAAAAAAKs2k","A2oiHVwisByxRn5RDT4LjAAAAAAAM56z"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4]},"naNkvUaKAyxw8L7AmrJp_A":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226539,39801748,39804999,39805475,40019662,39816300,32602139,24420574,24417550,19100458,18003551],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdREr","j8DVIOTu7Btj9lgFefJ84AAAAAACX1OU","j8DVIOTu7Btj9lgFefJ84AAAAAACX2BH","j8DVIOTu7Btj9lgFefJ84AAAAAACX2Ij","j8DVIOTu7Btj9lgFefJ84AAAAAACYqbO","j8DVIOTu7Btj9lgFefJ84AAAAAACX4xs","j8DVIOTu7Btj9lgFefJ84AAAAAAB8Xgb","j8DVIOTu7Btj9lgFefJ84AAAAAABdKDe","j8DVIOTu7Btj9lgFefJ84AAAAAABdJUO","j8DVIOTu7Btj9lgFefJ84AAAAAABI3Mq","j8DVIOTu7Btj9lgFefJ84AAAAAABErZf"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"INCPC3idrKxHgrRrb5yK7w":{"address_or_lines":[4652224,22357367,22385134,22366798,57079599,58878037,58675517,58634660,58648701,31265316,7372944,7295421,7297245,7300762,7297188,7304836,7297245,7300762,7297188,7304836,7297413,7310803,7320503],"file_ids":["wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw"],"frame_ids":["wfA2BgwfDNXUWsxkJ083RwAAAAAARvzA","wfA2BgwfDNXUWsxkJ083RwAAAAABVSV3","wfA2BgwfDNXUWsxkJ083RwAAAAABVZHu","wfA2BgwfDNXUWsxkJ083RwAAAAABVUpO","wfA2BgwfDNXUWsxkJ083RwAAAAADZvcv","wfA2BgwfDNXUWsxkJ083RwAAAAADgmhV","wfA2BgwfDNXUWsxkJ083RwAAAAADf1E9","wfA2BgwfDNXUWsxkJ083RwAAAAADfrGk","wfA2BgwfDNXUWsxkJ083RwAAAAADfuh9","wfA2BgwfDNXUWsxkJ083RwAAAAAB3RIk","wfA2BgwfDNXUWsxkJ083RwAAAAAAcICQ","wfA2BgwfDNXUWsxkJ083RwAAAAAAb1G9","wfA2BgwfDNXUWsxkJ083RwAAAAAAb1jd","wfA2BgwfDNXUWsxkJ083RwAAAAAAb2aa","wfA2BgwfDNXUWsxkJ083RwAAAAAAb1ik","wfA2BgwfDNXUWsxkJ083RwAAAAAAb3aE","wfA2BgwfDNXUWsxkJ083RwAAAAAAb1jd","wfA2BgwfDNXUWsxkJ083RwAAAAAAb2aa","wfA2BgwfDNXUWsxkJ083RwAAAAAAb1ik","wfA2BgwfDNXUWsxkJ083RwAAAAAAb3aE","wfA2BgwfDNXUWsxkJ083RwAAAAAAb1mF","wfA2BgwfDNXUWsxkJ083RwAAAAAAb43T","wfA2BgwfDNXUWsxkJ083RwAAAAAAb7O3"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"4-XWrzbKLiMzMN29SCKUhA":{"address_or_lines":[4652224,31041029,31055333,31057380,31059656,31451286,31449907,25120346,25115948,4970003,4971223,4754617,4757981,4219698,4219725,10485923,16807,2777344,2775602,2826949,2809805,2807527,2804929,2869997],"file_ids":["6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["6auiCMWq5cA-hAbqSYvdQQAAAAAARvzA","6auiCMWq5cA-hAbqSYvdQQAAAAAB2aYF","6auiCMWq5cA-hAbqSYvdQQAAAAAB2d3l","6auiCMWq5cA-hAbqSYvdQQAAAAAB2eXk","6auiCMWq5cA-hAbqSYvdQQAAAAAB2e7I","6auiCMWq5cA-hAbqSYvdQQAAAAAB3-iW","6auiCMWq5cA-hAbqSYvdQQAAAAAB3-Mz","6auiCMWq5cA-hAbqSYvdQQAAAAABf05a","6auiCMWq5cA-hAbqSYvdQQAAAAABfz0s","6auiCMWq5cA-hAbqSYvdQQAAAAAAS9YT","6auiCMWq5cA-hAbqSYvdQQAAAAAAS9rX","6auiCMWq5cA-hAbqSYvdQQAAAAAASIy5","6auiCMWq5cA-hAbqSYvdQQAAAAAASJnd","6auiCMWq5cA-hAbqSYvdQQAAAAAAQGMy","6auiCMWq5cA-hAbqSYvdQQAAAAAAQGNN","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKmEA","ew01Dk0sWZctP-VaEpavqQAAAAAAKloy","ew01Dk0sWZctP-VaEpavqQAAAAAAKyLF","ew01Dk0sWZctP-VaEpavqQAAAAAAKt_N","ew01Dk0sWZctP-VaEpavqQAAAAAAKtbn","ew01Dk0sWZctP-VaEpavqQAAAAAAKszB","ew01Dk0sWZctP-VaEpavqQAAAAAAK8rt"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"oazzZOrFVKPzoEMEINIH2g":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226539,39801748,39804999,39805475,40019662,39816300,32602256,32687470,24708823,24695729,24696100,20084005,20770646,20784592],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdREr","j8DVIOTu7Btj9lgFefJ84AAAAAACX1OU","j8DVIOTu7Btj9lgFefJ84AAAAAACX2BH","j8DVIOTu7Btj9lgFefJ84AAAAAACX2Ij","j8DVIOTu7Btj9lgFefJ84AAAAAACYqbO","j8DVIOTu7Btj9lgFefJ84AAAAAACX4xs","j8DVIOTu7Btj9lgFefJ84AAAAAAB8XiQ","j8DVIOTu7Btj9lgFefJ84AAAAAAB8sVu","j8DVIOTu7Btj9lgFefJ84AAAAAABeQbX","j8DVIOTu7Btj9lgFefJ84AAAAAABeNOx","j8DVIOTu7Btj9lgFefJ84AAAAAABeNUk","j8DVIOTu7Btj9lgFefJ84AAAAAABMnUl","j8DVIOTu7Btj9lgFefJ84AAAAAABPO9W","j8DVIOTu7Btj9lgFefJ84AAAAAABPSXQ"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"bgW4z1P_qeyGZ-BNg-EtzA":{"address_or_lines":[43732576,54345578,54346325,54347573,52524033,52636324,52637912,52417621,52420674,52436132,51874398,51910204,51902690,51903112,51905980,51885853,51874436,51883428,51874436,51883428,51874436,51883398,51839246,52405829,52404692,44450492],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAADPT9q","MNBJ5seVz_ocW6tcr1HSmwAAAAADPUJV","MNBJ5seVz_ocW6tcr1HSmwAAAAADPUc1","MNBJ5seVz_ocW6tcr1HSmwAAAAADIXQB","MNBJ5seVz_ocW6tcr1HSmwAAAAADIyqk","MNBJ5seVz_ocW6tcr1HSmwAAAAADIzDY","MNBJ5seVz_ocW6tcr1HSmwAAAAADH9RV","MNBJ5seVz_ocW6tcr1HSmwAAAAADH-BC","MNBJ5seVz_ocW6tcr1HSmwAAAAADIByk","MNBJ5seVz_ocW6tcr1HSmwAAAAADF4pe","MNBJ5seVz_ocW6tcr1HSmwAAAAADGBY8","MNBJ5seVz_ocW6tcr1HSmwAAAAADF_ji","MNBJ5seVz_ocW6tcr1HSmwAAAAADF_qI","MNBJ5seVz_ocW6tcr1HSmwAAAAADGAW8","MNBJ5seVz_ocW6tcr1HSmwAAAAADF7cd","MNBJ5seVz_ocW6tcr1HSmwAAAAADF4qE","MNBJ5seVz_ocW6tcr1HSmwAAAAADF62k","MNBJ5seVz_ocW6tcr1HSmwAAAAADF4qE","MNBJ5seVz_ocW6tcr1HSmwAAAAADF62k","MNBJ5seVz_ocW6tcr1HSmwAAAAADF4qE","MNBJ5seVz_ocW6tcr1HSmwAAAAADF62G","MNBJ5seVz_ocW6tcr1HSmwAAAAADFwEO","MNBJ5seVz_ocW6tcr1HSmwAAAAADH6ZF","MNBJ5seVz_ocW6tcr1HSmwAAAAADH6HU","MNBJ5seVz_ocW6tcr1HSmwAAAAACpkK8"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"_7muG2H-TTX5D3mi3LROgw":{"address_or_lines":[4652224,31041029,31055333,31057380,31059656,31451179,30792516,25540230,25548731,25550840,25503472,25504260,25481372,25481181,25484711,25484964,4951332,4960527,4959954,4897957,4893996,4627954,4660663,10485923,16807,3103928,3101167],"file_ids":["6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","6auiCMWq5cA-hAbqSYvdQQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["6auiCMWq5cA-hAbqSYvdQQAAAAAARvzA","6auiCMWq5cA-hAbqSYvdQQAAAAAB2aYF","6auiCMWq5cA-hAbqSYvdQQAAAAAB2d3l","6auiCMWq5cA-hAbqSYvdQQAAAAAB2eXk","6auiCMWq5cA-hAbqSYvdQQAAAAAB2e7I","6auiCMWq5cA-hAbqSYvdQQAAAAAB3-gr","6auiCMWq5cA-hAbqSYvdQQAAAAAB1dtE","6auiCMWq5cA-hAbqSYvdQQAAAAABhbaG","6auiCMWq5cA-hAbqSYvdQQAAAAABhde7","6auiCMWq5cA-hAbqSYvdQQAAAAABhd_4","6auiCMWq5cA-hAbqSYvdQQAAAAABhSbw","6auiCMWq5cA-hAbqSYvdQQAAAAABhSoE","6auiCMWq5cA-hAbqSYvdQQAAAAABhNCc","6auiCMWq5cA-hAbqSYvdQQAAAAABhM_d","6auiCMWq5cA-hAbqSYvdQQAAAAABhN2n","6auiCMWq5cA-hAbqSYvdQQAAAAABhN6k","6auiCMWq5cA-hAbqSYvdQQAAAAAAS40k","6auiCMWq5cA-hAbqSYvdQQAAAAAAS7EP","6auiCMWq5cA-hAbqSYvdQQAAAAAAS67S","6auiCMWq5cA-hAbqSYvdQQAAAAAASryl","6auiCMWq5cA-hAbqSYvdQQAAAAAASq0s","6auiCMWq5cA-hAbqSYvdQQAAAAAARp3y","6auiCMWq5cA-hAbqSYvdQQAAAAAARx23","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAL1y4","ew01Dk0sWZctP-VaEpavqQAAAAAAL1Hv"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4]},"nKCqWW03DZONEM_Nq2LvwQ":{"address_or_lines":[12540096,19004791,19032250,19014236,19907031,31278974,31279321,31305795,31279321,31290406,31279321,31317002,19907351,21668882,21654220,21663244,21662923,16321295,16318241,16372475,15847297,16321906,16318704,15818442,15818729,12152742,12151794,12187561],"file_ids":["67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg","67s2TwiMngM0yin5Y8pvEg"],"frame_ids":["67s2TwiMngM0yin5Y8pvEgAAAAAAv1jA","67s2TwiMngM0yin5Y8pvEgAAAAABIf13","67s2TwiMngM0yin5Y8pvEgAAAAABImi6","67s2TwiMngM0yin5Y8pvEgAAAAABIiJc","67s2TwiMngM0yin5Y8pvEgAAAAABL8HX","67s2TwiMngM0yin5Y8pvEgAAAAAB3Ud-","67s2TwiMngM0yin5Y8pvEgAAAAAB3UjZ","67s2TwiMngM0yin5Y8pvEgAAAAAB3bBD","67s2TwiMngM0yin5Y8pvEgAAAAAB3UjZ","67s2TwiMngM0yin5Y8pvEgAAAAAB3XQm","67s2TwiMngM0yin5Y8pvEgAAAAAB3UjZ","67s2TwiMngM0yin5Y8pvEgAAAAAB3dwK","67s2TwiMngM0yin5Y8pvEgAAAAABL8MX","67s2TwiMngM0yin5Y8pvEgAAAAABSqQS","67s2TwiMngM0yin5Y8pvEgAAAAABSmrM","67s2TwiMngM0yin5Y8pvEgAAAAABSo4M","67s2TwiMngM0yin5Y8pvEgAAAAABSozL","67s2TwiMngM0yin5Y8pvEgAAAAAA-QsP","67s2TwiMngM0yin5Y8pvEgAAAAAA-P8h","67s2TwiMngM0yin5Y8pvEgAAAAAA-dL7","67s2TwiMngM0yin5Y8pvEgAAAAAA8c-B","67s2TwiMngM0yin5Y8pvEgAAAAAA-Q1y","67s2TwiMngM0yin5Y8pvEgAAAAAA-QDw","67s2TwiMngM0yin5Y8pvEgAAAAAA8V7K","67s2TwiMngM0yin5Y8pvEgAAAAAA8V_p","67s2TwiMngM0yin5Y8pvEgAAAAAAuW-m","67s2TwiMngM0yin5Y8pvEgAAAAAAuWvy","67s2TwiMngM0yin5Y8pvEgAAAAAAufep"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"08TjeY9jNFfBuPDWZvzcGA":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226539,39801748,39804999,39805475,40019662,39816300,32602256,32687470,24708845,24702901,19816356,19817629,19819812,19827076,19819869,19823237,19819812,19819076],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdREr","j8DVIOTu7Btj9lgFefJ84AAAAAACX1OU","j8DVIOTu7Btj9lgFefJ84AAAAAACX2BH","j8DVIOTu7Btj9lgFefJ84AAAAAACX2Ij","j8DVIOTu7Btj9lgFefJ84AAAAAACYqbO","j8DVIOTu7Btj9lgFefJ84AAAAAACX4xs","j8DVIOTu7Btj9lgFefJ84AAAAAAB8XiQ","j8DVIOTu7Btj9lgFefJ84AAAAAAB8sVu","j8DVIOTu7Btj9lgFefJ84AAAAAABeQbt","j8DVIOTu7Btj9lgFefJ84AAAAAABeO-1","j8DVIOTu7Btj9lgFefJ84AAAAAABLl-k","j8DVIOTu7Btj9lgFefJ84AAAAAABLmSd","j8DVIOTu7Btj9lgFefJ84AAAAAABLm0k","j8DVIOTu7Btj9lgFefJ84AAAAAABLomE","j8DVIOTu7Btj9lgFefJ84AAAAAABLm1d","j8DVIOTu7Btj9lgFefJ84AAAAAABLnqF","j8DVIOTu7Btj9lgFefJ84AAAAAABLm0k","j8DVIOTu7Btj9lgFefJ84AAAAAABLmpE"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"41gF_giRSTRZMXWPVpvLYA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791213,24785269,19897796,19899069,19901252,19908516,19901309,19904677,19901252,19907099,19901069],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekit","v6HIzNa4K6G4nRP9032RIAAAAAABejF1","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6t9","v6HIzNa4K6G4nRP9032RIAAAAAABL7il","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8Ib","v6HIzNa4K6G4nRP9032RIAAAAAABL6qN"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"CCCw9Z7XCAUBXfzhCKjvyQ":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791191,24778097,24778468,20166836,20169482,20167663,20167859,19086136,19109575,19098127,19092114,19079610],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekiX","v6HIzNa4K6G4nRP9032RIAAAAAABehVx","v6HIzNa4K6G4nRP9032RIAAAAAABehbk","v6HIzNa4K6G4nRP9032RIAAAAAABM7i0","v6HIzNa4K6G4nRP9032RIAAAAAABM8MK","v6HIzNa4K6G4nRP9032RIAAAAAABM7vv","v6HIzNa4K6G4nRP9032RIAAAAAABM7yz","v6HIzNa4K6G4nRP9032RIAAAAAABIzs4","v6HIzNa4K6G4nRP9032RIAAAAAABI5bH","v6HIzNa4K6G4nRP9032RIAAAAAABI2oP","v6HIzNa4K6G4nRP9032RIAAAAAABI1KS","v6HIzNa4K6G4nRP9032RIAAAAAABIyG6"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"RK2MfkyDuA83Ote1DRpnig":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791213,24785269,19897796,19899069,19901252,19908516,19901252,19908516,19901309,19904677,19901477,19914228,19923006],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekit","v6HIzNa4K6G4nRP9032RIAAAAAABejF1","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6t9","v6HIzNa4K6G4nRP9032RIAAAAAABL7il","v6HIzNa4K6G4nRP9032RIAAAAAABL6wl","v6HIzNa4K6G4nRP9032RIAAAAAABL930","v6HIzNa4K6G4nRP9032RIAAAAAABMAA-"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"E9YrFLZE6ytYTLr5nOdeqA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16755],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEFz"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4]},"OaI2ikXPfU9oPJVr7qHqRA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791191,24778097,24778417,19045737,19044484,19054298,18859716,18879913,10485923,16807,2741468,2828042,2818852,4377977,4376240],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekiX","v6HIzNa4K6G4nRP9032RIAAAAAABehVx","v6HIzNa4K6G4nRP9032RIAAAAAABehax","v6HIzNa4K6G4nRP9032RIAAAAAABIp1p","v6HIzNa4K6G4nRP9032RIAAAAAABIpiE","v6HIzNa4K6G4nRP9032RIAAAAAABIr7a","v6HIzNa4K6G4nRP9032RIAAAAAABH8bE","v6HIzNa4K6G4nRP9032RIAAAAAABIBWp","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKdTc","ew01Dk0sWZctP-VaEpavqQAAAAAAKycK","ew01Dk0sWZctP-VaEpavqQAAAAAAKwMk","ew01Dk0sWZctP-VaEpavqQAAAAAAQs15","ew01Dk0sWZctP-VaEpavqQAAAAAAQsaw"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4]},"BeervgrHDOwHnECUdx-R1Q":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54557252,54545733,54548081,54524484,54525381,54528188,54495447,54497074,54477482,44043465,44042020,44050767,44050194,43988037,43983308,43704594,43741015,10485923,16807,3103112,3099892,3094686,3393841,3393734,3091863,2557902,2671840],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHpE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQE1F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQFZx","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_pE","MNBJ5seVz_ocW6tcr1HSmwAAAAADP_3F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQAi8","MNBJ5seVz_ocW6tcr1HSmwAAAAADP4jX","MNBJ5seVz_ocW6tcr1HSmwAAAAADP48y","MNBJ5seVz_ocW6tcr1HSmwAAAAADP0Kq","MNBJ5seVz_ocW6tcr1HSmwAAAAACoAzJ","MNBJ5seVz_ocW6tcr1HSmwAAAAACoAck","MNBJ5seVz_ocW6tcr1HSmwAAAAACoClP","MNBJ5seVz_ocW6tcr1HSmwAAAAACoCcS","MNBJ5seVz_ocW6tcr1HSmwAAAAACnzRF","MNBJ5seVz_ocW6tcr1HSmwAAAAACnyHM","MNBJ5seVz_ocW6tcr1HSmwAAAAACmuES","MNBJ5seVz_ocW6tcr1HSmwAAAAACm29X","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAL1mI","9LzzIocepYcOjnUsLlgOjgAAAAAAL0z0","9LzzIocepYcOjnUsLlgOjgAAAAAALzie","9LzzIocepYcOjnUsLlgOjgAAAAAAM8kx","9LzzIocepYcOjnUsLlgOjgAAAAAAM8jG","9LzzIocepYcOjnUsLlgOjgAAAAAALy2X","9LzzIocepYcOjnUsLlgOjgAAAAAAJwfO","9LzzIocepYcOjnUsLlgOjgAAAAAAKMTg"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4]},"_E7kI3XeP50ndUGgLwozRw":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226539,39801748,39804999,39805475,40019662,39816300,32602256,32687470,24708823,24695729,24696049,18964841,18963588,18973402,18778948,18799145,10485923,16743,2737420,2823946,2813708,2804875,2803431,2800833,2865890],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdREr","j8DVIOTu7Btj9lgFefJ84AAAAAACX1OU","j8DVIOTu7Btj9lgFefJ84AAAAAACX2BH","j8DVIOTu7Btj9lgFefJ84AAAAAACX2Ij","j8DVIOTu7Btj9lgFefJ84AAAAAACYqbO","j8DVIOTu7Btj9lgFefJ84AAAAAACX4xs","j8DVIOTu7Btj9lgFefJ84AAAAAAB8XiQ","j8DVIOTu7Btj9lgFefJ84AAAAAAB8sVu","j8DVIOTu7Btj9lgFefJ84AAAAAABeQbX","j8DVIOTu7Btj9lgFefJ84AAAAAABeNOx","j8DVIOTu7Btj9lgFefJ84AAAAAABeNTx","j8DVIOTu7Btj9lgFefJ84AAAAAABIWFp","j8DVIOTu7Btj9lgFefJ84AAAAAABIVyE","j8DVIOTu7Btj9lgFefJ84AAAAAABIYLa","j8DVIOTu7Btj9lgFefJ84AAAAAABHotE","j8DVIOTu7Btj9lgFefJ84AAAAAABHtop","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKcUM","piWSMQrh4r040D0BPNaJvwAAAAAAKxcK","piWSMQrh4r040D0BPNaJvwAAAAAAKu8M","piWSMQrh4r040D0BPNaJvwAAAAAAKsyL","piWSMQrh4r040D0BPNaJvwAAAAAAKsbn","piWSMQrh4r040D0BPNaJvwAAAAAAKrzB","piWSMQrh4r040D0BPNaJvwAAAAAAK7ri"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"PiAbunsxsTWIrlVv5AJCxQ":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7441528],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYx4"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"gcylfs4yiiRtiY_AHc1fkQ":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440136,7508562],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI","ew01Dk0sWZctP-VaEpavqQAAAAAAcpJS"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"2J6chKI2om9Kbvwi1SgqlA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7441584,6770797,6773738,2395067],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYyw","ew01Dk0sWZctP-VaEpavqQAAAAAAZ1Bt","ew01Dk0sWZctP-VaEpavqQAAAAAAZ1vq","ew01Dk0sWZctP-VaEpavqQAAAAAAJIu7"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"YX2R7C2iz4FGt5q5Tnk6TA":{"address_or_lines":[18434496,22515341,22492438,22512730,32109966,22497902,40241913,34110888,40114070,40112026,41252858,41226601,40103401,19895453,19846041,19847127,19902436,19861609,19902628,19862836,19902820,19863773,19901256,19856467,19901444,19858248,18713630,18723524,18720816,19859472,18001099,10488398,10493154,585983,12583132,6817209,21184,6815932,6812296,6811747,6811254,7304819,7302120],"file_ids":["j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","j8DVIOTu7Btj9lgFefJ84A","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","_3bHXKBtA1BrvZVdhZK3vg","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["j8DVIOTu7Btj9lgFefJ84AAAAAABGUnA","j8DVIOTu7Btj9lgFefJ84AAAAAABV46N","j8DVIOTu7Btj9lgFefJ84AAAAAABVzUW","j8DVIOTu7Btj9lgFefJ84AAAAAABV4Ra","j8DVIOTu7Btj9lgFefJ84AAAAAAB6fWO","j8DVIOTu7Btj9lgFefJ84AAAAAABV0pu","j8DVIOTu7Btj9lgFefJ84AAAAAACZgr5","j8DVIOTu7Btj9lgFefJ84AAAAAACCH2o","j8DVIOTu7Btj9lgFefJ84AAAAAACZBeW","j8DVIOTu7Btj9lgFefJ84AAAAAACZA-a","j8DVIOTu7Btj9lgFefJ84AAAAAACdXf6","j8DVIOTu7Btj9lgFefJ84AAAAAACdRFp","j8DVIOTu7Btj9lgFefJ84AAAAAACY-3p","j8DVIOTu7Btj9lgFefJ84AAAAAABL5Sd","j8DVIOTu7Btj9lgFefJ84AAAAAABLtOZ","j8DVIOTu7Btj9lgFefJ84AAAAAABLtfX","j8DVIOTu7Btj9lgFefJ84AAAAAABL6_k","j8DVIOTu7Btj9lgFefJ84AAAAAABLxBp","j8DVIOTu7Btj9lgFefJ84AAAAAABL7Ck","j8DVIOTu7Btj9lgFefJ84AAAAAABLxU0","j8DVIOTu7Btj9lgFefJ84AAAAAABL7Fk","j8DVIOTu7Btj9lgFefJ84AAAAAABLxjd","j8DVIOTu7Btj9lgFefJ84AAAAAABL6tI","j8DVIOTu7Btj9lgFefJ84AAAAAABLvxT","j8DVIOTu7Btj9lgFefJ84AAAAAABL6wE","j8DVIOTu7Btj9lgFefJ84AAAAAABLwNI","j8DVIOTu7Btj9lgFefJ84AAAAAABHYwe","j8DVIOTu7Btj9lgFefJ84AAAAAABHbLE","j8DVIOTu7Btj9lgFefJ84AAAAAABHagw","j8DVIOTu7Btj9lgFefJ84AAAAAABLwgQ","j8DVIOTu7Btj9lgFefJ84AAAAAABEqzL","piWSMQrh4r040D0BPNaJvwAAAAAAoApO","piWSMQrh4r040D0BPNaJvwAAAAAAoBzi","piWSMQrh4r040D0BPNaJvwAAAAAACPD_","piWSMQrh4r040D0BPNaJvwAAAAAAwADc","piWSMQrh4r040D0BPNaJvwAAAAAAaAW5","_3bHXKBtA1BrvZVdhZK3vgAAAAAAAFLA","piWSMQrh4r040D0BPNaJvwAAAAAAaAC8","piWSMQrh4r040D0BPNaJvwAAAAAAZ_KI","piWSMQrh4r040D0BPNaJvwAAAAAAZ_Bj","piWSMQrh4r040D0BPNaJvwAAAAAAZ-52","piWSMQrh4r040D0BPNaJvwAAAAAAb3Zz","piWSMQrh4r040D0BPNaJvwAAAAAAb2vo"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4]},"--7TGRswVMtk5qWYdGBDUw":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7439971,6798378,6797926,4866621,4855697,8473771],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYZj","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7wq","ew01Dk0sWZctP-VaEpavqQAAAAAAZ7pm","ew01Dk0sWZctP-VaEpavqQAAAAAASkI9","ew01Dk0sWZctP-VaEpavqQAAAAAASheR","ew01Dk0sWZctP-VaEpavqQAAAAAAgUyr"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"iVZ81pgajC_4cYBykPWgBg":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440136,7508344,7393457,7394824,7384416,6869315,6866863,2643],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","6miIyyucTZf5zXHCk7PT1g"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI","ew01Dk0sWZctP-VaEpavqQAAAAAAcpF4","ew01Dk0sWZctP-VaEpavqQAAAAAAcNCx","ew01Dk0sWZctP-VaEpavqQAAAAAAcNYI","ew01Dk0sWZctP-VaEpavqQAAAAAAcK1g","ew01Dk0sWZctP-VaEpavqQAAAAAAaNFD","ew01Dk0sWZctP-VaEpavqQAAAAAAaMev","6miIyyucTZf5zXHCk7PT1gAAAAAAAApT"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"dg33Fg5TLDtB9bOuPSPREA":{"address_or_lines":[980270,29770,3203438,1526226,1526293,1526410,1522622,1523799,453712,1320069,1900469,1899334,1898707,2062274,2293545,2285857,2284809,2485949,2472275,2784493,2826658,2823003,3007344,3001783,2924437,3112045,3104142,1417998,1456694,1456323,1393341,1348522,1348436,1345741,1348060,1347558,1345741,1348060,1347558,1345741,1348060,1347558,1345954,1343030,1342299,1335062,1334604,1334212,452199,518055,509958],"file_ids":["Z_CHd3Zjsh2cWE2NSdbiNQ","eOfhJQFIxbIEScd007tROw","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","-p9BlJh9JZMPPNjY_j92ng","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","huWyXZbCBWCe2ZtK9BiokQ","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ"],"frame_ids":["Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADvUu","eOfhJQFIxbIEScd007tROwAAAAAAAHRK","-p9BlJh9JZMPPNjY_j92ngAAAAAAMOFu","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0nS","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0oV","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0qK","-p9BlJh9JZMPPNjY_j92ngAAAAAAFzu-","-p9BlJh9JZMPPNjY_j92ngAAAAAAF0BX","-p9BlJh9JZMPPNjY_j92ngAAAAAABuxQ","-p9BlJh9JZMPPNjY_j92ngAAAAAAFCSF","-p9BlJh9JZMPPNjY_j92ngAAAAAAHP-1","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPtG","-p9BlJh9JZMPPNjY_j92ngAAAAAAHPjT","-p9BlJh9JZMPPNjY_j92ngAAAAAAH3fC","-p9BlJh9JZMPPNjY_j92ngAAAAAAIv8p","-p9BlJh9JZMPPNjY_j92ngAAAAAAIuEh","-p9BlJh9JZMPPNjY_j92ngAAAAAAIt0J","-p9BlJh9JZMPPNjY_j92ngAAAAAAJe69","-p9BlJh9JZMPPNjY_j92ngAAAAAAJblT","-p9BlJh9JZMPPNjY_j92ngAAAAAAKnzt","-p9BlJh9JZMPPNjY_j92ngAAAAAAKyGi","-p9BlJh9JZMPPNjY_j92ngAAAAAAKxNb","-p9BlJh9JZMPPNjY_j92ngAAAAAALeNw","-p9BlJh9JZMPPNjY_j92ngAAAAAALc23","-p9BlJh9JZMPPNjY_j92ngAAAAAALJ-V","-p9BlJh9JZMPPNjY_j92ngAAAAAAL3xt","-p9BlJh9JZMPPNjY_j92ngAAAAAAL12O","huWyXZbCBWCe2ZtK9BiokQAAAAAAFaMO","huWyXZbCBWCe2ZtK9BiokQAAAAAAFjo2","huWyXZbCBWCe2ZtK9BiokQAAAAAAFjjD","huWyXZbCBWCe2ZtK9BiokQAAAAAAFUK9","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJOq","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJNU","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN","huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc","huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m","huWyXZbCBWCe2ZtK9BiokQAAAAAAFImi","huWyXZbCBWCe2ZtK9BiokQAAAAAAFH42","huWyXZbCBWCe2ZtK9BiokQAAAAAAFHtb","huWyXZbCBWCe2ZtK9BiokQAAAAAAFF8W","huWyXZbCBWCe2ZtK9BiokQAAAAAAFF1M","huWyXZbCBWCe2ZtK9BiokQAAAAAAFFvE","huWyXZbCBWCe2ZtK9BiokQAAAAAABuZn","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB-en","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB8gG"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]}},"stack_frames":{"ew01Dk0sWZctP-VaEpavqQAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAALz5s":{"file_name":[],"function_name":["__x64_sys_epoll_pwait"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAALzn0":{"file_name":[],"function_name":["do_epoll_wait"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAAEFn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEqRj":{"file_name":[],"function_name":["__x64_sys_futex"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEpnh":{"file_name":[],"function_name":["do_futex"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEqXT":{"file_name":[],"function_name":["__x64_sys_futex"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEp6h":{"file_name":[],"function_name":["do_futex"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEnfL":{"file_name":[],"function_name":["futex_wait"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAACar9":{"file_name":[],"function_name":["__x64_sys_tgkill"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAACaq0":{"file_name":[],"function_name":["do_tkill"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAACan-":{"file_name":[],"function_name":["do_send_specific"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAACaJE":{"file_name":[],"function_name":["do_send_sig_info"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEnhG":{"file_name":[],"function_name":["futex_wait"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEnal":{"file_name":[],"function_name":["futex_wait_setup"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEm2v":{"file_name":[],"function_name":["get_futex_key"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAEmab":{"file_name":[],"function_name":["get_futex_key_refs.isra.8"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEp0x":{"file_name":[],"function_name":["do_futex"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEnfb":{"file_name":[],"function_name":["futex_wait"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEUoQ":{"file_name":[],"function_name":["hrtimer_cancel"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEUmo":{"file_name":[],"function_name":["hrtimer_try_to_cancel"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAgU79":{"file_name":[],"function_name":["__lock_text_start"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKdPM":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKyX6":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKv6U":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKs2k":{"file_name":[],"function_name":["lookup_fast"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAM56z":{"file_name":[],"function_name":["kernfs_dop_revalidate"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKmEA":{"file_name":[],"function_name":["__do_sys_newfstatat"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKloy":{"file_name":[],"function_name":["vfs_statx"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKyLF":{"file_name":[],"function_name":["filename_lookup"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKt_N":{"file_name":[],"function_name":["path_lookupat"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKtbn":{"file_name":[],"function_name":["walk_component"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKszB":{"file_name":[],"function_name":["lookup_fast"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAK8rt":{"file_name":[],"function_name":["__d_lookup_rcu"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAL1y4":{"file_name":[],"function_name":["__x64_sys_epoll_ctl"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAL1Hv":{"file_name":[],"function_name":["ep_insert"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAAEFz":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKdTc":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKycK":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKwMk":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAQs15":{"file_name":[],"function_name":["ima_file_check"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAQsaw":{"file_name":[],"function_name":["process_measurement"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAL1mI":{"file_name":[],"function_name":["__x64_sys_epoll_ctl"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAL0z0":{"file_name":[],"function_name":["ep_insert"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAALzie":{"file_name":[],"function_name":["ep_item_poll.isra.15"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAM8kx":{"file_name":[],"function_name":["kernfs_fop_poll"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAM8jG":{"file_name":[],"function_name":["kernfs_generic_poll"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAALy2X":{"file_name":[],"function_name":["ep_ptable_queue_proc"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAJwfO":{"file_name":[],"function_name":["kmem_cache_alloc"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKMTg":{"file_name":[],"function_name":["memcg_kmem_get_cache"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKcUM":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKxcK":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKu8M":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKsyL":{"file_name":[],"function_name":["link_path_walk.part.33"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKsbn":{"file_name":[],"function_name":["walk_component"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKrzB":{"file_name":[],"function_name":["lookup_fast"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAK7ri":{"file_name":[],"function_name":["__d_lookup_rcu"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM":{"file_name":[],"function_name":["inet_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYx4":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcpJS":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYyw":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ1Bt":{"file_name":[],"function_name":["__kfree_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ1vq":{"file_name":[],"function_name":["skb_release_data"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAJIu7":{"file_name":[],"function_name":["free_unref_page"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAoApO":{"file_name":[],"function_name":["ret_from_intr"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAoBzi":{"file_name":[],"function_name":["do_IRQ"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAACPD_":{"file_name":[],"function_name":["irq_exit"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAwADc":{"file_name":[],"function_name":["__softirqentry_text_start"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAaAW5":{"file_name":[],"function_name":["net_rx_action"],"function_offset":[],"line_number":[]},"_3bHXKBtA1BrvZVdhZK3vgAAAAAAAFLA":{"file_name":[],"function_name":["ena_io_poll"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAaAC8":{"file_name":[],"function_name":["napi_complete_done"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZ_KI":{"file_name":[],"function_name":["gro_normal_list.part.131"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZ_Bj":{"file_name":[],"function_name":["netif_receive_skb_list_internal"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZ-52":{"file_name":[],"function_name":["__netif_receive_skb_list_core"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAb3Zz":{"file_name":[],"function_name":["ip_list_rcv"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAb2vo":{"file_name":[],"function_name":["ip_rcv_core.isra.17"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYZj":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7wq":{"file_name":[],"function_name":["skb_copy_datagram_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZ7pm":{"file_name":[],"function_name":["__skb_datagram_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAASkI9":{"file_name":[],"function_name":["_copy_to_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAASheR":{"file_name":[],"function_name":["copyout"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAgUyr":{"file_name":[],"function_name":["copy_user_generic_string"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcpF4":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcNCx":{"file_name":[],"function_name":["__ip_queue_xmit"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcNYI":{"file_name":[],"function_name":["ip_output"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcK1g":{"file_name":[],"function_name":["ip_finish_output2"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaNFD":{"file_name":[],"function_name":["__dev_queue_xmit"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaMev":{"file_name":[],"function_name":["dev_hard_start_xmit"],"function_offset":[],"line_number":[]},"6miIyyucTZf5zXHCk7PT1gAAAAAAAApT":{"file_name":[],"function_name":["veth_xmit"],"function_offset":[],"line_number":[]},"eOfhJQFIxbIEScd007tROwAAAAAAAHRK":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/nptl/pthread_create.c"],"function_name":["start_thread"],"function_offset":[],"line_number":[465]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFaMO":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/x509_d2.c"],"function_name":["X509_STORE_load_locations"],"function_offset":[],"line_number":[94]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFjo2":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/by_file.c"],"function_name":["by_file_ctrl"],"function_offset":[],"line_number":[117]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFjjD":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/x509/by_file.c"],"function_name":["X509_load_cert_crl_file"],"function_offset":[],"line_number":[261]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFUK9":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/pem/pem_info.c"],"function_name":["PEM_X509_INFO_read_bio"],"function_offset":[],"line_number":[248]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJOq":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["ASN1_item_d2i"],"function_offset":[],"line_number":[154]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJNU":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["ASN1_item_ex_d2i"],"function_offset":[],"line_number":[553]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFIjN":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_item_ex_d2i"],"function_offset":[],"line_number":[478]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFJHc":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_template_ex_d2i"],"function_offset":[],"line_number":[623]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFI_m":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_template_noexp_d2i"],"function_offset":[],"line_number":[735]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFImi":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_item_ex_d2i"],"function_offset":[],"line_number":[261]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFH42":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_d2i_ex_primitive"],"function_offset":[],"line_number":[874]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFHtb":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_dec.c"],"function_name":["asn1_ex_c2i"],"function_offset":[],"line_number":[903]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFF8W":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_new.c"],"function_name":["ASN1_item_new"],"function_offset":[],"line_number":[76]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFF1M":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_new.c"],"function_name":["asn1_item_ex_combine_new"],"function_offset":[],"line_number":[136]},"huWyXZbCBWCe2ZtK9BiokQAAAAAAFFvE":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/asn1/tasn_new.c"],"function_name":["ASN1_primitive_new"],"function_offset":[],"line_number":[342]},"huWyXZbCBWCe2ZtK9BiokQAAAAAABuZn":{"file_name":["/usr/src/debug/openssl-1.0.2k/crypto/mem.c"],"function_name":["CRYPTO_malloc"],"function_offset":[],"line_number":[346]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB-en":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/malloc/malloc.c"],"function_name":["__GI___libc_malloc"],"function_offset":[],"line_number":[3068]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAB8gG":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/malloc/malloc.c"],"function_name":["_int_malloc"],"function_offset":[],"line_number":[3584]}},"executables":{"Ij7mO1SCteAnvtNe95RpEg":"linux-vdso.so.1","B56YkhsK1JwqD-8F8sjS3A":"prometheus","QvG8QEGAld88D676NL_Y2Q":"filebeat","FWZ9q3TQKZZok58ua1HDsg":"pf-debug-metadata-service","67s2TwiMngM0yin5Y8pvEg":"containerd","-1kQFVGzdQWpzLSZ9TRmnw":"kube-state-metrics","v6HIzNa4K6G4nRP9032RIA":"dockerd","LvhLWomlc0dSPYzQ8C620g":"controller","B8JRxL079xbhqQBqGvksAg":"kubelet","ew01Dk0sWZctP-VaEpavqQ":"vmlinux","j8DVIOTu7Btj9lgFefJ84A":"dockerd","piWSMQrh4r040D0BPNaJvw":"vmlinux","6auiCMWq5cA-hAbqSYvdQQ":"kubelet","gfRL5jyxmWedM28UI08hFQ":"snapshot-controller","1QjX8mEQC0-5qYXzadOESA":"containerd-shim-runc-v2","wfA2BgwfDNXUWsxkJ083Rw":"kubelet","A2oiHVwisByxRn5RDT4LjA":"vmlinux","MNBJ5seVz_ocW6tcr1HSmw":"metricbeat","9LzzIocepYcOjnUsLlgOjg":"vmlinux","_3bHXKBtA1BrvZVdhZK3vg":"ena","6miIyyucTZf5zXHCk7PT1g":"veth","Z_CHd3Zjsh2cWE2NSdbiNQ":"libc-2.26.so","eOfhJQFIxbIEScd007tROw":"libpthread-2.26.so","-p9BlJh9JZMPPNjY_j92ng":"awsagent","huWyXZbCBWCe2ZtK9BiokQ":"libcrypto.so.1.0.2k"},"total_frames":13116,"sampling_rate":1} diff --git a/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_86400s_125x.json b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_86400s_125x.json new file mode 100644 index 0000000000000..35bdfd7883688 --- /dev/null +++ b/packages/kbn-profiling-utils/common/__fixtures__/stacktraces_86400s_125x.json @@ -0,0 +1 @@ +{"stack_trace_events":{"clTcDPwSeibw16tpSQPVxA":38,"1sIZ88dgfmQewwimPWuaWw":80,"2gFeSnOvAhz1aSRiNEVnjQ":213,"0CNUMdOdpmKJxWeUmvWvXg":1062,"9_06LL00QkYIeiFNCWu0XQ":919,"StwAKCpFAmfI3NKtrFQDVg":494,"Jd0qjF7XxnghG2_AZCQTFA":408,"1Ez9iBhqi5bXK2tpNXVjRA":380,"2Ov4wSepfExdnFvsJSSjog":281,"DALs1IxJ3oi7BZ8FFjuM_Q":418,"VmRA1Zd-R_saxzv9stOlrw":364,"u31aX9a6CI2OuomWQHSx1Q":397,"7zatBTElj7KkoApkBS7dzw":438,"ErI-d7HGvspCKDUrR8E64A":371,"-s21TvA-EsTWbfCutQG83Q":373,"kryT_w4Id2yAnU578aXk1w":330,"AsgowTLQhiAbue_lxpHIHw":373,"hecRkAhRG62NML7wI512zA":230,"woPu0Q2DCHU5xpBNJFRNGw":179,"-t2pi-xr8qjFCfIHra96OA":203,"qbtMiMC37gp-mMp0u-WgYw":238,"ZZck2mgLZGHuLiBDFerx6w":244,"af-YU39AX7WoGwE66OjkRg":197,"DkjcsUWzUMWlzGIG7vWPLA":201,"9sZZ-MQWzCV4c64gJJBU6Q":261,"rQhVFvlTg_4aQXNpF_LGMQ":213,"-t0hOBsBrsbJ-S8NPXUTmg":175,"VoyVx3eKZvx3I7o9LV75WA":148,"SwXYsounAV_Jw1AjJobr2g":120,"Z84n0-wX6U6-iVSLGr0n7A":130,"PPkg_Kb06KioYNLVH5MUSw":114,"lMQPlrvTe5c5NiwvC7JXZg":102,"0BFlivqqa58juwW6lzxBVg":70,"cKHQmDxYocbgoxaTvYj6SA":53,"KnJHmq-Dv1WTEbftpdA5Zg":39,"2-DAEecFvG7qyB6YjY5nOg":38,"Ocoebh9gAlmO1k7rQilo0w":23,"XyR38J9TfiJQyusyqjnL0Q":12,"9s4s_y43ZAfUdYXm930H4A":9,"LeV2oAqU4BVeWoabuoh-cw":10,"2gcYNFzbFyKxWn73M5202w":12,"CU-T9AvnxmWd1TTRjgV01Q":27,"nnsc9UkL_oA5SAi5cs_ZPg":9,"wAujHiFN47_oNUI63d6EtA":15,"ia-QZTf1AEqK7KEggAUJSw":12,"YxsKA4n0U7pKfHmrePpfjA":2,"mqliNf10_gB69yQo7_zlzg":9,"24tLFB3hY9xz1zbZCjaBXA":1,"MLSOPRH6z6HuctKh5rsAnA":4,"krdohOL0KiVMtm4q-6fmjg":2,"FtHYpmBv9BwyjtHQeYFcCw":2,"FuFG7sSEAg94nZpDT4nzlA":3,"chida0TNeXOPGVvI0kALCQ":4,"UDWRHwtQcuK3KYw4Lj118w":3,"wQhKHV5i9LyZbGr1o38TMA":1,"TtsX1UxF45-CxViHFwbKJw":1,"iu7dYG1YyobzAXC7AJADOw":1,"WmwSnxyphedkasVyGbhNdg":2,"YWZby9VC56JtR6BAaYHEoA":1,"Hi8HEHDniMkBvPgm-_IXdg":2,"X86DUuQ7tHAxGBaWu4tZLg":3,"Tx8lhCcOjrVLOl1hWK6aBw":1,"oKVObqTWF9QIjxgKf8UkTw":3,"rsb7cL4OAenBHrp0F_Wcgg":2,"mWVVBnqMHfG9pWtaZUm47Q":1,"r1nqJ9JqsZyOKqlpBmuvLg":1,"5MDEZjYH98Woy4iHbcvgDg":1,"WYRZ4mSdJHjsW8s2yoKnfA":1,"C4ItszXjQjtRADEg560AUw":6,"8IBqDIuSolkkEHIjO_CfMw":5,"T2hqeT_yirkauwcO1cGJEw":4,"OIXgOJgQPE-F5rS7DPPzZA":2,"i0e78nPZCZ2CbzzLMEOcMw":4,"34DMF2kw8Djh_MjcdchMzw":6,"XG9tjujXJl2nWpbHppoRMA":6,"SrSwvDbs2pmPg3SRfXJBCA":8,"bcNRMcXtTRgNPl4vy6M5KQ":8,"XmiUdMqa5OViUnHQ_LS4Uw":3,"3odHGojcaqq4ImPnmLLSzw":6,"bRKRM4i4-XY2LCfN18mOow":8,"W936jUeelyxTrQQ2V9mn-w":3,"AlH3zgnqwh5sdMMzX8AXxg":3,"YHwQa4NMDpWa9cokfF0xqw":1,"AlRn0MJA_RCD0pN2OpIRZA":4,"inhNt-Ftru1dLAPaXB98Gw":2,"qaaAfLAUIerA8yhApFJRYQ":2,"cj3H8UtNXHeFFvSKCpbt_Q":1,"XT5dbBR70HCMmAkhladaCQ":1,"Kfnso_5TQwyEGb1cfr-n5A":1,"O3_UY4IxBGbcnXlHSqWz_w":2},"stack_traces":{"clTcDPwSeibw16tpSQPVxA":{"address_or_lines":[4646313],"file_ids":["FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARuWp"],"type_ids":[3]},"1sIZ88dgfmQewwimPWuaWw":{"address_or_lines":[4660883,2469],"file_ids":["B8JRxL079xbhqQBqGvksAg","edNJ10OjHiWc5nzuTQdvig"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARx6T","edNJ10OjHiWc5nzuTQdvigAAAAAAAAml"],"type_ids":[3,3]},"2gFeSnOvAhz1aSRiNEVnjQ":{"address_or_lines":[10486356,710610,1071113],"file_ids":["piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["piWSMQrh4r040D0BPNaJvwAAAAAAoAJU","piWSMQrh4r040D0BPNaJvwAAAAAACtfS","piWSMQrh4r040D0BPNaJvwAAAAAAEFgJ"],"type_ids":[4,4,4]},"0CNUMdOdpmKJxWeUmvWvXg":{"address_or_lines":[32434917,32101228,32115955,32118104],"file_ids":["QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q"],"frame_ids":["QvG8QEGAld88D676NL_Y2QAAAAAB7url","QvG8QEGAld88D676NL_Y2QAAAAAB6dNs","QvG8QEGAld88D676NL_Y2QAAAAAB6gzz","QvG8QEGAld88D676NL_Y2QAAAAAB6hVY"],"type_ids":[3,3,3,3]},"9_06LL00QkYIeiFNCWu0XQ":{"address_or_lines":[4643592,4325284,4339923,4341903,4293837],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARtsI","B8JRxL079xbhqQBqGvksAgAAAAAAQf-k","B8JRxL079xbhqQBqGvksAgAAAAAAQjjT","B8JRxL079xbhqQBqGvksAgAAAAAAQkCP","B8JRxL079xbhqQBqGvksAgAAAAAAQYTN"],"type_ids":[3,3,3,3,3]},"StwAKCpFAmfI3NKtrFQDVg":{"address_or_lines":[4646312,4600750,4594821,4561903,4559144,4562383],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARuWo","FWZ9q3TQKZZok58ua1HDsgAAAAAARjOu","FWZ9q3TQKZZok58ua1HDsgAAAAAARhyF","FWZ9q3TQKZZok58ua1HDsgAAAAAARZvv","FWZ9q3TQKZZok58ua1HDsgAAAAAARZEo","FWZ9q3TQKZZok58ua1HDsgAAAAAARZ3P"],"type_ids":[3,3,3,3,3,3]},"Jd0qjF7XxnghG2_AZCQTFA":{"address_or_lines":[43723813,43390308,43405438,43397462,43398148,43406419,43408369],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACmywl","MNBJ5seVz_ocW6tcr1HSmwAAAAAClhVk","MNBJ5seVz_ocW6tcr1HSmwAAAAACllB-","MNBJ5seVz_ocW6tcr1HSmwAAAAACljFW","MNBJ5seVz_ocW6tcr1HSmwAAAAACljQE","MNBJ5seVz_ocW6tcr1HSmwAAAAACllRT","MNBJ5seVz_ocW6tcr1HSmwAAAAACllvx"],"type_ids":[3,3,3,3,3,3,3]},"1Ez9iBhqi5bXK2tpNXVjRA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271933,15288920,9572292,9497568],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6Qf9","FWZ9q3TQKZZok58ua1HDsgAAAAAA6UpY","FWZ9q3TQKZZok58ua1HDsgAAAAAAkg_E","FWZ9q3TQKZZok58ua1HDsgAAAAAAkOvg"],"type_ids":[3,3,3,3,3,3,3,3]},"2Ov4wSepfExdnFvsJSSjog":{"address_or_lines":[4654944,15291206,14341928,15275435,15271933,15288920,9572292,9504548,5043327],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6Qf9","FWZ9q3TQKZZok58ua1HDsgAAAAAA6UpY","FWZ9q3TQKZZok58ua1HDsgAAAAAAkg_E","FWZ9q3TQKZZok58ua1HDsgAAAAAAkQck","FWZ9q3TQKZZok58ua1HDsgAAAAAATPR_"],"type_ids":[3,3,3,3,3,3,3,3,3]},"DALs1IxJ3oi7BZ8FFjuM_Q":{"address_or_lines":[4654944,15291206,14341928,15275435,15271933,15288920,9572292,9504218,4890989,4889187],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6Qf9","FWZ9q3TQKZZok58ua1HDsgAAAAAA6UpY","FWZ9q3TQKZZok58ua1HDsgAAAAAAkg_E","FWZ9q3TQKZZok58ua1HDsgAAAAAAkQXa","FWZ9q3TQKZZok58ua1HDsgAAAAAASqFt","FWZ9q3TQKZZok58ua1HDsgAAAAAASppj"],"type_ids":[3,3,3,3,3,3,3,3,3,3]},"VmRA1Zd-R_saxzv9stOlrw":{"address_or_lines":[4650848,9850853,9880398,9883181,9807044,9827268,9781937,9782483,9784009,9784300,9829781],"file_ids":["QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg","QaIvzvU8UoclQMd_OMt-Pg"],"frame_ids":["QaIvzvU8UoclQMd_OMt-PgAAAAAARvdg","QaIvzvU8UoclQMd_OMt-PgAAAAAAlk_l","QaIvzvU8UoclQMd_OMt-PgAAAAAAlsNO","QaIvzvU8UoclQMd_OMt-PgAAAAAAls4t","QaIvzvU8UoclQMd_OMt-PgAAAAAAlaTE","QaIvzvU8UoclQMd_OMt-PgAAAAAAlfPE","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUKx","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUTT","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUrJ","QaIvzvU8UoclQMd_OMt-PgAAAAAAlUvs","QaIvzvU8UoclQMd_OMt-PgAAAAAAlf2V"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3]},"u31aX9a6CI2OuomWQHSx1Q":{"address_or_lines":[4652224,22357367,22385134,22366798,57080079,58879477,58676957,58636100,58650141,31265796,7372663,7364083],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZvkP","B8JRxL079xbhqQBqGvksAgAAAAADgm31","B8JRxL079xbhqQBqGvksAgAAAAADf1bd","B8JRxL079xbhqQBqGvksAgAAAAADfrdE","B8JRxL079xbhqQBqGvksAgAAAAADfu4d","B8JRxL079xbhqQBqGvksAgAAAAAB3RQE","B8JRxL079xbhqQBqGvksAgAAAAAAcH93","B8JRxL079xbhqQBqGvksAgAAAAAAcF3z"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3]},"7zatBTElj7KkoApkBS7dzw":{"address_or_lines":[32443680,58256816,58381230,58319266,58327970,58359946,58318775,58321276,58323254,58419093,58425670,32747421,32699470],"file_ids":["QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q","QvG8QEGAld88D676NL_Y2Q"],"frame_ids":["QvG8QEGAld88D676NL_Y2QAAAAAB7w0g","QvG8QEGAld88D676NL_Y2QAAAAADeO2w","QvG8QEGAld88D676NL_Y2QAAAAADetOu","QvG8QEGAld88D676NL_Y2QAAAAADeeGi","QvG8QEGAld88D676NL_Y2QAAAAADegOi","QvG8QEGAld88D676NL_Y2QAAAAADeoCK","QvG8QEGAld88D676NL_Y2QAAAAADed-3","QvG8QEGAld88D676NL_Y2QAAAAADeel8","QvG8QEGAld88D676NL_Y2QAAAAADefE2","QvG8QEGAld88D676NL_Y2QAAAAADe2eV","QvG8QEGAld88D676NL_Y2QAAAAADe4FG","QvG8QEGAld88D676NL_Y2QAAAAAB86-d","QvG8QEGAld88D676NL_Y2QAAAAAB8vRO"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3]},"ErI-d7HGvspCKDUrR8E64A":{"address_or_lines":[152249,135481,144741,190122,831754,827742,928935,925466,103752,102294,100426,61069,75059,73332],"file_ids":["w5zBqPf1_9mIVEf-Rn7EdA","Z_CHd3Zjsh2cWE2NSdbiNQ","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","w5zBqPf1_9mIVEf-Rn7EdA","OTWX4UsOVMrSIF5cD4zUzg","OTWX4UsOVMrSIF5cD4zUzg","OTWX4UsOVMrSIF5cD4zUzg","OTWX4UsOVMrSIF5cD4zUzg","OTWX4UsOVMrSIF5cD4zUzg","OTWX4UsOVMrSIF5cD4zUzg"],"frame_ids":["w5zBqPf1_9mIVEf-Rn7EdAAAAAAAAlK5","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","w5zBqPf1_9mIVEf-Rn7EdAAAAAAAAjVl","w5zBqPf1_9mIVEf-Rn7EdAAAAAAAAuaq","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADLEK","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADKFe","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADiyn","w5zBqPf1_9mIVEf-Rn7EdAAAAAAADh8a","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAZVI","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAY-W","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAYhK","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAO6N","OTWX4UsOVMrSIF5cD4zUzgAAAAAAASUz","OTWX4UsOVMrSIF5cD4zUzgAAAAAAAR50"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"-s21TvA-EsTWbfCutQG83Q":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10733159,10733818,10618404,10387225,4547736,4658752],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Zn","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8j6","FWZ9q3TQKZZok58ua1HDsgAAAAAAogYk","FWZ9q3TQKZZok58ua1HDsgAAAAAAnn8Z","FWZ9q3TQKZZok58ua1HDsgAAAAAARWSY","FWZ9q3TQKZZok58ua1HDsgAAAAAARxZA"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"kryT_w4Id2yAnU578aXk1w":{"address_or_lines":[4652224,22357367,22385134,22366798,57089650,58932906,58679635,58644118,58665750,31406998,7372944,7295421,7297188,7304836,7297245,5131680],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZx5y","B8JRxL079xbhqQBqGvksAgAAAAADgz6q","B8JRxL079xbhqQBqGvksAgAAAAADf2FT","B8JRxL079xbhqQBqGvksAgAAAAADftaW","B8JRxL079xbhqQBqGvksAgAAAAADfysW","B8JRxL079xbhqQBqGvksAgAAAAAB3zuW","B8JRxL079xbhqQBqGvksAgAAAAAAcICQ","B8JRxL079xbhqQBqGvksAgAAAAAAb1G9","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1jd","B8JRxL079xbhqQBqGvksAgAAAAAATk2g"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"AsgowTLQhiAbue_lxpHIHw":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41460538,41453510,39934947,37247976,34247181,33672088,18131287],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeKM6","v6HIzNa4K6G4nRP9032RIAAAAAACeIfG","v6HIzNa4K6G4nRP9032RIAAAAAACYVvj","v6HIzNa4K6G4nRP9032RIAAAAAACOFvo","v6HIzNa4K6G4nRP9032RIAAAAAACCpIN","v6HIzNa4K6G4nRP9032RIAAAAAACAcuY","v6HIzNa4K6G4nRP9032RIAAAAAABFKlX"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"hecRkAhRG62NML7wI512zA":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000356,39998369,27959205,27961373,27940684],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYltk","v6HIzNa4K6G4nRP9032RIAAAAAACYlOh","v6HIzNa4K6G4nRP9032RIAAAAAABqp-l","v6HIzNa4K6G4nRP9032RIAAAAAABqqgd","v6HIzNa4K6G4nRP9032RIAAAAAABqldM"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"woPu0Q2DCHU5xpBNJFRNGw":{"address_or_lines":[43732576,54345578,54346325,54347573,52524033,52636324,52637912,52417621,52420674,52436132,51874398,51910204,51902690,51903112,51905980,51885853,51874212,51875084,44164621],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAADPT9q","MNBJ5seVz_ocW6tcr1HSmwAAAAADPUJV","MNBJ5seVz_ocW6tcr1HSmwAAAAADPUc1","MNBJ5seVz_ocW6tcr1HSmwAAAAADIXQB","MNBJ5seVz_ocW6tcr1HSmwAAAAADIyqk","MNBJ5seVz_ocW6tcr1HSmwAAAAADIzDY","MNBJ5seVz_ocW6tcr1HSmwAAAAADH9RV","MNBJ5seVz_ocW6tcr1HSmwAAAAADH-BC","MNBJ5seVz_ocW6tcr1HSmwAAAAADIByk","MNBJ5seVz_ocW6tcr1HSmwAAAAADF4pe","MNBJ5seVz_ocW6tcr1HSmwAAAAADGBY8","MNBJ5seVz_ocW6tcr1HSmwAAAAADF_ji","MNBJ5seVz_ocW6tcr1HSmwAAAAADF_qI","MNBJ5seVz_ocW6tcr1HSmwAAAAADGAW8","MNBJ5seVz_ocW6tcr1HSmwAAAAADF7cd","MNBJ5seVz_ocW6tcr1HSmwAAAAADF4mk","MNBJ5seVz_ocW6tcr1HSmwAAAAADF40M","MNBJ5seVz_ocW6tcr1HSmwAAAAACoeYN"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"-t2pi-xr8qjFCfIHra96OA":{"address_or_lines":[4620832,23557195,23527051,9749435,9749637,9750553,9750935,9746779,9746522,23527477,23529910,23522407,10849724,10839125,10834845,10836246,10842317,4508401,4247613,4282212],"file_ids":["hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg","hc6JHMKlLXjOZcU9MGxvfg"],"frame_ids":["hc6JHMKlLXjOZcU9MGxvfgAAAAAARoIg","hc6JHMKlLXjOZcU9MGxvfgAAAAABZ3RL","hc6JHMKlLXjOZcU9MGxvfgAAAAABZv6L","hc6JHMKlLXjOZcU9MGxvfgAAAAAAlMO7","hc6JHMKlLXjOZcU9MGxvfgAAAAAAlMSF","hc6JHMKlLXjOZcU9MGxvfgAAAAAAlMgZ","hc6JHMKlLXjOZcU9MGxvfgAAAAAAlMmX","hc6JHMKlLXjOZcU9MGxvfgAAAAAAlLlb","hc6JHMKlLXjOZcU9MGxvfgAAAAAAlLha","hc6JHMKlLXjOZcU9MGxvfgAAAAABZwA1","hc6JHMKlLXjOZcU9MGxvfgAAAAABZwm2","hc6JHMKlLXjOZcU9MGxvfgAAAAABZuxn","hc6JHMKlLXjOZcU9MGxvfgAAAAAApY28","hc6JHMKlLXjOZcU9MGxvfgAAAAAApWRV","hc6JHMKlLXjOZcU9MGxvfgAAAAAApVOd","hc6JHMKlLXjOZcU9MGxvfgAAAAAApVkW","hc6JHMKlLXjOZcU9MGxvfgAAAAAApXDN","hc6JHMKlLXjOZcU9MGxvfgAAAAAARMrx","hc6JHMKlLXjOZcU9MGxvfgAAAAAAQNA9","hc6JHMKlLXjOZcU9MGxvfgAAAAAAQVdk"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"qbtMiMC37gp-mMp0u-WgYw":{"address_or_lines":[4652224,22357367,22385134,22366798,57076399,58917522,58676957,58636100,58650141,31265796,7372944,7295421,7297245,7300762,7297188,7304836,7297188,7305194,5143289,5150220,5146267],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZuqv","B8JRxL079xbhqQBqGvksAgAAAAADgwKS","B8JRxL079xbhqQBqGvksAgAAAAADf1bd","B8JRxL079xbhqQBqGvksAgAAAAADfrdE","B8JRxL079xbhqQBqGvksAgAAAAADfu4d","B8JRxL079xbhqQBqGvksAgAAAAAB3RQE","B8JRxL079xbhqQBqGvksAgAAAAAAcICQ","B8JRxL079xbhqQBqGvksAgAAAAAAb1G9","B8JRxL079xbhqQBqGvksAgAAAAAAb1jd","B8JRxL079xbhqQBqGvksAgAAAAAAb2aa","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3fq","B8JRxL079xbhqQBqGvksAgAAAAAATnr5","B8JRxL079xbhqQBqGvksAgAAAAAATpYM","B8JRxL079xbhqQBqGvksAgAAAAAAToab"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"ZZck2mgLZGHuLiBDFerx6w":{"address_or_lines":[4652224,22357367,22385134,22366798,57076399,58917522,58676957,58636100,58650141,31265796,7372944,7295421,7297245,7300762,7297188,7304836,7297188,7304836,7297188,7304836,7297188,7303473],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVSV3","B8JRxL079xbhqQBqGvksAgAAAAABVZHu","B8JRxL079xbhqQBqGvksAgAAAAABVUpO","B8JRxL079xbhqQBqGvksAgAAAAADZuqv","B8JRxL079xbhqQBqGvksAgAAAAADgwKS","B8JRxL079xbhqQBqGvksAgAAAAADf1bd","B8JRxL079xbhqQBqGvksAgAAAAADfrdE","B8JRxL079xbhqQBqGvksAgAAAAADfu4d","B8JRxL079xbhqQBqGvksAgAAAAAB3RQE","B8JRxL079xbhqQBqGvksAgAAAAAAcICQ","B8JRxL079xbhqQBqGvksAgAAAAAAb1G9","B8JRxL079xbhqQBqGvksAgAAAAAAb1jd","B8JRxL079xbhqQBqGvksAgAAAAAAb2aa","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3aE","B8JRxL079xbhqQBqGvksAgAAAAAAb1ik","B8JRxL079xbhqQBqGvksAgAAAAAAb3Ex"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"af-YU39AX7WoGwE66OjkRg":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000356,39998369,27959205,27961306,27960060,27907285,27885784,27888182,18793031,27888361],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYltk","v6HIzNa4K6G4nRP9032RIAAAAAACYlOh","v6HIzNa4K6G4nRP9032RIAAAAAABqp-l","v6HIzNa4K6G4nRP9032RIAAAAAABqqfa","v6HIzNa4K6G4nRP9032RIAAAAAABqqL8","v6HIzNa4K6G4nRP9032RIAAAAAABqdTV","v6HIzNa4K6G4nRP9032RIAAAAAABqYDY","v6HIzNa4K6G4nRP9032RIAAAAAABqYo2","v6HIzNa4K6G4nRP9032RIAAAAAABHsJH","v6HIzNa4K6G4nRP9032RIAAAAAABqYrp"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"DkjcsUWzUMWlzGIG7vWPLA":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54556506,44024036,44026008,44007166,43828228,43837959,43282962,43282989,10485923,16807,2845749,2845580,2841596,3335577,3325166,699747],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHda","MNBJ5seVz_ocW6tcr1HSmwAAAAACn8Dk","MNBJ5seVz_ocW6tcr1HSmwAAAAACn8iY","MNBJ5seVz_ocW6tcr1HSmwAAAAACn37-","MNBJ5seVz_ocW6tcr1HSmwAAAAACnMQE","MNBJ5seVz_ocW6tcr1HSmwAAAAACnOoH","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIS","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIt","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAK2w1","A2oiHVwisByxRn5RDT4LjAAAAAAAK2uM","A2oiHVwisByxRn5RDT4LjAAAAAAAK1v8","A2oiHVwisByxRn5RDT4LjAAAAAAAMuWZ","A2oiHVwisByxRn5RDT4LjAAAAAAAMrzu","A2oiHVwisByxRn5RDT4LjAAAAAAACq1j"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"9sZZ-MQWzCV4c64gJJBU6Q":{"address_or_lines":[4652224,59362286,59048854,59078134,59085018,59179681,31752932,6709540,4933796,4937114,4970099,4971610,4754617,4757981,4219698,4219725,10485923,16807,2777072,2775330,2826677,2809572,2808699,2807483,2863936],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAADicvu","B8JRxL079xbhqQBqGvksAgAAAAADhQOW","B8JRxL079xbhqQBqGvksAgAAAAADhXX2","B8JRxL079xbhqQBqGvksAgAAAAADhZDa","B8JRxL079xbhqQBqGvksAgAAAAADhwKh","B8JRxL079xbhqQBqGvksAgAAAAAB5ILk","B8JRxL079xbhqQBqGvksAgAAAAAAZmEk","B8JRxL079xbhqQBqGvksAgAAAAAAS0ik","B8JRxL079xbhqQBqGvksAgAAAAAAS1Wa","B8JRxL079xbhqQBqGvksAgAAAAAAS9Zz","B8JRxL079xbhqQBqGvksAgAAAAAAS9xa","B8JRxL079xbhqQBqGvksAgAAAAAASIy5","B8JRxL079xbhqQBqGvksAgAAAAAASJnd","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKl_w","A2oiHVwisByxRn5RDT4LjAAAAAAAKlki","A2oiHVwisByxRn5RDT4LjAAAAAAAKyG1","A2oiHVwisByxRn5RDT4LjAAAAAAAKt7k","A2oiHVwisByxRn5RDT4LjAAAAAAAKtt7","A2oiHVwisByxRn5RDT4LjAAAAAAAKta7","A2oiHVwisByxRn5RDT4LjAAAAAAAK7NA"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"rQhVFvlTg_4aQXNpF_LGMQ":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41428732,20150746,19897796,19899069,19901252,19906953,20160590,19897796,19899069,19901252,19910358,18737412,18488391,18154825,18129756],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCb8","v6HIzNa4K6G4nRP9032RIAAAAAABM3na","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8GJ","v6HIzNa4K6G4nRP9032RIAAAAAABM6BO","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL87W","v6HIzNa4K6G4nRP9032RIAAAAAABHekE","v6HIzNa4K6G4nRP9032RIAAAAAABGhxH","v6HIzNa4K6G4nRP9032RIAAAAAABFQVJ","v6HIzNa4K6G4nRP9032RIAAAAAABFKNc"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"-t0hOBsBrsbJ-S8NPXUTmg":{"address_or_lines":[4652224,22033901,21942103,21951046,9844260,9839268,22072132,22072395,5590500,5508424,4907789,4749540,4757831,4219698,4219725,10485923,16807,2756576,2755820,2745050,6715782,6715626,7926696,6795731,4869416,4855393,8472925],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABUDXt","B8JRxL079xbhqQBqGvksAgAAAAABTs9X","B8JRxL079xbhqQBqGvksAgAAAAABTvJG","B8JRxL079xbhqQBqGvksAgAAAAAAljYk","B8JRxL079xbhqQBqGvksAgAAAAAAliKk","B8JRxL079xbhqQBqGvksAgAAAAABUMtE","B8JRxL079xbhqQBqGvksAgAAAAABUMxL","B8JRxL079xbhqQBqGvksAgAAAAAAVU3k","B8JRxL079xbhqQBqGvksAgAAAAAAVA1I","B8JRxL079xbhqQBqGvksAgAAAAAASuMN","B8JRxL079xbhqQBqGvksAgAAAAAASHjk","B8JRxL079xbhqQBqGvksAgAAAAAASJlH","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg_g","A2oiHVwisByxRn5RDT4LjAAAAAAAKgzs","A2oiHVwisByxRn5RDT4LjAAAAAAAKeLa","A2oiHVwisByxRn5RDT4LjAAAAAAAZnmG","A2oiHVwisByxRn5RDT4LjAAAAAAAZnjq","A2oiHVwisByxRn5RDT4LjAAAAAAAePOo","A2oiHVwisByxRn5RDT4LjAAAAAAAZ7HT","A2oiHVwisByxRn5RDT4LjAAAAAAASk0o","A2oiHVwisByxRn5RDT4LjAAAAAAAShZh","A2oiHVwisByxRn5RDT4LjAAAAAAAgUld"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4]},"VoyVx3eKZvx3I7o9LV75WA":{"address_or_lines":[4652224,22354373,22356417,22043891,9840916,9838765,4872825,5688954,5590020,5506248,4899556,4748900,4757831,4219698,4219725,10485923,16807,2756288,2755416,2744627,6715329,7926130,7924288,7914841,6798266,6797590,6797444,2726038],"file_ids":["B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","B8JRxL079xbhqQBqGvksAg","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["B8JRxL079xbhqQBqGvksAgAAAAAARvzA","B8JRxL079xbhqQBqGvksAgAAAAABVRnF","B8JRxL079xbhqQBqGvksAgAAAAABVSHB","B8JRxL079xbhqQBqGvksAgAAAAABUFzz","B8JRxL079xbhqQBqGvksAgAAAAAAlikU","B8JRxL079xbhqQBqGvksAgAAAAAAliCt","B8JRxL079xbhqQBqGvksAgAAAAAASlp5","B8JRxL079xbhqQBqGvksAgAAAAAAVs56","B8JRxL079xbhqQBqGvksAgAAAAAAVUwE","B8JRxL079xbhqQBqGvksAgAAAAAAVATI","B8JRxL079xbhqQBqGvksAgAAAAAASsLk","B8JRxL079xbhqQBqGvksAgAAAAAASHZk","B8JRxL079xbhqQBqGvksAgAAAAAASJlH","B8JRxL079xbhqQBqGvksAgAAAAAAQGMy","B8JRxL079xbhqQBqGvksAgAAAAAAQGNN","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A","A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY","A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz","A2oiHVwisByxRn5RDT4LjAAAAAAAZnfB","A2oiHVwisByxRn5RDT4LjAAAAAAAePFy","A2oiHVwisByxRn5RDT4LjAAAAAAAeOpA","A2oiHVwisByxRn5RDT4LjAAAAAAAeMVZ","A2oiHVwisByxRn5RDT4LjAAAAAAAZ7u6","A2oiHVwisByxRn5RDT4LjAAAAAAAZ7kW","A2oiHVwisByxRn5RDT4LjAAAAAAAZ7iE","A2oiHVwisByxRn5RDT4LjAAAAAAAKZiW"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"SwXYsounAV_Jw1AjJobr2g":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791289,24794610,24781052,24778417,19045737,19044484,19054298,18859588,18399464,18130636],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekj5","v6HIzNa4K6G4nRP9032RIAAAAAABelXy","v6HIzNa4K6G4nRP9032RIAAAAAABeiD8","v6HIzNa4K6G4nRP9032RIAAAAAABehax","v6HIzNa4K6G4nRP9032RIAAAAAABIp1p","v6HIzNa4K6G4nRP9032RIAAAAAABIpiE","v6HIzNa4K6G4nRP9032RIAAAAAABIr7a","v6HIzNa4K6G4nRP9032RIAAAAAABH8ZE","v6HIzNa4K6G4nRP9032RIAAAAAABGMDo","v6HIzNa4K6G4nRP9032RIAAAAAABFKbM"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"Z84n0-wX6U6-iVSLGr0n7A":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791213,24785269,19897796,19899069,19901252,19908516,19901309,19904677,19901252,19907213,19923168],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekit","v6HIzNa4K6G4nRP9032RIAAAAAABejF1","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6t9","v6HIzNa4K6G4nRP9032RIAAAAAABL7il","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8KN","v6HIzNa4K6G4nRP9032RIAAAAAABMADg"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"PPkg_Kb06KioYNLVH5MUSw":{"address_or_lines":[43732576,69269321,69269937,69272583,69273587,69274533,75195556,54542596,54557252,54545733,54547559,54558277,54570436,44043866,44037437,43989636,43829252,43837959,43282962,43282989,10485923,16807,2756288,2755416,2924231,3319181,3316454,2921821,2921711,8455053,8481479],"file_ids":["MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","MNBJ5seVz_ocW6tcr1HSmw","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["MNBJ5seVz_ocW6tcr1HSmwAAAAACm05g","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPdJ","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIPmx","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQQH","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQfz","MNBJ5seVz_ocW6tcr1HSmwAAAAAEIQul","MNBJ5seVz_ocW6tcr1HSmwAAAAAEe2Sk","MNBJ5seVz_ocW6tcr1HSmwAAAAADQEEE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQHpE","MNBJ5seVz_ocW6tcr1HSmwAAAAADQE1F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQFRn","MNBJ5seVz_ocW6tcr1HSmwAAAAADQH5F","MNBJ5seVz_ocW6tcr1HSmwAAAAADQK3E","MNBJ5seVz_ocW6tcr1HSmwAAAAACoA5a","MNBJ5seVz_ocW6tcr1HSmwAAAAACn_U9","MNBJ5seVz_ocW6tcr1HSmwAAAAACnzqE","MNBJ5seVz_ocW6tcr1HSmwAAAAACnMgE","MNBJ5seVz_ocW6tcr1HSmwAAAAACnOoH","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIS","MNBJ5seVz_ocW6tcr1HSmwAAAAAClHIt","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A","A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY","A2oiHVwisByxRn5RDT4LjAAAAAAALJ7H","A2oiHVwisByxRn5RDT4LjAAAAAAAMqWN","A2oiHVwisByxRn5RDT4LjAAAAAAAMprm","A2oiHVwisByxRn5RDT4LjAAAAAAALJVd","A2oiHVwisByxRn5RDT4LjAAAAAAALJTv","A2oiHVwisByxRn5RDT4LjAAAAAAAgQON","A2oiHVwisByxRn5RDT4LjAAAAAAAgWrH"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"lMQPlrvTe5c5NiwvC7JXZg":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429353,40304297,19976893,19927481,19928567,19983876,19943049,19984068,19944276,19984260,19945213,19982696,19937907,19983876,19943049,19984068,19944276,19982696,19937907,19935862,19142858],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeClp","v6HIzNa4K6G4nRP9032RIAAAAAACZv6p","v6HIzNa4K6G4nRP9032RIAAAAAABMNK9","v6HIzNa4K6G4nRP9032RIAAAAAABMBG5","v6HIzNa4K6G4nRP9032RIAAAAAABMBX3","v6HIzNa4K6G4nRP9032RIAAAAAABMO4E","v6HIzNa4K6G4nRP9032RIAAAAAABME6J","v6HIzNa4K6G4nRP9032RIAAAAAABMO7E","v6HIzNa4K6G4nRP9032RIAAAAAABMFNU","v6HIzNa4K6G4nRP9032RIAAAAAABMO-E","v6HIzNa4K6G4nRP9032RIAAAAAABMFb9","v6HIzNa4K6G4nRP9032RIAAAAAABMOlo","v6HIzNa4K6G4nRP9032RIAAAAAABMDpz","v6HIzNa4K6G4nRP9032RIAAAAAABMO4E","v6HIzNa4K6G4nRP9032RIAAAAAABME6J","v6HIzNa4K6G4nRP9032RIAAAAAABMO7E","v6HIzNa4K6G4nRP9032RIAAAAAABMFNU","v6HIzNa4K6G4nRP9032RIAAAAAABMOlo","v6HIzNa4K6G4nRP9032RIAAAAAABMDpz","v6HIzNa4K6G4nRP9032RIAAAAAABMDJ2","v6HIzNa4K6G4nRP9032RIAAAAAABJBjK"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"0BFlivqqa58juwW6lzxBVg":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791213,24785269,19897796,19899069,19901252,19908516,19901309,19904677,19901252,19908516,19901477,19920683,18932457,18903037],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekit","v6HIzNa4K6G4nRP9032RIAAAAAABejF1","v6HIzNa4K6G4nRP9032RIAAAAAABL53E","v6HIzNa4K6G4nRP9032RIAAAAAABL6K9","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6t9","v6HIzNa4K6G4nRP9032RIAAAAAABL7il","v6HIzNa4K6G4nRP9032RIAAAAAABL6tE","v6HIzNa4K6G4nRP9032RIAAAAAABL8ek","v6HIzNa4K6G4nRP9032RIAAAAAABL6wl","v6HIzNa4K6G4nRP9032RIAAAAAABL_cr","v6HIzNa4K6G4nRP9032RIAAAAAABIOLp","v6HIzNa4K6G4nRP9032RIAAAAAABIG_9"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"cKHQmDxYocbgoxaTvYj6SA":{"address_or_lines":[4652224,58814799,10400775,10401064,10401333,10401661,58829797,58814910,58812516,58789549,58791347,58770754,58772726,13824541,13825258,13823212,13823370,4964628,4731769,4742286,4757722,4219698,4219725,10485923,16807,2795169,2795020,2794811,2794650,2760034,2759532,2759330,2758281,2557765],"file_ids":["wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","wfA2BgwfDNXUWsxkJ083Rw","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["wfA2BgwfDNXUWsxkJ083RwAAAAAARvzA","wfA2BgwfDNXUWsxkJ083RwAAAAADgXFP","wfA2BgwfDNXUWsxkJ083RwAAAAAAnrQH","wfA2BgwfDNXUWsxkJ083RwAAAAAAnrUo","wfA2BgwfDNXUWsxkJ083RwAAAAAAnrY1","wfA2BgwfDNXUWsxkJ083RwAAAAAAnrd9","wfA2BgwfDNXUWsxkJ083RwAAAAADgavl","wfA2BgwfDNXUWsxkJ083RwAAAAADgXG-","wfA2BgwfDNXUWsxkJ083RwAAAAADgWhk","wfA2BgwfDNXUWsxkJ083RwAAAAADgQ6t","wfA2BgwfDNXUWsxkJ083RwAAAAADgRWz","wfA2BgwfDNXUWsxkJ083RwAAAAADgMVC","wfA2BgwfDNXUWsxkJ083RwAAAAADgMz2","wfA2BgwfDNXUWsxkJ083RwAAAAAA0vId","wfA2BgwfDNXUWsxkJ083RwAAAAAA0vTq","wfA2BgwfDNXUWsxkJ083RwAAAAAA0uzs","wfA2BgwfDNXUWsxkJ083RwAAAAAA0u2K","wfA2BgwfDNXUWsxkJ083RwAAAAAAS8EU","wfA2BgwfDNXUWsxkJ083RwAAAAAASDN5","wfA2BgwfDNXUWsxkJ083RwAAAAAASFyO","wfA2BgwfDNXUWsxkJ083RwAAAAAASJja","wfA2BgwfDNXUWsxkJ083RwAAAAAAQGMy","wfA2BgwfDNXUWsxkJ083RwAAAAAAQGNN","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKqah","9LzzIocepYcOjnUsLlgOjgAAAAAAKqYM","9LzzIocepYcOjnUsLlgOjgAAAAAAKqU7","9LzzIocepYcOjnUsLlgOjgAAAAAAKqSa","9LzzIocepYcOjnUsLlgOjgAAAAAAKh1i","9LzzIocepYcOjnUsLlgOjgAAAAAAKhts","9LzzIocepYcOjnUsLlgOjgAAAAAAKhqi","9LzzIocepYcOjnUsLlgOjgAAAAAAKhaJ","9LzzIocepYcOjnUsLlgOjgAAAAAAJwdF"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"KnJHmq-Dv1WTEbftpdA5Zg":{"address_or_lines":[4652224,30971941,30986245,30988292,30990568,30935955,30723428,25540326,25548591,25550478,25503568,25504356,25481468,25481277,25484807,25485060,4951332,4960314,4742003,4757981,4219698,4219725,10485923,16743,2737420,2823946,2813561,2756082,2755033,2554964,2554477,2553932,2551218,2411027,2394415],"file_ids":["-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["-pk6w5puGcp-wKnQ61BZzQAAAAAARvzA","-pk6w5puGcp-wKnQ61BZzQAAAAAB2Jgl","-pk6w5puGcp-wKnQ61BZzQAAAAAB2NAF","-pk6w5puGcp-wKnQ61BZzQAAAAAB2NgE","-pk6w5puGcp-wKnQ61BZzQAAAAAB2ODo","-pk6w5puGcp-wKnQ61BZzQAAAAAB2AuT","-pk6w5puGcp-wKnQ61BZzQAAAAAB1M1k","-pk6w5puGcp-wKnQ61BZzQAAAAABhbbm","-pk6w5puGcp-wKnQ61BZzQAAAAABhdcv","-pk6w5puGcp-wKnQ61BZzQAAAAABhd6O","-pk6w5puGcp-wKnQ61BZzQAAAAABhSdQ","-pk6w5puGcp-wKnQ61BZzQAAAAABhSpk","-pk6w5puGcp-wKnQ61BZzQAAAAABhND8","-pk6w5puGcp-wKnQ61BZzQAAAAABhNA9","-pk6w5puGcp-wKnQ61BZzQAAAAABhN4H","-pk6w5puGcp-wKnQ61BZzQAAAAABhN8E","-pk6w5puGcp-wKnQ61BZzQAAAAAAS40k","-pk6w5puGcp-wKnQ61BZzQAAAAAAS7A6","-pk6w5puGcp-wKnQ61BZzQAAAAAASFtz","-pk6w5puGcp-wKnQ61BZzQAAAAAASJnd","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGMy","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGNN","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKcUM","piWSMQrh4r040D0BPNaJvwAAAAAAKxcK","piWSMQrh4r040D0BPNaJvwAAAAAAKu55","piWSMQrh4r040D0BPNaJvwAAAAAAKg3y","piWSMQrh4r040D0BPNaJvwAAAAAAKgnZ","piWSMQrh4r040D0BPNaJvwAAAAAAJvxU","piWSMQrh4r040D0BPNaJvwAAAAAAJvpt","piWSMQrh4r040D0BPNaJvwAAAAAAJvhM","piWSMQrh4r040D0BPNaJvwAAAAAAJu2y","piWSMQrh4r040D0BPNaJvwAAAAAAJMoT","piWSMQrh4r040D0BPNaJvwAAAAAAJIkv"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"2-DAEecFvG7qyB6YjY5nOg":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755650,4215846],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxC","ew01Dk0sWZctP-VaEpavqQAAAAAAQFQm"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4]},"Ocoebh9gAlmO1k7rQilo0w":{"address_or_lines":[18515232,22597677,22574774,22595066,32287086,22580238,40442809,34294056,40314966,40312922,41455610,41429291,39997332,40000583,40001059,40220526,40011884,32784080,32870382,24791191,24778097,24778417,19046138,19039453,18993092,18869484,18879802,10485923,16807,2756560,2755688,2744899,3827767,3827522,2050302,4868077,4855663],"file_ids":["v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","v6HIzNa4K6G4nRP9032RIA","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["v6HIzNa4K6G4nRP9032RIAAAAAABGoUg","v6HIzNa4K6G4nRP9032RIAAAAAABWNAt","v6HIzNa4K6G4nRP9032RIAAAAAABWHa2","v6HIzNa4K6G4nRP9032RIAAAAAABWMX6","v6HIzNa4K6G4nRP9032RIAAAAAAB7Klu","v6HIzNa4K6G4nRP9032RIAAAAAABWIwO","v6HIzNa4K6G4nRP9032RIAAAAAACaRu5","v6HIzNa4K6G4nRP9032RIAAAAAACC0ko","v6HIzNa4K6G4nRP9032RIAAAAAACZyhW","v6HIzNa4K6G4nRP9032RIAAAAAACZyBa","v6HIzNa4K6G4nRP9032RIAAAAAACeI_6","v6HIzNa4K6G4nRP9032RIAAAAAACeCkr","v6HIzNa4K6G4nRP9032RIAAAAAACYk-U","v6HIzNa4K6G4nRP9032RIAAAAAACYlxH","v6HIzNa4K6G4nRP9032RIAAAAAACYl4j","v6HIzNa4K6G4nRP9032RIAAAAAACZbdu","v6HIzNa4K6G4nRP9032RIAAAAAACYohs","v6HIzNa4K6G4nRP9032RIAAAAAAB9D7Q","v6HIzNa4K6G4nRP9032RIAAAAAAB9Y_u","v6HIzNa4K6G4nRP9032RIAAAAAABekiX","v6HIzNa4K6G4nRP9032RIAAAAAABehVx","v6HIzNa4K6G4nRP9032RIAAAAAABehax","v6HIzNa4K6G4nRP9032RIAAAAAABIp76","v6HIzNa4K6G4nRP9032RIAAAAAABIoTd","v6HIzNa4K6G4nRP9032RIAAAAAABIc_E","v6HIzNa4K6G4nRP9032RIAAAAAABH-zs","v6HIzNa4K6G4nRP9032RIAAAAAABIBU6","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAOmg3","ew01Dk0sWZctP-VaEpavqQAAAAAAOmdC","ew01Dk0sWZctP-VaEpavqQAAAAAAH0j-","ew01Dk0sWZctP-VaEpavqQAAAAAASkft","ew01Dk0sWZctP-VaEpavqQAAAAAAShdv"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4]},"XyR38J9TfiJQyusyqjnL0Q":{"address_or_lines":[4652224,22354871,22382638,22364302,56672751,58471189,58268669,58227812,58241853,31197476,7372151,7373114,7374151,8925121,8860356,8860667,8477214,5688773,8906989,5590020,5506248,4899556,4748900,4757831,4219698,4219725,10485923,16743,2752512,2751640,2740851,6649793,7859650,7859044,6707098,6708074,2391221,2381065],"file_ids":["-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","-pk6w5puGcp-wKnQ61BZzQ","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["-pk6w5puGcp-wKnQ61BZzQAAAAAARvzA","-pk6w5puGcp-wKnQ61BZzQAAAAABVRu3","-pk6w5puGcp-wKnQ61BZzQAAAAABVYgu","-pk6w5puGcp-wKnQ61BZzQAAAAABVUCO","-pk6w5puGcp-wKnQ61BZzQAAAAADYMHv","-pk6w5puGcp-wKnQ61BZzQAAAAADfDMV","-pk6w5puGcp-wKnQ61BZzQAAAAADeRv9","-pk6w5puGcp-wKnQ61BZzQAAAAADeHxk","-pk6w5puGcp-wKnQ61BZzQAAAAADeLM9","-pk6w5puGcp-wKnQ61BZzQAAAAAB3Akk","-pk6w5puGcp-wKnQ61BZzQAAAAAAcH13","-pk6w5puGcp-wKnQ61BZzQAAAAAAcIE6","-pk6w5puGcp-wKnQ61BZzQAAAAAAcIVH","-pk6w5puGcp-wKnQ61BZzQAAAAAAiC_B","-pk6w5puGcp-wKnQ61BZzQAAAAAAhzLE","-pk6w5puGcp-wKnQ61BZzQAAAAAAhzP7","-pk6w5puGcp-wKnQ61BZzQAAAAAAgVoe","-pk6w5puGcp-wKnQ61BZzQAAAAAAVs3F","-pk6w5puGcp-wKnQ61BZzQAAAAAAh-jt","-pk6w5puGcp-wKnQ61BZzQAAAAAAVUwE","-pk6w5puGcp-wKnQ61BZzQAAAAAAVATI","-pk6w5puGcp-wKnQ61BZzQAAAAAASsLk","-pk6w5puGcp-wKnQ61BZzQAAAAAASHZk","-pk6w5puGcp-wKnQ61BZzQAAAAAASJlH","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGMy","-pk6w5puGcp-wKnQ61BZzQAAAAAAQGNN","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKgAA","piWSMQrh4r040D0BPNaJvwAAAAAAKfyY","piWSMQrh4r040D0BPNaJvwAAAAAAKdJz","piWSMQrh4r040D0BPNaJvwAAAAAAZXfB","piWSMQrh4r040D0BPNaJvwAAAAAAd-3C","piWSMQrh4r040D0BPNaJvwAAAAAAd-tk","piWSMQrh4r040D0BPNaJvwAAAAAAZlea","piWSMQrh4r040D0BPNaJvwAAAAAAZltq","piWSMQrh4r040D0BPNaJvwAAAAAAJHy1","piWSMQrh4r040D0BPNaJvwAAAAAAJFUJ"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4]},"9s4s_y43ZAfUdYXm930H4A":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2755760,2754888,2744099,6711233,6711003,4219907],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw","9LzzIocepYcOjnUsLlgOjgAAAAAAKglI","9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j","9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB","9LzzIocepYcOjnUsLlgOjgAAAAAAZmbb","9LzzIocepYcOjnUsLlgOjgAAAAAAQGQD"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4]},"LeV2oAqU4BVeWoabuoh-cw":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2755760,2754888,2744099,6711233,7651644,7435512,7503313],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw","9LzzIocepYcOjnUsLlgOjgAAAAAAKglI","9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j","9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB","9LzzIocepYcOjnUsLlgOjgAAAAAAdME8","9LzzIocepYcOjnUsLlgOjgAAAAAAcXT4","9LzzIocepYcOjnUsLlgOjgAAAAAAcn3R"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4]},"2gcYNFzbFyKxWn73M5202w":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2755760,2754888,2744099,6711233,7651644,7436960,2551475,2548988],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw","9LzzIocepYcOjnUsLlgOjgAAAAAAKglI","9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j","9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB","9LzzIocepYcOjnUsLlgOjgAAAAAAdME8","9LzzIocepYcOjnUsLlgOjgAAAAAAcXqg","9LzzIocepYcOjnUsLlgOjgAAAAAAJu6z","9LzzIocepYcOjnUsLlgOjgAAAAAAJuT8"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4]},"CU-T9AvnxmWd1TTRjgV01Q":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2755760,2754888,2744099,6711233,7651644,7435512,7508830,6761766,2559050],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw","9LzzIocepYcOjnUsLlgOjgAAAAAAKglI","9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j","9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB","9LzzIocepYcOjnUsLlgOjgAAAAAAdME8","9LzzIocepYcOjnUsLlgOjgAAAAAAcXT4","9LzzIocepYcOjnUsLlgOjgAAAAAAcpNe","9LzzIocepYcOjnUsLlgOjgAAAAAAZy0m","9LzzIocepYcOjnUsLlgOjgAAAAAAJwxK"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4]},"nnsc9UkL_oA5SAi5cs_ZPg":{"address_or_lines":[4195929,135481,1080531,1010960,1006705,1002538,905832,905294,893117,905294,893117,905294,895510,893117,905294,893117,905294,893117,905294,893117,905294,887126,310194,449006,905294,893117,905294,885107,310194,633609,646930,310194,366119,310194,448792,905294,895510,876495,513798,506886,539471,539386,531635],"file_ids":["YsKzCJ9e4eZnuT00vj7Pcw","Z_CHd3Zjsh2cWE2NSdbiNQ","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw","N4ILulabOfF5MnyRJbvDXw"],"frame_ids":["YsKzCJ9e4eZnuT00vj7PcwAAAAAAQAZZ","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","N4ILulabOfF5MnyRJbvDXwAAAAAAEHzT","N4ILulabOfF5MnyRJbvDXwAAAAAAD20Q","N4ILulabOfF5MnyRJbvDXwAAAAAAD1xx","N4ILulabOfF5MnyRJbvDXwAAAAAAD0wq","N4ILulabOfF5MnyRJbvDXwAAAAAADdJo","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaC9","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaC9","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaoW","N4ILulabOfF5MnyRJbvDXwAAAAAADaC9","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaC9","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaC9","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaC9","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADYlW","N4ILulabOfF5MnyRJbvDXwAAAAAABLuy","N4ILulabOfF5MnyRJbvDXwAAAAAABtnu","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaC9","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADYFz","N4ILulabOfF5MnyRJbvDXwAAAAAABLuy","N4ILulabOfF5MnyRJbvDXwAAAAAACasJ","N4ILulabOfF5MnyRJbvDXwAAAAAACd8S","N4ILulabOfF5MnyRJbvDXwAAAAAABLuy","N4ILulabOfF5MnyRJbvDXwAAAAAABZYn","N4ILulabOfF5MnyRJbvDXwAAAAAABLuy","N4ILulabOfF5MnyRJbvDXwAAAAAABtkY","N4ILulabOfF5MnyRJbvDXwAAAAAADdBO","N4ILulabOfF5MnyRJbvDXwAAAAAADaoW","N4ILulabOfF5MnyRJbvDXwAAAAAADV_P","N4ILulabOfF5MnyRJbvDXwAAAAAAB9cG","N4ILulabOfF5MnyRJbvDXwAAAAAAB7wG","N4ILulabOfF5MnyRJbvDXwAAAAAACDtP","N4ILulabOfF5MnyRJbvDXwAAAAAACDr6","N4ILulabOfF5MnyRJbvDXwAAAAAACByz"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"wAujHiFN47_oNUI63d6EtA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440136,7513502,6765905,6759805,2574033,2218596],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI","ew01Dk0sWZctP-VaEpavqQAAAAAAcqWe","ew01Dk0sWZctP-VaEpavqQAAAAAAZz1R","ew01Dk0sWZctP-VaEpavqQAAAAAAZyV9","ew01Dk0sWZctP-VaEpavqQAAAAAAJ0bR","ew01Dk0sWZctP-VaEpavqQAAAAAAIdpk"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"ia-QZTf1AEqK7KEggAUJSw":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2756560,2755688,2744899,6715329,7656460,7440136,7508344,7393457,7394824,7384416,6868281,6866019],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","ew01Dk0sWZctP-VaEpavqQAAAAAAoACj","ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn","ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q","ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo","ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD","ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB","ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM","ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI","ew01Dk0sWZctP-VaEpavqQAAAAAAcpF4","ew01Dk0sWZctP-VaEpavqQAAAAAAcNCx","ew01Dk0sWZctP-VaEpavqQAAAAAAcNYI","ew01Dk0sWZctP-VaEpavqQAAAAAAcK1g","ew01Dk0sWZctP-VaEpavqQAAAAAAaM05","ew01Dk0sWZctP-VaEpavqQAAAAAAaMRj"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"YxsKA4n0U7pKfHmrePpfjA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10489481,12583132,6878809,6871998,6871380,7366427,7363873,7362975,7354531,7354154,7352952,7752506,7093274,7753394,7707617],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoA6J","9LzzIocepYcOjnUsLlgOjgAAAAAAwADc","9LzzIocepYcOjnUsLlgOjgAAAAAAaPZZ","9LzzIocepYcOjnUsLlgOjgAAAAAAaNu-","9LzzIocepYcOjnUsLlgOjgAAAAAAaNlU","9LzzIocepYcOjnUsLlgOjgAAAAAAcGcb","9LzzIocepYcOjnUsLlgOjgAAAAAAcF0h","9LzzIocepYcOjnUsLlgOjgAAAAAAcFmf","9LzzIocepYcOjnUsLlgOjgAAAAAAcDij","9LzzIocepYcOjnUsLlgOjgAAAAAAcDcq","9LzzIocepYcOjnUsLlgOjgAAAAAAcDJ4","9LzzIocepYcOjnUsLlgOjgAAAAAAdks6","9LzzIocepYcOjnUsLlgOjgAAAAAAbDwa","9LzzIocepYcOjnUsLlgOjgAAAAAAdk6y","9LzzIocepYcOjnUsLlgOjgAAAAAAdZvh"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"mqliNf10_gB69yQo7_zlzg":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,18612,22306,4364,53010,48188,14432,38826,1480561,1970211,1481652,1480953,2600004,1079483,19966,39758,10892,28340,55468,1479960,1494280,2600004,1079483,63826,64498,1479960,2600004,1079483,60540,21276,37564,30612,1479868,2600004,1079483,54304,30612,1479868,2600004,1066627,7128,57352],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","EFJHOn-GACfHXgae-R1yDA","GdaBUD9IUEkKxIBryNqV2w","QU8QLoFK6ojrywKrBFfTzA","V558DAsp4yi8bwa8eYwk5Q","tuTnMBfyc9UiPsI0QyvErA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","cHp4MwXaY5FCuFRuAA6tWw","-9oyoP4Jj2iRkwEezqId-g","3FRCbvQLPuJyn2B-2wELGw","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","--q8cwZVXbHL2zOM_p3RlQ","yaTrLhUSIq2WitrTHLBy3Q"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAEi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAFci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAABEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAM8S","grZNsSElR5ITq8H2yHCNSwAAAAAAALw8","W8AFtEsepzrJ6AasHrCttwAAAAAAADhg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAJeq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","EFJHOn-GACfHXgae-R1yDAAAAAAAAE3-","GdaBUD9IUEkKxIBryNqV2wAAAAAAAJtO","QU8QLoFK6ojrywKrBFfTzAAAAAAAACqM","V558DAsp4yi8bwa8eYwk5QAAAAAAAG60","tuTnMBfyc9UiPsI0QyvErAAAAAAAANis","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","oERZXsH8EPeoSRxNNaSWfQAAAAAAAPlS","gMhgHDYSMmyInNJ15VwYFgAAAAAAAPvy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","cHp4MwXaY5FCuFRuAA6tWwAAAAAAAOx8","-9oyoP4Jj2iRkwEezqId-gAAAAAAAFMc","3FRCbvQLPuJyn2B-2wELGwAAAAAAAJK8","FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","GEIvPhvjHWZLHz2BksVgvAAAAAAAANQg","FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEEaD","--q8cwZVXbHL2zOM_p3RlQAAAAAAABvY","yaTrLhUSIq2WitrTHLBy3QAAAAAAAOAI"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,1,1,1,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,1]},"24tLFB3hY9xz1zbZCjaBXA":{"address_or_lines":[4654944,15291206,14341928,15275435,15271887,9565966,9575659,9566094,9566425,10732849,10691669,9933294,9934938,9900484,9900235,9617319,9584395,5101817,7575182,7550869,7561892,5676919,7561404,5629448,5551236,5477192,5131149,4738084,4746343,4209682,4209709,10485923,16807,2755760,2754888,2744099,6711233,7651644,7435512,7503672,7388865,7390232,7379824,6864947,6862495,2596,6843125,7212243],"file_ids":["FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","FWZ9q3TQKZZok58ua1HDsg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg","aUXpdArtZf510BJKvwiFDw","9LzzIocepYcOjnUsLlgOjg","9LzzIocepYcOjnUsLlgOjg"],"frame_ids":["FWZ9q3TQKZZok58ua1HDsgAAAAAARwdg","FWZ9q3TQKZZok58ua1HDsgAAAAAA6VNG","FWZ9q3TQKZZok58ua1HDsgAAAAAA2tco","FWZ9q3TQKZZok58ua1HDsgAAAAAA6RWr","FWZ9q3TQKZZok58ua1HDsgAAAAAA6QfP","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfcO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkhzr","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfeO","FWZ9q3TQKZZok58ua1HDsgAAAAAAkfjZ","FWZ9q3TQKZZok58ua1HDsgAAAAAAo8Ux","FWZ9q3TQKZZok58ua1HDsgAAAAAAoyRV","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5Hu","FWZ9q3TQKZZok58ua1HDsgAAAAAAl5ha","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxHE","FWZ9q3TQKZZok58ua1HDsgAAAAAAlxDL","FWZ9q3TQKZZok58ua1HDsgAAAAAAkr-n","FWZ9q3TQKZZok58ua1HDsgAAAAAAkj8L","FWZ9q3TQKZZok58ua1HDsgAAAAAATdj5","FWZ9q3TQKZZok58ua1HDsgAAAAAAc5aO","FWZ9q3TQKZZok58ua1HDsgAAAAAAczeV","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2Kk","FWZ9q3TQKZZok58ua1HDsgAAAAAAVp93","FWZ9q3TQKZZok58ua1HDsgAAAAAAc2C8","FWZ9q3TQKZZok58ua1HDsgAAAAAAVeYI","FWZ9q3TQKZZok58ua1HDsgAAAAAAVLSE","FWZ9q3TQKZZok58ua1HDsgAAAAAAU5NI","FWZ9q3TQKZZok58ua1HDsgAAAAAATkuN","FWZ9q3TQKZZok58ua1HDsgAAAAAASEwk","FWZ9q3TQKZZok58ua1HDsgAAAAAASGxn","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwS","FWZ9q3TQKZZok58ua1HDsgAAAAAAQDwt","9LzzIocepYcOjnUsLlgOjgAAAAAAoACj","9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn","9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw","9LzzIocepYcOjnUsLlgOjgAAAAAAKglI","9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j","9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB","9LzzIocepYcOjnUsLlgOjgAAAAAAdME8","9LzzIocepYcOjnUsLlgOjgAAAAAAcXT4","9LzzIocepYcOjnUsLlgOjgAAAAAAcn84","9LzzIocepYcOjnUsLlgOjgAAAAAAcL7B","9LzzIocepYcOjnUsLlgOjgAAAAAAcMQY","9LzzIocepYcOjnUsLlgOjgAAAAAAcJtw","9LzzIocepYcOjnUsLlgOjgAAAAAAaMAz","9LzzIocepYcOjnUsLlgOjgAAAAAAaLaf","aUXpdArtZf510BJKvwiFDwAAAAAAAAok","9LzzIocepYcOjnUsLlgOjgAAAAAAaGr1","9LzzIocepYcOjnUsLlgOjgAAAAAAbgzT"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]},"MLSOPRH6z6HuctKh5rsAnA":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,2228,5922,53516,36626,29084,63584,18346,1480561,1970211,1481652,1480953,2600004,1079669,3708,1480561,1970211,1481652,1480953,2600004,1079669,5350,11456,17946,62630,26608,28264,8452,1480561,1941045,1970515,1481652,1481047,2600004,1058958,26942,1844654,1847116,1788409,1758317,1865641,10490014,422731,937166],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","EFJHOn-GACfHXgae-R1yDA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","ktj-IOmkEpvZJouiJkQjTg","O_h7elJSxPO7SiCsftYRZg","_s_-RvH9Io2qUzM6f5JLGg","8UGQaqEhTX9IIJEQCXnRsQ","jn4X0YIYIsTeszwLEaje9g","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","TesF2I_BvQoOuJH9P_M2mA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ","ew01Dk0sWZctP-VaEpavqQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0","U4Le8nh-beog_B7jq7uTIAAAAAAAABci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAHGc","W8AFtEsepzrJ6AasHrCttwAAAAAAAPhg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAEeq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","EFJHOn-GACfHXgae-R1yDAAAAAAAAA58","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","kSaNXrGzSS3BnDNNWezzMAAAAAAAABTm","ne8F__HPIVgxgycJADVSzAAAAAAAACzA","ktj-IOmkEpvZJouiJkQjTgAAAAAAAEYa","O_h7elJSxPO7SiCsftYRZgAAAAAAAPSm","_s_-RvH9Io2qUzM6f5JLGgAAAAAAAGfw","8UGQaqEhTX9IIJEQCXnRsQAAAAAAAG5o","jn4X0YIYIsTeszwLEaje9gAAAAAAACEE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ41","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhFT","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFplX","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAECiO","TesF2I_BvQoOuJH9P_M2mAAAAAAAAGk-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHCWu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHC9M","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG0n5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAGtRt","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHHep","ew01Dk0sWZctP-VaEpavqQAAAAAAoBCe","ew01Dk0sWZctP-VaEpavqQAAAAAABnNL","ew01Dk0sWZctP-VaEpavqQAAAAAADkzO"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,3,1,3,3,3,3,3,4,4,4]},"krdohOL0KiVMtm4q-6fmjg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,388,33826,620,51986,5836,10976,12298,1480209,1969795,1481300,1480601,2595076,1079144,1868,1480209,1969795,1481300,1480601,2595076,1079144,37910,8000,46852,32076,49840,40252,33434,32730,43978,37948,30428,26428,19370,1480209,1940645,1970099,1481300,1480695,2595076,1079144,20016,37192,1480141,1913750],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","ne8F__HPIVgxgycJADVSzA","CwUjPVV5_7q7c0GhtW0aPw","okehWevKsEA4q6dk779jgw","-IuadWGT89NVzIyF_Emodw","XXJY7v4esGWnaxtMW3FA0g","FbrXdcA4j750RyQ3q9JXMw","pL34QuyxyP6XYzGDBMK_5w","IoAk4kM-M4DsDPp7ia5QXw","uHLoBslr3h6S7ooNeXzEbw","iRoTPXvR_cRsnzDO-aurpQ","fB79lJck2X90l-j7VqPR-Q","gbMheDI1NZ3NY96J0seddg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GquRfhZBLBKr9rIBPuH3nA","_DA_LSFNMjbu9L2Dcselpw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS","grZNsSElR5ITq8H2yHCNSwAAAAAAABbM","W8AFtEsepzrJ6AasHrCttwAAAAAAACrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAADAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAAdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","kSaNXrGzSS3BnDNNWezzMAAAAAAAAJQW","ne8F__HPIVgxgycJADVSzAAAAAAAAB9A","CwUjPVV5_7q7c0GhtW0aPwAAAAAAALcE","okehWevKsEA4q6dk779jgwAAAAAAAH1M","-IuadWGT89NVzIyF_EmodwAAAAAAAMKw","XXJY7v4esGWnaxtMW3FA0gAAAAAAAJ08","FbrXdcA4j750RyQ3q9JXMwAAAAAAAIKa","pL34QuyxyP6XYzGDBMK_5wAAAAAAAH_a","IoAk4kM-M4DsDPp7ia5QXwAAAAAAAKvK","uHLoBslr3h6S7ooNeXzEbwAAAAAAAJQ8","iRoTPXvR_cRsnzDO-aurpQAAAAAAAHbc","fB79lJck2X90l-j7VqPR-QAAAAAAAGc8","gbMheDI1NZ3NY96J0seddgAAAAAAAEuq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZyl","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg-z","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpf3","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","GquRfhZBLBKr9rIBPuH3nAAAAAAAAE4w","_DA_LSFNMjbu9L2DcselpwAAAAAAAJFI","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpXN","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHTOW"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,1,1,3,3]},"FtHYpmBv9BwyjtHQeYFcCw":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1091475,64358,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,61360,18470,16624,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1079144,14936,1481694,1828960,2581397,1480843,1480209,1940568,1917258,1481300,1480601,2595076,1076587,6244,3453440,1376741,1877279,3072226],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","8EY5iPD5-FtlXFBTyb6lkw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","dCCKy6JoX0PADOFic8hRNQ","9w9lF96vJW7ZhBoZ8ETsBw","xUQuo4OgBaS_Le-fdAwt8A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zkPjzY2Et3KehkHOcSphkA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","mBpjyQvq6ftE7Wm1BUpcFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","8EY5iPD5-FtlXFBTyb6lkwAAAAAAAPtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","dCCKy6JoX0PADOFic8hRNQAAAAAAAO-w","9w9lF96vJW7ZhBoZ8ETsBwAAAAAAAEgm","xUQuo4OgBaS_Le-fdAwt8AAAAAAAAEDw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","zkPjzY2Et3KehkHOcSphkAAAAAAAADpY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpiL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUFK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","mBpjyQvq6ftE7Wm1BUpcFgAAAAAAABhk","-Z7SlEXhuy5tL2BF-xmy3gAAAAAANLIA","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFQHl","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHKUf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuDi"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3]},"FuFG7sSEAg94nZpDT4nzlA":{"address_or_lines":[4623936,24755503,6980046,23231210,6980046,23264536,6980046,23232004,23232150,6980046,23230455,6980046,23232004,23232150,6980046,23230455,6980046,23272795,6980046,23232004,23232150,6980046,24742300,6980046,23230455,6980046,23269877,22973163,22972451,22973163,22972451,22964890,22884541,11721444,11715672,11715835,11715578,22884850,22966101,22967654,19588556,8970856,8920596,9005417,9007845,7887684,7888285,7889956,7894532,7945899,4658568,4210208],"file_ids":["pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g","pRLjmMO0U8sO4DFopfFU5g"],"frame_ids":["pRLjmMO0U8sO4DFopfFU5gAAAAAARo5A","pRLjmMO0U8sO4DFopfFU5gAAAAABeb0v","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYnrq","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYv0Y","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYn4E","pRLjmMO0U8sO4DFopfFU5gAAAAABYn6W","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYnf3","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYn4E","pRLjmMO0U8sO4DFopfFU5gAAAAABYn6W","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYnf3","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYx1b","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYn4E","pRLjmMO0U8sO4DFopfFU5gAAAAABYn6W","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABeYmc","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYnf3","pRLjmMO0U8sO4DFopfFU5gAAAAAAaoHO","pRLjmMO0U8sO4DFopfFU5gAAAAABYxH1","pRLjmMO0U8sO4DFopfFU5gAAAAABXorr","pRLjmMO0U8sO4DFopfFU5gAAAAABXogj","pRLjmMO0U8sO4DFopfFU5gAAAAABXorr","pRLjmMO0U8sO4DFopfFU5gAAAAABXogj","pRLjmMO0U8sO4DFopfFU5gAAAAABXmqa","pRLjmMO0U8sO4DFopfFU5gAAAAABXTC9","pRLjmMO0U8sO4DFopfFU5gAAAAAAstrk","pRLjmMO0U8sO4DFopfFU5gAAAAAAssRY","pRLjmMO0U8sO4DFopfFU5gAAAAAAssT7","pRLjmMO0U8sO4DFopfFU5gAAAAAAssP6","pRLjmMO0U8sO4DFopfFU5gAAAAABXTHy","pRLjmMO0U8sO4DFopfFU5gAAAAABXm9V","pRLjmMO0U8sO4DFopfFU5gAAAAABXnVm","pRLjmMO0U8sO4DFopfFU5gAAAAABKuXM","pRLjmMO0U8sO4DFopfFU5gAAAAAAiOJo","pRLjmMO0U8sO4DFopfFU5gAAAAAAiB4U","pRLjmMO0U8sO4DFopfFU5gAAAAAAiWlp","pRLjmMO0U8sO4DFopfFU5gAAAAAAiXLl","pRLjmMO0U8sO4DFopfFU5gAAAAAAeFtE","pRLjmMO0U8sO4DFopfFU5gAAAAAAeF2d","pRLjmMO0U8sO4DFopfFU5gAAAAAAeGQk","pRLjmMO0U8sO4DFopfFU5gAAAAAAeHYE","pRLjmMO0U8sO4DFopfFU5gAAAAAAeT6r","pRLjmMO0U8sO4DFopfFU5gAAAAAARxWI","pRLjmMO0U8sO4DFopfFU5gAAAAAAQD4g"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"chida0TNeXOPGVvI0kALCQ":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,824,116,12,8,54,12,46,22,1091612,1804498,665668,663668,1112453,1232178,833111,2265137,2264574,2258601,1016110,2256845],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","IlUL618nbeW5Kz4uyGZLrQ","U7DZUwH_4YU5DSkoQhGJWw","bmb3nSRfimrjfhanpjR1rQ","oN7OWDJeuc8DmI2f_earDQ","Yj7P3-Rt3nirG6apRl4A7A","pz3Evn9laHNJFMwOKIXbsw","7aaw2O1Vn7-6eR8XuUWQZQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAAM4","IlUL618nbeW5Kz4uyGZLrQAAAAAAAAB0","U7DZUwH_4YU5DSkoQhGJWwAAAAAAAAAM","bmb3nSRfimrjfhanpjR1rQAAAAAAAAAI","oN7OWDJeuc8DmI2f_earDQAAAAAAAAA2","Yj7P3-Rt3nirG6apRl4A7AAAAAAAAAAM","pz3Evn9laHNJFMwOKIXbswAAAAAAAAAu","7aaw2O1Vn7-6eR8XuUWQZQAAAAAAAAAW","G68hjsyagwq6LpWrMjDdngAAAAAAEKgc","G68hjsyagwq6LpWrMjDdngAAAAAAG4jS","G68hjsyagwq6LpWrMjDdngAAAAAACihE","G68hjsyagwq6LpWrMjDdngAAAAAACiB0","G68hjsyagwq6LpWrMjDdngAAAAAAEPmF","G68hjsyagwq6LpWrMjDdngAAAAAAEs0y","G68hjsyagwq6LpWrMjDdngAAAAAADLZX","G68hjsyagwq6LpWrMjDdngAAAAAAIpAx","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAInap","G68hjsyagwq6LpWrMjDdngAAAAAAD4Eu","G68hjsyagwq6LpWrMjDdngAAAAAAIm_N"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3]},"UDWRHwtQcuK3KYw4Lj118w":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,33156,1058,33388,19218,30038,33244,3444,11060,9712,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,49806,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,61514,2790352,1482889,1482415,2595076,1057495,58094,59978,64928,29086,21086],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","SD7uzoegJjRT3jYNpuQ5wQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","lOUbi56SanKTCh9Y7fIwDw","n74P5OxFm1hAo5ZWtgcKHQ","zXbqXCWr0lCbi_b24hNBRQ"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAHVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAIHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAA10","xwuAPHgc12-8PZB3i-320gAAAAAAACs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAMKO","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","SD7uzoegJjRT3jYNpuQ5wQAAAAAAAPBK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAECLX","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOpK","lOUbi56SanKTCh9Y7fIwDwAAAAAAAP2g","n74P5OxFm1hAo5ZWtgcKHQAAAAAAAHGe","zXbqXCWr0lCbi_b24hNBRQAAAAAAAFJe"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1]},"wQhKHV5i9LyZbGr1o38TMA":{"address_or_lines":[4631744,4426728,23175065,22765086,22101979,22101626,22103238,19925815,19926028,19930622,22109732,19929162,22109403,22104583,22092442,20383549,20126576,20124268,7004126,6995902,6997458,19974869,19979184,7254420,7366379,8869213,8813007,8830631,8835818,5761274,8899923,8811367,6480793,6476612,6475553,6139725,6059982,5083307,5091601,4714216,4721177,4729434,10485923,16743,2752800,2752044,2741274,6650246,6650083,7384662,7382442,7451553,7447772,7440959,7439791],"file_ids":["-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","-V-5ede56KMAXhjFbz84Sw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["-V-5ede56KMAXhjFbz84SwAAAAAARqzA","-V-5ede56KMAXhjFbz84SwAAAAAAQ4vo","-V-5ede56KMAXhjFbz84SwAAAAABYZ-Z","-V-5ede56KMAXhjFbz84SwAAAAABW14e","-V-5ede56KMAXhjFbz84SwAAAAABUT_b","-V-5ede56KMAXhjFbz84SwAAAAABUT56","-V-5ede56KMAXhjFbz84SwAAAAABUUTG","-V-5ede56KMAXhjFbz84SwAAAAABMAs3","-V-5ede56KMAXhjFbz84SwAAAAABMAwM","-V-5ede56KMAXhjFbz84SwAAAAABMB3-","-V-5ede56KMAXhjFbz84SwAAAAABUV4k","-V-5ede56KMAXhjFbz84SwAAAAABMBhK","-V-5ede56KMAXhjFbz84SwAAAAABUVzb","-V-5ede56KMAXhjFbz84SwAAAAABUUoH","-V-5ede56KMAXhjFbz84SwAAAAABURqa","-V-5ede56KMAXhjFbz84SwAAAAABNwc9","-V-5ede56KMAXhjFbz84SwAAAAABMxtw","-V-5ede56KMAXhjFbz84SwAAAAABMxJs","-V-5ede56KMAXhjFbz84SwAAAAAAat_e","-V-5ede56KMAXhjFbz84SwAAAAAAar--","-V-5ede56KMAXhjFbz84SwAAAAAAasXS","-V-5ede56KMAXhjFbz84SwAAAAABMMrV","-V-5ede56KMAXhjFbz84SwAAAAABMNuw","-V-5ede56KMAXhjFbz84SwAAAAAAbrGU","-V-5ede56KMAXhjFbz84SwAAAAAAcGbr","-V-5ede56KMAXhjFbz84SwAAAAAAh1Vd","-V-5ede56KMAXhjFbz84SwAAAAAAhnnP","-V-5ede56KMAXhjFbz84SwAAAAAAhr6n","-V-5ede56KMAXhjFbz84SwAAAAAAhtLq","-V-5ede56KMAXhjFbz84SwAAAAAAV-j6","-V-5ede56KMAXhjFbz84SwAAAAAAh81T","-V-5ede56KMAXhjFbz84SwAAAAAAhnNn","-V-5ede56KMAXhjFbz84SwAAAAAAYuOZ","-V-5ede56KMAXhjFbz84SwAAAAAAYtNE","-V-5ede56KMAXhjFbz84SwAAAAAAYs8h","-V-5ede56KMAXhjFbz84SwAAAAAAXa9N","-V-5ede56KMAXhjFbz84SwAAAAAAXHfO","-V-5ede56KMAXhjFbz84SwAAAAAATZCr","-V-5ede56KMAXhjFbz84SwAAAAAATbER","-V-5ede56KMAXhjFbz84SwAAAAAAR-7o","-V-5ede56KMAXhjFbz84SwAAAAAASAoZ","-V-5ede56KMAXhjFbz84SwAAAAAASCpa","piWSMQrh4r040D0BPNaJvwAAAAAAoACj","piWSMQrh4r040D0BPNaJvwAAAAAAAEFn","piWSMQrh4r040D0BPNaJvwAAAAAAKgEg","piWSMQrh4r040D0BPNaJvwAAAAAAKf4s","piWSMQrh4r040D0BPNaJvwAAAAAAKdQa","piWSMQrh4r040D0BPNaJvwAAAAAAZXmG","piWSMQrh4r040D0BPNaJvwAAAAAAZXjj","piWSMQrh4r040D0BPNaJvwAAAAAAcK5W","piWSMQrh4r040D0BPNaJvwAAAAAAcKWq","piWSMQrh4r040D0BPNaJvwAAAAAAcbOh","piWSMQrh4r040D0BPNaJvwAAAAAAcaTc","piWSMQrh4r040D0BPNaJvwAAAAAAcYo_","piWSMQrh4r040D0BPNaJvwAAAAAAcYWv"],"type_ids":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4]},"TtsX1UxF45-CxViHFwbKJw":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,49540,17442,33388,19218,34134,37340,19828,11060,26096,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,53982,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,41518,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,17976,33110,26922,19187,41240,50343],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uo8E5My6tupMEt-pfV-uhA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","dGWvVtQJJ5wuqNyQVpi8lA","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAIVW","LF6DFcGHEMqhhhlptO_M_QAAAAAAAJHc","Af6E3BeG383JVVbu67NJ0QAAAAAAAE10","xwuAPHgc12-8PZB3i-320gAAAAAAACs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAANLe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","uo8E5My6tupMEt-pfV-uhAAAAAAAAKIu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAGkq","dGWvVtQJJ5wuqNyQVpi8lAAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMSn"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"iu7dYG1YyobzAXC7AJADOw":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,4,38,174,104,68,88,38,174,104,14,32,190,1091944,2047231,2046923,2044755,2041537,2044755,2041537,2044780,2041460,1171829,2265239,2264574,2258463,1179954],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ZBnr-5IlLVGCdkX_lTNKmw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ZBnr-5IlLVGCdkX_lTNKmwAAAAAAAABY","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAAC-","G68hjsyagwq6LpWrMjDdngAAAAAAEKlo","G68hjsyagwq6LpWrMjDdngAAAAAAHzz_","G68hjsyagwq6LpWrMjDdngAAAAAAHzvL","G68hjsyagwq6LpWrMjDdngAAAAAAHzNT","G68hjsyagwq6LpWrMjDdngAAAAAAHybB","G68hjsyagwq6LpWrMjDdngAAAAAAHzNT","G68hjsyagwq6LpWrMjDdngAAAAAAHybB","G68hjsyagwq6LpWrMjDdngAAAAAAHzNs","G68hjsyagwq6LpWrMjDdngAAAAAAHyZ0","G68hjsyagwq6LpWrMjDdngAAAAAAEeF1","G68hjsyagwq6LpWrMjDdngAAAAAAIpCX","G68hjsyagwq6LpWrMjDdngAAAAAAIo3-","G68hjsyagwq6LpWrMjDdngAAAAAAInYf","G68hjsyagwq6LpWrMjDdngAAAAAAEgEy"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"WmwSnxyphedkasVyGbhNdg":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,18612,22306,4364,53010,23142,41180,18932,30244,42480,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,4420,2578675,2599636,1091600,29418,2795776,1483241,1482767,2600004,1074397,3150,5208,43696,4420,2578675,2599636,1091600,58990,2795776,1483241,1482767,2600004,1073803,3150,5208,43696,4204,342,33506,2852079,2851771,2849353,2846190,2846190,2845732],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","l97YFeEKpeLfa-lEAZVNcA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAEi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAFci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAABEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAM8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAFpm","LF6DFcGHEMqhhhlptO_M_QAAAAAAAKDc","Af6E3BeG383JVVbu67NJ0QAAAAAAAEn0","xwuAPHgc12-8PZB3i-320gAAAAAAAHYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAHLq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","l97YFeEKpeLfa-lEAZVNcAAAAAAAAOZu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAAFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAILi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4Tv","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK4O7","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK3pJ","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK23u","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAK2wk"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3]},"YWZby9VC56JtR6BAaYHEoA":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,18612,22306,4364,53010,16796,14432,6058,1480561,1970211,1481652,1480953,2600004,1079669,20092,1480561,1970211,1481652,1480953,2600004,1062448,57610,1845095,1847963,1481919,2600004,1079483,60588,38154,52556,1479960,1494280,2600004,1079483,55468,1479960,1494280,2600004,1079483,14674,64498,1479960,2600004,1079483,48678,25810,37884,46996,1479868,2600004,1079483,7536,46996,1479868,2600004,1049946,29322],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","EFJHOn-GACfHXgae-R1yDA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","kSaNXrGzSS3BnDNNWezzMA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xNMiNBkMujk7ZnRv0OEjrQ","MYrgKQIxdDhr1gdpucfc-Q","un9fLDZOLvDMO52ltZtueg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","tuTnMBfyc9UiPsI0QyvErA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","rTFMSHhLRlj86vHPR06zoQ","oArGmvsy3VNtTf_V9EHNeQ","-T5rZCijT5TDJjmoEi8Kxg","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","--q8cwZVXbHL2zOM_p3RlQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAEi0","U4Le8nh-beog_B7jq7uTIAAAAAAAAFci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAABEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAM8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAEGc","W8AFtEsepzrJ6AasHrCttwAAAAAAADhg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAABeq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","EFJHOn-GACfHXgae-R1yDAAAAAAAAE58","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhAj","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEDYw","kSaNXrGzSS3BnDNNWezzMAAAAAAAAOEK","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHCdn","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDKb","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpy_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","xNMiNBkMujk7ZnRv0OEjrQAAAAAAAOys","MYrgKQIxdDhr1gdpucfc-QAAAAAAAJUK","un9fLDZOLvDMO52ltZtuegAAAAAAAM1M","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","tuTnMBfyc9UiPsI0QyvErAAAAAAAANis","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","oERZXsH8EPeoSRxNNaSWfQAAAAAAADlS","gMhgHDYSMmyInNJ15VwYFgAAAAAAAPvy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpUY","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","rTFMSHhLRlj86vHPR06zoQAAAAAAAL4m","oArGmvsy3VNtTf_V9EHNeQAAAAAAAGTS","-T5rZCijT5TDJjmoEi8KxgAAAAAAAJP8","FqNqtF0e0OG1VJJtWE9clwAAAAAAALeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","GEIvPhvjHWZLHz2BksVgvAAAAAAAAB1w","FqNqtF0e0OG1VJJtWE9clwAAAAAAALeU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEAVa","--q8cwZVXbHL2zOM_p3RlQAAAAAAAHKK"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1]},"Hi8HEHDniMkBvPgm-_IXdg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,388,33826,620,51986,50422,53628,36212,43828,42480,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,3426,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1091475,5270,2790352,1482889,1482415,2595076,1073749,53998,56056,29040,34524,2573747,2594708,1055190,28766,23366,29852,29250,6740,37336,23068],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ynoRUNDFNh_CC1ViETMulA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fxzD8soKl4etJ4L6nJl81g","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAMT2","LF6DFcGHEMqhhhlptO_M_QAAAAAAANF8","Af6E3BeG383JVVbu67NJ0QAAAAAAAI10","xwuAPHgc12-8PZB3i-320gAAAAAAAKs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAA1i","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","ynoRUNDFNh_CC1ViETMulAAAAAAAABSW","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAANLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEBnW","fxzD8soKl4etJ4L6nJl81gAAAAAAAHBe","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAFtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAHSc","_lF8o5tJDcePvza_IYtgSQAAAAAAAHJC","TRd7r6mvdzYdjMdTtebtwwAAAAAAABpU","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAJHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAFoc"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,1,1,1,1]},"X86DUuQ7tHAxGBaWu4tZLg":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1079669,2228,5922,53516,36626,19046,37084,2548,13860,26096,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,324,2578675,2599636,1091600,64610,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,324,2578675,2599636,1091600,39726,2795776,1483241,1482767,2600004,1074397,52302,54360,27312,324,2578675,2599636,1091600,0,2794972,1848805,1837992,1848417,2718329,2222078,2208786],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BrhWuphS0ZH9x8_V0fpb0A","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","780bLUPADqfQ3x1T5lnVOg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","_____________________w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0","U4Le8nh-beog_B7jq7uTIAAAAAAAABci","CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM","SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S","grZNsSElR5ITq8H2yHCNSwAAAAAAAEpm","LF6DFcGHEMqhhhlptO_M_QAAAAAAAJDc","Af6E3BeG383JVVbu67NJ0QAAAAAAAAn0","xwuAPHgc12-8PZB3i-320gAAAAAAADYk","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","BrhWuphS0ZH9x8_V0fpb0AAAAAAAAPxi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","780bLUPADqfQ3x1T5lnVOgAAAAAAAJsu","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","_____________________wAAAAAAAAAA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqXc","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDXl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHAuo","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDRh","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKXp5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAIef-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAIbQS"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3]},"Tx8lhCcOjrVLOl1hWK6aBw":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,33156,1058,33388,19218,38700,43744,45066,1480209,1969795,1481300,1480601,2595076,1079144,34636,1480209,1969795,1481300,1480601,2595076,1062336,4250,1844695,1847563,1481567,2595076,1079485,3004,57258,27404,1479608,1493928,2595076,1079485,63084,1479608,1493928,2595076,1079485,14194,64498,1479608,2595076,1079485,18374,41842,34364,14228,1479516,2595076,1079485,24640,14228,1479516,2595076,1087128,21352,26392,2571436,1909209],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","xNMiNBkMujk7ZnRv0OEjrQ","MYrgKQIxdDhr1gdpucfc-Q","un9fLDZOLvDMO52ltZtueg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","grikUXlisBLUbeL_OWixIw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","rTFMSHhLRlj86vHPR06zoQ","oArGmvsy3VNtTf_V9EHNeQ","7v-k2b21f_Xuf-3329jFyw","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","--q8cwZVXbHL2zOM_p3RlQ","yaTrLhUSIq2WitrTHLBy3Q","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAJcs","W8AFtEsepzrJ6AasHrCttwAAAAAAAKrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAALAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAIdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEDXA","kSaNXrGzSS3BnDNNWezzMAAAAAAAABCa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDEL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","xNMiNBkMujk7ZnRv0OEjrQAAAAAAAAu8","MYrgKQIxdDhr1gdpucfc-QAAAAAAAN-q","un9fLDZOLvDMO52ltZtuegAAAAAAAGsM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","grikUXlisBLUbeL_OWixIwAAAAAAAPZs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","oERZXsH8EPeoSRxNNaSWfQAAAAAAADdy","gMhgHDYSMmyInNJ15VwYFgAAAAAAAPvy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","rTFMSHhLRlj86vHPR06zoQAAAAAAAEfG","oArGmvsy3VNtTf_V9EHNeQAAAAAAAKNy","7v-k2b21f_Xuf-3329jFywAAAAAAAIY8","FqNqtF0e0OG1VJJtWE9clwAAAAAAADeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","GEIvPhvjHWZLHz2BksVgvAAAAAAAAGBA","FqNqtF0e0OG1VJJtWE9clwAAAAAAADeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEJaY","--q8cwZVXbHL2zOM_p3RlQAAAAAAAFNo","yaTrLhUSIq2WitrTHLBy3QAAAAAAAGcY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJzys","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHSHZ"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,1,3,3]},"oKVObqTWF9QIjxgKf8UkTw":{"address_or_lines":[4201744,135481,4208244,4207404,2599636,1091600,51328,2795776,1483241,1482767,2600004,1079483,27726,29268,38054,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,27726,29784,2736,41284,2578675,2599636,1091600,50170,2795776,1483241,1482767,2600004,1074397,27726,29784,2736,41284,2578675,2599636,1091600,13752,2795776,1483241,1482767,2600004,1079483,27726,29268,38054,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,27726,29784,2736,41068,49494,4746,19187,41141,49404],"file_ids":["SbPwzb_Kog2bWn8uc7xhDQ","Z_CHd3Zjsh2cWE2NSdbiNQ","SbPwzb_Kog2bWn8uc7xhDQ","SbPwzb_Kog2bWn8uc7xhDQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","DTRaillMS4wmG2CDEfm9rQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","08Dc0vnMK9C_nl7yQB6ZKQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","zuPG_tF81PcJTwjfBwKlDg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["SbPwzb_Kog2bWn8uc7xhDQAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDZ0","SbPwzb_Kog2bWn8uc7xhDQAAAAAAQDMs","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","DTRaillMS4wmG2CDEfm9rQAAAAAAAMiA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAJSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAKFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","08Dc0vnMK9C_nl7yQB6ZKQAAAAAAAMP6","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAKFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","zuPG_tF81PcJTwjfBwKlDgAAAAAAADW4","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAJSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAKBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAMFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAABKK","ASi9f26ltguiwFajNwOaZwAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKC1","jaBVtokSUzfS97d-XKjijgAAAAAAAMD8"],"type_ids":[3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"rsb7cL4OAenBHrp0F_Wcgg":{"address_or_lines":[30070,2795776,1483241,1482767,2600004,1074397,48206,50264,23216,33092,2578675,2599636,1091600,1150,2795776,1483241,1482767,2600004,1074397,48206,50264,23216,33092,2578675,2599636,1091600,47798,2795776,1483241,1482767,2600004,1074397,48206,50264,23216,33092,2578675,2599636,1091600,18886,2795776,1483241,1482767,2600004,1074397,48206,50264,23216,33092,2578675,2599636,1074397,51858,2586225,2600004,1055835,28542,1975041,2600004,1079669,52004,1480561,1940968,1917658,1481652,1480953,2600004,1057290,36296,2944663],"file_ids":["pv4wAezdMMO0SVuGgaEMTg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","qns5vQ3LMi6QrIMOgD_TwQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","J_Lkq1OzUHxWQhnTgF6FwA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","XkOSW26Xa6_lkqHv5givKg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","BuJIbGFo3xNyZaTAXvW1Ag","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","L9BMhx_jo5vrPGr_NYlXCQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","pZhbjLL2hYCcec5rSvEEGw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","kkqG_q7yucIGLE7ky-QX9A","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["pv4wAezdMMO0SVuGgaEMTgAAAAAAAHV2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAALxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAMRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAFqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","qns5vQ3LMi6QrIMOgD_TwQAAAAAAAAR-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAALxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAMRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAFqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","J_Lkq1OzUHxWQhnTgF6FwAAAAAAAALq2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAALxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAMRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAFqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","XkOSW26Xa6_lkqHv5givKgAAAAAAAEnG","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAALxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAMRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAFqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","BuJIbGFo3xNyZaTAXvW1AgAAAAAAAMqS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3Zx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEBxb","L9BMhx_jo5vrPGr_NYlXCQAAAAAAAG9-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHiMB","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","pZhbjLL2hYCcec5rSvEEGwAAAAAAAMsk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHULa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAECIK","kkqG_q7yucIGLE7ky-QX9AAAAAAAAI3I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALO6X"],"type_ids":[1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,1,3,3,3,1,3,3,3,3,3,3,3,1,3]},"mWVVBnqMHfG9pWtaZUm47Q":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,49540,1058,33388,19218,58614,61820,19828,11060,26096,1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,11498,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,56810,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,31598,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,17976,33110,51498,19187,41240,50348],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","LF6DFcGHEMqhhhlptO_M_Q","Af6E3BeG383JVVbu67NJ0Q","xwuAPHgc12-8PZB3i-320g","6WJ6x4R10ox82_e3Ea4eiA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","3HhVgGD2yvuFLpoZq7RfKw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uSWUCgHgLPG4OFtPdUp0rg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","dGWvVtQJJ5wuqNyQVpi8lA","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAOT2","LF6DFcGHEMqhhhlptO_M_QAAAAAAAPF8","Af6E3BeG383JVVbu67NJ0QAAAAAAAE10","xwuAPHgc12-8PZB3i-320gAAAAAAACs0","6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAACzq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","3HhVgGD2yvuFLpoZq7RfKwAAAAAAAN3q","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","uSWUCgHgLPG4OFtPdUp0rgAAAAAAAHtu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAMkq","dGWvVtQJJ5wuqNyQVpi8lAAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMSs"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"r1nqJ9JqsZyOKqlpBmuvLg":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,16772,50210,17004,2834,30508,27360,36874,1480209,1969795,1481300,1480601,2595076,1079144,18252,1480209,1969795,1481300,1480601,2595076,1062336,61594,1844695,1847563,1481567,2595076,1079485,3004,49066,11020,1479608,1493928,2595076,1079485,46700,1479608,1493928,2595076,1079485,63346,48114,1479608,2595076,1079485,10182,25458,17980,63380,1479516,2595076,1079485,16448,63380,1479516,2595076,1073749,13188,3118087,767068,768138,10485923,16807,2845274,2841596,3817899,3815886,3627192],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","xNMiNBkMujk7ZnRv0OEjrQ","MYrgKQIxdDhr1gdpucfc-Q","un9fLDZOLvDMO52ltZtueg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","grikUXlisBLUbeL_OWixIw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","rTFMSHhLRlj86vHPR06zoQ","oArGmvsy3VNtTf_V9EHNeQ","7v-k2b21f_Xuf-3329jFyw","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","--q8cwZVXbHL2zOM_p3RlQ","-Z7SlEXhuy5tL2BF-xmy3g","Z_CHd3Zjsh2cWE2NSdbiNQ","Z_CHd3Zjsh2cWE2NSdbiNQ","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAEGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAMQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAEJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAAsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAHcs","W8AFtEsepzrJ6AasHrCttwAAAAAAAGrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAJAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAEdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEDXA","kSaNXrGzSS3BnDNNWezzMAAAAAAAAPCa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDEL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","xNMiNBkMujk7ZnRv0OEjrQAAAAAAAAu8","MYrgKQIxdDhr1gdpucfc-QAAAAAAAL-q","un9fLDZOLvDMO52ltZtuegAAAAAAACsM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","grikUXlisBLUbeL_OWixIwAAAAAAALZs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","oERZXsH8EPeoSRxNNaSWfQAAAAAAAPdy","gMhgHDYSMmyInNJ15VwYFgAAAAAAALvy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","rTFMSHhLRlj86vHPR06zoQAAAAAAACfG","oArGmvsy3VNtTf_V9EHNeQAAAAAAAGNy","7v-k2b21f_Xuf-3329jFywAAAAAAAEY8","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","GEIvPhvjHWZLHz2BksVgvAAAAAAAAEBA","FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","--q8cwZVXbHL2zOM_p3RlQAAAAAAADOE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAL5QH","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAC7Rc","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAC7iK","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAK2pa","A2oiHVwisByxRn5RDT4LjAAAAAAAK1v8","A2oiHVwisByxRn5RDT4LjAAAAAAAOkGr","A2oiHVwisByxRn5RDT4LjAAAAAAAOjnO","A2oiHVwisByxRn5RDT4LjAAAAAAAN1i4"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,3,3,3,4,4,4,4,4,4,4]},"5MDEZjYH98Woy4iHbcvgDg":{"address_or_lines":[2573747,2594708,1091475,65190,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,22586,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,12514,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,25530,2790352,1482889,1482415,2595076,1073749,58094,60152,33136,34524,2573747,2594708,1091475,37170,2790352,1482889,1482415,2595076,1079144,58108,1481694,1493928,2595076,1080441,8392,15128,1480209,1827586,3439453,2746712,2738096],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MU3fJpOZe9TA4mzeo52wZg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","auEGiAr7C6IfT0eiHbOlyA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","mP9Tk3T74fjOyYWKUaqdMQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","I4X8AC1-B0GuL4JyYemPzw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","s6flibJ32CsA8wnq-j6RkQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","3EA5Wz2lIIw6eu5uv4gkTw","hjYcB64xHdoySaNOZ8xYqg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","MU3fJpOZe9TA4mzeo52wZgAAAAAAAP6m","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","auEGiAr7C6IfT0eiHbOlyAAAAAAAAFg6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","mP9Tk3T74fjOyYWKUaqdMQAAAAAAADDi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","I4X8AC1-B0GuL4JyYemPzwAAAAAAAGO6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAOLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","s6flibJ32CsA8wnq-j6RkQAAAAAAAJEy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","ik6PIX946fW_erE7uBJlVQAAAAAAAOL8","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHx5","3EA5Wz2lIIw6eu5uv4gkTwAAAAAAACDI","hjYcB64xHdoySaNOZ8xYqgAAAAAAADsY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-MC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAANHtd","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKelY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKcew"],"type_ids":[3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,3,1,1,3,3,3,3,3]},"WYRZ4mSdJHjsW8s2yoKnfA":{"address_or_lines":[1858,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,30594,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1091475,34158,2790352,1482889,1482415,2595076,1073749,37614,39672,12656,18140,2573747,2594708,1079144,56186,1481694,1828960,2581397,1480843,1480209,1940568,1917258,1481300,1480601,2595076,1079485,9718,1479772,1827586,1940195,1986609,1483518,1482415,1493679,2595076,1073425,15208,2566502,1844254,1972704,2595076,1071886,41592,1850963,1844695,1917599,1539319,3072295,1865140],"file_ids":["Gp9aOxUrrpSVBx4-ftlTOA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","y9R94bQUxts02WzRWfV7xg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uI6css-d8SGQRK6a_Ntl-A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","SlnkBp0IIJFLHVOe4KbxwQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uPGvGNXBf1JXGeeDSsmGQA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","PmtIuZrIdDPbhY30JCQRww","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","yos2k6ZH69vZXiBQV3d7cQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["Gp9aOxUrrpSVBx4-ftlTOAAAAAAAAAdC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","y9R94bQUxts02WzRWfV7xgAAAAAAAHeC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","uI6css-d8SGQRK6a_Ntl-AAAAAAAAIVu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAJLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4","J1eggTwSzYdi9OsSu1q37gAAAAAAADFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","SlnkBp0IIJFLHVOe4KbxwQAAAAAAANt6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2OV","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpiL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZxY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUFK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","uPGvGNXBf1JXGeeDSsmGQAAAAAAAACX2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpRc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-MC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZrj","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHlAx","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqL-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsqv","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGER","PmtIuZrIdDPbhY30JCQRwwAAAAAAADto","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJylm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCQe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHhng","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEFsO","yos2k6ZH69vZXiBQV3d7cQAAAAAAAKJ4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHD5T","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHUKf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAF3z3","-Z7SlEXhuy5tL2BF-xmy3gAAAAAALuEn","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHW0"],"type_ids":[1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,1,3,3,3,3,3,3]},"C4ItszXjQjtRADEg560AUw":{"address_or_lines":[4201744,135481,4208244,4207404,2594708,1079144,388,33826,620,51986,30508,10976,36874,1480209,1969795,1481300,1480601,2595076,1079144,1868,1480209,1969795,1481300,1480601,2595076,1062336,61594,1844695,1847563,1481567,2595076,1079485,35772,49066,60172,1479608,1493928,2595076,1079485,30316,1479608,1493928,2595076,1079485,30578,15346,1479608,2595076,1079485,10678,9074,1596,46996,1479516,2595076,1079485,16448,46996,1479516,2595076,1073749,13088,6410,24756,3150002,920932,10485923,16807,2776792,2775330,2826677,2809533,2807255,2804657,2869654],"file_ids":["WpYcHtr4qx88B8CBJZ2GTw","Z_CHd3Zjsh2cWE2NSdbiNQ","WpYcHtr4qx88B8CBJZ2GTw","WpYcHtr4qx88B8CBJZ2GTw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","DTRaillMS4wmG2CDEfm9rQ","U4Le8nh-beog_B7jq7uTIA","CqoTgn4VUlwTNyUw7wsMHQ","SjQZVYGLzro7G-9yPjVJlg","grZNsSElR5ITq8H2yHCNSw","W8AFtEsepzrJ6AasHrCttw","sur1OQS0yB3u_A1ZgjRjFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EFJHOn-GACfHXgae-R1yDA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","kSaNXrGzSS3BnDNNWezzMA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","xNMiNBkMujk7ZnRv0OEjrQ","MYrgKQIxdDhr1gdpucfc-Q","un9fLDZOLvDMO52ltZtueg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","grikUXlisBLUbeL_OWixIw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","oERZXsH8EPeoSRxNNaSWfQ","gMhgHDYSMmyInNJ15VwYFg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","rTFMSHhLRlj86vHPR06zoQ","oArGmvsy3VNtTf_V9EHNeQ","7v-k2b21f_Xuf-3329jFyw","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GEIvPhvjHWZLHz2BksVgvA","FqNqtF0e0OG1VJJtWE9clw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","--q8cwZVXbHL2zOM_p3RlQ","wXOyVgf5_nNg6CUH5kFBbg","zEgDK4qMawUAQZjg5YHyww","-Z7SlEXhuy5tL2BF-xmy3g","Z_CHd3Zjsh2cWE2NSdbiNQ","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["WpYcHtr4qx88B8CBJZ2GTwAAAAAAQB0Q","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDZ0","WpYcHtr4qx88B8CBJZ2GTwAAAAAAQDMs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE","U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi","CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs","SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS","grZNsSElR5ITq8H2yHCNSwAAAAAAAHcs","W8AFtEsepzrJ6AasHrCttwAAAAAAACrg","sur1OQS0yB3u_A1ZgjRjFgAAAAAAAJAK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","EFJHOn-GACfHXgae-R1yDAAAAAAAAAdM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg6D","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEDXA","kSaNXrGzSS3BnDNNWezzMAAAAAAAAPCa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHCXX","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHDEL","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFptf","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","xNMiNBkMujk7ZnRv0OEjrQAAAAAAAIu8","MYrgKQIxdDhr1gdpucfc-QAAAAAAAL-q","un9fLDZOLvDMO52ltZtuegAAAAAAAOsM","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","grikUXlisBLUbeL_OWixIwAAAAAAAHZs","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsuo","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","oERZXsH8EPeoSRxNNaSWfQAAAAAAAHdy","gMhgHDYSMmyInNJ15VwYFgAAAAAAADvy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpO4","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","rTFMSHhLRlj86vHPR06zoQAAAAAAACm2","oArGmvsy3VNtTf_V9EHNeQAAAAAAACNy","7v-k2b21f_Xuf-3329jFywAAAAAAAAY8","FqNqtF0e0OG1VJJtWE9clwAAAAAAALeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","GEIvPhvjHWZLHz2BksVgvAAAAAAAAEBA","FqNqtF0e0OG1VJJtWE9clwAAAAAAALeU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","--q8cwZVXbHL2zOM_p3RlQAAAAAAADMg","wXOyVgf5_nNg6CUH5kFBbgAAAAAAABkK","zEgDK4qMawUAQZjg5YHywwAAAAAAAGC0","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAMBCy","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAADg1k","A2oiHVwisByxRn5RDT4LjAAAAAAAoACj","A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn","A2oiHVwisByxRn5RDT4LjAAAAAAAKl7Y","A2oiHVwisByxRn5RDT4LjAAAAAAAKlki","A2oiHVwisByxRn5RDT4LjAAAAAAAKyG1","A2oiHVwisByxRn5RDT4LjAAAAAAAKt69","A2oiHVwisByxRn5RDT4LjAAAAAAAKtXX","A2oiHVwisByxRn5RDT4LjAAAAAAAKsux","A2oiHVwisByxRn5RDT4LjAAAAAAAK8mW"],"type_ids":[3,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,3,3,3,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,1,3,3,3,3,1,1,3,3,3,1,1,1,1,3,3,3,1,1,3,3,3,1,1,1,3,3,4,4,4,4,4,4,4,4,4]},"8IBqDIuSolkkEHIjO_CfMw":{"address_or_lines":[1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,57338,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,46806,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,4702,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,25478,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1079144,57700,1481694,1828960,2580566,1480601,1493679,2595076,1052274,37402,1973088,2595076,1059438,7162],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","VY0EiAO0DxwLRTE4PfFhdw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2AkHKX3hFovQqnWGTZG4BA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","JEYMXKhPKBKP90oNIKO6Ww","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Fq3uvTWKo9OreZfu-LOYYQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","f2CfX6aaJGZ4Su3cCY2vCQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","yxUFWTEZsQP-FeNV2RKnFQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Q2lceMFM0t8w5Hdokg8e8A"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","VY0EiAO0DxwLRTE4PfFhdwAAAAAAAN_6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2AkHKX3hFovQqnWGTZG4BAAAAAAAALbW","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","JEYMXKhPKBKP90oNIKO6WwAAAAAAABJe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","Fq3uvTWKo9OreZfu-LOYYQAAAAAAAGOG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","f2CfX6aaJGZ4Su3cCY2vCQAAAAAAAOFk","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2BW","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFsqv","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEA5y","yxUFWTEZsQP-FeNV2RKnFQAAAAAAAJIa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHhtg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAECpu","Q2lceMFM0t8w5Hdokg8e8AAAAAAAABv6"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,3,3,1,3,3,3,1]},"T2hqeT_yirkauwcO1cGJEw":{"address_or_lines":[74,6,18,8,18,80,24,4,84,38,174,104,68,116,38,174,104,68,4,38,174,104,68,96,38,174,104,68,60,38,38,10,38,174,104,68,124,38,174,104,68,124,38,174,104,68,100,140,10,38,174,104,68,76,38,174,34,24,10,10,786829,1091612,1986900,1997206,2238455,4240,5748,1213299,4101,76200,1213299,77535,52678,1213299,52081,33630,106222],"file_ids":["a5aMcPOeWx28QSVng73nBQ","inI9W0bfekFTCpu0ceKTHg","RPwdw40HEBL87wRkKV2ozw","pT2bgvKv3bKR6LMAYtKFRw","Rsr7q4vCSh2ppRtyNkwZAA","cKQfWSgZRgu_1Goz5QGSHw","T2fhmP8acUvRZslK7YRDPw","lrxXzNEmAlflj7bCNDjxdA","SMoSw8cr-PdrIATvljOPrQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","xaCec3W8F6xlvd_EISI7vw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","QCNrAtEDVSYrGKsToy3LYA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ocuGLNOciiOP6W8cfH2-qw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","bjI4Jot-SXYwqfMr0sl7Xg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjBJSIgrJ7WBnrV9WxdKEQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9-_Y7FNFlkawnHBUI4HVnA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","suQJt7m9qyZP3i8d45HwBQ","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5w2Emmm2pdiPFBnzFSNcKg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","1bzyoH1Mbbzc-oKA3fR-7Q","BXKFYOU6E7YaW5MDpfBf8w","zP58DjIs7uq1cghmzykyNA","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","PVZV2uq5ZRt-FFaczL10BA","PVZV2uq5ZRt-FFaczL10BA","Z_CHd3Zjsh2cWE2NSdbiNQ","PVZV2uq5ZRt-FFaczL10BA","3nN3bymnZ8E42aLEtgglmA","Z_CHd3Zjsh2cWE2NSdbiNQ","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","Z_CHd3Zjsh2cWE2NSdbiNQ","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAABK","inI9W0bfekFTCpu0ceKTHgAAAAAAAAAG","RPwdw40HEBL87wRkKV2ozwAAAAAAAAAS","pT2bgvKv3bKR6LMAYtKFRwAAAAAAAAAI","Rsr7q4vCSh2ppRtyNkwZAAAAAAAAAAAS","cKQfWSgZRgu_1Goz5QGSHwAAAAAAAABQ","T2fhmP8acUvRZslK7YRDPwAAAAAAAAAY","lrxXzNEmAlflj7bCNDjxdAAAAAAAAAAE","SMoSw8cr-PdrIATvljOPrQAAAAAAAABU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","xaCec3W8F6xlvd_EISI7vwAAAAAAAAB0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","QCNrAtEDVSYrGKsToy3LYAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ocuGLNOciiOP6W8cfH2-qwAAAAAAAABg","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","bjI4Jot-SXYwqfMr0sl7XgAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjBJSIgrJ7WBnrV9WxdKEQAAAAAAAAB8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9-_Y7FNFlkawnHBUI4HVnAAAAAAAAAB8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","suQJt7m9qyZP3i8d45HwBQAAAAAAAABk","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5w2Emmm2pdiPFBnzFSNcKgAAAAAAAABM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAAAi","1bzyoH1Mbbzc-oKA3fR-7QAAAAAAAAAY","BXKFYOU6E7YaW5MDpfBf8wAAAAAAAAAK","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","G68hjsyagwq6LpWrMjDdngAAAAAADAGN","G68hjsyagwq6LpWrMjDdngAAAAAAEKgc","G68hjsyagwq6LpWrMjDdngAAAAAAHlFU","G68hjsyagwq6LpWrMjDdngAAAAAAHnmW","G68hjsyagwq6LpWrMjDdngAAAAAAIif3","PVZV2uq5ZRt-FFaczL10BAAAAAAAABCQ","PVZV2uq5ZRt-FFaczL10BAAAAAAAABZ0","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","PVZV2uq5ZRt-FFaczL10BAAAAAAAABAF","3nN3bymnZ8E42aLEtgglmAAAAAAAASmo","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","3nN3bymnZ8E42aLEtgglmAAAAAAAAS7f","3nN3bymnZ8E42aLEtgglmAAAAAAAAM3G","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","3nN3bymnZ8E42aLEtgglmAAAAAAAAMtx","3nN3bymnZ8E42aLEtgglmAAAAAAAAINe","3nN3bymnZ8E42aLEtgglmAAAAAAAAZ7u"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"OIXgOJgQPE-F5rS7DPPzZA":{"address_or_lines":[2795776,1483241,1482767,2600004,1079483,23630,25172,33958,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,20804,2578675,2599636,1091600,20658,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,20804,2578675,2599636,1091600,0,2795776,1483241,1482767,2600004,1074397,23630,25688,64176,20804,2578675,2599636,1079669,0,1482046,1829360,2586225,2600004,1079669,36060,1482046,1829360,2586325,1481195,1480561,1940968,1917658,1481652,1480953,2600004,1079483,61874,1480124,1827986,1940595,1989057,1480953,1494106,2600004,1073803,20418,2569666],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","93AmMdBRQTTNSFcMQ_Ywdg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","_____________________w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","_____________________w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","29RxCcCS3qayH8Wz47EBXQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","mBpjyQvq6ftE7Wm1BUpcFg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","IWme5rHQfgYd-9YstXSeGA","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGJU","eV_m28NnKeeTL60KO2H3SAAAAAAAAISm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","93AmMdBRQTTNSFcMQ_YwdgAAAAAAAFCy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","_____________________wAAAAAAAAAA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","_____________________wAAAAAAAAAA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3Zx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","29RxCcCS3qayH8Wz47EBXQAAAAAAAIzc","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3bV","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpnr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ3o","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHULa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","mBpjyQvq6ftE7Wm1BUpcFgAAAAAAAPGy","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpW8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-SS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZxz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHlnB","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFsxa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGKL","IWme5rHQfgYd-9YstXSeGAAAAAAAAE_C","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJzXC"],"type_ids":[3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,1,3]},"i0e78nPZCZ2CbzzLMEOcMw":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,38,10,38,38,10,38,174,104,14,32,190,1091944,2047231,2046923,2044755,2041537,2044733,2042086,2025366,954962],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAAC-","G68hjsyagwq6LpWrMjDdngAAAAAAEKlo","G68hjsyagwq6LpWrMjDdngAAAAAAHzz_","G68hjsyagwq6LpWrMjDdngAAAAAAHzvL","G68hjsyagwq6LpWrMjDdngAAAAAAHzNT","G68hjsyagwq6LpWrMjDdngAAAAAAHybB","G68hjsyagwq6LpWrMjDdngAAAAAAHzM9","G68hjsyagwq6LpWrMjDdngAAAAAAHyjm","G68hjsyagwq6LpWrMjDdngAAAAAAHueW","G68hjsyagwq6LpWrMjDdngAAAAAADpJS"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3]},"34DMF2kw8Djh_MjcdchMzw":{"address_or_lines":[2795776,1483241,1482767,2600004,1074397,31822,33880,6832,33092,2578675,2599636,1091600,34914,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,33092,2578675,2599636,1091600,7430,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,33092,2578675,2599636,1091600,3230,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,33092,2578675,2599636,1091600,61846,2795776,1483241,1482767,2600004,1074397,31822,33880,6832,33092,2578675,2599636,1079669,38686,1482046,1829360,2586225,2600004,1079669,15794,56134,43516,45442,36964,61672,47980,1480561,1940984,1479155],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","y4VaggFtn5eGbiM4h45zCg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","aovhV1VhdNHhPwAmk_rOhg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","px3SfTg4DYOeiT_Yemty2w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","opI8K6Q9RBhmYCrRVwNTgA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","cVEUVwL4zVVcM9r_4PTCXA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","GGxNFCJdZtgXLG8zgUfn_Q","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","y4VaggFtn5eGbiM4h45zCgAAAAAAAIhi","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","aovhV1VhdNHhPwAmk_rOhgAAAAAAAB0G","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","px3SfTg4DYOeiT_Yemty2wAAAAAAAAye","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","opI8K6Q9RBhmYCrRVwNTgAAAAAAAAPGW","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY","J1eggTwSzYdi9OsSu1q37gAAAAAAABqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","cVEUVwL4zVVcM9r_4PTCXAAAAAAAAJce","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3Zx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","GGxNFCJdZtgXLG8zgUfn_QAAAAAAAD2y","jtp3NDFNJGnK6sK5oOFo8QAAAAAAANtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAKn8","_lF8o5tJDcePvza_IYtgSQAAAAAAALGC","TRd7r6mvdzYdjMdTtebtwwAAAAAAAJBk","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAPDo","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALts","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ34","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpHz"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,1,3,3,3]},"XG9tjujXJl2nWpbHppoRMA":{"address_or_lines":[2573747,2594708,1091475,39286,2790352,1482889,1482415,2595076,1079485,29422,30964,39782,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,10978,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,35610,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,10138,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,58142,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,17976,33110,47402,19187,41240,50602],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ZVYMRqiL5oPAMqs8XcON8Q","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","V6gUZHzBRISi-Z25klK5DQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zWNEoAKVTnnzSns045VKhw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","n4Ao4OZE2osF0FygfcWo3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","1y9WuJpjgBMcQb3shY5phQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","dGWvVtQJJ5wuqNyQVpi8lA","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","ZVYMRqiL5oPAMqs8XcON8QAAAAAAAJl2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHj0","eV_m28NnKeeTL60KO2H3SAAAAAAAAJtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","V6gUZHzBRISi-Z25klK5DQAAAAAAACri","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zWNEoAKVTnnzSns045VKhwAAAAAAAIsa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","n4Ao4OZE2osF0FygfcWo3gAAAAAAACea","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","1y9WuJpjgBMcQb3shY5phQAAAAAAAOMe","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAALkq","dGWvVtQJJ5wuqNyQVpi8lAAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMWq"],"type_ids":[3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"SrSwvDbs2pmPg3SRfXJBCA":{"address_or_lines":[1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,10978,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,35610,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,11318,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,15678,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,250,2790352,1482889,1482415,2595076,1076587,29422,31480,4464,17976,33110,51586,2846655,2846347,2843929,2840766,2843907,2841214,1439462],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","V6gUZHzBRISi-Z25klK5DQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zWNEoAKVTnnzSns045VKhw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","n4Ao4OZE2osF0FygfcWo3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","NGbZlnLCqeq3LFq89r_SpQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","PmhxUKv5sePRxhCBONca8g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","V6gUZHzBRISi-Z25klK5DQAAAAAAACri","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zWNEoAKVTnnzSns045VKhwAAAAAAAIsa","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","n4Ao4OZE2osF0FygfcWo3gAAAAAAACw2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","NGbZlnLCqeq3LFq89r_SpQAAAAAAAD0-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","PmhxUKv5sePRxhCBONca8gAAAAAAAAD6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAMmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UD","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1p-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFfbm"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3]},"bcNRMcXtTRgNPl4vy6M5KQ":{"address_or_lines":[2573747,2594708,1091475,48050,2789627,1482889,1482415,2595076,1079485,29808,43878,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,47414,2789627,1482889,1482415,2595076,1079485,29808,43878,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,21414,2790352,1482889,1482415,2595076,1073749,33518,35576,8560,18140,2573747,2594708,1091475,12682,2790352,1482889,1482415,2595076,1076587,33518,35576,8560,17976,49494,55682,2846655,2846347,2843929,2840766,2843929,2840766,2843954,2840766,2841312],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","xDXQtI2vA5YySwpx7QFiwA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fSQ747oLNh0c0zFQjsVRWg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","yp8MidCGMe4czbl-NigsYQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2noK4QoWxdzASRHkjOFwVA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","xDXQtI2vA5YySwpx7QFiwAAAAAAAALuy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAHRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAKtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","fSQ747oLNh0c0zFQjsVRWgAAAAAAALk2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAHRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAKtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","yp8MidCGMe4czbl-NigsYQAAAAAAAFOm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2noK4QoWxdzASRHkjOFwVAAAAAAAADGK","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAAILu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4","J1eggTwSzYdi9OsSu1q37gAAAAAAACFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAMFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAANmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2Uy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1rg"],"type_ids":[3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3]},"XmiUdMqa5OViUnHQ_LS4Uw":{"address_or_lines":[61654,2795051,1483241,1482767,2600004,1079483,32208,46246,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,324,2578675,2599636,1091600,61890,2795051,1483241,1482767,2600004,1079483,32208,46246,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,324,2578675,2599636,1091600,27010,2795051,1483241,1482767,2600004,1079483,32208,46246,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,324,2578675,2599636,1091600,2254,2795776,1483241,1482767,2600004,1074397,35918,37976,10928,108,49494,29322,19187,41240,50348],"file_ids":["mfGJjedIJMvFXgX3QuTMfQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","9NWoah56eYULAP_zGE9Puw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","IKrIDHd5n47PpDQsRXxvvg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","oG7568kMJujZxPJfj7VMjA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["mfGJjedIJMvFXgX3QuTMfQAAAAAAAPDW","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAALSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","9NWoah56eYULAP_zGE9PuwAAAAAAAPHC","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAALSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","IKrIDHd5n47PpDQsRXxvvgAAAAAAAGmC","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAALSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","oG7568kMJujZxPJfj7VMjAAAAAAAAAjO","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY","J1eggTwSzYdi9OsSu1q37gAAAAAAACqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAABs","p5XvqZgoydjTl8thPo5KGwAAAAAAAMFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAHKK","ASi9f26ltguiwFajNwOaZwAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMSs"],"type_ids":[1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"3odHGojcaqq4ImPnmLLSzw":{"address_or_lines":[1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1091600,43246,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1091600,17846,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1091600,13950,2795051,1483241,1482767,2600004,1079483,60880,9382,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,64590,1112,39600,28996,2578675,2599636,1079669,4762,1482046,1829360,2586225,2600004,1079669,34130,1480561,1941045,1970515,1481652,1480953,2600004,1069341,25906,23366,39420,41384,9542,10212,11330,8962,13084,1693331,1865533],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","HENgRXYeEs7mDD8Gk_MNmg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fFS0upy5lIaT99RhlTN5LQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","lSdGU4igLMOpLhL_6XP15w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","QAp_Nt6XUeNsCXnAUgW7Xg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","20O937106XMbOD0LQR4SPw","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","gPzb0fXoBe1225fbKepMRA","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","OHQX9IWLaZElAgxGbX3P5g","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","JrU1PwRIxl_8SXdnTESnog","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","HENgRXYeEs7mDD8Gk_MNmgAAAAAAAKju","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","fFS0upy5lIaT99RhlTN5LQAAAAAAAEW2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","lSdGU4igLMOpLhL_6XP15wAAAAAAADZ-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAO3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAACSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY","J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","QAp_Nt6XUeNsCXnAUgW7XgAAAAAAABKa","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ3Zx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","20O937106XMbOD0LQR4SPwAAAAAAAIVS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpdx","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHZ41","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHhFT","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpu0","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpj5","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEFEd","gPzb0fXoBe1225fbKepMRAAAAAAAAGUy","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAFtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAJn8","_lF8o5tJDcePvza_IYtgSQAAAAAAAKGo","OHQX9IWLaZElAgxGbX3P5gAAAAAAACVG","E2b-mzlh_8261-JxcySn-AAAAAAAACfk","E2b-mzlh_8261-JxcySn-AAAAAAAACxC","E2b-mzlh_8261-JxcySn-AAAAAAAACMC","JrU1PwRIxl_8SXdnTESnogAAAAAAADMc","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAGdaT","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHHc9"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3]},"bRKRM4i4-XY2LCfN18mOow":{"address_or_lines":[1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,32078,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,9638,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1091475,5742,2789627,1482889,1482415,2595076,1079485,25712,39782,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,29422,31480,4464,18140,2573747,2594708,1079144,37050,1481694,1828960,2581297,2595076,1079144,25922,1480209,1940645,1970099,1481300,1480601,2595076,1052274,41714,56134,54428,53864,42310,53828,54946,52578,59942,1429990,1365958,1365461],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","HENgRXYeEs7mDD8Gk_MNmg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","fFS0upy5lIaT99RhlTN5LQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","lSdGU4igLMOpLhL_6XP15w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","QAp_Nt6XUeNsCXnAUgW7Xg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","20O937106XMbOD0LQR4SPw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","gPzb0fXoBe1225fbKepMRA","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","OHQX9IWLaZElAgxGbX3P5g","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","E2b-mzlh_8261-JxcySn-A","JrU1PwRIxl_8SXdnTESnog","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","HENgRXYeEs7mDD8Gk_MNmgAAAAAAAH1O","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","fFS0upy5lIaT99RhlTN5LQAAAAAAACWm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","lSdGU4igLMOpLhL_6XP15wAAAAAAABZu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAAGRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAJtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAHLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4","J1eggTwSzYdi9OsSu1q37gAAAAAAABFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","QAp_Nt6XUeNsCXnAUgW7XgAAAAAAAJC6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ2Mx","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHdo","20O937106XMbOD0LQR4SPwAAAAAAAGVC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpYR","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHZyl","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHg-z","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFppU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpeZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEA5y","gPzb0fXoBe1225fbKepMRAAAAAAAAKLy","jtp3NDFNJGnK6sK5oOFo8QAAAAAAANtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAANSc","_lF8o5tJDcePvza_IYtgSQAAAAAAANJo","OHQX9IWLaZElAgxGbX3P5gAAAAAAAKVG","E2b-mzlh_8261-JxcySn-AAAAAAAANJE","E2b-mzlh_8261-JxcySn-AAAAAAAANai","E2b-mzlh_8261-JxcySn-AAAAAAAAM1i","JrU1PwRIxl_8SXdnTESnogAAAAAAAOom","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFdHm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFNfG","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFNXV"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3]},"W936jUeelyxTrQQ2V9mn-w":{"address_or_lines":[1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,59834,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,60574,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,64656,2790352,1482889,1482415,2595076,1079485,13038,14580,23398,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,13038,15096,53616,1756,2573747,2594708,1091475,42430,2790352,1482889,1482415,2595076,1076587,13038,15096,53616,1592,16726,47490,2846655,2846347,2843929,2840766,2843929,2840766,2843929,2840766,2840766,2842897,2268402,1775000,1761295,1048381],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zWCVT22bUHN0NWIQIBSuKg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","zj3hc8VBXxWxcbGVwJZYLA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","EHb2BWbkIivImSAfaUtw-A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-7Nhzq0bVRejx7IVqpbbZQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zWCVT22bUHN0NWIQIBSuKgAAAAAAAOm6","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","zj3hc8VBXxWxcbGVwJZYLAAAAAAAAOye","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","EHb2BWbkIivImSAfaUtw-AAAAAAAAPyQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADj0","eV_m28NnKeeTL60KO2H3SAAAAAAAAFtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","-7Nhzq0bVRejx7IVqpbbZQAAAAAAAKW-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAADLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4","J1eggTwSzYdi9OsSu1q37gAAAAAAANFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAEFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAALmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2ER","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAIpzy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGxWY","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAGuAP","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAD_89"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"AlH3zgnqwh5sdMMzX8AXxg":{"address_or_lines":[1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,52130,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,61558,2790352,1482889,1482415,2595076,1079485,25326,26868,35686,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,8770,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1091475,17970,2790352,1482889,1482415,2595076,1073749,25326,27384,368,1756,2573747,2594708,1066158,3868,39750,21660,21058,64084,29144,22318,29144,18030,1840882,1970521,2595076,1049850,1910],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","OlTvyWQFXjOweJcs3kiGyg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N2mxDWkAZe8CHgZMQpxZ7A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","1eW8DnM19kiBGqMWGVkHPA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2kgk5qEgdkkSXT9cIdjqxQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MsEmysGbXhMvgdbwhcZDCg","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Gxt7_MN7XgUOe9547JcHVQ"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","OlTvyWQFXjOweJcs3kiGygAAAAAAAMui","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAPB2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGj0","eV_m28NnKeeTL60KO2H3SAAAAAAAAItm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","1eW8DnM19kiBGqMWGVkHPAAAAAAAACJC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2kgk5qEgdkkSXT9cIdjqxQAAAAAAAEYy","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAGLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEESu","MsEmysGbXhMvgdbwhcZDCgAAAAAAAA8c","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAAFSc","_lF8o5tJDcePvza_IYtgSQAAAAAAAFJC","TRd7r6mvdzYdjMdTtebtwwAAAAAAAPpU","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAHHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAFcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAAHHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAEZu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHBby","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHhFZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEAT6","Gxt7_MN7XgUOe9547JcHVQAAAAAAAAd2"],"type_ids":[3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,1,1,1,1,1,1,3,3,3,3,1]},"YHwQa4NMDpWa9cokfF0xqw":{"address_or_lines":[2795776,1483241,1482767,2600004,1074397,19534,21592,60080,4420,2578675,2599636,1091600,35162,2795051,1483241,1482767,2600004,1079483,15824,29862,1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,4420,2578675,2599636,1091600,62314,2795776,1483241,1482767,2600004,1079669,19534,21418,26368,41208,8202,42532,1482046,1829983,2572841,1848805,1978934,1481919,1494280,2600004,1079669,55198,34238,39164,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,4420,2578675,2599636,1091600,55698,2795776,1483241,1482767,2600004,1074397,19534,21592,60080,4204,33110,33418,19187,41240,50763],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","2L4SW1rQgEVXRj3pZAI3nQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","Bd3XiVd_ucXTo7t4NwSjLA","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","7bd6QJSfWZZfOOpDMHqLMA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","ZPxtkRXufuVf4tqV5k5k2Q","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fj70ljef7nDHOqVJGSIoEQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","p5XvqZgoydjTl8thPo5KGw","oR5jBuG11Az1rZkKaPBmAg","ASi9f26ltguiwFajNwOaZw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","2L4SW1rQgEVXRj3pZAI3nQAAAAAAAIla","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqYr","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q","eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","7bd6QJSfWZZfOOpDMHqLMAAAAAAAAPNq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFOq","ZPxtkRXufuVf4tqV5k5k2QAAAAAAAGcA","8R2Lkqe-tYqq-plJ22QNzAAAAAAAAKD4","h0l-9tGi18mC40qpcJbyDwAAAAAAACAK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAAKYk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-xf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0Ip","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDXl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHjI2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpy_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","705jmHYNd7I4Z4L4c0vfiAAAAAAAANee","TBeSzkyqIwKL8td602zDjAAAAAAAAIW-","NH3zvSjFAfTSy6bEocpNyQAAAAAAAJj8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","fj70ljef7nDHOqVJGSIoEQAAAAAAANmS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAAExO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY","J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAABBs","p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAIKK","ASi9f26ltguiwFajNwOaZwAAAAAAAErz","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMZL"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3]},"AlRn0MJA_RCD0pN2OpIRZA":{"address_or_lines":[1481694,1828960,2567559,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,11962,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,59882,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,31598,2790352,1482889,1482415,2595076,1073749,17134,19192,57712,1756,2573747,2594708,1091475,28926,2789627,1482889,1482415,2595076,1079485,13424,27494,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1076587,17134,19192,57712,1592,33110,51586,2846655,2846347,2843929,2840766,2843929,2840766,2843907,2841214,1439429,1865241,10489950,423063,2283967,2281521,8542303],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","GP7h96O0_ppGVtc-UpQQIQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","3HhVgGD2yvuFLpoZq7RfKw","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","uSWUCgHgLPG4OFtPdUp0rg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-BjW54fwMksXBor9R-YN9w","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","Npep8JfxWDWZ3roJSD7jPg","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","M_-aGo2vWhLu7lS5grLv9w","oR5jBuG11Az1rZkKaPBmAg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA","A2oiHVwisByxRn5RDT4LjA"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpve","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","GP7h96O0_ppGVtc-UpQQIQAAAAAAAC66","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","3HhVgGD2yvuFLpoZq7RfKwAAAAAAAOnq","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","uSWUCgHgLPG4OFtPdUp0rgAAAAAAAHtu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","-BjW54fwMksXBor9R-YN9wAAAAAAAHD-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpD7","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","Npep8JfxWDWZ3roJSD7jPgAAAAAAADRw","eV_m28NnKeeTL60KO2H3SAAAAAAAAGtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEG1r","ik6PIX946fW_erE7uBJlVQAAAAAAAELu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4","M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW","oR5jBuG11Az1rZkKaPBmAgAAAAAAAMmC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2-_","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK26L","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1i-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK2UD","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAK1p-","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFfbF","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHHYZ","A2oiHVwisByxRn5RDT4LjAAAAAAAoBBe","A2oiHVwisByxRn5RDT4LjAAAAAAABnSX","A2oiHVwisByxRn5RDT4LjAAAAAAAItm_","A2oiHVwisByxRn5RDT4LjAAAAAAAItAx","A2oiHVwisByxRn5RDT4LjAAAAAAAglhf"],"type_ids":[3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,3,3,3,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4]},"inhNt-Ftru1dLAPaXB98Gw":{"address_or_lines":[2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,8722,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,20598,2790352,1482889,1482415,2595076,1079485,62190,63732,7014,1479516,1828960,2567559,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,25154,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1091475,40098,2790352,1482889,1482415,2595076,1073749,62190,64248,37232,50908,2573747,2594708,1066158,25996,23366,46236,45634,23124,53720,46894,53720,46894,53720,46894,53720,42606,1840882,1970521,2594999,2587827],"file_ids":["-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","OlTvyWQFXjOweJcs3kiGyg","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","N2mxDWkAZe8CHgZMQpxZ7A","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","eV_m28NnKeeTL60KO2H3SA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","1eW8DnM19kiBGqMWGVkHPA","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","2kgk5qEgdkkSXT9cIdjqxQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","ik6PIX946fW_erE7uBJlVQ","r3Nzr2WeUwu3gjU4N-rWyA","J1eggTwSzYdi9OsSu1q37g","CNgPIV65Suq5GVbO7eJK7g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","MsEmysGbXhMvgdbwhcZDCg","jtp3NDFNJGnK6sK5oOFo8Q","7R-mHvx47pWvF_ng7rKpHw","_lF8o5tJDcePvza_IYtgSQ","TRd7r6mvdzYdjMdTtebtww","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","bgsqxCFBdtyNwHEAo-3p1w","5PnOjelHYJZ6ovJAXK5uiQ","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g","-Z7SlEXhuy5tL2BF-xmy3g"],"frame_ids":["-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","OlTvyWQFXjOweJcs3kiGygAAAAAAACIS","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAFB2","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEHi9","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPj0","eV_m28NnKeeTL60KO2H3SAAAAAAAABtm","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFpNc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAG-hg","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJy2H","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","1eW8DnM19kiBGqMWGVkHPAAAAAAAAGJC","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEKeT","2kgk5qEgdkkSXT9cIdjqxQAAAAAAAJyi","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAKpPQ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFqCJ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAFp6v","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5kE","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEGJV","ik6PIX946fW_erE7uBJlVQAAAAAAAPLu","r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4","J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw","CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ0Wz","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5eU","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAEESu","MsEmysGbXhMvgdbwhcZDCgAAAAAAAGWM","jtp3NDFNJGnK6sK5oOFo8QAAAAAAAFtG","7R-mHvx47pWvF_ng7rKpHwAAAAAAALSc","_lF8o5tJDcePvza_IYtgSQAAAAAAALJC","TRd7r6mvdzYdjMdTtebtwwAAAAAAAFpU","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu","bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY","5PnOjelHYJZ6ovJAXK5uiQAAAAAAAKZu","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHBby","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAHhFZ","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ5i3","-Z7SlEXhuy5tL2BF-xmy3gAAAAAAJ3yz"],"type_ids":[3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3]},"qaaAfLAUIerA8yhApFJRYQ":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,72,38,174,104,68,88,38,174,104,68,124,38,38,10,38,174,104,68,72,38,174,104,68,120,38,174,104,68,354,6,108,20,50,50,2970,50,2970,50,2970,50,684,1109029,956192],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","qkYSh95E1urNTie_gKbr7w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","V8ldXm9NGXsJ182jEHEsUw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","xVaa0cBWNcFeS-8zFezQgA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","UBINlIxj95Sa_x2_k5IddA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gRRk0W_9P4SGZLXFJ5KU8Q","VIK6i3XoO6nxn9WkNabugA","SGPpASrxkViIc4Sq7x-WYQ","9xG1GRY3A4PQMfXDNvrOxQ","cbxfeE2AkqKne6oKUxdB6g","aEZUIXI_cV9kZCa4-U1NsQ","MebnOxK5WOhP29sl19Jefw","aEZUIXI_cV9kZCa4-U1NsQ","MebnOxK5WOhP29sl19Jefw","aEZUIXI_cV9kZCa4-U1NsQ","MebnOxK5WOhP29sl19Jefw","aEZUIXI_cV9kZCa4-U1NsQ","MebnOxK5WOhP29sl19Jefw","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAABI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","qkYSh95E1urNTie_gKbr7wAAAAAAAABY","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","V8ldXm9NGXsJ182jEHEsUwAAAAAAAAB8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","xVaa0cBWNcFeS-8zFezQgAAAAAAAAABI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","UBINlIxj95Sa_x2_k5IddAAAAAAAAAB4","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gRRk0W_9P4SGZLXFJ5KU8QAAAAAAAAFi","VIK6i3XoO6nxn9WkNabugAAAAAAAAAAG","SGPpASrxkViIc4Sq7x-WYQAAAAAAAABs","9xG1GRY3A4PQMfXDNvrOxQAAAAAAAAAU","cbxfeE2AkqKne6oKUxdB6gAAAAAAAAAy","aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy","MebnOxK5WOhP29sl19JefwAAAAAAAAua","aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy","MebnOxK5WOhP29sl19JefwAAAAAAAAua","aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy","MebnOxK5WOhP29sl19JefwAAAAAAAAua","aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy","MebnOxK5WOhP29sl19JefwAAAAAAAAKs","G68hjsyagwq6LpWrMjDdngAAAAAAEOwl","G68hjsyagwq6LpWrMjDdngAAAAAADpcg"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3]},"cj3H8UtNXHeFFvSKCpbt_Q":{"address_or_lines":[1479868,1829360,2572487,2795776,1483241,1482767,2600004,1074397,7246,9304,47792,324,2578675,2599636,1091600,58218,2795776,1483241,1482767,2600004,1079669,7246,9130,14080,57592,61450,9764,1482046,1829983,2572841,1848805,1978934,1481919,1494280,2600004,1079669,22430,50622,6396,1482046,1829360,2572487,2795776,1483241,1482767,2600004,1074397,7246,9304,47792,324,2578675,2599636,1091600,51602,2795776,1483241,1482767,2600004,1074397,7246,9304,47792,324,2578675,2599636,1091600,62974,2795776,1483241,1482767,2600004,1079483,7246,9304,47608,55224,29888,17574,1479868,1829983,2783616,2800188,3063028,4240,5748,1213299,4101,76200,1213299,77886,46784,40082,38821],"file_ids":["xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","7bd6QJSfWZZfOOpDMHqLMA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","ZPxtkRXufuVf4tqV5k5k2Q","8R2Lkqe-tYqq-plJ22QNzA","h0l-9tGi18mC40qpcJbyDw","5EZV-eYYYtY-VAcSTmCvtg","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","705jmHYNd7I4Z4L4c0vfiA","TBeSzkyqIwKL8td602zDjA","NH3zvSjFAfTSy6bEocpNyQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","fj70ljef7nDHOqVJGSIoEQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","ywhwSu3fiEha0QwvHF6X9w","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","zo4mnjDJ1PlZka7jS9k2BA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","LEy-wm0GIvRoYVAga55Hiw","wdQNqQ99iFSdp4ceNJQKBg","J1eggTwSzYdi9OsSu1q37g","0S3htaCNkzxOYeavDR1GTQ","rBzW547V0L_mH4nnWK1FUQ","eV_m28NnKeeTL60KO2H3SA","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","xLxcEbwnZ5oNrk99ZsxcSQ","PVZV2uq5ZRt-FFaczL10BA","PVZV2uq5ZRt-FFaczL10BA","Z_CHd3Zjsh2cWE2NSdbiNQ","PVZV2uq5ZRt-FFaczL10BA","3nN3bymnZ8E42aLEtgglmA","Z_CHd3Zjsh2cWE2NSdbiNQ","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA","3nN3bymnZ8E42aLEtgglmA"],"frame_ids":["xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAABxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAACRY","J1eggTwSzYdi9OsSu1q37gAAAAAAALqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","7bd6QJSfWZZfOOpDMHqLMAAAAAAAAONq","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","LEy-wm0GIvRoYVAga55HiwAAAAAAABxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAACOq","ZPxtkRXufuVf4tqV5k5k2QAAAAAAADcA","8R2Lkqe-tYqq-plJ22QNzAAAAAAAAOD4","h0l-9tGi18mC40qpcJbyDwAAAAAAAPAK","5EZV-eYYYtY-VAcSTmCvtgAAAAAAACYk","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-xf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0Ip","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHDXl","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAHjI2","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpy_","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFs0I","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHl1","705jmHYNd7I4Z4L4c0vfiAAAAAAAAFee","TBeSzkyqIwKL8td602zDjAAAAAAAAMW-","NH3zvSjFAfTSy6bEocpNyQAAAAAAABj8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFp0-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-nw","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ0DH","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAABxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAACRY","J1eggTwSzYdi9OsSu1q37gAAAAAAALqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","fj70ljef7nDHOqVJGSIoEQAAAAAAAMmS","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEGTd","LEy-wm0GIvRoYVAga55HiwAAAAAAABxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAACRY","J1eggTwSzYdi9OsSu1q37gAAAAAAALqw","ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ1jz","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6rU","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEKgQ","zo4mnjDJ1PlZka7jS9k2BAAAAAAAAPX-","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKqkA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqHp","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFqAP","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAJ6xE","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAEHi7","LEy-wm0GIvRoYVAga55HiwAAAAAAABxO","wdQNqQ99iFSdp4ceNJQKBgAAAAAAACRY","J1eggTwSzYdi9OsSu1q37gAAAAAAALn4","0S3htaCNkzxOYeavDR1GTQAAAAAAANe4","rBzW547V0L_mH4nnWK1FUQAAAAAAAHTA","eV_m28NnKeeTL60KO2H3SAAAAAAAAESm","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAFpS8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAG-xf","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKnmA","xLxcEbwnZ5oNrk99ZsxcSQAAAAAAKro8","xLxcEbwnZ5oNrk99ZsxcSQAAAAAALrz0","PVZV2uq5ZRt-FFaczL10BAAAAAAAABCQ","PVZV2uq5ZRt-FFaczL10BAAAAAAAABZ0","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","PVZV2uq5ZRt-FFaczL10BAAAAAAAABAF","3nN3bymnZ8E42aLEtgglmAAAAAAAASmo","Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz","3nN3bymnZ8E42aLEtgglmAAAAAAAATA-","3nN3bymnZ8E42aLEtgglmAAAAAAAALbA","3nN3bymnZ8E42aLEtgglmAAAAAAAAJyS","3nN3bymnZ8E42aLEtgglmAAAAAAAAJel"],"type_ids":[3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,3,3,3,1,3,3,3,3,3,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]},"XT5dbBR70HCMmAkhladaCQ":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,212,38,174,104,68,228,38,174,104,68,4,38,174,104,68,92,38,174,104,68,8,38,174,104,68,44,38,38,10,38,174,104,68,4,38,174,104,68,40,38,174,104,68,68,38,38,10,38,174,104,68,4,38,174,104,14,32,166,1090933,19429,42789,49059],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","bAXCoU3-CU0WlRxl5l1tmw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","qordvIiilnF7CmkWCAd7eA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","iWpqwwcHV8E8OOnqGCYj9g","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","M61AJsljWf0TM7wD6IJVZw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ED3bhsHkhBwZ5ynmMnkPRA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","cZ-wyq9rmPl5QnqP0Smp6Q","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","GLV-c6bk0E-nhaaCp6u20w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","c_1Yb4rio2EAH6C9SFwQog","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","O4ILxZswquMzuET9RRf5QA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","G68hjsyagwq6LpWrMjDdng","EX9l-cE0x8X9W8uz4iKUfw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAADU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","bAXCoU3-CU0WlRxl5l1tmwAAAAAAAADk","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","qordvIiilnF7CmkWCAd7eAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","iWpqwwcHV8E8OOnqGCYj9gAAAAAAAABc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","M61AJsljWf0TM7wD6IJVZwAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ED3bhsHkhBwZ5ynmMnkPRAAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","cZ-wyq9rmPl5QnqP0Smp6QAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","GLV-c6bk0E-nhaaCp6u20wAAAAAAAAAo","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","c_1Yb4rio2EAH6C9SFwQogAAAAAAAABE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","O4ILxZswquMzuET9RRf5QAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAACm","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","EX9l-cE0x8X9W8uz4iKUfwAAAAAAAEvl","jaBVtokSUzfS97d-XKjijgAAAAAAAKcl","jaBVtokSUzfS97d-XKjijgAAAAAAAL-j"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3]},"Kfnso_5TQwyEGb1cfr-n5A":{"address_or_lines":[48,38,174,104,68,500,38,174,104,68,28,38,174,104,68,44,38,38,10,38,174,104,68,8,38,174,104,68,4,38,174,104,68,212,38,174,104,68,228,38,174,104,68,4,38,174,104,68,92,38,174,104,68,8,38,174,104,68,44,38,38,10,38,174,104,68,4,38,174,104,68,64,38,174,104,68,40,38,174,104,68,48,38,174,104,14,32,166,1090933,19429,41240,51098,10490014,423687,2280415,2277754,2506475,2411027,2395201],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","gZNrskHHFmNkCQ_HaCv8sA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","LUEJ1TSRGwRkHbcAyZ3RuQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","9h_0PKFtQeN0f7xWevHlTQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","nIG-LJ6Pj1PzNMyyppUoqg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ApbUUYSZlAYucbB88oZaGw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","bAXCoU3-CU0WlRxl5l1tmw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","qordvIiilnF7CmkWCAd7eA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","iWpqwwcHV8E8OOnqGCYj9g","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","M61AJsljWf0TM7wD6IJVZw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","ED3bhsHkhBwZ5ynmMnkPRA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","cZ-wyq9rmPl5QnqP0Smp6Q","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","GLV-c6bk0E-nhaaCp6u20w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rJZ4aC9w8bMvzrC0ApyIjg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","TC9v9fO0nTP4oypYCgB_1Q","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","NNy6Y3cHKuqblVbtSVjWfw","coeZ_4yf5sOePIKKlm8FNQ","G68hjsyagwq6LpWrMjDdng","EX9l-cE0x8X9W8uz4iKUfw","jaBVtokSUzfS97d-XKjijg","jaBVtokSUzfS97d-XKjijg","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw","piWSMQrh4r040D0BPNaJvw"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ApbUUYSZlAYucbB88oZaGwAAAAAAAADU","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","bAXCoU3-CU0WlRxl5l1tmwAAAAAAAADk","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","qordvIiilnF7CmkWCAd7eAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","iWpqwwcHV8E8OOnqGCYj9gAAAAAAAABc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","M61AJsljWf0TM7wD6IJVZwAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","ED3bhsHkhBwZ5ynmMnkPRAAAAAAAAAAs","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","cZ-wyq9rmPl5QnqP0Smp6QAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","GLV-c6bk0E-nhaaCp6u20wAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rJZ4aC9w8bMvzrC0ApyIjgAAAAAAAAAo","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","TC9v9fO0nTP4oypYCgB_1QAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO","NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg","coeZ_4yf5sOePIKKlm8FNQAAAAAAAACm","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","EX9l-cE0x8X9W8uz4iKUfwAAAAAAAEvl","jaBVtokSUzfS97d-XKjijgAAAAAAAKEY","jaBVtokSUzfS97d-XKjijgAAAAAAAMea","piWSMQrh4r040D0BPNaJvwAAAAAAoBCe","piWSMQrh4r040D0BPNaJvwAAAAAABncH","piWSMQrh4r040D0BPNaJvwAAAAAAIsvf","piWSMQrh4r040D0BPNaJvwAAAAAAIsF6","piWSMQrh4r040D0BPNaJvwAAAAAAJj7r","piWSMQrh4r040D0BPNaJvwAAAAAAJMoT","piWSMQrh4r040D0BPNaJvwAAAAAAJIxB"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,4]},"O3_UY4IxBGbcnXlHSqWz_w":{"address_or_lines":[48,38,174,104,68,60,38,174,104,68,64,38,174,104,68,20,140,10,38,174,104,68,28,38,38,10,38,174,104,68,12,38,174,104,68,4,38,174,104,68,12,38,174,104,68,156,38,174,104,68,48,140,10,38,174,104,68,16,38,138,138,16,100,12,4,6,4,38,174,104,68,8,38,174,104,68,32,38,174,104,68,24,140,10,38,174,104,68,210,1090933,1814182,788459,788130,1197048,1243927,788130,1197115,1198576,1948785,1941513],"file_ids":["a5aMcPOeWx28QSVng73nBQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","79pMuEW6_o55K0jHDJ-2dQ","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","mHiYHSEggclUi1ELZIxq4A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","_GLtmpX5QFDXCzO6KY35mA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","CF4TEudhKTIdEsoPP0l9iw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","5t_H28X3eSBfyQs-F2v7cA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","z0g3aE3w1Ik-suUArUsniA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","1VzILo0_Ivjn6dWL8BqT1A","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","rTTtzMEIQRrn8RDFEbl1zw","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","zjk1GYHhesH1oTuILj3ToA","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","r63cbyeLjspI6IMVvcBjIg","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","JaHOMfnX0DG4ZnNTpPORVA","MepUYc0jU0AjPrrjuvTgGg","yWt46REABLfKH6PXLAE18A","VQs3Erq77xz92EfpT8sTKw","n7IiY_TlCWEfi47-QpeCLw","Ua3frjTXWBuWpTsQD8aKeA","GtyMRLq4aaDvuQ4C3N95mA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","clFhkTaiph2aOjCNuZDWKA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","DLEY7W0VXWLE5Ol-plW-_w","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","RY-vzTa9LfseI7kmcIcbgQ","fiyOjJSGn-Eja0GP7-aFCg","zP58DjIs7uq1cghmzykyNA","OSzao_jV2aCbdBGfMYY-XA","-pUZ8YYbKKOu4w9rcMsXSw","XnUkhGmJNwiHTUPaIuILqg","4ES22TXzFLCEFBoqI_YoOg","-gq3a70QOgdn9HetYyf2Og","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng","G68hjsyagwq6LpWrMjDdng"],"frame_ids":["a5aMcPOeWx28QSVng73nBQAAAAAAAAAw","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","mHiYHSEggclUi1ELZIxq4AAAAAAAAABA","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK","JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK","MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ","yWt46REABLfKH6PXLAE18AAAAAAAAABk","VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM","n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE","Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG","GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAY","fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM","zP58DjIs7uq1cghmzykyNAAAAAAAAAAK","OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm","-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu","XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo","4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE","-gq3a70QOgdn9HetYyf2OgAAAAAAAADS","G68hjsyagwq6LpWrMjDdngAAAAAAEKV1","G68hjsyagwq6LpWrMjDdngAAAAAAG66m","G68hjsyagwq6LpWrMjDdngAAAAAADAfr","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkP4","G68hjsyagwq6LpWrMjDdngAAAAAAEvsX","G68hjsyagwq6LpWrMjDdngAAAAAADAai","G68hjsyagwq6LpWrMjDdngAAAAAAEkQ7","G68hjsyagwq6LpWrMjDdngAAAAAAEknw","G68hjsyagwq6LpWrMjDdngAAAAAAHbxx","G68hjsyagwq6LpWrMjDdngAAAAAAHaAJ"],"type_ids":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3]}},"stack_frames":{"piWSMQrh4r040D0BPNaJvwAAAAAAoAJU":{"file_name":[],"function_name":["ret_from_fork"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAACtfS":{"file_name":[],"function_name":["kthread"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAEFgJ":{"file_name":[],"function_name":["rcu_gp_kthread"],"function_offset":[],"line_number":[]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAAhE5":{"file_name":["../csu/libc-start.c"],"function_name":["__libc_start_main"],"function_offset":[],"line_number":[308]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAZVI":{"file_name":["libmount/src/tab_parse.c"],"function_name":["__mnt_table_parse_mtab"],"function_offset":[],"line_number":[1102]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAY-W":{"file_name":["libmount/src/tab_parse.c"],"function_name":["mnt_table_parse_file"],"function_offset":[],"line_number":[707]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAYhK":{"file_name":["libmount/src/tab_parse.c","libmount/src/tab_parse.c","libmount/src/tab_parse.c"],"function_name":["mnt_table_parse_stream","mnt_table_parse_next","mnt_parse_mountinfo_line"],"function_offset":[],"line_number":[643,506,215]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAO6N":{"file_name":["libmount/src/fs.c","libmount/src/fs.c"],"function_name":["mnt_fs_strdup_options","merge_optstr"],"function_offset":[],"line_number":[751,715]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAASUz":{"file_name":["libmount/src/optstr.c"],"function_name":["mnt_optstr_remove_option"],"function_offset":[],"line_number":[490]},"OTWX4UsOVMrSIF5cD4zUzgAAAAAAAR50":{"file_name":["libmount/src/optstr.c"],"function_name":["mnt_optstr_locate_option"],"function_offset":[],"line_number":[122]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK2w1":{"file_name":[],"function_name":["__x64_sys_getdents64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK2uM":{"file_name":[],"function_name":["ksys_getdents64"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK1v8":{"file_name":[],"function_name":["iterate_dir"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAMuWZ":{"file_name":[],"function_name":["proc_pid_readdir"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAMrzu":{"file_name":[],"function_name":["next_tgid"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAACq1j":{"file_name":[],"function_name":["pid_nr_ns"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKl_w":{"file_name":[],"function_name":["__do_sys_newfstatat"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKlki":{"file_name":[],"function_name":["vfs_statx"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKyG1":{"file_name":[],"function_name":["filename_lookup"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKt7k":{"file_name":[],"function_name":["path_lookupat"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKtt7":{"file_name":[],"function_name":["link_path_walk.part.33"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKta7":{"file_name":[],"function_name":["walk_component"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK7NA":{"file_name":[],"function_name":["dput"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKg_g":{"file_name":[],"function_name":["ksys_write"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKgzs":{"file_name":[],"function_name":["vfs_write"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKeLa":{"file_name":[],"function_name":["new_sync_write"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZnmG":{"file_name":[],"function_name":["sock_write_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZnjq":{"file_name":[],"function_name":["sock_sendmsg"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAePOo":{"file_name":[],"function_name":["unix_stream_sendmsg"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ7HT":{"file_name":[],"function_name":["skb_copy_datagram_from_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAASk0o":{"file_name":[],"function_name":["copy_page_from_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAShZh":{"file_name":[],"function_name":["copyin"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAgUld":{"file_name":[],"function_name":["copy_user_enhanced_fast_string"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKg7A":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKgtY":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKeEz":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZnfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAePFy":{"file_name":[],"function_name":["unix_stream_recvmsg"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAeOpA":{"file_name":[],"function_name":["unix_stream_read_generic"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAeMVZ":{"file_name":[],"function_name":["unix_stream_read_actor"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ7u6":{"file_name":[],"function_name":["skb_copy_datagram_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ7kW":{"file_name":[],"function_name":["__skb_datagram_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAZ7iE":{"file_name":[],"function_name":["simple_copy_to_iter"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKZiW":{"file_name":[],"function_name":["__check_object_size"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALJ7H":{"file_name":[],"function_name":["seq_read"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAMqWN":{"file_name":[],"function_name":["proc_single_show"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAMprm":{"file_name":[],"function_name":["proc_pid_limits"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALJVd":{"file_name":[],"function_name":["seq_printf"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAALJTv":{"file_name":[],"function_name":["seq_vprintf"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAgQON":{"file_name":[],"function_name":["vsnprintf"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAgWrH":{"file_name":[],"function_name":["memcpy_erms"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqah":{"file_name":[],"function_name":["__x64_sys_pipe2"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqYM":{"file_name":[],"function_name":["do_pipe2"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqU7":{"file_name":[],"function_name":["__do_pipe_flags"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKqSa":{"file_name":[],"function_name":["create_pipe_files"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKh1i":{"file_name":[],"function_name":["alloc_file_clone"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKhts":{"file_name":[],"function_name":["alloc_file"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKhqi":{"file_name":[],"function_name":["alloc_empty_file"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKhaJ":{"file_name":[],"function_name":["__alloc_file"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAJwdF":{"file_name":[],"function_name":["kmem_cache_alloc"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAAEFn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKcUM":{"file_name":[],"function_name":["do_sys_open"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKxcK":{"file_name":[],"function_name":["do_filp_open"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKu55":{"file_name":[],"function_name":["path_openat"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKg3y":{"file_name":[],"function_name":["alloc_empty_file"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKgnZ":{"file_name":[],"function_name":["__alloc_file"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJvxU":{"file_name":[],"function_name":["kmem_cache_alloc"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJvpt":{"file_name":[],"function_name":["__slab_alloc"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJvhM":{"file_name":[],"function_name":["___slab_alloc"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJu2y":{"file_name":[],"function_name":["new_slab"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJMoT":{"file_name":[],"function_name":["__alloc_pages_nodemask"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJIkv":{"file_name":[],"function_name":["get_page_from_freelist"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAoACj":{"file_name":[],"function_name":["entry_SYSCALL_64_after_hwframe"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAAEGn":{"file_name":[],"function_name":["do_syscall_64"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKg_Q":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKgxC":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAQFQm":{"file_name":[],"function_name":["security_file_permission"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKgxo":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAKeJD":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAOmg3":{"file_name":[],"function_name":["xfs_file_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAOmdC":{"file_name":[],"function_name":["xfs_file_buffered_aio_read"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAH0j-":{"file_name":[],"function_name":["generic_file_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAASkft":{"file_name":[],"function_name":["copy_page_to_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAShdv":{"file_name":[],"function_name":["copyout"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKgAA":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKfyY":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKdJz":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAd-3C":{"file_name":[],"function_name":["unix_stream_recvmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAd-tk":{"file_name":[],"function_name":["unix_stream_read_generic"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZlea":{"file_name":[],"function_name":["consume_skb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZltq":{"file_name":[],"function_name":["skb_release_data"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJHy1":{"file_name":[],"function_name":["free_unref_page"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJFUJ":{"file_name":[],"function_name":["free_unref_page_prepare.part.71"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKgyw":{"file_name":[],"function_name":["ksys_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKglI":{"file_name":[],"function_name":["vfs_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAKd8j":{"file_name":[],"function_name":["new_sync_read"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZmfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZmbb":{"file_name":[],"function_name":["sock_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAQGQD":{"file_name":[],"function_name":["security_socket_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAdME8":{"file_name":[],"function_name":["inet_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcXT4":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcn3R":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcXqg":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAJu6z":{"file_name":[],"function_name":["kmem_cache_free"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAJuT8":{"file_name":[],"function_name":["__slab_free"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcpNe":{"file_name":[],"function_name":["__tcp_send_ack.part.47"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAZy0m":{"file_name":[],"function_name":["__alloc_skb"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAJwxK":{"file_name":[],"function_name":["kmem_cache_alloc_node"],"function_offset":[],"line_number":[]},"N4ILulabOfF5MnyRJbvDXwAAAAAAEHzT":{"file_name":["/usr/src/debug/Python-2.7.18/Modules/main.c"],"function_name":["Py_Main"],"function_offset":[],"line_number":[645]},"N4ILulabOfF5MnyRJbvDXwAAAAAAD20Q":{"file_name":["/usr/src/debug/Python-2.7.18/Python/pythonrun.c"],"function_name":["PyRun_SimpleFileExFlags"],"function_offset":[],"line_number":[957]},"N4ILulabOfF5MnyRJbvDXwAAAAAAD1xx":{"file_name":["/usr/src/debug/Python-2.7.18/Python/pythonrun.c"],"function_name":["PyRun_FileExFlags"],"function_offset":[],"line_number":[1371]},"N4ILulabOfF5MnyRJbvDXwAAAAAAD0wq":{"file_name":["/usr/src/debug/Python-2.7.18/Python/pythonrun.c"],"function_name":["run_mod"],"function_offset":[],"line_number":[1385]},"N4ILulabOfF5MnyRJbvDXwAAAAAADdJo":{"file_name":["/usr/src/debug/Python-2.7.18/Python/ceval.c"],"function_name":["PyEval_EvalCode"],"function_offset":[],"line_number":[691]},"N4ILulabOfF5MnyRJbvDXwAAAAAADdBO":{"file_name":["/usr/src/debug/Python-2.7.18/Python/ceval.c"],"function_name":["PyEval_EvalCodeEx"],"function_offset":[],"line_number":[3685]},"N4ILulabOfF5MnyRJbvDXwAAAAAADaC9":{"file_name":["/usr/src/debug/Python-2.7.18/Python/ceval.c","/usr/src/debug/Python-2.7.18/Python/ceval.c","/usr/src/debug/Python-2.7.18/Python/ceval.c"],"function_name":["PyEval_EvalFrameEx","call_function","fast_function"],"function_offset":[],"line_number":[3087,4473,4548]},"N4ILulabOfF5MnyRJbvDXwAAAAAADaoW":{"file_name":["/usr/src/debug/Python-2.7.18/Python/ceval.c","/usr/src/debug/Python-2.7.18/Python/ceval.c","/usr/src/debug/Python-2.7.18/Python/ceval.c"],"function_name":["PyEval_EvalFrameEx","call_function","fast_function"],"function_offset":[],"line_number":[3087,4473,4538]},"N4ILulabOfF5MnyRJbvDXwAAAAAADYlW":{"file_name":["/usr/src/debug/Python-2.7.18/Python/ceval.c","/usr/src/debug/Python-2.7.18/Python/ceval.c"],"function_name":["PyEval_EvalFrameEx","ext_do_call"],"function_offset":[],"line_number":[3126,4767]},"N4ILulabOfF5MnyRJbvDXwAAAAAABLuy":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/abstract.c"],"function_name":["PyObject_Call"],"function_offset":[],"line_number":[2544]},"N4ILulabOfF5MnyRJbvDXwAAAAAABtnu":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/funcobject.c"],"function_name":["function_call"],"function_offset":[],"line_number":[523]},"N4ILulabOfF5MnyRJbvDXwAAAAAADYFz":{"file_name":["/usr/src/debug/Python-2.7.18/Python/ceval.c","/usr/src/debug/Python-2.7.18/Python/ceval.c","/usr/src/debug/Python-2.7.18/Python/ceval.c"],"function_name":["PyEval_EvalFrameEx","call_function","do_call"],"function_offset":[],"line_number":[3087,4475,4670]},"N4ILulabOfF5MnyRJbvDXwAAAAAACasJ":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/typeobject.c"],"function_name":["type_call"],"function_offset":[],"line_number":[765]},"N4ILulabOfF5MnyRJbvDXwAAAAAACd8S":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/typeobject.c"],"function_name":["slot_tp_init"],"function_offset":[],"line_number":[5869]},"N4ILulabOfF5MnyRJbvDXwAAAAAABZYn":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/classobject.c"],"function_name":["instancemethod_call"],"function_offset":[],"line_number":[2600]},"N4ILulabOfF5MnyRJbvDXwAAAAAABtkY":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/funcobject.c"],"function_name":["function_call"],"function_offset":[],"line_number":[523]},"N4ILulabOfF5MnyRJbvDXwAAAAAADV_P":{"file_name":["/usr/src/debug/Python-2.7.18/Python/ceval.c"],"function_name":["PyEval_EvalFrameEx"],"function_offset":[],"line_number":[1629]},"N4ILulabOfF5MnyRJbvDXwAAAAAAB9cG":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/dictobject.c"],"function_name":["dict_subscript"],"function_offset":[],"line_number":[1261]},"N4ILulabOfF5MnyRJbvDXwAAAAAAB7wG":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/dictobject.c"],"function_name":["lookdict"],"function_offset":[],"line_number":[351]},"N4ILulabOfF5MnyRJbvDXwAAAAAACDtP":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/object.c"],"function_name":["PyObject_RichCompareBool"],"function_offset":[],"line_number":[1009]},"N4ILulabOfF5MnyRJbvDXwAAAAAACDr6":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/object.c","/usr/src/debug/Python-2.7.18/Objects/object.c","/usr/src/debug/Python-2.7.18/Objects/object.c"],"function_name":["PyObject_RichCompare","do_richcmp","try_3way_to_rich_compare"],"function_offset":[],"line_number":[987,940,921]},"N4ILulabOfF5MnyRJbvDXwAAAAAACByz":{"file_name":["/usr/src/debug/Python-2.7.18/Objects/object.c"],"function_name":["convert_3way_to_object"],"function_offset":[],"line_number":[881]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZnfB":{"file_name":[],"function_name":["sock_read_iter"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAdNQM":{"file_name":[],"function_name":["inet_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcYcI":{"file_name":[],"function_name":["tcp_recvmsg"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcqWe":{"file_name":[],"function_name":["__tcp_send_ack.part.47"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZz1R":{"file_name":[],"function_name":["__alloc_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAZyV9":{"file_name":[],"function_name":["__kmalloc_reserve.isra.57"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAJ0bR":{"file_name":[],"function_name":["__kmalloc_node_track_caller"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAIdpk":{"file_name":[],"function_name":["kmalloc_slab"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcpF4":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcNCx":{"file_name":[],"function_name":["__ip_queue_xmit"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcNYI":{"file_name":[],"function_name":["ip_output"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAcK1g":{"file_name":[],"function_name":["ip_finish_output2"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaM05":{"file_name":[],"function_name":["__dev_queue_xmit"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAAaMRj":{"file_name":[],"function_name":["validate_xmit_skb"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAoA6J":{"file_name":[],"function_name":["do_softirq_own_stack"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAwADc":{"file_name":[],"function_name":["__softirqentry_text_start"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaPZZ":{"file_name":[],"function_name":["net_rx_action"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaNu-":{"file_name":[],"function_name":["process_backlog"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaNlU":{"file_name":[],"function_name":["__netif_receive_skb_one_core"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcGcb":{"file_name":[],"function_name":["ip_rcv"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcF0h":{"file_name":[],"function_name":["ip_rcv_finish"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcFmf":{"file_name":[],"function_name":["ip_rcv_finish_core.isra.16"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcDij":{"file_name":[],"function_name":["ip_route_input_noref"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcDcq":{"file_name":[],"function_name":["ip_route_input_rcu"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcDJ4":{"file_name":[],"function_name":["ip_route_input_slow"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAdks6":{"file_name":[],"function_name":["__fib_lookup"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAbDwa":{"file_name":[],"function_name":["fib_rules_lookup"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAdk6y":{"file_name":[],"function_name":["fib4_rule_action"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAdZvh":{"file_name":[],"function_name":["fib_table_lookup"],"function_offset":[],"line_number":[]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAEi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAFci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAABEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAM8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAALw8":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAADhg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAJeq":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAE3-":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"GdaBUD9IUEkKxIBryNqV2wAAAAAAAJtO":{"file_name":["clidriver.py"],"function_name":["create_parser"],"function_offset":[4],"line_number":[635]},"QU8QLoFK6ojrywKrBFfTzAAAAAAAACqM":{"file_name":["clidriver.py"],"function_name":["_get_command_table"],"function_offset":[3],"line_number":[580]},"V558DAsp4yi8bwa8eYwk5QAAAAAAAG60":{"file_name":["clidriver.py"],"function_name":["_create_command_table"],"function_offset":[18],"line_number":[615]},"tuTnMBfyc9UiPsI0QyvErAAAAAAAANis":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[700]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAAPlS":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"gMhgHDYSMmyInNJ15VwYFgAAAAAAAPvy":{"file_name":["hooks.py"],"function_name":["_emit"],"function_offset":[38],"line_number":[215]},"cHp4MwXaY5FCuFRuAA6tWwAAAAAAAOx8":{"file_name":["waiters.py"],"function_name":["add_waiters"],"function_offset":[11],"line_number":[36]},"-9oyoP4Jj2iRkwEezqId-gAAAAAAAFMc":{"file_name":["waiters.py"],"function_name":["get_waiter_model_from_service_model"],"function_offset":[5],"line_number":[48]},"3FRCbvQLPuJyn2B-2wELGwAAAAAAAJK8":{"file_name":["session.py"],"function_name":["get_waiter_model"],"function_offset":[4],"line_number":[527]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAAHeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAANQg":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAABvY":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"yaTrLhUSIq2WitrTHLBy3QAAAAAAAOAI":{"file_name":["posixpath.py"],"function_name":["join"],"function_offset":[21],"line_number":[92]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcn84":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcL7B":{"file_name":[],"function_name":["__ip_queue_xmit"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcMQY":{"file_name":[],"function_name":["ip_output"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAcJtw":{"file_name":[],"function_name":["ip_finish_output2"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaMAz":{"file_name":[],"function_name":["__dev_queue_xmit"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaLaf":{"file_name":[],"function_name":["dev_hard_start_xmit"],"function_offset":[],"line_number":[]},"aUXpdArtZf510BJKvwiFDwAAAAAAAAok":{"file_name":[],"function_name":["veth_xmit"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAaGr1":{"file_name":[],"function_name":["__dev_forward_skb"],"function_offset":[],"line_number":[]},"9LzzIocepYcOjnUsLlgOjgAAAAAAbgzT":{"file_name":[],"function_name":["eth_type_trans"],"function_offset":[],"line_number":[]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAAi0":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAABci":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAANEM":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAI8S":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAHGc":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAPhg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAEeq":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAA58":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAABTm":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"ne8F__HPIVgxgycJADVSzAAAAAAAACzA":{"file_name":["clidriver.py"],"function_name":["invoke"],"function_offset":[29],"line_number":[930]},"ktj-IOmkEpvZJouiJkQjTgAAAAAAAEYa":{"file_name":["session.py"],"function_name":["create_client"],"function_offset":[117],"line_number":[854]},"O_h7elJSxPO7SiCsftYRZgAAAAAAAPSm":{"file_name":["client.py"],"function_name":["create_client"],"function_offset":[52],"line_number":[142]},"_s_-RvH9Io2qUzM6f5JLGgAAAAAAAGfw":{"file_name":["client.py"],"function_name":["_create_client_class"],"function_offset":[12],"line_number":[160]},"8UGQaqEhTX9IIJEQCXnRsQAAAAAAAG5o":{"file_name":["client.py"],"function_name":["_create_methods"],"function_offset":[5],"line_number":[319]},"jn4X0YIYIsTeszwLEaje9gAAAAAAACEE":{"file_name":["client.py"],"function_name":["_create_api_method"],"function_offset":[25],"line_number":[356]},"TesF2I_BvQoOuJH9P_M2mAAAAAAAAGk-":{"file_name":["docstring.py"],"function_name":["__init__"],"function_offset":[9],"line_number":[36]},"ew01Dk0sWZctP-VaEpavqQAAAAAAoBCe":{"file_name":[],"function_name":["async_page_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAABnNL":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"ew01Dk0sWZctP-VaEpavqQAAAAAADkzO":{"file_name":[],"function_name":["down_read_trylock"],"function_offset":[],"line_number":[]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAAGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAIQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAAJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAMsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAABbM":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAACrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAADAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAAdM":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAJQW":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"ne8F__HPIVgxgycJADVSzAAAAAAAAB9A":{"file_name":["clidriver.py"],"function_name":["invoke"],"function_offset":[29],"line_number":[930]},"CwUjPVV5_7q7c0GhtW0aPwAAAAAAALcE":{"file_name":["session.py"],"function_name":["create_client"],"function_offset":[112],"line_number":[848]},"okehWevKsEA4q6dk779jgwAAAAAAAH1M":{"file_name":["session.py"],"function_name":["get_credentials"],"function_offset":[12],"line_number":[445]},"-IuadWGT89NVzIyF_EmodwAAAAAAAMKw":{"file_name":["credentials.py"],"function_name":["load_credentials"],"function_offset":[18],"line_number":[1953]},"XXJY7v4esGWnaxtMW3FA0gAAAAAAAJ08":{"file_name":["credentials.py"],"function_name":["load"],"function_offset":[18],"line_number":[1009]},"FbrXdcA4j750RyQ3q9JXMwAAAAAAAIKa":{"file_name":["utils.py"],"function_name":["retrieve_iam_role_credentials"],"function_offset":[30],"line_number":[517]},"pL34QuyxyP6XYzGDBMK_5wAAAAAAAH_a":{"file_name":["utils.py"],"function_name":["_get_iam_role"],"function_offset":[1],"line_number":[524]},"IoAk4kM-M4DsDPp7ia5QXwAAAAAAAKvK":{"file_name":["utils.py"],"function_name":["_get_request"],"function_offset":[32],"line_number":[435]},"uHLoBslr3h6S7ooNeXzEbwAAAAAAAJQ8":{"file_name":["httpsession.py"],"function_name":["send"],"function_offset":[56],"line_number":[487]},"iRoTPXvR_cRsnzDO-aurpQAAAAAAAHbc":{"file_name":["connectionpool.py"],"function_name":["urlopen"],"function_offset":[361],"line_number":[894]},"fB79lJck2X90l-j7VqPR-QAAAAAAAGc8":{"file_name":["connectionpool.py"],"function_name":["_make_request"],"function_offset":[116],"line_number":[494]},"gbMheDI1NZ3NY96J0seddgAAAAAAAEuq":{"file_name":["client.py"],"function_name":["getresponse"],"function_offset":[58],"line_number":[1389]},"GquRfhZBLBKr9rIBPuH3nAAAAAAAAE4w":{"file_name":["client.py"],"function_name":["__init__"],"function_offset":[28],"line_number":[276]},"_DA_LSFNMjbu9L2DcselpwAAAAAAAJFI":{"file_name":["socket.py"],"function_name":["makefile"],"function_offset":[40],"line_number":[343]},"8EY5iPD5-FtlXFBTyb6lkwAAAAAAAPtm":{"file_name":["pyi_rth_pkgutil.py"],"function_name":[""],"function_offset":[33],"line_number":[34]},"ik6PIX946fW_erE7uBJlVQAAAAAAAILu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAIr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAACFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAEbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"dCCKy6JoX0PADOFic8hRNQAAAAAAAO-w":{"file_name":["pkgutil.py"],"function_name":[""],"function_offset":[315],"line_number":[316]},"9w9lF96vJW7ZhBoZ8ETsBwAAAAAAAEgm":{"file_name":["functools.py"],"function_name":["register"],"function_offset":[50],"line_number":[902]},"xUQuo4OgBaS_Le-fdAwt8AAAAAAAAEDw":{"file_name":["functools.py"],"function_name":["_is_union_type"],"function_offset":[2],"line_number":[843]},"zkPjzY2Et3KehkHOcSphkAAAAAAAADpY":{"file_name":["typing.py"],"function_name":[""],"function_offset":[2084],"line_number":[2085]},"mBpjyQvq6ftE7Wm1BUpcFgAAAAAAABhk":{"file_name":["abc.py"],"function_name":["__new__"],"function_offset":[3],"line_number":[108]},"a5aMcPOeWx28QSVng73nBQAAAAAAAAAw":{"file_name":["aws"],"function_name":[""],"function_offset":[5],"line_number":[19]},"OSzao_jV2aCbdBGfMYY-XAAAAAAAAAAm":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[5],"line_number":[1007]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[19],"line_number":[986]},"XnUkhGmJNwiHTUPaIuILqgAAAAAAAABo":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[21],"line_number":[680]},"4ES22TXzFLCEFBoqI_YoOgAAAAAAAABE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[499]},"79pMuEW6_o55K0jHDJ-2dQAAAAAAAAH0":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[49],"line_number":[62]},"gZNrskHHFmNkCQ_HaCv8sAAAAAAAAAAc":{"file_name":["core.py"],"function_name":[""],"function_offset":[3],"line_number":[16]},"LUEJ1TSRGwRkHbcAyZ3RuQAAAAAAAAAs":{"file_name":["prompttoolkit.py"],"function_name":[""],"function_offset":[5],"line_number":[18]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAAAm":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[5],"line_number":[972]},"zP58DjIs7uq1cghmzykyNAAAAAAAAAAK":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[228]},"9h_0PKFtQeN0f7xWevHlTQAAAAAAAAAI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"nIG-LJ6Pj1PzNMyyppUoqgAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAAM4":{"file_name":["application.py"],"function_name":[""],"function_offset":[114],"line_number":[115]},"IlUL618nbeW5Kz4uyGZLrQAAAAAAAAB0":{"file_name":["application.py"],"function_name":["Application"],"function_offset":[91],"line_number":[206]},"U7DZUwH_4YU5DSkoQhGJWwAAAAAAAAAM":{"file_name":["typing.py"],"function_name":["inner"],"function_offset":[3],"line_number":[274]},"bmb3nSRfimrjfhanpjR1rQAAAAAAAAAI":{"file_name":["typing.py"],"function_name":["__getitem__"],"function_offset":[2],"line_number":[354]},"oN7OWDJeuc8DmI2f_earDQAAAAAAAAA2":{"file_name":["typing.py"],"function_name":["Union"],"function_offset":[32],"line_number":[466]},"Yj7P3-Rt3nirG6apRl4A7AAAAAAAAAAM":{"file_name":["typing.py"],"function_name":[""],"function_offset":[0],"line_number":[466]},"pz3Evn9laHNJFMwOKIXbswAAAAAAAAAu":{"file_name":["typing.py"],"function_name":["_type_check"],"function_offset":[18],"line_number":[155]},"7aaw2O1Vn7-6eR8XuUWQZQAAAAAAAAAW":{"file_name":["typing.py"],"function_name":["_type_convert"],"function_offset":[4],"line_number":[132]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAIGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAAQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAIJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAEsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAHVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAIHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAA10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAACs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAACXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"ik6PIX946fW_erE7uBJlVQAAAAAAAOLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAIFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAIbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAMKO":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"SD7uzoegJjRT3jYNpuQ5wQAAAAAAAPBK":{"file_name":["configure.py"],"function_name":[""],"function_offset":[56],"line_number":[57]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAOpK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"lOUbi56SanKTCh9Y7fIwDwAAAAAAAP2g":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1099]},"n74P5OxFm1hAo5ZWtgcKHQAAAAAAAHGe":{"file_name":["__init__.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[93]},"zXbqXCWr0lCbi_b24hNBRQAAAAAAAFJe":{"file_name":["pyimod02_importers.py"],"function_name":["find_spec"],"function_offset":[87],"line_number":[302]},"piWSMQrh4r040D0BPNaJvwAAAAAAKgEg":{"file_name":[],"function_name":["ksys_write"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKf4s":{"file_name":[],"function_name":["vfs_write"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAKdQa":{"file_name":[],"function_name":["new_sync_write"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXmG":{"file_name":[],"function_name":["sock_write_iter"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAZXjj":{"file_name":[],"function_name":["sock_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcK5W":{"file_name":[],"function_name":["tcp_sendmsg"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcKWq":{"file_name":[],"function_name":["tcp_sendmsg_locked"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcbOh":{"file_name":[],"function_name":["__tcp_push_pending_frames"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcaTc":{"file_name":[],"function_name":["tcp_write_xmit"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcYo_":{"file_name":[],"function_name":["__tcp_transmit_skb"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAcYWv":{"file_name":[],"function_name":["__tcp_select_window"],"function_offset":[],"line_number":[]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAEQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAIVW":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAJHc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAE10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAGXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"ik6PIX946fW_erE7uBJlVQAAAAAAAHLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAABFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAANLe":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"uo8E5My6tupMEt-pfV-uhAAAAAAAAKIu":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAEY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAIFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAGkq":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAAAE":{"file_name":["application.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"ZBnr-5IlLVGCdkX_lTNKmwAAAAAAAABY":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[16],"line_number":[17]},"4ES22TXzFLCEFBoqI_YoOgAAAAAAAAAO":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[13],"line_number":[482]},"NNy6Y3cHKuqblVbtSVjWfwAAAAAAAAAg":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[14],"line_number":[298]},"coeZ_4yf5sOePIKKlm8FNQAAAAAAAAC-":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[18],"line_number":[304]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAFpm":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAKDc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAEn0":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAHYk":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"6WJ6x4R10ox82_e3Ea4eiAAAAAAAAKXw":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[10],"line_number":[78]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAAxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAABRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAKqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAABFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAHLq":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"l97YFeEKpeLfa-lEAZVNcAAAAAAAAOZu":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[16],"line_number":[17]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAABBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAAFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAILi":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAEGc":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAABeq":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAE58":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAOEK":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"xNMiNBkMujk7ZnRv0OEjrQAAAAAAAOys":{"file_name":["clidriver.py"],"function_name":["arg_table"],"function_offset":[4],"line_number":[733]},"MYrgKQIxdDhr1gdpucfc-QAAAAAAAJUK":{"file_name":["clidriver.py"],"function_name":["_create_argument_table"],"function_offset":[26],"line_number":[867]},"un9fLDZOLvDMO52ltZtuegAAAAAAAM1M":{"file_name":["clidriver.py"],"function_name":["_emit"],"function_offset":[1],"line_number":[874]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAADlS":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"rTFMSHhLRlj86vHPR06zoQAAAAAAAL4m":{"file_name":["paginate.py"],"function_name":["unify_paging_params"],"function_offset":[51],"line_number":[175]},"oArGmvsy3VNtTf_V9EHNeQAAAAAAAGTS":{"file_name":["paginate.py"],"function_name":["get_paginator_config"],"function_offset":[10],"line_number":[92]},"-T5rZCijT5TDJjmoEi8KxgAAAAAAAJP8":{"file_name":["session.py"],"function_name":["get_paginator_model"],"function_offset":[4],"line_number":[533]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAALeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAAB1w":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAAHKK":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAMT2":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAANF8":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAI10":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAAKs0":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"ik6PIX946fW_erE7uBJlVQAAAAAAANLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAANr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAHFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAA1i":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"ynoRUNDFNh_CC1ViETMulAAAAAAAABSW":{"file_name":["subscribe.py"],"function_name":[""],"function_offset":[150],"line_number":[151]},"fxzD8soKl4etJ4L6nJl81gAAAAAAAHBe":{"file_name":["utils.py"],"function_name":[""],"function_offset":[584],"line_number":[585]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAAFtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAHSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAHJC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAABpU":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAAJHY":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAFoc":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAEpm":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAJDc":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"Af6E3BeG383JVVbu67NJ0QAAAAAAAAn0":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[33],"line_number":[58]},"xwuAPHgc12-8PZB3i-320gAAAAAAADYk":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[2],"line_number":[63]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAMxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAANRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAGqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAAFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"BrhWuphS0ZH9x8_V0fpb0AAAAAAAAPxi":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[106],"line_number":[107]},"780bLUPADqfQ3x1T5lnVOgAAAAAAAJsu":{"file_name":["emr.py"],"function_name":[""],"function_offset":[42],"line_number":[43]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAJcs":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAKrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAALAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAIdM":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAABCa":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"xNMiNBkMujk7ZnRv0OEjrQAAAAAAAAu8":{"file_name":["clidriver.py"],"function_name":["arg_table"],"function_offset":[4],"line_number":[733]},"MYrgKQIxdDhr1gdpucfc-QAAAAAAAN-q":{"file_name":["clidriver.py"],"function_name":["_create_argument_table"],"function_offset":[26],"line_number":[867]},"un9fLDZOLvDMO52ltZtuegAAAAAAAGsM":{"file_name":["clidriver.py"],"function_name":["_emit"],"function_offset":[1],"line_number":[874]},"grikUXlisBLUbeL_OWixIwAAAAAAAPZs":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[699]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAADdy":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"rTFMSHhLRlj86vHPR06zoQAAAAAAAEfG":{"file_name":["paginate.py"],"function_name":["unify_paging_params"],"function_offset":[51],"line_number":[175]},"oArGmvsy3VNtTf_V9EHNeQAAAAAAAKNy":{"file_name":["paginate.py"],"function_name":["get_paginator_config"],"function_offset":[10],"line_number":[92]},"7v-k2b21f_Xuf-3329jFywAAAAAAAIY8":{"file_name":["session.py"],"function_name":["get_paginator_model"],"function_offset":[4],"line_number":[532]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAADeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAAGBA":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAAFNo":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"yaTrLhUSIq2WitrTHLBy3QAAAAAAAGcY":{"file_name":["posixpath.py"],"function_name":["join"],"function_offset":[21],"line_number":[92]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAMiA":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAGxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHJU":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAJSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAHRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAAqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAKFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"08Dc0vnMK9C_nl7yQB6ZKQAAAAAAAMP6":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[47],"line_number":[48]},"zuPG_tF81PcJTwjfBwKlDgAAAAAAADW4":{"file_name":["abc.py"],"function_name":[""],"function_offset":[267],"line_number":[268]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAKBs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAMFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAABKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"pv4wAezdMMO0SVuGgaEMTgAAAAAAAHV2":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[17],"line_number":[18]},"LEy-wm0GIvRoYVAga55HiwAAAAAAALxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAMRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAFqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAIFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"qns5vQ3LMi6QrIMOgD_TwQAAAAAAAAR-":{"file_name":["service.py"],"function_name":[""],"function_offset":[20],"line_number":[21]},"J_Lkq1OzUHxWQhnTgF6FwAAAAAAAALq2":{"file_name":["restdoc.py"],"function_name":[""],"function_offset":[22],"line_number":[23]},"XkOSW26Xa6_lkqHv5givKgAAAAAAAEnG":{"file_name":["compat.py"],"function_name":[""],"function_offset":[231],"line_number":[232]},"BuJIbGFo3xNyZaTAXvW1AgAAAAAAAMqS":{"file_name":["datetime.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"L9BMhx_jo5vrPGr_NYlXCQAAAAAAAG9-":{"file_name":["datetime.py"],"function_name":["timezone"],"function_offset":[97],"line_number":[2394]},"pZhbjLL2hYCcec5rSvEEGwAAAAAAAMsk":{"file_name":["datetime.py"],"function_name":["__neg__"],"function_offset":[3],"line_number":[768]},"kkqG_q7yucIGLE7ky-QX9AAAAAAAAI3I":{"file_name":["datetime.py"],"function_name":["__new__"],"function_offset":[99],"line_number":[691]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAOT2":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"LF6DFcGHEMqhhhlptO_M_QAAAAAAAPF8":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[12],"line_number":[101]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAACzq":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"3HhVgGD2yvuFLpoZq7RfKwAAAAAAAN3q":{"file_name":["cloudfront.py"],"function_name":[""],"function_offset":[179],"line_number":[180]},"uSWUCgHgLPG4OFtPdUp0rgAAAAAAAHtu":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[27],"line_number":[28]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAMkq":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"DTRaillMS4wmG2CDEfm9rQAAAAAAAEGE":{"file_name":["aws"],"function_name":[""],"function_offset":[25],"line_number":[26]},"U4Le8nh-beog_B7jq7uTIAAAAAAAAMQi":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"CqoTgn4VUlwTNyUw7wsMHQAAAAAAAEJs":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"SjQZVYGLzro7G-9yPjVJlgAAAAAAAAsS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[12],"line_number":[176]},"grZNsSElR5ITq8H2yHCNSwAAAAAAAHcs":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[18],"line_number":[200]},"W8AFtEsepzrJ6AasHrCttwAAAAAAAGrg":{"file_name":["clidriver.py"],"function_name":["_run_driver"],"function_offset":[2],"line_number":[180]},"sur1OQS0yB3u_A1ZgjRjFgAAAAAAAJAK":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[23],"line_number":[459]},"EFJHOn-GACfHXgae-R1yDAAAAAAAAEdM":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[7],"line_number":[595]},"kSaNXrGzSS3BnDNNWezzMAAAAAAAAPCa":{"file_name":["clidriver.py"],"function_name":["__call__"],"function_offset":[57],"line_number":[798]},"MYrgKQIxdDhr1gdpucfc-QAAAAAAAL-q":{"file_name":["clidriver.py"],"function_name":["_create_argument_table"],"function_offset":[26],"line_number":[867]},"un9fLDZOLvDMO52ltZtuegAAAAAAACsM":{"file_name":["clidriver.py"],"function_name":["_emit"],"function_offset":[1],"line_number":[874]},"grikUXlisBLUbeL_OWixIwAAAAAAALZs":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[699]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAAPdy":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"gMhgHDYSMmyInNJ15VwYFgAAAAAAALvy":{"file_name":["hooks.py"],"function_name":["_emit"],"function_offset":[38],"line_number":[215]},"rTFMSHhLRlj86vHPR06zoQAAAAAAACfG":{"file_name":["paginate.py"],"function_name":["unify_paging_params"],"function_offset":[51],"line_number":[175]},"oArGmvsy3VNtTf_V9EHNeQAAAAAAAGNy":{"file_name":["paginate.py"],"function_name":["get_paginator_config"],"function_offset":[10],"line_number":[92]},"7v-k2b21f_Xuf-3329jFywAAAAAAAEY8":{"file_name":["session.py"],"function_name":["get_paginator_model"],"function_offset":[4],"line_number":[532]},"FqNqtF0e0OG1VJJtWE9clwAAAAAAAPeU":{"file_name":["loaders.py"],"function_name":["_wrapper"],"function_offset":[8],"line_number":[132]},"GEIvPhvjHWZLHz2BksVgvAAAAAAAAEBA":{"file_name":["loaders.py"],"function_name":["load_service_model"],"function_offset":[45],"line_number":[386]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAADOE":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAC7Rc":{"file_name":["../sysdeps/posix/readdir.c"],"function_name":["__readdir"],"function_offset":[],"line_number":[65]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK2pa":{"file_name":[],"function_name":["__x64_sys_getdents"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAOkGr":{"file_name":[],"function_name":["xfs_readdir"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAOjnO":{"file_name":[],"function_name":["xfs_dir2_sf_getdents.isra.9"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAN1i4":{"file_name":[],"function_name":["xfs_dir2_sf_get_parent_ino"],"function_offset":[],"line_number":[]},"MU3fJpOZe9TA4mzeo52wZgAAAAAAAP6m":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[297],"line_number":[298]},"auEGiAr7C6IfT0eiHbOlyAAAAAAAAFg6":{"file_name":["session.py"],"function_name":[""],"function_offset":[184],"line_number":[185]},"mP9Tk3T74fjOyYWKUaqdMQAAAAAAADDi":{"file_name":["client.py"],"function_name":[""],"function_offset":[119],"line_number":[120]},"I4X8AC1-B0GuL4JyYemPzwAAAAAAAGO6":{"file_name":["args.py"],"function_name":[""],"function_offset":[35],"line_number":[36]},"s6flibJ32CsA8wnq-j6RkQAAAAAAAJEy":{"file_name":["regions.py"],"function_name":[""],"function_offset":[139],"line_number":[140]},"ik6PIX946fW_erE7uBJlVQAAAAAAAOL8":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"3EA5Wz2lIIw6eu5uv4gkTwAAAAAAACDI":{"file_name":["_bootstrap.py"],"function_name":["__exit__"],"function_offset":[1],"line_number":[174]},"hjYcB64xHdoySaNOZ8xYqgAAAAAAADsY":{"file_name":["_bootstrap.py"],"function_name":["release"],"function_offset":[2],"line_number":[127]},"Gp9aOxUrrpSVBx4-ftlTOAAAAAAAAAdC":{"file_name":["auth.py"],"function_name":[""],"function_offset":[603],"line_number":[604]},"ik6PIX946fW_erE7uBJlVQAAAAAAAJLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAJr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAADFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"y9R94bQUxts02WzRWfV7xgAAAAAAAHeC":{"file_name":["auth.py"],"function_name":[""],"function_offset":[316],"line_number":[317]},"uI6css-d8SGQRK6a_Ntl-AAAAAAAAIVu":{"file_name":["auth.py"],"function_name":[""],"function_offset":[336],"line_number":[337]},"SlnkBp0IIJFLHVOe4KbxwQAAAAAAANt6":{"file_name":["http.py"],"function_name":[""],"function_offset":[231],"line_number":[232]},"uPGvGNXBf1JXGeeDSsmGQAAAAAAAACX2":{"file_name":["enum.py"],"function_name":["__new__"],"function_offset":[194],"line_number":[679]},"PmtIuZrIdDPbhY30JCQRwwAAAAAAADto":{"file_name":["enum.py"],"function_name":["__set_name__"],"function_offset":[96],"line_number":[333]},"yos2k6ZH69vZXiBQV3d7cQAAAAAAAKJ4":{"file_name":["enum.py"],"function_name":["__setattr__"],"function_offset":[11],"line_number":[839]},"xNMiNBkMujk7ZnRv0OEjrQAAAAAAAIu8":{"file_name":["clidriver.py"],"function_name":["arg_table"],"function_offset":[4],"line_number":[733]},"un9fLDZOLvDMO52ltZtuegAAAAAAAOsM":{"file_name":["clidriver.py"],"function_name":["_emit"],"function_offset":[1],"line_number":[874]},"grikUXlisBLUbeL_OWixIwAAAAAAAHZs":{"file_name":["session.py"],"function_name":["emit"],"function_offset":[1],"line_number":[699]},"oERZXsH8EPeoSRxNNaSWfQAAAAAAAHdy":{"file_name":["hooks.py"],"function_name":["emit"],"function_offset":[11],"line_number":[228]},"gMhgHDYSMmyInNJ15VwYFgAAAAAAADvy":{"file_name":["hooks.py"],"function_name":["_emit"],"function_offset":[38],"line_number":[215]},"rTFMSHhLRlj86vHPR06zoQAAAAAAACm2":{"file_name":["paginate.py"],"function_name":["unify_paging_params"],"function_offset":[51],"line_number":[175]},"oArGmvsy3VNtTf_V9EHNeQAAAAAAACNy":{"file_name":["paginate.py"],"function_name":["get_paginator_config"],"function_offset":[10],"line_number":[92]},"7v-k2b21f_Xuf-3329jFywAAAAAAAAY8":{"file_name":["session.py"],"function_name":["get_paginator_model"],"function_offset":[4],"line_number":[532]},"--q8cwZVXbHL2zOM_p3RlQAAAAAAADMg":{"file_name":["loaders.py"],"function_name":["list_available_services"],"function_offset":[38],"line_number":[285]},"wXOyVgf5_nNg6CUH5kFBbgAAAAAAABkK":{"file_name":["loaders.py"],"function_name":[""],"function_offset":[0],"line_number":[273]},"zEgDK4qMawUAQZjg5YHywwAAAAAAAGC0":{"file_name":["genericpath.py"],"function_name":["isdir"],"function_offset":[6],"line_number":[45]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKl7Y":{"file_name":[],"function_name":["__do_sys_newstat"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKt69":{"file_name":[],"function_name":["path_lookupat"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKtXX":{"file_name":[],"function_name":["walk_component"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAKsux":{"file_name":[],"function_name":["lookup_fast"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAK8mW":{"file_name":[],"function_name":["__d_lookup_rcu"],"function_offset":[],"line_number":[]},"ik6PIX946fW_erE7uBJlVQAAAAAAAELu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAEr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAOFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAAbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"VY0EiAO0DxwLRTE4PfFhdwAAAAAAAN_6":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[6],"line_number":[7]},"2AkHKX3hFovQqnWGTZG4BAAAAAAAALbW":{"file_name":["base.py"],"function_name":[""],"function_offset":[44],"line_number":[45]},"JEYMXKhPKBKP90oNIKO6WwAAAAAAABJe":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[33],"line_number":[34]},"Fq3uvTWKo9OreZfu-LOYYQAAAAAAAGOG":{"file_name":["defaults.py"],"function_name":[""],"function_offset":[2553],"line_number":[2554]},"f2CfX6aaJGZ4Su3cCY2vCQAAAAAAAOFk":{"file_name":["style.py"],"function_name":[""],"function_offset":[506],"line_number":[507]},"yxUFWTEZsQP-FeNV2RKnFQAAAAAAAJIa":{"file_name":["enum.py"],"function_name":["__prepare__"],"function_offset":[13],"line_number":[483]},"Q2lceMFM0t8w5Hdokg8e8AAAAAAAABv6":{"file_name":["enum.py"],"function_name":["__setitem__"],"function_offset":[93],"line_number":[446]},"a5aMcPOeWx28QSVng73nBQAAAAAAAABK":{"file_name":["aws"],"function_name":[""],"function_offset":[13],"line_number":[27]},"inI9W0bfekFTCpu0ceKTHgAAAAAAAAAG":{"file_name":["aws"],"function_name":["main"],"function_offset":[1],"line_number":[23]},"RPwdw40HEBL87wRkKV2ozwAAAAAAAAAS":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[1],"line_number":[86]},"pT2bgvKv3bKR6LMAYtKFRwAAAAAAAAAI":{"file_name":["clidriver.py"],"function_name":["main"],"function_offset":[2],"line_number":[166]},"Rsr7q4vCSh2ppRtyNkwZAAAAAAAAAAAS":{"file_name":["clidriver.py"],"function_name":["_do_main"],"function_offset":[3],"line_number":[185]},"cKQfWSgZRgu_1Goz5QGSHwAAAAAAAABQ":{"file_name":["clidriver.py"],"function_name":["create_clidriver"],"function_offset":[8],"line_number":[97]},"T2fhmP8acUvRZslK7YRDPwAAAAAAAAAY":{"file_name":["plugin.py"],"function_name":["load_plugins"],"function_offset":[23],"line_number":[48]},"lrxXzNEmAlflj7bCNDjxdAAAAAAAAAAE":{"file_name":["plugin.py"],"function_name":["_load_plugins"],"function_offset":[1],"line_number":[62]},"SMoSw8cr-PdrIATvljOPrQAAAAAAAABU":{"file_name":["plugin.py"],"function_name":["_import_plugins"],"function_offset":[8],"line_number":[76]},"xaCec3W8F6xlvd_EISI7vwAAAAAAAAB0":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[15],"line_number":[28]},"QCNrAtEDVSYrGKsToy3LYAAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[13]},"ocuGLNOciiOP6W8cfH2-qwAAAAAAAABg":{"file_name":["package.py"],"function_name":[""],"function_offset":[12],"line_number":[26]},"bjI4Jot-SXYwqfMr0sl7XgAAAAAAAAA8":{"file_name":["s3uploader.py"],"function_name":[""],"function_offset":[8],"line_number":[22]},"zjBJSIgrJ7WBnrV9WxdKEQAAAAAAAAB8":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[130],"line_number":[143]},"9-_Y7FNFlkawnHBUI4HVnAAAAAAAAAB8":{"file_name":["compat.py"],"function_name":[""],"function_offset":[81],"line_number":[94]},"suQJt7m9qyZP3i8d45HwBQAAAAAAAABk":{"file_name":["managers.py"],"function_name":[""],"function_offset":[18],"line_number":[29]},"fiyOjJSGn-Eja0GP7-aFCgAAAAAAAACM":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[25],"line_number":[1058]},"5w2Emmm2pdiPFBnzFSNcKgAAAAAAAABM":{"file_name":["connection.py"],"function_name":[""],"function_offset":[11],"line_number":[21]},"XnUkhGmJNwiHTUPaIuILqgAAAAAAAAAi":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[7],"line_number":[666]},"1bzyoH1Mbbzc-oKA3fR-7QAAAAAAAAAY":{"file_name":["_bootstrap.py"],"function_name":["module_from_spec"],"function_offset":[7],"line_number":[565]},"BXKFYOU6E7YaW5MDpfBf8wAAAAAAAAAK":{"file_name":["_bootstrap_external.py"],"function_name":["create_module"],"function_offset":[2],"line_number":[1173]},"PVZV2uq5ZRt-FFaczL10BAAAAAAAABCQ":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/dlfcn/dlopen.c"],"function_name":["__dlopen"],"function_offset":[],"line_number":[87]},"PVZV2uq5ZRt-FFaczL10BAAAAAAAABZ0":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/dlfcn/dlerror.c"],"function_name":["_dlerror_run"],"function_offset":[],"line_number":[163]},"Z_CHd3Zjsh2cWE2NSdbiNQAAAAAAEoNz":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-error-skeleton.c"],"function_name":["__GI__dl_catch_error"],"function_offset":[],"line_number":[198]},"PVZV2uq5ZRt-FFaczL10BAAAAAAAABAF":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/dlfcn/dlopen.c"],"function_name":["dlopen_doit"],"function_offset":[],"line_number":[66]},"3nN3bymnZ8E42aLEtgglmAAAAAAAASmo":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-open.c"],"function_name":["_dl_open"],"function_offset":[],"line_number":[649]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAS7f":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-open.c"],"function_name":["dl_open_worker"],"function_offset":[],"line_number":[269]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAM3G":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-deps.c"],"function_name":["_dl_map_object_deps"],"function_offset":[],"line_number":[253]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAMtx":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-deps.c"],"function_name":["openaux"],"function_offset":[],"line_number":[64]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAINe":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-load.c"],"function_name":["_dl_map_object"],"function_offset":[],"line_number":[1943]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAFxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGJU":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAISm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAGRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAPqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAFFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"93AmMdBRQTTNSFcMQ_YwdgAAAAAAAFCy":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[22],"line_number":[23]},"29RxCcCS3qayH8Wz47EBXQAAAAAAAIzc":{"file_name":["_adapters.py"],"function_name":["CompatibilityFiles"],"function_offset":[81],"line_number":[123]},"mBpjyQvq6ftE7Wm1BUpcFgAAAAAAAPGy":{"file_name":["abc.py"],"function_name":["__new__"],"function_offset":[3],"line_number":[108]},"IWme5rHQfgYd-9YstXSeGAAAAAAAAE_C":{"file_name":["typing.py"],"function_name":["__init_subclass__"],"function_offset":[57],"line_number":[2092]},"79pMuEW6_o55K0jHDJ-2dQAAAAAAAAA8":{"file_name":["clidriver.py"],"function_name":[""],"function_offset":[8],"line_number":[21]},"mHiYHSEggclUi1ELZIxq4AAAAAAAAABA":{"file_name":["session.py"],"function_name":[""],"function_offset":[13],"line_number":[27]},"_GLtmpX5QFDXCzO6KY35mAAAAAAAAAAU":{"file_name":["client.py"],"function_name":[""],"function_offset":[3],"line_number":[16]},"CF4TEudhKTIdEsoPP0l9iwAAAAAAAAAc":{"file_name":["waiter.py"],"function_name":[""],"function_offset":[4],"line_number":[17]},"5t_H28X3eSBfyQs-F2v7cAAAAAAAAAAM":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[2],"line_number":[15]},"z0g3aE3w1Ik-suUArUsniAAAAAAAAAAE":{"file_name":["service.py"],"function_name":[""],"function_offset":[0],"line_number":[13]},"1VzILo0_Ivjn6dWL8BqT1AAAAAAAAAAM":{"file_name":["restdoc.py"],"function_name":[""],"function_offset":[2],"line_number":[15]},"rTTtzMEIQRrn8RDFEbl1zwAAAAAAAACc":{"file_name":["compat.py"],"function_name":[""],"function_offset":[17],"line_number":[31]},"zjk1GYHhesH1oTuILj3ToAAAAAAAAAAw":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[10],"line_number":[11]},"r63cbyeLjspI6IMVvcBjIgAAAAAAAAAQ":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[2],"line_number":[3]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAHxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAIRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAABqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"y4VaggFtn5eGbiM4h45zCgAAAAAAAIhi":{"file_name":["formatter.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"aovhV1VhdNHhPwAmk_rOhgAAAAAAAB0G":{"file_name":["table.py"],"function_name":[""],"function_offset":[189],"line_number":[190]},"px3SfTg4DYOeiT_Yemty2wAAAAAAAAye":{"file_name":["."],"function_name":["utils"],"function_offset":[5],"line_number":[6]},"opI8K6Q9RBhmYCrRVwNTgAAAAAAAAPGW":{"file_name":["initialise.py"],"function_name":[""],"function_offset":[120],"line_number":[121]},"cVEUVwL4zVVcM9r_4PTCXAAAAAAAAJce":{"file_name":["ansitowin32.py"],"function_name":[""],"function_offset":[71],"line_number":[72]},"GGxNFCJdZtgXLG8zgUfn_QAAAAAAAD2y":{"file_name":["ansitowin32.py"],"function_name":["AnsiToWin32"],"function_offset":[182],"line_number":[254]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAANtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAKn8":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAALGC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAAJBk":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAAPDo":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAALts":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"ZVYMRqiL5oPAMqs8XcON8QAAAAAAAJl2":{"file_name":["prompttoolkit.py"],"function_name":[""],"function_offset":[58],"line_number":[59]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAHj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAJtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"V6gUZHzBRISi-Z25klK5DQAAAAAAACri":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[37],"line_number":[38]},"zWNEoAKVTnnzSns045VKhwAAAAAAAIsa":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[15],"line_number":[16]},"n4Ao4OZE2osF0FygfcWo3gAAAAAAACea":{"file_name":["application.py"],"function_name":[""],"function_offset":[237],"line_number":[238]},"1y9WuJpjgBMcQb3shY5phQAAAAAAAOMe":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[45],"line_number":[46]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAALkq":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"n4Ao4OZE2osF0FygfcWo3gAAAAAAACw2":{"file_name":["application.py"],"function_name":[""],"function_offset":[237],"line_number":[238]},"NGbZlnLCqeq3LFq89r_SpQAAAAAAAD0-":{"file_name":["buffer.py"],"function_name":[""],"function_offset":[191],"line_number":[192]},"PmhxUKv5sePRxhCBONca8gAAAAAAAAD6":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[19],"line_number":[20]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAMmC":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"xDXQtI2vA5YySwpx7QFiwAAAAAAAALuy":{"file_name":["popen_forkserver.py"],"function_name":[""],"function_offset":[27],"line_number":[28]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAAHRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAKtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"fSQ747oLNh0c0zFQjsVRWgAAAAAAALk2":{"file_name":["forkserver.py"],"function_name":[""],"function_offset":[80],"line_number":[81]},"yp8MidCGMe4czbl-NigsYQAAAAAAAFOm":{"file_name":["connection.py"],"function_name":[""],"function_offset":[524],"line_number":[525]},"2noK4QoWxdzASRHkjOFwVAAAAAAAADGK":{"file_name":["tempfile.py"],"function_name":[""],"function_offset":[547],"line_number":[548]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAMFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAANmC":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"mfGJjedIJMvFXgX3QuTMfQAAAAAAAPDW":{"file_name":["core.py"],"function_name":[""],"function_offset":[275],"line_number":[276]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAH3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAALSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAIxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAJRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAACqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"9NWoah56eYULAP_zGE9PuwAAAAAAAPHC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[101],"line_number":[102]},"IKrIDHd5n47PpDQsRXxvvgAAAAAAAGmC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[81],"line_number":[82]},"oG7568kMJujZxPJfj7VMjAAAAAAAAAjO":{"file_name":["frontend.py"],"function_name":[""],"function_offset":[390],"line_number":[391]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAABs":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAHKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAPxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAARY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAJqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"ywhwSu3fiEha0QwvHF6X9wAAAAAAAHFE":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[43],"line_number":[373]},"HENgRXYeEs7mDD8Gk_MNmgAAAAAAAKju":{"file_name":["help.py"],"function_name":[""],"function_offset":[202],"line_number":[203]},"fFS0upy5lIaT99RhlTN5LQAAAAAAAEW2":{"file_name":["clidocs.py"],"function_name":[""],"function_offset":[399],"line_number":[400]},"lSdGU4igLMOpLhL_6XP15wAAAAAAADZ-":{"file_name":["argprocess.py"],"function_name":[""],"function_offset":[278],"line_number":[279]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAO3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAACSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"QAp_Nt6XUeNsCXnAUgW7XgAAAAAAABKa":{"file_name":["shorthand.py"],"function_name":[""],"function_offset":[132],"line_number":[133]},"20O937106XMbOD0LQR4SPwAAAAAAAIVS":{"file_name":["shorthand.py"],"function_name":["ShorthandParser"],"function_offset":[257],"line_number":[379]},"gPzb0fXoBe1225fbKepMRAAAAAAAAGUy":{"file_name":["shorthand.py"],"function_name":["__init__"],"function_offset":[2],"line_number":[53]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAJn8":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAKGo":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"OHQX9IWLaZElAgxGbX3P5gAAAAAAACVG":{"file_name":["_compiler.py"],"function_name":["_code"],"function_offset":[13],"line_number":[584]},"E2b-mzlh_8261-JxcySn-AAAAAAAACfk":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAACxC":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAACMC":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"JrU1PwRIxl_8SXdnTESnogAAAAAAADMc":{"file_name":["_compiler.py"],"function_name":["_optimize_charset"],"function_offset":[138],"line_number":[379]},"HENgRXYeEs7mDD8Gk_MNmgAAAAAAAH1O":{"file_name":["help.py"],"function_name":[""],"function_offset":[202],"line_number":[203]},"fFS0upy5lIaT99RhlTN5LQAAAAAAACWm":{"file_name":["clidocs.py"],"function_name":[""],"function_offset":[399],"line_number":[400]},"lSdGU4igLMOpLhL_6XP15wAAAAAAABZu":{"file_name":["argprocess.py"],"function_name":[""],"function_offset":[278],"line_number":[279]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAAGRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"QAp_Nt6XUeNsCXnAUgW7XgAAAAAAAJC6":{"file_name":["shorthand.py"],"function_name":[""],"function_offset":[132],"line_number":[133]},"20O937106XMbOD0LQR4SPwAAAAAAAGVC":{"file_name":["shorthand.py"],"function_name":["ShorthandParser"],"function_offset":[257],"line_number":[379]},"gPzb0fXoBe1225fbKepMRAAAAAAAAKLy":{"file_name":["shorthand.py"],"function_name":["__init__"],"function_offset":[2],"line_number":[53]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAANSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAANJo":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"OHQX9IWLaZElAgxGbX3P5gAAAAAAAKVG":{"file_name":["_compiler.py"],"function_name":["_code"],"function_offset":[13],"line_number":[584]},"E2b-mzlh_8261-JxcySn-AAAAAAAANJE":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAANai":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"E2b-mzlh_8261-JxcySn-AAAAAAAAM1i":{"file_name":["_compiler.py"],"function_name":["_compile"],"function_offset":[18],"line_number":[55]},"JrU1PwRIxl_8SXdnTESnogAAAAAAAOom":{"file_name":["_compiler.py"],"function_name":["_optimize_charset"],"function_offset":[138],"line_number":[379]},"ik6PIX946fW_erE7uBJlVQAAAAAAADLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAANFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"zWCVT22bUHN0NWIQIBSuKgAAAAAAAOm6":{"file_name":["defaults.py"],"function_name":[""],"function_offset":[32],"line_number":[33]},"zj3hc8VBXxWxcbGVwJZYLAAAAAAAAOye":{"file_name":["basic.py"],"function_name":[""],"function_offset":[31],"line_number":[32]},"EHb2BWbkIivImSAfaUtw-AAAAAAAAPyQ":{"file_name":["named_commands.py"],"function_name":[""],"function_offset":[586],"line_number":[587]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAADj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAFtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"-7Nhzq0bVRejx7IVqpbbZQAAAAAAAKW-":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[96],"line_number":[97]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAAY4":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"M_-aGo2vWhLu7lS5grLv9wAAAAAAAEFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[150]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAALmC":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"ik6PIX946fW_erE7uBJlVQAAAAAAAGLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAAFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"OlTvyWQFXjOweJcs3kiGygAAAAAAAMui":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[155],"line_number":[156]},"N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAPB2":{"file_name":["connection.py"],"function_name":[""],"function_offset":[87],"line_number":[88]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAGj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAItm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"1eW8DnM19kiBGqMWGVkHPAAAAAAAACJC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[23],"line_number":[24]},"2kgk5qEgdkkSXT9cIdjqxQAAAAAAAEYy":{"file_name":["ssl_.py"],"function_name":[""],"function_offset":[258],"line_number":[259]},"MsEmysGbXhMvgdbwhcZDCgAAAAAAAA8c":{"file_name":["url.py"],"function_name":[""],"function_offset":[238],"line_number":[239]},"jtp3NDFNJGnK6sK5oOFo8QAAAAAAAJtG":{"file_name":["__init__.py"],"function_name":["compile"],"function_offset":[2],"line_number":[227]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAAFSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAAFJC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAAPpU":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAAHHY":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAFcu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAEZu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"Gxt7_MN7XgUOe9547JcHVQAAAAAAAAd2":{"file_name":["_parser.py"],"function_name":["__len__"],"function_offset":[1],"line_number":[159]},"LEy-wm0GIvRoYVAga55HiwAAAAAAAExO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAOqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"2L4SW1rQgEVXRj3pZAI3nQAAAAAAAIla":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[97],"line_number":[98]},"Bd3XiVd_ucXTo7t4NwSjLAAAAAAAAD3Q":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1241]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAHSm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"7bd6QJSfWZZfOOpDMHqLMAAAAAAAAPNq":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[319],"line_number":[320]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAAFOq":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"ZPxtkRXufuVf4tqV5k5k2QAAAAAAAGcA":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1097]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAAKD4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAACAK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAAKYk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAANee":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAIW-":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAAJj8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"fj70ljef7nDHOqVJGSIoEQAAAAAAANmS":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"p5XvqZgoydjTl8thPo5KGwAAAAAAAIFW":{"file_name":["pyimod02_importers.py"],"function_name":["get_code"],"function_offset":[13],"line_number":[158]},"oR5jBuG11Az1rZkKaPBmAgAAAAAAAIKK":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[37],"line_number":[202]},"GP7h96O0_ppGVtc-UpQQIQAAAAAAAC66":{"file_name":["handlers.py"],"function_name":[""],"function_offset":[105],"line_number":[106]},"3HhVgGD2yvuFLpoZq7RfKwAAAAAAAOnq":{"file_name":["cloudfront.py"],"function_name":[""],"function_offset":[179],"line_number":[180]},"-BjW54fwMksXBor9R-YN9wAAAAAAAHD-":{"file_name":["ssh.py"],"function_name":[""],"function_offset":[575],"line_number":[576]},"Npep8JfxWDWZ3roJSD7jPgAAAAAAADRw":{"file_name":["_bootstrap.py"],"function_name":["_handle_fromlist"],"function_offset":[34],"line_number":[1243]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAGtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"A2oiHVwisByxRn5RDT4LjAAAAAAAoBBe":{"file_name":[],"function_name":["page_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAABnSX":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAItm_":{"file_name":[],"function_name":["handle_mm_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAItAx":{"file_name":[],"function_name":["__handle_mm_fault"],"function_offset":[],"line_number":[]},"A2oiHVwisByxRn5RDT4LjAAAAAAAglhf":{"file_name":[],"function_name":["_raw_spin_lock"],"function_offset":[],"line_number":[]},"ik6PIX946fW_erE7uBJlVQAAAAAAAPLu":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1191]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPr4":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"J1eggTwSzYdi9OsSu1q37gAAAAAAAJFw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"CNgPIV65Suq5GVbO7eJK7gAAAAAAAMbc":{"file_name":["pyimod02_importers.py"],"function_name":["exec_module"],"function_offset":[30],"line_number":[352]},"OlTvyWQFXjOweJcs3kiGygAAAAAAACIS":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[155],"line_number":[156]},"N2mxDWkAZe8CHgZMQpxZ7AAAAAAAAFB2":{"file_name":["connection.py"],"function_name":[""],"function_offset":[87],"line_number":[88]},"r3Nzr2WeUwu3gjU4N-rWyAAAAAAAAPj0":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1156]},"eV_m28NnKeeTL60KO2H3SAAAAAAAABtm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"1eW8DnM19kiBGqMWGVkHPAAAAAAAAGJC":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[23],"line_number":[24]},"2kgk5qEgdkkSXT9cIdjqxQAAAAAAAJyi":{"file_name":["ssl_.py"],"function_name":[""],"function_offset":[258],"line_number":[259]},"MsEmysGbXhMvgdbwhcZDCgAAAAAAAGWM":{"file_name":["url.py"],"function_name":[""],"function_offset":[238],"line_number":[239]},"7R-mHvx47pWvF_ng7rKpHwAAAAAAALSc":{"file_name":["__init__.py"],"function_name":["_compile"],"function_offset":[27],"line_number":[299]},"_lF8o5tJDcePvza_IYtgSQAAAAAAALJC":{"file_name":["_compiler.py"],"function_name":["compile"],"function_offset":[21],"line_number":[759]},"TRd7r6mvdzYdjMdTtebtwwAAAAAAAFpU":{"file_name":["_parser.py"],"function_name":["parse"],"function_offset":[25],"line_number":[995]},"bgsqxCFBdtyNwHEAo-3p1wAAAAAAANHY":{"file_name":["_parser.py"],"function_name":["_parse_sub"],"function_offset":[58],"line_number":[505]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAALcu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"5PnOjelHYJZ6ovJAXK5uiQAAAAAAAKZu":{"file_name":["_parser.py"],"function_name":["_parse"],"function_offset":[0],"line_number":[507]},"zjk1GYHhesH1oTuILj3ToAAAAAAAAABI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[12],"line_number":[13]},"qkYSh95E1urNTie_gKbr7wAAAAAAAABY":{"file_name":["connectionpool.py"],"function_name":[""],"function_offset":[11],"line_number":[12]},"V8ldXm9NGXsJ182jEHEsUwAAAAAAAAB8":{"file_name":["connection.py"],"function_name":[""],"function_offset":[14],"line_number":[15]},"xVaa0cBWNcFeS-8zFezQgAAAAAAAAABI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[7],"line_number":[8]},"UBINlIxj95Sa_x2_k5IddAAAAAAAAAB4":{"file_name":["ssl_.py"],"function_name":[""],"function_offset":[16],"line_number":[17]},"gRRk0W_9P4SGZLXFJ5KU8QAAAAAAAAFi":{"file_name":["url.py"],"function_name":[""],"function_offset":[70],"line_number":[71]},"VIK6i3XoO6nxn9WkNabugAAAAAAAAAAG":{"file_name":["re.py"],"function_name":["compile"],"function_offset":[2],"line_number":[252]},"SGPpASrxkViIc4Sq7x-WYQAAAAAAAABs":{"file_name":["re.py"],"function_name":["_compile"],"function_offset":[15],"line_number":[304]},"9xG1GRY3A4PQMfXDNvrOxQAAAAAAAAAU":{"file_name":["sre_compile.py"],"function_name":["compile"],"function_offset":[5],"line_number":[764]},"cbxfeE2AkqKne6oKUxdB6gAAAAAAAAAy":{"file_name":["sre_parse.py"],"function_name":["parse"],"function_offset":[11],"line_number":[948]},"aEZUIXI_cV9kZCa4-U1NsQAAAAAAAAAy":{"file_name":["sre_parse.py"],"function_name":["_parse_sub"],"function_offset":[8],"line_number":[443]},"MebnOxK5WOhP29sl19JefwAAAAAAAAua":{"file_name":["sre_parse.py"],"function_name":["_parse"],"function_offset":[341],"line_number":[834]},"MebnOxK5WOhP29sl19JefwAAAAAAAAKs":{"file_name":["sre_parse.py"],"function_name":["_parse"],"function_offset":[98],"line_number":[591]},"LEy-wm0GIvRoYVAga55HiwAAAAAAABxO":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load"],"function_offset":[24],"line_number":[1189]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAACRY":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"J1eggTwSzYdi9OsSu1q37gAAAAAAALqw":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"7bd6QJSfWZZfOOpDMHqLMAAAAAAAAONq":{"file_name":["exceptions.py"],"function_name":[""],"function_offset":[319],"line_number":[320]},"wdQNqQ99iFSdp4ceNJQKBgAAAAAAACOq":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[34],"line_number":[1154]},"ZPxtkRXufuVf4tqV5k5k2QAAAAAAADcA":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[43],"line_number":[1097]},"8R2Lkqe-tYqq-plJ22QNzAAAAAAAAOD4":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[3],"line_number":[193]},"h0l-9tGi18mC40qpcJbyDwAAAAAAAPAK":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[24],"line_number":[446]},"5EZV-eYYYtY-VAcSTmCvtgAAAAAAACYk":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"705jmHYNd7I4Z4L4c0vfiAAAAAAAAFee":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[4],"line_number":[124]},"TBeSzkyqIwKL8td602zDjAAAAAAAAMW-":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"NH3zvSjFAfTSy6bEocpNyQAAAAAAABj8":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[3],"line_number":[88]},"fj70ljef7nDHOqVJGSIoEQAAAAAAAMmS":{"file_name":["client.py"],"function_name":[""],"function_offset":[211],"line_number":[212]},"zo4mnjDJ1PlZka7jS9k2BAAAAAAAAPX-":{"file_name":["ssl.py"],"function_name":[""],"function_offset":[780],"line_number":[781]},"J1eggTwSzYdi9OsSu1q37gAAAAAAALn4":{"file_name":["_bootstrap.py"],"function_name":["_load_unlocked"],"function_offset":[41],"line_number":[707]},"0S3htaCNkzxOYeavDR1GTQAAAAAAANe4":{"file_name":["_bootstrap.py"],"function_name":["module_from_spec"],"function_offset":[14],"line_number":[580]},"rBzW547V0L_mH4nnWK1FUQAAAAAAAHTA":{"file_name":["_bootstrap_external.py"],"function_name":["create_module"],"function_offset":[6],"line_number":[1237]},"eV_m28NnKeeTL60KO2H3SAAAAAAAAESm":{"file_name":["_bootstrap.py"],"function_name":["_call_with_frames_removed"],"function_offset":[8],"line_number":[241]},"3nN3bymnZ8E42aLEtgglmAAAAAAAATA-":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-open.c"],"function_name":["dl_open_worker"],"function_offset":[],"line_number":[424]},"3nN3bymnZ8E42aLEtgglmAAAAAAAALbA":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-reloc.c"],"function_name":["_dl_relocate_object"],"function_offset":[],"line_number":[160]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAJyS":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-lookup.c"],"function_name":["_dl_lookup_symbol_x"],"function_offset":[],"line_number":[833]},"3nN3bymnZ8E42aLEtgglmAAAAAAAAJel":{"file_name":["/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-lookup.c","/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-lookup.c","/usr/src/debug/glibc-2.26-193-ga0bc5dd3be/elf/dl-lookup.c"],"function_name":["do_lookup_x","do_lookup_unique","enter_unique_sym"],"function_offset":[],"line_number":[544,322,197]},"ApbUUYSZlAYucbB88oZaGwAAAAAAAADU":{"file_name":["application.py"],"function_name":[""],"function_offset":[40],"line_number":[41]},"bAXCoU3-CU0WlRxl5l1tmwAAAAAAAADk":{"file_name":["buffer.py"],"function_name":[""],"function_offset":[35],"line_number":[36]},"qordvIiilnF7CmkWCAd7eAAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"iWpqwwcHV8E8OOnqGCYj9gAAAAAAAABc":{"file_name":["base.py"],"function_name":[""],"function_offset":[8],"line_number":[9]},"M61AJsljWf0TM7wD6IJVZwAAAAAAAAAI":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[12],"line_number":[13]},"ED3bhsHkhBwZ5ynmMnkPRAAAAAAAAAAs":{"file_name":["ansi.py"],"function_name":[""],"function_offset":[3],"line_number":[4]},"cZ-wyq9rmPl5QnqP0Smp6QAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"GLV-c6bk0E-nhaaCp6u20wAAAAAAAAAo":{"file_name":["base.py"],"function_name":[""],"function_offset":[6],"line_number":[7]},"c_1Yb4rio2EAH6C9SFwQogAAAAAAAABE":{"file_name":["cursor_shapes.py"],"function_name":[""],"function_offset":[5],"line_number":[6]},"O4ILxZswquMzuET9RRf5QAAAAAAAAAAE":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[0],"line_number":[1]},"coeZ_4yf5sOePIKKlm8FNQAAAAAAAACm":{"file_name":["pyimod01_archive.py"],"function_name":["extract"],"function_offset":[16],"line_number":[302]},"GLV-c6bk0E-nhaaCp6u20wAAAAAAAABA":{"file_name":["base.py"],"function_name":[""],"function_offset":[8],"line_number":[9]},"rJZ4aC9w8bMvzrC0ApyIjgAAAAAAAAAo":{"file_name":["__init__.py"],"function_name":[""],"function_offset":[11],"line_number":[12]},"TC9v9fO0nTP4oypYCgB_1QAAAAAAAAAw":{"file_name":["defaults.py"],"function_name":[""],"function_offset":[7],"line_number":[8]},"piWSMQrh4r040D0BPNaJvwAAAAAAoBCe":{"file_name":[],"function_name":["async_page_fault"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAABncH":{"file_name":[],"function_name":["__do_page_fault"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAIsvf":{"file_name":[],"function_name":["handle_mm_fault"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAIsF6":{"file_name":[],"function_name":["__handle_mm_fault"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJj7r":{"file_name":[],"function_name":["alloc_pages_vma"],"function_offset":[],"line_number":[]},"piWSMQrh4r040D0BPNaJvwAAAAAAJIxB":{"file_name":[],"function_name":["get_page_from_freelist"],"function_offset":[],"line_number":[]},"-pUZ8YYbKKOu4w9rcMsXSwAAAAAAAACK":{"file_name":["_bootstrap.py"],"function_name":["_find_and_load_unlocked"],"function_offset":[15],"line_number":[982]},"JaHOMfnX0DG4ZnNTpPORVAAAAAAAAACK":{"file_name":["_bootstrap.py"],"function_name":["_find_spec"],"function_offset":[24],"line_number":[925]},"MepUYc0jU0AjPrrjuvTgGgAAAAAAAAAQ":{"file_name":["six.py"],"function_name":["find_spec"],"function_offset":[2],"line_number":[192]},"yWt46REABLfKH6PXLAE18AAAAAAAAABk":{"file_name":["_bootstrap.py"],"function_name":["spec_from_loader"],"function_offset":[16],"line_number":[431]},"VQs3Erq77xz92EfpT8sTKwAAAAAAAAAM":{"file_name":["six.py"],"function_name":["is_package"],"function_offset":[7],"line_number":[222]},"n7IiY_TlCWEfi47-QpeCLwAAAAAAAAAE":{"file_name":["six.py"],"function_name":["__getattr__"],"function_offset":[1],"line_number":[121]},"Ua3frjTXWBuWpTsQD8aKeAAAAAAAAAAG":{"file_name":["six.py"],"function_name":["_resolve"],"function_offset":[1],"line_number":[118]},"GtyMRLq4aaDvuQ4C3N95mAAAAAAAAAAE":{"file_name":["six.py"],"function_name":["_import_module"],"function_offset":[2],"line_number":[87]},"clFhkTaiph2aOjCNuZDWKAAAAAAAAAAI":{"file_name":["client.py"],"function_name":[""],"function_offset":[70],"line_number":[71]},"DLEY7W0VXWLE5Ol-plW-_wAAAAAAAAAg":{"file_name":["parser.py"],"function_name":[""],"function_offset":[7],"line_number":[12]},"RY-vzTa9LfseI7kmcIcbgQAAAAAAAAAY":{"file_name":["feedparser.py"],"function_name":[""],"function_offset":[21],"line_number":[26]},"-gq3a70QOgdn9HetYyf2OgAAAAAAAADS":{"file_name":["errors.py"],"function_name":[""],"function_offset":[51],"line_number":[56]}},"executables":{"FWZ9q3TQKZZok58ua1HDsg":"pf-debug-metadata-service","B8JRxL079xbhqQBqGvksAg":"kubelet","edNJ10OjHiWc5nzuTQdvig":"linux-vdso.so.1","piWSMQrh4r040D0BPNaJvw":"vmlinux","QvG8QEGAld88D676NL_Y2Q":"filebeat","MNBJ5seVz_ocW6tcr1HSmw":"metricbeat","QaIvzvU8UoclQMd_OMt-Pg":"elastic-operator","w5zBqPf1_9mIVEf-Rn7EdA":"systemd","Z_CHd3Zjsh2cWE2NSdbiNQ":"libc-2.26.so","OTWX4UsOVMrSIF5cD4zUzg":"libmount.so.1.1.0","v6HIzNa4K6G4nRP9032RIA":"dockerd","hc6JHMKlLXjOZcU9MGxvfg":"kube-proxy","A2oiHVwisByxRn5RDT4LjA":"vmlinux","wfA2BgwfDNXUWsxkJ083Rw":"kubelet","9LzzIocepYcOjnUsLlgOjg":"vmlinux","-pk6w5puGcp-wKnQ61BZzQ":"kubelet","ew01Dk0sWZctP-VaEpavqQ":"vmlinux","YsKzCJ9e4eZnuT00vj7Pcw":"python2.7","N4ILulabOfF5MnyRJbvDXw":"libpython2.7.so.1.0","SbPwzb_Kog2bWn8uc7xhDQ":"aws","xLxcEbwnZ5oNrk99ZsxcSQ":"libpython3.11.so.1.0","aUXpdArtZf510BJKvwiFDw":"veth","WpYcHtr4qx88B8CBJZ2GTw":"aws","-Z7SlEXhuy5tL2BF-xmy3g":"libpython3.11.so.1.0","pRLjmMO0U8sO4DFopfFU5g":"metrics-server","G68hjsyagwq6LpWrMjDdng":"libpython3.9.so.1.0","-V-5ede56KMAXhjFbz84Sw":"csi-provisioner","dGWvVtQJJ5wuqNyQVpi8lA":"zlib.cpython-311-x86_64-linux-gnu.so","jaBVtokSUzfS97d-XKjijg":"libz.so.1","ASi9f26ltguiwFajNwOaZw":"zlib.cpython-311-x86_64-linux-gnu.so","PVZV2uq5ZRt-FFaczL10BA":"libdl-2.26.so","3nN3bymnZ8E42aLEtgglmA":"ld-2.26.so","EX9l-cE0x8X9W8uz4iKUfw":"zlib.cpython-39-x86_64-linux-gnu.so"},"total_frames":150718,"sampling_rate":0.008000000000000002} diff --git a/x-pack/plugins/profiling/common/callee.test.ts b/packages/kbn-profiling-utils/common/callee.test.ts similarity index 90% rename from x-pack/plugins/profiling/common/callee.test.ts rename to packages/kbn-profiling-utils/common/callee.test.ts index a796062948b33..431f914bd6a10 100644 --- a/x-pack/plugins/profiling/common/callee.test.ts +++ b/packages/kbn-profiling-utils/common/callee.test.ts @@ -1,15 +1,15 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { sum } from 'lodash'; import { createCalleeTree } from './callee'; import { decodeStackTraceResponse } from './stack_traces'; - import { stackTraceFixtures } from './__fixtures__/stacktraces'; describe('Callee operations', () => { diff --git a/x-pack/plugins/profiling/common/callee.ts b/packages/kbn-profiling-utils/common/callee.ts similarity index 86% rename from x-pack/plugins/profiling/common/callee.ts rename to packages/kbn-profiling-utils/common/callee.ts index 68fa9170f44ec..9315548b81ef3 100644 --- a/x-pack/plugins/profiling/common/callee.ts +++ b/packages/kbn-profiling-utils/common/callee.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { createFrameGroupID, FrameGroupID } from './frame_group'; @@ -20,24 +21,48 @@ import { type NodeID = number; +/** + * Callee tree + */ export interface CalleeTree { + /** size */ Size: number; + /** edges */ Edges: Array>; - + /** file ids */ FileID: string[]; + /** frame types */ FrameType: number[]; + /** inlines */ Inline: boolean[]; + /** executable file names */ ExeFilename: string[]; + /** address or lines */ AddressOrLine: number[]; + /** function names */ FunctionName: string[]; + /** function offsets */ FunctionOffset: number[]; + /** source file names */ SourceFilename: string[]; + /** source lines */ SourceLine: number[]; - + /** total cpu */ CountInclusive: number[]; + /** self cpu */ CountExclusive: number[]; } +/** + * Create a callee tree + * @param events Map + * @param stackTraces Map + * @param stackFrames Map + * @param executables Map + * @param totalFrames number + * @param samplingRate number + * @returns + */ export function createCalleeTree( events: Map, stackTraces: Map, diff --git a/x-pack/plugins/profiling/common/elasticsearch.ts b/packages/kbn-profiling-utils/common/elasticsearch.ts similarity index 92% rename from x-pack/plugins/profiling/common/elasticsearch.ts rename to packages/kbn-profiling-utils/common/elasticsearch.ts index a47e4c018d581..c3d18c16545e1 100644 --- a/x-pack/plugins/profiling/common/elasticsearch.ts +++ b/packages/kbn-profiling-utils/common/elasticsearch.ts @@ -1,12 +1,16 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { UnionToIntersection, ValuesType } from 'utility-types'; +/** + * Profiling Elasticsearch fields + */ export enum ProfilingESField { Timestamp = '@timestamp', ContainerName = 'container.name', diff --git a/x-pack/plugins/profiling/common/flamegraph.test.ts b/packages/kbn-profiling-utils/common/flamegraph.test.ts similarity index 90% rename from x-pack/plugins/profiling/common/flamegraph.test.ts rename to packages/kbn-profiling-utils/common/flamegraph.test.ts index 8014264667239..cc6b3cca69926 100644 --- a/x-pack/plugins/profiling/common/flamegraph.test.ts +++ b/packages/kbn-profiling-utils/common/flamegraph.test.ts @@ -1,14 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { createCalleeTree } from './callee'; import { createBaseFlameGraph, createFlameGraph } from './flamegraph'; import { decodeStackTraceResponse } from './stack_traces'; - import { stackTraceFixtures } from './__fixtures__/stacktraces'; describe('Flamegraph operations', () => { diff --git a/x-pack/plugins/profiling/common/flamegraph.ts b/packages/kbn-profiling-utils/common/flamegraph.ts similarity index 80% rename from x-pack/plugins/profiling/common/flamegraph.ts rename to packages/kbn-profiling-utils/common/flamegraph.ts index 16fb8c1a396c5..20dc5419d6230 100644 --- a/x-pack/plugins/profiling/common/flamegraph.ts +++ b/packages/kbn-profiling-utils/common/flamegraph.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { CalleeTree } from './callee'; @@ -10,28 +11,49 @@ import { createFrameGroupID } from './frame_group'; import { fnv1a64 } from './hash'; import { createStackFrameMetadata, getCalleeLabel } from './profiling'; +/** + * Base Flamegraph + */ export interface BaseFlameGraph { + /** size */ Size: number; + /** edges */ Edges: number[][]; - + /** file ids */ FileID: string[]; + /** frame types */ FrameType: number[]; + /** inlines */ Inline: boolean[]; + /** executable file names */ ExeFilename: string[]; + /** address or line */ AddressOrLine: number[]; + /** function names */ FunctionName: string[]; + /** function offsets */ FunctionOffset: number[]; + /** source file names */ SourceFilename: string[]; + /** source lines */ SourceLine: number[]; - + /** total cpu */ CountInclusive: number[]; + /** self cpu */ CountExclusive: number[]; - + /** total seconds */ TotalSeconds: number; + /** sampling rate */ SamplingRate: number; } -// createBaseFlameGraph encapsulates the tree representation into a serialized form. +/** + * createBaseFlameGraph encapsulates the tree representation into a serialized form. + * @param tree CalleeTree + * @param samplingRate number + * @param totalSeconds number + * @returns BaseFlameGraph + */ export function createBaseFlameGraph( tree: CalleeTree, samplingRate: number, @@ -71,14 +93,22 @@ export function createBaseFlameGraph( return graph; } +/** Elasticsearch flamegraph */ export interface ElasticFlameGraph extends BaseFlameGraph { + /** ID */ ID: string[]; + /** Label */ Label: string[]; } -// createFlameGraph combines the base flamegraph with CPU-intensive values. -// This allows us to create a flamegraph in two steps (e.g. first on the server -// and finally in the browser). +/** + * + * createFlameGraph combines the base flamegraph with CPU-intensive values. + * This allows us to create a flamegraph in two steps (e.g. first on the server + * and finally in the browser). + * @param base BaseFlameGraph + * @returns ElasticFlameGraph + */ export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph { const graph: ElasticFlameGraph = { Size: base.Size, diff --git a/x-pack/plugins/profiling/common/frame_group.test.ts b/packages/kbn-profiling-utils/common/frame_group.test.ts similarity index 94% rename from x-pack/plugins/profiling/common/frame_group.test.ts rename to packages/kbn-profiling-utils/common/frame_group.test.ts index b5f0fddd7b903..b6bfa6161a175 100644 --- a/x-pack/plugins/profiling/common/frame_group.test.ts +++ b/packages/kbn-profiling-utils/common/frame_group.test.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { createFrameGroupID } from './frame_group'; diff --git a/packages/kbn-profiling-utils/common/frame_group.ts b/packages/kbn-profiling-utils/common/frame_group.ts new file mode 100644 index 0000000000000..56a190ee58062 --- /dev/null +++ b/packages/kbn-profiling-utils/common/frame_group.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { takeRight } from 'lodash'; +import { StackFrameMetadata } from './profiling'; + +/** Frame group ID */ +export type FrameGroupID = string; + +function stripLeadingSubdirs(sourceFileName: string) { + return takeRight(sourceFileName.split('/'), 2).join('/'); +} + +/** + * + * createFrameGroupID is the "standard" way of grouping frames, by commonly shared group identifiers. + * For ELF-symbolized frames, group by FunctionName, ExeFileName and FileID. + * For non-symbolized frames, group by FileID and AddressOrLine. + * otherwise group by ExeFileName, SourceFilename and FunctionName. + * @param fileID string + * @param addressOrLine string + * @param exeFilename string + * @param sourceFilename string + * @param functionName string + * @returns FrameGroupID + */ +export function createFrameGroupID( + fileID: StackFrameMetadata['FileID'], + addressOrLine: StackFrameMetadata['AddressOrLine'], + exeFilename: StackFrameMetadata['ExeFileName'], + sourceFilename: StackFrameMetadata['SourceFilename'], + functionName: StackFrameMetadata['FunctionName'] +): FrameGroupID { + if (functionName === '') { + return `empty;${fileID};${addressOrLine}`; + } + + if (sourceFilename === '') { + return `elf;${exeFilename};${functionName}`; + } + + return `full;${exeFilename};${functionName};${stripLeadingSubdirs(sourceFilename || '')}`; +} diff --git a/x-pack/plugins/profiling/common/functions.test.ts b/packages/kbn-profiling-utils/common/functions.test.ts similarity index 89% rename from x-pack/plugins/profiling/common/functions.test.ts rename to packages/kbn-profiling-utils/common/functions.test.ts index 1ba31d397a338..d5facff78d13b 100644 --- a/x-pack/plugins/profiling/common/functions.test.ts +++ b/packages/kbn-profiling-utils/common/functions.test.ts @@ -1,15 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { sum } from 'lodash'; - import { createTopNFunctions } from './functions'; -import { decodeStackTraceResponse } from './stack_traces'; - +import { decodeStackTraceResponse } from '..'; import { stackTraceFixtures } from './__fixtures__/stacktraces'; describe('TopN function operations', () => { diff --git a/x-pack/plugins/profiling/common/functions.ts b/packages/kbn-profiling-utils/common/functions.ts similarity index 95% rename from x-pack/plugins/profiling/common/functions.ts rename to packages/kbn-profiling-utils/common/functions.ts index 304c56b81e906..47bf0a3100077 100644 --- a/x-pack/plugins/profiling/common/functions.ts +++ b/packages/kbn-profiling-utils/common/functions.ts @@ -1,25 +1,29 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import * as t from 'io-ts'; import { sumBy } from 'lodash'; -import { createFrameGroupID, FrameGroupID } from './frame_group'; -import { - createStackFrameMetadata, - emptyExecutable, - emptyStackFrame, - emptyStackTrace, +import type { Executable, FileID, + FrameGroupID, StackFrame, StackFrameID, StackFrameMetadata, StackTrace, StackTraceID, -} from './profiling'; +} from '..'; +import { + createFrameGroupID, + createStackFrameMetadata, + emptyExecutable, + emptyStackFrame, + emptyStackTrace, +} from '..'; interface TopNFunctionAndFrameGroup { Frame: StackFrameMetadata; diff --git a/x-pack/plugins/profiling/common/hash.test.ts b/packages/kbn-profiling-utils/common/hash.test.ts similarity index 87% rename from x-pack/plugins/profiling/common/hash.test.ts rename to packages/kbn-profiling-utils/common/hash.test.ts index eaec348caa0ea..14a167d2c5998 100644 --- a/x-pack/plugins/profiling/common/hash.test.ts +++ b/packages/kbn-profiling-utils/common/hash.test.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { fnv1a64 } from './hash'; diff --git a/packages/kbn-profiling-utils/common/hash.ts b/packages/kbn-profiling-utils/common/hash.ts new file mode 100644 index 0000000000000..2c19b2f19b723 --- /dev/null +++ b/packages/kbn-profiling-utils/common/hash.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// prettier-ignore +const lowerHex = [ + '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', + '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', + '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', + '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', + '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', + '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', + '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', + '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', + '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', + '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', + 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', + 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', + 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', + 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', + 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', + 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff', +]; + +/* eslint no-bitwise: ["error", { "allow": ["^=", ">>", "&"] }] */ + +/** + * - fnv1a64 computes a 64-bit hash of a byte array using the FNV-1a hash function [1]. + * Due to the lack of a native uint64 in JavaScript, we operate on 64-bit values using an array + * of 4 uint16s instead. This method follows Knuth's Algorithm M in section 4.3.1 [2] using a + * modified multiword multiplication implementation described in [3]. The modifications include: + * - rewrite default algorithm for the special case m = n = 4 + * - unroll loops + * - simplify expressions + * - create pre-computed lookup table for serialization to hexadecimal + * 1. https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + * 2. Knuth, Donald E. The Art of Computer Programming, Volume 2, Third Edition: Seminumerical + * Algorithms. Addison-Wesley, 1998. + * 3. Warren, Henry S. Hacker's Delight. Upper Saddle River, NJ: Addison-Wesley, 2013. + * @param bytes Uint8Array + * @returns string + */ +export function fnv1a64(bytes: Uint8Array): string { + const n = bytes.length; + let [h0, h1, h2, h3] = [0x2325, 0x8422, 0x9ce4, 0xcbf2]; + let [t0, t1, t2, t3] = [0, 0, 0, 0]; + + for (let i = 0; i < n; i++) { + h0 ^= bytes[i]; + + t0 = h0 * 0x01b3; + t1 = h1 * 0x01b3; + t2 = h2 * 0x01b3; + t3 = h3 * 0x01b3; + + t1 += t0 >> 16; + t2 += t1 >> 16; + t2 += h0 * 0x0100; + t3 += h1 * 0x0100; + + h0 = t0 & 0xffff; + h1 = t1 & 0xffff; + h2 = t2 & 0xffff; + h3 = (t3 + (t2 >> 16)) & 0xffff; + } + + return ( + lowerHex[h3 >> 8] + + lowerHex[h3 & 0xff] + + lowerHex[h2 >> 8] + + lowerHex[h2 & 0xff] + + lowerHex[h1 >> 8] + + lowerHex[h1 & 0xff] + + lowerHex[h0 >> 8] + + lowerHex[h0 & 0xff] + ); +} diff --git a/x-pack/plugins/profiling/common/profiling.test.ts b/packages/kbn-profiling-utils/common/profiling.test.ts similarity index 88% rename from x-pack/plugins/profiling/common/profiling.test.ts rename to packages/kbn-profiling-utils/common/profiling.test.ts index 5115055ad3c94..e235633890069 100644 --- a/x-pack/plugins/profiling/common/profiling.test.ts +++ b/packages/kbn-profiling-utils/common/profiling.test.ts @@ -1,31 +1,21 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { - createStackFrameID, createStackFrameMetadata, FrameSymbolStatus, FrameType, - getAddressFromStackFrameID, getCalleeFunction, getCalleeSource, - getFileIDFromStackFrameID, getFrameSymbolStatus, getLanguageType, } from './profiling'; -describe('Stack frame operations', () => { - test('decode stack frame ID', () => { - const frameID = createStackFrameID('ABCDEFGHIJKLMNOPQRSTUw', 123456789); - expect(getAddressFromStackFrameID(frameID)).toEqual(123456789); - expect(getFileIDFromStackFrameID(frameID)).toEqual('ABCDEFGHIJKLMNOPQRSTUw'); - }); -}); - describe('Stack frame metadata operations', () => { test('metadata has executable and function names', () => { const metadata = createStackFrameMetadata({ diff --git a/packages/kbn-profiling-utils/common/profiling.ts b/packages/kbn-profiling-utils/common/profiling.ts new file mode 100644 index 0000000000000..955be2e1485e6 --- /dev/null +++ b/packages/kbn-profiling-utils/common/profiling.ts @@ -0,0 +1,388 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Stacktrace ID + */ +export type StackTraceID = string; +/** + * StackFrame ID + */ +export type StackFrameID = string; +/** + * File ID + */ +export type FileID = string; + +/** + * Frame type + */ +export enum FrameType { + Unsymbolized = 0, + Python, + PHP, + Native, + Kernel, + JVM, + Ruby, + Perl, + JavaScript, + PHPJIT, +} + +const frameTypeDescriptions = { + [FrameType.Unsymbolized]: '', + [FrameType.Python]: 'Python', + [FrameType.PHP]: 'PHP', + [FrameType.Native]: 'Native', + [FrameType.Kernel]: 'Kernel', + [FrameType.JVM]: 'JVM/Hotspot', + [FrameType.Ruby]: 'Ruby', + [FrameType.Perl]: 'Perl', + [FrameType.JavaScript]: 'JavaScript', + [FrameType.PHPJIT]: 'PHP JIT', +}; + +/** + * get frame type name + * @param ft FrameType + * @returns string + */ +export function describeFrameType(ft: FrameType): string { + return frameTypeDescriptions[ft]; +} + +export interface StackTraceEvent { + /** stacktrace ID */ + StackTraceID: StackTraceID; + /** count */ + Count: number; +} + +/** Stack trace */ +export interface StackTrace { + /** frame ids */ + FrameIDs: string[]; + /** file ids */ + FileIDs: string[]; + /** address or lines */ + AddressOrLines: number[]; + /** types */ + Types: number[]; +} +/** + * Empty stack trace + */ +export const emptyStackTrace: StackTrace = { + /** Frame IDs */ + FrameIDs: [], + /** File IDs */ + FileIDs: [], + /** Address or lines */ + AddressOrLines: [], + /** Types */ + Types: [], +}; + +/** Stack frame */ +export interface StackFrame { + /** file name */ + FileName: string; + /** function name */ + FunctionName: string; + /** function offset */ + FunctionOffset: number; + /** line number */ + LineNumber: number; + /** inline */ + Inline: boolean; +} + +/** + * Empty stack frame + */ +export const emptyStackFrame: StackFrame = { + /** File name */ + FileName: '', + /** Function name */ + FunctionName: '', + /** Function offset */ + FunctionOffset: 0, + /** Line number */ + LineNumber: 0, + /** Inline */ + Inline: false, +}; + +/** Executable */ +export interface Executable { + /** file name */ + FileName: string; +} + +/** + * Empty exectutable + */ +export const emptyExecutable: Executable = { + /** file name */ + FileName: '', +}; + +/** Stack frame metadata */ +export interface StackFrameMetadata { + /** StackTrace.FrameID */ + FrameID: string; + /** StackTrace.FileID */ + FileID: FileID; + /** StackTrace.Type */ + FrameType: FrameType; + /** StackFrame.Inline */ + Inline: boolean; + /** StackTrace.AddressOrLine */ + AddressOrLine: number; + /** StackFrame.FunctionName */ + FunctionName: string; + /** StackFrame.FunctionOffset */ + FunctionOffset: number; + /** should this be StackFrame.SourceID? */ + SourceID: FileID; + /** StackFrame.Filename */ + SourceFilename: string; + /** StackFrame.LineNumber */ + SourceLine: number; + /** auto-generated - see createStackFrameMetadata */ + FunctionSourceLine: number; + /** Executable.FileName */ + ExeFileName: string; + /** unused atm due to lack of symbolization metadata */ + CommitHash: string; + /** unused atm due to lack of symbolization metadata */ + SourceCodeURL: string; + /** unused atm due to lack of symbolization metadata */ + SourcePackageHash: string; + /** unused atm due to lack of symbolization metadata */ + SourcePackageURL: string; + /** unused atm due to lack of symbolization metadata */ + SamplingRate: number; +} + +/** + * create stackframe metadata + * @param options Partial + * @returns StackFrameMetadata + */ +export function createStackFrameMetadata( + options: Partial = {} +): StackFrameMetadata { + const metadata = {} as StackFrameMetadata; + + metadata.FrameID = options.FrameID ?? ''; + metadata.FileID = options.FileID ?? ''; + metadata.FrameType = options.FrameType ?? 0; + metadata.Inline = options.Inline ?? false; + metadata.AddressOrLine = options.AddressOrLine ?? 0; + metadata.FunctionName = options.FunctionName ?? ''; + metadata.FunctionOffset = options.FunctionOffset ?? 0; + metadata.SourceID = options.SourceID ?? ''; + metadata.SourceLine = options.SourceLine ?? 0; + metadata.ExeFileName = options.ExeFileName ?? ''; + metadata.CommitHash = options.CommitHash ?? ''; + metadata.SourceCodeURL = options.SourceCodeURL ?? ''; + metadata.SourceFilename = options.SourceFilename ?? ''; + metadata.SourcePackageHash = options.SourcePackageHash ?? ''; + metadata.SourcePackageURL = options.SourcePackageURL ?? ''; + metadata.SamplingRate = options.SamplingRate ?? 1.0; + + // Unknown/invalid offsets are currently set to 0. + // + // In this case we leave FunctionSourceLine=0 as a flag for the UI that the + // FunctionSourceLine should not be displayed. + // + // As FunctionOffset=0 could also be a legit value, this work-around needs + // a real fix. The idea for after GA is to change FunctionOffset=-1 to + // indicate unknown/invalid. + if (metadata.FunctionOffset > 0) { + metadata.FunctionSourceLine = metadata.SourceLine - metadata.FunctionOffset; + } else { + metadata.FunctionSourceLine = 0; + } + + return metadata; +} + +function checkIfStringHasParentheses(s: string) { + return /\(|\)/.test(s); +} + +function getFunctionName(metadata: StackFrameMetadata) { + return metadata.FunctionName !== '' && !checkIfStringHasParentheses(metadata.FunctionName) + ? `${metadata.FunctionName}()` + : metadata.FunctionName; +} + +function getExeFileName(metadata: StackFrameMetadata) { + if (metadata?.ExeFileName === undefined) { + return ''; + } + if (metadata.ExeFileName !== '') { + return metadata.ExeFileName; + } + return describeFrameType(metadata.FrameType); +} + +/** + * Get callee label + * @param metadata StackFrameMetadata + * @returns string + */ +export function getCalleeLabel(metadata: StackFrameMetadata) { + if (metadata.FunctionName !== '') { + const sourceFilename = metadata.SourceFilename; + const sourceURL = sourceFilename ? sourceFilename.split('/').pop() : ''; + return `${getExeFileName(metadata)}: ${getFunctionName(metadata)} in ${sourceURL}#${ + metadata.SourceLine + }`; + } + return getExeFileName(metadata); +} +/** + * Get callee function name + * @param frame StackFrameMetadata + * @returns string + */ +export function getCalleeFunction(frame: StackFrameMetadata): string { + // In the best case scenario, we have the file names, source lines, + // and function names. However we need to deal with missing function or + // executable info. + const exeDisplayName = frame.ExeFileName ? frame.ExeFileName : describeFrameType(frame.FrameType); + + // When there is no function name, only use the executable name + return frame.FunctionName ? exeDisplayName + ': ' + frame.FunctionName : exeDisplayName; +} +/** + * Frame symbol status + */ +export enum FrameSymbolStatus { + PARTIALLY_SYMBOLYZED = 'PARTIALLY_SYMBOLYZED', + NOT_SYMBOLIZED = 'NOT_SYMBOLIZED', + SYMBOLIZED = 'SYMBOLIZED', +} + +/** Frame symbols status params */ +interface FrameSymbolStatusParams { + /** source file name */ + sourceFilename: string; + /** source file line */ + sourceLine: number; + /** executable file name */ + exeFileName?: string; +} + +/** + * Get frame symbol status + * @param param FrameSymbolStatusParams + * @returns FrameSymbolStatus + */ +export function getFrameSymbolStatus(param: FrameSymbolStatusParams) { + const { sourceFilename, sourceLine, exeFileName } = param; + if (sourceFilename === '' && sourceLine === 0) { + if (exeFileName) { + return FrameSymbolStatus.PARTIALLY_SYMBOLYZED; + } + + return FrameSymbolStatus.NOT_SYMBOLIZED; + } + + return FrameSymbolStatus.SYMBOLIZED; +} + +const nativeLanguages = [FrameType.Native, FrameType.Kernel]; + +interface LanguageTypeParams { + /** frame type */ + frameType: FrameType; +} + +/** + * Get language type + * @param param LanguageTypeParams + * @returns string + */ +export function getLanguageType(param: LanguageTypeParams) { + return nativeLanguages.includes(param.frameType) ? 'NATIVE' : 'INTERPRETED'; +} + +/** + * Get callee source information. + * If we don't have the executable filename, display + * If no source line or filename available, display the executable offset + * @param frame StackFrameMetadata + * @returns string + */ +export function getCalleeSource(frame: StackFrameMetadata): string { + const frameSymbolStatus = getFrameSymbolStatus({ + sourceFilename: frame.SourceFilename, + sourceLine: frame.SourceLine, + exeFileName: frame.ExeFileName, + }); + + switch (frameSymbolStatus) { + case FrameSymbolStatus.NOT_SYMBOLIZED: { + // If we don't have the executable filename, display + return ''; + } + case FrameSymbolStatus.PARTIALLY_SYMBOLYZED: { + // If no source line or filename available, display the executable offset + return frame.ExeFileName + '+0x' + frame.AddressOrLine.toString(16); + } + case FrameSymbolStatus.SYMBOLIZED: { + return frame.SourceFilename + (frame.SourceLine !== 0 ? `#${frame.SourceLine}` : ''); + } + } +} + +/** + * Group stackframe by stack trace + * @param stackTraces Map + * @param stackFrames Map + * @param executables Map + * @returns Record + */ +export function groupStackFrameMetadataByStackTrace( + stackTraces: Map, + stackFrames: Map, + executables: Map +): Record { + const stackTraceMap: Record = {}; + for (const [stackTraceID, trace] of stackTraces) { + const numFramesPerTrace = trace.FrameIDs.length; + const frameMetadata = new Array(numFramesPerTrace); + for (let i = 0; i < numFramesPerTrace; i++) { + const frameID = trace.FrameIDs[i]; + const fileID = trace.FileIDs[i]; + const addressOrLine = trace.AddressOrLines[i]; + const frame = stackFrames.get(frameID) ?? emptyStackFrame; + const executable = executables.get(fileID) ?? emptyExecutable; + + frameMetadata[i] = createStackFrameMetadata({ + FrameID: frameID, + FileID: fileID, + AddressOrLine: addressOrLine, + FrameType: trace.Types[i], + Inline: frame.Inline, + FunctionName: frame.FunctionName, + FunctionOffset: frame.FunctionOffset, + SourceLine: frame.LineNumber, + SourceFilename: frame.FileName, + ExeFileName: executable.FileName, + }); + } + stackTraceMap[stackTraceID] = frameMetadata; + } + return stackTraceMap; +} diff --git a/x-pack/plugins/profiling/common/stack_traces.test.ts b/packages/kbn-profiling-utils/common/stack_traces.test.ts similarity index 97% rename from x-pack/plugins/profiling/common/stack_traces.test.ts rename to packages/kbn-profiling-utils/common/stack_traces.test.ts index 9486ba1e4b920..832ebe7bb66b4 100644 --- a/x-pack/plugins/profiling/common/stack_traces.test.ts +++ b/packages/kbn-profiling-utils/common/stack_traces.test.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { diff --git a/x-pack/plugins/profiling/common/stack_traces.ts b/packages/kbn-profiling-utils/common/stack_traces.ts similarity index 81% rename from x-pack/plugins/profiling/common/stack_traces.ts rename to packages/kbn-profiling-utils/common/stack_traces.ts index 97a18d09ed389..f7893c66c5e29 100644 --- a/x-pack/plugins/profiling/common/stack_traces.ts +++ b/packages/kbn-profiling-utils/common/stack_traces.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { ProfilingESField } from './elasticsearch'; @@ -15,13 +16,17 @@ import { StackTraceID, } from './profiling'; +/** Profiling status response */ export interface ProfilingStatusResponse { + /** profiling enabled */ profiling: { enabled: boolean; }; + /** resource management status*/ resource_management: { enabled: boolean; }; + /** Indices creates / pre 8.9.1 data still available */ resources: { created: boolean; pre_8_9_1_data: boolean; @@ -58,24 +63,43 @@ interface ProfilingExecutables { [key: string]: string; } +/** Profiling stacktrace */ export interface StackTraceResponse { + /** stack trace events */ ['stack_trace_events']?: ProfilingEvents; + /** stack traces */ ['stack_traces']?: ProfilingStackTraces; + /** stack frames */ ['stack_frames']?: ProfilingStackFrames; + /** executables */ ['executables']?: ProfilingExecutables; + /** total frames */ ['total_frames']: number; + /** sampling rate */ ['sampling_rate']: number; } +/** Decoded stack trace response */ export interface DecodedStackTraceResponse { + /** Map of Stacktrace ID and event */ events: Map; + /** Map of stacktrace ID and stacktrace */ stackTraces: Map; + /** Map of stackframe ID and stackframe */ stackFrames: Map; + /** Map of file ID and Executables */ executables: Map; + /** Total number of frames */ totalFrames: number; + /** sampling rate */ samplingRate: number; } - +/** + * Generate Frame ID + * @param frameID string + * @param n number + * @returns string + */ export const makeFrameID = (frameID: string, n: number): string => { return n === 0 ? frameID : frameID + ';' + n.toString(); }; @@ -119,6 +143,11 @@ const createInlineTrace = ( } as StackTrace; }; +/** + * Decodes stack trace response + * @param response StackTraceResponse + * @returns DecodedStackTraceResponse + */ export function decodeStackTraceResponse(response: StackTraceResponse): DecodedStackTraceResponse { const stackTraceEvents: Map = new Map(); for (const [key, value] of Object.entries(response.stack_trace_events ?? {})) { @@ -165,11 +194,17 @@ export function decodeStackTraceResponse(response: StackTraceResponse): DecodedS }; } +/** + * Stacktraces options + */ export enum StackTracesDisplayOption { StackTraces = 'stackTraces', Percentage = 'percentage', } +/** + * Functions TopN types definition + */ export enum TopNType { Containers = 'containers', Deployments = 'deployments', @@ -178,6 +213,11 @@ export enum TopNType { Traces = 'traces', } +/** + * Get Profiling ES field based on TopN Type + * @param type TopNType + * @returns string + */ export function getFieldNameForTopNType(type: TopNType): string { return { [TopNType.Containers]: ProfilingESField.ContainerName, diff --git a/packages/kbn-profiling-utils/index.ts b/packages/kbn-profiling-utils/index.ts new file mode 100644 index 0000000000000..f5719dbc7b32d --- /dev/null +++ b/packages/kbn-profiling-utils/index.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { decodeStackTraceResponse } from './common/stack_traces'; +export { createBaseFlameGraph, createFlameGraph } from './common/flamegraph'; +export { createCalleeTree } from './common/callee'; +export { ProfilingESField } from './common/elasticsearch'; +export { + groupStackFrameMetadataByStackTrace, + describeFrameType, + FrameType, + getCalleeFunction, + getCalleeSource, + getLanguageType, + FrameSymbolStatus, + getFrameSymbolStatus, + createStackFrameMetadata, + emptyExecutable, + emptyStackFrame, + emptyStackTrace, +} from './common/profiling'; +export { getFieldNameForTopNType, TopNType, StackTracesDisplayOption } from './common/stack_traces'; +export { createFrameGroupID } from './common/frame_group'; +export { + createTopNFunctions, + TopNFunctionSortField, + topNFunctionSortFieldRt, +} from './common/functions'; + +export type { CalleeTree } from './common/callee'; +export type { + ProfilingStatusResponse, + StackTraceResponse, + DecodedStackTraceResponse, +} from './common/stack_traces'; +export type { ElasticFlameGraph, BaseFlameGraph } from './common/flamegraph'; +export type { FrameGroupID } from './common/frame_group'; +export type { + Executable, + FileID, + StackFrame, + StackFrameID, + StackFrameMetadata, + StackTrace, + StackTraceID, +} from './common/profiling'; +export type { TopNFunctions } from './common/functions'; diff --git a/packages/kbn-profiling-utils/jest.config.js b/packages/kbn-profiling-utils/jest.config.js new file mode 100644 index 0000000000000..a853bb5666fc6 --- /dev/null +++ b/packages/kbn-profiling-utils/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-profiling-utils'], +}; diff --git a/packages/kbn-profiling-utils/kibana.jsonc b/packages/kbn-profiling-utils/kibana.jsonc new file mode 100644 index 0000000000000..dc45e822e620b --- /dev/null +++ b/packages/kbn-profiling-utils/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/profiling-utils", + "owner": "@elastic/profiling-ui" +} diff --git a/packages/kbn-profiling-utils/package.json b/packages/kbn-profiling-utils/package.json new file mode 100644 index 0000000000000..984883e078975 --- /dev/null +++ b/packages/kbn-profiling-utils/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/profiling-utils", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-profiling-utils/tsconfig.json b/packages/kbn-profiling-utils/tsconfig.json new file mode 100644 index 0000000000000..0bf626e25d9f9 --- /dev/null +++ b/packages/kbn-profiling-utils/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + "**/*.json", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/packages/kbn-search-api-panels/components/code_box.scss b/packages/kbn-search-api-panels/components/code_box.scss index b04dbe0ffba42..50e1bcb2b6168 100644 --- a/packages/kbn-search-api-panels/components/code_box.scss +++ b/packages/kbn-search-api-panels/components/code_box.scss @@ -1,3 +1,3 @@ -.serverlessSearchCodeBoxPanel { +.codeBoxPanel { border-top: $euiBorderThin $euiColorLightShade; } diff --git a/packages/kbn-search-api-panels/components/code_box.tsx b/packages/kbn-search-api-panels/components/code_box.tsx index 9a00bdd86e4e3..ca3dd4d8b52c5 100644 --- a/packages/kbn-search-api-panels/components/code_box.tsx +++ b/packages/kbn-search-api-panels/components/code_box.tsx @@ -86,7 +86,7 @@ export const CodeBox: React.FC = ({ return ( - + diff --git a/packages/kbn-search-api-panels/components/install_client.tsx b/packages/kbn-search-api-panels/components/install_client.tsx index 6d56f69e09530..7fa0ecee6a049 100644 --- a/packages/kbn-search-api-panels/components/install_client.tsx +++ b/packages/kbn-search-api-panels/components/install_client.tsx @@ -96,15 +96,19 @@ export const InstallClientPanel: React.FC = ({ defaultMessage: 'Elastic builds and maintains clients in several popular languages and our community has contributed many more. Install your favorite language client to get started.', })} - links={[ - { - href: language.docLink, - label: i18n.translate('searchApiPanels.welcomeBanner.installClient.clientDocLink', { - defaultMessage: '{languageName} client documentation', - values: { languageName: language.name }, - }), - }, - ]} + links={ + language.docLink + ? [ + { + href: language.docLink, + label: i18n.translate('searchApiPanels.welcomeBanner.installClient.clientDocLink', { + defaultMessage: '{languageName} client documentation', + values: { languageName: language.name }, + }), + }, + ] + : [] + } title={i18n.translate('searchApiPanels.welcomeBanner.installClient.title', { defaultMessage: 'Install a client', })} diff --git a/packages/kbn-search-api-panels/components/language_client_panel.tsx b/packages/kbn-search-api-panels/components/language_client_panel.tsx index 9940e7a6fed10..d749ebc3fe5de 100644 --- a/packages/kbn-search-api-panels/components/language_client_panel.tsx +++ b/packages/kbn-search-api-panels/components/language_client_panel.tsx @@ -56,7 +56,7 @@ export const LanguageClientPanel: React.FC = ({ diff --git a/packages/kbn-search-api-panels/index.tsx b/packages/kbn-search-api-panels/index.tsx index feb26ae501de7..67bc9b221bdab 100644 --- a/packages/kbn-search-api-panels/index.tsx +++ b/packages/kbn-search-api-panels/index.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer, EuiImage, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { AuthenticatedUser } from '@kbn/security-plugin/common'; export * from './components/code_box'; export * from './components/github_link'; @@ -24,19 +25,14 @@ export * from './types'; export * from './utils'; export interface WelcomeBannerProps { - userProfile: { - user: { - full_name?: string; - username?: string; - }; - }; + user?: AuthenticatedUser; assetBasePath?: string; image?: string; showDescription?: boolean; } export const WelcomeBanner: React.FC = ({ - userProfile, + user, assetBasePath, image, showDescription = true, @@ -54,16 +50,22 @@ export const WelcomeBanner: React.FC = ({ - - -

    - {i18n.translate('searchApiPanels.welcomeBanner.header.greeting.title', { - defaultMessage: 'Hi {name}!', - values: { name: userProfile?.user?.full_name || userProfile?.user?.username }, - })} -

    -
    -
    + {Boolean(user) && ( + + +

    + {user + ? i18n.translate('searchApiPanels.welcomeBanner.header.greeting.customTitle', { + defaultMessage: 'Hi {name}!', + values: { name: user.full_name || user.username }, + }) + : i18n.translate('searchApiPanels.welcomeBanner.header.greeting.defaultTitle', { + defaultMessage: 'Hi!', + })} +

    +
    +
    + )}
    {showDescription && ( diff --git a/packages/kbn-search-api-panels/languages/console.ts b/packages/kbn-search-api-panels/languages/console.ts index be924d5fa3cbf..e156409239242 100644 --- a/packages/kbn-search-api-panels/languages/console.ts +++ b/packages/kbn-search-api-panels/languages/console.ts @@ -8,6 +8,8 @@ import { LanguageDefinition } from '../types'; +const INDEX_NAME_PLACEHOLDER = 'index_name'; + export const consoleDefinition: Partial = { buildSearchQuery: `POST /books/_search?pretty { @@ -30,4 +32,8 @@ export const consoleDefinition: Partial = { {"name": "Brave New World", "author": "Aldous Huxley", "release_date": "1932-06-01", "page_count": 268} { "index" : { "_index" : "books" } } {"name": "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311}`, + ingestDataIndex: ({ indexName }) => `POST _bulk?pretty +{ "index" : { "_index" : "${indexName ?? INDEX_NAME_PLACEHOLDER}" } } +{"name": "foo", "title": "bar"} +`, }; diff --git a/packages/kbn-search-api-panels/tsconfig.json b/packages/kbn-search-api-panels/tsconfig.json index 82fd44f2cbb32..768b73f8cef46 100644 --- a/packages/kbn-search-api-panels/tsconfig.json +++ b/packages/kbn-search-api-panels/tsconfig.json @@ -20,6 +20,7 @@ "@kbn/core-http-browser", "@kbn/core-application-browser", "@kbn/share-plugin", - "@kbn/i18n-react" + "@kbn/i18n-react", + "@kbn/security-plugin" ] } diff --git a/packages/kbn-search-api-panels/types.ts b/packages/kbn-search-api-panels/types.ts index 63edec82c345d..774dd6c4bc9f0 100644 --- a/packages/kbn-search-api-panels/types.ts +++ b/packages/kbn-search-api-panels/types.ts @@ -24,26 +24,28 @@ export interface LanguageDefinitionSnippetArguments { apiKey: string; indexName?: string; cloudId?: string; + ingestPipeline?: string; + extraIngestDocumentValues?: Record; } type CodeSnippet = string | ((args: LanguageDefinitionSnippetArguments) => string); export interface LanguageDefinition { + name: string; + id: Languages; + iconType: string; + docLink?: string; + configureClient?: CodeSnippet; + ingestData?: CodeSnippet; + ingestDataIndex?: CodeSnippet; + installClient?: string; + buildSearchQuery?: CodeSnippet; + testConnection?: CodeSnippet; advancedConfig?: string; apiReference?: string; basicConfig?: string; - configureClient: CodeSnippet; - docLink: string; github?: { link: string; label: string; }; - iconType: string; - id: Languages; - ingestData: CodeSnippet; - ingestDataIndex: CodeSnippet; - installClient: string; languageStyling?: string; - name: string; - buildSearchQuery: CodeSnippet; - testConnection: CodeSnippet; } diff --git a/packages/kbn-search-api-panels/utils.test.ts b/packages/kbn-search-api-panels/utils.test.ts new file mode 100644 index 0000000000000..c842dd03cf275 --- /dev/null +++ b/packages/kbn-search-api-panels/utils.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { consoleDefinition } from './languages/console'; +import { getConsoleRequest } from './utils'; + +describe('utils', () => { + describe('getConsoleRequest()', () => { + test('accepts string values', () => { + const consoleRequest = getConsoleRequest('ingestData'); + expect(consoleRequest).toEqual(consoleDefinition.ingestData); + }); + + test('accepts function values', () => { + const consoleRequest = getConsoleRequest('ingestDataIndex', { + url: 'https://your_deployment_url', + apiKey: 'yourApiKey', + indexName: 'test-index', + }); + expect(consoleRequest).toContain(`POST _bulk?pretty +{ \"index\" : { \"_index\" : \"test-index\" } } +{\"name\": \"foo\", \"title\": \"bar\"}`); + }); + + test('returns undefined if language definition is undefined', () => { + // @ts-ignore TS should not allow an invalid language definition + // We add @ts-ignore here to test the safeguard + const consoleRequest = getConsoleRequest('nonExistentRequest'); + expect(consoleRequest).toEqual(undefined); + }); + }); +}); diff --git a/packages/kbn-search-api-panels/utils.ts b/packages/kbn-search-api-panels/utils.ts index ffd81257c5a30..6cc16439ea2f5 100644 --- a/packages/kbn-search-api-panels/utils.ts +++ b/packages/kbn-search-api-panels/utils.ts @@ -26,7 +26,23 @@ export const getLanguageDefinitionCodeSnippet = ( } }; -export const getConsoleRequest = (code: keyof LanguageDefinition): string | undefined => - code in consoleDefinition && typeof consoleDefinition[code] === 'string' - ? (consoleDefinition[code] as string) - : undefined; +export const getConsoleRequest = ( + code: keyof LanguageDefinition, + args?: LanguageDefinitionSnippetArguments +): string | undefined => { + if (code in consoleDefinition) { + const codeType = consoleDefinition[code]; + + switch (typeof codeType) { + case 'string': + return codeType as string; + case 'function': + if (args) { + return codeType(args) as string; + } + return undefined; + default: + return undefined; + } + } +}; diff --git a/packages/kbn-search-connectors/README.md b/packages/kbn-search-connectors/README.md new file mode 100644 index 0000000000000..5233a782a964f --- /dev/null +++ b/packages/kbn-search-connectors/README.md @@ -0,0 +1,3 @@ +# @kbn/search-connectors + +Empty package generated by @kbn/generate diff --git a/packages/kbn-search-connectors/connectors.ts b/packages/kbn-search-connectors/connectors.ts new file mode 100644 index 0000000000000..119de69a0c5c0 --- /dev/null +++ b/packages/kbn-search-connectors/connectors.ts @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export interface ConnectorServerSideDefinition { + iconPath: string; + isBeta: boolean; + isNative: boolean; + isTechPreview?: boolean; + keywords: string[]; + name: string; + serviceType: string; +} + +/* The consumer should host these icons and transform the iconPath into something usable + * Enterprise Search and Serverless Search do this right now + */ + +export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ + { + iconPath: 'azure_blob_storage.svg', + isBeta: true, + isNative: true, + keywords: ['cloud', 'azure', 'blob', 's3', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.azureBlob.name', { + defaultMessage: 'Azure Blob Storage', + }), + serviceType: 'azure_blob_storage', + }, + { + iconPath: 'confluence_cloud.svg', + isBeta: false, + isNative: true, + keywords: ['confluence', 'cloud', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.confluence.name', { + defaultMessage: 'Confluence Cloud & Server', + }), + serviceType: 'confluence', + }, + { + iconPath: 'dropbox.svg', + isBeta: true, + isNative: true, + isTechPreview: false, + keywords: ['dropbox', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.dropbox.name', { + defaultMessage: 'Dropbox', + }), + serviceType: 'dropbox', + }, + { + iconPath: 'jira_cloud.svg', + isBeta: false, + isNative: true, + keywords: ['jira', 'cloud', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.jira.name', { + defaultMessage: 'Jira Cloud & Server', + }), + serviceType: 'jira', + }, + { + iconPath: 'github.svg', + isBeta: true, + isNative: false, + keywords: ['github', 'cloud', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.github.name', { + defaultMessage: 'GitHub & GitHub Enterprise Server', + }), + serviceType: 'github', + }, + { + iconPath: 'google_cloud_storage.svg', + isBeta: true, + isNative: false, + keywords: ['google', 'cloud', 'blob', 's3', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.googleCloud.name', { + defaultMessage: 'Google Cloud Storage', + }), + serviceType: 'google_cloud_storage', + }, + { + iconPath: 'google_drive.svg', + isBeta: true, + isNative: false, + keywords: ['google', 'drive', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.googleDrive.name', { + defaultMessage: 'Google Drive', + }), + serviceType: 'google_drive', + }, + { + iconPath: 'mongodb.svg', + isBeta: false, + isNative: true, + keywords: ['mongo', 'mongodb', 'database', 'nosql', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.mongodb.name', { + defaultMessage: 'MongoDB', + }), + serviceType: 'mongodb', + }, + { + iconPath: 'mysql.svg', + isBeta: false, + isNative: true, + keywords: ['mysql', 'sql', 'database', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.mysql.name', { + defaultMessage: 'MySQL', + }), + serviceType: 'mysql', + }, + { + iconPath: 'mssql.svg', + isBeta: true, + isNative: true, + keywords: ['mssql', 'microsoft', 'sql', 'database', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.microsoftSQL.name', { + defaultMessage: 'Microsoft SQL', + }), + serviceType: 'mssql', + }, + { + iconPath: 'network_drive.svg', + isBeta: false, + isNative: true, + keywords: ['network', 'drive', 'file', 'directory', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.networkDrive.name', { + defaultMessage: 'Network drive', + }), + serviceType: 'network_drive', + }, + { + iconPath: 'postgresql.svg', + isBeta: true, + isNative: true, + keywords: ['postgresql', 'sql', 'database', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.postgresql.name', { + defaultMessage: 'PostgreSQL', + }), + serviceType: 'postgresql', + }, + { + iconPath: 'salesforce.svg', + isBeta: true, + isNative: false, + isTechPreview: false, + keywords: ['salesforce', 'cloud', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.salesforce.name', { + defaultMessage: 'Salesforce', + }), + serviceType: 'salesforce', + }, + { + iconPath: 'servicenow.svg', + isBeta: true, + isNative: true, + isTechPreview: false, + keywords: ['servicenow', 'cloud', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.serviceNow.name', { + defaultMessage: 'ServiceNow', + }), + serviceType: 'servicenow', + }, + { + iconPath: 'sharepoint_online.svg', + isBeta: false, + isNative: true, + isTechPreview: false, + keywords: ['sharepoint', 'office365', 'cloud', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.sharepoint_online.name', { + defaultMessage: 'Sharepoint Online', + }), + serviceType: 'sharepoint_online', + }, + { + iconPath: 'gmail.svg', + isBeta: false, + isNative: false, + isTechPreview: true, + keywords: ['google', 'gmail', 'connector', 'mail'], + name: i18n.translate('searchConnectors.content.nativeConnectors.gmail.name', { + defaultMessage: 'Gmail', + }), + serviceType: 'gmail', + }, + { + iconPath: 'oracle.svg', + isBeta: true, + isNative: false, + keywords: ['oracle', 'sql', 'database', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.oracle.name', { + defaultMessage: 'Oracle', + }), + serviceType: 'oracle', + }, + { + iconPath: 'onedrive.svg', + isBeta: true, + isNative: false, + keywords: ['network', 'drive', 'file', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.oneDrive.name', { + defaultMessage: 'OneDrive', + }), + serviceType: 'onedrive', + }, + { + iconPath: 's3.svg', + isBeta: true, + isNative: false, + keywords: ['s3', 'cloud', 'amazon', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.s3.name', { + defaultMessage: 'S3', + }), + serviceType: 's3', + }, + { + iconPath: 'slack.svg', + isBeta: false, + isNative: false, + isTechPreview: true, + keywords: ['slack', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.slack.name', { + defaultMessage: 'Slack', + }), + serviceType: 'slack', + }, + { + iconPath: 'sharepoint_server.svg', + isBeta: true, + isNative: false, + isTechPreview: false, + keywords: ['sharepoint', 'cloud', 'connector'], + name: i18n.translate('searchConnectors.content.nativeConnectors.sharepointServer.name', { + defaultMessage: 'Sharepoint Server', + }), + serviceType: 'sharepoint_server', + }, + { + iconPath: 'custom.svg', + isBeta: true, + isNative: false, + keywords: ['custom', 'connector', 'code'], + name: i18n.translate('searchConnectors.content.nativeConnectors.customConnector.name', { + defaultMessage: 'Customized connector', + }), + serviceType: '', + }, +]; diff --git a/packages/kbn-search-connectors/index.ts b/packages/kbn-search-connectors/index.ts new file mode 100644 index 0000000000000..f232081031fb6 --- /dev/null +++ b/packages/kbn-search-connectors/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const CONNECTORS_INDEX = '.elastic-connectors'; +export const CURRENT_CONNECTORS_INDEX = '.elastic-connectors-v1'; +export const CONNECTORS_JOBS_INDEX = '.elastic-connectors-sync-jobs'; +export const CURRENT_CONNECTORS_JOB_INDEX = '.elastic-connectors-sync-jobs-v1'; +export const CONNECTORS_VERSION = 1; +export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = '.search-acl-filter-'; + +export * from './connectors'; +export * from './lib'; +export * from './types'; +export * from './utils'; diff --git a/packages/kbn-search-connectors/jest.config.js b/packages/kbn-search-connectors/jest.config.js new file mode 100644 index 0000000000000..99c132b768bfe --- /dev/null +++ b/packages/kbn-search-connectors/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-search-connectors'], +}; diff --git a/packages/kbn-search-connectors/kibana.jsonc b/packages/kbn-search-connectors/kibana.jsonc new file mode 100644 index 0000000000000..4c1162ed0300b --- /dev/null +++ b/packages/kbn-search-connectors/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/search-connectors", + "owner": "@elastic/enterprise-search-frontend" +} diff --git a/packages/kbn-search-connectors/lib/cancel_syncs.test.ts b/packages/kbn-search-connectors/lib/cancel_syncs.test.ts new file mode 100644 index 0000000000000..05c2f5fcd529d --- /dev/null +++ b/packages/kbn-search-connectors/lib/cancel_syncs.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '..'; +import { SyncStatus } from '../types/connectors'; + +import { cancelSyncs } from './cancel_syncs'; + +describe('cancelSync lib function', () => { + const mockClient = { + update: jest.fn(), + updateByQuery: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + const now = new Date('2022-05-22T10:10:11.111Z'); + jest.spyOn(Date, 'now').mockImplementation(() => now.getTime()); + }); + + it('should call updateByQuery to cancel syncs', async () => { + mockClient.updateByQuery.mockImplementation(() => ({ _id: 'fakeId' })); + + await expect( + cancelSyncs(mockClient as unknown as ElasticsearchClient, 'connectorId') + ).resolves.toEqual(undefined); + expect(mockClient.updateByQuery).toHaveBeenCalledTimes(2); + expect(mockClient.updateByQuery).toHaveBeenCalledWith({ + index: CONNECTORS_JOBS_INDEX, + query: { + bool: { + must: [ + { + term: { + 'connector.id': 'connectorId', + }, + }, + { + terms: { + status: [SyncStatus.PENDING, SyncStatus.SUSPENDED], + }, + }, + ], + }, + }, + script: { + lang: 'painless', + source: `ctx._source['status'] = '${SyncStatus.CANCELED}'; +ctx._source['cancelation_requested_at'] = '${new Date(Date.now()).toISOString()}'; +ctx._source['canceled_at'] = '${new Date(Date.now()).toISOString()}'; +ctx._source['completed_at'] = '${new Date(Date.now()).toISOString()}';`, + }, + }); + expect(mockClient.updateByQuery).toHaveBeenCalledWith({ + index: CONNECTORS_JOBS_INDEX, + query: { + bool: { + must: [ + { + term: { + 'connector.id': 'connectorId', + }, + }, + { + terms: { + status: [SyncStatus.IN_PROGRESS], + }, + }, + ], + }, + }, + script: { + lang: 'painless', + source: `ctx._source['status'] = '${SyncStatus.CANCELING}'; +ctx._source['cancelation_requested_at'] = '${new Date(Date.now()).toISOString()}';`, + }, + }); + await expect(mockClient.update).toHaveBeenCalledWith({ + doc: { last_sync_status: SyncStatus.CANCELED, sync_now: false }, + id: 'connectorId', + index: CONNECTORS_INDEX, + }); + }); +}); diff --git a/packages/kbn-search-connectors/lib/cancel_syncs.ts b/packages/kbn-search-connectors/lib/cancel_syncs.ts new file mode 100644 index 0000000000000..2a331e19d4cb7 --- /dev/null +++ b/packages/kbn-search-connectors/lib/cancel_syncs.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '..'; +import { SyncStatus } from '../types/connectors'; + +export const cancelSyncs = async ( + client: ElasticsearchClient, + connectorId: string +): Promise => { + await client.updateByQuery({ + index: CONNECTORS_JOBS_INDEX, + query: { + bool: { + must: [ + { + term: { + 'connector.id': connectorId, + }, + }, + { + terms: { + status: [SyncStatus.PENDING, SyncStatus.SUSPENDED], + }, + }, + ], + }, + }, + script: { + lang: 'painless', + source: `ctx._source['status'] = '${SyncStatus.CANCELED}'; +ctx._source['cancelation_requested_at'] = '${new Date(Date.now()).toISOString()}'; +ctx._source['canceled_at'] = '${new Date(Date.now()).toISOString()}'; +ctx._source['completed_at'] = '${new Date(Date.now()).toISOString()}';`, + }, + }); + await client.updateByQuery({ + index: CONNECTORS_JOBS_INDEX, + query: { + bool: { + must: [ + { + term: { + 'connector.id': connectorId, + }, + }, + { + terms: { + status: [SyncStatus.IN_PROGRESS], + }, + }, + ], + }, + }, + script: { + lang: 'painless', + source: `ctx._source['status'] = '${SyncStatus.CANCELING}'; +ctx._source['cancelation_requested_at'] = '${new Date(Date.now()).toISOString()}';`, + }, + }); + await client.update({ + doc: { last_sync_status: SyncStatus.CANCELED, sync_now: false }, + id: connectorId, + index: CONNECTORS_INDEX, + }); +}; diff --git a/packages/kbn-search-connectors/lib/create_connector.ts b/packages/kbn-search-connectors/lib/create_connector.ts new file mode 100644 index 0000000000000..d4ff1230727c8 --- /dev/null +++ b/packages/kbn-search-connectors/lib/create_connector.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { CURRENT_CONNECTORS_INDEX } from '..'; + +import { Connector, ConnectorConfiguration, IngestPipelineParams } from '../types/connectors'; +import { createConnectorDocument } from './create_connector_document'; + +export const createConnector = async ( + client: ElasticsearchClient, + input: { + configuration?: ConnectorConfiguration; + features?: Connector['features']; + indexName: string | null; + isNative: boolean; + language: string | null; + name?: string; + pipeline: IngestPipelineParams; + serviceType?: string | null; + } +): Promise => { + const document = createConnectorDocument({ + ...input, + serviceType: input.serviceType || null, + }); + + const result = await client.index({ + document, + index: CURRENT_CONNECTORS_INDEX, + refresh: 'wait_for', + }); + + return { ...document, id: result._id }; +}; diff --git a/packages/kbn-search-connectors/lib/create_connector_document.test.ts b/packages/kbn-search-connectors/lib/create_connector_document.test.ts new file mode 100644 index 0000000000000..aa1c54c3c90ea --- /dev/null +++ b/packages/kbn-search-connectors/lib/create_connector_document.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ConnectorStatus } from '../types/connectors'; + +import { createConnectorDocument } from './create_connector_document'; + +describe('createConnectorDocument', () => { + it('should create a connector document', () => { + expect( + createConnectorDocument({ + indexName: 'indexName', + isNative: false, + language: 'fr', + name: 'indexName-name', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + serviceType: null, + }) + ).toEqual({ + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + }, + ], + index_name: 'indexName', + is_native: false, + language: 'fr', + last_access_control_sync_error: null, + last_access_control_sync_scheduled_at: null, + last_access_control_sync_status: null, + last_incremental_sync_scheduled_at: null, + last_seen: null, + last_sync_error: null, + last_sync_scheduled_at: null, + last_sync_status: null, + last_synced: null, + name: 'indexName-name', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, + service_type: null, + status: ConnectorStatus.CREATED, + sync_now: false, + }); + }); +}); diff --git a/packages/kbn-search-connectors/lib/create_connector_document.ts b/packages/kbn-search-connectors/lib/create_connector_document.ts new file mode 100644 index 0000000000000..c5654e9a2436a --- /dev/null +++ b/packages/kbn-search-connectors/lib/create_connector_document.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + Connector, + ConnectorConfiguration, + ConnectorDocument, + ConnectorStatus, + FilteringPolicy, + FilteringRuleRule, + FilteringValidationState, + IngestPipelineParams, +} from '../types/connectors'; + +export function createConnectorDocument({ + configuration, + features, + indexName, + isNative, + name, + pipeline, + serviceType, + language, +}: { + configuration?: ConnectorConfiguration; + features?: Connector['features']; + indexName: string | null; + isNative: boolean; + language: string | null; + name?: string; + pipeline?: IngestPipelineParams | null; + serviceType: string | null; +}): ConnectorDocument { + const currentTimestamp = new Date().toISOString(); + + return { + api_key_id: null, + configuration: configuration || {}, + custom_scheduling: {}, + description: null, + error: null, + features: features || null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: currentTimestamp, + updated_at: currentTimestamp, + value: {}, + }, + rules: [ + { + created_at: currentTimestamp, + field: '_', + id: 'DEFAULT', + order: 0, + policy: FilteringPolicy.INCLUDE, + rule: FilteringRuleRule.REGEX, + updated_at: currentTimestamp, + value: '.*', + }, + ], + validation: { + errors: [], + state: FilteringValidationState.VALID, + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: currentTimestamp, + updated_at: currentTimestamp, + value: {}, + }, + rules: [ + { + created_at: currentTimestamp, + field: '_', + id: 'DEFAULT', + order: 0, + policy: FilteringPolicy.INCLUDE, + rule: FilteringRuleRule.REGEX, + updated_at: currentTimestamp, + value: '.*', + }, + ], + validation: { + errors: [], + state: FilteringValidationState.VALID, + }, + }, + }, + ], + index_name: indexName, + is_native: isNative, + language, + last_access_control_sync_error: null, + last_access_control_sync_scheduled_at: null, + last_access_control_sync_status: null, + last_incremental_sync_scheduled_at: null, + last_seen: null, + last_sync_error: null, + last_sync_scheduled_at: null, + last_sync_status: null, + last_synced: null, + name: name ?? '', + pipeline, + scheduling: { + access_control: { enabled: false, interval: '0 0 0 * * ?' }, + full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, + }, + service_type: serviceType || null, + status: ConnectorStatus.CREATED, + sync_now: false, + }; +} diff --git a/packages/kbn-search-connectors/lib/delete_connector.test.ts b/packages/kbn-search-connectors/lib/delete_connector.test.ts new file mode 100644 index 0000000000000..55dfdcbeae92b --- /dev/null +++ b/packages/kbn-search-connectors/lib/delete_connector.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX } from '..'; + +import { deleteConnectorById } from './delete_connector'; + +jest.mock('./cancel_syncs', () => ({ + cancelSyncs: jest.fn(), +})); +import { cancelSyncs } from './cancel_syncs'; + +describe('deleteConnector lib function', () => { + const mockClient = { + delete: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers(); + }); + + it('should delete connector and cancel syncs', async () => { + mockClient.delete.mockImplementation(() => true); + + await deleteConnectorById(mockClient as unknown as ElasticsearchClient, 'connectorId'); + expect(cancelSyncs as jest.Mock).toHaveBeenCalledWith(mockClient, 'connectorId'); + expect(mockClient.delete).toHaveBeenCalledWith({ + id: 'connectorId', + index: CONNECTORS_INDEX, + refresh: 'wait_for', + }); + jest.useRealTimers(); + }); +}); diff --git a/packages/kbn-search-connectors/lib/delete_connector.ts b/packages/kbn-search-connectors/lib/delete_connector.ts new file mode 100644 index 0000000000000..c64edfd3d906a --- /dev/null +++ b/packages/kbn-search-connectors/lib/delete_connector.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { CONNECTORS_INDEX } from '..'; +import { cancelSyncs } from './cancel_syncs'; + +export const deleteConnectorById = async (client: ElasticsearchClient, id: string) => { + // timeout function to mitigate race condition with external connector running job and recreating index + const timeout = async () => { + const promise = new Promise((resolve) => setTimeout(resolve, 500)); + return promise; + }; + await Promise.all([cancelSyncs(client, id), timeout]); + return await client.delete({ id, index: CONNECTORS_INDEX, refresh: 'wait_for' }); +}; diff --git a/packages/kbn-search-connectors/lib/fetch_connector_index_names.ts b/packages/kbn-search-connectors/lib/fetch_connector_index_names.ts new file mode 100644 index 0000000000000..bed99173c859b --- /dev/null +++ b/packages/kbn-search-connectors/lib/fetch_connector_index_names.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX } from '..'; +import { isIndexNotFoundException } from '../utils/identify_exceptions'; + +export async function fetchConnectorIndexNames(client: ElasticsearchClient): Promise { + try { + const result = await client.search({ + _source: false, + fields: [{ field: 'index_name' }], + index: CONNECTORS_INDEX, + size: 10000, + }); + return (result?.hits.hits ?? []).map((field) => field.fields?.index_name[0] ?? ''); + } catch (error) { + if (isIndexNotFoundException(error)) { + return []; + } + throw error; + } +} diff --git a/packages/kbn-search-connectors/lib/fetch_connectors.test.ts b/packages/kbn-search-connectors/lib/fetch_connectors.test.ts new file mode 100644 index 0000000000000..e1bb872710594 --- /dev/null +++ b/packages/kbn-search-connectors/lib/fetch_connectors.test.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CONNECTORS_INDEX } from '..'; + +import { fetchConnectorById, fetchConnectorByIndexName, fetchConnectors } from './fetch_connectors'; + +const indexNotFoundError = { + meta: { + body: { + error: { + type: 'index_not_found_exception', + }, + }, + }, +}; + +const otherError = { + meta: { + body: { + error: { + type: 'other_error', + }, + }, + }, +}; + +describe('fetchConnectors lib', () => { + const mockClient = { + get: jest.fn(), + search: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('fetch connector by id', () => { + it('should fetch connector by id', async () => { + mockClient.get.mockImplementationOnce(() => + Promise.resolve({ + _id: 'connectorId', + _primary_term: 'primaryTerm', + _seq_no: 5, + _source: { source: 'source' }, + }) + ); + await expect(fetchConnectorById(mockClient as any, 'id')).resolves.toEqual({ + primaryTerm: 'primaryTerm', + seqNo: 5, + value: { id: 'connectorId', source: 'source' }, + }); + expect(mockClient.get).toHaveBeenCalledWith({ + id: 'id', + index: CONNECTORS_INDEX, + }); + }); + it('should return undefined on index not found error', async () => { + mockClient.get.mockImplementationOnce(() => Promise.reject(indexNotFoundError)); + await expect(fetchConnectorById(mockClient as any, 'id')).resolves.toEqual(undefined); + expect(mockClient.get).toHaveBeenCalledWith({ + id: 'id', + index: CONNECTORS_INDEX, + }); + }); + it('should throw on other errors', async () => { + mockClient.get.mockImplementationOnce(() => Promise.reject(otherError)); + await expect(fetchConnectorById(mockClient as any, 'id')).rejects.toEqual(otherError); + expect(mockClient.get).toHaveBeenCalledWith({ + id: 'id', + index: CONNECTORS_INDEX, + }); + }); + }); + describe('fetch connector by name', () => { + it('should fetch connector by index name', async () => { + mockClient.search.mockImplementationOnce(() => + Promise.resolve({ hits: { hits: [{ _id: 'connectorId', _source: { source: 'source' } }] } }) + ); + mockClient.get.mockImplementationOnce(() => + Promise.resolve({ _id: 'connectorId', _source: { source: 'source' } }) + ); + await expect(fetchConnectorByIndexName(mockClient as any, 'id')).resolves.toEqual({ + id: 'connectorId', + source: 'source', + }); + expect(mockClient.search).toHaveBeenCalledWith({ + index: CONNECTORS_INDEX, + query: { + term: { + ['index_name']: 'id', + }, + }, + }); + }); + it('should return undefined on index not found error', async () => { + mockClient.search.mockImplementationOnce(() => Promise.reject(indexNotFoundError)); + await expect(fetchConnectorByIndexName(mockClient as any, 'id')).resolves.toEqual(undefined); + expect(mockClient.search).toHaveBeenCalledWith({ + index: CONNECTORS_INDEX, + query: { + term: { + ['index_name']: 'id', + }, + }, + }); + }); + it('should throw on other errors', async () => { + mockClient.search.mockImplementationOnce(() => Promise.reject(otherError)); + await expect(fetchConnectorByIndexName(mockClient as any, 'id')).rejects.toEqual(otherError); + expect(mockClient.search).toHaveBeenCalledWith({ + index: CONNECTORS_INDEX, + query: { + term: { + ['index_name']: 'id', + }, + }, + }); + }); + }); + describe('fetch connectors', () => { + it('should fetch connectors', async () => { + mockClient.search.mockImplementationOnce(() => + Promise.resolve({ hits: { hits: [{ _id: 'connectorId', _source: { source: 'source' } }] } }) + ); + await expect(fetchConnectors(mockClient as any)).resolves.toEqual([ + { + id: 'connectorId', + source: 'source', + }, + ]); + expect(mockClient.search).toHaveBeenCalledWith({ + from: 0, + index: CONNECTORS_INDEX, + query: { match_all: {} }, + size: 1000, + }); + }); + it('should fetch all connectors if there are more than 1000', async () => { + const hits = [...Array(1000).keys()].map((key) => ({ + _id: key, + _source: { source: 'source' }, + })); + const resultHits = hits.map((hit) => ({ ...hit._source, id: hit._id })); + + let count = 0; + + mockClient.search.mockImplementation(() => { + count += 1; + if (count === 3) { + return Promise.resolve({ hits: { hits: [] } }); + } + return Promise.resolve({ hits: { hits } }); + }); + await expect(fetchConnectors(mockClient as any)).resolves.toEqual([ + ...resultHits, + ...resultHits, + ]); + expect(mockClient.search).toHaveBeenCalledWith({ + from: 0, + index: CONNECTORS_INDEX, + query: { match_all: {} }, + size: 1000, + }); + expect(mockClient.search).toHaveBeenCalledTimes(3); + }); + it('should return empty array on index not found error', async () => { + mockClient.search.mockImplementationOnce(() => Promise.reject(indexNotFoundError)); + await expect(fetchConnectors(mockClient as any)).resolves.toEqual([]); + expect(mockClient.search).toHaveBeenCalledWith({ + from: 0, + index: CONNECTORS_INDEX, + query: { match_all: {} }, + size: 1000, + }); + }); + it('should throw on other errors', async () => { + mockClient.search.mockImplementationOnce(() => Promise.reject(otherError)); + await expect(fetchConnectors(mockClient as any)).rejects.toEqual(otherError); + expect(mockClient.search).toHaveBeenCalledWith({ + from: 0, + index: CONNECTORS_INDEX, + query: { match_all: {} }, + size: 1000, + }); + }); + }); +}); diff --git a/packages/kbn-search-connectors/lib/fetch_connectors.ts b/packages/kbn-search-connectors/lib/fetch_connectors.ts new file mode 100644 index 0000000000000..7d9463228c0a9 --- /dev/null +++ b/packages/kbn-search-connectors/lib/fetch_connectors.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { QueryDslQueryContainer, SearchHit } from '@elastic/elasticsearch/lib/api/types'; +import { ElasticsearchClient } from '@kbn/core/server'; + +import { OptimisticConcurrency } from '../types/optimistic_concurrency'; +import { Connector, ConnectorDocument } from '../types/connectors'; + +import { isIndexNotFoundException } from '../utils/identify_exceptions'; +import { CONNECTORS_INDEX } from '..'; +import { isNotNullish } from '../utils/is_not_nullish'; + +export const fetchConnectorById = async ( + client: ElasticsearchClient, + connectorId: string +): Promise | undefined> => { + try { + const connectorResult = await client.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + return connectorResult._source + ? { + primaryTerm: connectorResult._primary_term, + seqNo: connectorResult._seq_no, + value: { ...connectorResult._source, id: connectorResult._id }, + } + : undefined; + } catch (error) { + if (isIndexNotFoundException(error)) { + return undefined; + } + throw error; + } +}; + +export const fetchConnectorByIndexName = async ( + client: ElasticsearchClient, + indexName: string +): Promise => { + try { + const connectorResult = await client.search({ + index: CONNECTORS_INDEX, + query: { term: { index_name: indexName } }, + }); + // Because we cannot guarantee that the index has been refreshed and is giving us the most recent source + // we need to fetch the source with a direct get from the index, which will always be up to date + const result = connectorResult.hits.hits[0]?._source + ? (await fetchConnectorById(client, connectorResult.hits.hits[0]._id))?.value + : undefined; + return result; + } catch (error) { + if (isIndexNotFoundException(error)) { + return undefined; + } + throw error; + } +}; + +export const fetchConnectors = async ( + client: ElasticsearchClient, + indexNames?: string[] +): Promise => { + const query: QueryDslQueryContainer = indexNames + ? { terms: { index_name: indexNames } } + : { match_all: {} }; + + try { + let hits: Array> = []; + let accumulator: Array> = []; + + do { + const connectorResult = await client.search({ + from: accumulator.length, + index: CONNECTORS_INDEX, + query, + size: 1000, + }); + hits = connectorResult.hits.hits; + accumulator = accumulator.concat(hits); + } while (hits.length >= 1000); + + return accumulator + .map(({ _source, _id }) => (_source ? { ..._source, id: _id } : undefined)) + .filter(isNotNullish); + } catch (error) { + if (isIndexNotFoundException(error)) { + return []; + } + throw error; + } +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts b/packages/kbn-search-connectors/lib/fetch_sync_jobs.test.ts similarity index 82% rename from x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts rename to packages/kbn-search-connectors/lib/fetch_sync_jobs.test.ts index 651849e803c41..debfedbcfeb67 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts +++ b/packages/kbn-search-connectors/lib/fetch_sync_jobs.test.ts @@ -1,19 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { fetchSyncJobsByConnectorId } from './fetch_sync_jobs'; describe('fetchSyncJobs lib', () => { const mockClient = { - asCurrentUser: { - get: jest.fn(), - search: jest.fn(), - }, - asInternalUser: {}, + get: jest.fn(), + search: jest.fn(), }; beforeEach(() => { @@ -21,7 +19,7 @@ describe('fetchSyncJobs lib', () => { }); describe('fetch sync jobs by connector id', () => { it('should fetch sync jobs by connector id', async () => { - mockClient.asCurrentUser.search.mockImplementationOnce(() => + mockClient.search.mockImplementationOnce(() => Promise.resolve({ hits: { hits: ['result1', 'result2'] }, total: 2 }) ); await expect(fetchSyncJobsByConnectorId(mockClient as any, 'id', 0, 10)).resolves.toEqual({ @@ -35,7 +33,7 @@ describe('fetchSyncJobs lib', () => { }, data: [], }); - expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({ + expect(mockClient.search).toHaveBeenCalledWith({ from: 0, index: '.elastic-connectors-sync-jobs', query: { @@ -63,10 +61,10 @@ describe('fetchSyncJobs lib', () => { }, data: [], }); - expect(mockClient.asCurrentUser.search).not.toHaveBeenCalled(); + expect(mockClient.search).not.toHaveBeenCalled(); }); it('should return empty array on index not found error', async () => { - mockClient.asCurrentUser.search.mockImplementationOnce(() => + mockClient.search.mockImplementationOnce(() => Promise.reject({ meta: { body: { @@ -88,7 +86,7 @@ describe('fetchSyncJobs lib', () => { }, data: [], }); - expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({ + expect(mockClient.search).toHaveBeenCalledWith({ from: 0, index: '.elastic-connectors-sync-jobs', query: { @@ -105,7 +103,7 @@ describe('fetchSyncJobs lib', () => { }); }); it('should throw on other errors', async () => { - mockClient.asCurrentUser.search.mockImplementationOnce(() => + mockClient.search.mockImplementationOnce(() => Promise.reject({ meta: { body: { @@ -125,7 +123,7 @@ describe('fetchSyncJobs lib', () => { }, }, }); - expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({ + expect(mockClient.search).toHaveBeenCalledWith({ from: 0, index: '.elastic-connectors-sync-jobs', query: { diff --git a/packages/kbn-search-connectors/lib/fetch_sync_jobs.ts b/packages/kbn-search-connectors/lib/fetch_sync_jobs.ts new file mode 100644 index 0000000000000..1a56103702d83 --- /dev/null +++ b/packages/kbn-search-connectors/lib/fetch_sync_jobs.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_JOBS_INDEX } from '..'; +import { ConnectorSyncJob, SyncJobType } from '../types/connectors'; +import { Paginate } from '../types/pagination'; +import { isNotNullish } from '../utils/is_not_nullish'; + +import { fetchWithPagination } from '../utils/fetch_with_pagination'; +import { isIndexNotFoundException } from '../utils/identify_exceptions'; + +const defaultResult: Paginate = { + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data: [], +}; + +export const fetchSyncJobsByConnectorId = async ( + client: ElasticsearchClient, + connectorId: string, + from: number, + size: number, + syncJobType: 'content' | 'access_control' | 'all' = 'all' +): Promise> => { + try { + const query = + syncJobType === 'all' + ? { + term: { + 'connector.id': connectorId, + }, + } + : { + bool: { + filter: [ + { + term: { + 'connector.id': connectorId, + }, + }, + { + terms: { + job_type: + syncJobType === 'content' + ? [SyncJobType.FULL, SyncJobType.INCREMENTAL] + : [SyncJobType.ACCESS_CONTROL], + }, + }, + ], + }, + }; + const result = await fetchWithPagination( + async () => + await client.search({ + from, + index: CONNECTORS_JOBS_INDEX, + query, + size, + sort: { created_at: { order: 'desc' } }, + }), + from, + size + ); + return { + ...result, + data: result.data + .map((hit) => (hit._source ? { ...hit._source, id: hit._id } : null)) + .filter(isNotNullish), + }; + } catch (error) { + if (isIndexNotFoundException(error)) { + return defaultResult; + } + throw error; + } +}; diff --git a/packages/kbn-search-connectors/lib/index.ts b/packages/kbn-search-connectors/lib/index.ts new file mode 100644 index 0000000000000..4fd4706268327 --- /dev/null +++ b/packages/kbn-search-connectors/lib/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './cancel_syncs'; +export * from './create_connector'; +export * from './create_connector_document'; +export * from './delete_connector'; +export * from './fetch_connectors'; +export * from './fetch_sync_jobs'; +export * from './update_filtering'; +export * from './update_filtering_draft'; +export * from './update_native'; +export * from './start_sync'; +export * from './update_connector_configuration'; +export * from './update_connector_name_and_description'; +export * from './update_connector_scheduling'; +export * from './update_connector_service_type'; +export * from './update_connector_status'; diff --git a/packages/kbn-search-connectors/lib/start_sync.test.ts b/packages/kbn-search-connectors/lib/start_sync.test.ts new file mode 100644 index 0000000000000..c32b82bc7ba7f --- /dev/null +++ b/packages/kbn-search-connectors/lib/start_sync.test.ts @@ -0,0 +1,242 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX, CURRENT_CONNECTORS_JOB_INDEX } from '..'; +import { SyncJobType, SyncStatus, TriggerMethod } from '../types/connectors'; +import { CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX } from '..'; + +import { startConnectorSync } from './start_sync'; + +describe('startSync lib function', () => { + const mockClient = { + get: jest.fn(), + index: jest.fn(), + update: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should start a full sync', async () => { + mockClient.get.mockImplementation(() => { + return Promise.resolve({ + _id: 'connectorId', + _source: { + api_key_id: null, + configuration: {}, + created_at: null, + custom_scheduling: {}, + error: null, + index_name: 'index_name', + language: null, + last_access_control_sync_error: null, + last_access_control_sync_scheduled_at: null, + last_access_control_sync_status: null, + last_seen: null, + last_sync_error: null, + last_sync_scheduled_at: null, + last_sync_status: null, + last_synced: null, + scheduling: { enabled: true, interval: '1 2 3 4 5' }, + service_type: null, + status: 'not connected', + sync_now: false, + }, + index: CONNECTORS_INDEX, + }); + }); + mockClient.index.mockImplementation(() => ({ _id: 'fakeId' })); + + await expect( + startConnectorSync(mockClient as unknown as ElasticsearchClient, { + connectorId: 'connectorId', + jobType: SyncJobType.FULL, + }) + ).resolves.toEqual({ _id: 'fakeId' }); + expect(mockClient.index).toHaveBeenCalledWith({ + document: { + cancelation_requested_at: null, + canceled_at: null, + completed_at: null, + connector: { + configuration: {}, + filtering: null, + id: 'connectorId', + index_name: 'index_name', + language: null, + pipeline: null, + service_type: null, + }, + created_at: expect.any(String), + deleted_document_count: 0, + error: null, + indexed_document_count: 0, + indexed_document_volume: 0, + job_type: SyncJobType.FULL, + last_seen: null, + metadata: {}, + started_at: null, + status: SyncStatus.PENDING, + total_document_count: null, + trigger_method: TriggerMethod.ON_DEMAND, + worker_hostname: null, + }, + index: CURRENT_CONNECTORS_JOB_INDEX, + }); + }); + + it('should not create index if there is no connector', async () => { + mockClient.get.mockImplementation(() => { + return Promise.resolve({}); + }); + await expect( + startConnectorSync(mockClient as unknown as ElasticsearchClient, { + connectorId: 'connectorId', + jobType: SyncJobType.FULL, + }) + ).rejects.toEqual(new Error('resource_not_found')); + expect(mockClient.index).not.toHaveBeenCalled(); + }); + + it('should start an incremental sync', async () => { + mockClient.get.mockImplementation(() => { + return Promise.resolve({ + _id: 'connectorId', + _source: { + api_key_id: null, + configuration: {}, + created_at: null, + custom_scheduling: {}, + error: null, + filtering: [], + index_name: 'index_name', + language: null, + last_access_control_sync_status: null, + last_seen: null, + last_sync_error: null, + last_sync_scheduled_at: null, + last_sync_status: null, + last_synced: null, + scheduling: { enabled: true, interval: '1 2 3 4 5' }, + service_type: null, + status: 'not connected', + sync_now: false, + }, + index: CONNECTORS_INDEX, + }); + }); + mockClient.index.mockImplementation(() => ({ _id: 'fakeId' })); + + await expect( + startConnectorSync(mockClient as unknown as ElasticsearchClient, { + connectorId: 'connectorId', + jobType: SyncJobType.INCREMENTAL, + }) + ).resolves.toEqual({ _id: 'fakeId' }); + expect(mockClient.index).toHaveBeenCalledWith({ + document: { + cancelation_requested_at: null, + canceled_at: null, + completed_at: null, + connector: { + configuration: {}, + filtering: null, + id: 'connectorId', + index_name: 'index_name', + language: null, + pipeline: null, + service_type: null, + }, + created_at: expect.any(String), + deleted_document_count: 0, + error: null, + indexed_document_count: 0, + indexed_document_volume: 0, + job_type: SyncJobType.INCREMENTAL, + last_seen: null, + metadata: {}, + started_at: null, + status: SyncStatus.PENDING, + total_document_count: null, + trigger_method: TriggerMethod.ON_DEMAND, + worker_hostname: null, + }, + index: CURRENT_CONNECTORS_JOB_INDEX, + }); + }); + + it('should start an access control sync', async () => { + mockClient.get.mockImplementation(() => { + return Promise.resolve({ + _id: 'connectorId', + _source: { + api_key_id: null, + configuration: {}, + created_at: null, + custom_scheduling: {}, + error: null, + index_name: 'search-index_name', + language: null, + last_access_control_sync_status: null, + last_seen: null, + last_sync_error: null, + last_sync_scheduled_at: null, + last_sync_status: null, + last_synced: null, + scheduling: { enabled: true, interval: '1 2 3 4 5' }, + service_type: null, + status: 'not connected', + sync_now: false, + }, + index: CONNECTORS_INDEX, + }); + }); + mockClient.index.mockImplementation(() => ({ _id: 'fakeId' })); + + await expect( + startConnectorSync(mockClient as unknown as ElasticsearchClient, { + connectorId: 'connectorId', + targetIndexName: '.search-acl-filter-index_name', + jobType: SyncJobType.ACCESS_CONTROL, + }) + ).resolves.toEqual({ _id: 'fakeId' }); + expect(mockClient.index).toHaveBeenCalledWith({ + document: { + cancelation_requested_at: null, + canceled_at: null, + completed_at: null, + connector: { + configuration: {}, + filtering: null, + id: 'connectorId', + index_name: `${CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX}index_name`, + language: null, + pipeline: null, + service_type: null, + }, + created_at: expect.any(String), + deleted_document_count: 0, + error: null, + indexed_document_count: 0, + indexed_document_volume: 0, + job_type: SyncJobType.ACCESS_CONTROL, + last_seen: null, + metadata: {}, + started_at: null, + status: SyncStatus.PENDING, + total_document_count: null, + trigger_method: TriggerMethod.ON_DEMAND, + worker_hostname: null, + }, + index: CURRENT_CONNECTORS_JOB_INDEX, + }); + }); +}); diff --git a/packages/kbn-search-connectors/lib/start_sync.ts b/packages/kbn-search-connectors/lib/start_sync.ts new file mode 100644 index 0000000000000..878901953e6a1 --- /dev/null +++ b/packages/kbn-search-connectors/lib/start_sync.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX, CURRENT_CONNECTORS_JOB_INDEX } from '..'; +import { + ConnectorConfiguration, + ConnectorDocument, + SyncJobType, + SyncStatus, + TriggerMethod, +} from '../types/connectors'; +import { isConfigEntry } from '../utils/is_category_entry'; + +export const startConnectorSync = async ( + client: ElasticsearchClient, + { + connectorId, + jobType, + targetIndexName, + }: { + connectorId: string; + jobType?: SyncJobType; + targetIndexName?: string; + } +) => { + const connectorResult = await client.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + const connector = connectorResult._source; + if (connector) { + const configuration = Object.entries(connector.configuration).reduce( + (acc, [key, configEntry]) => { + if (isConfigEntry(configEntry)) { + acc[key] = configEntry; + } + return acc; + }, + {} as ConnectorConfiguration + ); + const { + filtering, + index_name: connectorIndexName, + language, + pipeline, + service_type: serviceType, + } = connector; + + const now = new Date().toISOString(); + + return await client.index({ + document: { + cancelation_requested_at: null, + canceled_at: null, + completed_at: null, + connector: { + configuration, + filtering: filtering ? filtering[0]?.active ?? null : null, + id: connectorId, + index_name: targetIndexName || connectorIndexName, + language, + pipeline: pipeline ?? null, + service_type: serviceType, + }, + created_at: now, + deleted_document_count: 0, + error: null, + indexed_document_count: 0, + indexed_document_volume: 0, + job_type: jobType, + last_seen: null, + metadata: {}, + started_at: null, + status: SyncStatus.PENDING, + total_document_count: null, + trigger_method: TriggerMethod.ON_DEMAND, + worker_hostname: null, + }, + index: CURRENT_CONNECTORS_JOB_INDEX, + }); + } else { + throw new Error('resource_not_found'); + } +}; diff --git a/packages/kbn-search-connectors/lib/update_connector_configuration.test.ts b/packages/kbn-search-connectors/lib/update_connector_configuration.test.ts new file mode 100644 index 0000000000000..fd1e014037863 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_configuration.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX } from '..'; +import { fetchConnectorById } from './fetch_connectors'; +import { ConnectorStatus } from '../types/connectors'; + +import { updateConnectorConfiguration } from './update_connector_configuration'; + +jest.mock('./fetch_connectors', () => ({ fetchConnectorById: jest.fn() })); + +describe('updateConnectorConfiguration lib function', () => { + const mockClient = { + update: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + (fetchConnectorById as jest.Mock).mockResolvedValue({ + primaryTerm: 0, + seqNo: 3, + value: { + configuration: { test: { label: 'haha', value: 'this' } }, + id: 'connectorId', + status: ConnectorStatus.NEEDS_CONFIGURATION, + }, + }); + }); + + it('should update configuration', async () => { + await expect( + updateConnectorConfiguration(mockClient as unknown as ElasticsearchClient, 'connectorId', { + test: 'newValue', + }) + ).resolves.toEqual({ test: { label: 'haha', value: 'newValue' } }); + expect(mockClient.update).toHaveBeenCalledWith({ + doc: { + configuration: { test: { label: 'haha', value: 'newValue' } }, + status: ConnectorStatus.CONFIGURED, + }, + id: 'connectorId', + if_primary_term: 0, + if_seq_no: 3, + index: CONNECTORS_INDEX, + }); + }); + + it('should reject if connector does not exist', async () => { + (fetchConnectorById as jest.Mock).mockImplementation(() => undefined); + + await expect( + updateConnectorConfiguration(mockClient as unknown as ElasticsearchClient, 'connectorId', { + test: 'newValue', + }) + ).rejects.toEqual(new Error('Could not find connector')); + expect(mockClient.update).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-search-connectors/lib/update_connector_configuration.ts b/packages/kbn-search-connectors/lib/update_connector_configuration.ts new file mode 100644 index 0000000000000..dfb43af53db44 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_configuration.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { i18n } from '@kbn/i18n'; + +import { CONNECTORS_INDEX } from '..'; + +import { fetchConnectorById } from './fetch_connectors'; +import { ConnectorConfiguration, ConnectorDocument, ConnectorStatus } from '../types/connectors'; +import { isConfigEntry } from '../utils/is_category_entry'; +import { isNotNullish } from '../utils/is_not_nullish'; + +export const updateConnectorConfiguration = async ( + client: ElasticsearchClient, + connectorId: string, + configuration: Record +) => { + const connectorResult = await fetchConnectorById(client, connectorId); + const connector = connectorResult?.value; + if (connector) { + const status = + connector.status === ConnectorStatus.NEEDS_CONFIGURATION + ? ConnectorStatus.CONFIGURED + : connector.status; + const updatedConfig = Object.keys(connector.configuration) + .map((key) => { + const configEntry = connector.configuration[key]; + return isConfigEntry(configEntry) + ? { + ...configEntry, // ugly but needed because typescript refuses to believe this is defined + key, + value: configuration[key] ?? configEntry.value, + } + : undefined; + }) + .filter(isNotNullish) + .reduce((prev: ConnectorConfiguration, curr) => { + const { key, ...config } = curr; + return { ...prev, [curr.key]: config }; + }, {}); + await client.update({ + doc: { configuration: updatedConfig, status }, + id: connectorId, + if_primary_term: connectorResult?.primaryTerm, + if_seq_no: connectorResult?.seqNo, + index: CONNECTORS_INDEX, + }); + return updatedConfig; + } else { + throw new Error( + i18n.translate('searchConnectors.server.connectors.configuration.error', { + defaultMessage: 'Could not find connector', + }) + ); + } +}; diff --git a/packages/kbn-search-connectors/lib/update_connector_name_and_description.ts b/packages/kbn-search-connectors/lib/update_connector_name_and_description.ts new file mode 100644 index 0000000000000..6323387c4259f --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_name_and_description.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { CONNECTORS_INDEX } from '..'; + +import { Connector, ConnectorDocument } from '../types/connectors'; + +export const updateConnectorNameAndDescription = async ( + client: ElasticsearchClient, + connectorId: string, + connectorUpdates: Partial> +) => { + const connectorResult = await client.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + const connector = connectorResult._source; + if (connector) { + const result = await client.index({ + document: { ...connector, ...connectorUpdates }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + await client.indices.refresh({ index: CONNECTORS_INDEX }); + return result; + } else { + throw new Error( + i18n.translate('searchConnectors.server.connectors.serviceType.error', { + defaultMessage: 'Could not find document', + }) + ); + } +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts b/packages/kbn-search-connectors/lib/update_connector_scheduling.test.ts similarity index 76% rename from x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts rename to packages/kbn-search-connectors/lib/update_connector_scheduling.test.ts index c314e101624fb..618522831118d 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts +++ b/packages/kbn-search-connectors/lib/update_connector_scheduling.test.ts @@ -1,26 +1,24 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { IScopedClusterClient } from '@kbn/core/server'; +import { ElasticsearchClient } from '@kbn/core/server'; -import { CONNECTORS_INDEX } from '../..'; +import { CONNECTORS_INDEX } from '..'; import { updateConnectorScheduling } from './update_connector_scheduling'; describe('addConnector lib function', () => { const mockClient = { - asCurrentUser: { - get: jest.fn(), - index: jest.fn(), - indices: { - refresh: jest.fn(), - }, + get: jest.fn(), + index: jest.fn(), + indices: { + refresh: jest.fn(), }, - asInternalUser: {}, }; beforeEach(() => { @@ -28,7 +26,7 @@ describe('addConnector lib function', () => { }); it('should update connector scheduling', async () => { - mockClient.asCurrentUser.get.mockImplementationOnce(() => { + mockClient.get.mockImplementationOnce(() => { return Promise.resolve({ _source: { api_key_id: null, @@ -57,10 +55,10 @@ describe('addConnector lib function', () => { index: CONNECTORS_INDEX, }); }); - mockClient.asCurrentUser.index.mockImplementation(() => ({ _id: 'fakeId' })); + mockClient.index.mockImplementation(() => ({ _id: 'fakeId' })); await expect( - updateConnectorScheduling(mockClient as unknown as IScopedClusterClient, 'connectorId', { + updateConnectorScheduling(mockClient as unknown as ElasticsearchClient, 'connectorId', { access_control: { enabled: false, interval: '* * * * *' }, full: { enabled: true, @@ -69,7 +67,7 @@ describe('addConnector lib function', () => { incremental: { enabled: false, interval: '* * * * *' }, }) ).resolves.toEqual({ _id: 'fakeId' }); - expect(mockClient.asCurrentUser.index).toHaveBeenCalledWith({ + expect(mockClient.index).toHaveBeenCalledWith({ document: { api_key_id: null, configuration: {}, @@ -97,17 +95,17 @@ describe('addConnector lib function', () => { id: 'connectorId', index: CONNECTORS_INDEX, }); - expect(mockClient.asCurrentUser.indices.refresh).toHaveBeenCalledWith({ + expect(mockClient.indices.refresh).toHaveBeenCalledWith({ index: CONNECTORS_INDEX, }); }); it('should not index document if there is no connector', async () => { - mockClient.asCurrentUser.get.mockImplementationOnce(() => { + mockClient.get.mockImplementationOnce(() => { return Promise.resolve({}); }); await expect( - updateConnectorScheduling(mockClient as unknown as IScopedClusterClient, 'connectorId', { + updateConnectorScheduling(mockClient as unknown as ElasticsearchClient, 'connectorId', { access_control: { enabled: false, interval: '* * * * *' }, full: { enabled: true, @@ -116,6 +114,6 @@ describe('addConnector lib function', () => { incremental: { enabled: false, interval: '* * * * *' }, }) ).rejects.toEqual(new Error('Could not find document')); - expect(mockClient.asCurrentUser.index).not.toHaveBeenCalled(); + expect(mockClient.index).not.toHaveBeenCalled(); }); }); diff --git a/packages/kbn-search-connectors/lib/update_connector_scheduling.ts b/packages/kbn-search-connectors/lib/update_connector_scheduling.ts new file mode 100644 index 0000000000000..a0693dc3cd6f6 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_scheduling.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { CONNECTORS_INDEX } from '..'; + +import { ConnectorDocument, SchedulingConfiguraton } from '../types/connectors'; + +export const updateConnectorScheduling = async ( + client: ElasticsearchClient, + connectorId: string, + scheduling: SchedulingConfiguraton +) => { + const connectorResult = await client.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + const connector = connectorResult._source; + if (connector) { + const result = await client.index({ + document: { ...connector, scheduling }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + await client.indices.refresh({ index: CONNECTORS_INDEX }); + return result; + } else { + throw new Error( + i18n.translate('searchConnectors.server.connectors.scheduling.error', { + defaultMessage: 'Could not find document', + }) + ); + } +}; diff --git a/packages/kbn-search-connectors/lib/update_connector_service_type.ts b/packages/kbn-search-connectors/lib/update_connector_service_type.ts new file mode 100644 index 0000000000000..27732e17c8c2a --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_service_type.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { CONNECTORS_INDEX } from '..'; + +import { ConnectorDocument } from '../types/connectors'; + +export const updateConnectorServiceType = async ( + client: ElasticsearchClient, + connectorId: string, + serviceType: string +) => { + const connectorResult = await client.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + const connector = connectorResult._source; + if (connector) { + const result = await client.index({ + document: { ...connector, service_type: serviceType }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + await client.indices.refresh({ index: CONNECTORS_INDEX }); + return result; + } else { + throw new Error( + i18n.translate('searchConnectors.server.connectors.serviceType.error', { + defaultMessage: 'Could not find document', + }) + ); + } +}; diff --git a/packages/kbn-search-connectors/lib/update_connector_status.ts b/packages/kbn-search-connectors/lib/update_connector_status.ts new file mode 100644 index 0000000000000..42ba9adb4f9e0 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_status.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { CONNECTORS_INDEX } from '..'; + +import { ConnectorDocument, ConnectorStatus } from '../types/connectors'; + +export const updateConnectorStatus = async ( + client: ElasticsearchClient, + connectorId: string, + status: ConnectorStatus +) => { + const connectorResult = await client.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + const connector = connectorResult._source; + if (connector) { + const result = await client.index({ + document: { ...connector, status }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + await client.indices.refresh({ index: CONNECTORS_INDEX }); + return result; + } else { + throw new Error( + i18n.translate('searchConnectors.server.connectors.serviceType.error', { + defaultMessage: 'Could not find document', + }) + ); + } +}; diff --git a/packages/kbn-search-connectors/lib/update_filtering.ts b/packages/kbn-search-connectors/lib/update_filtering.ts new file mode 100644 index 0000000000000..5d32639f23396 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_filtering.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_INDEX } from '..'; +import { fetchConnectorById } from './fetch_connectors'; +import { + Connector, + FilteringRule, + FilteringRules, + FilteringValidationState, +} from '../types/connectors'; + +export const updateFiltering = async ( + client: ElasticsearchClient, + connectorId: string, + { + advancedSnippet, + filteringRules, + }: { + advancedSnippet: string; + filteringRules: FilteringRule[]; + } +): Promise => { + const now = new Date().toISOString(); + const parsedAdvancedSnippet: Record = advancedSnippet + ? JSON.parse(advancedSnippet) + : {}; + const parsedFilteringRules = filteringRules.map((filteringRule) => ({ + ...filteringRule, + created_at: filteringRule.created_at ? filteringRule.created_at : now, + updated_at: now, + })); + const connectorResult = await fetchConnectorById(client, connectorId); + if (!connectorResult) { + throw new Error(`Could not find connector with id ${connectorId}`); + } + const { value: connector, seqNo, primaryTerm } = connectorResult; + const active: FilteringRules = { + advanced_snippet: { + created_at: connector.filtering[0].active.advanced_snippet.created_at || now, + updated_at: now, + value: parsedAdvancedSnippet, + }, + rules: parsedFilteringRules, + validation: { + errors: [], + state: FilteringValidationState.VALID, + }, + }; + + const result = await client.update({ + doc: { ...connector, filtering: [{ ...connector.filtering[0], active, draft: active }] }, + id: connectorId, + if_primary_term: primaryTerm, + if_seq_no: seqNo, + index: CONNECTORS_INDEX, + }); + + return result.result === 'updated' ? active : undefined; +}; diff --git a/packages/kbn-search-connectors/lib/update_filtering_draft.ts b/packages/kbn-search-connectors/lib/update_filtering_draft.ts new file mode 100644 index 0000000000000..deb6eabd66e23 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_filtering_draft.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_INDEX } from '..'; +import { fetchConnectorById } from './fetch_connectors'; +import { + Connector, + FilteringRule, + FilteringRules, + FilteringValidationState, +} from '../types/connectors'; + +export const updateFilteringDraft = async ( + client: ElasticsearchClient, + connectorId: string, + { + advancedSnippet, + filteringRules, + }: { + advancedSnippet: string; + filteringRules: FilteringRule[]; + } +): Promise => { + const now = new Date().toISOString(); + const parsedAdvancedSnippet: Record = advancedSnippet + ? JSON.parse(advancedSnippet) + : {}; + const parsedFilteringRules = filteringRules.map((filteringRule) => ({ + ...filteringRule, + created_at: filteringRule.created_at ? filteringRule.created_at : now, + updated_at: now, + })); + const draft: FilteringRules = { + advanced_snippet: { + created_at: now, + updated_at: now, + value: parsedAdvancedSnippet, + }, + rules: parsedFilteringRules, + validation: { + errors: [], + state: FilteringValidationState.EDITED, + }, + }; + const connectorResult = await fetchConnectorById(client, connectorId); + if (!connectorResult) { + throw new Error(`Could not find connector with id ${connectorId}`); + } + const { value: connector, seqNo, primaryTerm } = connectorResult; + + const result = await client.update({ + doc: { ...connector, filtering: [{ ...connector.filtering[0], draft }] }, + id: connectorId, + if_primary_term: primaryTerm, + if_seq_no: seqNo, + index: CONNECTORS_INDEX, + }); + + return result.result === 'updated' ? draft : undefined; +}; diff --git a/packages/kbn-search-connectors/lib/update_native.ts b/packages/kbn-search-connectors/lib/update_native.ts new file mode 100644 index 0000000000000..73af04e75aa8b --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_native.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_INDEX } from '..'; +import { Connector, ConnectorStatus } from '../types/connectors'; + +export const putUpdateNative = async ( + client: ElasticsearchClient, + connectorId: string, + isNative: boolean +) => { + const result = await client.update({ + doc: { + is_native: isNative, + status: ConnectorStatus.CONFIGURED, + }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + + return result; +}; diff --git a/packages/kbn-search-connectors/package.json b/packages/kbn-search-connectors/package.json new file mode 100644 index 0000000000000..d2fb29822e269 --- /dev/null +++ b/packages/kbn-search-connectors/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/search-connectors", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-search-connectors/tsconfig.json b/packages/kbn-search-connectors/tsconfig.json new file mode 100644 index 0000000000000..a8c8d3c9840a6 --- /dev/null +++ b/packages/kbn-search-connectors/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n", + "@kbn/core", + "@kbn/core-elasticsearch-server", + ] +} diff --git a/packages/kbn-search-connectors/types/connectors.ts b/packages/kbn-search-connectors/types/connectors.ts new file mode 100644 index 0000000000000..8f649c15f348f --- /dev/null +++ b/packages/kbn-search-connectors/types/connectors.ts @@ -0,0 +1,277 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface SelectOption { + label: string; + value: string; +} + +export interface Dependency { + field: string; + value: string | number | boolean | null; +} + +export type DependencyLookup = Record; + +export enum DisplayType { + TEXTBOX = 'textbox', + TEXTAREA = 'textarea', + NUMERIC = 'numeric', + TOGGLE = 'toggle', + DROPDOWN = 'dropdown', +} + +export enum FieldType { + STRING = 'str', + INTEGER = 'int', + LIST = 'list', + BOOLEAN = 'bool', +} + +export interface ConnectorConfigCategoryProperties { + label: string; + order: number; + type: 'category'; +} + +export interface Validation { + constraint: string | number; + type: string; +} + +export interface ConnectorConfigProperties { + category?: string; + default_value: string | number | boolean | null; + depends_on: Dependency[]; + display: DisplayType; + label: string; + options: SelectOption[]; + order?: number | null; + placeholder?: string; + required: boolean; + sensitive: boolean; + tooltip: string | null; + type: FieldType; + ui_restrictions: string[]; + validations: Validation[]; + value: string | number | boolean | null; +} + +export type ConnectorConfiguration = Record< + string, + ConnectorConfigProperties | ConnectorConfigCategoryProperties | null +> & { + extract_full_html?: { label: string; value: boolean }; // This only exists for Crawler + use_document_level_security?: ConnectorConfigProperties; + use_text_extraction_service?: ConnectorConfigProperties; // This only exists for SharePoint Online +}; + +export interface ConnectorScheduling { + enabled: boolean; + interval: string; // interval has crontab syntax +} + +export interface CustomScheduling { + configuration_overrides: Record; + enabled: boolean; + interval: string; + last_synced: string | null; + name: string; +} + +export type ConnectorCustomScheduling = Record; + +export enum ConnectorStatus { + CREATED = 'created', + NEEDS_CONFIGURATION = 'needs_configuration', + CONFIGURED = 'configured', + CONNECTED = 'connected', + ERROR = 'error', +} + +export enum SyncStatus { + CANCELING = 'canceling', + CANCELED = 'canceled', + COMPLETED = 'completed', + ERROR = 'error', + IN_PROGRESS = 'in_progress', + PENDING = 'pending', + SUSPENDED = 'suspended', +} + +export interface IngestPipelineParams { + extract_binary_content: boolean; + name: string; + reduce_whitespace: boolean; + run_ml_inference: boolean; +} + +export enum FilteringPolicy { + EXCLUDE = 'exclude', + INCLUDE = 'include', +} + +export enum FilteringRuleRule { + CONTAINS = 'contains', + ENDS_WITH = 'ends_with', + EQUALS = 'equals', + GT = '>', + LT = '<', + REGEX = 'regex', + STARTS_WITH = 'starts_with', +} + +export interface FilteringRule { + created_at: string; + field: string; + id: string; + order: number; + policy: FilteringPolicy; + rule: FilteringRuleRule; + updated_at: string; + value: string; +} + +export interface FilteringValidation { + ids: string[]; + messages: string[]; +} + +export enum FilteringValidationState { + EDITED = 'edited', + INVALID = 'invalid', + VALID = 'valid', +} + +export interface FilteringRules { + advanced_snippet: { + created_at: string; + updated_at: string; + value: Record; + }; + rules: FilteringRule[]; + validation: { + errors: FilteringValidation[]; + state: FilteringValidationState; + }; +} + +export interface FilteringConfig { + active: FilteringRules; + domain: string; + draft: FilteringRules; +} + +export enum TriggerMethod { + ON_DEMAND = 'on_demand', + SCHEDULED = 'scheduled', +} + +export enum SyncJobType { + FULL = 'full', + INCREMENTAL = 'incremental', + ACCESS_CONTROL = 'access_control', +} + +export enum FeatureName { + FILTERING_ADVANCED_CONFIG = 'filtering_advanced_config', + FILTERING_RULES = 'filtering_rules', + DOCUMENT_LEVEL_SECURITY = 'document_level_security', + INCREMENTAL_SYNC = 'incremental_sync', + SYNC_RULES = 'sync_rules', +} + +export type ConnectorFeatures = Partial<{ + [FeatureName.DOCUMENT_LEVEL_SECURITY]: { enabled: boolean }; + [FeatureName.FILTERING_ADVANCED_CONFIG]: boolean; + [FeatureName.FILTERING_RULES]: boolean; + [FeatureName.INCREMENTAL_SYNC]: { enabled: boolean }; + [FeatureName.SYNC_RULES]: { + advanced?: { + enabled: boolean; + }; + basic?: { + enabled: boolean; + }; + }; +}> | null; + +export interface SchedulingConfiguraton { + access_control: ConnectorScheduling; + full: ConnectorScheduling; + incremental: ConnectorScheduling; +} + +export interface Connector { + api_key_id: string | null; + configuration: ConnectorConfiguration; + custom_scheduling: ConnectorCustomScheduling; + description: string | null; + error: string | null; + features: ConnectorFeatures; + filtering: FilteringConfig[]; + id: string; + index_name: string | null; + is_native: boolean; + language: string | null; + last_access_control_sync_error: string | null; + last_access_control_sync_scheduled_at: string | null; + last_access_control_sync_status: SyncStatus | null; + last_incremental_sync_scheduled_at: string | null; + last_seen: string | null; + last_sync_error: string | null; + last_sync_scheduled_at: string | null; + last_sync_status: SyncStatus | null; + last_synced: string | null; + name: string; + pipeline?: IngestPipelineParams | null; + scheduling: SchedulingConfiguraton; + service_type: string | null; + status: ConnectorStatus; + sync_now: boolean; +} + +export type ConnectorDocument = Omit; + +export interface ConnectorSyncJob { + cancelation_requested_at: string | null; + canceled_at: string | null; + completed_at: string | null; + connector: { + configuration: ConnectorConfiguration; + filtering: FilteringRules | FilteringRules[] | null; + id: string; + index_name: string; + language: string | null; + pipeline: IngestPipelineParams | null; + service_type: string | null; + }; + created_at: string; + deleted_document_count: number; + error: string | null; + id: string; + indexed_document_count: number; + indexed_document_volume: number; + job_type: SyncJobType; + last_seen: string | null; + metadata: Record; + started_at: string | null; + status: SyncStatus; + total_document_count: number | null; + trigger_method: TriggerMethod; + worker_hostname: string | null; +} + +export type ConnectorSyncJobDocument = Omit; + +export interface NativeConnector { + configuration: ConnectorConfiguration; + features: Connector['features']; + name: string; + serviceType: string; +} diff --git a/packages/kbn-search-connectors/types/index.ts b/packages/kbn-search-connectors/types/index.ts new file mode 100644 index 0000000000000..f7ef8e3b4d941 --- /dev/null +++ b/packages/kbn-search-connectors/types/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './connectors'; +export * from './native_connectors'; +export * from './optimistic_concurrency'; +export * from './pagination'; diff --git a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts b/packages/kbn-search-connectors/types/native_connectors.ts similarity index 83% rename from x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts rename to packages/kbn-search-connectors/types/native_connectors.ts index b12c23e26351a..0e767b4bdd345 100644 --- a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts +++ b/packages/kbn-search-connectors/types/native_connectors.ts @@ -1,59 +1,60 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { i18n } from '@kbn/i18n'; -import { DisplayType, FeatureName, FieldType, NativeConnector } from '../types/connectors'; +import { DisplayType, FeatureName, FieldType, NativeConnector } from './connectors'; -const USERNAME_LABEL = i18n.translate('xpack.enterpriseSearch.nativeConnectors.usernameLabel', { +const USERNAME_LABEL = i18n.translate('searchConnectors.nativeConnectors.usernameLabel', { defaultMessage: 'Username', }); -const PASSWORD_LABEL = i18n.translate('xpack.enterpriseSearch.nativeConnectors.passwordLabel', { +const PASSWORD_LABEL = i18n.translate('searchConnectors.nativeConnectors.passwordLabel', { defaultMessage: 'Password', }); -const ENABLE_SSL_LABEL = i18n.translate('xpack.enterpriseSearch.nativeConnectors.enableSSL.label', { +const ENABLE_SSL_LABEL = i18n.translate('searchConnectors.nativeConnectors.enableSSL.label', { defaultMessage: 'Enable SSL', }); const SSL_CERTIFICATE_LABEL = i18n.translate( - 'xpack.enterpriseSearch.nativeConnectors.sslCertificate.label', + 'searchConnectors.nativeConnectors.sslCertificate.label', { defaultMessage: 'SSL certificate', } ); const RETRIES_PER_REQUEST_LABEL = i18n.translate( - 'xpack.enterpriseSearch.nativeConnectors.retriesPerRequest.label', + 'searchConnectors.nativeConnectors.retriesPerRequest.label', { defaultMessage: 'Retries per request', } ); const ADVANCED_RULES_IGNORED_LABEL = i18n.translate( - 'xpack.enterpriseSearch.nativeConnectors.advancedRulesIgnored.label', + 'searchConnectors.nativeConnectors.advancedRulesIgnored.label', { defaultMessage: 'This configurable field is ignored when Advanced Sync Rules are used.', } ); const MAX_CONCURRENT_DOWNLOADS_LABEL = i18n.translate( - 'xpack.enterpriseSearch.nativeConnectors.nativeConnectors.maximumConcurrentLabel', + 'searchConnectors.nativeConnectors.nativeConnectors.maximumConcurrentLabel', { defaultMessage: 'Maximum concurrent downloads', } ); -const DATABASE_LABEL = i18n.translate('xpack.enterpriseSearch.nativeConnectors.databaseLabel', { +const DATABASE_LABEL = i18n.translate('searchConnectors.nativeConnectors.databaseLabel', { defaultMessage: 'Database', }); -const PORT_LABEL = i18n.translate('xpack.enterpriseSearch.nativeConnectors.portLabel', { +const PORT_LABEL = i18n.translate('searchConnectors.nativeConnectors.portLabel', { defaultMessage: 'Port', }); @@ -65,7 +66,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record { + primaryTerm: number | undefined; + seqNo: number | undefined; + value: T; +} diff --git a/packages/kbn-search-connectors/types/pagination.ts b/packages/kbn-search-connectors/types/pagination.ts new file mode 100644 index 0000000000000..65f397105013b --- /dev/null +++ b/packages/kbn-search-connectors/types/pagination.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface Page { + from: number; // current page index, 0-based + has_more_hits_than_total?: boolean; + size: number; // size per page + total: number; // total number of hits +} +export interface Meta { + page: Page; +} + +export interface Paginate { + _meta: Meta; + data: T[]; +} diff --git a/packages/kbn-search-connectors/utils/fetch_with_pagination.test.ts b/packages/kbn-search-connectors/utils/fetch_with_pagination.test.ts new file mode 100644 index 0000000000000..9fa0831955840 --- /dev/null +++ b/packages/kbn-search-connectors/utils/fetch_with_pagination.test.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; + +import { fetchWithPagination } from './fetch_with_pagination'; + +describe('fetchWithPagination util', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('fetchWithPagination', () => { + it('should fetch mock data with pagination', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { hits: ['result1', 'result2'], total: 2 }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 2, + }, + }, + data: ['result1', 'result2'], + }); + }); + it('should return empty result if size is 0', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { hits: [], total: 0 }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 0)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data: [], + }); + }); + it('should handle total as an object correctly', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { + hits: [], + total: { + relation: 'lte', + value: 555, + }, + }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 555, + }, + }, + data: [], + }); + }); + + it('should handle undefined total correctly', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { + hits: [], + total: undefined, + }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data: [], + }); + }); + + it('should handle has_more_hits_than_total correctly', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { hits: ['result1', 'result2'], total: { relation: 'gte', value: 10000 } }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 50, 10)).resolves.toEqual({ + _meta: { + page: { + from: 50, + has_more_hits_than_total: true, + size: 10, + total: 10000, + }, + }, + data: ['result1', 'result2'], + }); + }); + }); +}); diff --git a/packages/kbn-search-connectors/utils/fetch_with_pagination.ts b/packages/kbn-search-connectors/utils/fetch_with_pagination.ts new file mode 100644 index 0000000000000..fd6c6a5232374 --- /dev/null +++ b/packages/kbn-search-connectors/utils/fetch_with_pagination.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SearchHit, SearchResponse, SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; + +import { Paginate } from '../types/pagination'; + +const defaultResult = (data: T[]) => ({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data, +}); + +export const fetchWithPagination = async ( + fetchFunction: () => Promise>, + from: number, + size: number +): Promise>> => { + if (size === 0) { + return defaultResult>([]); + } + const result = await fetchFunction(); + const total = totalToPaginateTotal(result.hits.total); + return { + _meta: { + page: { + from, + size, + ...total, + }, + }, + data: result.hits.hits, + }; +}; + +function totalToPaginateTotal(input: number | SearchTotalHits | undefined): { + has_more_hits_than_total: boolean; + total: number; +} { + if (typeof input === 'number') { + return { has_more_hits_than_total: false, total: input }; + } + + return input + ? { + has_more_hits_than_total: input.relation === 'gte' ? true : false, + total: input.value, + } + : { has_more_hits_than_total: false, total: 0 }; +} diff --git a/packages/kbn-search-connectors/utils/identify_exceptions.test.ts b/packages/kbn-search-connectors/utils/identify_exceptions.test.ts new file mode 100644 index 0000000000000..321b0310353bb --- /dev/null +++ b/packages/kbn-search-connectors/utils/identify_exceptions.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isIndexNotFoundException, isResourceAlreadyExistsException } from './identify_exceptions'; + +describe('IdentifyExceptions', () => { + describe('IsIndexNotFoundException', () => { + it('should return true for index not found exception', () => { + const error = { + meta: { + body: { + error: { + type: 'index_not_found_exception', + }, + status: 404, + }, + name: 'ResponseError', + statusCode: 404, + }, + }; + expect(isIndexNotFoundException(error as any)).toEqual(true); + }); + it('should return false for other exception', () => { + const error = { + meta: { + body: { + error: { + type: 'other_exception', + }, + status: 404, + }, + name: 'ResponseError', + statusCode: 404, + }, + }; + expect(isIndexNotFoundException(error as any)).toEqual(false); + }); + it('should return false for other object', () => { + expect(isIndexNotFoundException({} as any)).toEqual(false); + }); + }); + describe('isResourceAlreadyExistsError', () => { + it('should return true for resource already exists exception', () => { + const error = { + meta: { + body: { + error: { + type: 'resource_already_exists_exception', + }, + status: 400, + }, + name: 'ResponseError', + statusCode: 400, + }, + }; + expect(isResourceAlreadyExistsException(error as any)).toEqual(true); + }); + it('should return false for other exception', () => { + const error = { + meta: { + body: { + error: { + type: 'other_exception', + }, + status: 404, + }, + name: 'ResponseError', + statusCode: 404, + }, + }; + expect(isResourceAlreadyExistsException(error as any)).toEqual(false); + }); + it('should return false for other object', () => { + expect(isResourceAlreadyExistsException({} as any)).toEqual(false); + }); + }); +}); diff --git a/packages/kbn-search-connectors/utils/identify_exceptions.ts b/packages/kbn-search-connectors/utils/identify_exceptions.ts new file mode 100644 index 0000000000000..0bc395710d8f5 --- /dev/null +++ b/packages/kbn-search-connectors/utils/identify_exceptions.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface ElasticsearchResponseError { + meta?: { + body?: { + error?: { + type: string; + }; + }; + statusCode?: number; + }; + name: 'ResponseError'; +} + +const MISSING_ALIAS_ERROR = new RegExp(/^alias \[.+\] missing/); + +export const isIndexNotFoundException = (error: ElasticsearchResponseError) => + error?.meta?.body?.error?.type === 'index_not_found_exception'; + +export const isResourceAlreadyExistsException = (error: ElasticsearchResponseError) => + error?.meta?.body?.error?.type === 'resource_already_exists_exception'; + +export const isResourceNotFoundException = (error: ElasticsearchResponseError) => + error?.meta?.body?.error?.type === 'resource_not_found_exception'; + +export const isUnauthorizedException = (error: ElasticsearchResponseError) => + error.meta?.statusCode === 403; + +export const isNotFoundException = (error: ElasticsearchResponseError) => + error.meta?.statusCode === 404; + +export const isIllegalArgumentException = (error: ElasticsearchResponseError) => + error.meta?.body?.error?.type === 'illegal_argument_exception'; + +export const isVersionConflictEngineException = (error: ElasticsearchResponseError) => + error.meta?.body?.error?.type === 'version_conflict_engine_exception'; + +export const isInvalidSearchApplicationNameException = (error: ElasticsearchResponseError) => + error.meta?.body?.error?.type === 'invalid_alias_name_exception'; + +export const isMissingAliasException = (error: ElasticsearchResponseError) => + error.meta?.statusCode === 404 && + typeof error.meta?.body?.error === 'string' && + MISSING_ALIAS_ERROR.test(error.meta?.body?.error); diff --git a/packages/kbn-search-connectors/utils/index.ts b/packages/kbn-search-connectors/utils/index.ts new file mode 100644 index 0000000000000..ae2bbe2a818aa --- /dev/null +++ b/packages/kbn-search-connectors/utils/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './is_category_entry'; diff --git a/packages/kbn-search-connectors/utils/is_category_entry.ts b/packages/kbn-search-connectors/utils/is_category_entry.ts new file mode 100644 index 0000000000000..fb72db52944df --- /dev/null +++ b/packages/kbn-search-connectors/utils/is_category_entry.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ConnectorConfigProperties, ConnectorConfigCategoryProperties } from '../types/connectors'; + +export function isCategoryEntry( + input: + | ConnectorConfigProperties + | ConnectorConfigCategoryProperties + | { label: string; value: boolean } + | null +): input is ConnectorConfigCategoryProperties { + return (input as ConnectorConfigCategoryProperties)?.type === 'category'; +} + +export function isConfigEntry( + input: + | ConnectorConfigProperties + | ConnectorConfigCategoryProperties + | { label: string; value: boolean } + | null +): input is ConnectorConfigProperties { + return (input as ConnectorConfigCategoryProperties).type !== 'category'; +} diff --git a/packages/kbn-search-connectors/utils/is_not_nullish.ts b/packages/kbn-search-connectors/utils/is_not_nullish.ts new file mode 100644 index 0000000000000..88b39b70d4ec8 --- /dev/null +++ b/packages/kbn-search-connectors/utils/is_not_nullish.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export function isNotNullish(value: T | null | undefined): value is T { + return value !== null && value !== undefined; +} diff --git a/packages/kbn-search-response-warnings/index.ts b/packages/kbn-search-response-warnings/index.ts index 8ef18d86b9a92..d81b3510553e6 100644 --- a/packages/kbn-search-response-warnings/index.ts +++ b/packages/kbn-search-response-warnings/index.ts @@ -13,7 +13,5 @@ export { type SearchResponseWarningsProps, } from './src/components/search_response_warnings'; -export { - getSearchResponseInterceptedWarnings, - removeInterceptedWarningDuplicates, -} from './src/utils/get_search_response_intercepted_warnings'; +export { getSearchResponseInterceptedWarnings } from './src/utils/get_search_response_intercepted_warnings'; +export { hasUnsupportedDownsampledAggregationFailure } from './src/utils/has_unsupported_downsampled_aggregation_failure'; diff --git a/packages/kbn-search-response-warnings/src/__mocks__/search_response_warnings.ts b/packages/kbn-search-response-warnings/src/__mocks__/search_response_warnings.ts index 9fe2cf02a1671..d4110f1bb62b7 100644 --- a/packages/kbn-search-response-warnings/src/__mocks__/search_response_warnings.ts +++ b/packages/kbn-search-response-warnings/src/__mocks__/search_response_warnings.ts @@ -8,43 +8,33 @@ import type { SearchResponseWarning } from '@kbn/data-plugin/public'; -export const searchResponseTimeoutWarningMock: SearchResponseWarning = { - type: 'timed_out', - message: 'Data might be incomplete because your request timed out', - reason: undefined, -}; - -export const searchResponseShardFailureWarningMock: SearchResponseWarning = { - type: 'shard_failure', - message: '3 of 4 shards failed', - text: 'The data might be incomplete or wrong.', - reason: { - type: 'illegal_argument_exception', - reason: 'Field [__anonymous_] of type [boolean] does not support custom formats', - }, -}; - -export const searchResponseWarningsMock: SearchResponseWarning[] = [ - searchResponseTimeoutWarningMock, - searchResponseShardFailureWarningMock, - { - type: 'shard_failure', - message: '3 of 4 shards failed', - text: 'The data might be incomplete or wrong.', - reason: { - type: 'query_shard_exception', - reason: - 'failed to create query: [.ds-kibana_sample_data_logs-2023.07.11-000001][0] Testing shard failures!', - }, - }, - { - type: 'shard_failure', - message: '1 of 4 shards failed', - text: 'The data might be incomplete or wrong.', - reason: { - type: 'query_shard_exception', - reason: - 'failed to create query: [.ds-kibana_sample_data_logs-2023.07.11-000001][0] Testing shard failures!', +export const searchResponseIncompleteWarningLocalCluster: SearchResponseWarning = { + type: 'incomplete', + message: 'The data might be incomplete or wrong.', + clusters: { + '(local)': { + status: 'partial', + indices: '', + took: 25, + timed_out: false, + _shards: { + total: 4, + successful: 3, + skipped: 0, + failed: 1, + }, + failures: [ + { + shard: 0, + index: 'sample-01-rollup', + node: 'VFTFJxpHSdaoiGxJFLSExQ', + reason: { + type: 'illegal_argument_exception', + reason: + 'Field [kubernetes.container.memory.available.bytes] of type [aggregate_metric_double] is not supported for aggregation [percentiles]', + }, + }, + ], }, }, -]; +}; diff --git a/packages/kbn-search-response-warnings/src/components/search_response_warnings/__snapshots__/search_response_warnings.test.tsx.snap b/packages/kbn-search-response-warnings/src/components/search_response_warnings/__snapshots__/search_response_warnings.test.tsx.snap index 01f917a0e6dbc..079d3d9b90b06 100644 --- a/packages/kbn-search-response-warnings/src/components/search_response_warnings/__snapshots__/search_response_warnings.test.tsx.snap +++ b/packages/kbn-search-response-warnings/src/components/search_response_warnings/__snapshots__/search_response_warnings.test.tsx.snap @@ -13,7 +13,7 @@ exports[`SearchResponseWarnings renders "badge" correctly 1`] = ` @@ -68,84 +68,14 @@ exports[`SearchResponseWarnings renders "callout" correctly 1`] = ` class="euiText emotion-euiText-s" data-test-subj="test1_warningTitle" > - Data might be incomplete because your request timed out -
    -
    -
    -
    -
    - -
    -
    -

    -

    - -
  • -
    -

    -

    -
    -
    -
    -
    -
    - - 3 of 4 shards failed - -
    -
    -
    -
    -

    - The data might be incomplete or wrong. -

    + The data might be incomplete or wrong.
    @@ -155,161 +85,7 @@ exports[`SearchResponseWarnings renders "callout" correctly 1`] = ` > -
    -
    -

    -

    -
  • -
  • -
    -

    -

    -
    -
    -
    -
    -
    - - 3 of 4 shards failed - -
    -
    -
    -
    -

    - The data might be incomplete or wrong. -

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

    -

    -
  • -
  • -
    -

    -

    -
    -
    -
    -
    -
    - - 1 of 4 shards failed - -
    -
    -
    -
    -

    - The data might be incomplete or wrong. -

    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
  • -
  • -
    -
    -
    - - 3 of 4 shards failed - -
    -
    -
    -
    -

    - The data might be incomplete or wrong. -

    -
    -
    -
    - -
    -
    -
  • -
  • -
    -
    -
    - - 3 of 4 shards failed - -
    -
    -
    -
    -

    - The data might be incomplete or wrong. -

    -
    -
    -
    - -
    -
    -
  • -
  • -
    -
    -
    - - 1 of 4 shards failed - -
    -
    -
    -
    -

    - The data might be incomplete or wrong. -

    + The data might be incomplete or wrong.
    diff --git a/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.test.tsx b/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.test.tsx index 6e3c1b1a0d08d..ca51bd8babee8 100644 --- a/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.test.tsx +++ b/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.test.tsx @@ -9,12 +9,14 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { SearchResponseWarnings } from './search_response_warnings'; -import { searchResponseWarningsMock } from '../../__mocks__/search_response_warnings'; +import { searchResponseIncompleteWarningLocalCluster } from '../../__mocks__/search_response_warnings'; -const interceptedWarnings = searchResponseWarningsMock.map((originalWarning, index) => ({ - originalWarning, - action: originalWarning.type === 'shard_failure' ? : undefined, -})); +const interceptedWarnings = [ + { + originalWarning: searchResponseIncompleteWarningLocalCluster, + action: , + }, +]; describe('SearchResponseWarnings', () => { it('renders "callout" correctly', () => { diff --git a/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.tsx b/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.tsx index 3c92096aa982b..8588e7275505e 100644 --- a/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.tsx +++ b/packages/kbn-search-response-warnings/src/components/search_response_warnings/search_response_warnings.tsx @@ -270,22 +270,13 @@ function WarningContent({ groupStyles?: Partial; 'data-test-subj': string; }) { - const hasDescription = 'text' in originalWarning; - return ( - {hasDescription ? {originalWarning.message} : originalWarning.message} + {originalWarning.message} - {hasDescription ? ( - - -

    {originalWarning.text}

    -
    -
    - ) : null} {action ? {action} : null}
    ); @@ -306,6 +297,7 @@ function CalloutTitleWrapper({ onClick={onCloseCallout} type="button" iconType="cross" + color="warning" /> diff --git a/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.test.tsx b/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.test.tsx index 34ae546f42ba6..b6bf93169ae63 100644 --- a/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.test.tsx +++ b/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.test.tsx @@ -9,11 +9,8 @@ import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { coreMock } from '@kbn/core/public/mocks'; -import { - getSearchResponseInterceptedWarnings, - removeInterceptedWarningDuplicates, -} from './get_search_response_intercepted_warnings'; -import { searchResponseWarningsMock } from '../__mocks__/search_response_warnings'; +import { getSearchResponseInterceptedWarnings } from './get_search_response_intercepted_warnings'; +import { searchResponseIncompleteWarningLocalCluster } from '../__mocks__/search_response_warnings'; const servicesMock = { data: dataPluginMock.createStartContract(), @@ -23,162 +20,66 @@ const servicesMock = { describe('getSearchResponseInterceptedWarnings', () => { const adapter = new RequestAdapter(); - it('should catch warnings correctly', () => { + it('should return intercepted incomplete data warnings', () => { const services = { ...servicesMock, }; services.data.search.showWarnings = jest.fn((_, callback) => { // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[0], {}); - // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[1], {}); - // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[2], {}); - // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[3], {}); - - // plus duplicates - // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[0], {}); - // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[1], {}); - // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[2], {}); + callback?.(searchResponseIncompleteWarningLocalCluster, {}); }); - expect( - getSearchResponseInterceptedWarnings({ - services, - adapter, - options: { - disableShardFailureWarning: true, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "action": undefined, - "originalWarning": Object { - "message": "Data might be incomplete because your request timed out", - "reason": undefined, - "type": "timed_out", - }, - }, - Object { - "action": , - "originalWarning": Object { - "message": "3 of 4 shards failed", - "reason": Object { - "reason": "Field [__anonymous_] of type [boolean] does not support custom formats", - "type": "illegal_argument_exception", - }, - "text": "The data might be incomplete or wrong.", - "type": "shard_failure", - }, - }, - Object { - "action": , - "originalWarning": Object { - "message": "3 of 4 shards failed", - "reason": Object { - "reason": "failed to create query: [.ds-kibana_sample_data_logs-2023.07.11-000001][0] Testing shard failures!", - "type": "query_shard_exception", + const warnings = getSearchResponseInterceptedWarnings({ + services, + adapter, + }); + + expect(warnings.length).toBe(1); + expect(warnings[0].originalWarning).toEqual(searchResponseIncompleteWarningLocalCluster); + expect(warnings[0].action).toMatchInlineSnapshot(` + , - "originalWarning": Object { - "message": "1 of 4 shards failed", - "reason": Object { - "reason": "failed to create query: [.ds-kibana_sample_data_logs-2023.07.11-000001][0] Testing shard failures!", - "type": "query_shard_exception", + "failures": Array [ + Object { + "index": "sample-01-rollup", + "node": "VFTFJxpHSdaoiGxJFLSExQ", + "reason": Object { + "reason": "Field [kubernetes.container.memory.available.bytes] of type [aggregate_metric_double] is not supported for aggregation [percentiles]", + "type": "illegal_argument_exception", + }, + "shard": 0, + }, + ], + "indices": "", + "status": "partial", + "timed_out": false, + "took": 25, + }, }, - "text": "The data might be incomplete or wrong.", - "type": "shard_failure", - }, - }, - ] + "message": "The data might be incomplete or wrong.", + "type": "incomplete", + } + } + /> `); }); - - it('should not catch any warnings if disableShardFailureWarning is false', () => { - const services = { - ...servicesMock, - }; - services.data.search.showWarnings = jest.fn((_, callback) => { - // @ts-expect-error for empty meta - callback?.(searchResponseWarningsMock[0], {}); - }); - expect( - getSearchResponseInterceptedWarnings({ - services, - adapter, - options: { - disableShardFailureWarning: false, - }, - }) - ).toBeUndefined(); - }); -}); - -describe('removeInterceptedWarningDuplicates', () => { - it('should remove duplicates successfully', () => { - const interceptedWarnings = searchResponseWarningsMock.map((originalWarning) => ({ - originalWarning, - })); - - expect(removeInterceptedWarningDuplicates([interceptedWarnings[0]])).toEqual([ - interceptedWarnings[0], - ]); - expect(removeInterceptedWarningDuplicates(interceptedWarnings)).toEqual(interceptedWarnings); - expect( - removeInterceptedWarningDuplicates([...interceptedWarnings, ...interceptedWarnings]) - ).toEqual(interceptedWarnings); - }); - - it('should return undefined if the list is empty', () => { - expect(removeInterceptedWarningDuplicates([])).toBeUndefined(); - expect(removeInterceptedWarningDuplicates(undefined)).toBeUndefined(); - }); }); diff --git a/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx b/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx index 38ad0da2639f7..6518d12109a1d 100644 --- a/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx +++ b/packages/kbn-search-response-warnings/src/utils/get_search_response_intercepted_warnings.tsx @@ -7,11 +7,9 @@ */ import React from 'react'; -import { uniqBy } from 'lodash'; import { type DataPublicPluginStart, - type ShardFailureRequest, - ShardFailureOpenModalButton, + OpenIncompleteResultsModalButton, } from '@kbn/data-plugin/public'; import type { RequestAdapter } from '@kbn/inspector-plugin/common'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; @@ -26,21 +24,13 @@ import type { SearchResponseInterceptedWarning } from '../types'; export const getSearchResponseInterceptedWarnings = ({ services, adapter, - options, }: { services: { data: DataPublicPluginStart; theme: CoreStart['theme']; }; adapter: RequestAdapter; - options?: { - disableShardFailureWarning?: boolean; - }; -}): SearchResponseInterceptedWarning[] | undefined => { - if (!options?.disableShardFailureWarning) { - return undefined; - } - +}): SearchResponseInterceptedWarning[] => { const interceptedWarnings: SearchResponseInterceptedWarning[] = []; services.data.search.showWarnings(adapter, (warning, meta) => { @@ -49,13 +39,13 @@ export const getSearchResponseInterceptedWarnings = ({ interceptedWarnings.push({ originalWarning: warning, action: - warning.type === 'shard_failure' && warning.text && warning.message ? ( - ({ - request: request as ShardFailureRequest, + request, response, })} color="primary" @@ -66,23 +56,5 @@ export const getSearchResponseInterceptedWarnings = ({ return true; // suppress the default behaviour }); - return removeInterceptedWarningDuplicates(interceptedWarnings); -}; - -/** - * Removes duplicated warnings - * @param interceptedWarnings - */ -export const removeInterceptedWarningDuplicates = ( - interceptedWarnings: SearchResponseInterceptedWarning[] | undefined -): SearchResponseInterceptedWarning[] | undefined => { - if (!interceptedWarnings?.length) { - return undefined; - } - - const uniqInterceptedWarnings = uniqBy(interceptedWarnings, (interceptedWarning) => - JSON.stringify(interceptedWarning.originalWarning) - ); - - return uniqInterceptedWarnings?.length ? uniqInterceptedWarnings : undefined; + return interceptedWarnings; }; diff --git a/packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.test.ts b/packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.test.ts new file mode 100644 index 0000000000000..e8d722feb131a --- /dev/null +++ b/packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { hasUnsupportedDownsampledAggregationFailure } from './has_unsupported_downsampled_aggregation_failure'; + +describe('hasUnsupportedDownsampledAggregationFailure', () => { + test('should return false when unsupported_aggregation_on_downsampled_index shard failure does not exist', () => { + expect( + hasUnsupportedDownsampledAggregationFailure({ + type: 'incomplete', + message: 'The data might be incomplete or wrong.', + clusters: { + '(local)': { + status: 'partial', + indices: '', + took: 25, + timed_out: false, + _shards: { + total: 4, + successful: 3, + skipped: 0, + failed: 1, + }, + failures: [ + { + shard: 0, + index: 'sample-01-rollup', + node: 'VFTFJxpHSdaoiGxJFLSExQ', + reason: { + type: 'illegal_argument_exception', + reason: + 'Field [kubernetes.container.memory.available.bytes] of type [aggregate_metric_double] is not supported for aggregation [percentiles]', + }, + }, + ], + }, + }, + }) + ).toBe(false); + }); + + test('should return true when unsupported_aggregation_on_downsampled_index shard failure exists', () => { + expect( + hasUnsupportedDownsampledAggregationFailure({ + type: 'incomplete', + message: 'The data might be incomplete or wrong.', + clusters: { + '(local)': { + status: 'partial', + indices: '', + took: 25, + timed_out: false, + _shards: { + total: 4, + successful: 3, + skipped: 0, + failed: 1, + }, + failures: [ + { + shard: 0, + index: 'sample-01-rollup', + node: 'VFTFJxpHSdaoiGxJFLSExQ', + reason: { + type: 'unsupported_aggregation_on_downsampled_index', + reason: 'blah blah blah timeseries data', + }, + }, + ], + }, + }, + }) + ).toBe(true); + }); +}); diff --git a/packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.ts b/packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.ts new file mode 100644 index 0000000000000..d6dcd4e176498 --- /dev/null +++ b/packages/kbn-search-response-warnings/src/utils/has_unsupported_downsampled_aggregation_failure.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { + SearchResponseWarning, + SearchResponseIncompleteWarning, +} from '@kbn/data-plugin/public'; + +export function hasUnsupportedDownsampledAggregationFailure(warning: SearchResponseWarning) { + return warning.type === 'incomplete' + ? Object.values((warning as SearchResponseIncompleteWarning).clusters).some( + (clusterDetails) => { + return clusterDetails.failures + ? clusterDetails.failures.some((shardFailure) => { + return shardFailure.reason?.type === 'unsupported_aggregation_on_downsampled_index'; + }) + : false; + } + ) + : false; +} diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/__snapshots__/comments.test.tsx.snap b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/__snapshots__/comments.test.tsx.snap index f194c0af3908a..3520fe5a52f22 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/__snapshots__/comments.test.tsx.snap +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/comments/__snapshots__/comments.test.tsx.snap @@ -71,11 +71,11 @@ Object { data-test-subj="ExceptionItemCardCommentsContainer" >
  • -
  • +
  • +
  • +
    +
    +
    -
    -

    - some old comment -

    -
    +

    + some old comment +

    -
  • - -
    + + + @@ -209,11 +209,11 @@ Object { data-test-subj="ExceptionItemCardCommentsContainer" >
    @@ -542,11 +541,11 @@ Object { data-test-subj="ExceptionItemCardCommentsContainer" >
    diff --git a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx index f51bec6f2c84b..60237b2a10780 100644 --- a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx +++ b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx @@ -41,7 +41,7 @@ const DefaultGroupPanelRenderer = ({
    - +

    {title}

    diff --git a/packages/kbn-securitysolution-list-api/src/api/index.ts b/packages/kbn-securitysolution-list-api/src/api/index.ts index 98b83b6279953..970282080cb89 100644 --- a/packages/kbn-securitysolution-list-api/src/api/index.ts +++ b/packages/kbn-securitysolution-list-api/src/api/index.ts @@ -43,6 +43,8 @@ import { } from '@kbn/securitysolution-list-constants'; import { toError, toPromise } from '../fp_utils'; +const version = '2023-10-31'; + /** * Add new ExceptionList * @@ -62,6 +64,7 @@ const addExceptionList = async ({ body: JSON.stringify(list), method: 'POST', signal, + version, }); const addExceptionListWithValidation = async ({ @@ -105,6 +108,7 @@ const addExceptionListItem = async ({ body: JSON.stringify(listItem), method: 'POST', signal, + version, }); const addExceptionListItemWithValidation = async ({ @@ -148,6 +152,7 @@ const updateExceptionList = async ({ body: JSON.stringify(list), method: 'PUT', signal, + version, }); const updateExceptionListWithValidation = async ({ @@ -191,6 +196,7 @@ const updateExceptionListItem = async ({ body: JSON.stringify(listItem), method: 'PUT', signal, + version, }); const updateExceptionListItemWithValidation = async ({ @@ -247,6 +253,7 @@ const fetchExceptionLists = async ({ method: 'GET', query, signal, + version, }); }; @@ -298,6 +305,7 @@ const fetchExceptionListById = async ({ method: 'GET', query: { id, namespace_type: namespaceType }, signal, + version, }); const fetchExceptionListByIdWithValidation = async ({ @@ -361,6 +369,7 @@ const fetchExceptionListsItemsByListIds = async ({ method: 'GET', query, signal, + version, }); }; @@ -414,6 +423,7 @@ const fetchExceptionListItemById = async ({ method: 'GET', query: { id, namespace_type: namespaceType }, signal, + version, }); const fetchExceptionListItemByIdWithValidation = async ({ @@ -450,6 +460,7 @@ const deleteExceptionListById = async ({ method: 'DELETE', query: { id, namespace_type: namespaceType }, signal, + version, }); const deleteExceptionListByIdWithValidation = async ({ @@ -486,6 +497,7 @@ const deleteExceptionListItemById = async ({ method: 'DELETE', query: { id, namespace_type: namespaceType }, signal, + version, }); const deleteExceptionListItemByIdWithValidation = async ({ @@ -518,6 +530,7 @@ const addEndpointExceptionList = async ({ http.fetch(ENDPOINT_LIST_URL, { method: 'POST', signal, + version, }); const addEndpointExceptionListWithValidation = async ({ @@ -561,6 +574,7 @@ export const exportExceptionList = async ({ include_expired_exceptions: includeExpiredExceptions, }, signal, + version, }); /** @@ -647,4 +661,5 @@ export const duplicateExceptionList = async ({ include_expired_exceptions: includeExpiredExceptions, }, signal, + version, }); diff --git a/packages/kbn-securitysolution-list-api/src/list_api/index.ts b/packages/kbn-securitysolution-list-api/src/list_api/index.ts index 5c280c7959763..59303b9e8abaf 100644 --- a/packages/kbn-securitysolution-list-api/src/list_api/index.ts +++ b/packages/kbn-securitysolution-list-api/src/list_api/index.ts @@ -57,6 +57,8 @@ export type { ImportListParams, } from './types'; +const version = '2023-10-31'; + const findLists = async ({ http, cursor, @@ -79,6 +81,7 @@ const findLists = async ({ sort_order, }, signal, + version, }); }; @@ -167,6 +170,7 @@ const importList = async ({ method: 'POST', query: { list_id, type }, signal, + version, }); }; @@ -207,6 +211,7 @@ const deleteList = async ({ method: 'DELETE', query: { deleteReferences, id, ignoreReferences }, signal, + version, }); const deleteListWithValidation = async ({ @@ -236,6 +241,7 @@ const exportList = async ({ method: 'POST', query: { list_id }, signal, + version, }); const exportListWithValidation = async ({ @@ -256,6 +262,7 @@ const readListIndex = async ({ http, signal }: ApiParams): Promise(LIST_INDEX, { method: 'GET', signal, + version, }); const readListIndexWithValidation = async ({ @@ -273,15 +280,16 @@ export { readListIndexWithValidation as readListIndex }; // TODO add types and validation export const readListPrivileges = async ({ http, signal }: ApiParams): Promise => http.fetch(LIST_PRIVILEGES_URL, { - version: '2023-10-31', method: 'GET', signal, + version, }); const createListIndex = async ({ http, signal }: ApiParams): Promise => http.fetch(LIST_INDEX, { method: 'POST', signal, + version, }); const createListIndexWithValidation = async ({ diff --git a/packages/kbn-storybook/templates/index.ejs b/packages/kbn-storybook/templates/index.ejs index 899c6a5897c56..bf40dfb9fd3ca 100644 --- a/packages/kbn-storybook/templates/index.ejs +++ b/packages/kbn-storybook/templates/index.ejs @@ -46,7 +46,7 @@ @@ -74,4 +74,4 @@ <% }); %> - + \ No newline at end of file diff --git a/packages/kbn-subscription-tracking/README.md b/packages/kbn-subscription-tracking/README.md new file mode 100644 index 0000000000000..4f84593980881 --- /dev/null +++ b/packages/kbn-subscription-tracking/README.md @@ -0,0 +1,35 @@ +# @kbn/subscription-tracking + +This package leverages the `@kbn/analytics-client` package to send dedicated subscription tracking events. + +It provides a set of React components that automatically track `impression` and `click` events. Consumers of those components need to specify a `subscription context` that gives more details on the type of feature that is advertised and the location of the upsell. + +```typescript +import { SubscriptionLink } from '@kbn/subscription-tracking'; +import type { SubscriptionContext } from '@kbn/subscription-tracking'; + +const subscriptionContext: SubscriptionContext = { + feature: 'threat-intelligence', + source: 'security__threat-intelligence', +}; + +export const Paywall = () => { + return ( +
    + + Upgrade to Platinum to get this feature + +
    + ); +}; +``` + +The example above uses a `SubscriptionLink` which is a wrapper of `EuiLink` . So it behaves just like a normal link. Alternatively, upsells can also use a `SubscriptionButton` or `SubscriptionButtonEmpty` which wrap `EuiButton` and `EuiButtonEmpty` respectively. + +When the link is mounted, it will send off an `impression` event with the given `subscriptionContext`. That piece of metadata consists of an identifier of the advertised feature (in this case `threat-intelligence`) and the `source` (aka location) of the impression (in this case the `threat-intelligence` page in the `security` solution). `source` follows the following format: `{solution-identifier}__location-identifier`. + +There are no special rules for how to name these identifiers but it's good practise to make sure that `feature` has the same value for all upsells advertising the same feature (e.g. use enums for features to prevent spelling mistakes). + +Upon interaction with the upsell link/button, a special `click` event is sent, which, again, contains the same subscription context. + +If you want to use the `subscription-tracking` elements in your app, you have to set up a `SubscriptionTrackingProvider` in your plugin setup and register the tracking events on startup. Have a look at https://github.com/elastic/kibana/pull/143910 for an example of an integration. diff --git a/packages/kbn-subscription-tracking/index.ts b/packages/kbn-subscription-tracking/index.ts new file mode 100644 index 0000000000000..de17c595918d5 --- /dev/null +++ b/packages/kbn-subscription-tracking/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src/subscription_elements'; + +export { + SubscriptionTrackingContext, + SubscriptionTrackingProvider, + registerEvents, +} from './src/services'; + +export * from './types'; diff --git a/packages/kbn-subscription-tracking/jest.config.js b/packages/kbn-subscription-tracking/jest.config.js new file mode 100644 index 0000000000000..edc1839850dae --- /dev/null +++ b/packages/kbn-subscription-tracking/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-subscription-tracking'], +}; diff --git a/packages/kbn-subscription-tracking/kibana.jsonc b/packages/kbn-subscription-tracking/kibana.jsonc new file mode 100644 index 0000000000000..e165baebfa76b --- /dev/null +++ b/packages/kbn-subscription-tracking/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/subscription-tracking", + "owner": "@elastic/security-threat-hunting-investigations" +} diff --git a/packages/kbn-subscription-tracking/mocks.tsx b/packages/kbn-subscription-tracking/mocks.tsx new file mode 100644 index 0000000000000..b918f9bba2828 --- /dev/null +++ b/packages/kbn-subscription-tracking/mocks.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { analyticsClientMock } from '@kbn/analytics-client/src/mocks'; + +import { SubscriptionTrackingProvider } from './src/services'; + +const analyticsClientMockInst = analyticsClientMock.create(); + +/** + * Mock for the external services provider. Only use in tests! + */ +export const MockSubscriptionTrackingProvider: FC = ({ children }) => { + return ( + + {children} + + ); +}; diff --git a/packages/kbn-subscription-tracking/package.json b/packages/kbn-subscription-tracking/package.json new file mode 100644 index 0000000000000..e9dd11b56c81b --- /dev/null +++ b/packages/kbn-subscription-tracking/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/subscription-tracking", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-subscription-tracking/src/helpers.test.ts b/packages/kbn-subscription-tracking/src/helpers.test.ts new file mode 100644 index 0000000000000..fa000567d35d3 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/helpers.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isValidContext } from './helpers'; + +describe('tracking', () => { + describe('isValidLocation', () => { + it('identifies correct contexts', () => { + expect( + isValidContext({ + feature: 'test', + source: 'security__test', + }) + ).toBeTruthy(); + }); + + it('identifies incorrect contexts', () => { + expect( + isValidContext({ + feature: '', + source: 'security__', + }) + ).toBeFalsy(); + + expect( + isValidContext({ + feature: 'test', + source: 'security__', + }) + ).toBeFalsy(); + + expect( + isValidContext({ + feature: '', + source: 'security__test', + }) + ).toBeFalsy(); + }); + }); +}); diff --git a/packages/kbn-subscription-tracking/src/helpers.ts b/packages/kbn-subscription-tracking/src/helpers.ts new file mode 100644 index 0000000000000..251c0d1c04116 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/helpers.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SubscriptionContextData } from '../types'; + +const sourceStringRegEx = /^(\w[\w\-_]*)__(\w[\w\-_]*)$/; +export function isValidContext(context: SubscriptionContextData): boolean { + return context.feature.length > 0 && sourceStringRegEx.test(context.source); +} diff --git a/packages/kbn-subscription-tracking/src/services.tsx b/packages/kbn-subscription-tracking/src/services.tsx new file mode 100644 index 0000000000000..857bd0b0dcd89 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/services.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC, useContext } from 'react'; +import type { AnalyticsClient, EventTypeOpts } from '@kbn/analytics-client'; +import { EVENT_NAMES, Services, SubscriptionContextData } from '../types'; + +export const SubscriptionTrackingContext = React.createContext(null); + +/** + * External services provider + */ +export const SubscriptionTrackingProvider: FC = ({ children, ...services }) => { + return ( + + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + */ +export function useServices() { + const context = useContext(SubscriptionTrackingContext); + + if (!context) { + throw new Error( + 'SubscriptionTrackingContext is missing. Ensure your component or React root is wrapped with SubscriptionTrackingProvider.' + ); + } + + return context; +} + +const subscriptionContextSchema: EventTypeOpts['schema'] = { + source: { + type: 'keyword', + _meta: { + description: + 'A human-readable identifier describing the location of the beginning of the subscription flow', + }, + }, + feature: { + type: 'keyword', + _meta: { + description: 'A human-readable identifier describing the feature that is being promoted', + }, + }, +}; + +/** + * Registers the subscription-specific event types + */ +export function registerEvents(analyticsClient: Pick) { + analyticsClient.registerEventType({ + eventType: EVENT_NAMES.IMPRESSION, + schema: subscriptionContextSchema, + }); + + analyticsClient.registerEventType({ + eventType: EVENT_NAMES.CLICK, + schema: subscriptionContextSchema, + }); +} diff --git a/packages/kbn-subscription-tracking/src/subscription_elements.test.tsx b/packages/kbn-subscription-tracking/src/subscription_elements.test.tsx new file mode 100644 index 0000000000000..1795bbf42dd0c --- /dev/null +++ b/packages/kbn-subscription-tracking/src/subscription_elements.test.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { + SubscriptionLink, + SubscriptionButton, + SubscriptionButtonEmpty, +} from './subscription_elements'; +import { SubscriptionTrackingProvider } from './services'; +import { EVENT_NAMES, Services, SubscriptionContextData } from '../types'; +import { coolDownTimeMs, resetCoolDown } from './use_impression'; + +const testServices: Services = { + navigateToApp: jest.fn(), + analyticsClient: { + reportEvent: jest.fn(), + registerEventType: jest.fn(), + } as any, +}; +const testContext: SubscriptionContextData = { feature: 'test', source: 'security__test' }; + +const WithProviders: React.FC = ({ children }) => ( + + {children} + +); + +const renderWithProviders = (children: React.ReactElement) => + render(children, { wrapper: WithProviders }); + +const reset = () => { + jest.resetAllMocks(); + resetCoolDown(); +}; + +describe('SubscriptionElements', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + [SubscriptionButton, SubscriptionLink, SubscriptionButtonEmpty].forEach((SubscriptionElement) => { + describe(SubscriptionElement.name, () => { + beforeEach(reset); + + it('renders the children correctly', () => { + renderWithProviders( + Hello + ); + expect(screen.getByText('Hello')).toBeTruthy(); + }); + + it('fires an impression event when rendered', () => { + renderWithProviders(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledWith( + EVENT_NAMES.IMPRESSION, + testContext + ); + }); + + it('fires an impression event when rendered (but only once)', () => { + const { unmount } = renderWithProviders( + + ); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(1); + unmount(); + + // does not create an impression again when remounted + const { unmount: unmountAgain } = renderWithProviders( + + ); + unmountAgain(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(1); + + // only creates anew impression when the cooldown time has passed + jest.setSystemTime(Date.now() + coolDownTimeMs); + renderWithProviders(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(2); + }); + + it('tracks a click when clicked and navigates to page', () => { + renderWithProviders( + hello + ); + + screen.getByText('hello').click(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledWith( + EVENT_NAMES.CLICK, + testContext + ); + expect(testServices.navigateToApp).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/packages/kbn-subscription-tracking/src/subscription_elements.tsx b/packages/kbn-subscription-tracking/src/subscription_elements.tsx new file mode 100644 index 0000000000000..f29c58d8a0a41 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/subscription_elements.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiLink, EuiButton, EuiButtonEmpty } from '@elastic/eui'; +import type { EuiLinkProps, EuiButtonEmptyProps, EuiButtonProps } from '@elastic/eui'; +import { useGoToSubscription } from './use_go_to_subscription'; +import { useImpression } from './use_impression'; +import type { SubscriptionContextData } from '../types'; + +interface CommonProps { + /** The context information for this subscription element */ + subscriptionContext: SubscriptionContextData; +} + +export type SubscriptionLinkProps = EuiLinkProps & CommonProps; + +/** + * Wrapper around `EuiLink` that provides subscription events + */ +export function SubscriptionLink({ + subscriptionContext, + children, + ...restProps +}: SubscriptionLinkProps) { + const goToSubscription = useGoToSubscription({ subscriptionContext }); + useImpression(subscriptionContext); + + return ( + + {children} + + ); +} + +export type SubscriptionButtonProps = EuiButtonProps & CommonProps; + +/** + * Wrapper around `EuiButton` that provides subscription events + */ +export function SubscriptionButton({ + subscriptionContext, + children, + ...restProps +}: SubscriptionButtonProps) { + const goToSubscription = useGoToSubscription({ subscriptionContext }); + useImpression(subscriptionContext); + + return ( + + {children} + + ); +} + +export type SubscriptionButtonEmptyProps = EuiButtonEmptyProps & CommonProps; + +/** + * Wrapper around `EuiButtonEmpty` that provides subscription events + */ +export function SubscriptionButtonEmpty({ + subscriptionContext, + children, + ...restProps +}: SubscriptionButtonEmptyProps) { + const goToSubscription = useGoToSubscription({ subscriptionContext }); + useImpression(subscriptionContext); + + return ( + + {children} + + ); +} diff --git a/packages/kbn-subscription-tracking/src/use_go_to_subscription.ts b/packages/kbn-subscription-tracking/src/use_go_to_subscription.ts new file mode 100644 index 0000000000000..6c93fb27ee9bd --- /dev/null +++ b/packages/kbn-subscription-tracking/src/use_go_to_subscription.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { useCallback } from 'react'; +import { isValidContext } from './helpers'; +import { useServices } from './services'; +import { EVENT_NAMES, SubscriptionContextData } from '../types'; + +interface Options { + subscriptionContext: SubscriptionContextData; +} + +/** + * Provides a navigation function that navigates to the subscription + * management page. When the function executes, a click event with the + * given context is emitted. + */ +export const useGoToSubscription = ({ subscriptionContext }: Options) => { + const { navigateToApp, analyticsClient } = useServices(); + const goToSubscription = useCallback(() => { + if (isValidContext(subscriptionContext)) { + analyticsClient.reportEvent(EVENT_NAMES.CLICK, subscriptionContext); + } else { + // eslint-disable-next-line no-console + console.error('The provided subscription context is invalid', subscriptionContext); + } + navigateToApp('management', { path: 'stack/license_management' }); + }, [analyticsClient, navigateToApp, subscriptionContext]); + + return goToSubscription; +}; diff --git a/packages/kbn-subscription-tracking/src/use_impression.ts b/packages/kbn-subscription-tracking/src/use_impression.ts new file mode 100644 index 0000000000000..eb8aa4c2e0ec5 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/use_impression.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useEffect } from 'react'; +import { isValidContext } from './helpers'; +import { useServices } from './services'; +import { EVENT_NAMES, SubscriptionContextData } from '../types'; + +/** + * Sends an impression event with the given context. + * + * Note: impression events are throttled and will not fire more + * often than once every 30 seconds. + */ +export const useImpression = (context: SubscriptionContextData) => { + const { analyticsClient } = useServices(); + + useEffect(() => { + if (!isValidContext(context)) { + // eslint-disable-next-line no-console + console.error('The provided subscription context is invalid', context); + return; + } + if (!isCoolingDown(context)) { + analyticsClient.reportEvent(EVENT_NAMES.IMPRESSION, context); + coolDown(context); + } + }, [analyticsClient, context]); +}; + +/** + * Impressions from the same context should not fire more than once every 30 seconds. + * This prevents logging too many impressions in case a page is reloaded often or + * if the user is navigating back and forth rapidly. + */ +export const coolDownTimeMs = 30 * 1000; +let impressionCooldown = new WeakMap(); + +function isCoolingDown(context: SubscriptionContextData) { + const previousLog = impressionCooldown.get(context); + + // we logged before and we are in the cooldown period + return previousLog && Date.now() - previousLog < coolDownTimeMs; +} + +function coolDown(context: SubscriptionContextData) { + impressionCooldown.set(context, Date.now()); +} + +export function resetCoolDown() { + impressionCooldown = new WeakMap(); +} diff --git a/packages/kbn-subscription-tracking/tsconfig.json b/packages/kbn-subscription-tracking/tsconfig.json new file mode 100644 index 0000000000000..677e9db998bb7 --- /dev/null +++ b/packages/kbn-subscription-tracking/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node", "react"] + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["target/**/*"], + "kbn_references": ["@kbn/analytics-client"] +} diff --git a/packages/kbn-subscription-tracking/types.ts b/packages/kbn-subscription-tracking/types.ts new file mode 100644 index 0000000000000..a2adf0c6d90c5 --- /dev/null +++ b/packages/kbn-subscription-tracking/types.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { AnalyticsClient } from '@kbn/analytics-client'; + +enum SolutionIdentifier { + observability = 'observability', + security = 'security', +} +type LocationString = string; +type SourceIdentifier = `${SolutionIdentifier}__${LocationString}`; +/** + * A piece of metadata which consists of an identifier of the advertised feature and + * the `source` (e.g. location) of the subscription element. + */ +export interface SubscriptionContextData { + /** + * A human-readable identifier describing the location of the beginning of the + * subscription flow. + * Location identifiers are prefixed with a solution identifier, e.g. `security__` + * + * @example "security__host-overview" - the user is looking at an upsell button + * on the host overview page in the security solution + */ + source: SourceIdentifier; + + /** + * A human-readable identifier describing the feature that is being promoted. + * + * @example "alerts-by-process-ancestry" + */ + feature: string; +} + +export interface Services { + navigateToApp: (app: string, options: { path: string }) => void; + analyticsClient: Pick; +} + +export enum EVENT_NAMES { + CLICK = 'subscription__upsell__click', + IMPRESSION = 'subscription__upsell__impression', +} diff --git a/packages/kbn-test/src/es/test_es_cluster.ts b/packages/kbn-test/src/es/test_es_cluster.ts index 580840b6b35a8..3cd90d21d1d91 100644 --- a/packages/kbn-test/src/es/test_es_cluster.ts +++ b/packages/kbn-test/src/es/test_es_cluster.ts @@ -18,7 +18,7 @@ import { Cluster } from '@kbn/es'; import { Client, HttpConnection } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import { REPO_ROOT } from '@kbn/repo-info'; - +import type { ArtifactLicense } from '@kbn/es'; import { CI_PARALLEL_PROCESS_PREFIX } from '../ci_parallel_process_prefix'; import { esTestConfig } from './es_test_config'; @@ -77,7 +77,7 @@ export interface CreateTestEsClusterOptions { * you'll likely need to use `basic` or `gold` to prevent the test from failing * when the license expires. */ - license?: 'basic' | 'gold' | 'trial'; // | 'oss' + license?: ArtifactLicense; log: ToolingLog; writeLogsToPath?: string; /** @@ -224,7 +224,15 @@ export function createTestEsCluster< // multiple nodes, they'll all share the same ESinstallation. const firstNode = this.nodes[0]; if (esFrom === 'source') { - installPath = (await firstNode.installSource(config)).installPath; + installPath = ( + await firstNode.installSource({ + sourcePath: config.sourcePath, + license: config.license, + password: config.password, + basePath: config.basePath, + esArgs: config.esArgs, + }) + ).installPath; } else if (esFrom === 'snapshot') { installPath = (await firstNode.installSnapshot(config)).installPath; } else if (esFrom === 'serverless') { @@ -233,11 +241,11 @@ export function createTestEsCluster< esArgs: customEsArgs, port, clean: true, - teardown: true, - ssl: true, background: true, files, + ssl, kill: true, // likely don't need this but avoids any issues where the ESS cluster wasn't cleaned up + waitForReady: true, }); } else if (Path.isAbsolute(esFrom)) { installPath = esFrom; diff --git a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts index 09e251d70a25b..f9c83161b521b 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts +++ b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts @@ -10,6 +10,7 @@ import { resolve } from 'path'; import type { ToolingLog } from '@kbn/tooling-log'; import getPort from 'get-port'; import { REPO_ROOT } from '@kbn/repo-info'; +import type { ArtifactLicense } from '@kbn/es'; import type { Config } from '../../functional_test_runner'; import { createTestEsCluster } from '../../es'; @@ -33,7 +34,7 @@ function getEsConfig({ esFrom = config.get('esTestCluster.from'), }: RunElasticsearchOptions) { const ssl = !!config.get('esTestCluster.ssl'); - const license: 'basic' | 'trial' | 'gold' = config.get('esTestCluster.license'); + const license: ArtifactLicense = config.get('esTestCluster.license'); const esArgs: string[] = config.get('esTestCluster.serverArgs'); const esJavaOpts: string | undefined = config.get('esTestCluster.esJavaOpts'); const isSecurityEnabled = esArgs.includes('xpack.security.enabled=true'); diff --git a/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts index e12b95340ff4f..17bb9f190646d 100644 --- a/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts +++ b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts @@ -6,7 +6,28 @@ * Side Public License, v 1. */ -import type { Agent } from 'elastic-apm-node'; +import type { Agent, Transaction } from 'elastic-apm-node'; + +const transaction: jest.Mocked = { + addLabels: jest.fn().mockReturnValue(true), + ensureParentId: jest.fn().mockReturnValue(''), + setLabel: jest.fn().mockReturnValue(true), + setOutcome: jest.fn(), + setType: jest.fn(), + startSpan: jest.fn().mockReturnValue(null), + end: jest.fn(), + // Following OTel convention + // https://github.com/open-telemetry/opentelemetry-js/blob/27897d6c34839ee722d92b12c1d55d8bdab5a0c1/api/src/trace/invalid-span-constants.ts + ids: { + 'trace.id': '00000000000000000000000000000000', + 'transaction.id': '0000000000000000', + }, + traceparent: '00-00000000000000000000000000000-0000000000000000-00', + name: 'Mock Transaction', + result: '', + outcome: 'unknown', + type: null, +}; /** * `elastic-apm-node` patches the runtime at import time @@ -26,7 +47,7 @@ const agent: jest.Mocked = { captureError: jest.fn(), currentTraceparent: null, currentTraceIds: {}, - startTransaction: jest.fn().mockReturnValue(null), + startTransaction: jest.fn().mockReturnValue(transaction), setTransactionName: jest.fn(), endTransaction: jest.fn(), currentTransaction: null, @@ -43,7 +64,7 @@ const agent: jest.Mocked = { addTransactionFilter: jest.fn(), addMetadataFilter: jest.fn(), flush: jest.fn(), - destroy: jest.fn(), + destroy: jest.fn().mockResolvedValue(undefined), registerMetric: jest.fn(), setTransactionOutcome: jest.fn(), setSpanOutcome: jest.fn(), diff --git a/packages/kbn-test/src/jest/setup/polyfills.jsdom.js b/packages/kbn-test/src/jest/setup/polyfills.jsdom.js index 1d963afdfc4da..77aa4a6e389d1 100644 --- a/packages/kbn-test/src/jest/setup/polyfills.jsdom.js +++ b/packages/kbn-test/src/jest/setup/polyfills.jsdom.js @@ -17,9 +17,9 @@ if (!global.URL.hasOwnProperty('createObjectURL')) { // https://github.com/jsdom/jsdom/issues/2524 if (!global.hasOwnProperty('TextEncoder')) { - const { TextEncoder, TextDecoder } = require('util'); - global.TextEncoder = TextEncoder; - global.TextDecoder = TextDecoder; + const customTextEncoding = require('@kayahr/text-encoding'); + global.TextEncoder = customTextEncoding.TextEncoder; + global.TextDecoder = customTextEncoding.TextDecoder; } // NOTE: We should evaluate removing this once we upgrade to Node 18 and find out if loaders.gl already fixed this usage diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index 75e093b047158..c64c9d7a543aa 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -105,6 +105,7 @@ const STANDARD_LIST_TYPES = [ 'osquery-saved-query', 'osquery-pack', 'infrastructure-ui-source', + 'metrics-data-source', 'metrics-explorer-view', 'inventory-view', 'infrastructure-monitoring-log-view', diff --git a/packages/kbn-text-based-editor/index.ts b/packages/kbn-text-based-editor/index.ts index fd7b19839b87f..01f106af6639f 100644 --- a/packages/kbn-text-based-editor/index.ts +++ b/packages/kbn-text-based-editor/index.ts @@ -7,6 +7,7 @@ */ export type { TextBasedLanguagesEditorProps } from './src/text_based_languages_editor'; +export { fetchFieldsFromESQL } from './src/fetch_fields_from_esql'; import { TextBasedLanguagesEditor } from './src/text_based_languages_editor'; // React.lazy support diff --git a/packages/kbn-text-based-editor/src/__stories__/text_based_editor.stories.mdx b/packages/kbn-text-based-editor/src/__stories__/text_based_editor.stories.mdx index 0874305b1975b..f6e0a751e7755 100644 --- a/packages/kbn-text-based-editor/src/__stories__/text_based_editor.stories.mdx +++ b/packages/kbn-text-based-editor/src/__stories__/text_based_editor.stories.mdx @@ -31,7 +31,7 @@ The TextBasedLanguagesEditor component is a reusable component and can be used t name='compact mode' args={ { - query: { sql: 'SELECT field1, field2 FROM DATAVIEW' }, + query: { esql: 'from dataview | keep field1, field2' }, isCodeEditorExpanded:false, 'data-test-subj':'test-id' } @@ -51,7 +51,7 @@ When there are errors to the query the UI displays the errors to the editor: name='with errors' args={ { - query: { sql: 'SELECT field1, field2 FROM DATAVIEW' }, + query: { esql: 'from dataview | keep field1, field2' }, isCodeEditorExpanded:false, 'data-test-subj':'test-id', errors: [ @@ -76,7 +76,7 @@ When there the query is long and the editor is on the compact view: name='with long query' args={ { - query: { sql: 'SELECT field1, field2, field 3, field 4, field 5 FROM DATAVIEW WHERE field5 IS NOT NULL AND field4 IS NULL' }, + query: { esql: 'from dataview | keep field1, field2, field 3, field 4, field 5 | where field5 > 5 | stats var = avg(field3)' }, isCodeEditorExpanded:false, 'data-test-subj':'test-id', } @@ -97,7 +97,7 @@ The editor also works on the expanded mode: name='on expanded mode' args={ { - query: { sql: 'SELECT field1, field2 FROM DATAVIEW' }, + query: { esql: 'from dataview | keep field1, field2' }, isCodeEditorExpanded:true, 'data-test-subj':'test-id', } @@ -110,6 +110,27 @@ The editor also works on the expanded mode: +The editor also works on the expanded mode with the minimize button hidden: + + + + {Template.bind({})} + + + ## Component props The component exposes the following properties: diff --git a/packages/kbn-text-based-editor/src/editor_footer.tsx b/packages/kbn-text-based-editor/src/editor_footer.tsx index 3119d548098de..f89a14d06f106 100644 --- a/packages/kbn-text-based-editor/src/editor_footer.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer.tsx @@ -28,22 +28,145 @@ import type { MonacoError } from './helpers'; const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; const COMMAND_KEY = isMac ? '⌘' : '^'; +const getConstsByType = (type: 'error' | 'warning', count: number) => { + if (type === 'error') { + return { + color: 'danger', + message: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.errorCount', { + defaultMessage: '{count} {count, plural, one {error} other {errors}}', + values: { count }, + }), + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.errorsTitle', { + defaultMessage: 'Errors', + }), + }; + } else { + return { + color: 'warning', + message: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.warningCount', { + defaultMessage: '{count} {count, plural, one {warning} other {warnings}}', + values: { count }, + }), + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.warningsTitle', { + defaultMessage: 'Warnings', + }), + }; + } +}; + +export function ErrorsWarningsPopover({ + isPopoverOpen, + items, + type, + refreshErrors, + setIsPopoverOpen, + onErrorClick, +}: { + isPopoverOpen: boolean; + items: MonacoError[]; + type: 'error' | 'warning'; + refreshErrors: () => void; + setIsPopoverOpen: (flag: boolean) => void; + onErrorClick: (error: MonacoError) => void; +}) { + const strings = getConstsByType(type, items.length); + return ( + + + + + + + { + refreshErrors(); + setIsPopoverOpen(!isPopoverOpen); + }} + > +

    {strings.message}

    + + } + ownFocus={false} + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + > +
    + {strings.label} + + {items.map((item, index) => { + return ( + onErrorClick(item)} + > + + + + + + + + {i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.lineNumber', + { + defaultMessage: 'Line {lineNumber}', + values: { lineNumber: item.startLineNumber }, + } + )} + + + + + {item.message} + + + + ); + })} + +
    +
    +
    +
    +
    + ); +} + interface EditorFooterProps { lines: number; containerCSS: Interpolation; errors?: MonacoError[]; + warning?: MonacoError[]; detectTimestamp: boolean; onErrorClick: (error: MonacoError) => void; refreshErrors: () => void; + hideRunQueryText?: boolean; } export const EditorFooter = memo(function EditorFooter({ lines, containerCSS, errors, + warning, detectTimestamp, onErrorClick, refreshErrors, + hideRunQueryText, }: EditorFooterProps) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); return ( @@ -57,97 +180,24 @@ export const EditorFooter = memo(function EditorFooter({ {errors && errors.length > 0 && ( - - - - - - - { - refreshErrors(); - setIsPopoverOpen(!isPopoverOpen); - }} - > -

    - {i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.errorCount', - { - defaultMessage: '{count} {count, plural, one {error} other {errors}}', - values: { count: errors.length }, - } - )} -

    - - } - ownFocus={false} - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} - > -
    - - {i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.errorsTitle', - { - defaultMessage: 'Errors', - } - )} - - - {errors.map((error, index) => { - return ( - onErrorClick(error)} - > - - - - - - - - {i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.lineNumber', - { - defaultMessage: 'Line {lineNumber}', - values: { lineNumber: error.startLineNumber }, - } - )} - - - - - {error.message} - - - - ); - })} - -
    -
    -
    -
    -
    + + )} + {warning && warning.length > 0 && ( + )} @@ -187,27 +237,29 @@ export const EditorFooter = memo(function EditorFooter({
    - - - - -

    - {i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.runQuery', { - defaultMessage: 'Run query', - })} -

    -
    -
    - - {`${COMMAND_KEY} + Enter`} - -
    -
    + {!hideRunQueryText && ( + + + + +

    + {i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.runQuery', { + defaultMessage: 'Run query', + })} +

    +
    +
    + + {`${COMMAND_KEY} + Enter`} + +
    +
    + )}
    ); }); diff --git a/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx new file mode 100644 index 0000000000000..6dde72c1c22d5 --- /dev/null +++ b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx @@ -0,0 +1,3240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Markdown } from '@kbn/kibana-react-plugin/public'; + +export const initialSection = ( + +); + +export const sourceCommands = { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.sourceCommands', { + defaultMessage: 'Source commands', + }), + description: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.commandsDescription', + { + defaultMessage: `A source command produces a table, typically with data from Elasticsearch. ES|QL supports the following source commands.`, + } + ), + items: [ + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.from', + { + defaultMessage: 'FROM', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.row', + { + defaultMessage: 'ROW', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.show', + { + defaultMessage: 'SHOW', + } + ), + description: ( + \` source command returns information about the deployment and its capabilities: + +* Use \`SHOW INFO\` to return the deployment's version, build date and hash. +* Use \`SHOW FUNCTIONS\` to return a list of all supported functions and a synopsis of each function. + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} + /> + ), + }, + ], +}; + +export const processingCommands = { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.processingCommands', { + defaultMessage: 'Processing commands', + }), + description: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.processingCommandsDescription', + { + defaultMessage: `Processing commands change an input table by adding, removing, or changing rows and columns. ES|QL supports the following processing commands.`, + } + ), + items: [ + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.dissect', + { + defaultMessage: 'DISSECT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.drop', + { + defaultMessage: 'DROP', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.enrich', + { + defaultMessage: 'ENRICH', + } + ), + description: ( + \`; if it’s not specified, the match will be performed on a field with the same name as the match field defined in the enrich policy. + +\`\`\` +ROW a = "1" +| ENRICH languages_policy ON a +\`\`\` + +You can specify which attributes (between those defined as enrich fields in the policy) have to be added to the result, using \`WITH , ...\` syntax. + +\`\`\` +ROW a = "1" +| ENRICH languages_policy ON a WITH language_name +\`\`\` + +Attributes can also be renamed using \`WITH new_name=\` + +\`\`\` +ROW a = "1" +| ENRICH languages_policy ON a WITH name = language_name +\`\`\` + +By default (if no \`WITH\` is defined), \`ENRICH\` will add all the enrich fields defined in the enrich policy to the result. + +In case of name collisions, the newly created fields will override the existing fields. + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} + /> + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.eval', + { + defaultMessage: 'EVAL', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.grok', + { + defaultMessage: 'GROK', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.keep', + { + defaultMessage: 'KEEP', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.limit', + { + defaultMessage: 'LIMIT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvExpand', + { + defaultMessage: 'MV_EXPAND', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.rename', + { + defaultMessage: 'RENAME', + } + ), + description: ( + AS +\`\`\` + +For example: + +\`\`\` +FROM employees +| KEEP first_name, last_name, still_hired +| RENAME still_hired AS employed +\`\`\` + +If a column with the new name already exists, it will be replaced by the new column. + +Multiple columns can be renamed with a single \`RENAME\` command: + +\`\`\` +FROM employees +| KEEP first_name, last_name +| RENAME first_name AS fn, last_name AS ln +\`\`\` + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} + /> + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.sort', + { + defaultMessage: 'SORT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.statsby', + { + defaultMessage: 'STATS ... BY', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.where', + { + defaultMessage: 'WHERE', + } + ), + description: ( + + ), + }, + ], +}; + +export const functions = { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.functions', { + defaultMessage: 'Functions', + }), + description: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.functionsDocumentationESQLDescription', + { + defaultMessage: `Functions are supported by ROW, EVAL and WHERE.`, + } + ), + items: [ + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.absFunction', + { + defaultMessage: 'ABS', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.acosFunction', + { + defaultMessage: 'ACOS', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.asinFunction', + { + defaultMessage: 'ASIN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.atanFunction', + { + defaultMessage: 'ATAN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.atan2Function', + { + defaultMessage: 'ATAN2', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.autoBucketFunction', + { + defaultMessage: 'AUTO_BUCKET', + } + ), + description: ( + = "1985-01-01T00:00:00Z" AND hire_date < "1986-01-01T00:00:00Z" +| EVAL bucket = AUTO_BUCKET(hire_date, 20, "1985-01-01T00:00:00Z", "1986-01-01T00:00:00Z") +| STATS AVG(salary) BY bucket +| SORT bucket +\`\`\` + +Returning: +\`\`\` +46305.0 | 1985-02-01T00:00:00.000Z +44817.0 | 1985-05-01T00:00:00.000Z +62405.0 | 1985-07-01T00:00:00.000Z +49095.0 | 1985-09-01T00:00:00.000Z +51532.0 | 1985-10-01T00:00:00.000Z +54539.75 | 1985-11-01T00:00:00.000 +\`\`\` + +NOTE: \`AUTO_BUCKET\` does not create buckets that don’t match any documents. That’s why the example above is missing 1985-03-01 and other dates. + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} + /> + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.caseFunction', + { + defaultMessage: 'CASE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.ceilFunction', + { + defaultMessage: 'CEIL', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.cidrMatchFunction', + { + defaultMessage: 'CIDR_MATCH', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.coalesceFunction', + { + defaultMessage: 'COALESCE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.concatFunction', + { + defaultMessage: 'CONCAT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.cosFunction', + { + defaultMessage: 'COS', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.coshFunction', + { + defaultMessage: 'COSH', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.dateExtractFunction', + { + defaultMessage: 'DATE_EXTRACT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.dateFormatFunction', + { + defaultMessage: 'DATE_FORMAT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.dateParseFunction', + { + defaultMessage: 'DATE_PARSE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.dateTruncFunction', + { + defaultMessage: 'DATE_TRUNC', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.eFunction', + { + defaultMessage: 'E', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.floorFunction', + { + defaultMessage: 'FLOOR', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.greatestFunction', + { + defaultMessage: 'GREATEST', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.isFiniteFunction', + { + defaultMessage: 'IS_FINITE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.isInfiniteFunction', + { + defaultMessage: 'IS_INFINITE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.isNanFunction', + { + defaultMessage: 'IS_NAN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.leastFunction', + { + defaultMessage: 'LEAST', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.leftFunction', + { + defaultMessage: 'LEFT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.lengthFunction', + { + defaultMessage: 'LENGTH', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.log10Function', + { + defaultMessage: 'LOG10', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.ltrimunction', + { + defaultMessage: 'LTRIM', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvAvgFunction', + { + defaultMessage: 'MV_AVG', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvConcatFunction', + { + defaultMessage: 'MV_CONCAT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvCountFunction', + { + defaultMessage: 'MV_COUNT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvDedupeFunction', + { + defaultMessage: 'MV_DEDUPE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvMaxFunction', + { + defaultMessage: 'MV_MAX', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvMedianFunction', + { + defaultMessage: 'MV_MEDIAN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvMinFunction', + { + defaultMessage: 'MV_MIN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mvSumFunction', + { + defaultMessage: 'MV_SUM', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.nowFunction', + { + defaultMessage: 'NOW', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.piFunction', + { + defaultMessage: 'PI', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.powFunction', + { + defaultMessage: 'POW', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.rightFunction', + { + defaultMessage: 'RIGHT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.roundFunction', + { + defaultMessage: 'ROUND', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.rtrimFunction', + { + defaultMessage: 'RTRIM', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.sinFunction', + { + defaultMessage: 'SIN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.sinhFunction', + { + defaultMessage: 'SINH', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.splitFunction', + { + defaultMessage: 'SPLIT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.sqrtFunction', + { + defaultMessage: 'SQRT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.startsWithFunction', + { + defaultMessage: 'STARTS_WITH', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.substringFunction', + { + defaultMessage: 'SUBSTRING', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.tanFunction', + { + defaultMessage: 'TAN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.tanhFunction', + { + defaultMessage: 'TANH', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.tauFunction', + { + defaultMessage: 'TAU', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toBooleanFunction', + { + defaultMessage: 'TO_BOOLEAN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toDatetimeFunction', + { + defaultMessage: 'TO_DATETIME', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toDegreesFunction', + { + defaultMessage: 'TO_DEGREES', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toDoubleFunction', + { + defaultMessage: 'TO_DOUBLE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toIntegerFunction', + { + defaultMessage: 'TO_INTEGER', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toIpFunction', + { + defaultMessage: 'TO_IP', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toLongFunction', + { + defaultMessage: 'TO_LONG', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toRadiansFunction', + { + defaultMessage: 'TO_RADIANS', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toStringFunction', + { + defaultMessage: 'TO_STRING', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toUnsignedLongFunction', + { + defaultMessage: 'TO_UNSIGNED_LONG', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.toVersionFunction', + { + defaultMessage: 'TO_VERSION', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.trimFunction', + { + defaultMessage: 'TRIM', + } + ), + description: ( + + ), + }, + ], +}; + +export const aggregationFunctions = { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.aggregationFunctions', { + defaultMessage: 'Aggregation functions', + }), + description: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.aggregationFunctionsDocumentationESQLDescription', + { + defaultMessage: `These functions can by used with STATS...BY:`, + } + ), + items: [ + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.avgFunction', + { + defaultMessage: 'AVG', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.countFunction', + { + defaultMessage: 'COUNT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.countDistinctFunction', + { + defaultMessage: 'COUNT_DISTINCT', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.maxFunction', + { + defaultMessage: 'MAX', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.medianFunction', + { + defaultMessage: 'MEDIAN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.medianAbsoluteDeviationFunction', + { + defaultMessage: 'MEDIAN_ABSOLUTE_DEVIATION', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.minFunction', + { + defaultMessage: 'MIN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.percentileFunction', + { + defaultMessage: 'PERCENTILE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.sumFunction', + { + defaultMessage: 'SUM', + } + ), + description: ( + + ), + }, + ], +}; + +export const operators = { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.operators', { + defaultMessage: 'Operators', + }), + description: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.operatorsDocumentationESQLDescription', + { + defaultMessage: `ES|QL supports the following operators:`, + } + ), + items: [ + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.binaryOperators', + { + defaultMessage: 'Binary operators', + } + ), + description: ( + \` +* larger than or equal: \`>=\` + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} + /> + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.booleanOperators', + { + defaultMessage: 'Boolean operators', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.inOperator', + { + defaultMessage: 'IN', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.stringOperators', + { + defaultMessage: 'LIKE and RLIKE', + } + ), + description: ( + + ), + }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.predicates', + { + defaultMessage: 'NULL values', + } + ), + description: ( + + ), + }, + ], +}; diff --git a/packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts b/packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts new file mode 100644 index 0000000000000..7b9ebd66d76c6 --- /dev/null +++ b/packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { pluck } from 'rxjs/operators'; +import { lastValueFrom } from 'rxjs'; +import { Query, AggregateQuery, TimeRange } from '@kbn/es-query'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { Datatable } from '@kbn/expressions-plugin/public'; +import { textBasedQueryStateToAstWithValidation } from '@kbn/data-plugin/common'; + +interface TextBasedLanguagesErrorResponse { + error: { + message: string; + }; + type: 'error'; +} + +export function fetchFieldsFromESQL( + query: Query | AggregateQuery, + expressions: ExpressionsStart, + time?: TimeRange +) { + return textBasedQueryStateToAstWithValidation({ + query, + time, + }) + .then((ast) => { + if (ast) { + const execution = expressions.run(ast, null); + let finalData: Datatable; + let error: string | undefined; + execution.pipe(pluck('result')).subscribe((resp) => { + const response = resp as Datatable | TextBasedLanguagesErrorResponse; + if (response.type === 'error') { + error = response.error.message; + } else { + finalData = response; + } + }); + return lastValueFrom(execution).then(() => { + if (error) { + throw new Error(error); + } else { + return finalData; + } + }); + } + return undefined; + }) + .catch((err) => { + throw new Error(err.message); + }); +} diff --git a/packages/kbn-text-based-editor/src/helpers.test.ts b/packages/kbn-text-based-editor/src/helpers.test.ts index a56d0ecee141c..5f1546ccc138e 100644 --- a/packages/kbn-text-based-editor/src/helpers.test.ts +++ b/packages/kbn-text-based-editor/src/helpers.test.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { parseErrors, getInlineEditorText } from './helpers'; +import { parseErrors, parseWarning, getInlineEditorText, getWrappedInPipesCode } from './helpers'; describe('helpers', function () { describe('parseErrors', function () { - it('should return the correct error object from SQL ES response for an one liner query', function () { + it('should return the correct error object from ESQL ES response for an one liner query', function () { const error = new Error( '[essql] > Unexpected error from Elasticsearch: verification_exception - Found 1 problem\nline 1:8: Unknown column [miaou]' ); @@ -27,7 +27,7 @@ describe('helpers', function () { ]); }); - it('should return the correct error object from SQL ES response for an multi liner query', function () { + it('should return the correct error object from ESQL ES response for an multi liner query', function () { const error = new Error( '[essql] > Unexpected error from Elasticsearch: verification_exception - Found 1 problem line 3:7: Condition expression needs to be boolean, found [TEXT]' ); @@ -54,7 +54,7 @@ describe('helpers', function () { it('should return the generic error object for an error of unknown format', function () { const error = new Error('I am an unknown error'); const errors = [error]; - expect(parseErrors(errors, `SELECT * FROM "kibana_sample_data_ecommerce"`)).toEqual([ + expect(parseErrors(errors, `FROM "kibana_sample_data_ecommerce"`)).toEqual([ { endColumn: 10, endLineNumber: 1, @@ -67,29 +67,96 @@ describe('helpers', function () { }); }); + describe('parseWarning', function () { + it('should return the correct warning object from ESQL ES response for an one liner query', function () { + const warning = + '299 Elasticsearch-8.10.0-SNAPSHOT-adb9fce96079b421c2575f0d2d445f492eb5f075 "Line 1:52: evaluation of [date_parse(geo.dest)] failed, treating result as null. Only first 20 failures recorded."'; + expect(parseWarning(warning)).toEqual([ + { + endColumn: 138, + endLineNumber: 1, + message: + 'evaluation of [date_parse(geo.dest)] failed, treating result as null. Only first 20 failures recorded.', + severity: 8, + startColumn: 52, + startLineNumber: 1, + }, + ]); + }); + + it('should return the correct array of warnings if multiple warnins are detected', function () { + const warning = + '299 Elasticsearch-8.10.0-SNAPSHOT-adb9fce96079b421c2575f0d2d445f492eb5f075 "Line 1:52: evaluation of [date_parse(geo.dest)] failed, treating result as null. Only first 20 failures recorded.", 299 Elasticsearch-8.10.0-SNAPSHOT-adb9fce96079b421c2575f0d2d445f492eb5f075 "Line 1:84: evaluation of [date_parse(geo.src)] failed, treating result as null. Only first 20 failures recorded."'; + expect(parseWarning(warning)).toEqual([ + { + endColumn: 138, + endLineNumber: 1, + message: + 'evaluation of [date_parse(geo.dest)] failed, treating result as null. Only first 20 failures recorded.', + severity: 8, + startColumn: 52, + startLineNumber: 1, + }, + { + endColumn: 169, + endLineNumber: 1, + message: + 'evaluation of [date_parse(geo.src)] failed, treating result as null. Only first 20 failures recorded.', + severity: 8, + startColumn: 84, + startLineNumber: 1, + }, + ]); + }); + }); + describe('getInlineEditorText', function () { it('should return the entire query if it is one liner', function () { - const text = getInlineEditorText( - 'SELECT field1, count(*) FROM index1 ORDER BY field1', - false - ); + const text = getInlineEditorText('FROM index1 | keep field1, field2 | order field1', false); expect(text).toEqual(text); }); it('should return the query on one line with extra space if is multiliner', function () { const text = getInlineEditorText( - 'SELECT field1, count(*)\nFROM index1 ORDER BY field1', + 'FROM index1 | keep field1, field2\n| keep field1, field2 | order field1', true ); - expect(text).toEqual('SELECT field1, count(*) FROM index1 ORDER BY field1'); + expect(text).toEqual( + 'FROM index1 | keep field1, field2 | keep field1, field2 | order field1' + ); }); it('should return the query on one line with extra spaces removed if is multiliner', function () { const text = getInlineEditorText( - 'SELECT field1, count(*)\nFROM index1 \n ORDER BY field1', + 'FROM index1 | keep field1, field2\n| keep field1, field2 \n | order field1', + true + ); + expect(text).toEqual( + 'FROM index1 | keep field1, field2 | keep field1, field2 | order field1' + ); + }); + }); + + describe('getWrappedInPipesCode', function () { + it('should return the code wrapped', function () { + const code = getWrappedInPipesCode('FROM index1 | keep field1, field2 | order field1', false); + expect(code).toEqual('FROM index1\n| keep field1, field2\n| order field1'); + }); + + it('should return the code unwrapped', function () { + const code = getWrappedInPipesCode( + 'FROM index1 \n| keep field1, field2 \n| order field1', + true + ); + expect(code).toEqual('FROM index1 | keep field1, field2 | order field1'); + }); + + it('should return the code unwrapped and trimmed', function () { + const code = getWrappedInPipesCode( + 'FROM index1 \n| keep field1, field2 \n| order field1', true ); - expect(text).toEqual('SELECT field1, count(*) FROM index1 ORDER BY field1'); + expect(code).toEqual('FROM index1 | keep field1, field2 | order field1'); }); }); }); diff --git a/packages/kbn-text-based-editor/src/helpers.ts b/packages/kbn-text-based-editor/src/helpers.ts index d1deae5bf0d80..6b4e99f6e3093 100644 --- a/packages/kbn-text-based-editor/src/helpers.ts +++ b/packages/kbn-text-based-editor/src/helpers.ts @@ -42,6 +42,43 @@ export const useDebounceWithOptions = ( ); }; +export const parseWarning = (warning: string): MonacoError[] => { + if (warning.includes('Line')) { + const splitByLine = warning.split('Line'); + splitByLine.shift(); + return splitByLine.map((item) => { + const [lineNumber, startPosition, warningMessage] = item.split(':'); + const [trimmedMessage] = warningMessage.split('"'); + // initialize the length to 10 in case no error word found + let errorLength = 10; + const [_, wordWithError] = trimmedMessage.split('['); + if (wordWithError) { + errorLength = wordWithError.length - 1; + } + return { + message: trimmedMessage.trimStart(), + startColumn: Number(startPosition), + startLineNumber: Number(lineNumber), + endColumn: Number(startPosition) + errorLength, + endLineNumber: Number(lineNumber), + severity: monaco.MarkerSeverity.Error, + }; + }); + } else { + // unknown warning message + return [ + { + message: warning, + startColumn: 1, + startLineNumber: 1, + endColumn: 10, + endLineNumber: 1, + severity: monaco.MarkerSeverity.Error, + }, + ]; + } +}; + export const parseErrors = (errors: Error[], code: string): MonacoError[] => { return errors.map((error) => { if (error.message.includes('line')) { @@ -101,8 +138,37 @@ export const getDocumentationSections = async (language: string) => { initialSection, }; } + if (language === 'esql') { + const { + sourceCommands, + processingCommands, + initialSection, + functions, + aggregationFunctions, + operators, + } = await import('./esql_documentation_sections'); + groups.push({ + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.esql', { + defaultMessage: 'ES|QL', + }), + items: [], + }); + groups.push(sourceCommands, processingCommands, functions, aggregationFunctions, operators); + return { + groups, + initialSection, + }; + } }; export const getInlineEditorText = (queryString: string, isMultiLine: boolean) => { return isMultiLine ? queryString.replace(/\r?\n|\r/g, ' ').replace(/ +/g, ' ') : queryString; }; + +export const getWrappedInPipesCode = (code: string, isWrapped: boolean): string => { + const pipes = code?.split('|'); + const codeNoLines = pipes?.map((pipe) => { + return pipe.replaceAll('\n', '').trim(); + }); + return codeNoLines.join(isWrapped ? ' | ' : '\n| '); +}; diff --git a/packages/kbn-text-based-editor/src/resizable_button.scss b/packages/kbn-text-based-editor/src/resizable_button.scss deleted file mode 100644 index cd0342df39a77..0000000000000 --- a/packages/kbn-text-based-editor/src/resizable_button.scss +++ /dev/null @@ -1,87 +0,0 @@ -.TextBasedLangEditor--resizableButtonContainer { - position: absolute; - bottom: 0; - left: 0; - right: 0; - display: flex; - flex-direction: column; -} - -.TextBasedLangEditor--resizableButton { - position: relative; - flex-shrink: 0; - z-index: 1; - cursor: row-resize; - height: $euiSize; - margin-top: -($euiSize / 2); - margin-bottom: -($euiSize / 2); - - &:before, - &:after { - content: ''; - display: block; - position: absolute; - top: 50%; - left: 50%; - width: $euiSizeM; - height: 1px; - background-color: $euiColorDarkestShade; - transition: ( - width $euiAnimSpeedFast ease, - height $euiAnimSpeedFast ease, - transform $euiAnimSpeedFast ease, - background-color $euiAnimSpeedFast ease - ); - } - - &:before { - transform: translate(-50%, -2px); - } - - &:after { - transform: translate(-50%, 2px); - } - - //Lighten the "grab" icon on :hover - &:hover:not(:disabled) { - &:before, - &:after { - background-color: $euiColorMediumShade; - transition-delay: $euiAnimSpeedFast; //Delay transition on hover so animation is not accidentally triggered on mouse over - } - } - - //Add a transparent background to the container and emphasis the "grab" icon with primary color on :focus - &:focus:not(:disabled) { - background-color: transparentize($euiColorPrimary, .9); - - &:before, - &:after { - background-color: $euiColorPrimary; - // Overrides default transition so that "grab" icon background-color doesn't animate - transition: ( - width $euiAnimSpeedFast ease, - height $euiAnimSpeedFast ease, - transform $euiAnimSpeedFast ease - ); - transition-delay: $euiAnimSpeedFast / 2; - } - } - - //Morph the "grab" icon into a fluid 2px straight line on :hover and :focus - &:hover:not(:disabled), - &:focus:not(:disabled) { - &:before, - &:after { - width: 100%; - } - - &:before { - transform: translate(-50%, -1px); - } - - &:after { - transform: translate(-50%, 0); - } - } -} \ No newline at end of file diff --git a/packages/kbn-text-based-editor/src/resizable_button.tsx b/packages/kbn-text-based-editor/src/resizable_button.tsx index 7ae69b4e33d73..fb4ee944bc2f5 100644 --- a/packages/kbn-text-based-editor/src/resizable_button.tsx +++ b/packages/kbn-text-based-editor/src/resizable_button.tsx @@ -7,29 +7,30 @@ */ import React from 'react'; - -import './resizable_button.scss'; +import { EuiResizableButton } from '@elastic/eui'; +import { css } from '@emotion/react'; export function ResizableButton({ onMouseDownResizeHandler, onKeyDownResizeHandler, }: { onMouseDownResizeHandler: ( - mouseDownEvent: React.MouseEvent + mouseDownEvent: React.MouseEvent | React.TouchEvent ) => void; onKeyDownResizeHandler: (keyDownEvernt: React.KeyboardEvent) => void; }) { - const setFocus = (e: React.MouseEvent) => e.currentTarget.focus(); - return ( -
    -
    + ); } diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts b/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts index 86a68b5ba342b..f7d57a6d60213 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts @@ -18,7 +18,9 @@ export const textBasedLanguagedEditorStyles = ( editorHeight: number, isCodeEditorExpanded: boolean, hasErrors: boolean, - isCodeEditorExpandedFocused: boolean + hasWarning: boolean, + isCodeEditorExpandedFocused: boolean, + hasReference: boolean ) => { let position = isCompactFocused ? ('absolute' as 'absolute') : ('relative' as 'relative'); // cast string to type 'relative' | 'absolute' if (isCodeEditorExpanded) { @@ -40,7 +42,7 @@ export const textBasedLanguagedEditorStyles = ( }, resizableContainer: { display: 'flex', - width: isCodeEditorExpanded ? '100%' : 'calc(100% - 80px)', + width: isCodeEditorExpanded ? '100%' : `calc(100% - ${hasReference ? 80 : 40}px)`, alignItems: isCompactFocused ? 'flex-start' : 'center', border: !isCompactFocused ? euiTheme.border.thin : 'none', borderTopLeftRadius: '6px', @@ -51,7 +53,7 @@ export const textBasedLanguagedEditorStyles = ( linesBadge: { position: 'absolute' as 'absolute', // cast string to type 'absolute', zIndex: 1, - right: hasErrors ? '60px' : '12px', + right: hasErrors || hasWarning ? '60px' : '12px', top: '50%', transform: 'translate(0, -50%)', }, diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx index 90c973a609874..0be4c38eed749 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx @@ -16,6 +16,22 @@ import { TextBasedLanguagesEditor, TextBasedLanguagesEditorProps, } from './text_based_languages_editor'; +import { ReactWrapper } from 'enzyme'; + +jest.mock('./helpers', () => { + const module = jest.requireActual('./helpers'); + return { + ...module, + getDocumentationSections: () => ({ + groups: [ + { + label: 'How it works', + items: [], + }, + ], + }), + }; +}); import { of } from 'rxjs'; describe('TextBasedLanguagesEditor', () => { @@ -45,7 +61,7 @@ describe('TextBasedLanguagesEditor', () => { let props: TextBasedLanguagesEditorProps; beforeEach(() => { props = { - query: { sql: 'SELECT * FROM test' }, + query: { esql: 'from test' }, isCodeEditorExpanded: false, onTextLangQueryChange: jest.fn(), onTextLangQuerySubmit: jest.fn(), @@ -108,16 +124,32 @@ describe('TextBasedLanguagesEditor', () => { }); }); - it('should render the correct buttons for the inline code editor mode', async () => { + it('should render the warnings badge for the inline mode by default if warning are provides', async () => { + const newProps = { + ...props, + warning: 'Line 1: 20: Warning', + }; await act(async () => { - const component = mount(renderTextBasedLanguagesEditorComponent({ ...props })); - expect(component.find('[data-test-subj="TextBasedLangEditor-expand"]').length).not.toBe(0); + const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); expect( - component.find('[data-test-subj="TextBasedLangEditor-inline-documentation"]').length + component.find('[data-test-subj="TextBasedLangEditor-inline-warning-badge"]').length ).not.toBe(0); }); }); + it('should render the correct buttons for the inline code editor mode', async () => { + let component: ReactWrapper; + + await act(async () => { + component = mount(renderTextBasedLanguagesEditorComponent({ ...props })); + }); + component!.update(); + expect(component!.find('[data-test-subj="TextBasedLangEditor-expand"]').length).not.toBe(0); + expect( + component!.find('[data-test-subj="TextBasedLangEditor-inline-documentation"]').length + ).not.toBe(0); + }); + it('should call the expand editor function when expand button is clicked', async () => { const expandCodeEditorSpy = jest.fn(); const newProps = { @@ -136,12 +168,36 @@ describe('TextBasedLanguagesEditor', () => { ...props, isCodeEditorExpanded: true, }; + let component: ReactWrapper; + await act(async () => { + component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); + }); + component!.update(); + expect( + component!.find('[data-test-subj="TextBasedLangEditor-toggleWordWrap"]').length + ).not.toBe(0); + expect(component!.find('[data-test-subj="TextBasedLangEditor-minimize"]').length).not.toBe(0); + expect(component!.find('[data-test-subj="TextBasedLangEditor-documentation"]').length).not.toBe( + 0 + ); + }); + + it('should not render the minimize button for the expanded code editor mode if the prop is set to true', async () => { + const newProps = { + ...props, + isCodeEditorExpanded: true, + hideMinimizeButton: true, + }; + let component: ReactWrapper; + await act(async () => { + component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); + }); + component!.update(); await act(async () => { - const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); expect( component.find('[data-test-subj="TextBasedLangEditor-toggleWordWrap"]').length ).not.toBe(0); - expect(component.find('[data-test-subj="TextBasedLangEditor-minimize"]').length).not.toBe(0); + expect(component.find('[data-test-subj="TextBasedLangEditor-minimize"]').length).toBe(0); expect( component.find('[data-test-subj="TextBasedLangEditor-documentation"]').length ).not.toBe(0); @@ -186,4 +242,27 @@ describe('TextBasedLanguagesEditor', () => { ).toBe('1 line'); }); }); + + it('should render the run query text', async () => { + const newProps = { + ...props, + isCodeEditorExpanded: true, + }; + await act(async () => { + const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); + expect(component.find('[data-test-subj="TextBasedLangEditor-run-query"]').length).not.toBe(0); + }); + }); + + it('should not render the run query text if the hideRunQueryText prop is set to true', async () => { + const newProps = { + ...props, + isCodeEditorExpanded: true, + hideRunQueryText: true, + }; + await act(async () => { + const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); + expect(component.find('[data-test-subj="TextBasedLangEditor-run-query"]').length).toBe(0); + }); + }); }); diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index 5f432c090eb8c..84abd18de3323 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -6,16 +6,27 @@ * Side Public License, v 1. */ -import React, { useRef, useEffect, useState, useCallback, memo } from 'react'; +import React, { useRef, memo, useEffect, useState, useCallback } from 'react'; import classNames from 'classnames'; -import { SQLLang, monaco } from '@kbn/monaco'; +import { + SQLLang, + monaco, + ESQL_LANG_ID, + ESQL_THEME_ID, + ESQLLang, + ESQLCustomAutocompleteCallbacks, +} from '@kbn/monaco'; import type { AggregateQuery } from '@kbn/es-query'; -import { getAggregateQueryMode } from '@kbn/es-query'; +import { getAggregateQueryMode, getLanguageDisplayName } from '@kbn/es-query'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { IndexManagementPluginSetup } from '@kbn/index-management-plugin/public'; +import type { SerializedEnrichPolicy } from '@kbn/index-management-plugin/common'; import { type LanguageDocumentationSections, LanguageDocumentationPopover, } from '@kbn/language-documentation-popover'; - +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { i18n } from '@kbn/i18n'; import { EuiBadge, @@ -40,12 +51,15 @@ import { import { useDebounceWithOptions, parseErrors, + parseWarning, getInlineEditorText, getDocumentationSections, MonacoError, + getWrappedInPipesCode, } from './helpers'; import { EditorFooter } from './editor_footer'; import { ResizableButton } from './resizable_button'; +import { fetchFieldsFromESQL } from './fetch_fields_from_esql'; import './overwrite.scss'; @@ -57,9 +71,18 @@ export interface TextBasedLanguagesEditorProps { isCodeEditorExpanded: boolean; detectTimestamp?: boolean; errors?: Error[]; + warning?: string; isDisabled?: boolean; isDarkMode?: boolean; dataTestSubj?: string; + hideMinimizeButton?: boolean; + hideRunQueryText?: boolean; +} + +interface TextBasedEditorDeps { + dataViews: DataViewsPublicPluginStart; + expressions: ExpressionsStart; + indexManagementApiService?: IndexManagementPluginSetup['apiService']; } const MAX_COMPACT_VIEW_LENGTH = 250; @@ -72,6 +95,9 @@ const KEYCODE_ARROW_DOWN = 40; const languageId = (language: string) => { switch (language) { + case 'esql': { + return ESQL_LANG_ID; + } case 'sql': default: { return SQLLang.ID; @@ -82,6 +108,9 @@ const languageId = (language: string) => { let clickedOutside = false; let initialRender = true; let updateLinesFromModel = false; +let currentCursorContent = ''; +let lines = 1; + export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ query, onTextLangQueryChange, @@ -90,14 +119,18 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ isCodeEditorExpanded, detectTimestamp = false, errors, + warning, isDisabled, isDarkMode, + hideMinimizeButton, + hideRunQueryText, dataTestSubj, }: TextBasedLanguagesEditorProps) { const { euiTheme } = useEuiTheme(); const language = getAggregateQueryMode(query); const queryString: string = query[language] ?? ''; - const [lines, setLines] = useState(1); + const kibana = useKibana(); + const { dataViews, expressions, indexManagementApiService } = kibana.services; const [code, setCode] = useState(queryString ?? ''); const [codeOneLiner, setCodeOneLiner] = useState(''); const [editorHeight, setEditorHeight] = useState( @@ -106,18 +139,23 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const [showLineNumbers, setShowLineNumbers] = useState(isCodeEditorExpanded); const [isCompactFocused, setIsCompactFocused] = useState(isCodeEditorExpanded); const [isCodeEditorExpandedFocused, setIsCodeEditorExpandedFocused] = useState(false); - const [isWordWrapped, setIsWordWrapped] = useState(true); + const [isWordWrapped, setIsWordWrapped] = useState(false); const [editorErrors, setEditorErrors] = useState([]); + const [editorWarning, setEditorWarning] = useState([]); + const [documentationSections, setDocumentationSections] = useState(); + const policiesRef = useRef([]); const styles = textBasedLanguagedEditorStyles( euiTheme, isCompactFocused, editorHeight, isCodeEditorExpanded, Boolean(errors?.length), - isCodeEditorExpandedFocused + Boolean(warning), + isCodeEditorExpandedFocused, + Boolean(documentationSections) ); const isDark = isDarkMode; const editorModel = useRef(); @@ -167,22 +205,6 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ [editorHeight] ); - const updateHeight = () => { - if (editor1.current) { - const linesCount = editorModel.current?.getLineCount() || 1; - if (linesCount === 1 || clickedOutside || initialRender) return; - const editorElement = editor1.current.getDomNode(); - const contentHeight = Math.min(MAX_COMPACT_VIEW_LENGTH, editor1.current.getContentHeight()); - - if (editorElement) { - editorElement.style.height = `${contentHeight}px`; - } - const contentWidth = Number(editorElement?.style.width.replace('px', '')); - editor1.current.layout({ width: contentWidth, height: contentHeight }); - setEditorHeight(contentHeight); - } - }; - const restoreInitialMode = () => { setIsCodeEditorExpandedFocused(false); if (isCodeEditorExpanded) return; @@ -202,30 +224,37 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ } }; + const updateHeight = useCallback((editor: monaco.editor.IStandaloneCodeEditor) => { + if (lines === 1 || clickedOutside || initialRender) return; + const editorElement = editor.getDomNode(); + const contentHeight = Math.min(MAX_COMPACT_VIEW_LENGTH, editor.getContentHeight()); + + if (editorElement) { + editorElement.style.height = `${contentHeight}px`; + } + const contentWidth = Number(editorElement?.style.width.replace('px', '')); + editor.layout({ width: contentWidth, height: contentHeight }); + setEditorHeight(contentHeight); + }, []); + + const onEditorFocus = useCallback(() => { + setIsCompactFocused(true); + setIsCodeEditorExpandedFocused(true); + setShowLineNumbers(true); + setCodeOneLiner(''); + clickedOutside = false; + initialRender = false; + updateLinesFromModel = true; + }, []); + useDebounceWithOptions( () => { if (!editorModel.current) return; - editor1.current?.onDidChangeModelContent((e) => { - if (updateLinesFromModel) { - setLines(editorModel.current?.getLineCount() || 1); - } - }); - editor1.current?.onDidFocusEditorText(() => { - setIsCompactFocused(true); - setIsCodeEditorExpandedFocused(true); - setShowLineNumbers(true); - setCodeOneLiner(''); - clickedOutside = false; - initialRender = false; - updateLinesFromModel = true; - }); - // on CMD/CTRL + Enter submit the query - // eslint-disable-next-line no-bitwise - editor1.current?.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () { - onTextLangQuerySubmit(); - }); - if (!isCodeEditorExpanded) { - editor1.current?.onDidContentSizeChange(updateHeight); + if (warning && (!errors || !errors.length)) { + const parsedWarning = parseWarning(warning); + setEditorWarning(parsedWarning); + } else { + setEditorWarning([]); } if (errors && errors.length) { const parsedErrors = parseErrors(errors, code); @@ -238,7 +267,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ }, { skipFirstRender: false }, 256, - [errors] + [errors, warning] ); const onErrorClick = useCallback(({ startLineNumber, startColumn }: MonacoError) => { @@ -270,12 +299,12 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ if (containerWidth && (!isCompactFocused || force)) { const hasLines = /\r|\n/.exec(queryString); if (hasLines && !updateLinesFromModel) { - setLines(queryString.split(/\r|\n/).length); + lines = queryString.split(/\r|\n/).length; } const text = getInlineEditorText(queryString, Boolean(hasLines)); const queryLength = text.length; const unusedSpace = - errors && errors.length + (errors && errors.length) || warning ? EDITOR_ONE_LINER_UNUSED_SPACE_WITH_ERRORS : EDITOR_ONE_LINER_UNUSED_SPACE; const charactersAlowed = Math.floor((width - unusedSpace) / FONT_WIDTH); @@ -288,7 +317,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ } } }, - [queryString, errors, isCompactFocused] + [isCompactFocused, queryString, errors, warning] ); useEffect(() => { @@ -304,6 +333,16 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ } }, [calculateVisibleCode, code, isCompactFocused, queryString]); + useEffect(() => { + if (isCodeEditorExpanded && !isWordWrapped) { + const pipes = code?.split('|'); + const pipesWithNewLine = code?.split('\n|'); + if (pipes?.length === pipesWithNewLine?.length) { + setIsWordWrapped(true); + } + } + }, [code, isCodeEditorExpanded, isWordWrapped]); + const onResize = ({ width }: { width: number }) => { calculateVisibleCode(width); if (editor1.current) { @@ -314,6 +353,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const onQueryUpdate = useCallback( (value: string) => { setCode(value); + setIsWordWrapped(false); onTextLangQueryChange({ [language]: value } as AggregateQuery); }, [language, onTextLangQueryChange] @@ -329,6 +369,78 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ } }, [language, documentationSections]); + const getSourceIdentifiers: ESQLCustomAutocompleteCallbacks['getSourceIdentifiers'] = + useCallback(async () => { + const indices = await dataViews.getIndices({ + showAllIndices: false, + pattern: '*', + isRollupIndex: () => false, + }); + return indices.map((i) => i.name); + }, [dataViews]); + + const getFieldsIdentifiers: ESQLCustomAutocompleteCallbacks['getFieldsIdentifiers'] = useCallback( + async (ctx) => { + const pipes = currentCursorContent?.split('|'); + pipes?.pop(); + const validContent = pipes?.join('|'); + if (validContent) { + // ES|QL with limit 0 returns only the columns and is more performant + const esqlQuery = { + esql: `${validContent} | limit 0`, + }; + try { + const table = await fetchFieldsFromESQL(esqlQuery, expressions); + return table?.columns.map((c) => c.name) || []; + } catch (e) { + // no action yet + } + } + return []; + }, + [expressions] + ); + + const getPoliciesIdentifiers: ESQLCustomAutocompleteCallbacks['getPoliciesIdentifiers'] = + useCallback( + async (ctx) => { + const { data: policies, error } = + (await indexManagementApiService?.getAllEnrichPolicies()) || {}; + policiesRef.current = policies || []; + if (error || !policies) { + return []; + } + return policies.map(({ name, sourceIndices }) => ({ name, indices: sourceIndices })); + }, + [indexManagementApiService] + ); + + const getPolicyFieldsIdentifiers: ESQLCustomAutocompleteCallbacks['getPolicyFieldsIdentifiers'] = + useCallback( + async (ctx) => + policiesRef.current + .filter(({ name }) => ctx.userDefinedVariables.policyIdentifiers.includes(name)) + .flatMap(({ enrichFields }) => enrichFields), + [] + ); + + const getPolicyMatchingFieldIdentifiers: ESQLCustomAutocompleteCallbacks['getPolicyMatchingFieldIdentifiers'] = + useCallback( + async (ctx) => { + // try to load the list if none is present yet but + // at least one policy is declared in the userDefinedVariables + // (this happens if the user pastes an ESQL statement with the policy name in it) + if (!policiesRef.current.length && ctx.userDefinedVariables.policyIdentifiers.length) { + await getPoliciesIdentifiers(ctx); + } + const matchingField = policiesRef.current.find(({ name }) => + ctx.userDefinedVariables.policyIdentifiers.includes(name) + )?.matchField; + return matchingField ? [matchingField] : []; + }, + [getPoliciesIdentifiers] + ); + const codeEditorOptions: CodeEditorProps['options'] = { automaticLayout: false, accessibilitySupport: 'off', @@ -343,7 +455,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ minimap: { enabled: false }, wordWrap: isWordWrapped ? 'on' : 'off', lineNumbers: showLineNumbers ? 'on' : 'off', - theme: isDark ? 'vs-dark' : 'vs', + theme: language === 'esql' ? ESQL_THEME_ID : isDark ? 'vs-dark' : 'vs', lineDecorationsWidth: 12, autoIndent: 'none', wrappingIndent: 'none', @@ -355,7 +467,8 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ vertical: 'auto', }, overviewRulerBorder: false, - readOnly: isDisabled, + readOnly: + isDisabled || Boolean(!isCompactFocused && codeOneLiner && codeOneLiner.includes('...')), }; if (isCompactFocused) { @@ -381,13 +494,13 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ ? i18n.translate( 'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel', { - defaultMessage: 'Disable word wrap', + defaultMessage: 'Disable wrap with pipes', } ) : i18n.translate( 'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel', { - defaultMessage: 'Enable word wrap', + defaultMessage: 'Wrap with pipes', } ) } @@ -401,13 +514,13 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ ? i18n.translate( 'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel', { - defaultMessage: 'Disable word wrap', + defaultMessage: 'Disable wrap with pipes', } ) : i18n.translate( 'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel', { - defaultMessage: 'Enable word wrap', + defaultMessage: 'Wrap with pipes', } ) } @@ -417,54 +530,65 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ wordWrap: isWordWrapped ? 'off' : 'on', }); setIsWordWrapped(!isWordWrapped); + const updatedCode = getWrappedInPipesCode(code, isWordWrapped); + if (code !== updatedCode) { + setCode(updatedCode); + onTextLangQueryChange({ [language]: updatedCode } as AggregateQuery); + } }} /> - - - + { - expandCodeEditor(false); - updateLinesFromModel = false; - }} - /> - - + > + { + expandCodeEditor(false); + updateLinesFromModel = false; + }} + /> + + + )} - + {documentationSections && ( + + + + )} @@ -515,11 +639,33 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ {errors.length} )} + {!isCompactFocused && warning && (!errors || errors.length === 0) && ( + + {editorWarning.length} + + )} { editor1.current = editor; @@ -528,7 +674,45 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ editorModel.current = model; } if (isCodeEditorExpanded) { - setLines(model?.getLineCount() || 1); + lines = model?.getLineCount() || 1; + } + + editor.onDidChangeModelContent((e) => { + if (updateLinesFromModel) { + lines = model?.getLineCount() || 1; + } + const currentPosition = editor.getPosition(); + const content = editorModel.current?.getValueInRange({ + startLineNumber: 0, + startColumn: 0, + endLineNumber: currentPosition?.lineNumber ?? 1, + endColumn: currentPosition?.column ?? 1, + }); + if (content) { + currentCursorContent = content || editor.getValue(); + } + }); + + editor.onDidFocusEditorText(() => { + onEditorFocus(); + }); + + editor.onKeyDown(() => { + onEditorFocus(); + }); + + // on CMD/CTRL + Enter submit the query + editor.addCommand( + // eslint-disable-next-line no-bitwise + monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, + function () { + onTextLangQuerySubmit(); + } + ); + if (!isCodeEditorExpanded) { + editor.onDidContentSizeChange((e) => { + updateHeight(editor); + }); } }} /> @@ -537,6 +721,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ lines={lines} containerCSS={styles.bottomContainer} errors={editorErrors} + warning={editorWarning} onErrorClick={onErrorClick} refreshErrors={onTextLangQuerySubmit} detectTimestamp={detectTimestamp} @@ -569,7 +754,14 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ onClick={() => expandCodeEditor(true)} data-test-subj="TextBasedLangEditor-expand" css={{ - borderRadius: 0, + ...(documentationSections + ? { + borderRadius: 0, + } + : { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + }), backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', border: '1px solid rgb(17 43 134 / 10%) !important', }} @@ -577,28 +769,34 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ - + + sections={documentationSections} + buttonProps={{ + display: 'empty', + 'data-test-subj': 'TextBasedLangEditor-inline-documentation', + 'aria-label': i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel', + { + defaultMessage: 'Documentation', + } + ), + size: 'm', + css: { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', + border: '1px solid rgb(17 43 134 / 10%) !important', + borderLeft: 'transparent !important', + }, + }} + /> + + )} @@ -609,9 +807,11 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ lines={lines} containerCSS={styles.bottomContainer} errors={editorErrors} + warning={editorWarning} onErrorClick={onErrorClick} refreshErrors={onTextLangQuerySubmit} detectTimestamp={detectTimestamp} + hideRunQueryText={hideRunQueryText} /> )} {isCodeEditorExpanded && ( diff --git a/packages/kbn-text-based-editor/tsconfig.json b/packages/kbn-text-based-editor/tsconfig.json index cefbe0335575b..63222d0d6026b 100644 --- a/packages/kbn-text-based-editor/tsconfig.json +++ b/packages/kbn-text-based-editor/tsconfig.json @@ -19,7 +19,11 @@ "@kbn/core", "@kbn/kibana-react-plugin", "@kbn/language-documentation-popover", - "@kbn/test-jest-helpers" + "@kbn/test-jest-helpers", + "@kbn/data-plugin", + "@kbn/expressions-plugin", + "@kbn/data-views-plugin", + "@kbn/index-management-plugin" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-ui-shared-deps-npm/BUILD.bazel b/packages/kbn-ui-shared-deps-npm/BUILD.bazel index 61569ac39c41d..f2ff64942a993 100644 --- a/packages/kbn-ui-shared-deps-npm/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-npm/BUILD.bazel @@ -62,6 +62,11 @@ RUNTIME_DEPS = [ "@npm//tslib", "@npm//uuid", "@npm//io-ts", + "@npm//@reduxjs/toolkit", + "@npm//redux", + "@npm//react-redux", + "@npm//immer", + "@npm//reselect" ] webpack_cli( diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 21eb15d016f7b..627a1747c0b2d 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -84,6 +84,10 @@ module.exports = (_, argv) => { '@emotion/cache', '@emotion/react', '@hello-pangea/dnd/dist/dnd.js', + '@reduxjs/toolkit', + 'redux', + 'react-redux', + 'immer', '@tanstack/react-query', '@tanstack/react-query-devtools', 'classnames', @@ -103,6 +107,7 @@ module.exports = (_, argv) => { 'react-router-dom-v5-compat', 'react-router', 'react', + 'reselect', 'rxjs', 'rxjs/operators', 'styled-components', @@ -138,19 +143,6 @@ module.exports = (_, argv) => { }, ], }, - // @hello-pangea/dnd emits optional chaining that confuses webpack. - // We need to transform it using babel before going further - { - test: /@hello-pangea\/dnd\/dist\/dnd\.js$/, - use: [ - { - loader: 'babel-loader', - options: { - plugins: [require.resolve('@babel/plugin-proposal-optional-chaining')], - }, - }, - ], - }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], diff --git a/packages/kbn-ui-shared-deps-src/src/definitions.js b/packages/kbn-ui-shared-deps-src/src/definitions.js index 08e5355a3f444..ae9dcd3b056f1 100644 --- a/packages/kbn-ui-shared-deps-src/src/definitions.js +++ b/packages/kbn-ui-shared-deps-src/src/definitions.js @@ -56,6 +56,11 @@ const externals = { // this is how plugins/consumers from npm load monaco 'monaco-editor/esm/vs/editor/editor.api': '__kbnSharedDeps__.MonacoBarePluginApi', 'io-ts': '__kbnSharedDeps__.IoTs', + '@reduxjs/toolkit': '__kbnSharedDeps__.ReduxjsToolkit', + 'react-redux': '__kbnSharedDeps__.ReactRedux', + redux: '__kbnSharedDeps__.Redux', + immer: '__kbnSharedDeps__.Immer', + reselect: '__kbnSharedDeps__.Reselect', /** * big deps which are locked to a single version diff --git a/packages/kbn-ui-shared-deps-src/src/entry.js b/packages/kbn-ui-shared-deps-src/src/entry.js index ac203abadb39a..2491a34193e2e 100644 --- a/packages/kbn-ui-shared-deps-src/src/entry.js +++ b/packages/kbn-ui-shared-deps-src/src/entry.js @@ -47,6 +47,11 @@ export const ElasticEuiLibServicesFormat = require('@elastic/eui/optimize/es/ser export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme'); export const KbnDatemath = require('@kbn/datemath'); export const HelloPangeaDnd = require('@hello-pangea/dnd/dist/dnd'); +export const ReduxjsToolkit = require('@reduxjs/toolkit'); +export const ReactRedux = require('react-redux'); +export const Redux = require('redux'); +export const Immer = require('immer'); +export const Reselect = require('reselect'); export const Lodash = require('lodash'); export const LodashFp = require('lodash/fp'); diff --git a/packages/kbn-unified-data-table/README.md b/packages/kbn-unified-data-table/README.md new file mode 100644 index 0000000000000..576a676289d7a --- /dev/null +++ b/packages/kbn-unified-data-table/README.md @@ -0,0 +1,241 @@ +# @kbn/unified-data-table + +This package contains components and services for the unified data table UI (as used in Discover). + +## UnifiedDataTable Component +Props description: +| Property | Type | Description | +| :--- | :--- | :--- | +| **ariaLabelledBy** | string | Determines which element labels the grid for ARIA. | +| **className** | (optional) string | Optional class name to apply. | +| **columns** | string[] | Determines ids of the columns which are displayed. | +| **expandedDoc** | (optional) DataTableRecord | If set, the given document is displayed in a flyout. | +| **dataView** | DataView | The used data view. | +| **loadingState** | DataLoadingState | Determines if data is currently loaded. | +| **onFilter** | DocViewFilterFn | Function to add a filter in the grid cell or document flyout. | +| **onResize** | (optional)(colSettings: { columnId: string; width: number }) => void; | Function triggered when a column is resized by the user. | +| **onSetColumns** | (columns: string[], hideTimeColumn: boolean) => void; | Function to set all columns. | +| **onSort** | (optional)(sort: string[][]) => void; | Function to change sorting of the documents, skipped when isSortEnabled is set to false. | +| **rows** | (optional)DataTableRecord[] | Array of documents provided by Elasticsearch. | +| **sampleSize** | number | The max size of the documents returned by Elasticsearch. | +| **setExpandedDoc** | (optional)(doc?: DataTableRecord) => void; | Function to set the expanded document, which is displayed in a flyout. | +| **settings** | (optional)UnifiedDataTableSettings | Grid display settings persisted in Elasticsearch (e.g. column width). | +| **searchDescription** | (optional)string | Search description. | +| **searchTitle** | (optional)string | Search title. | +| **showTimeCol** | boolean | Determines whether the time columns should be displayed (legacy settings). | +| **showFullScreenButton** | (optional)boolean | Determines whether the full screen button should be displayed. | +| **isSortEnabled** | (optional)boolean | Manage user sorting control. | +| **sort** | SortOrder[] | Current sort setting. | +| **useNewFieldsApi** | boolean | How the data is fetched. | +| **isPaginationEnabled** | (optional)boolean | Manage pagination control. | +| **controlColumnIds** | (optional)string[] | List of used control columns (available: 'openDetails', 'select'). | +| **rowHeightState** | (optional)number | Row height from state. | +| **onUpdateRowHeight** | (optional)(rowHeight: number) => void; | Update row height state. | +| **isPlainRecord** | (optional)boolean | Is text base lang mode enabled. | +| **rowsPerPageState** | (optional)number | Current state value for rowsPerPage. | +| **onUpdateRowsPerPage** | (optional)(rowsPerPage: number) => void; | Update rows per page state. | +| **onFieldEdited** | (optional)() => void; | Callback to execute on edit runtime field. | +| **cellActionsTriggerId** | (optional)string | Optional triggerId to retrieve the column cell actions that will override the default ones. | +| **services** | See Required **services** list below | Service dependencies. | +| **renderDocumentView** | (optional)(hit: DataTableRecord,displayedRows: DataTableRecord[],displayedColumns: string[]) => JSX.Element | undefined; | Callback to render DocumentView when the document is expanded. | +| **configRowHeight** | (optional)number | Optional value for providing configuration setting for UnifiedDataTable rows height. | +| **showMultiFields** | (optional)boolean | Optional value for providing configuration setting for enabling to display the complex fields in the table. Default is true. | +| **maxDocFieldsDisplayed** | (optional)number | Optional value for providing configuration setting for maximum number of document fields to display in the table. Default is 50. | +| **externalControlColumns** | (optional)EuiDataGridControlColumn[] | Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. | +| **totalHits** | (optional)number | Number total hits from ES. | +| **onFetchMoreRecords** | (optional)() => void | To fetch more. | +| **externalAdditionalControls** | (optional)React.ReactNode | Optional value for providing the additional controls available in the UnifiedDataTable toolbar to manage it's records or state. UnifiedDataTable includes Columns, Sorting and Bulk Actions. | +| **rowsPerPageOptions** | (optional)number[] | Optional list of number type values to set custom UnifiedDataTable paging options to display the records per page. | +| **renderCustomGridBody** | (optional)(args: EuiDataGridCustomBodyProps) => React.ReactNode; | An optional function called to completely customize and control the rendering of EuiDataGrid's body and cell placement. | +| **trailingControlColumns** | (optional)EuiDataGridControlColumn[] | An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. | +| **visibleCellActions** | (optional)number | An optional value for a custom number of the visible cell actions in the table. By default is up to 3. | +| **externalCustomRenderers** | (optional)Record React.ReactNode>; | An optional settings for a specified fields rendering like links. Applied only for the listed fields rendering. | +| **consumer** | (optional)string | Name of the UnifiedDataTable consumer component or application. | +| **componentsTourSteps** | (optional)Record | Optional key/value pairs to set guided onboarding steps ids for a data table components included to guided tour. | + +*Required **services** list: +``` + theme: ThemeServiceStart; + fieldFormats: FieldFormatsStart; + uiSettings: IUiSettingsClient; + dataViewFieldEditor: DataViewFieldEditorStart; + toastNotifications: ToastsStart; + storage: Storage; + data: DataPublicPluginStart; +``` + +Usage example: + +``` + // Memoize unified data table to avoid the unnecessary re-renderings + const DataTableMemoized = React.memo(UnifiedDataTable); + + // Add memoized component with all needed props + { + // Add logic to refetch the data when the filter by field was added/removed. Refetch data. + }} + onResize={(colSettings: { columnId: string; width: number }) => { + // Update the table state with the new width for the column + }} + onSetColumns={(columns: string[], hideTimeColumn: boolean) => { + // Update table state with the new columns. Refetch data. + }} + onSort={!isTextBasedQuery ? onSort : undefined + // Update table state with the new sorting settings. Refetch data. + } + rows={searchResultRows} + sampleSize={500} + setExpandedDoc={() => { + // Callback function to do the logic when the document is expanded + }} + settings={tableSettings} + showTimeCol={true} + isSortEnabled={true} + sort={sortingColumns} + rowHeightState={3} + onUpdateRowHeight={(rowHeight: number) => { + // Do the state update with the new setting of the row height + }} + isPlainRecord={isTextBasedQuery} + rowsPerPageState={50} + onUpdateRowsPerPage={(rowHeight: number) => { + // Do the state update with the new number of the rows per page + } + onFieldEdited={() => + // Callback to execute on edit runtime field. Refetch data. + } + cellActionsTriggerId={SecurityCellActionsTrigger.DEFAULT} + services={{ + theme, + fieldFormats, + storage, + toastNotifications: toastsService, + uiSettings, + dataViewFieldEditor, + data: dataPluginContract, + }} + visibleCellActions={3} + externalCustomRenderers={{ + // Set the record style definition for the specific fields rendering Record React.ReactNode> + }} + renderDocumentView={() => + // Implement similar callback to render the Document flyout + const renderDetailsPanel = useCallback( + () => ( + + ), + [browserFields, handleOnPanelClosed, runtimeMappings, timelineId] + ); + } + externalControlColumns={leadingControlColumns} + externalAdditionalControls={additionalControls} + trailingControlColumns={trailingControlColumns} + renderCustomGridBody={renderCustomGridBody} + rowsPerPageOptions={[10, 30, 40, 100]} + showFullScreenButton={false} + useNewFieldsApi={true} + maxDocFieldsDisplayed={50} + consumer="timeline" + totalHits={ + // total number of the documents in the search query result. For example: 1200 + } + onFetchMoreRecords={() => { + // Do some data fetch to get more data + }} + configRowHeight={3} + showMultiFields={true} + componentsTourSteps={'expandButton': DISCOVER_TOUR_STEP_ANCHOR_IDS.expandDocument} + /> +``` + +## JsonCodeEditorCommon Component +Props description: +| Property | Type | Description | +| :--- | :--- | :--- | +| **width** | (optional) string or number | Editor component width. | +| **height** | (optional) string or number | Editor component height. | +| **hasLineNumbers** | (optional) boolean | Define if the editor component has line numbers style. | +| **hideCopyButton** | (optional) boolean | Show/hide setting for Copy button. | +| **onEditorDidMount** | (editor: monaco.editor.IStandaloneCodeEditor) => void | Do some logic to update the state with the edotor component value. | + +Usage example: + +``` + setEditor(editorNode)} +/> + +``` + +## Utils + +* `getRowsPerPageOptions(currentRowsPerPage)` - gets list of the table defaults for perPage options. + +* `getDisplayedColumns(currentRowsPerPage)` - gets list of the table columns with the logic to define the empty list with _source column. + +* `popularizeField(...)` - helper function to define the dataView persistance and save indexPattern update capabilities. + +## Hooks + +* `useColumns(...)` - this hook define the state update for the columns event handlers and allows to use them for external components outside the UnifiedDataTable. + +An example of using hooks for defining event handlers for columns management with setting the consumer specific setAppState: + +``` +const { + columns: currentColumns, + onAddColumn, + onRemoveColumn, + onMoveColumn, + onSetColumns, + } = useColumns({ + capabilities, + defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING), + dataView, + dataViews, + setAppState: stateContainer.appState.update, + useNewFieldsApi, + columns, + sort, + }); + +// Use onAddColumn, onRemoveColumn handlers in the DocumentView + +const renderDocumentView = useCallback( + (hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + query={query} + /> + ), + [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] + ); +``` \ No newline at end of file diff --git a/packages/kbn-unified-data-table/__mocks__/config.ts b/packages/kbn-unified-data-table/__mocks__/config.ts new file mode 100644 index 0000000000000..b8ae4a531038c --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/config.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Config } from '@kbn/config'; + +export type ConfigMock = jest.Mocked; + +const createConfigMock = (): ConfigMock => ({ + has: jest.fn(), + get: jest.fn(), + set: jest.fn(), + getFlattenedPaths: jest.fn(), + toRaw: jest.fn(), +}); + +export const configMock = { + create: createConfigMock, +}; diff --git a/packages/kbn-unified-data-table/__mocks__/data_view_complex.ts b/packages/kbn-unified-data-table/__mocks__/data_view_complex.ts new file mode 100644 index 0000000000000..8122103c9f4d7 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/data_view_complex.ts @@ -0,0 +1,419 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataView } from '@kbn/data-views-plugin/public'; +import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; + +const fields = [ + { + count: 0, + name: '_id', + type: 'string', + esTypes: ['_id'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: '_index', + type: 'string', + esTypes: ['_index'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: '_score', + type: 'number', + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: '_source', + type: '_source', + esTypes: ['_source'], + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: '_type', + type: 'string', + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 2, + name: 'array_objects.description', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'array_objects.description.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'array_objects.description', + }, + }, + }, + { + count: 0, + name: 'array_objects.name', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'array_objects.name.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'array_objects.name', + }, + }, + }, + { + count: 0, + name: 'array_tags', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'array_tags.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'array_tags', + }, + }, + }, + { + count: 0, + name: 'binary_blob', + type: 'unknown', + esTypes: ['binary'], + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'bool_enabled', + type: 'boolean', + esTypes: ['boolean'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'date', + type: 'date', + esTypes: ['date'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 1, + name: 'date_nanos', + type: 'date', + esTypes: ['date_nanos'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'flattened_labels', + type: 'unknown', + esTypes: ['flattened'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'geo_point', + type: 'geo_point', + esTypes: ['geo_point'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 1, + name: 'geometry', + type: 'unknown', + esTypes: ['shape'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 1, + name: 'histogram', + type: 'histogram', + esTypes: ['histogram'], + scripted: false, + searchable: false, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'ip_addr', + type: 'ip', + esTypes: ['ip'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 4, + name: 'keyword_key', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'nested_user.first', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + subType: { + nested: { + path: 'nested_user', + }, + }, + }, + { + count: 0, + name: 'nested_user.first.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'nested_user.first', + }, + nested: { + path: 'nested_user', + }, + }, + }, + { + count: 0, + name: 'nested_user.last', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + subType: { + nested: { + path: 'nested_user', + }, + }, + }, + { + count: 0, + name: 'nested_user.last.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'nested_user.last', + }, + nested: { + path: 'nested_user', + }, + }, + }, + { + count: 3, + name: 'number_amount', + type: 'number', + esTypes: ['long'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 3, + name: 'number_price', + type: 'number', + esTypes: ['float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'object_user.first', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'object_user.last', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'range_time_frame', + type: 'date_range', + esTypes: ['date_range'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 1, + name: 'rank_features', + type: 'unknown', + esTypes: ['rank_features'], + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 5, + name: 'text_message', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'vector', + type: 'unknown', + esTypes: ['dense_vector'], + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'version', + type: 'string', + esTypes: ['version'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 1, + script: 'return "hi there"', + lang: 'painless', + name: 'scripted_string', + type: 'string', + scripted: true, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: 'runtime_number', + type: 'number', + esTypes: ['double'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, +] as DataView['fields']; + +export const dataViewComplexMock = buildDataViewMock({ + name: 'data-view-with-various-field-types', + fields, + timeFieldName: 'data', +}); diff --git a/packages/kbn-unified-data-table/__mocks__/data_view_with_timefield.ts b/packages/kbn-unified-data-table/__mocks__/data_view_with_timefield.ts new file mode 100644 index 0000000000000..374b6b23f837b --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/data_view_with_timefield.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataView } from '@kbn/data-views-plugin/public'; +import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; + +const fields = [ + { + name: '_index', + type: 'string', + scripted: false, + filterable: true, + }, + { + name: 'timestamp', + displayName: 'timestamp', + type: 'date', + scripted: false, + filterable: true, + aggregatable: true, + sortable: true, + }, + { + name: 'message', + displayName: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + { + name: 'extension', + displayName: 'extension', + type: 'string', + scripted: false, + filterable: true, + aggregatable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + scripted: false, + filterable: true, + aggregatable: true, + }, + { + name: 'scripted', + displayName: 'scripted', + type: 'number', + scripted: true, + filterable: false, + }, +] as DataView['fields']; + +export const dataViewWithTimefieldMock = buildDataViewMock({ + name: 'index-pattern-with-timefield', + fields, + timeFieldName: 'timestamp', +}); diff --git a/packages/kbn-unified-data-table/__mocks__/data_views.ts b/packages/kbn-unified-data-table/__mocks__/data_views.ts new file mode 100644 index 0000000000000..bdbf63ecfbfbe --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/data_views.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { dataViewComplexMock } from './data_view_complex'; +import { dataViewWithTimefieldMock } from './data_view_with_timefield'; + +export const dataViewMockList = [dataViewMock, dataViewComplexMock, dataViewWithTimefieldMock]; + +export function createDataViewsMock() { + return { + getCache: async () => { + return [dataViewMock]; + }, + get: async (id: string) => { + if (id === 'invalid-data-view-id') { + return Promise.reject('Invalid'); + } + const dataView = dataViewMockList.find((dv) => dv.id === id); + if (dataView) { + return Promise.resolve(dataView); + } else { + return Promise.reject(`DataView ${id} not found`); + } + }, + getDefaultDataView: jest.fn(() => dataViewMock), + updateSavedObject: jest.fn(), + getIdsWithTitle: jest.fn(() => { + return Promise.resolve(dataViewMockList); + }), + createFilter: jest.fn(), + create: jest.fn(), + clearInstanceCache: jest.fn(), + getFieldsForIndexPattern: jest.fn((dataView) => dataView.fields), + refreshFields: jest.fn(), + } as unknown as jest.Mocked; +} + +export const dataViewsMock = createDataViewsMock(); diff --git a/src/plugins/discover/public/__mocks__/es_hits_complex.ts b/packages/kbn-unified-data-table/__mocks__/es_hits_complex.ts similarity index 100% rename from src/plugins/discover/public/__mocks__/es_hits_complex.ts rename to packages/kbn-unified-data-table/__mocks__/es_hits_complex.ts diff --git a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx new file mode 100644 index 0000000000000..d67afccc01559 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; +import { + EuiCheckbox, + EuiButtonIcon, + EuiPopover, + EuiFlexGroup, + EuiFlexItem, + EuiPopoverTitle, + EuiSpacer, + EuiDataGridControlColumn, +} from '@elastic/eui'; + +const SelectionHeaderCell = () => { + return ( +
    + null} /> +
    + ); +}; + +const SimpleHeaderCell = () => { + return ( +
    + {'Additional Actions'} +
    + ); +}; + +const SelectionRowCell = ({ rowIndex }: { rowIndex: number }) => { + return ( +
    + null} + /> +
    + ); +}; + +const TestTrailingColumn = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + return ( + setIsPopoverOpen(!isPopoverOpen)} + /> + } + data-test-subj="test-trailing-column-popover-button" + closePopover={() => setIsPopoverOpen(false)} + > + {'Actions'} +
    + + + +
    +
    + ); +}; + +export const testTrailingControlColumns = [ + { + id: 'actions', + width: 96, + headerCellRender: SimpleHeaderCell, + rowCellRender: TestTrailingColumn, + }, +]; + +export const testLeadingControlColumn: EuiDataGridControlColumn = { + id: 'test-leading-control', + headerCellRender: SelectionHeaderCell, + rowCellRender: SelectionRowCell, + width: 100, +}; diff --git a/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts b/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts new file mode 100644 index 0000000000000..42cd33d2eb699 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export class LocalStorageMock { + private store: Record; + constructor(defaultStore: Record) { + this.store = defaultStore; + } + clear() { + this.store = {}; + } + get(key: string) { + return this.store[key] || null; + } + set(key: string, value: unknown) { + this.store[key] = String(value); + } + remove(key: string) { + delete this.store[key]; + } +} diff --git a/packages/kbn-unified-data-table/__mocks__/services.ts b/packages/kbn-unified-data-table/__mocks__/services.ts new file mode 100644 index 0000000000000..2c74668644497 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/services.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { of } from 'rxjs'; +import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; +import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; +import { chromeServiceMock, coreMock } from '@kbn/core/public/mocks'; +import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; +import { IUiSettingsClient, ToastsStart } from '@kbn/core/public'; +import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; + +export function createServicesMock() { + const expressionsPlugin = expressionsPluginMock.createStartContract(); + + expressionsPlugin.run = jest.fn(() => + of({ + partial: false, + result: { + rows: [], + }, + }) + ) as unknown as typeof expressionsPlugin.run; + + const corePluginMock = coreMock.createStart(); + + const uiSettingsMock: Partial = { + get: jest.fn(), + isDefault: jest.fn((key: string) => { + return true; + }), + }; + + corePluginMock.uiSettings = { + ...corePluginMock.uiSettings, + ...uiSettingsMock, + }; + + const theme = { + theme$: of({ darkMode: false }), + }; + + corePluginMock.theme = theme; + + const dataPlugin = dataPluginMock.createStartContract(); + + return { + core: corePluginMock, + charts: chartPluginMock.createSetupContract(), + chrome: chromeServiceMock.createStartContract(), + history: () => ({ + location: { + search: '', + }, + listen: jest.fn(), + }), + fieldFormats: fieldFormatsMock, + filterManager: jest.fn(), + inspector: { + open: jest.fn(), + }, + uiActions: uiActionsPluginMock.createStartContract(), + uiSettings: uiSettingsMock as IUiSettingsClient, + http: { + basePath: '/', + }, + dataViewEditor: { + openEditor: jest.fn(), + userPermissions: { + editDataView: jest.fn(() => true), + }, + }, + dataViewFieldEditor: { + openEditor: jest.fn(), + userPermissions: { + editIndexPattern: jest.fn(() => true), + }, + } as unknown as DataViewFieldEditorStart, + theme, + storage: { + clear: jest.fn(), + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + }, + toastNotifications: { + addInfo: jest.fn(), + addWarning: jest.fn(), + addDanger: jest.fn(), + addSuccess: jest.fn(), + } as unknown as ToastsStart, + expressions: expressionsPlugin, + savedObjectsTagging: { + ui: { + getTagIdsFromReferences: jest.fn().mockResolvedValue([]), + updateTagsReferences: jest.fn(), + }, + }, + dataViews: jest.fn(), + locator: { + useUrl: jest.fn(() => ''), + navigate: jest.fn(), + getUrl: jest.fn(() => Promise.resolve('')), + }, + contextLocator: { getRedirectUrl: jest.fn(() => '') }, + singleDocLocator: { getRedirectUrl: jest.fn(() => '') }, + data: dataPlugin, + }; +} + +export const servicesMock = createServicesMock(); diff --git a/packages/kbn-unified-data-table/__mocks__/table_context.ts b/packages/kbn-unified-data-table/__mocks__/table_context.ts new file mode 100644 index 0000000000000..4a4a75b0fa9e5 --- /dev/null +++ b/packages/kbn-unified-data-table/__mocks__/table_context.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { DataView } from '@kbn/data-views-plugin/public'; +import { dataViewMock, esHitsMock } from '@kbn/discover-utils/src/__mocks__'; +import { dataViewComplexMock } from './data_view_complex'; +import { esHitsComplex } from './es_hits_complex'; +import { servicesMock } from './services'; +import { DataTableContext } from '../src/table_context'; +import { convertValueToString } from '../src/utils/convert_value_to_string'; +import { buildDataTableRecord } from '@kbn/discover-utils'; +import type { EsHitRecord } from '@kbn/discover-utils/types'; + +const buildTableContext = (dataView: DataView, rows: EsHitRecord[]): DataTableContext => { + const usedRows = rows.map((row) => { + return buildDataTableRecord(row, dataView); + }); + + return { + expanded: undefined, + setExpanded: jest.fn(), + rows: usedRows, + onFilter: jest.fn(), + dataView, + isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), + valueToStringConverter: (rowIndex, columnId, options) => + convertValueToString({ + rowIndex, + columnId, + fieldFormats: servicesMock.fieldFormats, + rows: usedRows, + dataView, + options, + }), + }; +}; + +export const dataTableContextMock = buildTableContext(dataViewMock, esHitsMock); + +export const dataTableContextComplexMock = buildTableContext(dataViewComplexMock, esHitsComplex); diff --git a/packages/kbn-unified-data-table/index.ts b/packages/kbn-unified-data-table/index.ts new file mode 100644 index 0000000000000..cc692420cd209 --- /dev/null +++ b/packages/kbn-unified-data-table/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { UnifiedDataTable, DataLoadingState } from './src/components/data_table'; +export type { UnifiedDataTableProps } from './src/components/data_table'; +export { getDisplayedColumns } from './src/utils/columns'; +export { ROWS_HEIGHT_OPTIONS } from './src/constants'; + +export { JSONCodeEditorCommonMemoized } from './src/components/json_code_editor/json_code_editor_common'; + +export * from './src/types'; +export * as columnActions from './src/components/actions/columns'; + +export { getRowsPerPageOptions } from './src/utils/rows_per_page'; +export { popularizeField } from './src/utils/popularize_field'; + +export { useColumns } from './src/hooks/use_data_grid_columns'; diff --git a/packages/kbn-unified-data-table/jest.config.js b/packages/kbn-unified-data-table/jest.config.js new file mode 100644 index 0000000000000..5256221baacf4 --- /dev/null +++ b/packages/kbn-unified-data-table/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-unified-data-table'], +}; diff --git a/packages/kbn-unified-data-table/kibana.jsonc b/packages/kbn-unified-data-table/kibana.jsonc new file mode 100644 index 0000000000000..de49c4caff1e5 --- /dev/null +++ b/packages/kbn-unified-data-table/kibana.jsonc @@ -0,0 +1,6 @@ +{ + "type": "shared-common", + "id": "@kbn/unified-data-table", + "description": "Contains functionality for the unified data table which can be integrated into apps", + "owner": "@elastic/kibana-data-discovery" +} diff --git a/packages/kbn-unified-data-table/package.json b/packages/kbn-unified-data-table/package.json new file mode 100644 index 0000000000000..79d4157293c05 --- /dev/null +++ b/packages/kbn-unified-data-table/package.json @@ -0,0 +1,10 @@ +{ + "name": "@kbn/unified-data-table", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0", + "sideEffects": [ + "*.css", + "*.scss" + ] +} diff --git a/packages/kbn-unified-data-table/src/components/actions/columns.test.ts b/packages/kbn-unified-data-table/src/components/actions/columns.test.ts new file mode 100644 index 0000000000000..d8480cf2067b4 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/actions/columns.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getStateColumnActions } from './columns'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { Capabilities } from '@kbn/core/types'; +import { dataViewsMock } from '../../../__mocks__/data_views'; + +function getStateColumnAction( + state: { columns?: string[]; sort?: string[][] }, + setAppState: (state: { columns: string[]; sort?: string[][] }) => void +) { + return getStateColumnActions({ + capabilities: { + discover: { + save: false, + }, + } as unknown as Capabilities, + dataView: dataViewMock, + dataViews: dataViewsMock, + useNewFieldsApi: true, + setAppState, + columns: state.columns, + sort: state.sort, + defaultOrder: 'desc', + }); +} + +describe('Test column actions', () => { + test('getStateColumnActions with empty state', () => { + const setAppState = jest.fn(); + const actions = getStateColumnAction({}, setAppState); + + actions.onAddColumn('_score'); + expect(setAppState).toHaveBeenCalledWith({ columns: ['_score'], sort: [['_score', 'desc']] }); + actions.onAddColumn('test'); + expect(setAppState).toHaveBeenCalledWith({ columns: ['test'] }); + }); + test('getStateColumnActions with columns and sort in state', () => { + const setAppState = jest.fn(); + const actions = getStateColumnAction( + { columns: ['first', 'second'], sort: [['first', 'desc']] }, + setAppState + ); + + actions.onAddColumn('_score'); + expect(setAppState).toHaveBeenCalledWith({ + columns: ['first', 'second', '_score'], + sort: [['first', 'desc']], + }); + setAppState.mockClear(); + actions.onAddColumn('third'); + expect(setAppState).toHaveBeenCalledWith({ + columns: ['first', 'second', 'third'], + sort: [['first', 'desc']], + }); + setAppState.mockClear(); + actions.onRemoveColumn('first'); + expect(setAppState).toHaveBeenCalledWith({ + columns: ['second'], + sort: [], + }); + setAppState.mockClear(); + actions.onSetColumns(['first', 'second', 'third'], true); + expect(setAppState).toHaveBeenCalledWith({ + columns: ['first', 'second', 'third'], + }); + setAppState.mockClear(); + + actions.onMoveColumn('second', 0); + expect(setAppState).toHaveBeenCalledWith({ + columns: ['second', 'first'], + }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/actions/columns.ts b/packages/kbn-unified-data-table/src/components/actions/columns.ts new file mode 100644 index 0000000000000..3355902ece86e --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/actions/columns.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { Capabilities } from '@kbn/core/public'; +import type { DataViewsContract } from '@kbn/data-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { popularizeField } from '../../utils/popularize_field'; + +/** + * Helper function to provide a fallback to a single _source column if the given array of columns + * is empty, and removes _source if there are more than 1 columns given + * @param columns + * @param useNewFieldsApi should a new fields API be used + */ +function buildColumns(columns: string[], useNewFieldsApi = false) { + if (columns.length > 1 && columns.indexOf('_source') !== -1) { + return columns.filter((col) => col !== '_source'); + } else if (columns.length !== 0) { + return columns; + } + return useNewFieldsApi ? [] : ['_source']; +} + +export function addColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) { + if (columns.includes(columnName)) { + return columns; + } + return buildColumns([...columns, columnName], useNewFieldsApi); +} + +export function removeColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) { + if (!columns.includes(columnName)) { + return columns; + } + return buildColumns( + columns.filter((col) => col !== columnName), + useNewFieldsApi + ); +} + +export function moveColumn(columns: string[], columnName: string, newIndex: number) { + if (newIndex < 0 || newIndex >= columns.length || !columns.includes(columnName)) { + return columns; + } + const modifiedColumns = [...columns]; + modifiedColumns.splice(modifiedColumns.indexOf(columnName), 1); // remove at old index + modifiedColumns.splice(newIndex, 0, columnName); // insert before new index + return modifiedColumns; +} + +export function getStateColumnActions({ + capabilities, + dataView, + dataViews, + useNewFieldsApi, + setAppState, + columns, + sort, + defaultOrder, +}: { + capabilities: Capabilities; + dataView: DataView; + dataViews: DataViewsContract; + useNewFieldsApi: boolean; + setAppState: (state: { columns: string[]; sort?: string[][] }) => void; + columns?: string[]; + sort: string[][] | undefined; + defaultOrder: string; +}) { + function onAddColumn(columnName: string) { + popularizeField(dataView, columnName, dataViews, capabilities); + const nextColumns = addColumn(columns || [], columnName, useNewFieldsApi); + const nextSort = columnName === '_score' && !sort?.length ? [['_score', defaultOrder]] : sort; + setAppState({ columns: nextColumns, sort: nextSort }); + } + + function onRemoveColumn(columnName: string) { + popularizeField(dataView, columnName, dataViews, capabilities); + const nextColumns = removeColumn(columns || [], columnName, useNewFieldsApi); + // The state's sort property is an array of [sortByColumn,sortDirection] + const nextSort = sort && sort.length ? sort.filter((subArr) => subArr[0] !== columnName) : []; + setAppState({ columns: nextColumns, sort: nextSort }); + } + + function onMoveColumn(columnName: string, newIndex: number) { + const nextColumns = moveColumn(columns || [], columnName, newIndex); + setAppState({ columns: nextColumns }); + } + + function onSetColumns(nextColumns: string[], hideTimeColumn: boolean) { + // The next line should be gone when classic table will be removed + const actualColumns = + !hideTimeColumn && dataView.timeFieldName && dataView.timeFieldName === nextColumns[0] + ? (nextColumns || []).slice(1) + : nextColumns; + + setAppState({ columns: actualColumns }); + } + return { + onAddColumn, + onRemoveColumn, + onMoveColumn, + onSetColumns, + }; +} diff --git a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.test.tsx b/packages/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx similarity index 86% rename from src/plugins/discover/public/components/discover_grid/build_copy_column_button.test.tsx rename to packages/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx index dda3a904bda3f..02a3c6e7e425e 100644 --- a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.test.tsx +++ b/packages/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { discoverServiceMock } from '../../__mocks__/services'; -import { discoverGridContextMock } from '../../__mocks__/grid_context'; +import { servicesMock } from '../../__mocks__/services'; +import { dataTableContextMock } from '../../__mocks__/table_context'; import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; const execCommandMock = (global.document.execCommand = jest.fn()); @@ -20,7 +20,7 @@ describe('Build a column button to copy to clipboard', () => { it('should copy a column name to clipboard on click', () => { const { label, iconType, onClick } = buildCopyColumnNameButton({ columnDisplayName: 'test-field-name', - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, }); execCommandMock.mockImplementationOnce(() => true); @@ -49,9 +49,9 @@ describe('Build a column button to copy to clipboard', () => { const { label, iconType, onClick } = buildCopyColumnValuesButton({ columnId: 'extension', columnDisplayName: 'custom_extension', - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, rowsCount: 3, - valueToStringConverter: discoverGridContextMock.valueToStringConverter, + valueToStringConverter: dataTableContextMock.valueToStringConverter, }); const wrapper = mountWithIntl( @@ -72,8 +72,8 @@ describe('Build a column button to copy to clipboard', () => { } = buildCopyColumnValuesButton({ columnId: '_source', columnDisplayName: 'Document', - toastNotifications: discoverServiceMock.toastNotifications, - valueToStringConverter: discoverGridContextMock.valueToStringConverter, + toastNotifications: servicesMock.toastNotifications, + valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 3, }); @@ -101,7 +101,7 @@ describe('Build a column button to copy to clipboard', () => { it('should not copy to clipboard on click', () => { const { label, iconType, onClick } = buildCopyColumnNameButton({ columnDisplayName: 'test-field-name', - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, }); execCommandMock.mockImplementationOnce(() => false); diff --git a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.tsx b/packages/kbn-unified-data-table/src/components/build_copy_column_button.tsx similarity index 90% rename from src/plugins/discover/public/components/discover_grid/build_copy_column_button.tsx rename to packages/kbn-unified-data-table/src/components/build_copy_column_button.tsx index 111e80dd62e95..d1bfff1f1da41 100644 --- a/src/plugins/discover/public/components/discover_grid/build_copy_column_button.tsx +++ b/packages/kbn-unified-data-table/src/components/build_copy_column_button.tsx @@ -13,8 +13,8 @@ import type { ToastsStart } from '@kbn/core/public'; import { copyColumnValuesToClipboard, copyColumnNameToClipboard, -} from '../../utils/copy_value_to_clipboard'; -import type { ValueToStringConverter } from '../../types'; +} from '../utils/copy_value_to_clipboard'; +import type { ValueToStringConverter } from '../types'; function buildCopyColumnButton({ label, @@ -47,7 +47,7 @@ export function buildCopyColumnNameButton({ return buildCopyColumnButton({ label: ( ), @@ -72,7 +72,7 @@ export function buildCopyColumnValuesButton({ return buildCopyColumnButton({ label: ( ), diff --git a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.test.tsx b/packages/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx similarity index 81% rename from src/plugins/discover/public/components/discover_grid/build_edit_field_button.test.tsx rename to packages/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx index c78918976e88d..a9d1a37ab6eda 100644 --- a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.test.tsx +++ b/packages/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx @@ -11,7 +11,7 @@ import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { discoverServiceMock } from '../../__mocks__/services'; +import { servicesMock } from '../../__mocks__/services'; import { buildEditFieldButton } from './build_edit_field_button'; const dataView = buildDataViewMock({ @@ -50,8 +50,7 @@ describe('buildEditFieldButton', () => { it('should return null if the field is not editable', () => { const field = dataView.getFieldByName('unknown_field') as DataViewField; const button = buildEditFieldButton({ - hasEditDataViewPermission: () => - discoverServiceMock.dataViewEditor.userPermissions.editDataView(), + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), dataView, field, editField: jest.fn(), @@ -61,12 +60,11 @@ describe('buildEditFieldButton', () => { it('should return null if the data view is not editable', () => { jest - .spyOn(discoverServiceMock.dataViewEditor.userPermissions, 'editDataView') + .spyOn(servicesMock.dataViewEditor.userPermissions, 'editDataView') .mockReturnValueOnce(false); const field = dataView.getFieldByName('bytes') as DataViewField; const button = buildEditFieldButton({ - hasEditDataViewPermission: () => - discoverServiceMock.dataViewEditor.userPermissions.editDataView(), + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), dataView, field, editField: jest.fn(), @@ -77,8 +75,7 @@ describe('buildEditFieldButton', () => { it('should return null if passed the _source field', () => { const field = dataView.getFieldByName('_source') as DataViewField; const button = buildEditFieldButton({ - hasEditDataViewPermission: () => - discoverServiceMock.dataViewEditor.userPermissions.editDataView(), + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), dataView, field, editField: jest.fn(), @@ -89,8 +86,7 @@ describe('buildEditFieldButton', () => { it('should return EuiListGroupItemProps if the field and data view are editable', () => { const field = dataView.getFieldByName('bytes') as DataViewField; const button = buildEditFieldButton({ - hasEditDataViewPermission: () => - discoverServiceMock.dataViewEditor.userPermissions.editDataView(), + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), dataView, field, editField: jest.fn(), @@ -105,7 +101,7 @@ describe('buildEditFieldButton', () => { "iconType": "pencil", "label": , "onClick": [Function], @@ -118,8 +114,7 @@ describe('buildEditFieldButton', () => { const field = dataView.getFieldByName('bytes') as DataViewField; const editField = jest.fn(); const buttonProps = buildEditFieldButton({ - hasEditDataViewPermission: () => - discoverServiceMock.dataViewEditor.userPermissions.editDataView(), + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), dataView, field, editField, diff --git a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx b/packages/kbn-unified-data-table/src/components/build_edit_field_button.tsx similarity index 87% rename from src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx rename to packages/kbn-unified-data-table/src/components/build_edit_field_button.tsx index 71da6ca691f93..87d405f608c8c 100644 --- a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx +++ b/packages/kbn-unified-data-table/src/components/build_edit_field_button.tsx @@ -10,7 +10,7 @@ import { EuiListGroupItemProps } from '@elastic/eui'; import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -import { getFieldCapabilities } from '../../utils/get_field_capabilities'; +import { getFieldCapabilities } from '../utils/get_field_capabilities'; export const buildEditFieldButton = ({ hasEditDataViewPermission, @@ -37,7 +37,10 @@ export const buildEditFieldButton = ({ const editFieldButton: EuiListGroupItemProps = { size: 'xs', label: ( - + ), iconType: 'pencil', iconProps: { size: 'm' }, diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss new file mode 100644 index 0000000000000..048a641cf7562 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -0,0 +1,163 @@ +.unifiedDataTable { + width: 100%; + max-width: 100%; + height: 100%; + overflow: hidden; +} + +.unifiedDataTable__cellValue { + font-family: $euiCodeFontFamily; +} + +.unifiedDataTable__cellPopover { + // Fixes https://github.com/elastic/kibana/issues/145216 in Chrome + .lines-content.monaco-editor-background { + overflow: unset !important; + contain: unset !important; + } +} + +.unifiedDataTable__cellPopoverValue { + font-family: $euiCodeFontFamily; + font-size: $euiFontSizeS; +} + +.euiDataGridRowCell__definedHeight { + white-space: pre-wrap; +} + +.unifiedDataTable__inner { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + height: 100%; + + .euiDataGrid__content { + background: transparent; + } + + .euiDataGrid__controls { + border-top: $euiBorderThin; + } + + .euiDataGrid--headerUnderline .euiDataGridHeaderCell { + border-bottom: $euiBorderThin; + } + + .euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='openDetails'], + .euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='select'] { + padding-left: 0; + padding-right: 0; + } + + .euiDataGrid--rowHoverHighlight .euiDataGridRow:hover, + .euiDataGrid--rowHoverHighlight .euiDataGridRow:hover .euiDataGridRowCell__contentByHeight + .euiDataGridRowCell__expandActions { + background-color: tintOrShade($euiColorLightShade, 50%, 0); + } +} + +.unifiedDataTable__table { + flex-grow: 1; + flex-shrink: 1; + min-height: 0; +} + +.unifiedDataTable__flyoutHeader { + white-space: nowrap; +} + +.unifiedDataTable__flyoutDocumentNavigation { + justify-content: flex-end; +} + +// We only truncate if the cell is not a control column. +.euiDataGridHeader { + + .euiDataGridHeaderCell__content { + @include euiTextTruncate; + overflow: hidden; + white-space: pre-wrap; + flex-grow: 1; + } + + .euiDataGridHeaderCell__popover { + flex-grow: 0; + flex-basis: auto; + width: auto; + padding-left: $euiSizeXS; + } +} + +.euiDataGridRowCell--numeric { + text-align: right; +} + +.euiDataGrid__loading, +.euiDataGrid__noResults { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1 0 100%; + text-align: center; + height: 100%; + width: 100%; +} + +.unifiedDataTableFormatSource { + @include euiTextTruncate; +} + +.unifiedDataTable__rowControl { + // fine-tuning the vertical alignment with the text for any row height setting + margin-top: -3px; + .euiDataGridRowCell__truncate & { // "Single line" row height setting + margin-top: 0; + } +} + +.unifiedDataTable__descriptionList { + // force the content truncation when "Single line" row height setting is active + .euiDataGridRowCell__truncate & { + -webkit-line-clamp: 1; + display: -webkit-box; + -webkit-box-orient: vertical; + height: 100%; + overflow: hidden; + } +} + +.unifiedDataTable__descriptionListTitle { + margin-inline: 0 0; + padding-inline: 0; + background: transparent; + font-weight: $euiFontWeightBold; +} + +.unifiedDataTable__descriptionListDescription { + margin-inline: $euiSizeS $euiSizeS; + padding-inline: 0; + word-break: break-all; + white-space: normal; + + // Special handling for images coming from the image field formatter + img { + // Align the images vertically centered with the text + vertical-align: middle; + // Set the maximum height to the line-height. The used function is the same + // function used to calculate the line-height for the EuiDescriptionList Description. + // !important is required to overwrite the max-height on the element from the field formatter + max-height: lineHeightFromBaseline(2) !important; + // An arbitrary amount of width we don't want to go over, to not have very wide images. + // For most width-height-ratios that will never be hit, because we'd usually limit + // it by the way smaller height. But images with very large width and very small height + // might be limited by that. + max-width: ($euiSizeXXL * 12.5) !important; + } +} + +@include euiBreakpoint('xs', 's', 'm') { + // EUI issue to hide 'of' text https://github.com/elastic/eui/issues/4654 + .unifiedDataTable__flyoutDocumentNavigation .euiPagination__compressedText { + display: none; + } +} diff --git a/packages/kbn-unified-data-table/src/components/data_table.test.tsx b/packages/kbn-unified-data-table/src/components/data_table.test.tsx new file mode 100644 index 0000000000000..7ca0888230749 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table.test.tsx @@ -0,0 +1,435 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { ReactWrapper } from 'enzyme'; +import { + EuiButton, + EuiCopy, + EuiDataGridCellValueElementProps, + EuiDataGridCustomBodyProps, +} from '@elastic/eui'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { act } from 'react-dom/test-utils'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { buildDataViewMock, deepMockedFields, esHitsMock } from '@kbn/discover-utils/src/__mocks__'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { DataLoadingState, UnifiedDataTable, UnifiedDataTableProps } from './data_table'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { servicesMock } from '../../__mocks__/services'; +import { buildDataTableRecord, getDocId } from '@kbn/discover-utils'; +import type { DataTableRecord, EsHitRecord } from '@kbn/discover-utils/types'; +import { testLeadingControlColumn } from '../../__mocks__/external_control_columns'; + +const mockUseDataGridColumnsCellActions = jest.fn((prop: unknown) => []); +jest.mock('@kbn/cell-actions', () => ({ + ...jest.requireActual('@kbn/cell-actions'), + useDataGridColumnsCellActions: (prop: unknown) => mockUseDataGridColumnsCellActions(prop), +})); + +export const dataViewMock = buildDataViewMock({ + name: 'the-data-view', + fields: deepMockedFields, + timeFieldName: '@timestamp', +}); + +function getProps(): UnifiedDataTableProps { + const services = servicesMock; + services.dataViewFieldEditor.userPermissions.editIndexPattern = jest.fn().mockReturnValue(true); + + return { + ariaLabelledBy: '', + columns: [], + dataView: dataViewMock, + loadingState: DataLoadingState.loaded, + expandedDoc: undefined, + onFilter: jest.fn(), + onResize: jest.fn(), + onSetColumns: jest.fn(), + onSort: jest.fn(), + rows: esHitsMock.map((hit) => buildDataTableRecord(hit, dataViewMock)), + sampleSize: 30, + searchDescription: '', + searchTitle: '', + setExpandedDoc: jest.fn(), + settings: {}, + showTimeCol: true, + sort: [], + useNewFieldsApi: true, + services: { + fieldFormats: services.fieldFormats, + uiSettings: services.uiSettings, + dataViewFieldEditor: services.dataViewFieldEditor, + toastNotifications: services.toastNotifications, + storage: services.storage as unknown as Storage, + data: services.data, + theme: services.theme, + }, + }; +} + +async function getComponent(props: UnifiedDataTableProps = getProps()) { + const Proxy = (innerProps: UnifiedDataTableProps) => ( + + + + ); + + const component = mountWithIntl(); + await act(async () => { + // needed by cell actions to complete async loading + component.update(); + }); + return component; +} + +function getSelectedDocNr(component: ReactWrapper) { + const gridSelectionBtn = findTestSubject(component, 'dscGridSelectionBtn'); + if (!gridSelectionBtn.length) { + return 0; + } + const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-selected-documents'); + return Number(selectedNr); +} + +function getDisplayedDocNr(component: ReactWrapper) { + const gridSelectionBtn = findTestSubject(component, 'discoverDocTable'); + if (!gridSelectionBtn.length) { + return 0; + } + const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-document-number'); + return Number(selectedNr); +} + +async function toggleDocSelection( + component: ReactWrapper, + document: EsHitRecord +) { + act(() => { + const docId = getDocId(document); + findTestSubject(component, `dscGridSelectDoc-${docId}`).simulate('change'); + }); + component.update(); +} + +describe('UnifiedDataTable', () => { + afterEach(async () => { + jest.clearAllMocks(); + }); + + describe('Document selection', () => { + let component: ReactWrapper; + beforeEach(async () => { + component = await getComponent(); + }); + + test('no documents are selected initially', async () => { + expect(getSelectedDocNr(component)).toBe(0); + expect(getDisplayedDocNr(component)).toBe(5); + }); + + test('Allows selection/deselection of multiple documents', async () => { + await toggleDocSelection(component, esHitsMock[0]); + expect(getSelectedDocNr(component)).toBe(1); + await toggleDocSelection(component, esHitsMock[1]); + expect(getSelectedDocNr(component)).toBe(2); + await toggleDocSelection(component, esHitsMock[1]); + expect(getSelectedDocNr(component)).toBe(1); + }); + + test('deselection of all selected documents', async () => { + await toggleDocSelection(component, esHitsMock[0]); + await toggleDocSelection(component, esHitsMock[1]); + expect(getSelectedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridClearSelectedDocuments').simulate('click'); + expect(getSelectedDocNr(component)).toBe(0); + }); + + test('showing only selected documents and undo selection', async () => { + await toggleDocSelection(component, esHitsMock[0]); + await toggleDocSelection(component, esHitsMock[1]); + expect(getSelectedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + component.update(); + findTestSubject(component, 'dscGridShowAllDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(5); + }); + + test('showing selected documents, underlying data changes, all documents are displayed, selection is gone', async () => { + await toggleDocSelection(component, esHitsMock[0]); + await toggleDocSelection(component, esHitsMock[1]); + expect(getSelectedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(2); + component.setProps({ + rows: [ + { + _index: 'i', + _id: '6', + _score: 1, + _source: { + date: '2020-20-02T12:12:12.128', + name: 'test6', + extension: 'doc', + bytes: 50, + }, + }, + ].map((row) => buildDataTableRecord(row, dataViewMock)), + }); + expect(getDisplayedDocNr(component)).toBe(1); + expect(getSelectedDocNr(component)).toBe(0); + }); + + test('showing only selected documents and remove filter deselecting each doc manually', async () => { + await toggleDocSelection(component, esHitsMock[0]); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(1); + await toggleDocSelection(component, esHitsMock[0]); + expect(getDisplayedDocNr(component)).toBe(5); + await toggleDocSelection(component, esHitsMock[0]); + expect(getDisplayedDocNr(component)).toBe(5); + }); + + test('copying selected documents to clipboard', async () => { + await toggleDocSelection(component, esHitsMock[0]); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + expect(component.find(EuiCopy).prop('textToCopy')).toMatchInlineSnapshot( + `"[{\\"_index\\":\\"i\\",\\"_id\\":\\"1\\",\\"_score\\":1,\\"_type\\":\\"_doc\\",\\"_source\\":{\\"date\\":\\"2020-20-01T12:12:12.123\\",\\"message\\":\\"test1\\",\\"bytes\\":20}}]"` + ); + }); + }); + + describe('edit field button', () => { + it('should render the edit field button if onFieldEdited is provided', async () => { + const component = await getComponent({ + ...getProps(), + columns: ['message'], + onFieldEdited: jest.fn(), + }); + expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( + false + ); + findTestSubject(component, 'dataGridHeaderCell-message').find('button').simulate('click'); + expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( + true + ); + expect(findTestSubject(component, 'gridEditFieldButton').exists()).toBe(true); + }); + + it('should not render the edit field button if onFieldEdited is not provided', async () => { + const component = await getComponent({ + ...getProps(), + columns: ['message'], + }); + expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( + false + ); + findTestSubject(component, 'dataGridHeaderCell-message').find('button').simulate('click'); + expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( + true + ); + expect(findTestSubject(component, 'gridEditFieldButton').exists()).toBe(false); + }); + }); + + describe('cellActionsTriggerId', () => { + it('should call useDataGridColumnsCellActions with empty params when no cellActionsTriggerId is provided', async () => { + await getComponent({ + ...getProps(), + columns: ['message'], + onFieldEdited: jest.fn(), + }); + expect(mockUseDataGridColumnsCellActions).toHaveBeenCalledWith( + expect.objectContaining({ + triggerId: undefined, + getCellValue: expect.any(Function), + fields: undefined, + }) + ); + }); + + it('should call useDataGridColumnsCellActions properly when cellActionsTriggerId defined', async () => { + await getComponent({ + ...getProps(), + columns: ['message'], + onFieldEdited: jest.fn(), + cellActionsTriggerId: 'test', + }); + expect(mockUseDataGridColumnsCellActions).toHaveBeenCalledWith( + expect.objectContaining({ + triggerId: 'test', + getCellValue: expect.any(Function), + fields: [ + dataViewMock.getFieldByName('@timestamp')?.toSpec(), + dataViewMock.getFieldByName('message')?.toSpec(), + ], + }) + ); + }); + }); + + describe('sorting', () => { + it('should enable in memory sorting with plain records', async () => { + const component = await getComponent({ + ...getProps(), + columns: ['message'], + isPlainRecord: true, + }); + + expect( + ( + findTestSubject(component, 'docTable') + .find('EuiDataGridInMemoryRenderer') + .first() + .props() as Record + ).inMemory + ).toMatchInlineSnapshot(` + Object { + "level": "sorting", + } + `); + }); + }); + + describe('externalControlColumns', () => { + it('should render external leading control columns', async () => { + const component = await getComponent({ + ...getProps(), + expandedDoc: { + id: 'test', + raw: { + _index: 'test_i', + _id: 'test', + }, + flattened: { test: jest.fn() }, + }, + setExpandedDoc: jest.fn(), + renderDocumentView: jest.fn(), + externalControlColumns: [testLeadingControlColumn], + }); + + expect(findTestSubject(component, 'docTableExpandToggleColumn').exists()).toBeTruthy(); + expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy(); + }); + }); + + it('should render provided in renderDocumentView DocumentView on expand clicked', async () => { + const component = await getComponent({ + ...getProps(), + expandedDoc: { + id: 'test', + raw: { + _index: 'test_i', + _id: 'test', + }, + flattened: { test: jest.fn() }, + }, + setExpandedDoc: jest.fn(), + renderDocumentView: ( + hit: DataTableRecord, + displayedRows: DataTableRecord[], + displayedColumns: string[] + ) =>
    {hit.id}
    , + externalControlColumns: [testLeadingControlColumn], + }); + + findTestSubject(component, 'docTableExpandToggleColumn').first().simulate('click'); + expect(findTestSubject(component, 'test-document-view').exists()).toBeTruthy(); + }); + + describe('externalAdditionalControls', () => { + it('should render external additional toolbar controls', async () => { + const component = await getComponent({ + ...getProps(), + columns: ['message'], + externalAdditionalControls: , + }); + + expect(findTestSubject(component, 'test-additional-control').exists()).toBeTruthy(); + expect(findTestSubject(component, 'dataGridColumnSelectorButton').exists()).toBeTruthy(); + }); + }); + + describe('externalCustomRenderers', () => { + it('should render only host column with the custom renderer, message should be rendered with the default cell renderer', async () => { + const component = await getComponent({ + ...getProps(), + columns: ['message', 'host'], + externalCustomRenderers: { + host: (props: EuiDataGridCellValueElementProps) => ( +
    {props.columnId}
    + ), + }, + }); + + expect(findTestSubject(component, 'test-renderer-host').exists()).toBeTruthy(); + expect(findTestSubject(component, 'test-renderer-message').exists()).toBeFalsy(); + }); + }); + + describe('renderCustomGridBody', () => { + it('should render custom grid body for each row', async () => { + const component = await getComponent({ + ...getProps(), + columns: ['message', 'host'], + trailingControlColumns: [ + { + id: 'row-details', + + // The header cell should be visually hidden, but available to screen readers + width: 0, + headerCellRender: () => <>, + headerCellProps: { className: 'euiScreenReaderOnly' }, + + // The footer cell can be hidden to both visual & SR users, as it does not contain meaningful information + footerCellProps: { style: { display: 'none' } }, + + // When rendering this custom cell, we'll want to override + // the automatic width/heights calculated by EuiDataGrid + rowCellRender: jest.fn(), + }, + ], + renderCustomGridBody: (props: EuiDataGridCustomBodyProps) => ( +
    + +
    + ), + }); + + expect(findTestSubject(component, 'test-renderer-custom-grid-body').exists()).toBeTruthy(); + }); + }); + + describe('componentsTourSteps', () => { + it('should render tour step for the first row of leading control column expandButton', async () => { + const component = await getComponent({ + ...getProps(), + expandedDoc: { + id: 'test', + raw: { + _index: 'test_i', + _id: 'test', + }, + flattened: { test: jest.fn() }, + }, + setExpandedDoc: jest.fn(), + renderDocumentView: jest.fn(), + componentsTourSteps: { expandButton: 'test-expand' }, + }); + + const gridExpandBtn = findTestSubject(component, 'docTableExpandToggleColumn').first(); + const tourStep = gridExpandBtn.getDOMNode().getAttribute('id'); + expect(tourStep).toEqual('test-expand'); + }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx new file mode 100644 index 0000000000000..e5f5e5dbbba39 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -0,0 +1,836 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback, useMemo, useState, useRef, useEffect } from 'react'; +import classnames from 'classnames'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { of } from 'rxjs'; +import useObservable from 'react-use/lib/useObservable'; +import './data_table.scss'; +import type { Storage } from '@kbn/kibana-utils-plugin/public'; +import { + EuiDataGridSorting, + EuiDataGrid, + EuiScreenReaderOnly, + EuiSpacer, + EuiText, + htmlIdGenerator, + EuiLoadingSpinner, + EuiIcon, + EuiDataGridRefProps, + EuiDataGridInMemory, + EuiDataGridControlColumn, + EuiDataGridCustomBodyProps, + EuiDataGridCellValueElementProps, +} from '@elastic/eui'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { + useDataGridColumnsCellActions, + type UseDataGridColumnsCellActionsProps, +} from '@kbn/cell-actions'; +import type { ToastsStart, IUiSettingsClient } from '@kbn/core/public'; +import type { Serializable } from '@kbn/utility-types'; +import type { DataTableRecord } from '@kbn/discover-utils/types'; +import { getShouldShowFieldHandler, DOC_HIDE_TIME_COLUMN_SETTING } from '@kbn/discover-utils'; +import type { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { ThemeServiceStart } from '@kbn/react-kibana-context-common'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { UnifiedDataTableSettings, ValueToStringConverter } from '../types'; +import { getDisplayedColumns } from '../utils/columns'; +import { convertValueToString } from '../utils/convert_value_to_string'; +import { getRowsPerPageOptions } from '../utils/rows_per_page'; +import { getRenderCellValueFn } from '../utils/get_render_cell_value'; +import { getEuiGridColumns, getLeadControlColumns, getVisibleColumns } from './data_table_columns'; +import { UnifiedDataTableContext } from '../table_context'; +import { getSchemaDetectors } from './data_table_schema'; +import { DataTableDocumentToolbarBtn } from './data_table_document_selection'; +import { useRowHeightsOptions } from '../hooks/use_row_heights_options'; +import { + DEFAULT_ROWS_PER_PAGE, + GRID_STYLE, + toolbarVisibility as toolbarVisibilityDefaults, +} from '../constants'; +import { UnifiedDataTableFooter } from './data_table_footer'; + +export type SortOrder = [string, string]; + +export enum DataLoadingState { + loading = 'loading', + loadingMore = 'loadingMore', + loaded = 'loaded', +} + +const themeDefault = { darkMode: false }; + +interface SortObj { + id: string; + direction: string; +} + +export interface UnifiedDataTableProps { + /** + * Determines which element labels the grid for ARIA + */ + ariaLabelledBy: string; + /** + * Optional class name to apply + */ + className?: string; + /** + * Determines ids of the columns which are displayed + */ + columns: string[]; + /** + * If set, the given document is displayed in a flyout + */ + expandedDoc?: DataTableRecord; + /** + * The used data view + */ + dataView: DataView; + /** + * Determines if data is currently loaded + */ + loadingState: DataLoadingState; + /** + * Function to add a filter in the grid cell or document flyout + */ + onFilter: DocViewFilterFn; + /** + * Function triggered when a column is resized by the user + */ + onResize?: (colSettings: { columnId: string; width: number }) => void; + /** + * Function to set all columns + */ + onSetColumns: (columns: string[], hideTimeColumn: boolean) => void; + /** + * function to change sorting of the documents, skipped when isSortEnabled is set to false + */ + onSort?: (sort: string[][]) => void; + /** + * Array of documents provided by Elasticsearch + */ + rows?: DataTableRecord[]; + /** + * The max size of the documents returned by Elasticsearch + */ + sampleSize: number; + /** + * Function to set the expanded document, which is displayed in a flyout + */ + setExpandedDoc?: (doc?: DataTableRecord) => void; + /** + * Grid display settings persisted in Elasticsearch (e.g. column width) + */ + settings?: UnifiedDataTableSettings; + /** + * Search description + */ + searchDescription?: string; + /** + * Search title + */ + searchTitle?: string; + /** + * Determines whether the time columns should be displayed (legacy settings) + */ + showTimeCol: boolean; + /** + * Determines whether the full screen button should be displayed + */ + showFullScreenButton?: boolean; + /** + * Manage user sorting control + */ + isSortEnabled?: boolean; + /** + * Current sort setting + */ + sort: SortOrder[]; + /** + * How the data is fetched + */ + useNewFieldsApi: boolean; + /** + * Manage pagination control + */ + isPaginationEnabled?: boolean; + /** + * List of used control columns (available: 'openDetails', 'select') + */ + controlColumnIds?: string[]; + /** + * Row height from state + */ + rowHeightState?: number; + /** + * Update row height state + */ + onUpdateRowHeight?: (rowHeight: number) => void; + /** + * Is text base lang mode enabled + */ + isPlainRecord?: boolean; + /** + * Current state value for rowsPerPage + */ + rowsPerPageState?: number; + /** + * Update rows per page state + */ + onUpdateRowsPerPage?: (rowsPerPage: number) => void; + /** + * Callback to execute on edit runtime field + */ + onFieldEdited?: () => void; + /** + * Optional triggerId to retrieve the column cell actions that will override the default ones + */ + cellActionsTriggerId?: string; + /** + * Service dependencies + */ + services: { + theme: ThemeServiceStart; + fieldFormats: FieldFormatsStart; + uiSettings: IUiSettingsClient; + dataViewFieldEditor: DataViewFieldEditorStart; + toastNotifications: ToastsStart; + storage: Storage; + data: DataPublicPluginStart; + }; + /** + * Callback to render DocumentView when the document is expanded + */ + renderDocumentView?: ( + hit: DataTableRecord, + displayedRows: DataTableRecord[], + displayedColumns: string[] + ) => JSX.Element | undefined; + /** + * Optional value for providing configuration setting for UnifiedDataTable rows height + */ + configRowHeight?: number; + /** + * Optional value for providing configuration setting for enabling to display the complex fields in the table. Default is true. + */ + showMultiFields?: boolean; + /** + * Optional value for providing configuration setting for maximum number of document fields to display in the table. Default is 50. + */ + maxDocFieldsDisplayed?: number; + /** + * Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. + */ + externalControlColumns?: EuiDataGridControlColumn[]; + /** + * Number total hits from ES + */ + totalHits?: number; + /** + * To fetch more + */ + onFetchMoreRecords?: () => void; + /** + * Optional value for providing the additional controls available in the UnifiedDataTable toolbar to manage it's records or state. UnifiedDataTable includes Columns, Sorting and Bulk Actions. + */ + externalAdditionalControls?: React.ReactNode; + /** + * Optional list of number type values to set custom UnifiedDataTable paging options to display the records per page. + */ + rowsPerPageOptions?: number[]; + /** + * An optional function called to completely customize and control the rendering of + * EuiDataGrid's body and cell placement. This can be used to, e.g. remove EuiDataGrid's + * virtualization library, or roll your own. + * + * This component is **only** meant as an escape hatch for extremely custom use cases. + * + * Behind the scenes, this function is treated as a React component, + * allowing hooks, context, and other React concepts to be used. + * It receives #EuiDataGridCustomBodyProps as its only argument. + */ + renderCustomGridBody?: (args: EuiDataGridCustomBodyProps) => React.ReactNode; + /** + * An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. + */ + trailingControlColumns?: EuiDataGridControlColumn[]; + /** + * An optional value for a custom number of the visible cell actions in the table. By default is up to 3. + **/ + visibleCellActions?: number; + /** + * An optional settings for a specified fields rendering like links. Applied only for the listed fields rendering. + */ + externalCustomRenderers?: Record< + string, + (props: EuiDataGridCellValueElementProps) => React.ReactNode + >; + /** + * Name of the UnifiedDataTable consumer component or application + */ + consumer?: string; + /** + * Optional key/value pairs to set guided onboarding steps ids for a data table components included to guided tour. + */ + componentsTourSteps?: Record; +} + +export const EuiDataGridMemoized = React.memo(EuiDataGrid); + +const CONTROL_COLUMN_IDS_DEFAULT = ['openDetails', 'select']; + +export const UnifiedDataTable = ({ + ariaLabelledBy, + columns, + controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, + dataView, + loadingState, + onFilter, + onResize, + onSetColumns, + onSort, + rows, + sampleSize, + searchDescription, + searchTitle, + settings, + showTimeCol, + showFullScreenButton = true, + sort, + useNewFieldsApi, + isSortEnabled = true, + isPaginationEnabled = true, + cellActionsTriggerId, + className, + rowHeightState, + onUpdateRowHeight, + isPlainRecord = false, + rowsPerPageState, + onUpdateRowsPerPage, + onFieldEdited, + services, + renderCustomGridBody, + trailingControlColumns, + totalHits, + onFetchMoreRecords, + renderDocumentView, + setExpandedDoc, + expandedDoc, + configRowHeight, + showMultiFields = true, + maxDocFieldsDisplayed = 50, + externalControlColumns, + externalAdditionalControls, + rowsPerPageOptions, + visibleCellActions, + externalCustomRenderers, + consumer = 'discover', + componentsTourSteps, +}: UnifiedDataTableProps) => { + const { fieldFormats, toastNotifications, dataViewFieldEditor, uiSettings, storage, data } = + services; + const { darkMode } = useObservable(services.theme?.theme$ ?? of(themeDefault), themeDefault); + const dataGridRef = useRef(null); + const [selectedDocs, setSelectedDocs] = useState([]); + const [isFilterActive, setIsFilterActive] = useState(false); + const displayedColumns = getDisplayedColumns(columns, dataView); + const defaultColumns = displayedColumns.includes('_source'); + const usedSelectedDocs = useMemo(() => { + if (!selectedDocs.length || !rows?.length) { + return []; + } + const idMap = rows.reduce((map, row) => map.set(row.id, true), new Map()); + // filter out selected docs that are no longer part of the current data + const result = selectedDocs.filter((docId) => !!idMap.get(docId)); + if (result.length === 0 && isFilterActive) { + setIsFilterActive(false); + } + return result; + }, [selectedDocs, rows, isFilterActive]); + + const displayedRows = useMemo(() => { + if (!rows) { + return []; + } + if (!isFilterActive || usedSelectedDocs.length === 0) { + return rows; + } + const rowsFiltered = rows.filter((row) => usedSelectedDocs.includes(row.id)); + if (!rowsFiltered.length) { + // in case the selected docs are no longer part of the sample of 500, show all docs + return rows; + } + return rowsFiltered; + }, [rows, usedSelectedDocs, isFilterActive]); + + const valueToStringConverter: ValueToStringConverter = useCallback( + (rowIndex, columnId, options) => { + return convertValueToString({ + rowIndex, + rows: displayedRows, + dataView, + columnId, + fieldFormats, + options, + }); + }, + [displayedRows, dataView, fieldFormats] + ); + + const unifiedDataTableContextValue = useMemo( + () => ({ + expanded: expandedDoc, + setExpanded: setExpandedDoc, + rows: displayedRows, + onFilter, + dataView, + isDarkMode: darkMode, + selectedDocs: usedSelectedDocs, + setSelectedDocs: (newSelectedDocs: React.SetStateAction) => { + setSelectedDocs(newSelectedDocs); + if (isFilterActive && newSelectedDocs.length === 0) { + setIsFilterActive(false); + } + }, + valueToStringConverter, + componentsTourSteps, + }), + [ + componentsTourSteps, + darkMode, + dataView, + displayedRows, + expandedDoc, + isFilterActive, + onFilter, + setExpandedDoc, + usedSelectedDocs, + valueToStringConverter, + ] + ); + + /** + * Pagination + */ + const currentPageSize = + typeof rowsPerPageState === 'number' && rowsPerPageState > 0 + ? rowsPerPageState + : DEFAULT_ROWS_PER_PAGE; + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: currentPageSize, + }); + const rowCount = useMemo(() => (displayedRows ? displayedRows.length : 0), [displayedRows]); + const pageCount = useMemo( + () => Math.ceil(rowCount / pagination.pageSize), + [rowCount, pagination] + ); + + const paginationObj = useMemo(() => { + const onChangeItemsPerPage = (pageSize: number) => { + onUpdateRowsPerPage?.(pageSize); + }; + + const onChangePage = (pageIndex: number) => + setPagination((paginationData) => ({ ...paginationData, pageIndex })); + + return isPaginationEnabled + ? { + onChangeItemsPerPage, + onChangePage, + pageIndex: pagination.pageIndex > pageCount - 1 ? 0 : pagination.pageIndex, + pageSize: pagination.pageSize, + pageSizeOptions: rowsPerPageOptions ?? getRowsPerPageOptions(pagination.pageSize), + } + : undefined; + }, [ + isPaginationEnabled, + pagination.pageIndex, + pagination.pageSize, + pageCount, + rowsPerPageOptions, + onUpdateRowsPerPage, + ]); + + useEffect(() => { + setPagination((paginationData) => + paginationData.pageSize === currentPageSize + ? paginationData + : { ...paginationData, pageSize: currentPageSize } + ); + }, [currentPageSize, setPagination]); + + /** + * Sorting + */ + const sortingColumns = useMemo(() => sort.map(([id, direction]) => ({ id, direction })), [sort]); + + const [inmemorySortingColumns, setInmemorySortingColumns] = useState([]); + const onTableSort = useCallback( + (sortingColumnsData) => { + if (isSortEnabled) { + if (isPlainRecord) { + setInmemorySortingColumns(sortingColumnsData); + } else if (onSort) { + onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction])); + } + } + }, + [onSort, isSortEnabled, isPlainRecord, setInmemorySortingColumns] + ); + + const shouldShowFieldHandler = useMemo(() => { + const dataViewFields = dataView.fields.getAll().map((fld) => fld.name); + return getShouldShowFieldHandler(dataViewFields, dataView, showMultiFields); + }, [dataView, showMultiFields]); + + /** + * Cell rendering + */ + const renderCellValue = useMemo( + () => + getRenderCellValueFn( + dataView, + displayedRows, + useNewFieldsApi, + shouldShowFieldHandler, + () => dataGridRef.current?.closeCellPopover(), + services.fieldFormats, + maxDocFieldsDisplayed, + externalCustomRenderers + ), + [ + dataView, + displayedRows, + useNewFieldsApi, + shouldShowFieldHandler, + maxDocFieldsDisplayed, + services.fieldFormats, + externalCustomRenderers, + ] + ); + + /** + * Render variables + */ + const randomId = useMemo(() => htmlIdGenerator()(), []); + const closeFieldEditor = useRef<() => void | undefined>(); + + useEffect(() => { + return () => { + if (closeFieldEditor?.current) { + closeFieldEditor?.current(); + } + }; + }, []); + + const editField = useMemo( + () => + onFieldEdited + ? (fieldName: string) => { + closeFieldEditor.current = services.dataViewFieldEditor.openEditor({ + ctx: { + dataView, + }, + fieldName, + onSave: async () => { + await onFieldEdited(); + }, + }); + } + : undefined, + [dataView, onFieldEdited, services.dataViewFieldEditor] + ); + + const visibleColumns = useMemo( + () => getVisibleColumns(displayedColumns, dataView, showTimeCol), + [dataView, displayedColumns, showTimeCol] + ); + + const getCellValue = useCallback( + (fieldName, rowIndex) => + displayedRows[rowIndex % displayedRows.length].flattened[fieldName] as Serializable, + [displayedRows] + ); + + const cellActionsFields = useMemo( + () => + cellActionsTriggerId && !isPlainRecord + ? visibleColumns.map( + (columnName) => + dataView.getFieldByName(columnName)?.toSpec() ?? { + name: '', + type: '', + aggregatable: false, + searchable: false, + } + ) + : undefined, + [cellActionsTriggerId, isPlainRecord, visibleColumns, dataView] + ); + + const columnsCellActions = useDataGridColumnsCellActions({ + fields: cellActionsFields, + getCellValue, + triggerId: cellActionsTriggerId, + dataGridRef, + }); + + const euiGridColumns = useMemo( + () => + getEuiGridColumns({ + columns: visibleColumns, + columnsCellActions, + rowsCount: displayedRows.length, + settings, + dataView, + defaultColumns, + isSortEnabled, + isPlainRecord, + services: { + uiSettings, + toastNotifications, + }, + hasEditDataViewPermission: () => dataViewFieldEditor.userPermissions.editIndexPattern(), + valueToStringConverter, + onFilter, + editField, + visibleCellActions, + }), + [ + onFilter, + visibleColumns, + columnsCellActions, + displayedRows, + dataView, + settings, + defaultColumns, + isSortEnabled, + isPlainRecord, + uiSettings, + toastNotifications, + dataViewFieldEditor, + valueToStringConverter, + editField, + visibleCellActions, + ] + ); + + const hideTimeColumn = useMemo( + () => services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), + [services.uiSettings] + ); + const schemaDetectors = useMemo(() => getSchemaDetectors(), []); + const columnsVisibility = useMemo( + () => ({ + visibleColumns, + setVisibleColumns: (newColumns: string[]) => { + onSetColumns(newColumns, hideTimeColumn); + }, + }), + [visibleColumns, hideTimeColumn, onSetColumns] + ); + const sorting = useMemo(() => { + if (isSortEnabled) { + return { + columns: isPlainRecord ? inmemorySortingColumns : sortingColumns, + onSort: onTableSort, + }; + } + return { columns: sortingColumns, onSort: () => {} }; + }, [isSortEnabled, sortingColumns, isPlainRecord, inmemorySortingColumns, onTableSort]); + + const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView); + + const leadingControlColumns = useMemo(() => { + const internalControlColumns = getLeadControlColumns(canSetExpandedDoc).filter(({ id }) => + controlColumnIds.includes(id) + ); + return externalControlColumns + ? [...internalControlColumns, ...externalControlColumns] + : internalControlColumns; + }, [canSetExpandedDoc, externalControlColumns, controlColumnIds]); + + const additionalControls = useMemo( + () => ( + <> + {usedSelectedDocs.length ? ( + + ) : null} + {externalAdditionalControls} + + ), + [usedSelectedDocs, isFilterActive, rows, externalAdditionalControls] + ); + + const showDisplaySelector = useMemo( + () => + !!onUpdateRowHeight + ? { + allowDensity: false, + allowRowHeight: true, + } + : undefined, + [onUpdateRowHeight] + ); + + const inMemory = useMemo(() => { + return isPlainRecord && columns.length + ? ({ level: 'sorting' } as EuiDataGridInMemory) + : undefined; + }, [columns.length, isPlainRecord]); + + const toolbarVisibility = useMemo( + () => + defaultColumns + ? { + ...toolbarVisibilityDefaults, + showColumnSelector: false, + showSortSelector: isSortEnabled, + additionalControls, + showDisplaySelector, + showFullScreenSelector: showFullScreenButton, + } + : { + ...toolbarVisibilityDefaults, + showSortSelector: isSortEnabled, + additionalControls, + showDisplaySelector, + showFullScreenSelector: showFullScreenButton, + }, + [defaultColumns, isSortEnabled, additionalControls, showDisplaySelector, showFullScreenButton] + ); + + const rowHeightsOptions = useRowHeightsOptions({ + rowHeightState, + onUpdateRowHeight, + storage, + configRowHeight, + consumer, + }); + + const isRenderComplete = loadingState !== DataLoadingState.loading; + + if (!rowCount && loadingState === DataLoadingState.loading) { + return ( +
    + + + + + +
    + ); + } + + if (!rowCount) { + return ( +
    + + + + + +
    + ); + } + + return ( + + +
    + +
    + {loadingState !== DataLoadingState.loading && + isPaginationEnabled && ( // we hide the footer for Surrounding Documents page + + )} + {searchTitle && ( + +

    + {searchDescription ? ( + + ) : ( + + )} +

    +
    + )} + {canSetExpandedDoc && + expandedDoc && + renderDocumentView!(expandedDoc, displayedRows, displayedColumns)} +
    +
    + ); +}; diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx new file mode 100644 index 0000000000000..7eceeda173809 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx @@ -0,0 +1,541 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { getEuiGridColumns, getVisibleColumns } from './data_table_columns'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { dataTableContextMock } from '../../__mocks__/table_context'; +import { servicesMock } from '../../__mocks__/services'; + +const columns = ['extension', 'message']; +const columnsWithTimeCol = getVisibleColumns( + ['extension', 'message'], + dataViewWithTimefieldMock, + true +) as string[]; + +describe('Data table columns', function () { + describe('getEuiGridColumns', () => { + it('returns eui grid columns showing default columns', async () => { + const actual = getEuiGridColumns({ + columns, + settings: {}, + dataView: dataViewWithTimefieldMock, + defaultColumns: true, + isSortEnabled: true, + isPlainRecord: false, + valueToStringConverter: dataTableContextMock.valueToStringConverter, + rowsCount: 100, + services: { + uiSettings: servicesMock.uiSettings, + toastNotifications: servicesMock.toastNotifications, + }, + hasEditDataViewPermission: () => + servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(), + onFilter: () => {}, + }); + expect(actual).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": false, + "showMoveLeft": false, + "showMoveRight": false, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "displayAsText": "extension", + "id": "extension", + "isSortable": false, + "schema": "string", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": false, + "showMoveLeft": false, + "showMoveRight": false, + }, + "cellActions": Array [ + [Function], + ], + "displayAsText": "message", + "id": "message", + "isSortable": false, + "schema": "string", + "visibleCellActions": undefined, + }, + ] + `); + }); + + it('returns eui grid columns with time column', async () => { + const actual = getEuiGridColumns({ + columns: columnsWithTimeCol, + settings: {}, + dataView: dataViewWithTimefieldMock, + defaultColumns: false, + isSortEnabled: true, + isPlainRecord: false, + valueToStringConverter: dataTableContextMock.valueToStringConverter, + rowsCount: 100, + services: { + uiSettings: servicesMock.uiSettings, + toastNotifications: servicesMock.toastNotifications, + }, + hasEditDataViewPermission: () => + servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(), + onFilter: () => {}, + }); + expect(actual).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": false, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "display":
    + + + timestamp + + + + +
    , + "displayAsText": "timestamp", + "id": "timestamp", + "initialWidth": 212, + "isSortable": true, + "schema": "datetime", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "displayAsText": "extension", + "id": "extension", + "isSortable": false, + "schema": "string", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + ], + "displayAsText": "message", + "id": "message", + "isSortable": false, + "schema": "string", + "visibleCellActions": undefined, + }, + ] + `); + }); + + it('returns eui grid with in memory sorting', async () => { + const actual = getEuiGridColumns({ + columns: columnsWithTimeCol, + settings: {}, + dataView: dataViewWithTimefieldMock, + defaultColumns: false, + isSortEnabled: true, + isPlainRecord: true, + valueToStringConverter: dataTableContextMock.valueToStringConverter, + rowsCount: 100, + services: { + uiSettings: servicesMock.uiSettings, + toastNotifications: servicesMock.toastNotifications, + }, + hasEditDataViewPermission: () => + servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(), + onFilter: () => {}, + }); + expect(actual).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": false, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "display":
    + + + timestamp + + + + +
    , + "displayAsText": "timestamp", + "id": "timestamp", + "initialWidth": 212, + "isSortable": true, + "schema": "datetime", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "displayAsText": "extension", + "id": "extension", + "isSortable": true, + "schema": "string", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + ], + "displayAsText": "message", + "id": "message", + "isSortable": true, + "schema": "string", + "visibleCellActions": undefined, + }, + ] + `); + }); + }); + + describe('getVisibleColumns', () => { + it('returns grid columns without time column when data view has no timestamp field', () => { + const actual = getVisibleColumns(['extension', 'message'], dataViewMock, true) as string[]; + expect(actual).toEqual(['extension', 'message']); + }); + + it('returns grid columns without time column when showTimeCol is falsy', () => { + const actual = getVisibleColumns( + ['extension', 'message'], + dataViewWithTimefieldMock, + false + ) as string[]; + expect(actual).toEqual(['extension', 'message']); + }); + + it('returns grid columns with time column when data view has timestamp field', () => { + const actual = getVisibleColumns( + ['extension', 'message'], + dataViewWithTimefieldMock, + true + ) as string[]; + expect(actual).toEqual(['timestamp', 'extension', 'message']); + }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx new file mode 100644 index 0000000000000..4b4ac622e78f6 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -0,0 +1,255 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + type EuiDataGridColumn, + type EuiDataGridColumnCellAction, + EuiIcon, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { ToastsStart, IUiSettingsClient } from '@kbn/core/public'; +import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { ExpandButton } from './data_table_expand_button'; +import { UnifiedDataTableSettings } from '../types'; +import type { ValueToStringConverter } from '../types'; +import { buildCellActions } from './default_cell_actions'; +import { getSchemaByKbnType } from './data_table_schema'; +import { SelectButton } from './data_table_document_selection'; +import { defaultTimeColumnWidth } from '../constants'; +import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; +import { buildEditFieldButton } from './build_edit_field_button'; + +const openDetails = { + id: 'openDetails', + width: 26, + headerCellRender: () => ( + + + {i18n.translate('unifiedDataTable.controlColumnHeader', { + defaultMessage: 'Control column', + })} + + + ), + rowCellRender: ExpandButton, +}; + +const select = { + id: 'select', + width: 24, + rowCellRender: SelectButton, + headerCellRender: () => ( + + + {i18n.translate('unifiedDataTable.selectColumnHeader', { + defaultMessage: 'Select column', + })} + + + ), +}; + +export function getLeadControlColumns(canSetExpandedDoc: boolean) { + if (!canSetExpandedDoc) { + return [select]; + } + return [openDetails, select]; +} + +function buildEuiGridColumn({ + columnName, + columnWidth = 0, + dataView, + defaultColumns, + isSortEnabled, + isPlainRecord, + toastNotifications, + hasEditDataViewPermission, + valueToStringConverter, + rowsCount, + onFilter, + editField, + columnCellActions, + visibleCellActions, +}: { + columnName: string; + columnWidth: number | undefined; + dataView: DataView; + defaultColumns: boolean; + isSortEnabled: boolean; + isPlainRecord?: boolean; + toastNotifications: ToastsStart; + hasEditDataViewPermission: () => boolean; + valueToStringConverter: ValueToStringConverter; + rowsCount: number; + onFilter?: DocViewFilterFn; + editField?: (fieldName: string) => void; + columnCellActions?: EuiDataGridColumnCellAction[]; + visibleCellActions?: number; +}) { + const dataViewField = dataView.getFieldByName(columnName); + const editFieldButton = + editField && + dataViewField && + buildEditFieldButton({ hasEditDataViewPermission, dataView, field: dataViewField, editField }); + const columnDisplayName = + columnName === '_source' + ? i18n.translate('unifiedDataTable.grid.documentHeader', { + defaultMessage: 'Document', + }) + : dataViewField?.displayName || columnName; + + let cellActions: EuiDataGridColumnCellAction[]; + + if (columnCellActions?.length) { + cellActions = columnCellActions; + } else { + cellActions = dataViewField + ? buildCellActions(dataViewField, toastNotifications, valueToStringConverter, onFilter) + : []; + } + + const column: EuiDataGridColumn = { + id: columnName, + schema: getSchemaByKbnType(dataViewField?.type), + isSortable: isSortEnabled && (isPlainRecord || dataViewField?.sortable === true), + displayAsText: columnDisplayName, + actions: { + showHide: + defaultColumns || columnName === dataView.timeFieldName + ? false + : { + label: i18n.translate('unifiedDataTable.removeColumnLabel', { + defaultMessage: 'Remove column', + }), + iconType: 'cross', + }, + showMoveLeft: !defaultColumns, + showMoveRight: !defaultColumns, + additional: [ + ...(columnName === '__source' + ? [] + : [ + buildCopyColumnNameButton({ + columnDisplayName, + toastNotifications, + }), + ]), + buildCopyColumnValuesButton({ + columnId: columnName, + columnDisplayName, + toastNotifications, + rowsCount, + valueToStringConverter, + }), + ...(editFieldButton ? [editFieldButton] : []), + ], + }, + cellActions, + visibleCellActions, + }; + + if (column.id === dataView.timeFieldName) { + const timeFieldName = dataViewField?.customLabel ?? dataView.timeFieldName; + const primaryTimeAriaLabel = i18n.translate( + 'unifiedDataTable.tableHeader.timeFieldIconTooltipAriaLabel', + { + defaultMessage: '{timeFieldName} - this field represents the time that events occurred.', + values: { timeFieldName }, + } + ); + const primaryTimeTooltip = i18n.translate('unifiedDataTable.tableHeader.timeFieldIconTooltip', { + defaultMessage: 'This field represents the time that events occurred.', + }); + + column.display = ( +
    + + <> + {timeFieldName} + + +
    + ); + column.initialWidth = defaultTimeColumnWidth; + } + if (columnWidth > 0) { + column.initialWidth = Number(columnWidth); + } + return column; +} + +export function getEuiGridColumns({ + columns, + columnsCellActions, + rowsCount, + settings, + dataView, + defaultColumns, + isSortEnabled, + isPlainRecord, + services, + hasEditDataViewPermission, + valueToStringConverter, + onFilter, + editField, + visibleCellActions, +}: { + columns: string[]; + columnsCellActions?: EuiDataGridColumnCellAction[][]; + rowsCount: number; + settings: UnifiedDataTableSettings | undefined; + dataView: DataView; + defaultColumns: boolean; + isSortEnabled: boolean; + isPlainRecord?: boolean; + services: { + uiSettings: IUiSettingsClient; + toastNotifications: ToastsStart; + }; + hasEditDataViewPermission: () => boolean; + valueToStringConverter: ValueToStringConverter; + onFilter: DocViewFilterFn; + editField?: (fieldName: string) => void; + visibleCellActions?: number; +}) { + const getColWidth = (column: string) => settings?.columns?.[column]?.width ?? 0; + + return columns.map((column, columnIndex) => + buildEuiGridColumn({ + columnName: column, + columnCellActions: columnsCellActions?.[columnIndex], + columnWidth: getColWidth(column), + dataView, + defaultColumns, + isSortEnabled, + isPlainRecord, + toastNotifications: services.toastNotifications, + hasEditDataViewPermission, + valueToStringConverter, + rowsCount, + onFilter, + editField, + visibleCellActions, + }) + ); +} + +export function getVisibleColumns(columns: string[], dataView: DataView, showTimeCol: boolean) { + const timeFieldName = dataView.timeFieldName; + + if (showTimeCol && timeFieldName && !columns.find((col) => col === timeFieldName)) { + return [timeFieldName, ...columns]; + } + + return columns; +} diff --git a/packages/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx new file mode 100644 index 0000000000000..ca0d422948416 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { DataTableDocumentToolbarBtn, SelectButton } from './data_table_document_selection'; +import { dataTableContextMock } from '../../__mocks__/table_context'; +import { UnifiedDataTableContext } from '../table_context'; +import { getDocId } from '@kbn/discover-utils'; + +describe('document selection', () => { + describe('getDocId', () => { + test('doc with custom routing', () => { + const doc = { + _id: 'test-id', + _index: 'test-indices', + _routing: 'why-not', + }; + expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::why-not"`); + }); + test('doc without custom routing', () => { + const doc = { + _id: 'test-id', + _index: 'test-indices', + }; + expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::"`); + }); + }); + + describe('SelectButton', () => { + test('is not checked', () => { + const contextMock = { + ...dataTableContextMock, + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + expect(checkBox.props().checked).toBeFalsy(); + }); + + test('is checked', () => { + const contextMock = { + ...dataTableContextMock, + selectedDocs: ['i::1::'], + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + expect(checkBox.props().checked).toBeTruthy(); + }); + + test('adding a selection', () => { + const contextMock = { + ...dataTableContextMock, + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + checkBox.simulate('change'); + expect(contextMock.setSelectedDocs).toHaveBeenCalledWith(['i::1::']); + }); + test('removing a selection', () => { + const contextMock = { + ...dataTableContextMock, + selectedDocs: ['i::1::'], + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + checkBox.simulate('change'); + expect(contextMock.setSelectedDocs).toHaveBeenCalledWith([]); + }); + }); + describe('DataTableDocumentToolbarBtn', () => { + test('it renders a button clickable button', () => { + const props = { + isFilterActive: false, + rows: dataTableContextMock.rows, + selectedDocs: ['i::1::'], + setIsFilterActive: jest.fn(), + setSelectedDocs: jest.fn(), + }; + const component = mountWithIntl(); + const button = findTestSubject(component, 'dscGridSelectionBtn'); + expect(button.length).toBe(1); + }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx new file mode 100644 index 0000000000000..bb0a0dc2b775a --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import classNames from 'classnames'; +import { + EuiButtonEmpty, + EuiCheckbox, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiCopy, + EuiDataGridCellValueElementProps, + EuiPopover, + EuiFlexGroup, + EuiFlexItem, + useEuiTheme, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { euiDarkVars as themeDark, euiLightVars as themeLight } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import type { DataTableRecord } from '@kbn/discover-utils/types'; +import { UnifiedDataTableContext } from '../table_context'; + +export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { + const { euiTheme } = useEuiTheme(); + const { selectedDocs, expanded, rows, isDarkMode, setSelectedDocs } = + useContext(UnifiedDataTableContext); + const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]); + const checked = useMemo(() => selectedDocs.includes(doc.id), [selectedDocs, doc.id]); + + const toggleDocumentSelectionLabel = i18n.translate('unifiedDataTable.grid.selectDoc', { + defaultMessage: `Select document '{rowNumber}'`, + values: { rowNumber: rowIndex + 1 }, + }); + + useEffect(() => { + if (expanded && doc && expanded.id === doc.id) { + setCellProps({ + style: { + backgroundColor: isDarkMode ? themeDark.euiColorHighlight : themeLight.euiColorHighlight, + }, + }); + } else { + setCellProps({ style: undefined }); + } + }, [expanded, doc, setCellProps, isDarkMode]); + + return ( + + + { + if (checked) { + const newSelection = selectedDocs.filter((docId) => docId !== doc.id); + setSelectedDocs(newSelection); + } else { + setSelectedDocs([...selectedDocs, doc.id]); + } + }} + /> + + + ); +}; + +export function DataTableDocumentToolbarBtn({ + isFilterActive, + rows, + selectedDocs, + setIsFilterActive, + setSelectedDocs, +}: { + isFilterActive: boolean; + rows: DataTableRecord[]; + selectedDocs: string[]; + setIsFilterActive: (value: boolean) => void; + setSelectedDocs: (value: string[]) => void; +}) { + const [isSelectionPopoverOpen, setIsSelectionPopoverOpen] = useState(false); + + const getMenuItems = useCallback(() => { + return [ + isFilterActive ? ( + { + setIsSelectionPopoverOpen(false); + setIsFilterActive(false); + }} + > + + + ) : ( + { + setIsSelectionPopoverOpen(false); + setIsFilterActive(true); + }} + > + + + ), + selectedDocs.includes(row.id)).map((row) => row.raw) + ) + : '' + } + > + {(copy) => ( + + + + )} + , + { + setIsSelectionPopoverOpen(false); + setSelectedDocs([]); + setIsFilterActive(false); + }} + > + + , + ]; + }, [ + isFilterActive, + rows, + selectedDocs, + setIsFilterActive, + setIsSelectionPopoverOpen, + setSelectedDocs, + ]); + + const toggleSelectionToolbar = useCallback( + () => setIsSelectionPopoverOpen((prevIsOpen) => !prevIsOpen), + [] + ); + + return ( + setIsSelectionPopoverOpen(false)} + isOpen={isSelectionPopoverOpen} + panelPaddingSize="none" + button={ + + + + } + > + {isSelectionPopoverOpen && } + + ); +} diff --git a/packages/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx new file mode 100644 index 0000000000000..56d6d3ae3ce0e --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { ExpandButton } from './data_table_expand_button'; +import { UnifiedDataTableContext } from '../table_context'; +import { dataTableContextMock } from '../../__mocks__/table_context'; + +describe('Data table view button ', function () { + it('when no document is expanded, setExpanded is called with current document', async () => { + const contextMock = { + ...dataTableContextMock, + }; + + const component = mountWithIntl( + + + + ); + const button = findTestSubject(component, 'docTableExpandToggleColumn'); + await button.simulate('click'); + expect(contextMock.setExpanded).toHaveBeenCalledWith(dataTableContextMock.rows[0]); + }); + it('when the current document is expanded, setExpanded is called with undefined', async () => { + const contextMock = { + ...dataTableContextMock, + expanded: dataTableContextMock.rows[0], + }; + + const component = mountWithIntl( + + + + ); + const button = findTestSubject(component, 'docTableExpandToggleColumn'); + await button.simulate('click'); + expect(contextMock.setExpanded).toHaveBeenCalledWith(undefined); + }); + it('when another document is expanded, setExpanded is called with the current document', async () => { + const contextMock = { + ...dataTableContextMock, + expanded: dataTableContextMock.rows[0], + }; + + const component = mountWithIntl( + + + + ); + const button = findTestSubject(component, 'docTableExpandToggleColumn'); + await button.simulate('click'); + expect(contextMock.setExpanded).toHaveBeenCalledWith(dataTableContextMock.rows[1]); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx new file mode 100644 index 0000000000000..c44ea74791b33 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; +import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import { UnifiedDataTableContext } from '../table_context'; + +/** + * Button to expand a given row + */ +export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { + const toolTipRef = useRef(null); + const [pressed, setPressed] = useState(false); + const { expanded, setExpanded, rows, isDarkMode, componentsTourSteps } = + useContext(UnifiedDataTableContext); + const current = rows[rowIndex]; + + const tourStep = componentsTourSteps ? componentsTourSteps.expandButton : undefined; + useEffect(() => { + if (current.isAnchor) { + setCellProps({ + className: 'dscDocsGrid__cell--highlight', + }); + } else if (expanded && current && expanded.id === current.id) { + setCellProps({ + style: { + backgroundColor: isDarkMode ? themeDark.euiColorHighlight : themeLight.euiColorHighlight, + }, + }); + } else { + setCellProps({ style: undefined }); + } + }, [expanded, current, setCellProps, isDarkMode]); + + const isCurrentRowExpanded = current === expanded; + const buttonLabel = i18n.translate('unifiedDataTable.grid.viewDoc', { + defaultMessage: 'Toggle dialog with details', + }); + + const testSubj = current.isAnchor + ? 'docTableExpandToggleColumnAnchor' + : 'docTableExpandToggleColumn'; + + useEffect(() => { + if (!isCurrentRowExpanded && pressed) { + setPressed(false); + setTimeout(() => { + toolTipRef.current?.hideToolTip(); + }, 100); + } + }, [isCurrentRowExpanded, setPressed, pressed]); + + if (!setExpanded) { + return null; + } + + return ( +
    + + { + const nextHit = isCurrentRowExpanded ? undefined : current; + toolTipRef.current?.hideToolTip(); + setPressed(Boolean(nextHit)); + setExpanded?.(nextHit); + }} + color={isCurrentRowExpanded ? 'primary' : 'text'} + iconType={isCurrentRowExpanded ? 'minimize' : 'expand'} + isSelected={isCurrentRowExpanded} + /> + +
    + ); +}; diff --git a/packages/kbn-unified-data-table/src/components/data_table_footer.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_footer.test.tsx new file mode 100644 index 0000000000000..d4aa0e9562f2b --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_footer.test.tsx @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { UnifiedDataTableFooter } from './data_table_footer'; +import { servicesMock } from '../../__mocks__/services'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; + +describe('UnifiedDataTableFooter', function () { + it('should not render anything when not on the last page', async () => { + const component = mountWithIntl( + + + + ); + expect(component.isEmptyRender()).toBe(true); + }); + + it('should not render anything yet when all rows shown', async () => { + const component = mountWithIntl( + + ); + expect(component.isEmptyRender()).toBe(true); + }); + + it('should render a message for the last page', async () => { + const component = mountWithIntl( + + ); + expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( + 'Search results are limited to 500 documents. Add more search terms to narrow your search.' + ); + expect(findTestSubject(component, 'dscGridSampleSizeFetchMoreLink').exists()).toBe(false); + }); + + it('should render a message and the button for the last page', async () => { + const mockLoadMore = jest.fn(); + + const component = mountWithIntl( + + ); + expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( + 'Search results are limited to 500 documents.Load more' + ); + + const button = findTestSubject(component, 'dscGridSampleSizeFetchMoreLink'); + expect(button.exists()).toBe(true); + + button.simulate('click'); + + expect(mockLoadMore).toHaveBeenCalledTimes(1); + }); + + it('should render a disabled button when loading more', async () => { + const mockLoadMore = jest.fn(); + + const component = mountWithIntl( + + ); + expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( + 'Search results are limited to 500 documents.Load more' + ); + + const button = findTestSubject(component, 'dscGridSampleSizeFetchMoreLink'); + expect(button.exists()).toBe(true); + expect(button.prop('disabled')).toBe(true); + + button.simulate('click'); + + expect(mockLoadMore).not.toHaveBeenCalled(); + }); + + it('should render a message when max total limit is reached', async () => { + const component = mountWithIntl( + + ); + expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( + 'Search results are limited to 10000 documents. Add more search terms to narrow your search.' + ); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/data_table_footer.tsx b/packages/kbn-unified-data-table/src/components/data_table_footer.tsx new file mode 100644 index 0000000000000..21819a023afed --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_footer.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButtonEmpty, EuiToolTip, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import { MAX_LOADED_GRID_ROWS } from '../constants'; + +export interface UnifiedDataTableFooterProps { + isLoadingMore?: boolean; + rowCount: number; + sampleSize: number; + pageIndex?: number; // starts from 0 + pageCount: number; + totalHits?: number; + onFetchMoreRecords?: () => void; + data: DataPublicPluginStart; + fieldFormats: FieldFormatsStart; +} + +export const UnifiedDataTableFooter: React.FC = (props) => { + const { + isLoadingMore, + rowCount, + sampleSize, + pageIndex, + pageCount, + totalHits = 0, + onFetchMoreRecords, + data, + } = props; + const timefilter = data.query.timefilter.timefilter; + const [refreshInterval, setRefreshInterval] = useState(timefilter.getRefreshInterval()); + + useEffect(() => { + const subscriber = timefilter.getRefreshIntervalUpdate$().subscribe(() => { + setRefreshInterval(timefilter.getRefreshInterval()); + }); + + return () => subscriber.unsubscribe(); + }, [timefilter, setRefreshInterval]); + + const isRefreshIntervalOn = Boolean( + refreshInterval && refreshInterval.pause === false && refreshInterval.value > 0 + ); + + const { euiTheme } = useEuiTheme(); + const isOnLastPage = pageIndex === pageCount - 1 && rowCount < totalHits; + + if (!isOnLastPage) { + return null; + } + + // allow to fetch more records for UnifiedDataTable + if (onFetchMoreRecords && typeof isLoadingMore === 'boolean') { + if (rowCount <= MAX_LOADED_GRID_ROWS - sampleSize) { + return ( + + + + + + + + ); + } + + return ; + } + + if (rowCount < totalHits) { + // show only a message for embeddable + return ; + } + + return null; +}; + +interface UnifiedDataTableFooterContainerProps extends UnifiedDataTableFooterProps { + hasButton: boolean; +} + +const UnifiedDataTableFooterContainer: React.FC = ({ + hasButton, + rowCount, + children, + fieldFormats, +}) => { + const { euiTheme } = useEuiTheme(); + + const formattedRowCount = fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(rowCount); + + return ( +

    + + {hasButton ? ( + + ) : ( + + )} + + {children} +

    + ); +}; diff --git a/packages/kbn-unified-data-table/src/components/data_table_schema.ts b/packages/kbn-unified-data-table/src/components/data_table_schema.ts new file mode 100644 index 0000000000000..ad7a15feff1ca --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/data_table_schema.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { KBN_FIELD_TYPES } from '@kbn/field-types'; +import { kibanaJSON } from '../constants'; + +export function getSchemaByKbnType(kbnType: string | undefined) { + // Default DataGrid schemas: boolean, numeric, datetime, json, currency, string + switch (kbnType) { + case KBN_FIELD_TYPES.IP: + case KBN_FIELD_TYPES.GEO_SHAPE: + case KBN_FIELD_TYPES.NUMBER: + return 'numeric'; + case KBN_FIELD_TYPES.BOOLEAN: + return 'boolean'; + case KBN_FIELD_TYPES.STRING: + return 'string'; + case KBN_FIELD_TYPES.DATE: + return 'datetime'; + default: + return kibanaJSON; + } +} + +export function getSchemaDetectors() { + return [ + { + type: kibanaJSON, + detector() { + return 0; // this schema is always explicitly defined + }, + sortTextAsc: '', + sortTextDesc: '', + icon: '', + color: '', + }, + ]; +} diff --git a/packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx b/packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx new file mode 100644 index 0000000000000..6ecaa850da6a1 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +const mockCopyToClipboard = jest.fn((value) => true); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + copyToClipboard: (value: string) => mockCopyToClipboard(value), + }; +}); + +import React from 'react'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { + FilterInBtn, + FilterOutBtn, + buildCellActions, + buildCopyValueButton, +} from './default_cell_actions'; +import { servicesMock } from '../../__mocks__/services'; +import { UnifiedDataTableContext } from '../table_context'; +import { EuiButton, EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { dataTableContextMock } from '../../__mocks__/table_context'; +import { DataViewField } from '@kbn/data-views-plugin/public'; + +describe('Default cell actions ', function () { + const CopyBtn = buildCopyValueButton( + { + Component: () => <>, + colIndex: 0, + columnId: 'extension', + } as unknown as EuiDataGridColumnCellActionProps, + servicesMock.toastNotifications, + dataTableContextMock.valueToStringConverter + ); + + it('should not show cell actions for unfilterable fields', async () => { + const cellActions = buildCellActions( + { name: 'foo', filterable: false } as DataViewField, + servicesMock.toastNotifications, + dataTableContextMock.valueToStringConverter + ); + expect(cellActions.length).toEqual(1); + expect( + cellActions[0]({ + Component: () => <>, + colIndex: 1, + columnId: 'extension', + } as unknown as EuiDataGridColumnCellActionProps).props['aria-label'] + ).toEqual(CopyBtn.props['aria-label']); + }); + + it('should show filter actions for filterable fields', async () => { + const cellActions = buildCellActions( + { name: 'foo', filterable: true } as DataViewField, + servicesMock.toastNotifications, + dataTableContextMock.valueToStringConverter, + jest.fn() + ); + expect(cellActions).toContain(FilterInBtn); + expect(cellActions).toContain(FilterOutBtn); + }); + + it('should show Copy action for _source field', async () => { + const cellActions = buildCellActions( + { name: '_source', type: '_source', filterable: false } as DataViewField, + servicesMock.toastNotifications, + dataTableContextMock.valueToStringConverter + ); + expect( + cellActions[0]({ + Component: () => <>, + colIndex: 1, + columnId: 'extension', + } as unknown as EuiDataGridColumnCellActionProps).props['aria-label'] + ).toEqual(CopyBtn.props['aria-label']); + }); + + it('triggers filter function when FilterInBtn is clicked', async () => { + const component = mountWithIntl( + + } + rowIndex={1} + colIndex={1} + columnId="extension" + isExpanded={false} + /> + + ); + const button = findTestSubject(component, 'filterForButton'); + await button.simulate('click'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + dataTableContextMock.dataView.fields.getByName('extension'), + 'jpg', + '+' + ); + }); + it('triggers filter function when FilterInBtn is clicked for a non-provided value', async () => { + const component = mountWithIntl( + + } + rowIndex={0} + colIndex={1} + columnId="extension" + isExpanded={false} + /> + + ); + const button = findTestSubject(component, 'filterForButton'); + await button.simulate('click'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + dataTableContextMock.dataView.fields.getByName('extension'), + undefined, + '+' + ); + }); + it('triggers filter function when FilterInBtn is clicked for an empty string value', async () => { + const component = mountWithIntl( + + } + rowIndex={4} + colIndex={1} + columnId="message" + isExpanded={false} + /> + + ); + const button = findTestSubject(component, 'filterForButton'); + await button.simulate('click'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + dataTableContextMock.dataView.fields.getByName('message'), + '', + '+' + ); + }); + it('triggers filter function when FilterOutBtn is clicked', async () => { + const component = mountWithIntl( + + } + rowIndex={1} + colIndex={1} + columnId="extension" + isExpanded={false} + /> + + ); + const button = findTestSubject(component, 'filterOutButton'); + await button.simulate('click'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + dataTableContextMock.dataView.fields.getByName('extension'), + 'jpg', + '-' + ); + }); + it('triggers clipboard copy when CopyBtn is clicked', async () => { + const component = mountWithIntl( + + {buildCopyValueButton( + { + Component: (props: any) => , + colIndex: 1, + rowIndex: 1, + columnId: 'extension', + } as unknown as EuiDataGridColumnCellActionProps, + servicesMock.toastNotifications, + dataTableContextMock.valueToStringConverter + )} + + ); + const button = findTestSubject(component, 'copyClipboardButton'); + await button.simulate('click'); + expect(mockCopyToClipboard).toHaveBeenCalledWith('jpg'); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx b/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx new file mode 100644 index 0000000000000..6005d75ea6632 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useContext } from 'react'; +import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { DataViewField } from '@kbn/data-views-plugin/public'; +import { ToastsStart } from '@kbn/core/public'; +import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { UnifiedDataTableContext, DataTableContext } from '../table_context'; +import { copyValueToClipboard } from '../utils/copy_value_to_clipboard'; +import { ValueToStringConverter } from '../types'; + +function onFilterCell( + context: DataTableContext, + rowIndex: EuiDataGridColumnCellActionProps['rowIndex'], + columnId: EuiDataGridColumnCellActionProps['columnId'], + mode: '+' | '-' +) { + const row = context.rows[rowIndex]; + const value = row.flattened[columnId]; + const field = context.dataView.fields.getByName(columnId); + + if (field && context.onFilter) { + context.onFilter(field, value, mode); + } +} + +export const FilterInBtn = ({ + Component, + rowIndex, + columnId, +}: EuiDataGridColumnCellActionProps) => { + const context = useContext(UnifiedDataTableContext); + const buttonTitle = i18n.translate('unifiedDataTable.grid.filterForAria', { + defaultMessage: 'Filter for this {value}', + values: { value: columnId }, + }); + + return ( + { + onFilterCell(context, rowIndex, columnId, '+'); + }} + iconType="plusInCircle" + aria-label={buttonTitle} + title={buttonTitle} + data-test-subj="filterForButton" + > + {i18n.translate('unifiedDataTable.grid.filterFor', { + defaultMessage: 'Filter for', + })} + + ); +}; + +export const FilterOutBtn = ({ + Component, + rowIndex, + columnId, +}: EuiDataGridColumnCellActionProps) => { + const context = useContext(UnifiedDataTableContext); + const buttonTitle = i18n.translate('unifiedDataTable.grid.filterOutAria', { + defaultMessage: 'Filter out this {value}', + values: { value: columnId }, + }); + + return ( + { + onFilterCell(context, rowIndex, columnId, '-'); + }} + iconType="minusInCircle" + aria-label={buttonTitle} + title={buttonTitle} + data-test-subj="filterOutButton" + > + {i18n.translate('unifiedDataTable.grid.filterOut', { + defaultMessage: 'Filter out', + })} + + ); +}; + +export function buildCopyValueButton( + { Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps, + toastNotifications: ToastsStart, + valueToStringConverter: ValueToStringConverter +) { + const buttonTitle = i18n.translate('unifiedDataTable.grid.copyClipboardButtonTitle', { + defaultMessage: 'Copy value of {column}', + values: { column: columnId }, + }); + + return ( + { + copyValueToClipboard({ + rowIndex, + columnId, + valueToStringConverter, + toastNotifications, + }); + }} + iconType="copyClipboard" + aria-label={buttonTitle} + title={buttonTitle} + data-test-subj="copyClipboardButton" + > + {i18n.translate('unifiedDataTable.grid.copyCellValueButton', { + defaultMessage: 'Copy value', + })} + + ); +} + +export function buildCellActions( + field: DataViewField, + toastNotifications: ToastsStart, + valueToStringConverter: ValueToStringConverter, + onFilter?: DocViewFilterFn +) { + return [ + ...(onFilter && field.filterable ? [FilterInBtn, FilterOutBtn] : []), + ({ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps) => + buildCopyValueButton( + { Component, rowIndex, columnId } as EuiDataGridColumnCellActionProps, + toastNotifications, + valueToStringConverter + ), + ]; +} diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap b/packages/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap new file mode 100644 index 0000000000000..7af546298e0d8 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`returns the \`JsonCodeEditor\` component 1`] = ` + +`; diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.scss b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.scss new file mode 100644 index 0000000000000..a07f7ccac408d --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.scss @@ -0,0 +1,3 @@ +.unifiedDataTableJsonEditor { + width: 100%; +} diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx new file mode 100644 index 0000000000000..e1ec1373f8657 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import JsonCodeEditor from './json_code_editor'; + +it('returns the `JsonCodeEditor` component', () => { + const value = { + _index: 'test', + _type: 'doc', + _id: 'foo', + _score: 1, + _source: { test: 123 }, + }; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.tsx b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.tsx new file mode 100644 index 0000000000000..d08e35eb6d4bf --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import './json_code_editor.scss'; + +import React from 'react'; +import { JsonCodeEditorCommon } from './json_code_editor_common'; + +export interface JsonCodeEditorProps { + json: Record; + width?: string | number; + height?: string | number; + hasLineNumbers?: boolean; +} + +// Required for usage in React.lazy +// eslint-disable-next-line import/no-default-export +export default function JsonCodeEditor({ + json, + width, + height, + hasLineNumbers, +}: JsonCodeEditorProps) { + const jsonValue = JSON.stringify(json, null, 2); + + return ( + void 0} + hideCopyButton={true} + /> + ); +} diff --git a/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx new file mode 100644 index 0000000000000..079a98c305459 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/json_code_editor/json_code_editor_common.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import './json_code_editor.scss'; + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { monaco, XJsonLang } from '@kbn/monaco'; +import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { CodeEditor } from '@kbn/code-editor'; +const codeEditorAriaLabel = i18n.translate('unifiedDataTable.json.codeEditorAriaLabel', { + defaultMessage: 'Read only JSON view of an elasticsearch document', +}); +const copyToClipboardLabel = i18n.translate('unifiedDataTable.json.copyToClipboardLabel', { + defaultMessage: 'Copy to clipboard', +}); + +interface JsonCodeEditorCommonProps { + jsonValue: string; + onEditorDidMount: (editor: monaco.editor.IStandaloneCodeEditor) => void; + width?: string | number; + height?: string | number; + hasLineNumbers?: boolean; + hideCopyButton?: boolean; +} + +export const JsonCodeEditorCommon = ({ + jsonValue, + width, + height, + hasLineNumbers, + onEditorDidMount, + hideCopyButton, +}: JsonCodeEditorCommonProps) => { + if (jsonValue === '') { + return null; + } + + const codeEditor = ( + + ); + if (hideCopyButton) { + return codeEditor; + } + return ( + + + +
    + + {(copy) => ( + + {copyToClipboardLabel} + + )} + +
    +
    + {codeEditor} +
    + ); +}; + +export const JSONCodeEditorCommonMemoized = React.memo((props: JsonCodeEditorCommonProps) => { + return ; +}); diff --git a/packages/kbn-unified-data-table/src/constants.ts b/packages/kbn-unified-data-table/src/constants.ts new file mode 100644 index 0000000000000..6b5dda5ca54b8 --- /dev/null +++ b/packages/kbn-unified-data-table/src/constants.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { EuiDataGridStyle } from '@elastic/eui'; + +export const DEFAULT_ROWS_PER_PAGE = 100; +export const MAX_LOADED_GRID_ROWS = 10000; + +export const ROWS_PER_PAGE_OPTIONS = [10, 25, 50, DEFAULT_ROWS_PER_PAGE, 250, 500]; +/** + * Row height might be a value from -1 to 20 + * A value of -1 automatically adjusts the row height to fit the contents. + * A value of 0 displays the content in a single line. + * A value from 1 to 20 represents number of lines of Document explorer row to display. + */ +export const ROWS_HEIGHT_OPTIONS = { + auto: -1, + single: 0, + default: -1, +}; + +export const defaultMonacoEditorWidth = 370; +export const defaultTimeColumnWidth = 212; +export const kibanaJSON = 'kibana-json'; + +export const GRID_STYLE: EuiDataGridStyle = { + border: 'horizontal', + fontSize: 's', + cellPadding: 'l', + rowHover: 'highlight', + header: 'underline', + stripes: true, +}; + +export const toolbarVisibility = { + showColumnSelector: { + allowHide: false, + allowReorder: true, + }, +}; diff --git a/src/plugins/discover/public/hooks/use_data_grid_columns.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx similarity index 94% rename from src/plugins/discover/public/hooks/use_data_grid_columns.test.tsx rename to packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx index 4a1874ec8e940..e0afdab4ff043 100644 --- a/src/plugins/discover/public/hooks/use_data_grid_columns.test.tsx +++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx @@ -9,8 +9,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { useColumns } from './use_data_grid_columns'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { configMock } from '../__mocks__/config'; -import { dataViewsMock } from '../__mocks__/data_views'; +import { configMock } from '../../__mocks__/config'; +import { dataViewsMock } from '../../__mocks__/data_views'; import { Capabilities } from '@kbn/core/types'; describe('useColumns', () => { diff --git a/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts new file mode 100644 index 0000000000000..088f7b0491c69 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useEffect, useMemo, useState } from 'react'; +import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; + +import { Capabilities } from '@kbn/core/public'; +import { isEqual } from 'lodash'; +import { getStateColumnActions } from '../components/actions/columns'; + +interface UseColumnsProps { + capabilities: Capabilities; + dataView: DataView; + dataViews: DataViewsContract; + useNewFieldsApi: boolean; + setAppState: (state: { columns: string[]; sort?: string[][] }) => void; + columns?: string[]; + sort?: string[][]; + defaultOrder?: string; +} + +export const useColumns = ({ + capabilities, + dataView, + dataViews, + setAppState, + useNewFieldsApi, + columns, + sort, + defaultOrder = 'desc', +}: UseColumnsProps) => { + const [usedColumns, setUsedColumns] = useState(getColumns(columns, useNewFieldsApi)); + useEffect(() => { + const nextColumns = getColumns(columns, useNewFieldsApi); + if (isEqual(usedColumns, nextColumns)) { + return; + } + setUsedColumns(nextColumns); + }, [columns, useNewFieldsApi, usedColumns]); + const { onAddColumn, onRemoveColumn, onSetColumns, onMoveColumn } = useMemo( + () => + getStateColumnActions({ + capabilities, + dataView, + dataViews, + setAppState, + useNewFieldsApi, + columns: usedColumns, + sort, + defaultOrder, + }), + [ + capabilities, + dataView, + dataViews, + defaultOrder, + setAppState, + sort, + useNewFieldsApi, + usedColumns, + ] + ); + + return { + columns: usedColumns, + onAddColumn, + onRemoveColumn, + onMoveColumn, + onSetColumns, + }; +}; + +function getColumns(columns: string[] | undefined, useNewFieldsApi: boolean) { + if (!columns) { + return []; + } + return useNewFieldsApi ? columns.filter((col) => col !== '_source') : columns; +} diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx new file mode 100644 index 0000000000000..1ef0d9c70d139 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { LocalStorageMock } from '../../__mocks__/local_storage_mock'; +import { useRowHeightsOptions } from './use_row_heights_options'; + +describe('useRowHeightsOptions', () => { + test('should apply rowHeight from savedSearch', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + rowHeightState: 2, + storage: new LocalStorageMock({}) as unknown as Storage, + consumer: 'discover', + }); + }); + + expect(result.current.defaultHeight).toEqual({ lineCount: 2 }); + }); + + test('should apply rowHeight from local storage', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + storage: new LocalStorageMock({ + ['discover:dataGridRowHeight']: { + previousRowHeight: 5, + previousConfigRowHeight: -1, + }, + }) as unknown as Storage, + consumer: 'discover', + }); + }); + + expect(result.current.defaultHeight).toEqual({ lineCount: 5 }); + }); + + test('should apply rowHeight from configRowHeight', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + consumer: 'discover', + configRowHeight: 3, + storage: new LocalStorageMock({}) as unknown as Storage, + }); + }); + + expect(result.current.defaultHeight).toEqual({ + lineCount: 3, + }); + }); + + test('should apply rowHeight from uiSettings instead of local storage value, since uiSettings has been changed', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + storage: new LocalStorageMock({ + ['discover:dataGridRowHeight']: { + previousRowHeight: 5, + // different from uiSettings (config), now user changed it to -1, but prev was 4 + previousConfigRowHeight: 4, + }, + }) as unknown as Storage, + consumer: 'discover', + }); + }); + + expect(result.current.defaultHeight).toEqual('auto'); + }); +}); diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts new file mode 100644 index 0000000000000..727677a42e7df --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { EuiDataGridRowHeightOption, EuiDataGridRowHeightsOptions } from '@elastic/eui'; +import type { Storage } from '@kbn/kibana-utils-plugin/public'; +import { useMemo } from 'react'; +import { isValidRowHeight } from '../utils/validate_row_height'; +import { + DataGridOptionsRecord, + getStoredRowHeight, + updateStoredRowHeight, +} from '../utils/row_heights'; +import { ROWS_HEIGHT_OPTIONS } from '../constants'; + +interface UseRowHeightProps { + rowHeightState?: number; + onUpdateRowHeight?: (rowHeight: number) => void; + storage: Storage; + configRowHeight?: number; + consumer: string; +} + +/** + * Converts rowHeight of EuiDataGrid to rowHeight number (-1 to 20) + */ +const serializeRowHeight = (rowHeight?: EuiDataGridRowHeightOption): number => { + if (rowHeight === 'auto' || rowHeight === ROWS_HEIGHT_OPTIONS.auto) { + return ROWS_HEIGHT_OPTIONS.auto; + } else if (typeof rowHeight === 'object' && rowHeight.lineCount) { + return rowHeight.lineCount; // custom + } + + return ROWS_HEIGHT_OPTIONS.single; +}; + +/** + * Converts rowHeight number (-1 to 20) of EuiDataGrid rowHeight + */ +const deserializeRowHeight = (number: number): EuiDataGridRowHeightOption | undefined => { + if (number === ROWS_HEIGHT_OPTIONS.auto) { + return 'auto'; + } else if (number === ROWS_HEIGHT_OPTIONS.single) { + return undefined; + } + + return { lineCount: number }; // custom +}; + +export const useRowHeightsOptions = ({ + rowHeightState, + onUpdateRowHeight, + storage, + configRowHeight = ROWS_HEIGHT_OPTIONS.default, + consumer, +}: UseRowHeightProps) => { + return useMemo((): EuiDataGridRowHeightsOptions => { + const rowHeightFromLS = getStoredRowHeight(storage, consumer); + + const configHasNotChanged = ( + localStorageRecord: DataGridOptionsRecord | null + ): localStorageRecord is DataGridOptionsRecord => + localStorageRecord !== null && configRowHeight === localStorageRecord.previousConfigRowHeight; + + let rowHeight; + if (isValidRowHeight(rowHeightState)) { + rowHeight = rowHeightState; + } else if (configHasNotChanged(rowHeightFromLS)) { + rowHeight = rowHeightFromLS.previousRowHeight; + } else { + rowHeight = configRowHeight; + } + + const defaultHeight = deserializeRowHeight(rowHeight); + + return { + defaultHeight, + lineHeight: '1.6em', + onChange: ({ defaultHeight: newRowHeight }: EuiDataGridRowHeightsOptions) => { + const newSerializedRowHeight = serializeRowHeight( + // pressing "Reset to default" triggers onChange with the same value + newRowHeight === defaultHeight ? configRowHeight : newRowHeight + ); + updateStoredRowHeight(newSerializedRowHeight, configRowHeight, storage, consumer); + onUpdateRowHeight?.(newSerializedRowHeight); + }, + }; + }, [storage, consumer, rowHeightState, configRowHeight, onUpdateRowHeight]); +}; diff --git a/packages/kbn-unified-data-table/src/table_context.tsx b/packages/kbn-unified-data-table/src/table_context.tsx new file mode 100644 index 0000000000000..2a1d4656d4a65 --- /dev/null +++ b/packages/kbn-unified-data-table/src/table_context.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { DataTableRecord } from '@kbn/discover-utils/types'; +import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import type { ValueToStringConverter } from './types'; + +export interface DataTableContext { + expanded?: DataTableRecord | undefined; + setExpanded?: (hit?: DataTableRecord) => void; + rows: DataTableRecord[]; + onFilter?: DocViewFilterFn; + dataView: DataView; + isDarkMode: boolean; + selectedDocs: string[]; + setSelectedDocs: (selected: string[]) => void; + valueToStringConverter: ValueToStringConverter; + componentsTourSteps?: Record; +} + +const defaultContext = {} as unknown as DataTableContext; + +export const UnifiedDataTableContext = React.createContext(defaultContext); diff --git a/packages/kbn-unified-data-table/src/types.ts b/packages/kbn-unified-data-table/src/types.ts new file mode 100644 index 0000000000000..79ca4e721e910 --- /dev/null +++ b/packages/kbn-unified-data-table/src/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * User configurable state of data grid, persisted in saved search + */ +export interface UnifiedDataTableSettings { + columns?: Record; +} + +export interface UnifiedDataTableSettingsColumn { + width?: number; +} + +export type ValueToStringConverter = ( + rowIndex: number, + columnId: string, + options?: { compatibleWithCSV?: boolean } +) => { formattedString: string; withFormula: boolean }; diff --git a/packages/kbn-unified-data-table/src/utils/columns.test.ts b/packages/kbn-unified-data-table/src/utils/columns.test.ts new file mode 100644 index 0000000000000..36a8b60a6bc68 --- /dev/null +++ b/packages/kbn-unified-data-table/src/utils/columns.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { getDisplayedColumns } from './columns'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; + +describe('getDisplayedColumns', () => { + test('returns default columns given a data view without timefield', async () => { + const result = getDisplayedColumns([], dataViewMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "_source", + ] + `); + }); + test('returns default columns given a data view with timefield', async () => { + const result = getDisplayedColumns([], dataViewWithTimefieldMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "_source", + ] + `); + }); + test('returns default columns when just timefield is in state', async () => { + const result = getDisplayedColumns(['timestamp'], dataViewWithTimefieldMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "_source", + ] + `); + }); + test('returns columns given by argument, no fallback ', async () => { + const result = getDisplayedColumns(['test'], dataViewWithTimefieldMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "test", + ] + `); + }); + test('returns the same instance of ["_source"] over multiple calls', async () => { + const result = getDisplayedColumns([], dataViewWithTimefieldMock); + const result2 = getDisplayedColumns([], dataViewWithTimefieldMock); + expect(result).toBe(result2); + }); +}); diff --git a/packages/kbn-unified-data-table/src/utils/columns.ts b/packages/kbn-unified-data-table/src/utils/columns.ts new file mode 100644 index 0000000000000..f2a72f0a8b650 --- /dev/null +++ b/packages/kbn-unified-data-table/src/utils/columns.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { DataView } from '@kbn/data-views-plugin/public'; + +// We store this outside the function as a constant, so we're not creating a new array every time +// the function is returning this. A changing array might cause the data grid to think it got +// new columns, and thus performing worse than using the same array over multiple renders. +const SOURCE_ONLY = ['_source']; + +/** + * Function to provide fallback when + * 1) no columns are given + * 2) Just one column is given, which is the configured timefields + */ +export function getDisplayedColumns(stateColumns: string[] = [], dataView: DataView) { + return stateColumns && + stateColumns.length > 0 && + // check if all columns where removed except the configured timeField (this can't be removed) + !(stateColumns.length === 1 && stateColumns[0] === dataView.timeFieldName) + ? stateColumns + : SOURCE_ONLY; +} diff --git a/packages/kbn-unified-data-table/src/utils/convert_value_to_string.test.tsx b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.test.tsx new file mode 100644 index 0000000000000..aa8ba719c5ba2 --- /dev/null +++ b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.test.tsx @@ -0,0 +1,585 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { dataTableContextComplexMock, dataTableContextMock } from '../../__mocks__/table_context'; +import { servicesMock } from '../../__mocks__/services'; +import { convertValueToString, convertNameToString } from './convert_value_to_string'; + +describe('convertValueToString', () => { + it('should convert a keyword value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'keyword_key', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('abcd1'); + }); + + it('should convert a text value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'text_message', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"Hi there! I am a sample string."'); + }); + + it('should convert a text value to text (not for CSV)', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'text_message', + rowIndex: 0, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe('Hi there! I am a sample string.'); + }); + + it('should convert a multiline text value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'text_message', + rowIndex: 1, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"I\'m multiline\n*&%$#@"'); + expect(result.withFormula).toBe(false); + }); + + it('should convert a number value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'number_price', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('10.99'); + }); + + it('should convert a date value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'date', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"2022-05-22T12:10:30.000Z"'); + }); + + it('should convert a date nanos value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'date_nanos', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"2022-01-01T12:10:30.123456789Z"'); + }); + + it('should convert a date nanos value to text (not for CSV)', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'date_nanos', + rowIndex: 0, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe('2022-01-01T12:10:30.123456789Z'); + }); + + it('should convert a boolean value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'bool_enabled', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('false'); + }); + + it('should convert a binary value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'binary_blob', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"U29tZSBiaW5hcnkgYmxvYg=="'); + }); + + it('should convert a binary value to text (not for CSV)', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'binary_blob', + rowIndex: 0, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe('U29tZSBiaW5hcnkgYmxvYg=='); + }); + + it('should convert an object value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'object_user.first', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('John'); + }); + + it('should convert a nested value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'nested_user', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe( + '{"last":["Smith"],"last.keyword":["Smith"],"first":["John"],"first.keyword":["John"]}, {"last":["White"],"last.keyword":["White"],"first":["Alice"],"first.keyword":["Alice"]}' + ); + }); + + it('should convert a flattened value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'flattened_labels', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('{"release":["v1.2.5","v1.3.0"],"priority":"urgent"}'); + }); + + it('should convert a range value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'range_time_frame', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe( + '{"gte":"2015-10-31 12:00:00","lte":"2015-11-01 00:00:00"}' + ); + }); + + it('should convert a rank features value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'rank_features', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('{"2star":100,"1star":10}'); + }); + + it('should convert a histogram value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'histogram', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('{"counts":[3,7,23,12,6],"values":[0.1,0.2,0.3,0.4,0.5]}'); + }); + + it('should convert a IP value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'ip_addr', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"192.168.1.1"'); + }); + + it('should convert a IP value to text (not for CSV)', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'ip_addr', + rowIndex: 0, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe('192.168.1.1'); + }); + + it('should convert a version value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'version', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"1.2.3"'); + }); + + it('should convert a version value to text (not for CSV)', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'version', + rowIndex: 0, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe('1.2.3'); + }); + + it('should convert a vector value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'vector', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('0.5, 10, 6'); + }); + + it('should convert a geo point value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'geo_point', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('{"coordinates":[-71.34,41.12],"type":"Point"}'); + }); + + it('should convert a geo point object value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'geo_point', + rowIndex: 1, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('{"coordinates":[-71.34,41.12],"type":"Point"}'); + }); + + it('should convert an array value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'array_tags', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('elasticsearch, wow'); + }); + + it('should convert a shape value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'geometry', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe( + '{"coordinates":[[[1000,-1001],[1001,-1001],[1001,-1000],[1000,-1000],[1000,-1001]]],"type":"Polygon"}' + ); + }); + + it('should convert a runtime value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'runtime_number', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('5.5'); + }); + + it('should convert a scripted value to text', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'scripted_string', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"hi there"'); + }); + + it('should convert a scripted value to text (not for CSV)', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'scripted_string', + rowIndex: 0, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe('hi there'); + }); + + it('should return an empty string and not fail', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'unknown', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe(''); + }); + + it('should return an empty string when rowIndex is out of range', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'unknown', + rowIndex: -1, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe(''); + }); + + it('should return _source value', () => { + const result = convertValueToString({ + rows: dataTableContextMock.rows, + dataView: dataTableContextMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: '_source', + rowIndex: 0, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe( + '{\n' + + ' "bytes": 20,\n' + + ' "date": "2020-20-01T12:12:12.123",\n' + + ' "message": "test1",\n' + + ' "_index": "i",\n' + + ' "_score": 1\n' + + '}' + ); + }); + + it('should return a formatted _source value', () => { + const result = convertValueToString({ + rows: dataTableContextMock.rows, + dataView: dataTableContextMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: '_source', + rowIndex: 0, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe( + '{"bytes":20,"date":"2020-20-01T12:12:12.123","message":"test1","_index":"i","_score":1}' + ); + }); + + it('should escape formula', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'array_tags', + rowIndex: 1, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result.formattedString).toBe('"\'=1+2\'"" ;,=1+2"'); + expect(result.withFormula).toBe(true); + + const result2 = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'scripted_string', + rowIndex: 1, + options: { + compatibleWithCSV: true, + }, + }); + + expect(result2.formattedString).toBe('"\'=1+2"";=1+2"'); + expect(result2.withFormula).toBe(true); + }); + + it('should not escape formulas when not for CSV', () => { + const result = convertValueToString({ + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, + columnId: 'array_tags', + rowIndex: 1, + options: { + compatibleWithCSV: false, + }, + }); + + expect(result.formattedString).toBe('=1+2\'" ;,=1+2'); + expect(result.withFormula).toBe(true); + }); + + it('should return a formatted name', () => { + const result = convertNameToString('test'); + + expect(result.formattedString).toBe('test'); + expect(result.withFormula).toBe(false); + }); + + it('should return a formatted name when with a formula', () => { + const result = convertNameToString('=1+2";=1+2'); + + expect(result.formattedString).toBe('"\'=1+2"";=1+2"'); + expect(result.withFormula).toBe(true); + }); +}); diff --git a/src/plugins/discover/public/utils/convert_value_to_string.ts b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.ts similarity index 95% rename from src/plugins/discover/public/utils/convert_value_to_string.ts rename to packages/kbn-unified-data-table/src/utils/convert_value_to_string.ts index 605c7912b17f1..486ed5574dbf2 100644 --- a/src/plugins/discover/public/utils/convert_value_to_string.ts +++ b/packages/kbn-unified-data-table/src/utils/convert_value_to_string.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { DataView } from '@kbn/data-views-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; import { cellHasFormulas, createEscapeValue } from '@kbn/data-plugin/common'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { formatFieldValue } from '@kbn/discover-utils'; diff --git a/src/plugins/discover/public/utils/copy_value_to_clipboard.test.tsx b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx similarity index 77% rename from src/plugins/discover/public/utils/copy_value_to_clipboard.test.tsx rename to packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx index 595b7601b6f65..7ff5c9b3f19b6 100644 --- a/src/plugins/discover/public/utils/copy_value_to_clipboard.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { discoverGridContextComplexMock } from '../__mocks__/grid_context'; -import { discoverServiceMock } from '../__mocks__/services'; +import { dataTableContextComplexMock } from '../../__mocks__/table_context'; +import { servicesMock } from '../../__mocks__/services'; import { copyValueToClipboard, copyColumnNameToClipboard, @@ -22,9 +22,9 @@ const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); describe('copyValueToClipboard', () => { const valueToStringConverter: ValueToStringConverter = (rowIndex, columnId, options) => convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, + rows: dataTableContextComplexMock.rows, + dataView: dataTableContextComplexMock.dataView, + fieldFormats: servicesMock.fieldFormats, rowIndex, columnId, options, @@ -39,6 +39,10 @@ describe('copyValueToClipboard', () => { }, writable: true, }); + Object.defineProperty(window, 'sessionStorage', { + value: { clear: jest.fn() }, + writable: true, + }); }); afterAll(() => { @@ -50,7 +54,7 @@ describe('copyValueToClipboard', () => { it('should copy a value to clipboard', () => { execCommandMock.mockImplementationOnce(() => true); const result = copyValueToClipboard({ - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, columnId: 'keyword_key', rowIndex: 0, valueToStringConverter, @@ -59,7 +63,7 @@ describe('copyValueToClipboard', () => { expect(result).toBe('abcd1'); expect(execCommandMock).toHaveBeenCalledWith('copy'); expect(warn).not.toHaveBeenCalled(); - expect(discoverServiceMock.toastNotifications.addInfo).toHaveBeenCalledWith({ + expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({ title: 'Copied to clipboard', }); }); @@ -68,7 +72,7 @@ describe('copyValueToClipboard', () => { execCommandMock.mockImplementationOnce(() => false); const result = copyValueToClipboard({ - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, columnId: 'keyword_key', rowIndex: 0, valueToStringConverter, @@ -77,7 +81,7 @@ describe('copyValueToClipboard', () => { expect(result).toBe(null); expect(execCommandMock).toHaveBeenCalledWith('copy'); expect(warn).toHaveBeenCalledWith('Unable to copy to clipboard.'); - expect(discoverServiceMock.toastNotifications.addWarning).toHaveBeenCalledWith({ + expect(servicesMock.toastNotifications.addWarning).toHaveBeenCalledWith({ title: 'Unable to copy to clipboard in this browser', }); }); @@ -85,13 +89,13 @@ describe('copyValueToClipboard', () => { it('should copy a column name to clipboard', () => { execCommandMock.mockImplementationOnce(() => true); const result = copyColumnNameToClipboard({ - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, columnDisplayName: 'text_message', }); expect(result).toBe('"text_message"'); expect(execCommandMock).toHaveBeenCalledWith('copy'); - expect(discoverServiceMock.toastNotifications.addInfo).toHaveBeenCalledWith({ + expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({ title: 'Copied to clipboard', }); }); @@ -99,14 +103,14 @@ describe('copyValueToClipboard', () => { it('should inform when copy a column name to clipboard failed', () => { execCommandMock.mockImplementationOnce(() => false); const result = copyColumnNameToClipboard({ - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, columnDisplayName: 'text_message', }); expect(result).toBe(null); expect(execCommandMock).toHaveBeenCalledWith('copy'); expect(warn).toHaveBeenCalledWith('Unable to copy to clipboard.'); - expect(discoverServiceMock.toastNotifications.addWarning).toHaveBeenCalledWith({ + expect(servicesMock.toastNotifications.addWarning).toHaveBeenCalledWith({ title: 'Unable to copy to clipboard in this browser', }); }); @@ -115,7 +119,7 @@ describe('copyValueToClipboard', () => { execCommandMock.mockImplementationOnce(() => true); const result = await copyColumnValuesToClipboard({ - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, columnId: 'bool_enabled', columnDisplayName: 'custom_bool_enabled', rowsCount: 2, @@ -126,7 +130,7 @@ describe('copyValueToClipboard', () => { expect(global.window.navigator.clipboard.writeText).toHaveBeenCalledWith( '"custom_bool_enabled"\nfalse\ntrue' ); - expect(discoverServiceMock.toastNotifications.addInfo).toHaveBeenCalledWith({ + expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({ title: 'Values of "custom_bool_enabled" column copied to clipboard', }); }); @@ -134,7 +138,7 @@ describe('copyValueToClipboard', () => { it('should copy column values to clipboard with a warning', async () => { execCommandMock.mockImplementationOnce(() => true); const result = await copyColumnValuesToClipboard({ - toastNotifications: discoverServiceMock.toastNotifications, + toastNotifications: servicesMock.toastNotifications, columnId: 'scripted_string', columnDisplayName: 'custom_scripted_string', rowsCount: 2, @@ -142,7 +146,7 @@ describe('copyValueToClipboard', () => { }); expect(result).toBe('"custom_scripted_string"\n"hi there"\n"\'=1+2"";=1+2"'); - expect(discoverServiceMock.toastNotifications.addWarning).toHaveBeenCalledWith({ + expect(servicesMock.toastNotifications.addWarning).toHaveBeenCalledWith({ title: 'Values of "custom_scripted_string" column copied to clipboard', text: 'Values may contain formulas that are escaped.', }); diff --git a/src/plugins/discover/public/utils/copy_value_to_clipboard.ts b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts similarity index 89% rename from src/plugins/discover/public/utils/copy_value_to_clipboard.ts rename to packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts index c700fa748f335..2e9620b42728b 100644 --- a/src/plugins/discover/public/utils/copy_value_to_clipboard.ts +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts @@ -13,12 +13,12 @@ import type { ValueToStringConverter } from '../types'; import { convertNameToString } from './convert_value_to_string'; const WARNING_FOR_FORMULAS = i18n.translate( - 'discover.grid.copyEscapedValueWithFormulasToClipboardWarningText', + 'unifiedDataTable.copyEscapedValueWithFormulasToClipboardWarningText', { defaultMessage: 'Values may contain formulas that are escaped.', } ); -const COPY_FAILED_ERROR_MESSAGE = i18n.translate('discover.grid.copyFailedErrorText', { +const COPY_FAILED_ERROR_MESSAGE = i18n.translate('unifiedDataTable.copyFailedErrorText', { defaultMessage: 'Unable to copy to clipboard in this browser', }); @@ -46,7 +46,7 @@ export const copyValueToClipboard = ({ return null; } - const toastTitle = i18n.translate('discover.grid.copyValueToClipboard.toastTitle', { + const toastTitle = i18n.translate('unifiedDataTable.copyValueToClipboard.toastTitle', { defaultMessage: 'Copied to clipboard', }); @@ -105,7 +105,7 @@ export const copyColumnValuesToClipboard = async ({ return null; } - const toastTitle = i18n.translate('discover.grid.copyColumnValuesToClipboard.toastTitle', { + const toastTitle = i18n.translate('unifiedDataTable.copyColumnValuesToClipboard.toastTitle', { defaultMessage: 'Values of "{column}" column copied to clipboard', values: { column: columnDisplayName }, }); @@ -143,7 +143,7 @@ export const copyColumnNameToClipboard = ({ return null; } - const toastTitle = i18n.translate('discover.grid.copyColumnNameToClipboard.toastTitle', { + const toastTitle = i18n.translate('unifiedDataTable.copyColumnNameToClipboard.toastTitle', { defaultMessage: 'Copied to clipboard', }); diff --git a/src/plugins/discover/public/utils/get_field_capabilities.test.ts b/packages/kbn-unified-data-table/src/utils/get_field_capabilities.test.ts similarity index 100% rename from src/plugins/discover/public/utils/get_field_capabilities.test.ts rename to packages/kbn-unified-data-table/src/utils/get_field_capabilities.test.ts diff --git a/src/plugins/discover/public/utils/get_field_capabilities.ts b/packages/kbn-unified-data-table/src/utils/get_field_capabilities.ts similarity index 90% rename from src/plugins/discover/public/utils/get_field_capabilities.ts rename to packages/kbn-unified-data-table/src/utils/get_field_capabilities.ts index cd63c1c189e73..8374801ec311e 100644 --- a/src/plugins/discover/public/utils/get_field_capabilities.ts +++ b/packages/kbn-unified-data-table/src/utils/get_field_capabilities.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; export const getFieldCapabilities = (dataView: DataView, field: DataViewField) => { const isRuntimeField = Boolean(dataView.getFieldByName(field.name)?.runtimeField); diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx new file mode 100644 index 0000000000000..0f6624a120b9c --- /dev/null +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx @@ -0,0 +1,968 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { of } from 'rxjs'; +import { shallow } from 'enzyme'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { getRenderCellValueFn } from './get_render_cell_value'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { CodeEditorProps, KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { buildDataTableRecord } from '@kbn/discover-utils'; +import type { EsHitRecord } from '@kbn/discover-utils/types'; +import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; + +jest.mock('@kbn/code-editor', () => { + const original = jest.requireActual('@kbn/code-editor'); + + const CodeEditorMock = (props: CodeEditorProps) => ( + + ); + + return { + ...original, + CodeEditor: CodeEditorMock, + }; +}); + +window.matchMedia = jest.fn().mockImplementation((query) => { + return { + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + }; +}); + +const mockServices = { + settings: { + client: { + get: (key: string) => key === 'discover:maxDocFieldsDisplayed' && 200, + }, + }, + uiSettings: { + get: (key: string) => key === 'discover:maxDocFieldsDisplayed' && 200, + }, + fieldFormats: { + getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => (value ? value : '-') })), + }, + theme: { + theme$: of({ darkMode: false }), + }, +}; + +const rowsSource: EsHitRecord[] = [ + { + _id: '1', + _index: 'test', + _score: 1, + _source: { bytes: 100, extension: '.gz' }, + highlight: { + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], + }, + }, +]; + +const rowsFields: EsHitRecord[] = [ + { + _id: '1', + _index: 'test', + _score: 1, + _source: undefined, + fields: { bytes: [100], extension: ['.gz'] }, + highlight: { + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], + }, + }, +]; + +const rowsFieldsWithTopLevelObject: EsHitRecord[] = [ + { + _id: '1', + _index: 'test', + _score: 1, + _source: undefined, + fields: { 'object.value': [100], extension: ['.gz'] }, + highlight: { + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], + }, + }, +]; + +const build = (hit: EsHitRecord) => buildDataTableRecord(hit, dataViewMock); + +describe('Unified data table cell rendering', function () { + it('renders bytes column correctly', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsSource.map(build), + false, + () => false, + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot( + `"100"` + ); + }); + + it('renders bytes column correctly using _source when details is true', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsSource.map(build), + false, + () => false, + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot( + `"
    100
    "` + ); + }); + + it('renders bytes column correctly using fields when details is true', () => { + const closePopoverMockFn = jest.fn(); + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFields.map(build), + false, + () => false, + closePopoverMockFn, + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = mountWithIntl( + + ); + expect(component.html()).toMatchInlineSnapshot( + `"
    100
    "` + ); + findTestSubject(component, 'docTableClosePopover').simulate('click'); + expect(closePopoverMockFn).toHaveBeenCalledTimes(1); + }); + + it('renders _source column correctly', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsSource.map(build), + false, + (fieldName) => ['extension', 'bytes'].includes(fieldName), + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + extension + + + + bytesDisplayName + + + + _index + + + + _score + + + + `); + }); + + it('renders _source column correctly when isDetails is set to true', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsSource.map(build), + false, + () => false, + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + + + + + + + + + + + `); + }); + + it('renders fields-based column correctly', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFields.map(build), + true, + (fieldName) => ['extension', 'bytes'].includes(fieldName), + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + extension + + + + bytesDisplayName + + + + _index + + + + _score + + + + `); + }); + + it('limits amount of rendered items', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFields.map(build), + true, + (fieldName) => ['extension', 'bytes'].includes(fieldName), + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + // this is the number of rendered items + 1 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + extension + + + + bytesDisplayName + + + + _index + + + + _score + + + + `); + }); + + it('renders fields-based column correctly when isDetails is set to true', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFields.map(build), + true, + (fieldName) => false, + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + + + + + + + + + + + `); + }); + + it('collect object fields and renders them like _source', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFieldsWithTopLevelObject.map(build), + true, + (fieldName) => ['object.value', 'extension', 'bytes'].includes(fieldName), + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + object.value + + + + `); + }); + + it('collect object fields and renders them like _source with fallback for unmapped', () => { + (dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined); + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFieldsWithTopLevelObject.map(build), + true, + (fieldName) => ['extension', 'bytes', 'object.value'].includes(fieldName), + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + object.value + + + + `); + }); + + it('collect object fields and renders them as json in details', () => { + const closePopoverMockFn = jest.fn(); + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFieldsWithTopLevelObject.map(build), + true, + () => false, + closePopoverMockFn, + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + + + + + + + + + + + `); + }); + + it('renders a functional close button when CodeEditor is rendered', () => { + const closePopoverMockFn = jest.fn(); + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFieldsWithTopLevelObject.map(build), + true, + () => false, + closePopoverMockFn, + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = mountWithIntl( + + + + ); + const gridSelectionBtn = findTestSubject(component, 'docTableClosePopover'); + gridSelectionBtn.simulate('click'); + expect(closePopoverMockFn).toHaveBeenCalledTimes(1); + }); + + it('does not collect subfields when the the column is unmapped but part of fields response', () => { + (dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined); + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFieldsWithTopLevelObject.map(build), + true, + () => false, + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + `); + }); + + it('renders correctly when invalid row is given', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsSource.map(build), + false, + () => false, + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot( + `"-"` + ); + }); + + it('renders correctly when invalid column is given', () => { + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsSource.map(build), + false, + () => false, + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot( + `"-"` + ); + }); + + it('renders unmapped fields correctly', () => { + (dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined); + const rowsFieldsUnmapped: EsHitRecord[] = [ + { + _id: '1', + _index: 'test', + _score: 1, + _source: undefined, + fields: { unmapped: ['.gz'] }, + highlight: { + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], + }, + }, + ]; + const DataTableCellValue = getRenderCellValueFn( + dataViewMock, + rowsFieldsUnmapped.map(build), + true, + (fieldName) => ['unmapped'].includes(fieldName), + jest.fn(), + mockServices.fieldFormats as unknown as FieldFormatsStart, + 100 + ); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + `); + + const componentWithDetails = shallow( + + ); + expect(componentWithDetails).toMatchInlineSnapshot(` + + + + + + + + + `); + }); +}); diff --git a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx similarity index 80% rename from src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx rename to packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx index e4242fd289feb..d820860e60ab7 100644 --- a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { Fragment, useContext, useEffect, useMemo } from 'react'; +import React, { Fragment, useContext, useEffect } from 'react'; import classnames from 'classnames'; import { i18n } from '@kbn/i18n'; import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme'; @@ -20,19 +20,18 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { DataTableRecord, EsHitRecord, ShouldShowFieldInTableHandler, } from '@kbn/discover-utils/types'; -import { MAX_DOC_FIELDS_DISPLAYED, formatFieldValue, formatHit } from '@kbn/discover-utils'; -import { JsonCodeEditor } from '@kbn/unified-doc-viewer-plugin/public'; -import { DiscoverGridContext } from './discover_grid_context'; -import { defaultMonacoEditorWidth } from './constants'; -import { useDiscoverServices } from '../../hooks/use_discover_services'; +import { formatFieldValue, formatHit } from '@kbn/discover-utils'; +import { UnifiedDataTableContext } from '../table_context'; +import { defaultMonacoEditorWidth } from '../constants'; +import JsonCodeEditor from '../components/json_code_editor/json_code_editor'; -const CELL_CLASS = 'dscDiscoverGrid__cellValue'; +const CELL_CLASS = 'unifiedDataTable__cellValue'; export const getRenderCellValueFn = ( @@ -40,18 +39,42 @@ export const getRenderCellValueFn = rows: DataTableRecord[] | undefined, useNewFieldsApi: boolean, shouldShowFieldHandler: ShouldShowFieldInTableHandler, - maxDocFieldsDisplayed: number, - closePopover: () => void + closePopover: () => void, + fieldFormats: FieldFormatsStart, + maxEntries: number, + externalCustomRenderers?: Record< + string, + (props: EuiDataGridCellValueElementProps) => React.ReactNode + > ) => - ({ rowIndex, columnId, isDetails, setCellProps }: EuiDataGridCellValueElementProps) => { - const { uiSettings, fieldFormats } = useDiscoverServices(); - - const maxEntries = useMemo(() => uiSettings.get(MAX_DOC_FIELDS_DISPLAYED), [uiSettings]); - + ({ + rowIndex, + columnId, + isDetails, + setCellProps, + colIndex, + isExpandable, + isExpanded, + }: EuiDataGridCellValueElementProps) => { + if (!!externalCustomRenderers && !!externalCustomRenderers[columnId]) { + return ( + <> + {externalCustomRenderers[columnId]({ + rowIndex, + columnId, + isDetails, + setCellProps, + isExpandable, + isExpanded, + colIndex, + })} + + ); + } const row = rows ? rows[rowIndex] : undefined; const field = dataView.fields.getByName(columnId); - const ctx = useContext(DiscoverGridContext); + const ctx = useContext(UnifiedDataTableContext); useEffect(() => { if (row?.isAnchor) { @@ -102,7 +125,7 @@ export const getRenderCellValueFn = const pairs = useTopLevelObjectColumns ? getTopLevelObjectPairs(row.raw, columnId, dataView, shouldShowFieldHandler).slice( 0, - maxDocFieldsDisplayed + maxEntries ) : formatHit(row, dataView, shouldShowFieldHandler, maxEntries, fieldFormats); @@ -110,13 +133,15 @@ export const getRenderCellValueFn = {pairs.map(([key, value]) => ( - {key} + + {key} + @@ -144,7 +169,7 @@ export const getRenderCellValueFn = function getInnerColumns(fields: Record, columnId: string) { return Object.fromEntries( Object.entries(fields).filter(([key]) => { - return key.indexOf(`${columnId}.`) === 0; + return key.startsWith(`${columnId}.`); }) ); } @@ -178,7 +203,7 @@ function renderPopoverContent({ }) { const closeButton = ( @@ -216,7 +241,7 @@ function renderPopoverContent({ String(v) }; - const formatted = (values as unknown[]) + const formatted = values .map((val: unknown) => formatter.convert(val, 'html', { field: subField, diff --git a/src/plugins/discover/public/utils/popularize_field.test.ts b/packages/kbn-unified-data-table/src/utils/popularize_field.test.ts similarity index 100% rename from src/plugins/discover/public/utils/popularize_field.test.ts rename to packages/kbn-unified-data-table/src/utils/popularize_field.test.ts diff --git a/src/plugins/discover/public/utils/popularize_field.ts b/packages/kbn-unified-data-table/src/utils/popularize_field.ts similarity index 89% rename from src/plugins/discover/public/utils/popularize_field.ts rename to packages/kbn-unified-data-table/src/utils/popularize_field.ts index 9ab711be49266..3feca3fd3d4e7 100644 --- a/src/plugins/discover/public/utils/popularize_field.ts +++ b/packages/kbn-unified-data-table/src/utils/popularize_field.ts @@ -7,8 +7,8 @@ */ import type { Capabilities } from '@kbn/core/public'; -import { DataViewsContract } from '@kbn/data-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; +import type { DataViewsContract } from '@kbn/data-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; async function popularizeField( dataView: DataView, diff --git a/packages/kbn-unified-data-table/src/utils/row_heights.ts b/packages/kbn-unified-data-table/src/utils/row_heights.ts new file mode 100644 index 0000000000000..45f472286d030 --- /dev/null +++ b/packages/kbn-unified-data-table/src/utils/row_heights.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Storage } from '@kbn/kibana-utils-plugin/public'; +import { isValidRowHeight } from './validate_row_height'; + +export interface DataGridOptionsRecord { + previousRowHeight: number; + previousConfigRowHeight: number; +} + +const getRowHeightKey = (consumer: string) => `${consumer}:dataGridRowHeight`; + +export const getStoredRowHeight = ( + storage: Storage, + consumer: string +): DataGridOptionsRecord | null => { + const entry = storage.get(getRowHeightKey(consumer)); + if ( + typeof entry === 'object' && + entry !== null && + isValidRowHeight(entry.previousRowHeight) && + isValidRowHeight(entry.previousConfigRowHeight) + ) { + return entry; + } + return null; +}; + +export const updateStoredRowHeight = ( + newRowHeight: number, + configRowHeight: number, + storage: Storage, + consumer: string +) => { + storage.set(getRowHeightKey(consumer), { + previousRowHeight: newRowHeight, + previousConfigRowHeight: configRowHeight, + }); +}; diff --git a/packages/kbn-unified-data-table/src/utils/rows_per_page.test.ts b/packages/kbn-unified-data-table/src/utils/rows_per_page.test.ts new file mode 100644 index 0000000000000..8da8ea099734b --- /dev/null +++ b/packages/kbn-unified-data-table/src/utils/rows_per_page.test.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getRowsPerPageOptions } from './rows_per_page'; + +const SORTED_OPTIONS = [10, 25, 50, 100, 250, 500]; + +describe('rows per page', () => { + describe('getRowsPerPageOptions', () => { + it('should return default options if not provided', () => { + expect(getRowsPerPageOptions()).toEqual(SORTED_OPTIONS); + }); + + it('should return default options if current value is one of them', () => { + expect(getRowsPerPageOptions(250)).toEqual(SORTED_OPTIONS); + }); + + it('should return extended options if current value is not one of them', () => { + expect(getRowsPerPageOptions(350)).toEqual([10, 25, 50, 100, 250, 350, 500]); + }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/utils/rows_per_page.ts b/packages/kbn-unified-data-table/src/utils/rows_per_page.ts new file mode 100644 index 0000000000000..2eb547df1a36f --- /dev/null +++ b/packages/kbn-unified-data-table/src/utils/rows_per_page.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { sortBy, uniq } from 'lodash'; + +import { ROWS_PER_PAGE_OPTIONS } from '../constants'; + +export const getRowsPerPageOptions = (currentRowsPerPage?: number): number[] => { + return sortBy( + uniq( + typeof currentRowsPerPage === 'number' && currentRowsPerPage > 0 + ? [...ROWS_PER_PAGE_OPTIONS, currentRowsPerPage] + : ROWS_PER_PAGE_OPTIONS + ) + ); +}; diff --git a/src/plugins/discover/public/utils/validate_row_height.ts b/packages/kbn-unified-data-table/src/utils/validate_row_height.ts similarity index 100% rename from src/plugins/discover/public/utils/validate_row_height.ts rename to packages/kbn-unified-data-table/src/utils/validate_row_height.ts diff --git a/packages/kbn-unified-data-table/tsconfig.json b/packages/kbn-unified-data-table/tsconfig.json new file mode 100644 index 0000000000000..bed8d16a279b1 --- /dev/null +++ b/packages/kbn-unified-data-table/tsconfig.json @@ -0,0 +1,37 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": ["*.ts", "**/*.tsx", "src/**/*", "__mocks__/**/*.ts"], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n", + "@kbn/data-views-plugin", + "@kbn/unified-doc-viewer", + "@kbn/discover-utils", + "@kbn/kibana-utils-plugin", + "@kbn/expressions-plugin", + "@kbn/test-jest-helpers", + "@kbn/i18n-react", + "@kbn/ui-theme", + "@kbn/field-types", + "@kbn/kibana-utils-plugin", + "@kbn/cell-actions", + "@kbn/utility-types", + "@kbn/data-view-field-editor-plugin", + "@kbn/field-formats-plugin", + "@kbn/react-kibana-context-common", + "@kbn/data-plugin", + "@kbn/core", + "@kbn/ui-actions-plugin", + "@kbn/charts-plugin", + "@kbn/kibana-react-plugin", + "@kbn/monaco", + "@kbn/code-editor", + "@kbn/config", + "@kbn/monaco", + ] +} diff --git a/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap b/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap index 0f327bb0bb6a5..09becbd7c2bf5 100644 --- a/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap +++ b/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap @@ -191,7 +191,7 @@ exports[`UnifiedFieldList renders properly with a drag handl "aria-label": "Preview bytes: number", } } - className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists custom" + className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists unifiedFieldListItemButton--withDragHandle custom" dataTestSubj="test-subj" dragHandle={ diff --git a/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx b/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx index 13860a0e4f155..c9be7af08638c 100644 --- a/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx +++ b/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx @@ -105,6 +105,7 @@ export function FieldItemButton({ [`unifiedFieldListItemButton--${type}`]: type, [`unifiedFieldListItemButton--exists`]: !isEmpty, [`unifiedFieldListItemButton--missing`]: isEmpty, + [`unifiedFieldListItemButton--withDragHandle`]: Boolean(otherProps.dragHandle), }, className ); diff --git a/packages/kbn-unified-field-list/src/components/field_list_filters/field_list_filters.tsx b/packages/kbn-unified-field-list/src/components/field_list_filters/field_list_filters.tsx index e1834dd8fd5f0..4410bc82eeef8 100644 --- a/packages/kbn-unified-field-list/src/components/field_list_filters/field_list_filters.tsx +++ b/packages/kbn-unified-field-list/src/components/field_list_filters/field_list_filters.tsx @@ -23,6 +23,7 @@ export interface FieldListFiltersProps { getCustomFieldType?: FieldTypeFilterProps['getCustomFieldType']; onSupportedFieldFilter?: FieldTypeFilterProps['onSupportedFieldFilter']; onChangeFieldTypes: FieldTypeFilterProps['onChange']; + compressed?: FieldNameSearchProps['compressed']; nameFilter: FieldNameSearchProps['nameFilter']; screenReaderDescriptionId?: FieldNameSearchProps['screenReaderDescriptionId']; onChangeNameFilter: FieldNameSearchProps['onChange']; @@ -38,6 +39,7 @@ export interface FieldListFiltersProps { * @param getCustomFieldType * @param onSupportedFieldFilter * @param onChangeFieldTypes + * @param compressed * @param nameFilter * @param screenReaderDescriptionId * @param onChangeNameFilter @@ -52,6 +54,7 @@ function InnerFieldListFilters({ getCustomFieldType, onSupportedFieldFilter, onChangeFieldTypes, + compressed, nameFilter, screenReaderDescriptionId, onChangeNameFilter, @@ -72,6 +75,7 @@ function InnerFieldListFilters({ /> ) : undefined } + compressed={compressed} nameFilter={nameFilter} screenReaderDescriptionId={screenReaderDescriptionId} onChange={onChangeNameFilter} diff --git a/packages/kbn-unified-field-list/src/components/field_list_filters/field_name_search.tsx b/packages/kbn-unified-field-list/src/components/field_list_filters/field_name_search.tsx index 91d78850e4453..faf146adfd831 100644 --- a/packages/kbn-unified-field-list/src/components/field_list_filters/field_name_search.tsx +++ b/packages/kbn-unified-field-list/src/components/field_list_filters/field_name_search.tsx @@ -16,6 +16,7 @@ import { EuiFieldSearch, type EuiFieldSearchProps } from '@elastic/eui'; export interface FieldNameSearchProps { 'data-test-subj': string; append?: EuiFieldSearchProps['append']; + compressed?: EuiFieldSearchProps['compressed']; nameFilter: string; screenReaderDescriptionId?: string; onChange: (nameFilter: string) => unknown; @@ -25,6 +26,7 @@ export interface FieldNameSearchProps { * Search input for fields list * @param dataTestSubject * @param append + * @param compressed * @param nameFilter * @param screenReaderDescriptionId * @param onChange @@ -33,6 +35,7 @@ export interface FieldNameSearchProps { export const FieldNameSearch: React.FC = ({ 'data-test-subj': dataTestSubject, append, + compressed, nameFilter, screenReaderDescriptionId, onChange, @@ -52,6 +55,7 @@ export const FieldNameSearch: React.FC = ({ placeholder={searchPlaceholder} value={nameFilter} append={append} + compressed={compressed} /> ); }; diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx index 6ccbf54516995..4feeb9b1be23e 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx @@ -82,6 +82,7 @@ async function getComponent({ isEmpty: false, groupIndex: 1, itemIndex: 0, + size: 'xs', workspaceSelectedFieldNames: [], }; const comp = await mountWithIntl(); diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx index 837818399eccf..745d463b28386 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx @@ -33,6 +33,7 @@ import type { interface GetCommonFieldItemButtonPropsParams { stateService: UnifiedFieldListSidebarContainerStateService; field: DataViewField; + size: FieldItemButtonProps['size']; isSelected: boolean; toggleDisplay: (field: DataViewField, isSelected?: boolean) => void; } @@ -40,10 +41,12 @@ interface GetCommonFieldItemButtonPropsParams { function getCommonFieldItemButtonProps({ stateService, field, + size, isSelected, toggleDisplay, }: GetCommonFieldItemButtonPropsParams): { field: FieldItemButtonProps['field']; + size: FieldItemButtonProps['size']; isSelected: FieldItemButtonProps['isSelected']; buttonAddFieldToWorkspaceProps?: FieldItemButtonProps['buttonAddFieldToWorkspaceProps']; buttonRemoveFieldFromWorkspaceProps?: FieldItemButtonProps['buttonRemoveFieldFromWorkspaceProps']; @@ -54,6 +57,7 @@ function getCommonFieldItemButtonProps({ field.name === '_source' ? undefined : (f: DataViewField) => toggleDisplay(f, isSelected); return { field, + size, isSelected, buttonAddFieldToWorkspaceProps: stateService.creationOptions.buttonAddFieldToWorkspaceProps, buttonRemoveFieldFromWorkspaceProps: @@ -68,10 +72,11 @@ interface MultiFieldsProps { multiFields: NonNullable; toggleDisplay: (field: DataViewField) => void; alwaysShowActionButton: boolean; + size: FieldItemButtonProps['size']; } const MultiFields: React.FC = memo( - ({ stateService, multiFields, toggleDisplay, alwaysShowActionButton }) => ( + ({ stateService, multiFields, toggleDisplay, alwaysShowActionButton, size }) => (
    @@ -84,7 +89,6 @@ const MultiFields: React.FC = memo( {multiFields.map((entry) => ( = memo( field: entry.field, isSelected: entry.isSelected, toggleDisplay, + size, })} /> ))} @@ -187,6 +192,10 @@ export interface UnifiedFieldListItemProps { * Item index in the field list */ itemIndex: number; + /** + * Item size + */ + size: FieldItemButtonProps['size']; } function UnifiedFieldListItemComponent({ @@ -209,6 +218,7 @@ function UnifiedFieldListItemComponent({ workspaceSelectedFieldNames, groupIndex, itemIndex, + size, }: UnifiedFieldListItemProps) { const [infoIsOpen, setOpen] = useState(false); @@ -284,6 +294,7 @@ function UnifiedFieldListItemComponent({ multiFields={multiFields} alwaysShowActionButton={alwaysShowActionButton} toggleDisplay={toggleDisplay} + size={size} /> )} @@ -315,6 +326,8 @@ function UnifiedFieldListItemComponent({ [field, itemIndex] ); const order = useMemo(() => [0, groupIndex, itemIndex], [groupIndex, itemIndex]); + const isDragDisabled = + alwaysShowActionButton || stateService.creationOptions.disableFieldListItemDragAndDrop; return ( } diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.scss b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.scss index d01d93c345ef9..b646d60ec3b0f 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.scss +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.scss @@ -6,6 +6,11 @@ width: $euiSize * 19; height: 100%; + &--collapsed { + width: auto; + padding: $euiSizeS $euiSizeS 0; + } + @include euiBreakpoint('xs', 's') { width: 100%; padding: $euiSize; @@ -14,7 +19,7 @@ } .unifiedFieldListSidebar__list { - padding: $euiSizeS 0 $euiSizeS $euiSizeS; + padding: $euiSizeS $euiSizeS 0; @include euiBreakpoint('xs', 's') { padding: $euiSizeS 0 0 0; @@ -38,3 +43,18 @@ .unifiedFieldListSidebar__flyoutHeader { align-items: center; } + +.unifiedFieldListSidebar .unifiedFieldListItemButton { + &.kbnFieldButton { + margin-bottom: $euiSizeXS / 2; + } + + &.domDragDrop-isDraggable { + box-shadow: none; + } + + &:not(.unifiedFieldListItemButton__dragging) { + padding: 0; + background: none; + } +} diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx index 12eb7209cd05a..fb90e2b36d39e 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx @@ -9,16 +9,29 @@ import './field_list_sidebar.scss'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPageSidebar } from '@elastic/eui'; +import { css } from '@emotion/react'; +import classnames from 'classnames'; +import { + EuiButton, + EuiButtonProps, + EuiFlexGroup, + EuiFlexItem, + EuiHideFor, + EuiPageSidebar, + EuiPageSidebarProps, + useEuiTheme, +} from '@elastic/eui'; +import { ToolbarButton } from '@kbn/shared-ux-button-toolbar'; import { type DataViewField } from '@kbn/data-views-plugin/public'; import { getDataViewFieldSubtypeMulti } from '@kbn/es-query/src/utils'; import { FIELDS_LIMIT_SETTING, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils'; import { FieldList } from '../../components/field_list'; import { FieldListFilters } from '../../components/field_list_filters'; import { FieldListGrouped, type FieldListGroupedProps } from '../../components/field_list_grouped'; -import { FieldsGroupNames } from '../../types'; +import { FieldsGroupNames, type ButtonAddFieldVariant } from '../../types'; import { GroupedFieldsParams, useGroupedFields } from '../../hooks/use_grouped_fields'; import { UnifiedFieldListItem, type UnifiedFieldListItemProps } from '../unified_field_list_item'; +import { SidebarToggleButton, type SidebarToggleButtonProps } from './sidebar_toggle_button'; import { getSelectedFields, shouldShowField, @@ -46,6 +59,11 @@ export type UnifiedFieldListSidebarCustomizableProps = Pick< */ showFieldList?: boolean; + /** + * Compressed view + */ + compressed?: boolean; + /** * Custom logic for determining which field is selected */ @@ -83,6 +101,22 @@ interface UnifiedFieldListSidebarInternalProps { */ alwaysShowActionButton?: UnifiedFieldListItemProps['alwaysShowActionButton']; + /** + * What button style type to use + */ + buttonAddFieldVariant: ButtonAddFieldVariant; + + /** + * In case if sidebar is collapsible by default + * Pass `undefined` to hide the collapse/expand buttons from the sidebar + */ + isSidebarCollapsed?: boolean; + + /** + * A handler to toggle the sidebar + */ + onToggleSidebar?: SidebarToggleButtonProps['onChange']; + /** * Trigger a field editing */ @@ -104,10 +138,13 @@ export const UnifiedFieldListSidebarComponent: React.FC { const { dataViews, core } = services; const useNewFieldsApi = useMemo( @@ -210,6 +248,7 @@ export const UnifiedFieldListSidebarComponent: React.FC + ) : null; + + const pageSidebarProps: Partial = { + className: classnames('unifiedFieldListSidebar', { + 'unifiedFieldListSidebar--collapsed': isSidebarCollapsed, + }), + 'aria-label': i18n.translate( + 'unifiedFieldList.fieldListSidebar.indexAndFieldsSectionAriaLabel', + { + defaultMessage: 'Index and fields', } - > + ), + id: + stateService.creationOptions.dataTestSubj?.fieldListSidebarDataTestSubj ?? + 'unifiedFieldListSidebarId', + 'data-test-subj': + stateService.creationOptions.dataTestSubj?.fieldListSidebarDataTestSubj ?? + 'unifiedFieldListSidebarId', + }; + + if (isSidebarCollapsed && sidebarToggleButton) { + return ( + +
    {sidebarToggleButton}
    +
    + ); + } + + const hasButtonAddFieldToolbarStyle = buttonAddFieldVariant === 'toolbar'; + const buttonAddFieldCommonProps: Partial = { + size: 's', + iconType: 'indexOpen', + 'data-test-subj': + stateService.creationOptions.dataTestSubj?.fieldListAddFieldButtonTestSubj ?? + 'unifiedFieldListAddField', + }; + const buttonAddFieldLabel = i18n.translate( + 'unifiedFieldList.fieldListSidebar.addFieldButtonLabel', + { + defaultMessage: 'Add a field', + } + ); + + return ( + - {Boolean(prepend) && {prepend}} + {Boolean(prepend) && ( + + {prepend} + + )} } + prepend={ + + {sidebarToggleButton && ( + {sidebarToggleButton} + )} + + + + + } className="unifiedFieldListSidebar__list" > {showFieldList ? ( @@ -293,25 +387,33 @@ export const UnifiedFieldListSidebarComponent: React.FC )} - {!!onEditField && ( - - onEditField()} - size="s" - > - {i18n.translate('unifiedFieldList.fieldListSidebar.addFieldButtonLabel', { - defaultMessage: 'Add a field', - })} - - - )} + {!!onEditField && ( + + {hasButtonAddFieldToolbarStyle ? ( + onEditField()} + /> + ) : ( + onEditField()}> + {buttonAddFieldLabel} + + )} + + )} ); diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx index 4765280b4ef68..520a64f8d69b0 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx @@ -35,6 +35,7 @@ import { type ExistingFieldsFetcher, } from '../../hooks/use_existing_fields'; import { useQuerySubscriber } from '../../hooks/use_query_subscriber'; +import { useSidebarToggle } from '../../hooks/use_sidebar_toggle'; import { UnifiedFieldListSidebar, type UnifiedFieldListSidebarCustomizableProps, @@ -72,11 +73,6 @@ export type UnifiedFieldListSidebarContainerProps = Omit< */ getCreationOptions: () => UnifiedFieldListSidebarContainerCreationOptions; - /** - * In case if you have a sidebar toggle button - */ - isSidebarCollapsed?: boolean; - /** * Custom content to render at the top of field list in the flyout (for example a data view picker) */ @@ -115,7 +111,6 @@ const UnifiedFieldListSidebarContainer = forwardRef< services, dataView, workspaceSelectedFieldNames, - isSidebarCollapsed, // TODO later: pull the logic of collapsing the sidebar to this component prependInFlyout, variant = 'responsive', onFieldEdited, @@ -125,6 +120,7 @@ const UnifiedFieldListSidebarContainer = forwardRef< ); const { data, dataViewFieldEditor } = services; const [isFieldListFlyoutVisible, setIsFieldListFlyoutVisible] = useState(false); + const { isSidebarCollapsed, onToggleSidebar } = useSidebarToggle({ stateService }); const canEditDataView = Boolean(dataViewFieldEditor?.userPermissions.editIndexPattern()) || @@ -250,8 +246,15 @@ const UnifiedFieldListSidebarContainer = forwardRef< isAffectedByGlobalFilter, onEditField: editField, onDeleteField: deleteField, + compressed: stateService.creationOptions.compressed ?? false, + buttonAddFieldVariant: stateService.creationOptions.buttonAddFieldVariant ?? 'primary', }; + if (stateService.creationOptions.showSidebarToggleButton) { + commonSidebarProps.isSidebarCollapsed = isSidebarCollapsed; + commonSidebarProps.onToggleSidebar = onToggleSidebar; + } + const buttonPropsToTriggerFlyout = stateService.creationOptions.buttonPropsToTriggerFlyout; const renderListVariant = () => { @@ -319,6 +322,8 @@ const UnifiedFieldListSidebarContainer = forwardRef< @@ -333,12 +338,12 @@ const UnifiedFieldListSidebarContainer = forwardRef< } if (variant === 'list-always') { - return (!isSidebarCollapsed && renderListVariant()) || null; + return renderListVariant(); } return ( <> - {!isSidebarCollapsed && {renderListVariant()}} + {renderListVariant()} {renderButtonVariant()} ); diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/sidebar_toggle_button/index.ts b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/sidebar_toggle_button/index.ts new file mode 100644 index 0000000000000..f3dd50b48c968 --- /dev/null +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/sidebar_toggle_button/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { SidebarToggleButton, type SidebarToggleButtonProps } from './sidebar_toggle_button'; diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/sidebar_toggle_button/sidebar_toggle_button.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/sidebar_toggle_button/sidebar_toggle_button.tsx new file mode 100644 index 0000000000000..1bf8f62b2cced --- /dev/null +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/sidebar_toggle_button/sidebar_toggle_button.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { IconButtonGroup, type IconButtonGroupProps } from '@kbn/shared-ux-button-toolbar'; + +/** + * Toggle button props + */ +export interface SidebarToggleButtonProps { + 'data-test-subj'?: string; + isSidebarCollapsed: boolean; + buttonSize: IconButtonGroupProps['buttonSize']; + onChange: (isSidebarCollapsed: boolean) => void; +} + +/** + * A toggle button for the fields sidebar + * @param data-test-subj + * @param isSidebarCollapsed + * @param onChange + * @constructor + */ +export const SidebarToggleButton: React.FC = ({ + 'data-test-subj': dataTestSubj = 'unifiedFieldListSidebar__toggle', + isSidebarCollapsed, + buttonSize, + onChange, +}) => { + // TODO: replace with new Eui icons once available + return ( +
    + onChange(false), + }, + ] + : [ + { + label: i18n.translate('unifiedFieldList.fieldListSidebar.collapseSidebarButton', { + defaultMessage: 'Hide sidebar', + }), + iconType: 'menuLeft', + 'data-test-subj': `${dataTestSubj}-collapse`, + onClick: () => onChange(true), + }, + ]), + ]} + /> +
    + ); +}; diff --git a/packages/kbn-unified-field-list/src/hooks/use_sidebar_toggle.test.tsx b/packages/kbn-unified-field-list/src/hooks/use_sidebar_toggle.test.tsx new file mode 100644 index 0000000000000..16ee451400c6c --- /dev/null +++ b/packages/kbn-unified-field-list/src/hooks/use_sidebar_toggle.test.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useSidebarToggle } from './use_sidebar_toggle'; +import * as localStorageModule from 'react-use/lib/useLocalStorage'; + +jest.spyOn(localStorageModule, 'default'); + +describe('UnifiedFieldList useSidebarToggle', () => { + const stateService = { + creationOptions: { + originatingApp: 'test', + localStorageKeyPrefix: 'this', + }, + }; + + beforeEach(() => { + (localStorageModule.default as jest.Mock).mockClear(); + }); + + it('should toggle correctly', async () => { + const storeMock = jest.fn(); + (localStorageModule.default as jest.Mock).mockImplementation(() => { + return [false, storeMock]; + }); + + const { result } = renderHook(useSidebarToggle, { + initialProps: { + stateService, + }, + }); + + expect(result.current.isSidebarCollapsed).toBe(false); + + act(() => { + result.current.onToggleSidebar(true); + }); + + expect(result.current.isSidebarCollapsed).toBe(true); + expect(storeMock).toHaveBeenCalledWith(true); + + act(() => { + result.current.onToggleSidebar(false); + }); + + expect(result.current.isSidebarCollapsed).toBe(false); + expect(storeMock).toHaveBeenLastCalledWith(false); + }); + + it('should restore collapsed state and expand from it', async () => { + const storeMock = jest.fn(); + (localStorageModule.default as jest.Mock).mockImplementation(() => { + return [true, storeMock]; + }); + + const { result } = renderHook(useSidebarToggle, { + initialProps: { + stateService, + }, + }); + + expect(result.current.isSidebarCollapsed).toBe(true); + + act(() => { + result.current.onToggleSidebar(false); + }); + + expect(result.current.isSidebarCollapsed).toBe(false); + expect(storeMock).toHaveBeenCalledWith(false); + }); + + it('should not persist if local storage key is not defined', async () => { + const storeMock = jest.fn(); + (localStorageModule.default as jest.Mock).mockImplementation(() => { + return [false, storeMock]; + }); + + const { result } = renderHook(useSidebarToggle, { + initialProps: { + stateService: { + creationOptions: { + originatingApp: 'test', + localStorageKeyPrefix: undefined, + }, + }, + }, + }); + + expect(result.current.isSidebarCollapsed).toBe(false); + + act(() => { + result.current.onToggleSidebar(true); + }); + + expect(result.current.isSidebarCollapsed).toBe(true); + expect(storeMock).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-unified-field-list/src/hooks/use_sidebar_toggle.ts b/packages/kbn-unified-field-list/src/hooks/use_sidebar_toggle.ts new file mode 100644 index 0000000000000..b12c7dc7dae95 --- /dev/null +++ b/packages/kbn-unified-field-list/src/hooks/use_sidebar_toggle.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useCallback, useState, useMemo } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import type { UnifiedFieldListSidebarContainerStateService } from '../types'; + +/** + * Hook params + */ +export interface UseSidebarToggleParams { + /** + * Service for managing the state + */ + stateService: UnifiedFieldListSidebarContainerStateService; +} + +/** + * Hook result type + */ +export interface UseSidebarToggleResult { + isSidebarCollapsed: boolean; + onToggleSidebar: (isSidebarCollapsed: boolean) => void; +} + +/** + * Hook for managing sidebar toggle state + * @param stateService + */ +export const useSidebarToggle = ({ + stateService, +}: UseSidebarToggleParams): UseSidebarToggleResult => { + const [initialIsSidebarCollapsed, storeIsSidebarCollapsed] = useLocalStorage( + `${stateService.creationOptions.localStorageKeyPrefix ?? 'unifiedFieldList'}:sidebarClosed`, // as legacy `discover:sidebarClosed` key + false + ); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState( + initialIsSidebarCollapsed ?? false + ); + + const onToggleSidebar = useCallback( + (isCollapsed) => { + setIsSidebarCollapsed(isCollapsed); + if (stateService.creationOptions.localStorageKeyPrefix) { + storeIsSidebarCollapsed(isCollapsed); + } + }, + [ + storeIsSidebarCollapsed, + setIsSidebarCollapsed, + stateService.creationOptions.localStorageKeyPrefix, + ] + ); + + return useMemo( + () => ({ isSidebarCollapsed, onToggleSidebar }), + [isSidebarCollapsed, onToggleSidebar] + ); +}; diff --git a/packages/kbn-unified-field-list/src/types.ts b/packages/kbn-unified-field-list/src/types.ts index 76997c73176b3..dad321dbe56b3 100755 --- a/packages/kbn-unified-field-list/src/types.ts +++ b/packages/kbn-unified-field-list/src/types.ts @@ -107,6 +107,8 @@ export type OverrideFieldGroupDetails = ( export type TimeRangeUpdatesType = 'search-session' | 'timefilter'; +export type ButtonAddFieldVariant = 'primary' | 'toolbar'; + export type SearchMode = 'documents' | 'text-based'; export interface UnifiedFieldListSidebarContainerCreationOptions { @@ -116,7 +118,12 @@ export interface UnifiedFieldListSidebarContainerCreationOptions { originatingApp: string; /** - * Your app name: "discover", "lens", etc. If not provided, sections state would not be persisted. + * Pass `true` to enable the compressed view + */ + compressed?: boolean; + + /** + * Your app name: "discover", "lens", etc. If not provided, sections and sidebar toggle states would not be persisted. */ localStorageKeyPrefix?: string; @@ -125,6 +132,16 @@ export interface UnifiedFieldListSidebarContainerCreationOptions { */ timeRangeUpdatesType?: TimeRangeUpdatesType; + /** + * Choose how the bottom "Add a field" button should look like. Default `primary`. + */ + buttonAddFieldVariant?: ButtonAddFieldVariant; + + /** + * Pass `true` to make the sidebar collapsible. Additionally, define `localStorageKeyPrefix` to persist toggle state. + */ + showSidebarToggleButton?: boolean; + /** * Pass `true` to skip auto fetching of fields existence info */ diff --git a/packages/kbn-unified-field-list/tsconfig.json b/packages/kbn-unified-field-list/tsconfig.json index 78ea71ca44344..f60d203786439 100644 --- a/packages/kbn-unified-field-list/tsconfig.json +++ b/packages/kbn-unified-field-list/tsconfig.json @@ -29,6 +29,7 @@ "@kbn/shared-ux-utility", "@kbn/discover-utils", "@kbn/ebt-tools", + "@kbn/shared-ux-button-toolbar", ], "exclude": ["target/**/*"] } diff --git a/packages/kbn-visualization-ui-components/components/color_picker.tsx b/packages/kbn-visualization-ui-components/components/color_picker.tsx index 74c17b76ba2f1..702c17d06241f 100644 --- a/packages/kbn-visualization-ui-components/components/color_picker.tsx +++ b/packages/kbn-visualization-ui-components/components/color_picker.tsx @@ -97,12 +97,12 @@ export const ColorPicker = ({ onChange={handleColor} color={isDisabled ? '' : colorText} disabled={isDisabled} - placeholder={ - defaultColor?.toUpperCase() || - i18n.translate('visualizationUiComponents.colorPicker.seriesColor.auto', { - defaultMessage: 'Auto', - }) - } + placeholder={' '} + onBlur={() => { + if (!colorText) { + setColorText(overwriteColor ?? defaultColor); + } + }} aria-label={inputLabel} showAlpha={showAlpha} swatches={ diff --git a/packages/kbn-xstate-utils/README.md b/packages/kbn-xstate-utils/README.md new file mode 100644 index 0000000000000..ba185e1b466a3 --- /dev/null +++ b/packages/kbn-xstate-utils/README.md @@ -0,0 +1,3 @@ +# @kbn/xstate-utils + +Utilities to assist with development using the xstate library. diff --git a/packages/kbn-xstate-utils/index.ts b/packages/kbn-xstate-utils/index.ts new file mode 100644 index 0000000000000..de0577ee3ed83 --- /dev/null +++ b/packages/kbn-xstate-utils/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src'; diff --git a/packages/kbn-xstate-utils/jest.config.js b/packages/kbn-xstate-utils/jest.config.js new file mode 100644 index 0000000000000..9c747a6a128c3 --- /dev/null +++ b/packages/kbn-xstate-utils/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-xstate-utils'], +}; diff --git a/packages/kbn-xstate-utils/kibana.jsonc b/packages/kbn-xstate-utils/kibana.jsonc new file mode 100644 index 0000000000000..086bce23401aa --- /dev/null +++ b/packages/kbn-xstate-utils/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/xstate-utils", + "owner": "@elastic/infra-monitoring-ui" +} diff --git a/packages/kbn-xstate-utils/package.json b/packages/kbn-xstate-utils/package.json new file mode 100644 index 0000000000000..373931bd41c22 --- /dev/null +++ b/packages/kbn-xstate-utils/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/xstate-utils", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-xstate-utils/src/actions.ts b/packages/kbn-xstate-utils/src/actions.ts new file mode 100644 index 0000000000000..178f6499102ad --- /dev/null +++ b/packages/kbn-xstate-utils/src/actions.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + actions, + ActorRef, + AnyEventObject, + EventObject, + Expr, + PureAction, + SendActionOptions, +} from 'xstate'; + +export const sendIfDefined = + (target: string | ActorRef) => + ( + eventExpr: Expr, + options?: SendActionOptions + ): PureAction => { + return actions.pure((context, event) => { + const targetEvent = eventExpr(context, event); + return targetEvent != null && targetEvent !== undefined + ? [actions.sendTo(target, targetEvent, options)] + : undefined; + }); + }; diff --git a/packages/kbn-xstate-utils/src/dev_tools.ts b/packages/kbn-xstate-utils/src/dev_tools.ts new file mode 100644 index 0000000000000..fa16b808b3aec --- /dev/null +++ b/packages/kbn-xstate-utils/src/dev_tools.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const isDevMode = () => process.env.NODE_ENV !== 'production'; diff --git a/packages/kbn-xstate-utils/src/index.ts b/packages/kbn-xstate-utils/src/index.ts new file mode 100644 index 0000000000000..2cf5853db6e08 --- /dev/null +++ b/packages/kbn-xstate-utils/src/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './actions'; +export * from './notification_channel'; +export * from './types'; +export * from './dev_tools'; diff --git a/packages/kbn-xstate-utils/src/notification_channel.ts b/packages/kbn-xstate-utils/src/notification_channel.ts new file mode 100644 index 0000000000000..86f9c7f64f518 --- /dev/null +++ b/packages/kbn-xstate-utils/src/notification_channel.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ReplaySubject, Subject } from 'rxjs'; +import { ActionFunction, EventObject, Expr, Subscribable } from 'xstate'; + +export interface NotificationChannel { + createService: () => Subscribable; + notify: ( + eventExpr: Expr + ) => ActionFunction; +} + +export const createNotificationChannel = ( + shouldReplayLastEvent = true +): NotificationChannel => { + const eventsSubject = shouldReplayLastEvent + ? new ReplaySubject(1) + : new Subject(); + + const createService = () => eventsSubject.asObservable(); + + const notify = + (eventExpr: Expr) => + (context: TContext, event: TEvent) => { + const eventToSend = eventExpr(context, event); + + if (eventToSend != null) { + eventsSubject.next(eventToSend); + } + }; + + return { + createService, + notify, + }; +}; diff --git a/packages/kbn-xstate-utils/src/types.ts b/packages/kbn-xstate-utils/src/types.ts new file mode 100644 index 0000000000000..10d21697cdd28 --- /dev/null +++ b/packages/kbn-xstate-utils/src/types.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ActorRef, ActorRefWithDeprecatedState, EmittedFrom, State, StateValue } from 'xstate'; + +export type OmitDeprecatedState> = Omit< + T, + 'state' +>; + +export type MatchedState< + TState extends State, + TStateValue extends StateValue +> = TState extends State< + any, + infer TEvent, + infer TStateSchema, + infer TTypestate, + infer TResolvedTypesMeta +> + ? State< + (TTypestate extends any + ? { value: TStateValue; context: any } extends TTypestate + ? TTypestate + : never + : never)['context'], + TEvent, + TStateSchema, + TTypestate, + TResolvedTypesMeta + > & { + value: TStateValue; + } + : never; + +export type MatchedStateFromActor< + TActorRef extends ActorRef, + TStateValue extends StateValue +> = MatchedState, TStateValue>; diff --git a/packages/kbn-xstate-utils/tsconfig.json b/packages/kbn-xstate-utils/tsconfig.json new file mode 100644 index 0000000000000..2f9ddddbeea23 --- /dev/null +++ b/packages/kbn-xstate-utils/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/packages/serverless/settings/common/README.mdx b/packages/serverless/settings/common/README.mdx new file mode 100644 index 0000000000000..ece99219fbc3b --- /dev/null +++ b/packages/serverless/settings/common/README.mdx @@ -0,0 +1,14 @@ +--- +id: serverless/packages/settings/common +slug: /serverless/packages/settings/common +title: Serverless Common Advanced Settings +description: A package of common settings for all Serverless projects. +tags: ['serverless', 'package'] +date: 2023-08-24 +--- + +This package contains a list of UI settings that are available in all Serverless projects in the Advanced settings app. +This list is consumed by the `serverless` plugin, which merges it with any serverless project-specific settings that +have been set up, and sends it to the uiSettings service to set an allowlist for the settings. + +If you need to register a setting that should be available in all serverless projects, make sure to add its Id to this list. diff --git a/packages/serverless/settings/common/index.ts b/packages/serverless/settings/common/index.ts new file mode 100644 index 0000000000000..326108abcd747 --- /dev/null +++ b/packages/serverless/settings/common/index.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as settings from '@kbn/management-settings-ids'; + +const GENERAL_SETTINGS = [ + settings.CSV_QUOTE_VALUES_ID, + settings.DATE_FORMAT_DOW_ID, + settings.DATE_FORMAT_SCALED_ID, + settings.DATE_FORMAT_TZ_ID, + settings.DATE_FORMAT_NANOS_ID, + settings.DEFAULT_INDEX_ID, + settings.FILTERS_PINNED_BY_DEFAULT_ID, + settings.FORMAT_BYTES_DEFAULT_PATTERN_ID, + settings.FORMAT_CURRENCY_DEFAULT_PATTERN_ID, + settings.FORMAT_NUMBER_DEFAULT_LOCALE_ID, + settings.FORMAT_NUMBER_DEFAULT_PATTERN_ID, + settings.FORMAT_PERCENT_DEFAULT_PATTERN_ID, + settings.META_FIELDS_ID, + settings.STATE_STORE_IN_SESSION_STORAGE_ID, + settings.TIMEPICKER_QUICK_RANGES_ID, + settings.TIMEPICKER_TIME_DEFAULTS_ID, +]; + +const PRESENTATION_LABS_SETTINGS = [settings.LABS_DASHBOARD_DEFER_BELOW_FOLD_ID]; + +const ACCESSIBILITY_SETTINGS = [settings.ACCESSIBILITY_DISABLE_ANIMATIONS_ID]; + +const BANNER_SETTINGS = [ + settings.BANNERS_PLACEMENT_ID, + settings.BANNERS_TEXT_CONTENT_ID, + settings.BANNERS_TEXT_COLOR_ID, + settings.BANNERS_BACKGROUND_COLOR_ID, +]; + +const DISCOVER_SETTINGS = [settings.DEFAULT_COLUMNS_ID]; + +const NOTIFICATION_SETTINGS = [ + settings.NOTIFICATIONS_BANNER_ID, + settings.NOTIFICATIONS_LIFETIME_BANNER_ID, + settings.NOTIFICATIONS_LIFETIME_ERROR_ID, + settings.NOTIFICATIONS_LIFETIME_INFO_ID, + settings.NOTIFICATIONS_LIFETIME_WARNING_ID, +]; + +export const ALL_COMMON_SETTINGS = [ + ...GENERAL_SETTINGS, + ...PRESENTATION_LABS_SETTINGS, + ...ACCESSIBILITY_SETTINGS, + ...BANNER_SETTINGS, + ...DISCOVER_SETTINGS, + ...NOTIFICATION_SETTINGS, +]; diff --git a/packages/serverless/settings/common/kibana.jsonc b/packages/serverless/settings/common/kibana.jsonc new file mode 100644 index 0000000000000..ee4ea591c85c5 --- /dev/null +++ b/packages/serverless/settings/common/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/serverless-common-settings", + "owner": "@elastic/appex-sharedux @elastic/platform-deployment-management" +} diff --git a/packages/serverless/settings/common/package.json b/packages/serverless/settings/common/package.json new file mode 100644 index 0000000000000..3674091a63be5 --- /dev/null +++ b/packages/serverless/settings/common/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/serverless-common-settings", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/serverless/settings/common/tsconfig.json b/packages/serverless/settings/common/tsconfig.json new file mode 100644 index 0000000000000..16d6022e3d9bc --- /dev/null +++ b/packages/serverless/settings/common/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-ids", + ] +} diff --git a/packages/serverless/settings/observability_project/README.mdx b/packages/serverless/settings/observability_project/README.mdx new file mode 100644 index 0000000000000..e7c16971c6cb6 --- /dev/null +++ b/packages/serverless/settings/observability_project/README.mdx @@ -0,0 +1,31 @@ +--- +id: serverless/packages/settings/observability_project +slug: /serverless/packages/settings/observability_project +title: Serverless Observability Advanced Settings +description: A package of settings for the Serverless Observability project. +tags: ['serverless', 'package'] +date: 2023-08-24 +--- + +This package contains a list of UI settings that are only available in the Serverless Observability project. +This list is consumed by the `serverless_observability` plugin, which sets up its project settings: + +```ts +export class ServerlessObservabilityPlugin + implements + Plugin< + ServerlessObservabilityPluginSetup, + ServerlessObservabilityPluginStart, + SetupDependencies, + StartDependencies + > +{ + public setup(_coreSetup: CoreSetup, pluginsSetup: SetupDependencies) { + pluginsSetup.serverless.setupProjectSettings(OBSERVABILITY_PROJECT_SETTINGS); + return {}; + } +} +``` + +If you need to register a setting that should be available in the Serverless Observability project, make sure to add +its Id to this list. diff --git a/packages/serverless/settings/observability_project/index.ts b/packages/serverless/settings/observability_project/index.ts new file mode 100644 index 0000000000000..7f76a35e0fcea --- /dev/null +++ b/packages/serverless/settings/observability_project/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as settings from '@kbn/management-settings-ids'; + +export const OBSERVABILITY_PROJECT_SETTINGS = [ + settings.ML_ANOMALY_DETECTION_RESULTS_ENABLE_TIME_DEFAULTS_ID, + settings.ML_ANOMALY_DETECTION_RESULTS_TIME_DEFAULTS_ID, +]; diff --git a/packages/serverless/settings/observability_project/kibana.jsonc b/packages/serverless/settings/observability_project/kibana.jsonc new file mode 100644 index 0000000000000..4df29091e6619 --- /dev/null +++ b/packages/serverless/settings/observability_project/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/serverless-observability-settings", + "owner": "@elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management" +} diff --git a/packages/serverless/settings/observability_project/package.json b/packages/serverless/settings/observability_project/package.json new file mode 100644 index 0000000000000..127cb4621c742 --- /dev/null +++ b/packages/serverless/settings/observability_project/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/serverless-observability-settings", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/serverless/settings/observability_project/tsconfig.json b/packages/serverless/settings/observability_project/tsconfig.json new file mode 100644 index 0000000000000..16d6022e3d9bc --- /dev/null +++ b/packages/serverless/settings/observability_project/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-ids", + ] +} diff --git a/packages/serverless/settings/search_project/README.mdx b/packages/serverless/settings/search_project/README.mdx new file mode 100644 index 0000000000000..a19718c645a82 --- /dev/null +++ b/packages/serverless/settings/search_project/README.mdx @@ -0,0 +1,34 @@ +--- +id: serverless/packages/settings/search_project +slug: /serverless/packages/settings/search_project +title: Serverless Search Advanced Settings +description: A package of settings for the Serverless Search project. +tags: ['serverless', 'package'] +date: 2023-08-24 +--- + +This package contains a list of UI settings that are only available in the Serverless Search project. +This list is consumed by the `serverless_search` plugin, which sets up its project settings: + +```ts +export class ServerlessSearchPlugin + implements + Plugin< + ServerlessSearchPluginSetup, + ServerlessSearchPluginStart, + SetupDependencies, + StartDependencies + > +{ + public setup( + { getStartServices, http }: CoreSetup, + pluginsSetup: SetupDependencies + ) { + pluginsSetup.serverless.setupProjectSettings(SEARCH_PROJECT_SETTINGS); + return {}; + } +} +``` + +If you need to register a setting that should be available in the Serverless Search project, make sure to add +its Id to this list. diff --git a/packages/serverless/settings/search_project/index.ts b/packages/serverless/settings/search_project/index.ts new file mode 100644 index 0000000000000..a26c658501617 --- /dev/null +++ b/packages/serverless/settings/search_project/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX_ID } from '@kbn/management-settings-ids'; + +export const SEARCH_PROJECT_SETTINGS = [COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX_ID]; diff --git a/packages/serverless/settings/search_project/kibana.jsonc b/packages/serverless/settings/search_project/kibana.jsonc new file mode 100644 index 0000000000000..f73b63503ae4d --- /dev/null +++ b/packages/serverless/settings/search_project/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/serverless-search-settings", + "owner": "@elastic/enterprise-search-frontend @elastic/platform-deployment-management" +} diff --git a/packages/serverless/settings/search_project/package.json b/packages/serverless/settings/search_project/package.json new file mode 100644 index 0000000000000..2dc7bf717e612 --- /dev/null +++ b/packages/serverless/settings/search_project/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/serverless-search-settings", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/serverless/settings/search_project/tsconfig.json b/packages/serverless/settings/search_project/tsconfig.json new file mode 100644 index 0000000000000..16d6022e3d9bc --- /dev/null +++ b/packages/serverless/settings/search_project/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-ids", + ] +} diff --git a/packages/serverless/settings/security_project/README.mdx b/packages/serverless/settings/security_project/README.mdx new file mode 100644 index 0000000000000..4d5d788eb84d0 --- /dev/null +++ b/packages/serverless/settings/security_project/README.mdx @@ -0,0 +1,31 @@ +--- +id: serverless/packages/settings/security_project +slug: /serverless/packages/settings/security_project +title: Serverless Security Advanced Settings +description: A package of settings for the Serverless Security project. +tags: ['serverless', 'package'] +date: 2023-08-24 +--- + +This package contains a list of UI settings that are only available in the Serverless Security project. +This list is consumed by the `security_solution_serverless` plugin, which sets up its project settings: + +```ts +export class SecuritySolutionServerlessPlugin + implements + Plugin< + SecuritySolutionServerlessPluginSetup, + SecuritySolutionServerlessPluginStart, + SecuritySolutionServerlessPluginSetupDeps, + SecuritySolutionServerlessPluginStartDeps + > +{ + public setup(coreSetup: CoreSetup, pluginsSetup: SecuritySolutionServerlessPluginSetupDeps) { + pluginsSetup.serverless.setupProjectSettings(SECURITY_PROJECT_SETTINGS); + return {}; + } +} +``` + +If you need to register a setting that should be available in the Serverless Security project, make sure to add +its Id to this list. diff --git a/packages/serverless/settings/security_project/index.ts b/packages/serverless/settings/security_project/index.ts new file mode 100644 index 0000000000000..070a75f163d41 --- /dev/null +++ b/packages/serverless/settings/security_project/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as settings from '@kbn/management-settings-ids'; + +export const SECURITY_PROJECT_SETTINGS = [ + settings.ML_ANOMALY_DETECTION_RESULTS_ENABLE_TIME_DEFAULTS_ID, + settings.ML_ANOMALY_DETECTION_RESULTS_TIME_DEFAULTS_ID, + settings.SECURITY_SOLUTION_REFRESH_INTERVAL_DEFAULTS_ID, + settings.SECURITY_SOLUTION_TIME_DEFAULTS_ID, + settings.SECURITY_SOLUTION_DEFAULT_INDEX_ID, + settings.SECURITY_SOLUTION_DEFAULT_THREAT_INDEX_ID, + settings.SECURITY_SOLUTION_DEFAULT_ANOMALY_SCORE_ID, + settings.SECURITY_SOLUTION_RULES_TABLE_REFRESH_ID, + settings.SECURITY_SOLUTION_IP_REPUTATION_LINKS_ID, + settings.SECURITY_SOLUTION_ENABLE_CCS_WARNING_ID, + settings.SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID, +]; diff --git a/packages/serverless/settings/security_project/kibana.jsonc b/packages/serverless/settings/security_project/kibana.jsonc new file mode 100644 index 0000000000000..818e3068d704c --- /dev/null +++ b/packages/serverless/settings/security_project/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/serverless-security-settings", + "owner": "@elastic/security-solution @elastic/platform-deployment-management" +} diff --git a/packages/serverless/settings/security_project/package.json b/packages/serverless/settings/security_project/package.json new file mode 100644 index 0000000000000..5d58ccbd142b2 --- /dev/null +++ b/packages/serverless/settings/security_project/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/serverless-security-settings", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/serverless/settings/security_project/tsconfig.json b/packages/serverless/settings/security_project/tsconfig.json new file mode 100644 index 0000000000000..16d6022e3d9bc --- /dev/null +++ b/packages/serverless/settings/security_project/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-ids", + ] +} diff --git a/packages/shared-ux/chrome/navigation/mocks/src/navlinks.ts b/packages/shared-ux/chrome/navigation/mocks/src/navlinks.ts index b9b1c287dce73..c3d3b756eaae4 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/navlinks.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/navlinks.ts @@ -62,6 +62,7 @@ const allNavLinks: AppDeepLinkId[] = [ 'ml:fileUpload', 'ml:filterListsSettings', 'ml:indexDataVisualizer', + 'ml:dataComparison', 'ml:logPatternAnalysis', 'ml:logRateAnalysis', 'ml:memoryUsage', diff --git a/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap b/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap index 97d241f6c957f..f7f08fe075f56 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap +++ b/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap @@ -469,6 +469,26 @@ Array [ "renderItem": undefined, "title": "Data view", }, + Object { + "children": undefined, + "deepLink": Object { + "baseUrl": "/mocked", + "href": "http://mocked/ml:dataComparison", + "id": "ml:dataComparison", + "title": "Deeplink ml:dataComparison", + "url": "/mocked/ml:dataComparison", + }, + "href": undefined, + "id": "ml:dataComparison", + "isActive": false, + "path": Array [ + "rootNav:ml", + "data_visualizer", + "ml:dataComparison", + ], + "renderItem": undefined, + "title": "Data comparison", + }, ], "deepLink": undefined, "href": undefined, diff --git a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap index f86ba86999236..b8a983ce582f2 100644 --- a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap +++ b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap @@ -351,7 +351,7 @@ exports[` is rendered 1`] = ` = ({ value, onChange, width, + height = '100px', options, overrideEditorWillMount, editorDidMount, @@ -478,7 +479,7 @@ export const CodeEditor: React.FC = ({ onChange={onChange} width={isFullScreen ? '100vw' : width} // previously defaulted to height which defaulted to 100% but this makes it unviewable - height={isFullScreen ? '100vh' : '100px'} + height={isFullScreen ? '100vh' : height} editorWillMount={_editorWillMount} editorDidMount={_editorDidMount} options={{ diff --git a/packages/shared-ux/page/kibana_template/impl/src/__snapshots__/page_template_inner.test.tsx.snap b/packages/shared-ux/page/kibana_template/impl/src/__snapshots__/page_template_inner.test.tsx.snap index 0588fbfae152d..7a238b54533dd 100644 --- a/packages/shared-ux/page/kibana_template/impl/src/__snapshots__/page_template_inner.test.tsx.snap +++ b/packages/shared-ux/page/kibana_template/impl/src/__snapshots__/page_template_inner.test.tsx.snap @@ -66,7 +66,9 @@ exports[`KibanaPageTemplateInner page sidebar 1`] = ` minHeight={0} offset={0} > - + Test diff --git a/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx b/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx index 607b8fbd1776b..f9b9dcd247de6 100644 --- a/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx +++ b/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC } from 'react'; import classNames from 'classnames'; import { EuiPageTemplate } from '@elastic/eui'; @@ -21,9 +21,6 @@ const getClasses = (template?: string, className?: string) => { ); }; -const KIBANA_CHROME_SELECTOR = '[data-test-subj="kibanaChrome"]'; -const HEADER_GLOBAL_NAV_SELECTOR = '[data-test-subj="headerGlobalNav"]'; - /** * A thin wrapper around EuiPageTemplate with a few Kibana specific additions */ @@ -38,18 +35,6 @@ export const KibanaPageTemplateInner: FC = ({ }) => { let header; - const [offset, setOffset] = useState(); - - useEffect(() => { - const kibanaChrome = document.querySelector(KIBANA_CHROME_SELECTOR) as HTMLElement; - if (kibanaChrome) { - const kibanaChromeHeader = kibanaChrome.querySelector( - HEADER_GLOBAL_NAV_SELECTOR - ) as HTMLElement; - setOffset(kibanaChromeHeader?.offsetTop + kibanaChromeHeader?.offsetHeight); - } - }, []); - if (isEmptyState && pageHeader && !children) { const { iconType, pageTitle, description, rightSideItems } = pageHeader; const title = pageTitle ?

    {pageTitle}

    : undefined; @@ -70,9 +55,7 @@ export const KibanaPageTemplateInner: FC = ({ let sideBar; if (pageSideBar) { const sideBarProps = { ...pageSideBarProps }; - if (offset) { - sideBarProps.sticky = { offset }; - } + sideBarProps.sticky = true; sideBar = {pageSideBar}; } diff --git a/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap b/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap index d31d61c4b8129..f55fc4c110b11 100644 --- a/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap +++ b/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap @@ -52,7 +52,7 @@ exports[`WithSolutionNav renders wrapped component 1`] = ` } pageSideBarProps={ Object { - "className": "css-c34ez9", + "className": "kbnSolutionNav__sidebar css-c34ez9", "minWidth": undefined, "paddingSize": "none", } @@ -112,7 +112,7 @@ exports[`WithSolutionNav with children 1`] = ` } pageSideBarProps={ Object { - "className": "css-c34ez9", + "className": "kbnSolutionNav__sidebar css-c34ez9", "minWidth": undefined, "paddingSize": "none", } diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx index da73befdf519e..2e4879a4093cb 100644 --- a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx +++ b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx @@ -57,9 +57,8 @@ export const withSolutionNav =

    (WrappedComponent: Compo isMediumBreakpoint || (canBeCollapsed && isLargerBreakpoint && !isSideNavOpenOnDesktop); const withSolutionNavStyles = WithSolutionNavStyles(euiTheme); const sideBarClasses = classNames( - { - 'kbnSolutionNav__sidebar--shrink': isSidebarShrunk, - }, + 'kbnSolutionNav__sidebar', + { 'kbnSolutionNav__sidebar--shrink': isSidebarShrunk }, props.pageSideBarProps?.className, withSolutionNavStyles ); diff --git a/renovate.json b/renovate.json index cab03197e4c42..0fd6ad800c03c 100644 --- a/renovate.json +++ b/renovate.json @@ -278,6 +278,7 @@ { "groupName": "platform security modules", "matchPackageNames": [ + "css.escape", "node-forge", "formik", "@types/node-forge", @@ -285,7 +286,8 @@ "tough-cookie", "@types/tough-cookie", "xml-crypto", - "@types/xml-crypto" + "@types/xml-crypto", + "@kayahr/text-encoding" ], "reviewers": [ "team:kibana-security" diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index d03d7294e2806..911eecd45a9fb 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -44,8 +44,30 @@ const getBootstrapScript = (isDev) => { } }; -const setServerlessKibanaDevServiceAccountIfPossible = (set, opts) => { - if (!opts.dev || !opts.serverless || process.env.isDevCliChild === 'true') { +const setServerlessKibanaDevServiceAccountIfPossible = (get, set, opts) => { + const esHosts = [].concat( + get('elasticsearch.hosts', []), + opts.elasticsearch ? opts.elasticsearch.split(',') : [] + ); + + /* + * We only handle the service token if serverless ES is running locally. + * Example would be if the user is running SES in the cloud and KBN serverless + * locally, they would be expected to handle auth on their own and this token + * is likely invalid anyways. + */ + const isESlocalhost = esHosts.length + ? esHosts.some((hostUrl) => { + const parsedUrl = url.parse(hostUrl); + return ( + parsedUrl.hostname === 'localhost' || + parsedUrl.hostname === '127.0.0.1' || + parsedUrl.hostname === 'host.docker.internal' + ); + }) + : true; // default is localhost:9200 + + if (!opts.dev || !opts.serverless || !isESlocalhost) { return; } @@ -86,7 +108,7 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions) { if (opts.dev) { if (opts.serverless) { - setServerlessKibanaDevServiceAccountIfPossible(set, opts); + setServerlessKibanaDevServiceAccountIfPossible(get, set, opts); } if (!has('elasticsearch.serviceAccountToken') && opts.devCredentials !== false) { diff --git a/src/core/public/_css_variables.scss b/src/core/public/_css_variables.scss new file mode 100644 index 0000000000000..cef1be40d1239 --- /dev/null +++ b/src/core/public/_css_variables.scss @@ -0,0 +1,11 @@ +:root { + // height of the header banner + --kbnHeaderBannerHeight: #{$euiSizeXL}; + // total height of all fixed headers (when the banner is *not* present) inherited from EUI + --kbnHeaderOffset: var(--euiFixedHeadersOffset, 0); + // total height of everything when the banner is present + --kbnHeaderOffsetWithBanner: calc(var(--kbnHeaderBannerHeight) + var(--kbnHeaderOffset)); +} + +// Quick note: This shouldn't be mixed with Sass variable declarations, +// as each import will cause :root to be re-declared unnecessarily diff --git a/src/core/public/_mixins.scss b/src/core/public/_mixins.scss index 2dbef465e074e..9d533a87d1843 100644 --- a/src/core/public/_mixins.scss +++ b/src/core/public/_mixins.scss @@ -1,43 +1,6 @@ -@import './variables'; - -/* stylelint-disable-next-line length-zero-no-unit -- need consistent unit to sum them */ -@mixin kibanaFullBodyHeight($additionalOffset: 0px) { - // default - header, no banner - height: calc(100vh - #{$kbnHeaderOffset + $additionalOffset}); - - @at-root { - // no header, no banner - .kbnBody--chromeHidden & { - height: calc(100vh - #{$additionalOffset}); - } - // header, banner - .kbnBody--hasHeaderBanner & { - height: calc(100vh - #{$kbnHeaderOffsetWithBanner + $additionalOffset}); - } - // no header, banner - .kbnBody--chromeHidden.kbnBody--hasHeaderBanner & { - height: calc(100vh - #{$kbnHeaderBannerHeight + $additionalOffset}); - } - } -} - -/* stylelint-disable-next-line length-zero-no-unit -- need consistent unit to sum them */ -@mixin kibanaFullBodyMinHeight($additionalOffset: 0px) { - // default - header, no banner - min-height: calc(100vh - #{$kbnHeaderOffset + $additionalOffset}); - - @at-root { - // no header, no banner - .kbnBody--chromeHidden & { - min-height: calc(100vh - #{$additionalOffset}); - } - // header, banner - .kbnBody--hasHeaderBanner & { - min-height: calc(100vh - #{$kbnHeaderOffsetWithBanner + $additionalOffset}); - } - // no header, banner - .kbnBody--chromeHidden.kbnBody--hasHeaderBanner & { - min-height: calc(100vh - #{$kbnHeaderBannerHeight + $additionalOffset}); - } - } +@mixin kibanaFullBodyHeight($additionalOffset: 0) { + // The `--euiFixedHeadersOffset` CSS variable is automatically updated by + // styles/rendering/_base.scss, based on whether the Kibana chrome has a + // header banner, and is visible or hidden + height: calc(100vh - var(--euiFixedHeadersOffset, 0) - #{$additionalOffset}); } diff --git a/src/core/public/_variables.scss b/src/core/public/_variables.scss deleted file mode 100644 index 6c21c9760be97..0000000000000 --- a/src/core/public/_variables.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import '@elastic/eui/src/global_styling/variables/header'; - -// height of the header banner -$kbnHeaderBannerHeight: $euiSizeXL; // This value is also declared in `/x-pack/plugins/canvas/common/lib/constants.ts` -// total height of the header (when the banner is *not* present) -$kbnHeaderOffset: $euiHeaderHeightCompensation * 2; -// total height of the header when the banner is present -$kbnHeaderOffsetWithBanner: $kbnHeaderOffset + $kbnHeaderBannerHeight; diff --git a/src/core/public/index.scss b/src/core/public/index.scss index c056b0f851801..db05def14bc0c 100644 --- a/src/core/public/index.scss +++ b/src/core/public/index.scss @@ -1,3 +1,3 @@ -@import './variables'; +@import './css_variables'; @import './mixins'; @import './styles/index'; diff --git a/src/core/public/styles/chrome/_banner.scss b/src/core/public/styles/chrome/_banner.scss index 9c521da3f30ca..feb69e54a911f 100644 --- a/src/core/public/styles/chrome/_banner.scss +++ b/src/core/public/styles/chrome/_banner.scss @@ -2,7 +2,7 @@ position: fixed; top: 0; left: 0; - height: $kbnHeaderBannerHeight; + height: var(--kbnHeaderBannerHeight); width: 100%; z-index: $euiZHeader; } @@ -11,32 +11,3 @@ height: 100%; width: 100%; } - -// overriding `top` positioning of the chrome headers -.kbnBody--hasHeaderBanner .header__bars { - .header__firstBar { - top: $kbnHeaderBannerHeight; - } - .header__secondBar { - top: $kbnHeaderBannerHeight + $euiHeaderHeightCompensation; - } -} - -// overriding padding on the body element added by EUI -.kbnBody.kbnBody--hasHeaderBanner.kbnBody--projectLayout.euiBody--headerIsFixed { - padding-top: $kbnHeaderBannerHeight + $euiHeaderHeightCompensation; - - // overriding `top` positioning of the project side nav, and flyouts - // overriding `top` positioning of the project app menu toolbar - &.euiBody--headerIsFixed .euiCollapsibleNav, - &.euiBody--headerIsFixed:not(.euiDataGrid__restrictBody) .euiFlyout, - .header__actionMenu { - top: $kbnHeaderBannerHeight + $euiHeaderHeightCompensation; - } - - // overriding `height` calculation of the project side nav, and flyouts - &.euiBody--headerIsFixed .euiCollapsibleNav, - &.euiBody--headerIsFixed:not(.euiDataGrid__restrictBody) .euiFlyout { - height: calc(100% - #{$kbnHeaderBannerHeight + $euiHeaderHeightCompensation}); - } -} diff --git a/src/core/public/styles/core_app/_mixins.scss b/src/core/public/styles/core_app/_mixins.scss index a801ffd7a2cae..78691f71fe87d 100644 --- a/src/core/public/styles/core_app/_mixins.scss +++ b/src/core/public/styles/core_app/_mixins.scss @@ -1,5 +1,3 @@ -@import '../../variables'; - @mixin flexParent($grow: 1, $shrink: 1, $basis: auto, $direction: column) { flex: $grow $shrink $basis; display: flex; @@ -86,7 +84,7 @@ @at-root { .kbnBody--hasHeaderBanner & { - top: $kbnHeaderBannerHeight; + top: var(--kbnHeaderBannerHeight); } } diff --git a/src/core/public/styles/rendering/_base.scss b/src/core/public/styles/rendering/_base.scss index a9ece9955e6ca..8a7b14242f8bf 100644 --- a/src/core/public/styles/rendering/_base.scss +++ b/src/core/public/styles/rendering/_base.scss @@ -19,7 +19,7 @@ pointer-events: none; visibility: hidden; position: fixed; - top: 0; + top: var(--euiFixedHeadersOffset, 0); right: 0; bottom: 0; left: 0; @@ -35,50 +35,30 @@ position: relative; // This is temporary for apps that relied on this being present on `.application` } -@mixin kbnAffordForHeader($headerHeight) { - @include euiHeaderAffordForFixed($headerHeight); - - #securitySolutionStickyKQL, - #app-fixed-viewport { - top: $headerHeight; - } - - @include euiBreakpoint('xl', 'l') { - .kbnStickyMenu { - position: sticky; - max-height: calc(100vh - #{$headerHeight + $euiSize}); - top: $headerHeight + $euiSize; - } - - .kbnSolutionNav__sidebar { - position: sticky; - max-height: calc(100vh - #{$headerHeight}); - top: $headerHeight; - } - } -} - .kbnBody { - @include kbnAffordForHeader($kbnHeaderOffset); + padding-top: var(--euiFixedHeadersOffset, 0); +} - &.kbnBody--hasHeaderBanner { - padding-top: $kbnHeaderBannerHeight; +// Conditionally override :root CSS fixed header variable. Updating `--euiFixedHeadersOffset` +// on the body will cause all child EUI components to automatically update their offsets - @include kbnAffordForHeader($kbnHeaderOffsetWithBanner); +.kbnBody--hasHeaderBanner { + --euiFixedHeadersOffset: var(--kbnHeaderOffsetWithBanner); - // Prevents banners from covering full screen data grids - .euiDataGrid--fullScreen { - height: calc(100vh - #{$kbnHeaderBannerHeight}); - top: $kbnHeaderBannerHeight; - } - } - &.kbnBody--chromeHidden { - @include kbnAffordForHeader(0); - } - &.kbnBody--projectLayout { - @include kbnAffordForHeader($euiHeaderHeightCompensation); + // Offset fixed EuiHeaders by the top banner + .euiHeader[data-fixed-header] { + margin-top: var(--kbnHeaderBannerHeight); } - &.kbnBody--chromeHidden.kbnBody--hasHeaderBanner { - @include kbnAffordForHeader($kbnHeaderBannerHeight); + + // Prevent banners from covering full screen data grids + .euiDataGrid--fullScreen { + height: calc(100vh - var(--kbnHeaderBannerHeight)); + top: var(--kbnHeaderBannerHeight); } } +.kbnBody--chromeHidden { + --euiFixedHeadersOffset: 0; +} +.kbnBody--chromeHidden.kbnBody--hasHeaderBanner { + --euiFixedHeadersOffset: var(--kbnHeaderBannerHeight); +} diff --git a/src/core/server/integration_tests/elasticsearch/capabilities_serverless.test.ts b/src/core/server/integration_tests/elasticsearch/capabilities_serverless.test.ts index 56754a12daed7..314fa311cf705 100644 --- a/src/core/server/integration_tests/elasticsearch/capabilities_serverless.test.ts +++ b/src/core/server/integration_tests/elasticsearch/capabilities_serverless.test.ts @@ -13,9 +13,7 @@ import { import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { getCapabilitiesFromClient } from '@kbn/core-elasticsearch-server-internal'; -// skipped because test serverless ES nodes are currently using static ports -// causing parallel jest runners to fail for obvious port conflicts reasons. -describe.skip('ES capabilities for serverless ES', () => { +describe('ES capabilities for serverless ES', () => { let serverlessES: TestServerlessESUtils; let client: ElasticsearchClient; diff --git a/src/core/server/integration_tests/http/router.test.ts b/src/core/server/integration_tests/http/router.test.ts index 743624f2f46b3..5408aea01d373 100644 --- a/src/core/server/integration_tests/http/router.test.ts +++ b/src/core/server/integration_tests/http/router.test.ts @@ -568,6 +568,7 @@ describe('Handler', () => { router.get({ path: '/', validate: false }, (context, req, res) => { throw new Error('unexpected error'); }); + await server.start(); const result = await supertest(innerServer.listener).get('/').expect(500); @@ -575,13 +576,9 @@ describe('Handler', () => { expect(result.body.message).toBe( 'An internal server error occurred. Check Kibana server logs for details.' ); - expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` - Array [ - Array [ - [Error: unexpected error], - ], - ] - `); + + const [message] = loggingSystemMock.collect(logger).error[0]; + expect(message).toEqual('500 Server Error'); }); it('captures the error if handler throws', async () => { @@ -617,7 +614,14 @@ describe('Handler', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: Unauthorized], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -639,7 +643,14 @@ describe('Handler', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: Unexpected result from Route Handler. Expected KibanaResponse, but given: string.], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -672,6 +683,21 @@ describe('Handler', () => { message: '[request query.page]: expected value of type [number] but got [string]', statusCode: 400, }); + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "400 Bad Request", + Object { + "http": Object { + "response": Object { + "status_code": 400, + }, + }, + }, + ], + ] + `); }); it('accept to receive an array payload', async () => { @@ -1145,7 +1171,14 @@ describe('Response factory', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: expected 'location' header to be set], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -1551,7 +1584,14 @@ describe('Response factory', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: Unexpected Http status code. Expected from 400 to 599, but given: 200], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -1620,7 +1660,14 @@ describe('Response factory', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: expected 'location' header to be set], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -1760,7 +1807,14 @@ describe('Response factory', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: expected error message to be provided], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -1786,7 +1840,14 @@ describe('Response factory', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: expected error message to be provided], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -1811,7 +1872,14 @@ describe('Response factory', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: options.statusCode is expected to be set. given options: undefined], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); @@ -1836,7 +1904,14 @@ describe('Response factory', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - [Error: Unexpected Http status code. Expected from 100 to 599, but given: 20.], + "500 Server Error", + Object { + "http": Object { + "response": Object { + "status_code": 500, + }, + }, + }, ], ] `); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 90e06ec4cb1f2..5b1c610def783 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -115,17 +115,19 @@ describe('checking migration metadata changes on all registered SO types', () => "lens-ui-telemetry": "8c47a9e393861f76e268345ecbadfc8a5fb1e0bd", "maintenance-window": "d893544460abad56ff7a0e25b78f78776dfe10d1", "map": "76c71023bd198fb6b1163b31bafd926fe2ceb9da", + "metrics-data-source": "81b69dc9830699d9ead5ac8dcb9264612e2a3c89", "metrics-explorer-view": "98cf395d0e87b89ab63f173eae16735584a8ff42", "ml-job": "150e1ab260e87f9963cc99e013304b9c54703dab", "ml-module": "2225cbb4bd508ea5f69db4b848be9d8a74b60198", "ml-trained-model": "482195cefd6b04920e539d34d7356d22cb68e4f3", "monitoring-telemetry": "5d91bf75787d9d4dd2fae954d0b3f76d33d2e559", - "navigation_embeddable": "de71a127ed325261ca6bc926d93c4cd676d17a05", + "navigation_embeddable": "0019fdbbc07547035dac1720a2fe4b42711ed52b", "observability-onboarding-state": "b16064c516aac64ae699c737d7d10b6e199bfded", "osquery-manager-usage-metric": "983bcbc3b7dda0aad29b20907db233abba709bcc", "osquery-pack": "6ab4358ca4304a12dcfc1777c8135b75cffb4397", "osquery-pack-asset": "b14101d3172c4b60eb5404696881ce5275c84152", "osquery-saved-query": "44f1161e165defe3f9b6ad643c68c542a765fcdb", + "policy-settings-protection-updates-note": "33924bb246f9e5bcb876109cc83e3c7a28308352", "query": "21cbbaa09abb679078145ce90087b1e88b7eae95", "risk-engine-configuration": "b105d4a3c6adce40708d729d12e5ef3c8fbd9508", "rules-settings": "892a2918ebaeba809a612b8d97cec0b07c800b5f", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts index dc583d97190a9..a951ecc37d1f4 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts @@ -6,2061 +6,27 @@ * Side Public License, v 1. */ -import Path from 'path'; -import * as Either from 'fp-ts/lib/Either'; -import * as Option from 'fp-ts/lib/Option'; -import { errors } from '@elastic/elasticsearch'; -import type { TaskEither } from 'fp-ts/lib/TaskEither'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { createTestServers, type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; -import { - bulkOverwriteTransformedDocuments, - closePit, - createIndex, - openPit, - type OpenPitResponse, - reindex, - readWithPit, - type EsResponseTooLargeError, - type ReadWithPit, - setWriteBlock, - updateAliases, - waitForReindexTask, - type ReindexResponse, - waitForPickupUpdatedMappingsTask, - pickupUpdatedMappings, - type UpdateByQueryResponse, - updateAndPickupMappings, - type UpdateAndPickupMappingsResponse, - updateMappings, - removeWriteBlock, - transformDocs, - waitForIndexStatus, - initAction, - cloneIndex, - type DocumentsTransformFailed, - type DocumentsTransformSuccess, - MIGRATION_CLIENT_OPTIONS, - createBulkIndexOperationTuple, -} from '@kbn/core-saved-objects-migration-server-internal'; +import { createTestServers } from '@kbn/core-test-helpers-kbn-server'; +import { MIGRATION_CLIENT_OPTIONS } from '@kbn/core-saved-objects-migration-server-internal'; +import { runActionTestSuite } from './actions_test_suite'; const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), settings: { es: { license: 'basic', - dataArchive: Path.resolve(__dirname, '../../archives/7.7.2_xpack_100k_obj.zip'), esArgs: ['http.max_content_length=10Kb'], }, }, }); -let esServer: TestElasticsearchUtils; describe('migration actions', () => { - let client: ElasticsearchClient; - let esCapabilities: ReturnType; - - beforeAll(async () => { - esServer = await startES(); - client = esServer.es.getClient().child(MIGRATION_CLIENT_OPTIONS); - esCapabilities = elasticsearchServiceMock.createCapabilities(); - - // Create test fixture data: - await createIndex({ - client, - indexName: 'existing_index_with_docs', - aliases: ['existing_index_with_docs_alias'], - esCapabilities, - mappings: { - dynamic: true, - properties: { - someProperty: { - type: 'integer', - }, - }, - _meta: { - migrationMappingPropertyHashes: { - references: '7997cf5a56cc02bdc9c93361bde732b0', - }, - }, - }, - })(); - const docs = [ - { _source: { title: 'doc 1' } }, - { _source: { title: 'doc 2' } }, - { _source: { title: 'doc 3' } }, - { _source: { title: 'saved object 4', type: 'another_unused_type' } }, - { _source: { title: 'f-agent-event 5', type: 'f_agent_event' } }, - { _source: { title: new Array(1000).fill('a').join(), type: 'large' } }, // "large" saved object - ] as unknown as SavedObjectsRawDoc[]; - await bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_with_docs', - operations: docs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - })(); - - await createIndex({ - client, - indexName: 'existing_index_2', - mappings: { properties: {} }, - esCapabilities, - })(); - await createIndex({ - client, - indexName: 'existing_index_with_write_block', - mappings: { properties: {} }, - esCapabilities, - })(); - await bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_with_write_block', - operations: docs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - })(); - await setWriteBlock({ client, index: 'existing_index_with_write_block' })(); - await updateAliases({ - client, - aliasActions: [{ add: { index: 'existing_index_2', alias: 'existing_index_2_alias' } }], - })(); - }); - - afterAll(async () => { - await esServer.stop(); - }); - - describe('initAction', () => { - afterAll(async () => { - await client.cluster.putSettings({ - body: { - persistent: { - // Reset persistent test settings - cluster: { routing: { allocation: { enable: null } } }, - }, - }, - }); - }); - it('resolves right empty record if no indices were found', async () => { - expect.assertions(1); - const task = initAction({ client, indices: ['no_such_index'] }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": Object {}, - } - `); - }); - it('resolves right record with found indices', async () => { - expect.assertions(1); - const res = (await initAction({ - client, - indices: ['no_such_index', 'existing_index_with_docs'], - })()) as Either.Right; - - expect(res.right).toEqual( - expect.objectContaining({ - existing_index_with_docs: { - aliases: { - existing_index_with_docs_alias: {}, - }, - mappings: expect.anything(), - settings: expect.anything(), - }, - }) - ); - }); - it('includes the _meta data of the indices in the response', async () => { - expect.assertions(1); - const res = (await initAction({ - client, - indices: ['existing_index_with_docs'], - })()) as Either.Right; - - expect(res.right).toEqual( - expect.objectContaining({ - existing_index_with_docs: { - aliases: { - existing_index_with_docs_alias: {}, - }, - mappings: { - // FIXME https://github.com/elastic/elasticsearch-js/issues/1796 - dynamic: 'true', - properties: expect.anything(), - _meta: { - migrationMappingPropertyHashes: { - references: '7997cf5a56cc02bdc9c93361bde732b0', - }, - }, - }, - settings: expect.anything(), - }, - }) - ); - }); - it('resolves left when cluster.routing.allocation.enabled is incompatible', async () => { - expect.assertions(3); - await client.cluster.putSettings({ - body: { - persistent: { - // Disable all routing allocation - cluster: { routing: { allocation: { enable: 'none' } } }, - }, - }, - }); - const task = initAction({ - client, - indices: ['existing_index_with_docs'], - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "incompatible_cluster_routing_allocation", - }, - } - `); - await client.cluster.putSettings({ - body: { - persistent: { - // Allow routing to existing primaries only - cluster: { routing: { allocation: { enable: 'primaries' } } }, - }, - }, - }); - const task2 = initAction({ - client, - indices: ['existing_index_with_docs'], - }); - await expect(task2()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "incompatible_cluster_routing_allocation", - }, - } - `); - await client.cluster.putSettings({ - body: { - persistent: { - // Allow routing to new primaries only - cluster: { routing: { allocation: { enable: 'new_primaries' } } }, - }, - }, - }); - const task3 = initAction({ - client, - indices: ['existing_index_with_docs'], - }); - await expect(task3()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "incompatible_cluster_routing_allocation", - }, - } - `); - }); - it('resolves right when cluster.routing.allocation.enabled=all', async () => { - expect.assertions(1); - await client.cluster.putSettings({ - body: { - persistent: { - cluster: { routing: { allocation: { enable: 'all' } } }, - }, - }, - }); - const task = initAction({ - client, - indices: ['existing_index_with_docs'], - }); - const result = await task(); - expect(Either.isRight(result)).toBe(true); - }); - }); - - describe('setWriteBlock', () => { - beforeAll(async () => { - await createIndex({ - client, - indexName: 'new_index_without_write_block', - mappings: { properties: {} }, - esCapabilities, - })(); - }); - it('resolves right when setting the write block succeeds', async () => { - expect.assertions(1); - const task = setWriteBlock({ client, index: 'new_index_without_write_block' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "set_write_block_succeeded", - } - `); - }); - it('resolves right when setting a write block on an index that already has one', async () => { - expect.assertions(1); - const task = setWriteBlock({ client, index: 'existing_index_with_write_block' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "set_write_block_succeeded", - } - `); - }); - it('once resolved, prevents further writes to the index', async () => { - expect.assertions(1); - const task = setWriteBlock({ client, index: 'new_index_without_write_block' }); - await task(); - const sourceDocs = [ - { _source: { title: 'doc 1' } }, - { _source: { title: 'doc 2' } }, - { _source: { title: 'doc 3' } }, - { _source: { title: 'doc 4' } }, - ] as unknown as SavedObjectsRawDoc[]; - - const res = (await bulkOverwriteTransformedDocuments({ - client, - index: 'new_index_without_write_block', - operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - })()) as Either.Left; - - expect(res.left).toEqual({ - type: 'target_index_had_write_block', - }); - }); - it('resolves left index_not_found_exception when the index does not exist', async () => { - expect.assertions(1); - const task = setWriteBlock({ client, index: 'no_such_index' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "index": "no_such_index", - "type": "index_not_found_exception", - }, - } - `); - }); - }); - - describe('removeWriteBlock', () => { - beforeAll(async () => { - await createIndex({ - client, - indexName: 'existing_index_without_write_block_2', - mappings: { properties: {} }, - esCapabilities, - })(); - await createIndex({ - client, - indexName: 'existing_index_with_write_block_2', - mappings: { properties: {} }, - esCapabilities, - })(); - await setWriteBlock({ client, index: 'existing_index_with_write_block_2' })(); - }); - it('resolves right if successful when an index already has a write block', async () => { - expect.assertions(1); - const task = removeWriteBlock({ client, index: 'existing_index_with_write_block_2' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "remove_write_block_succeeded", - } - `); - }); - it('resolves right if successful when an index does not have a write block', async () => { - expect.assertions(1); - const task = removeWriteBlock({ client, index: 'existing_index_without_write_block_2' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "remove_write_block_succeeded", - } - `); - }); - it('rejects if there is a non-retryable error', async () => { - expect.assertions(1); - const task = removeWriteBlock({ client, index: 'no_such_index' }); - await expect(task()).rejects.toThrow('index_not_found_exception'); - }); - }); - - describe('waitForIndexStatus', () => { - afterEach(async () => { - try { - await client.indices.delete({ index: 'red_then_yellow_index' }); - await client.indices.delete({ index: 'red_index' }); - } catch (e) { - /** ignore */ - } - }); - it('resolves right after waiting for an index status to be yellow if the index already existed', async () => { - // Create a red index - await client.indices.create( - { - index: 'red_then_yellow_index', - timeout: '5s', - body: { - mappings: { properties: {} }, - settings: { - // Allocate 1 replica so that this index stays yellow - number_of_replicas: '1', - // Disable all shard allocation so that the index status is red - routing: { allocation: { enable: 'none' } }, - }, - }, - }, - { maxRetries: 0 /** handle retry ourselves for now */ } - ); - - // Start tracking the index status - const indexStatusPromise = waitForIndexStatus({ - client, - index: 'red_then_yellow_index', - status: 'yellow', - })(); - - const redStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); - expect(redStatusResponse.status).toBe('red'); - - client.indices.putSettings({ - index: 'red_then_yellow_index', - body: { - // Enable all shard allocation so that the index status turns yellow - routing: { allocation: { enable: 'all' } }, - }, - }); - - await indexStatusPromise; - // Assert that the promise didn't resolve before the index became yellow - - const yellowStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); - expect(yellowStatusResponse.status).toBe('yellow'); - }); - it('resolves left with "index_not_yellow_timeout" after waiting for an index status to be yellow timeout', async () => { - // Create a red index - await client.indices - .create({ - index: 'red_index', - timeout: '5s', - body: { - mappings: { properties: {} }, - settings: { - // Allocate no replicas so that this index stays red - number_of_replicas: '0', - // Disable all shard allocation so that the index status is red - index: { routing: { allocation: { enable: 'none' } } }, - }, - }, - }) - .catch((e) => {}); - // try to wait for index status yellow: - const task = waitForIndexStatus({ - client, - index: 'red_index', - timeout: '1s', - status: 'yellow', - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "message": "[index_not_yellow_timeout] Timeout waiting for the status of the [red_index] index to become 'yellow'", - "type": "index_not_yellow_timeout", - }, - } - `); - }); - - it('resolves left with "index_not_green_timeout" after waiting for an index status to be green timeout', async () => { - // Create a yellow index - await client.indices - .create({ - index: 'yellow_index', - timeout: '5s', - body: { - mappings: { properties: {} }, - settings: { - // Allocate no replicas so that this index stays yellow - number_of_replicas: '0', - }, - }, - }) - .catch((e) => {}); - // try to wait for index status yellow: - const task = waitForIndexStatus({ - client, - index: 'red_index', - timeout: '1s', - status: 'green', - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "message": "[index_not_green_timeout] Timeout waiting for the status of the [red_index] index to become 'green'", - "type": "index_not_green_timeout", - }, - } - `); - }); - }); - - describe('cloneIndex', () => { - afterAll(async () => { - try { - // Restore the default setting of 1000 shards per node - await client.cluster.putSettings({ - persistent: { cluster: { max_shards_per_node: null } }, - }); - await client.indices.delete({ index: 'clone_*' }); - } catch (e) { - /** ignore */ - } - }); - it('resolves right if cloning into a new target index', async () => { - const task = cloneIndex({ - client, - source: 'existing_index_with_write_block', - target: 'clone_target_1', - esCapabilities, - }); - expect.assertions(3); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": Object { - "acknowledged": true, - "shardsAcknowledged": true, - }, - } - `); - const { clone_target_1: cloneTarget1 } = await client.indices.getSettings({ - index: 'clone_target_1', - }); - // @ts-expect-error https://github.com/elastic/elasticsearch/issues/89381 - expect(cloneTarget1.settings?.index.mapping?.total_fields.limit).toBe('1500'); - expect(cloneTarget1.settings?.blocks?.write).toBeUndefined(); - }); - it('resolves right if clone target already existed after waiting for index status to be green ', async () => { - expect.assertions(2); - - // Create a red index that we later turn into green - await client.indices - .create({ - index: 'clone_red_then_green_index', - timeout: '5s', - body: { - mappings: { properties: {} }, - settings: { - // Allocate 1 replica so that this index can go to green - number_of_replicas: '0', - // Disable all shard allocation so that the index status is red - index: { routing: { allocation: { enable: 'none' } } }, - }, - }, - }) - .catch((e) => {}); - - // Call clone even though the index already exists - const cloneIndexPromise = cloneIndex({ - client, - source: 'existing_index_with_write_block', - target: 'clone_red_then_green_index', - esCapabilities, - })(); - - let indexGreen = false; - setTimeout(() => { - client.indices.putSettings({ - index: 'clone_red_then_green_index', - body: { - // Enable all shard allocation so that the index status goes green - routing: { allocation: { enable: 'all' } }, - }, - }); - indexGreen = true; - }, 10); - - await cloneIndexPromise.then((res) => { - // Assert that the promise didn't resolve before the index became green - expect(indexGreen).toBe(true); - expect(res).toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": Object { - "acknowledged": true, - "shardsAcknowledged": true, - }, - } - `); - }); - }); - it('resolves left with a index_not_green_timeout if clone target already exists but takes longer than the specified timeout before turning green', async () => { - // Create a red index - await client.indices - .create({ - index: 'clone_red_index', - timeout: '5s', - body: { - mappings: { properties: {} }, - settings: { - // Allocate 1 replica so that this index stays yellow - number_of_replicas: '1', - // Disable all shard allocation so that the index status is red - index: { routing: { allocation: { enable: 'none' } } }, - }, - }, - }) - .catch((e) => {}); - - // Call clone even though the index already exists - let cloneIndexPromise = cloneIndex({ - client, - source: 'existing_index_with_write_block', - target: 'clone_red_index', - timeout: '1s', - esCapabilities, - })(); - - await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "message": "[index_not_green_timeout] Timeout waiting for the status of the [clone_red_index] index to become 'green'", - "type": "index_not_green_timeout", - }, - } - `); - - // Now make the index yellow and repeat - - await client.indices.putSettings({ - index: 'clone_red_index', - body: { - // Enable all shard allocation so that the index status goes yellow - routing: { allocation: { enable: 'all' } }, - }, - }); - - // Call clone even though the index already exists - cloneIndexPromise = cloneIndex({ - client, - source: 'existing_index_with_write_block', - target: 'clone_red_index', - timeout: '1s', - esCapabilities, - })(); - - await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "message": "[index_not_green_timeout] Timeout waiting for the status of the [clone_red_index] index to become 'green'", - "type": "index_not_green_timeout", - }, - } - `); - - // Now make the index green and it should succeed - - await client.indices.putSettings({ - index: 'clone_red_index', - body: { - // Set zero replicas so status goes green - number_of_replicas: 0, - }, - }); - - // Call clone even though the index already exists - cloneIndexPromise = cloneIndex({ - client, - source: 'existing_index_with_write_block', - target: 'clone_red_index', - timeout: '30s', - esCapabilities, - })(); - - await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": Object { - "acknowledged": true, - "shardsAcknowledged": true, - }, - } - `); - }); - it('resolves left index_not_found_exception if the source index does not exist', async () => { - expect.assertions(1); - const task = cloneIndex({ - client, - source: 'no_such_index', - target: 'clone_target_3', - esCapabilities, - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "index": "no_such_index", - "type": "index_not_found_exception", - }, - } - `); - }); - it('resolves left cluster_shard_limit_exceeded when the action would exceed the maximum normal open shards', async () => { - // Set the max shards per node really low so that any new index that's created would exceed the maximum open shards for this cluster - await client.cluster.putSettings({ persistent: { cluster: { max_shards_per_node: 1 } } }); - const cloneIndexPromise = cloneIndex({ - client, - source: 'existing_index_with_write_block', - target: 'clone_target_4', - esCapabilities, - })(); - await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "cluster_shard_limit_exceeded", - }, - } - `); - }); - }); - - // Reindex doesn't return any errors on it's own, so we have to test - // together with waitForReindexTask - describe('reindex & waitForReindexTask', () => { - it('resolves right when reindex succeeds without reindex script', async () => { - const res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "reindex_succeeded", - } - `); - - const results = await client.search({ index: 'reindex_target', size: 1000 }); - expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) - .toMatchInlineSnapshot(` - Array [ - "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", - "doc 1", - "doc 2", - "doc 3", - "f-agent-event 5", - "saved object 4", - ] - `); - }); - it('resolves right and excludes all documents not matching the excludeOnUpgradeQuery', async () => { - const res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target_excluded_docs', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { - bool: { - must_not: ['f_agent_event', 'another_unused_type'].map((type) => ({ - term: { type }, - })), - }, - }, - batchSize: 1000, - })()) as Either.Right; - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "reindex_succeeded", - } - `); - - const results = await client.search({ index: 'reindex_target_excluded_docs', size: 1000 }); - expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) - .toMatchInlineSnapshot(` - Array [ - "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", - "doc 1", - "doc 2", - "doc 3", - ] - `); - }); - it('resolves right when reindex succeeds with reindex script', async () => { - expect.assertions(2); - const res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target_2', - reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "reindex_succeeded", - } - `); - - const results = await client.search({ index: 'reindex_target_2', size: 1000 }); - expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) - .toMatchInlineSnapshot(` - Array [ - "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", - "doc 1_updated", - "doc 2_updated", - "doc 3_updated", - "f-agent-event 5_updated", - "saved object 4_updated", - ] - `); - }); - it('resolves right, ignores version conflicts and does not update existing docs when reindex multiple times', async () => { - expect.assertions(3); - // Reindex with a script - let res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target_3', - reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - let task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "reindex_succeeded", - } - `); - - // reindex without a script - res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target_3', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "reindex_succeeded", - } - `); - - // Assert that documents weren't overridden by the second, unscripted reindex - const results = await client.search({ index: 'reindex_target_3', size: 1000 }); - expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) - .toMatchInlineSnapshot(` - Array [ - "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", - "doc 1_updated", - "doc 2_updated", - "doc 3_updated", - "f-agent-event 5_updated", - "saved object 4_updated", - ] - `); - }); - it('resolves right and proceeds to add missing documents if there are some existing docs conflicts', async () => { - expect.assertions(2); - // Simulate a reindex that only adds some of the documents from the - // source index into the target index - await createIndex({ - client, - indexName: 'reindex_target_4', - mappings: { properties: {} }, - esCapabilities, - })(); - const response = await client.search({ index: 'existing_index_with_docs', size: 1000 }); - const sourceDocs = (response.hits?.hits as SavedObjectsRawDoc[]) - .slice(0, 2) - .map(({ _id, _source }) => ({ - _id, - _source, - })); - await bulkOverwriteTransformedDocuments({ - client, - index: 'reindex_target_4', - operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - })(); - - // Now do a real reindex - const res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target_4', - reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "reindex_succeeded", - } - `); - // Assert that existing documents weren't overridden, but that missing - // documents were added by the reindex - const results = await client.search({ index: 'reindex_target_4', size: 1000 }); - expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) - .toMatchInlineSnapshot(` - Array [ - "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", - "doc 1", - "doc 2", - "doc 3_updated", - "f-agent-event 5_updated", - "saved object 4_updated", - ] - `); - }); - it('resolves left incompatible_mapping_exception if all reindex failures are due to a strict_dynamic_mapping_exception', async () => { - expect.assertions(1); - // Simulates one instance having completed the UPDATE_TARGET_MAPPINGS - // step which makes the mappings incompatible with outdated documents. - // If another instance then tries a reindex it will get a - // strict_dynamic_mapping_exception even if the documents already exist - // and should ignore this error. - - // Create an index with incompatible mappings - await createIndex({ - client, - indexName: 'reindex_target_5', - mappings: { - dynamic: 'strict', - properties: { - /** no title field */ - }, - }, - esCapabilities, - })(); - - const { - right: { taskId: reindexTaskId }, - } = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target_5', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - const task = waitForReindexTask({ client, taskId: reindexTaskId, timeout: '10s' }); - - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "incompatible_mapping_exception", - }, - } - `); - }); - it('resolves left incompatible_mapping_exception if all reindex failures are due to a mapper_parsing_exception', async () => { - expect.assertions(1); - // Simulates one instance having completed the UPDATE_TARGET_MAPPINGS - // step which makes the mappings incompatible with outdated documents. - // If another instance then tries a reindex it will get a - // strict_dynamic_mapping_exception even if the documents already exist - // and should ignore this error. - - // Create an index with incompatible mappings - await createIndex({ - client, - indexName: 'reindex_target_6', - mappings: { - dynamic: false, - properties: { title: { type: 'integer' } }, // integer is incompatible with string title - }, - esCapabilities, - })(); - - const { - right: { taskId: reindexTaskId }, - } = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'reindex_target_6', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - const task = waitForReindexTask({ client, taskId: reindexTaskId, timeout: '10s' }); - - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "incompatible_mapping_exception", - }, - } - `); - }); - it('resolves left index_not_found_exception if source index does not exist', async () => { - expect.assertions(1); - const res = (await reindex({ - client, - sourceIndex: 'no_such_index', - targetIndex: 'reindex_target', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { - match_all: {}, - }, - batchSize: 1000, - })()) as Either.Right; - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "index": "no_such_index", - "type": "index_not_found_exception", - }, - } - `); - }); - it('resolves left target_index_had_write_block if all failures are due to a write block', async () => { - expect.assertions(1); - const res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'existing_index_with_write_block', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "target_index_had_write_block", - }, - } - `); - }); - it('resolves left if requireAlias=true and the target is not an alias', async () => { - expect.assertions(1); - const res = (await reindex({ - client, - sourceIndex: 'existing_index_with_docs', - targetIndex: 'existing_index_with_write_block', - reindexScript: Option.none, - requireAlias: true, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); - - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "index": "existing_index_with_write_block", - "type": "index_not_found_exception", - }, - } - `); - }); - it('resolves left wait_for_task_completion_timeout when the task does not finish within the timeout', async () => { - await waitForIndexStatus({ - client, - index: '.kibana_1', - status: 'yellow', - })(); - - const res = (await reindex({ - client, - sourceIndex: '.kibana_1', - targetIndex: 'reindex_target', - reindexScript: Option.none, - requireAlias: false, - excludeOnUpgradeQuery: { match_all: {} }, - batchSize: 1000, - })()) as Either.Right; - - const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '0s' }); - - await expect(task()).resolves.toMatchObject({ - _tag: 'Left', - left: { - error: expect.any(errors.ResponseError), - message: expect.stringContaining('[timeout_exception]'), - type: 'wait_for_task_completion_timeout', - }, - }); - }); - }); - - describe('openPit', () => { - it('opens PointInTime for an index', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - expect(pitResponse.right.pitId).toEqual(expect.any(String)); - - const searchResponse = await client.search({ - body: { - pit: { id: pitResponse.right.pitId }, - }, - }); - - await expect(searchResponse.hits.hits.length).toBeGreaterThan(0); - }); - it('rejects if index does not exist', async () => { - const openPitTask = openPit({ client, index: 'no_such_index' }); - await expect(openPitTask()).rejects.toThrow('index_not_found_exception'); - }); - }); - - describe('readWithPit', () => { - it('requests documents from an index using given PIT', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - const readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { match_all: {} }, - batchSize: 1000, - searchAfter: undefined, - }); - const docsResponse = (await readWithPitTask()) as Either.Right; - - await expect(docsResponse.right.outdatedDocuments.length).toBe(6); - }); - - it('requests the batchSize of documents from an index', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - const readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { match_all: {} }, - batchSize: 3, - searchAfter: undefined, - }); - const docsResponse = (await readWithPitTask()) as Either.Right; - - await expect(docsResponse.right.outdatedDocuments.length).toBe(3); - }); - - it('it excludes documents not matching the provided "query"', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - const readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { - bool: { - must_not: [ - { - term: { - type: 'f_agent_event', - }, - }, - { - term: { - type: 'another_unused_type', - }, - }, - ], - }, - }, - batchSize: 1000, - searchAfter: undefined, - }); - - const docsResponse = (await readWithPitTask()) as Either.Right; - - expect(docsResponse.right.outdatedDocuments.map((doc) => doc._source.title).sort()) - .toMatchInlineSnapshot(` - Array [ - "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", - "doc 1", - "doc 2", - "doc 3", - ] - `); - }); - - it('only returns documents that match the provided "query"', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - const readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { - match: { title: { query: 'doc' } }, - }, - batchSize: 1000, - searchAfter: undefined, - }); - - const docsResponse = (await readWithPitTask()) as Either.Right; - - expect(docsResponse.right.outdatedDocuments.map((doc) => doc._source.title).sort()) - .toMatchInlineSnapshot(` - Array [ - "doc 1", - "doc 2", - "doc 3", - ] - `); - }); - - it('returns docs with _seq_no and _primary_term when specified', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - const readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { - match: { title: { query: 'doc' } }, - }, - batchSize: 1000, - searchAfter: undefined, - seqNoPrimaryTerm: true, - }); - - const docsResponse = (await readWithPitTask()) as Either.Right; - - expect(docsResponse.right.outdatedDocuments).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - _seq_no: expect.any(Number), - _primary_term: expect.any(Number), - }), - ]) - ); - }); - - it('does not return docs with _seq_no and _primary_term if not specified', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - const readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { - match: { title: { query: 'doc' } }, - }, - batchSize: 1000, - searchAfter: undefined, - }); - - const docsResponse = (await readWithPitTask()) as Either.Right; - - expect(docsResponse.right.outdatedDocuments).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - _seq_no: expect.any(Number), - _primary_term: expect.any(Number), - }), - ]) - ); - }); - - it('returns a left es_response_too_large error when a read batch exceeds the maxResponseSize', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - let readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { match_all: {} }, - batchSize: 1, // small batch size so we don't exceed the maxResponseSize - searchAfter: undefined, - maxResponseSizeBytes: 500, // set a small size to force the error - }); - const rightResponse = (await readWithPitTask()) as Either.Right; - - await expect(Either.isRight(rightResponse)).toBe(true); - - readWithPitTask = readWithPit({ - client, - pitId: pitResponse.right.pitId, - query: { match_all: {} }, - batchSize: 10, // a bigger batch will exceed the maxResponseSize - searchAfter: undefined, - maxResponseSizeBytes: 500, // set a small size to force the error - }); - const leftResponse = (await readWithPitTask()) as Either.Left; - - expect(leftResponse.left.type).toBe('es_response_too_large'); - // ES response contains a field that indicates how long it took ES to get the response, e.g.: "took": 7 - // if ES takes more than 9ms, the payload will be 1 byte bigger. - // see https://github.com/elastic/kibana/issues/160994 - // Thus, the statements below account for response times up to 99ms - expect(leftResponse.left.contentLength).toBeGreaterThanOrEqual(3184); - expect(leftResponse.left.contentLength).toBeLessThanOrEqual(3185); - }); - - it('rejects if PIT does not exist', async () => { - const readWithPitTask = readWithPit({ - client, - pitId: 'no_such_pit', - query: { match_all: {} }, - batchSize: 1000, - searchAfter: undefined, - }); - await expect(readWithPitTask()).rejects.toThrow('illegal_argument_exception'); - }); - }); - - describe('closePit', () => { - it('closes PointInTime', async () => { - const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); - const pitResponse = (await openPitTask()) as Either.Right; - - const pitId = pitResponse.right.pitId; - await closePit({ client, pitId })(); - - const searchTask = client.search({ - body: { - pit: { id: pitId }, - }, - }); - - await expect(searchTask).rejects.toThrow('search_phase_execution_exception'); - }); - - it('rejects if PIT does not exist', async () => { - const closePitTask = closePit({ client, pitId: 'no_such_pit' }); - await expect(closePitTask()).rejects.toThrow('illegal_argument_exception'); - }); - }); - - describe('transformDocs', () => { - it('applies "transformRawDocs" and returns the transformed documents', async () => { - const originalDocs = [ - { _id: 'foo:1', _source: { type: 'dashboard', value: 1 } }, - { _id: 'foo:2', _source: { type: 'dashboard', value: 2 } }, - ]; - - function innerTransformRawDocs( - docs: SavedObjectsRawDoc[] - ): TaskEither { - return async () => { - const processedDocs: SavedObjectsRawDoc[] = []; - for (const doc of docs) { - doc._source.value += 1; - processedDocs.push(doc); - } - return Either.right({ processedDocs }); - }; - } - - const transformTask = transformDocs({ - transformRawDocs: innerTransformRawDocs, - outdatedDocuments: originalDocs, - }); - - const resultsWithProcessDocs = ( - (await transformTask()) as Either.Right - ).right.processedDocs; - expect(resultsWithProcessDocs.length).toEqual(2); - const foo2 = resultsWithProcessDocs.find((h) => h._id === 'foo:2'); - expect(foo2?._source?.value).toBe(3); - }); - }); - - describe('waitForPickupUpdatedMappingsTask', () => { - it('rejects if there are failures', async () => { - const res = (await pickupUpdatedMappings( - client, - 'existing_index_with_write_block', - 1000 - )()) as Either.Right; - - const task = waitForPickupUpdatedMappingsTask({ - client, - taskId: res.right.taskId, - timeout: '10s', - }); - - // We can't do a snapshot match because the response includes an index - // id which ES assigns dynamically - await expect(task()).rejects.toMatchObject({ - message: - /pickupUpdatedMappings task failed with the following failures:\n\[\{\"index\":\"existing_index_with_write_block\"/, - }); - }); - it('rejects if there is an error', async () => { - const res = (await pickupUpdatedMappings( - client, - 'no_such_index', - 1000 - )()) as Either.Right; - - const task = waitForPickupUpdatedMappingsTask({ - client, - taskId: res.right.taskId, - timeout: '10s', - }); - - await expect(task()).rejects.toThrow('index_not_found_exception'); - }); - - it('resolves left wait_for_task_completion_timeout when the task does not complete within the timeout', async () => { - const res = (await pickupUpdatedMappings( - client, - '.kibana_1', - 1000 - )()) as Either.Right; - - const task = waitForPickupUpdatedMappingsTask({ - client, - taskId: res.right.taskId, - timeout: '0s', - }); - - await expect(task()).resolves.toMatchObject({ - _tag: 'Left', - left: { - error: expect.any(errors.ResponseError), - message: expect.stringContaining('[timeout_exception]'), - type: 'wait_for_task_completion_timeout', - }, - }); - }); - it('resolves right when successful', async () => { - const res = (await pickupUpdatedMappings( - client, - 'existing_index_with_docs', - 1000 - )()) as Either.Right; - - const task = waitForPickupUpdatedMappingsTask({ - client, - taskId: res.right.taskId, - timeout: '10s', - }); - - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "pickup_updated_mappings_succeeded", - } - `); - }); - }); - - describe('updateAndPickupMappings', () => { - it('resolves right when mappings were updated and picked up', async () => { - // Create an index without any mappings and insert documents into it - await createIndex({ - client, - indexName: 'existing_index_without_mappings', - mappings: { - dynamic: false, - properties: {}, - }, - esCapabilities, - })(); - const sourceDocs = [ - { _source: { title: 'doc 1' } }, - { _source: { title: 'doc 2' } }, - { _source: { title: 'doc 3' } }, - { _source: { title: 'doc 4' } }, - ] as unknown as SavedObjectsRawDoc[]; - await bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_without_mappings', - operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - })(); - - // Assert that we can't search over the unmapped fields of the document - - const originalSearchResults = await client.search({ - index: 'existing_index_without_mappings', - size: 1000, - query: { - match: { title: { query: 'doc' } }, - }, - }); - expect(originalSearchResults.hits?.hits.length).toBe(0); - - // Update and pickup mappings so that the title field is searchable - const res = await updateAndPickupMappings({ - client, - index: 'existing_index_without_mappings', - mappings: { - properties: { - title: { type: 'text' }, - }, - }, - batchSize: 1000, - })(); - expect(Either.isRight(res)).toBe(true); - const taskId = (res as Either.Right).right.taskId; - await waitForPickupUpdatedMappingsTask({ client, taskId, timeout: '60s' })(); - - // Repeat the search expecting to be able to find the existing documents - const pickedUpSearchResults = await client.search({ - index: 'existing_index_without_mappings', - size: 1000, - query: { - match: { title: { query: 'doc' } }, - }, - }); - expect(pickedUpSearchResults.hits?.hits.length).toBe(4); - }); - }); - - describe('updateMappings', () => { - it('rejects if ES throws an error', async () => { - const task = updateMappings({ - client, - index: 'no_such_index', - mappings: { - properties: { - created_at: { - type: 'date', - }, - }, - _meta: { - migrationMappingPropertyHashes: { - references: 'updateda56cc02bdc9c93361bupdated', - newReferences: 'fooBarHashMd509387420934879300d9', - }, - }, - }, - })(); - - await expect(task).rejects.toThrow('index_not_found_exception'); - }); - - it('resolves left when the mappings are incompatible', async () => { - const res = await updateMappings({ - client, - index: 'existing_index_with_docs', - mappings: { - properties: { - someProperty: { - type: 'date', // attempt to change an existing field's type in an incompatible fashion - }, - }, - _meta: { - migrationMappingPropertyHashes: { - references: 'updateda56cc02bdc9c93361bupdated', - newReferences: 'fooBarHashMd509387420934879300d9', - }, - }, - }, - })(); - - expect(Either.isLeft(res)).toBe(true); - expect(res).toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "incompatible_mapping_exception", - }, - } - `); - }); - - it('resolves right when mappings are correctly updated', async () => { - const res = await updateMappings({ - client, - index: 'existing_index_with_docs', - mappings: { - properties: { - created_at: { - type: 'date', - }, - }, - _meta: { - migrationMappingPropertyHashes: { - references: 'updateda56cc02bdc9c93361bupdated', - newReferences: 'fooBarHashMd509387420934879300d9', - }, - }, - }, - })(); - - expect(Either.isRight(res)).toBe(true); - - const indices = await client.indices.get({ - index: ['existing_index_with_docs'], - }); - - expect(indices.existing_index_with_docs.mappings?.properties).toEqual( - expect.objectContaining({ - created_at: { - type: 'date', - }, - }) - ); - - expect(indices.existing_index_with_docs.mappings?._meta).toEqual({ - migrationMappingPropertyHashes: { - references: 'updateda56cc02bdc9c93361bupdated', - newReferences: 'fooBarHashMd509387420934879300d9', - }, - }); - }); - }); - - describe('updateAliases', () => { - describe('remove', () => { - it('resolves left index_not_found_exception when the index does not exist', async () => { - const task = updateAliases({ - client, - aliasActions: [ - { - remove: { - alias: 'no_such_alias', - index: 'no_such_index', - must_exist: false, - }, - }, - ], - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "index": "no_such_index", - "type": "index_not_found_exception", - }, - } - `); - }); - describe('with must_exist=false', () => { - it('resolves left alias_not_found_exception when alias does not exist', async () => { - const task = updateAliases({ - client, - aliasActions: [ - { - remove: { - alias: 'no_such_alias', - index: 'existing_index_with_docs', - must_exist: false, - }, - }, - ], - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "alias_not_found_exception", - }, - } - `); - }); - }); - describe('with must_exist=true', () => { - it('resolves left alias_not_found_exception when alias does not exist on specified index', async () => { - const task = updateAliases({ - client, - aliasActions: [ - { - remove: { - alias: 'existing_index_2_alias', - index: 'existing_index_with_docs', - must_exist: true, - }, - }, - ], - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "alias_not_found_exception", - }, - } - `); - }); - it('resolves left alias_not_found_exception when alias does not exist', async () => { - const task = updateAliases({ - client, - aliasActions: [ - { - remove: { - alias: 'no_such_alias', - index: 'existing_index_with_docs', - must_exist: true, - }, - }, - ], - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "alias_not_found_exception", - }, - } - `); - }); - }); - }); - describe('remove_index', () => { - it('left index_not_found_exception if index does not exist', async () => { - const task = updateAliases({ - client, - aliasActions: [ - { - remove_index: { - index: 'no_such_index', - }, - }, - ], - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "index": "no_such_index", - "type": "index_not_found_exception", - }, - } - `); - }); - it('left remove_index_not_a_concrete_index when remove_index targets an alias', async () => { - const task = updateAliases({ - client, - aliasActions: [ - { - remove_index: { - index: 'existing_index_2_alias', - }, - }, - ], - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "remove_index_not_a_concrete_index", - }, - } - `); - }); - }); - }); - - describe('createIndex', () => { - afterEach(async () => { - // Restore the default setting of 1000 shards per node - await client.cluster.putSettings({ persistent: { cluster: { max_shards_per_node: null } } }); - }); - afterAll(async () => { - await client.indices.delete({ index: 'red_then_yellow_index' }).catch(); - await client.indices.delete({ index: 'yellow_then_green_index' }).catch(); - await client.indices.delete({ index: 'create_new_index' }).catch(); - }); - it('resolves right after waiting for an index status to become green when cluster state is not propagated within the timeout', async () => { - // By specifying a very short timeout Elasticsearch will respond before the shard is allocated - const createIndexPromise = createIndex({ - client, - indexName: 'create_new_index', - mappings: undefined as any, - timeout: '1nanos', - esCapabilities, - })(); - await expect(createIndexPromise).resolves.toEqual({ - _tag: 'Right', - right: 'create_index_succeeded', - }); - const { create_new_index: createNewIndex } = await client.indices.getSettings({ - index: 'create_new_index', - }); - // @ts-expect-error https://github.com/elastic/elasticsearch/issues/89381 - expect(createNewIndex.settings?.index?.mapping.total_fields.limit).toBe('1500'); - }); - it('resolves left if an existing index status does not become green', async () => { - expect.assertions(2); - // Create a red index - await client.indices - .create( - { - index: 'red_then_yellow_index', - timeout: '5s', - body: { - mappings: { properties: {} }, - settings: { - // Allocate 1 replica so that this index stays yellow - number_of_replicas: '1', - // Disable all shard allocation so that the index status starts as red - index: { routing: { allocation: { enable: 'none' } } }, - }, - }, - }, - { maxRetries: 0 /** handle retry ourselves for now */ } - ) - .catch((e) => { - /** ignore */ - }); - - // Call createIndex even though the index already exists - const createIndexPromise = createIndex({ - client, - indexName: 'red_then_yellow_index', - mappings: undefined as any, - esCapabilities, - })(); - let indexYellow = false; - - setTimeout(() => { - client.indices.putSettings({ - index: 'red_then_yellow_index', - body: { - // Renable allocation so that the status becomes yellow - routing: { allocation: { enable: 'all' } }, - }, - }); - indexYellow = true; - }, 10); - - await createIndexPromise.then((err) => { - // Assert that the promise didn't resolve before the index became yellow - expect(indexYellow).toBe(true); - expect(err).toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "message": "[index_not_green_timeout] Timeout waiting for the status of the [red_then_yellow_index] index to become 'green'", - "type": "index_not_green_timeout", - }, - } - `); - }); - }); - it('resolves right after waiting for an existing index status to become green', async () => { - expect.assertions(2); - // Create a yellow index - await client.indices - .create({ - index: 'yellow_then_green_index', - timeout: '5s', - body: { - mappings: { properties: {} }, - settings: { - // Allocate 1 replica so that this index stays yellow - number_of_replicas: '1', - }, - }, - }) - .catch((e) => { - /** ignore */ - }); - - // Call createIndex even though the index already exists - const createIndexPromise = createIndex({ - client, - indexName: 'yellow_then_green_index', - mappings: undefined as any, - esCapabilities, - })(); - let indexGreen = false; - - setTimeout(() => { - client.indices.putSettings({ - index: 'yellow_then_green_index', - body: { - // Set 0 replican so that this index becomes green - number_of_replicas: '0', - }, - }); - indexGreen = true; - }, 10); - - await createIndexPromise.then((res) => { - // Assert that the promise didn't resolve before the index became green - expect(indexGreen).toBe(true); - expect(res).toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "index_already_exists", - } - `); - }); - }); - it('resolves left cluster_shard_limit_exceeded when the action would exceed the maximum normal open shards', async () => { - // Set the max shards per node really low so that any new index that's created would exceed the maximum open shards for this cluster - await client.cluster.putSettings({ persistent: { cluster: { max_shards_per_node: 1 } } }); - const createIndexPromise = createIndex({ - client, - indexName: 'create_index_1', - mappings: undefined as any, - esCapabilities, - })(); - await expect(createIndexPromise).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "cluster_shard_limit_exceeded", - }, - } - `); - }); - it('rejects when there is an unexpected error creating the index', async () => { - // Creating an index with the same name as an existing alias to induce - // failure - await expect( - createIndex({ - client, - indexName: 'existing_index_2_alias', - mappings: undefined as any, - esCapabilities, - })() - ).rejects.toThrow('invalid_index_name_exception'); - }); - }); - - describe('bulkOverwriteTransformedDocuments', () => { - it('resolves right when documents do not yet exist in the index', async () => { - const newDocs = [ - { _source: { title: 'doc 5' } }, - { _source: { title: 'doc 6' } }, - { _source: { title: 'doc 7' } }, - ] as unknown as SavedObjectsRawDoc[]; - const task = bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_with_docs', - operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - }); - - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "bulk_index_succeeded", - } - `); - }); - it('resolves right even if there were some version_conflict_engine_exception', async () => { - const response = await client.search({ index: 'existing_index_with_docs', size: 1000 }); - const existingDocs = response.hits?.hits as SavedObjectsRawDoc[]; - - const task = bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_with_docs', - operations: [ - ...existingDocs, - { _source: { title: 'doc 8' } } as unknown as SavedObjectsRawDoc, - ].map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Right", - "right": "bulk_index_succeeded", - } - `); - }); - it('resolves left index_not_found_exception if the index does not exist and useAliasToPreventAutoCreate=true', async () => { - const newDocs = [ - { _source: { title: 'doc 5' } }, - { _source: { title: 'doc 6' } }, - { _source: { title: 'doc 7' } }, - ] as unknown as SavedObjectsRawDoc[]; - await expect( - bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_with_docs_alias_that_does_not_exist', - useAliasToPreventAutoCreate: true, - operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - })() - ).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "index": "existing_index_with_docs_alias_that_does_not_exist", - "type": "index_not_found_exception", - }, - } - `); - }); - it('resolves left target_index_had_write_block if there are write_block errors', async () => { - const newDocs = [ - { _source: { title: 'doc 5' } }, - { _source: { title: 'doc 6' } }, - { _source: { title: 'doc 7' } }, - ] as unknown as SavedObjectsRawDoc[]; - await expect( - bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_with_write_block', - operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), - refresh: 'wait_for', - })() - ).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "target_index_had_write_block", - }, - } - `); - }); - - it('resolves left request_entity_too_large_exception when the payload is too large', async () => { - const newDocs = new Array(10000).fill({ - _source: { - title: - 'how do I create a document thats large enoug to exceed the limits without typing long sentences', - }, - }) as SavedObjectsRawDoc[]; - const task = bulkOverwriteTransformedDocuments({ - client, - index: 'existing_index_with_docs', - operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), - }); - await expect(task()).resolves.toMatchInlineSnapshot(` - Object { - "_tag": "Left", - "left": Object { - "type": "request_entity_too_large_exception", - }, - } - `); - }); + runActionTestSuite({ + startEs: async () => { + const esServer = await startES(); + const client = esServer.es.getClient().child(MIGRATION_CLIENT_OPTIONS); + return { esServer, client }; + }, + environment: 'traditional', }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts new file mode 100644 index 0000000000000..9df98deea3b89 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts @@ -0,0 +1,2097 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Either from 'fp-ts/lib/Either'; +import * as Option from 'fp-ts/lib/Option'; +import { errors } from '@elastic/elasticsearch'; +import type { TaskEither } from 'fp-ts/lib/TaskEither'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { + ElasticsearchClient, + ElasticsearchCapabilities, +} from '@kbn/core-elasticsearch-server'; +import { getCapabilitiesFromClient } from '@kbn/core-elasticsearch-server-internal'; +import { + bulkOverwriteTransformedDocuments, + closePit, + createIndex, + openPit, + type OpenPitResponse, + reindex, + readWithPit, + type EsResponseTooLargeError, + type ReadWithPit, + setWriteBlock, + updateAliases, + waitForReindexTask, + type ReindexResponse, + waitForPickupUpdatedMappingsTask, + pickupUpdatedMappings, + type UpdateByQueryResponse, + updateAndPickupMappings, + type UpdateAndPickupMappingsResponse, + updateMappings, + removeWriteBlock, + transformDocs, + waitForIndexStatus, + initAction, + cloneIndex, + type DocumentsTransformFailed, + type DocumentsTransformSuccess, + createBulkIndexOperationTuple, +} from '@kbn/core-saved-objects-migration-server-internal'; + +interface EsServer { + stop: () => Promise; +} + +type StartEs = () => Promise<{ + esServer: EsServer; + client: ElasticsearchClient; +}>; + +export const runActionTestSuite = ({ + startEs, + environment, +}: { + startEs: StartEs; + environment: 'traditional' | 'serverless'; +}) => { + let esServer: EsServer; + let client: ElasticsearchClient; + let esCapabilities: ElasticsearchCapabilities; + + const runOnTraditionalOnly = (fn: Function) => { + if (environment === 'traditional') { + fn(); + } + }; + + beforeAll(async () => { + const { esServer: _esServer, client: _client } = await startEs(); + esServer = _esServer; + client = _client; + esCapabilities = await getCapabilitiesFromClient(client); + + // Create test fixture data: + await createIndex({ + client, + indexName: 'existing_index_with_docs', + aliases: ['existing_index_with_docs_alias'], + esCapabilities, + mappings: { + dynamic: true, + properties: { + someProperty: { + type: 'integer', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + }, + }, + }, + })(); + const docs = [ + { _source: { title: 'doc 1' } }, + { _source: { title: 'doc 2' } }, + { _source: { title: 'doc 3' } }, + { _source: { title: 'saved object 4', type: 'another_unused_type' } }, + { _source: { title: 'f-agent-event 5', type: 'f_agent_event' } }, + { _source: { title: new Array(1000).fill('a').join(), type: 'large' } }, // "large" saved object + ] as unknown as SavedObjectsRawDoc[]; + await bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_docs', + operations: docs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + })(); + + await createIndex({ + client, + indexName: 'existing_index_2', + mappings: { properties: {} }, + esCapabilities, + })(); + await createIndex({ + client, + indexName: 'existing_index_with_write_block', + mappings: { properties: {} }, + esCapabilities, + })(); + await bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_write_block', + operations: docs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + })(); + await setWriteBlock({ client, index: 'existing_index_with_write_block' })(); + await updateAliases({ + client, + aliasActions: [{ add: { index: 'existing_index_2', alias: 'existing_index_2_alias' } }], + })(); + }); + + afterAll(async () => { + await client.indices.delete({ index: 'existing_index_with_docs' }).catch(() => ({})); + await client.indices.delete({ index: 'existing_index_2' }).catch(() => ({})); + await client.indices.delete({ index: 'existing_index_with_write_block' }).catch(() => ({})); + + await esServer.stop(); + }); + + describe('initAction', () => { + afterAll(async () => { + await client.cluster.putSettings({ + body: { + persistent: { + // Reset persistent test settings + cluster: { routing: { allocation: { enable: null } } }, + }, + }, + }); + }); + it('resolves right empty record if no indices were found', async () => { + expect.assertions(1); + const task = initAction({ client, indices: ['no_such_index'] }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": Object {}, + } + `); + }); + it('resolves right record with found indices', async () => { + expect.assertions(1); + const res = (await initAction({ + client, + indices: ['no_such_index', 'existing_index_with_docs'], + })()) as Either.Right; + + expect(res.right).toEqual( + expect.objectContaining({ + existing_index_with_docs: expect.objectContaining({ + aliases: { + existing_index_with_docs_alias: {}, + }, + mappings: expect.anything(), + settings: expect.anything(), + }), + }) + ); + }); + it('includes the _meta data of the indices in the response', async () => { + expect.assertions(1); + const res = (await initAction({ + client, + indices: ['existing_index_with_docs'], + })()) as Either.Right; + + expect(res.right).toEqual( + expect.objectContaining({ + existing_index_with_docs: expect.objectContaining({ + aliases: { + existing_index_with_docs_alias: {}, + }, + mappings: { + // FIXME https://github.com/elastic/elasticsearch-js/issues/1796 + dynamic: 'true', + properties: expect.anything(), + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + }, + }, + }, + settings: expect.anything(), + }), + }) + ); + }); + it('resolves left when cluster.routing.allocation.enabled is incompatible', async () => { + expect.assertions(3); + await client.cluster.putSettings({ + body: { + persistent: { + // Disable all routing allocation + cluster: { routing: { allocation: { enable: 'none' } } }, + }, + }, + }); + const task = initAction({ + client, + indices: ['existing_index_with_docs'], + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_cluster_routing_allocation", + }, + } + `); + await client.cluster.putSettings({ + body: { + persistent: { + // Allow routing to existing primaries only + cluster: { routing: { allocation: { enable: 'primaries' } } }, + }, + }, + }); + const task2 = initAction({ + client, + indices: ['existing_index_with_docs'], + }); + await expect(task2()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_cluster_routing_allocation", + }, + } + `); + await client.cluster.putSettings({ + body: { + persistent: { + // Allow routing to new primaries only + cluster: { routing: { allocation: { enable: 'new_primaries' } } }, + }, + }, + }); + const task3 = initAction({ + client, + indices: ['existing_index_with_docs'], + }); + await expect(task3()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_cluster_routing_allocation", + }, + } + `); + }); + it('resolves right when cluster.routing.allocation.enabled=all', async () => { + expect.assertions(1); + await client.cluster.putSettings({ + body: { + persistent: { + cluster: { routing: { allocation: { enable: 'all' } } }, + }, + }, + }); + const task = initAction({ + client, + indices: ['existing_index_with_docs'], + }); + const result = await task(); + expect(Either.isRight(result)).toBe(true); + }); + }); + + describe('setWriteBlock', () => { + beforeAll(async () => { + await createIndex({ + client, + indexName: 'new_index_without_write_block', + mappings: { properties: {} }, + esCapabilities, + })(); + }); + it('resolves right when setting the write block succeeds', async () => { + expect.assertions(1); + const task = setWriteBlock({ client, index: 'new_index_without_write_block' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "set_write_block_succeeded", + } + `); + }); + it('resolves right when setting a write block on an index that already has one', async () => { + expect.assertions(1); + const task = setWriteBlock({ client, index: 'existing_index_with_write_block' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "set_write_block_succeeded", + } + `); + }); + it('once resolved, prevents further writes to the index', async () => { + expect.assertions(1); + const task = setWriteBlock({ client, index: 'new_index_without_write_block' }); + await task(); + const sourceDocs = [ + { _source: { title: 'doc 1' } }, + { _source: { title: 'doc 2' } }, + { _source: { title: 'doc 3' } }, + { _source: { title: 'doc 4' } }, + ] as unknown as SavedObjectsRawDoc[]; + + const res = (await bulkOverwriteTransformedDocuments({ + client, + index: 'new_index_without_write_block', + operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + })()) as Either.Left; + + expect(res.left).toEqual({ + type: 'target_index_had_write_block', + }); + }); + it('resolves left index_not_found_exception when the index does not exist', async () => { + expect.assertions(1); + const task = setWriteBlock({ client, index: 'no_such_index' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "index": "no_such_index", + "type": "index_not_found_exception", + }, + } + `); + }); + }); + + describe('removeWriteBlock', () => { + beforeAll(async () => { + await createIndex({ + client, + indexName: 'existing_index_without_write_block_2', + mappings: { properties: {} }, + esCapabilities, + })(); + await createIndex({ + client, + indexName: 'existing_index_with_write_block_2', + mappings: { properties: {} }, + esCapabilities, + })(); + await setWriteBlock({ client, index: 'existing_index_with_write_block_2' })(); + }); + it('resolves right if successful when an index already has a write block', async () => { + expect.assertions(1); + const task = removeWriteBlock({ client, index: 'existing_index_with_write_block_2' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "remove_write_block_succeeded", + } + `); + }); + it('resolves right if successful when an index does not have a write block', async () => { + expect.assertions(1); + const task = removeWriteBlock({ client, index: 'existing_index_without_write_block_2' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "remove_write_block_succeeded", + } + `); + }); + it('rejects if there is a non-retryable error', async () => { + expect.assertions(1); + const task = removeWriteBlock({ client, index: 'no_such_index' }); + await expect(task()).rejects.toThrow('index_not_found_exception'); + }); + }); + + describe('waitForIndexStatus', () => { + afterEach(async () => { + await client.indices.delete({ index: 'red_then_yellow_index' }).catch(() => ({})); + await client.indices.delete({ index: 'red_index' }).catch(() => ({})); + }); + + // routing allocation and number_of_replicas settings not supported on serverless + runOnTraditionalOnly(() => { + it('resolves right after waiting for an index status to be yellow if the index already existed', async () => { + // Create a red index + await client.indices.create( + { + index: 'red_then_yellow_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate 1 replica so that this index stays yellow + number_of_replicas: '1', + // Disable all shard allocation so that the index status is red + routing: { allocation: { enable: 'none' } }, + }, + }, + }, + { maxRetries: 0 /** handle retry ourselves for now */ } + ); + + // Start tracking the index status + const indexStatusPromise = waitForIndexStatus({ + client, + index: 'red_then_yellow_index', + status: 'yellow', + })(); + + const redStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); + expect(redStatusResponse.status).toBe('red'); + + client.indices.putSettings({ + index: 'red_then_yellow_index', + body: { + // Enable all shard allocation so that the index status turns yellow + routing: { allocation: { enable: 'all' } }, + }, + }); + + await indexStatusPromise; + // Assert that the promise didn't resolve before the index became yellow + + const yellowStatusResponse = await client.cluster.health({ + index: 'red_then_yellow_index', + }); + expect(yellowStatusResponse.status).toBe('yellow'); + }); + }); + + it('resolves left with "index_not_yellow_timeout" after waiting for an index status to be yellow timeout', async () => { + // Create a red index + await client.indices + .create({ + index: 'red_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate no replicas so that this index stays red + number_of_replicas: '0', + // Disable all shard allocation so that the index status is red + index: { routing: { allocation: { enable: 'none' } } }, + }, + }, + }) + .catch((e) => {}); + // try to wait for index status yellow: + const task = waitForIndexStatus({ + client, + index: 'red_index', + timeout: '1s', + status: 'yellow', + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "message": "[index_not_yellow_timeout] Timeout waiting for the status of the [red_index] index to become 'yellow'", + "type": "index_not_yellow_timeout", + }, + } + `); + }); + + it('resolves left with "index_not_green_timeout" after waiting for an index status to be green timeout', async () => { + // Create a yellow index + await client.indices + .create({ + index: 'yellow_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate no replicas so that this index stays yellow + number_of_replicas: '0', + }, + }, + }) + .catch((e) => {}); + // try to wait for index status yellow: + const task = waitForIndexStatus({ + client, + index: 'red_index', + timeout: '1s', + status: 'green', + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "message": "[index_not_green_timeout] Timeout waiting for the status of the [red_index] index to become 'green'", + "type": "index_not_green_timeout", + }, + } + `); + }); + }); + + // _clone is blocked on serverless + runOnTraditionalOnly(() => { + describe('cloneIndex', () => { + afterAll(async () => { + try { + // Restore the default setting of 1000 shards per node + await client.cluster.putSettings({ + persistent: { cluster: { max_shards_per_node: null } }, + }); + await client.indices.delete({ index: 'clone_*' }); + } catch (e) { + /** ignore */ + } + }); + it('resolves right if cloning into a new target index', async () => { + const task = cloneIndex({ + client, + source: 'existing_index_with_write_block', + target: 'clone_target_1', + esCapabilities, + }); + expect.assertions(3); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": Object { + "acknowledged": true, + "shardsAcknowledged": true, + }, + } + `); + const { clone_target_1: cloneTarget1 } = await client.indices.getSettings({ + index: 'clone_target_1', + }); + // @ts-expect-error https://github.com/elastic/elasticsearch/issues/89381 + expect(cloneTarget1.settings?.index.mapping?.total_fields.limit).toBe('1500'); + expect(cloneTarget1.settings?.blocks?.write).toBeUndefined(); + }); + it('resolves right if clone target already existed after waiting for index status to be green ', async () => { + expect.assertions(2); + + // Create a red index that we later turn into green + await client.indices + .create({ + index: 'clone_red_then_green_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate 1 replica so that this index can go to green + number_of_replicas: '0', + // Disable all shard allocation so that the index status is red + index: { routing: { allocation: { enable: 'none' } } }, + }, + }, + }) + .catch((e) => {}); + + // Call clone even though the index already exists + const cloneIndexPromise = cloneIndex({ + client, + source: 'existing_index_with_write_block', + target: 'clone_red_then_green_index', + esCapabilities, + })(); + + let indexGreen = false; + setTimeout(() => { + client.indices.putSettings({ + index: 'clone_red_then_green_index', + body: { + // Enable all shard allocation so that the index status goes green + routing: { allocation: { enable: 'all' } }, + }, + }); + indexGreen = true; + }, 10); + + await cloneIndexPromise.then((res) => { + // Assert that the promise didn't resolve before the index became green + expect(indexGreen).toBe(true); + expect(res).toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": Object { + "acknowledged": true, + "shardsAcknowledged": true, + }, + } + `); + }); + }); + it('resolves left with a index_not_green_timeout if clone target already exists but takes longer than the specified timeout before turning green', async () => { + // Create a red index + await client.indices + .create({ + index: 'clone_red_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate 1 replica so that this index stays yellow + number_of_replicas: '1', + // Disable all shard allocation so that the index status is red + index: { routing: { allocation: { enable: 'none' } } }, + }, + }, + }) + .catch((e) => {}); + + // Call clone even though the index already exists + let cloneIndexPromise = cloneIndex({ + client, + source: 'existing_index_with_write_block', + target: 'clone_red_index', + timeout: '1s', + esCapabilities, + })(); + + await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "message": "[index_not_green_timeout] Timeout waiting for the status of the [clone_red_index] index to become 'green'", + "type": "index_not_green_timeout", + }, + } + `); + + // Now make the index yellow and repeat + + await client.indices.putSettings({ + index: 'clone_red_index', + body: { + // Enable all shard allocation so that the index status goes yellow + routing: { allocation: { enable: 'all' } }, + }, + }); + + // Call clone even though the index already exists + cloneIndexPromise = cloneIndex({ + client, + source: 'existing_index_with_write_block', + target: 'clone_red_index', + timeout: '1s', + esCapabilities, + })(); + + await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "message": "[index_not_green_timeout] Timeout waiting for the status of the [clone_red_index] index to become 'green'", + "type": "index_not_green_timeout", + }, + } + `); + + // Now make the index green and it should succeed + + await client.indices.putSettings({ + index: 'clone_red_index', + body: { + // Set zero replicas so status goes green + number_of_replicas: 0, + }, + }); + + // Call clone even though the index already exists + cloneIndexPromise = cloneIndex({ + client, + source: 'existing_index_with_write_block', + target: 'clone_red_index', + timeout: '30s', + esCapabilities, + })(); + + await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": Object { + "acknowledged": true, + "shardsAcknowledged": true, + }, + } + `); + }); + it('resolves left index_not_found_exception if the source index does not exist', async () => { + expect.assertions(1); + const task = cloneIndex({ + client, + source: 'no_such_index', + target: 'clone_target_3', + esCapabilities, + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "index": "no_such_index", + "type": "index_not_found_exception", + }, + } + `); + }); + it('resolves left cluster_shard_limit_exceeded when the action would exceed the maximum normal open shards', async () => { + // Set the max shards per node really low so that any new index that's created would exceed the maximum open shards for this cluster + await client.cluster.putSettings({ persistent: { cluster: { max_shards_per_node: 1 } } }); + const cloneIndexPromise = cloneIndex({ + client, + source: 'existing_index_with_write_block', + target: 'clone_target_4', + esCapabilities, + })(); + await expect(cloneIndexPromise).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "cluster_shard_limit_exceeded", + }, + } + `); + }); + }); + }); + + // Reindex doesn't return any errors on it's own, so we have to test + // together with waitForReindexTask + // Failing: See https://github.com/elastic/kibana/issues/166190 + describe.skip('reindex & waitForReindexTask', () => { + it('resolves right when reindex succeeds without reindex script', async () => { + const res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "reindex_succeeded", + } + `); + + const results = await client.search({ index: 'reindex_target', size: 1000 }); + expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) + .toMatchInlineSnapshot(` + Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", + "doc 1", + "doc 2", + "doc 3", + "f-agent-event 5", + "saved object 4", + ] + `); + }); + it('resolves right and excludes all documents not matching the excludeOnUpgradeQuery', async () => { + const res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_excluded_docs', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { + bool: { + must_not: ['f_agent_event', 'another_unused_type'].map((type) => ({ + term: { type }, + })), + }, + }, + batchSize: 1000, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "reindex_succeeded", + } + `); + + const results = await client.search({ index: 'reindex_target_excluded_docs', size: 1000 }); + expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) + .toMatchInlineSnapshot(` + Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", + "doc 1", + "doc 2", + "doc 3", + ] + `); + }); + it('resolves right when reindex succeeds with reindex script', async () => { + expect.assertions(2); + const res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_2', + reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "reindex_succeeded", + } + `); + + const results = await client.search({ index: 'reindex_target_2', size: 1000 }); + expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) + .toMatchInlineSnapshot(` + Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", + "doc 1_updated", + "doc 2_updated", + "doc 3_updated", + "f-agent-event 5_updated", + "saved object 4_updated", + ] + `); + }); + it('resolves right, ignores version conflicts and does not update existing docs when reindex multiple times', async () => { + expect.assertions(3); + // Reindex with a script + let res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_3', + reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + let task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "reindex_succeeded", + } + `); + + // reindex without a script + res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_3', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "reindex_succeeded", + } + `); + + // Assert that documents weren't overridden by the second, unscripted reindex + const results = await client.search({ index: 'reindex_target_3', size: 1000 }); + expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) + .toMatchInlineSnapshot(` + Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", + "doc 1_updated", + "doc 2_updated", + "doc 3_updated", + "f-agent-event 5_updated", + "saved object 4_updated", + ] + `); + }); + it('resolves right and proceeds to add missing documents if there are some existing docs conflicts', async () => { + expect.assertions(2); + // Simulate a reindex that only adds some of the documents from the + // source index into the target index + await createIndex({ + client, + indexName: 'reindex_target_4', + mappings: { properties: {} }, + esCapabilities, + })(); + const response = await client.search({ index: 'existing_index_with_docs', size: 1000 }); + const sourceDocs = (response.hits?.hits as SavedObjectsRawDoc[]) + .slice(0, 2) + .map(({ _id, _source }) => ({ + _id, + _source, + })); + await bulkOverwriteTransformedDocuments({ + client, + index: 'reindex_target_4', + operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + })(); + + // Now do a real reindex + const res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_4', + reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "reindex_succeeded", + } + `); + // Assert that existing documents weren't overridden, but that missing + // documents were added by the reindex + const results = await client.search({ index: 'reindex_target_4', size: 1000 }); + expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) + .toMatchInlineSnapshot(` + Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", + "doc 1", + "doc 2", + "doc 3_updated", + "f-agent-event 5_updated", + "saved object 4_updated", + ] + `); + }); + it('resolves left incompatible_mapping_exception if all reindex failures are due to a strict_dynamic_mapping_exception', async () => { + expect.assertions(1); + // Simulates one instance having completed the UPDATE_TARGET_MAPPINGS + // step which makes the mappings incompatible with outdated documents. + // If another instance then tries a reindex it will get a + // strict_dynamic_mapping_exception even if the documents already exist + // and should ignore this error. + + // Create an index with incompatible mappings + await createIndex({ + client, + indexName: 'reindex_target_5', + mappings: { + dynamic: 'strict', + properties: { + /** no title field */ + }, + }, + esCapabilities, + })(); + + const { + right: { taskId: reindexTaskId }, + } = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_5', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: reindexTaskId, timeout: '10s' }); + + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_mapping_exception", + }, + } + `); + }); + it('resolves left incompatible_mapping_exception if all reindex failures are due to a mapper_parsing_exception', async () => { + expect.assertions(1); + // Simulates one instance having completed the UPDATE_TARGET_MAPPINGS + // step which makes the mappings incompatible with outdated documents. + // If another instance then tries a reindex it will get a + // strict_dynamic_mapping_exception even if the documents already exist + // and should ignore this error. + + // Create an index with incompatible mappings + await createIndex({ + client, + indexName: 'reindex_target_6', + mappings: { + dynamic: false, + properties: { title: { type: 'integer' } }, // integer is incompatible with string title + }, + esCapabilities, + })(); + + const { + right: { taskId: reindexTaskId }, + } = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_6', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: reindexTaskId, timeout: '10s' }); + + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_mapping_exception", + }, + } + `); + }); + it('resolves left index_not_found_exception if source index does not exist', async () => { + expect.assertions(1); + const res = (await reindex({ + client, + sourceIndex: 'no_such_index', + targetIndex: 'reindex_target', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { + match_all: {}, + }, + batchSize: 1000, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "index": "no_such_index", + "type": "index_not_found_exception", + }, + } + `); + }); + it('resolves left target_index_had_write_block if all failures are due to a write block', async () => { + expect.assertions(1); + const res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'existing_index_with_write_block', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "target_index_had_write_block", + }, + } + `); + }); + it('resolves left if requireAlias=true and the target is not an alias', async () => { + expect.assertions(1); + const res = (await reindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'existing_index_with_write_block', + reindexScript: Option.none, + requireAlias: true, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); + + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "index": "existing_index_with_write_block", + "type": "index_not_found_exception", + }, + } + `); + }); + it('resolves left wait_for_task_completion_timeout when the task does not finish within the timeout', async () => { + await waitForIndexStatus({ + client, + index: '.kibana_1', + status: 'yellow', + })(); + + const res = (await reindex({ + client, + sourceIndex: '.kibana_1', + targetIndex: 'reindex_target', + reindexScript: Option.none, + requireAlias: false, + excludeOnUpgradeQuery: { match_all: {} }, + batchSize: 1000, + })()) as Either.Right; + + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '0s' }); + + await expect(task()).resolves.toMatchObject({ + _tag: 'Left', + left: { + error: expect.any(errors.ResponseError), + message: expect.stringContaining('[timeout_exception]'), + type: 'wait_for_task_completion_timeout', + }, + }); + }); + }); + + describe('openPit', () => { + it('opens PointInTime for an index', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + expect(pitResponse.right.pitId).toEqual(expect.any(String)); + + const searchResponse = await client.search({ + body: { + pit: { id: pitResponse.right.pitId }, + }, + }); + + await expect(searchResponse.hits.hits.length).toBeGreaterThan(0); + }); + it('rejects if index does not exist', async () => { + const openPitTask = openPit({ client, index: 'no_such_index' }); + await expect(openPitTask()).rejects.toThrow('index_not_found_exception'); + }); + }); + + describe('readWithPit', () => { + it('requests documents from an index using given PIT', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + const readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 1000, + searchAfter: undefined, + }); + const docsResponse = (await readWithPitTask()) as Either.Right; + + await expect(docsResponse.right.outdatedDocuments.length).toBe(6); + }); + + it('requests the batchSize of documents from an index', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + const readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 3, + searchAfter: undefined, + }); + const docsResponse = (await readWithPitTask()) as Either.Right; + + await expect(docsResponse.right.outdatedDocuments.length).toBe(3); + }); + + it('it excludes documents not matching the provided "query"', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + const readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { + bool: { + must_not: [ + { + term: { + type: 'f_agent_event', + }, + }, + { + term: { + type: 'another_unused_type', + }, + }, + ], + }, + }, + batchSize: 1000, + searchAfter: undefined, + }); + + const docsResponse = (await readWithPitTask()) as Either.Right; + + expect(docsResponse.right.outdatedDocuments.map((doc) => doc._source.title).sort()) + .toMatchInlineSnapshot(` + Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", + "doc 1", + "doc 2", + "doc 3", + ] + `); + }); + + it('only returns documents that match the provided "query"', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + const readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { + match: { title: { query: 'doc' } }, + }, + batchSize: 1000, + searchAfter: undefined, + }); + + const docsResponse = (await readWithPitTask()) as Either.Right; + + expect(docsResponse.right.outdatedDocuments.map((doc) => doc._source.title).sort()) + .toMatchInlineSnapshot(` + Array [ + "doc 1", + "doc 2", + "doc 3", + ] + `); + }); + + it('returns docs with _seq_no and _primary_term when specified', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + const readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { + match: { title: { query: 'doc' } }, + }, + batchSize: 1000, + searchAfter: undefined, + seqNoPrimaryTerm: true, + }); + + const docsResponse = (await readWithPitTask()) as Either.Right; + + expect(docsResponse.right.outdatedDocuments).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + _seq_no: expect.any(Number), + _primary_term: expect.any(Number), + }), + ]) + ); + }); + + it('does not return docs with _seq_no and _primary_term if not specified', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + const readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { + match: { title: { query: 'doc' } }, + }, + batchSize: 1000, + searchAfter: undefined, + }); + + const docsResponse = (await readWithPitTask()) as Either.Right; + + expect(docsResponse.right.outdatedDocuments).toEqual( + expect.arrayContaining([ + expect.not.objectContaining({ + _seq_no: expect.any(Number), + _primary_term: expect.any(Number), + }), + ]) + ); + }); + + it('returns a left es_response_too_large error when a read batch exceeds the maxResponseSize', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + let readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 1, // small batch size so we don't exceed the maxResponseSize + searchAfter: undefined, + maxResponseSizeBytes: 500, // set a small size to force the error + }); + const rightResponse = (await readWithPitTask()) as Either.Right; + + await expect(Either.isRight(rightResponse)).toBe(true); + + readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 10, // a bigger batch will exceed the maxResponseSize + searchAfter: undefined, + maxResponseSizeBytes: 500, // set a small size to force the error + }); + const leftResponse = (await readWithPitTask()) as Either.Left; + + expect(leftResponse.left.type).toBe('es_response_too_large'); + // ES response contains a field that indicates how long it took ES to get the response, e.g.: "took": 7 + // if ES takes more than 9ms, the payload will be 1 byte bigger. + // see https://github.com/elastic/kibana/issues/160994 + // Thus, the statements below account for response times up to 99ms + expect(leftResponse.left.contentLength).toBeGreaterThanOrEqual(3184); + expect(leftResponse.left.contentLength).toBeLessThanOrEqual(3185); + }); + + it('rejects if PIT does not exist', async () => { + const readWithPitTask = readWithPit({ + client, + pitId: 'no_such_pit', + query: { match_all: {} }, + batchSize: 1000, + searchAfter: undefined, + }); + await expect(readWithPitTask()).rejects.toThrow('illegal_argument_exception'); + }); + }); + + describe('closePit', () => { + it('closes PointInTime', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + const pitId = pitResponse.right.pitId; + await closePit({ client, pitId })(); + + const searchTask = client.search({ + body: { + pit: { id: pitId }, + }, + }); + + await expect(searchTask).rejects.toThrow('search_phase_execution_exception'); + }); + + it('rejects if PIT does not exist', async () => { + const closePitTask = closePit({ client, pitId: 'no_such_pit' }); + await expect(closePitTask()).rejects.toThrow('illegal_argument_exception'); + }); + }); + + describe('transformDocs', () => { + it('applies "transformRawDocs" and returns the transformed documents', async () => { + const originalDocs = [ + { _id: 'foo:1', _source: { type: 'dashboard', value: 1 } }, + { _id: 'foo:2', _source: { type: 'dashboard', value: 2 } }, + ]; + + function innerTransformRawDocs( + docs: SavedObjectsRawDoc[] + ): TaskEither { + return async () => { + const processedDocs: SavedObjectsRawDoc[] = []; + for (const doc of docs) { + doc._source.value += 1; + processedDocs.push(doc); + } + return Either.right({ processedDocs }); + }; + } + + const transformTask = transformDocs({ + transformRawDocs: innerTransformRawDocs, + outdatedDocuments: originalDocs, + }); + + const resultsWithProcessDocs = ( + (await transformTask()) as Either.Right + ).right.processedDocs; + expect(resultsWithProcessDocs.length).toEqual(2); + const foo2 = resultsWithProcessDocs.find((h) => h._id === 'foo:2'); + expect(foo2?._source?.value).toBe(3); + }); + }); + + // Failing: See https://github.com/elastic/kibana/issues/166199 + describe.skip('waitForPickupUpdatedMappingsTask', () => { + it('rejects if there are failures', async () => { + const res = (await pickupUpdatedMappings( + client, + 'existing_index_with_write_block', + 1000 + )()) as Either.Right; + + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '10s', + }); + + // We can't do a snapshot match because the response includes an index + // id which ES assigns dynamically + await expect(task()).rejects.toMatchObject({ + message: + /pickupUpdatedMappings task failed with the following failures:\n\[\{\"index\":\"existing_index_with_write_block\"/, + }); + }); + it('rejects if there is an error', async () => { + const res = (await pickupUpdatedMappings( + client, + 'no_such_index', + 1000 + )()) as Either.Right; + + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '10s', + }); + + await expect(task()).rejects.toThrow('index_not_found_exception'); + }); + + it('resolves left wait_for_task_completion_timeout when the task does not complete within the timeout', async () => { + const res = (await pickupUpdatedMappings( + client, + '.kibana_1', + 1000 + )()) as Either.Right; + + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '0s', + }); + + await expect(task()).resolves.toMatchObject({ + _tag: 'Left', + left: { + error: expect.any(errors.ResponseError), + message: expect.stringContaining('[timeout_exception]'), + type: 'wait_for_task_completion_timeout', + }, + }); + }); + it('resolves right when successful', async () => { + const res = (await pickupUpdatedMappings( + client, + 'existing_index_with_docs', + 1000 + )()) as Either.Right; + + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '10s', + }); + + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "pickup_updated_mappings_succeeded", + } + `); + }); + }); + + describe('updateAndPickupMappings', () => { + it('resolves right when mappings were updated and picked up', async () => { + // Create an index without any mappings and insert documents into it + await createIndex({ + client, + indexName: 'existing_index_without_mappings', + mappings: { + dynamic: false, + properties: {}, + }, + esCapabilities, + })(); + const sourceDocs = [ + { _source: { title: 'doc 1' } }, + { _source: { title: 'doc 2' } }, + { _source: { title: 'doc 3' } }, + { _source: { title: 'doc 4' } }, + ] as unknown as SavedObjectsRawDoc[]; + await bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_without_mappings', + operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + })(); + + // Assert that we can't search over the unmapped fields of the document + + const originalSearchResults = await client.search({ + index: 'existing_index_without_mappings', + size: 1000, + query: { + match: { title: { query: 'doc' } }, + }, + }); + expect(originalSearchResults.hits?.hits.length).toBe(0); + + // Update and pickup mappings so that the title field is searchable + const res = await updateAndPickupMappings({ + client, + index: 'existing_index_without_mappings', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + batchSize: 1000, + })(); + expect(Either.isRight(res)).toBe(true); + const taskId = (res as Either.Right).right.taskId; + await waitForPickupUpdatedMappingsTask({ client, taskId, timeout: '60s' })(); + + // Repeat the search expecting to be able to find the existing documents + const pickedUpSearchResults = await client.search({ + index: 'existing_index_without_mappings', + size: 1000, + query: { + match: { title: { query: 'doc' } }, + }, + }); + expect(pickedUpSearchResults.hits?.hits.length).toBe(4); + }); + }); + + describe('updateMappings', () => { + it('rejects if ES throws an error', async () => { + const task = updateMappings({ + client, + index: 'no_such_index', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, + }, + }, + })(); + + await expect(task).rejects.toThrow('index_not_found_exception'); + }); + + it('resolves left when the mappings are incompatible', async () => { + const res = await updateMappings({ + client, + index: 'existing_index_with_docs', + mappings: { + properties: { + someProperty: { + type: 'date', // attempt to change an existing field's type in an incompatible fashion + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, + }, + }, + })(); + + expect(Either.isLeft(res)).toBe(true); + expect(res).toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_mapping_exception", + }, + } + `); + }); + + it('resolves right when mappings are correctly updated', async () => { + const res = await updateMappings({ + client, + index: 'existing_index_with_docs', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, + }, + }, + })(); + + expect(Either.isRight(res)).toBe(true); + + const indices = await client.indices.get({ + index: ['existing_index_with_docs'], + }); + + expect(indices.existing_index_with_docs.mappings?.properties).toEqual( + expect.objectContaining({ + created_at: { + type: 'date', + }, + }) + ); + + expect(indices.existing_index_with_docs.mappings?._meta).toEqual({ + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, + }); + }); + }); + + describe('updateAliases', () => { + describe('remove', () => { + it('resolves left index_not_found_exception when the index does not exist', async () => { + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'no_such_alias', + index: 'no_such_index', + must_exist: false, + }, + }, + ], + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "index": "no_such_index", + "type": "index_not_found_exception", + }, + } + `); + }); + describe('with must_exist=false', () => { + it('resolves left alias_not_found_exception when alias does not exist', async () => { + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'no_such_alias', + index: 'existing_index_with_docs', + must_exist: false, + }, + }, + ], + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "alias_not_found_exception", + }, + } + `); + }); + }); + describe('with must_exist=true', () => { + it('resolves left alias_not_found_exception when alias does not exist on specified index', async () => { + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'existing_index_2_alias', + index: 'existing_index_with_docs', + must_exist: true, + }, + }, + ], + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "alias_not_found_exception", + }, + } + `); + }); + it('resolves left alias_not_found_exception when alias does not exist', async () => { + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'no_such_alias', + index: 'existing_index_with_docs', + must_exist: true, + }, + }, + ], + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "alias_not_found_exception", + }, + } + `); + }); + }); + }); + describe('remove_index', () => { + it('left index_not_found_exception if index does not exist', async () => { + const task = updateAliases({ + client, + aliasActions: [ + { + remove_index: { + index: 'no_such_index', + }, + }, + ], + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "index": "no_such_index", + "type": "index_not_found_exception", + }, + } + `); + }); + it('left remove_index_not_a_concrete_index when remove_index targets an alias', async () => { + const task = updateAliases({ + client, + aliasActions: [ + { + remove_index: { + index: 'existing_index_2_alias', + }, + }, + ], + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "remove_index_not_a_concrete_index", + }, + } + `); + }); + }); + }); + + describe('createIndex', () => { + afterEach(async () => { + // Restore the default setting of 1000 shards per node + await client.cluster.putSettings({ persistent: { cluster: { max_shards_per_node: null } } }); + }); + afterAll(async () => { + await client.indices.delete({ index: 'red_then_yellow_index' }).catch(() => ({})); + await client.indices.delete({ index: 'yellow_then_green_index' }).catch(() => ({})); + await client.indices.delete({ index: 'create_new_index' }).catch(() => ({})); + }); + it('resolves right after waiting for an index status to become green when cluster state is not propagated within the timeout', async () => { + // By specifying a very short timeout Elasticsearch will respond before the shard is allocated + const createIndexPromise = createIndex({ + client, + indexName: 'create_new_index', + mappings: undefined as any, + timeout: '1nanos', + esCapabilities, + })(); + await expect(createIndexPromise).resolves.toEqual({ + _tag: 'Right', + right: 'create_index_succeeded', + }); + const { create_new_index: createNewIndex } = await client.indices.getSettings({ + index: 'create_new_index', + }); + // @ts-expect-error https://github.com/elastic/elasticsearch/issues/89381 + expect(createNewIndex.settings?.index?.mapping.total_fields.limit).toBe('1500'); + }); + + // number_of_replicas and routing allocation not available on serverless + runOnTraditionalOnly(() => { + it('resolves left if an existing index status does not become green', async () => { + expect.assertions(2); + // Create a red index + await client.indices + .create( + { + index: 'red_then_yellow_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate 1 replica so that this index stays yellow + number_of_replicas: '1', + // Disable all shard allocation so that the index status starts as red + index: { routing: { allocation: { enable: 'none' } } }, + }, + }, + }, + { maxRetries: 0 /** handle retry ourselves for now */ } + ) + .catch((e) => { + /** ignore */ + }); + + // Call createIndex even though the index already exists + const createIndexPromise = createIndex({ + client, + indexName: 'red_then_yellow_index', + mappings: undefined as any, + esCapabilities, + })(); + let indexYellow = false; + + setTimeout(() => { + client.indices.putSettings({ + index: 'red_then_yellow_index', + body: { + // Renable allocation so that the status becomes yellow + routing: { allocation: { enable: 'all' } }, + }, + }); + indexYellow = true; + }, 10); + + await createIndexPromise.then((err) => { + // Assert that the promise didn't resolve before the index became yellow + expect(indexYellow).toBe(true); + expect(err).toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "message": "[index_not_green_timeout] Timeout waiting for the status of the [red_then_yellow_index] index to become 'green'", + "type": "index_not_green_timeout", + }, + } + `); + }); + }); + it('resolves right after waiting for an existing index status to become green', async () => { + expect.assertions(2); + // Create a yellow index + await client.indices + .create({ + index: 'yellow_then_green_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate 1 replica so that this index stays yellow + number_of_replicas: '1', + }, + }, + }) + .catch((e) => { + /** ignore */ + }); + + // Call createIndex even though the index already exists + const createIndexPromise = createIndex({ + client, + indexName: 'yellow_then_green_index', + mappings: undefined as any, + esCapabilities, + })(); + let indexGreen = false; + + setTimeout(() => { + client.indices.putSettings({ + index: 'yellow_then_green_index', + body: { + // Set 0 replican so that this index becomes green + number_of_replicas: '0', + }, + }); + indexGreen = true; + }, 10); + + await createIndexPromise.then((res) => { + // Assert that the promise didn't resolve before the index became green + expect(indexGreen).toBe(true); + expect(res).toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "index_already_exists", + } + `); + }); + }); + }); + + it('resolves left cluster_shard_limit_exceeded when the action would exceed the maximum normal open shards', async () => { + // Set the max shards per node really low so that any new index that's created would exceed the maximum open shards for this cluster + await client.cluster.putSettings({ persistent: { cluster: { max_shards_per_node: 1 } } }); + const createIndexPromise = createIndex({ + client, + indexName: 'create_index_1', + mappings: undefined as any, + esCapabilities, + })(); + await expect(createIndexPromise).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "cluster_shard_limit_exceeded", + }, + } + `); + }); + it('rejects when there is an unexpected error creating the index', async () => { + // Creating an index with the same name as an existing alias to induce + // failure + await expect( + createIndex({ + client, + indexName: 'existing_index_2_alias', + mappings: undefined as any, + esCapabilities, + })() + ).rejects.toThrow('invalid_index_name_exception'); + }); + }); + + describe('bulkOverwriteTransformedDocuments', () => { + it('resolves right when documents do not yet exist in the index', async () => { + const newDocs = [ + { _source: { title: 'doc 5' } }, + { _source: { title: 'doc 6' } }, + { _source: { title: 'doc 7' } }, + ] as unknown as SavedObjectsRawDoc[]; + const task = bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_docs', + operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + }); + + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "bulk_index_succeeded", + } + `); + }); + it('resolves right even if there were some version_conflict_engine_exception', async () => { + const response = await client.search({ index: 'existing_index_with_docs', size: 1000 }); + const existingDocs = response.hits?.hits as SavedObjectsRawDoc[]; + + const task = bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_docs', + operations: [ + ...existingDocs, + { _source: { title: 'doc 8' } } as unknown as SavedObjectsRawDoc, + ].map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "bulk_index_succeeded", + } + `); + }); + it('resolves left index_not_found_exception if the index does not exist and useAliasToPreventAutoCreate=true', async () => { + const newDocs = [ + { _source: { title: 'doc 5' } }, + { _source: { title: 'doc 6' } }, + { _source: { title: 'doc 7' } }, + ] as unknown as SavedObjectsRawDoc[]; + await expect( + bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_docs_alias_that_does_not_exist', + useAliasToPreventAutoCreate: true, + operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + })() + ).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "index": "existing_index_with_docs_alias_that_does_not_exist", + "type": "index_not_found_exception", + }, + } + `); + }); + it('resolves left target_index_had_write_block if there are write_block errors', async () => { + const newDocs = [ + { _source: { title: 'doc 5' } }, + { _source: { title: 'doc 6' } }, + { _source: { title: 'doc 7' } }, + ] as unknown as SavedObjectsRawDoc[]; + await expect( + bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_write_block', + operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), + refresh: 'wait_for', + })() + ).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "target_index_had_write_block", + }, + } + `); + }); + + // no way to configure http.max_content_length on the serverless instance for now. + runOnTraditionalOnly(() => { + it('resolves left request_entity_too_large_exception when the payload is too large', async () => { + const newDocs = new Array(10000).fill({ + _source: { + title: + 'how do I create a document thats large enoug to exceed the limits without typing long sentences', + }, + }) as SavedObjectsRawDoc[]; + const task = bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_docs', + operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), + }); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "request_entity_too_large_exception", + }, + } + `); + }); + }); + }); +}; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts index b723ca1b62608..d663dc15e9a1c 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts @@ -125,7 +125,9 @@ describe('incompatible_cluster_routing_allocation', () => { await root.preboot(); await root.setup(); - root.start(); + root.start().catch(() => { + // Silent catch because the test might be done and call shutdown before starting is completed, causing unwanted thrown errors. + }); // Wait for the INIT -> INIT action retry await retryAsync( diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index 3235c6cfa057e..1809437f3cdcd 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -84,6 +84,7 @@ const previouslyRegisteredTypes = [ 'maintenance-window', 'map', 'maps-telemetry', + 'metrics-data-source', 'metrics-explorer-view', 'ml-job', 'ml-trained-model', @@ -96,6 +97,7 @@ const previouslyRegisteredTypes = [ 'osquery-saved-query', 'osquery-usage-metric', 'osquery-manager-usage-metric', + 'policy-settings-protection-updates-note', 'query', 'rules-settings', 'sample-data-telemetry', diff --git a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts index e6e7f677b898e..ae9a636466bae 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts @@ -235,6 +235,7 @@ describe('split .kibana index into multiple system indices', () => { "lens-ui-telemetry", "maintenance-window", "map", + "metrics-data-source", "metrics-explorer-view", "ml-job", "ml-module", @@ -246,6 +247,7 @@ describe('split .kibana index into multiple system indices', () => { "osquery-pack", "osquery-pack-asset", "osquery-saved-query", + "policy-settings-protection-updates-note", "query", "risk-engine-configuration", "rules-settings", diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index 6249137d8e7be..a911fcdbdead5 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -16,7 +16,6 @@ import { ConfigService, Env } from '@kbn/config'; import { getEnvOptions } from '@kbn/config-mocks'; import { REPO_ROOT } from '@kbn/repo-info'; import { KibanaMigrator } from '@kbn/core-saved-objects-migration-server-internal'; -import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { SavedObjectConfig, type SavedObjectsConfigType, @@ -30,6 +29,7 @@ import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-inter import { ElasticsearchConfig, type ElasticsearchConfigType, + getCapabilitiesFromClient, } from '@kbn/core-elasticsearch-server-internal'; import { AgentManager, configureClient } from '@kbn/core-elasticsearch-client-server-internal'; import { type LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server-internal'; @@ -276,6 +276,7 @@ interface GetMigratorParams { kibanaBranch: string; nodeRoles: NodeRoles; } + const getMigrator = async ({ configService, client, @@ -300,6 +301,8 @@ const getMigrator = async ({ links: getDocLinks({ kibanaBranch }), }; + const esCapabilities = await getCapabilitiesFromClient(client); + return new KibanaMigrator({ client, kibanaIndex, @@ -311,7 +314,7 @@ const getMigrator = async ({ docLinks, waitForMigrationCompletion: false, // ensure we have an active role in the migration nodeRoles, - esCapabilities: elasticsearchServiceMock.createCapabilities(), + esCapabilities, }); }; diff --git a/src/core/server/integration_tests/saved_objects/migrations/shared_suites/zdt/basic_document_migration.ts b/src/core/server/integration_tests/saved_objects/migrations/shared_suites/zdt/basic_document_migration.ts new file mode 100644 index 0000000000000..0c43a40478d03 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/shared_suites/zdt/basic_document_migration.ts @@ -0,0 +1,247 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs/promises'; +import { range, sortBy } from 'lodash'; +import { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; +import '../../jest_matchers'; +import { getKibanaMigratorTestKit } from '../../kibana_migrator_test_kit'; +import { delay, parseLogFile } from '../../test_utils'; +import { EsRunner, EsServer } from '../../test_types'; +import { + getBaseMigratorParams, + getSampleAType, + getSampleBType, +} from '../../fixtures/zdt_base.fixtures'; + +export function createBasicDocumentsMigrationTest({ + startES, + logFilePath, +}: { + startES: EsRunner; + logFilePath: string; +}) { + let esServer: EsServer; + + beforeAll(async () => { + await fs.unlink(logFilePath).catch(() => {}); + esServer = await startES(); + }); + + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); + + const createBaseline = async () => { + const { runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({ + ...getBaseMigratorParams(), + types: [getSampleAType(), getSampleBType()], + }); + await runMigrations(); + + const sampleAObjs = range(5).map((number) => ({ + id: `a-${number}`, + type: 'sample_a', + attributes: { + keyword: `a_${number}`, + boolean: true, + }, + })); + + await savedObjectsRepository.bulkCreate(sampleAObjs); + + const sampleBObjs = range(5).map((number) => ({ + id: `b-${number}`, + type: 'sample_b', + attributes: { + text: `i am number ${number}`, + text2: `some static text`, + }, + })); + + await savedObjectsRepository.bulkCreate(sampleBObjs); + }; + + it('migrates the documents', async () => { + await createBaseline(); + + const typeA = getSampleAType(); + const typeB = getSampleBType(); + + // typeA -> we add a new field and bump the model version by one with a migration + + typeA.mappings.properties = { + ...typeA.mappings.properties, + someAddedField: { type: 'keyword' }, + }; + + typeA.modelVersions = { + ...typeA.modelVersions, + '2': { + changes: [ + { + type: 'data_backfill', + backfillFn: (doc) => { + return { + attributes: { + someAddedField: `${doc.attributes.keyword}-mig`, + }, + }; + }, + }, + { + type: 'mappings_addition', + addedMappings: { + someAddedField: { type: 'keyword' }, + }, + }, + ], + }, + }; + + // typeB -> we add two new model version with migrations + + typeB.modelVersions = { + ...typeB.modelVersions, + '2': { + changes: [ + { + type: 'data_backfill', + backfillFn: (doc) => { + return { + attributes: { + text2: `${doc.attributes.text2} - mig2`, + }, + }; + }, + }, + ], + }, + '3': { + changes: [ + { + type: 'data_backfill', + backfillFn: (doc) => { + return { + attributes: { + text2: `${doc.attributes.text2} - mig3`, + }, + }; + }, + }, + ], + }, + }; + + const { runMigrations, client, savedObjectsRepository } = await getKibanaMigratorTestKit({ + ...getBaseMigratorParams(), + logFilePath, + types: [typeA, typeB], + }); + + await runMigrations(); + + const indices = await client.indices.get({ index: '.kibana*' }); + expect(Object.keys(indices)).toEqual(['.kibana_1']); + + const index = indices['.kibana_1']; + const mappings = index.mappings ?? {}; + const mappingMeta = mappings._meta ?? {}; + + expect(mappings.properties).toEqual( + expect.objectContaining({ + sample_a: typeA.mappings, + sample_b: typeB.mappings, + }) + ); + + expect(mappingMeta.docVersions).toEqual({ + sample_a: '10.2.0', + sample_b: '10.3.0', + }); + + const { saved_objects: sampleADocs } = await savedObjectsRepository.find({ type: 'sample_a' }); + const { saved_objects: sampleBDocs } = await savedObjectsRepository.find({ type: 'sample_b' }); + + expect(sampleADocs).toHaveLength(5); + expect(sampleBDocs).toHaveLength(5); + + const sampleAData = sortBy(sampleADocs, 'id').map((object) => ({ + id: object.id, + type: object.type, + attributes: object.attributes, + })); + + expect(sampleAData).toEqual([ + { + id: 'a-0', + type: 'sample_a', + attributes: { boolean: true, keyword: 'a_0', someAddedField: 'a_0-mig' }, + }, + { + id: 'a-1', + type: 'sample_a', + attributes: { boolean: true, keyword: 'a_1', someAddedField: 'a_1-mig' }, + }, + { + id: 'a-2', + type: 'sample_a', + attributes: { boolean: true, keyword: 'a_2', someAddedField: 'a_2-mig' }, + }, + { + id: 'a-3', + type: 'sample_a', + attributes: { boolean: true, keyword: 'a_3', someAddedField: 'a_3-mig' }, + }, + { + id: 'a-4', + type: 'sample_a', + attributes: { boolean: true, keyword: 'a_4', someAddedField: 'a_4-mig' }, + }, + ]); + + const sampleBData = sortBy(sampleBDocs, 'id').map((object) => ({ + id: object.id, + type: object.type, + attributes: object.attributes, + })); + + expect(sampleBData).toEqual([ + { + id: 'b-0', + type: 'sample_b', + attributes: { text: 'i am number 0', text2: 'some static text - mig2 - mig3' }, + }, + { + id: 'b-1', + type: 'sample_b', + attributes: { text: 'i am number 1', text2: 'some static text - mig2 - mig3' }, + }, + { + id: 'b-2', + type: 'sample_b', + attributes: { text: 'i am number 2', text2: 'some static text - mig2 - mig3' }, + }, + { + id: 'b-3', + type: 'sample_b', + attributes: { text: 'i am number 3', text2: 'some static text - mig2 - mig3' }, + }, + { + id: 'b-4', + type: 'sample_b', + attributes: { text: 'i am number 4', text2: 'some static text - mig2 - mig3' }, + }, + ]); + + const records = await parseLogFile(logFilePath); + expect(records).toContainLogEntry('Starting to process 10 documents'); + expect(records).toContainLogEntry('Migration completed'); + }); +} diff --git a/src/core/server/integration_tests/saved_objects/migrations/shared_suites/zdt/standard_workflow.ts b/src/core/server/integration_tests/saved_objects/migrations/shared_suites/zdt/standard_workflow.ts new file mode 100644 index 0000000000000..b22e522d1d2c1 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/shared_suites/zdt/standard_workflow.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs/promises'; +import { range } from 'lodash'; +import { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; +import '../../jest_matchers'; +import { getKibanaMigratorTestKit } from '../../kibana_migrator_test_kit'; +import { delay, parseLogFile } from '../../test_utils'; +import { EsRunner, EsServer } from '../../test_types'; +import { + getBaseMigratorParams, + getSampleAType, + getSampleBType, + dummyModelVersion, +} from '../../fixtures/zdt_base.fixtures'; + +export function createStandardWorkflowTest({ + startES, + logFilePath, +}: { + startES: EsRunner; + logFilePath: string; +}) { + let esServer: EsServer; + + beforeAll(async () => { + await fs.unlink(logFilePath).catch(() => {}); + esServer = await startES(); + }); + + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); + + const createBaseline = async () => { + const { runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({ + ...getBaseMigratorParams(), + types: [getSampleAType(), getSampleBType()], + }); + await runMigrations(); + + const sampleAObjs = range(5).map((number) => ({ + id: `a-${number}`, + type: 'sample_a', + attributes: { keyword: `a_${number}`, boolean: true }, + })); + + await savedObjectsRepository.bulkCreate(sampleAObjs); + + const sampleBObjs = range(5).map((number) => ({ + id: `b-${number}`, + type: 'sample_b', + attributes: { text: `i am number ${number}`, text2: `some static text` }, + })); + + await savedObjectsRepository.bulkCreate(sampleBObjs); + }; + + it('follows the expected stages and transitions', async () => { + await createBaseline(); + + const typeA = getSampleAType(); + const typeB = getSampleBType(); + + typeA.modelVersions = { + ...typeA.modelVersions, + '2': dummyModelVersion, + }; + + typeB.modelVersions = { + ...typeB.modelVersions, + '2': dummyModelVersion, + }; + + const { runMigrations } = await getKibanaMigratorTestKit({ + ...getBaseMigratorParams(), + logFilePath, + types: [typeA, typeB], + }); + + await runMigrations(); + + const records = await parseLogFile(logFilePath); + + expect(records).toContainLogEntries( + [ + 'INIT -> UPDATE_INDEX_MAPPINGS', + 'UPDATE_INDEX_MAPPINGS -> UPDATE_INDEX_MAPPINGS_WAIT_FOR_TASK', + 'UPDATE_INDEX_MAPPINGS_WAIT_FOR_TASK -> UPDATE_MAPPING_MODEL_VERSIONS', + 'UPDATE_MAPPING_MODEL_VERSIONS -> INDEX_STATE_UPDATE_DONE', + 'INDEX_STATE_UPDATE_DONE -> DOCUMENTS_UPDATE_INIT', + 'DOCUMENTS_UPDATE_INIT -> SET_DOC_MIGRATION_STARTED', + 'SET_DOC_MIGRATION_STARTED -> SET_DOC_MIGRATION_STARTED_WAIT_FOR_INSTANCES', + 'SET_DOC_MIGRATION_STARTED_WAIT_FOR_INSTANCES -> CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS', + 'CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS -> CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS_WAIT_FOR_TASK', + 'CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS_WAIT_FOR_TASK -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT', + 'OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT -> OUTDATED_DOCUMENTS_SEARCH_READ', + 'OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_TRANSFORM', + 'OUTDATED_DOCUMENTS_SEARCH_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_BULK_INDEX', + 'OUTDATED_DOCUMENTS_SEARCH_BULK_INDEX -> OUTDATED_DOCUMENTS_SEARCH_READ', + 'OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT', + 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT -> OUTDATED_DOCUMENTS_SEARCH_REFRESH', + 'OUTDATED_DOCUMENTS_SEARCH_REFRESH -> UPDATE_DOCUMENT_MODEL_VERSIONS', + 'UPDATE_DOCUMENT_MODEL_VERSIONS -> UPDATE_DOCUMENT_MODEL_VERSIONS_WAIT_FOR_INSTANCES', + 'UPDATE_DOCUMENT_MODEL_VERSIONS_WAIT_FOR_INSTANCES -> DONE', + 'Migration completed', + ], + { ordered: true } + ); + }); +} diff --git a/src/core/server/integration_tests/saved_objects/migrations/test_types.ts b/src/core/server/integration_tests/saved_objects/migrations/test_types.ts new file mode 100644 index 0000000000000..ed6990dd2b68e --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/test_types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface EsServer { + stop: () => Promise; +} + +export type EsRunner = () => Promise; diff --git a/src/core/server/integration_tests/saved_objects/migrations/zdt_1/basic_document_migration.test.ts b/src/core/server/integration_tests/saved_objects/migrations/zdt_1/basic_document_migration.test.ts index f6234c770eaf8..e38207a4ffeba 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/zdt_1/basic_document_migration.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/zdt_1/basic_document_migration.test.ts @@ -7,238 +7,13 @@ */ import Path from 'path'; -import fs from 'fs/promises'; -import { range, sortBy } from 'lodash'; -import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; -import { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; import '../jest_matchers'; -import { getKibanaMigratorTestKit, startElasticsearch } from '../kibana_migrator_test_kit'; -import { delay, parseLogFile } from '../test_utils'; -import { - getBaseMigratorParams, - getSampleAType, - getSampleBType, -} from '../fixtures/zdt_base.fixtures'; - -export const logFilePath = Path.join(__dirname, 'basic_document_migration.test.log'); +import { startElasticsearch } from '../kibana_migrator_test_kit'; +import { createBasicDocumentsMigrationTest } from '../shared_suites/zdt/basic_document_migration'; describe('ZDT upgrades - basic document migration', () => { - let esServer: TestElasticsearchUtils['es']; - - beforeAll(async () => { - await fs.unlink(logFilePath).catch(() => {}); - esServer = await startElasticsearch(); - }); - - afterAll(async () => { - await esServer?.stop(); - await delay(10); - }); - - const createBaseline = async () => { - const { runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({ - ...getBaseMigratorParams(), - types: [getSampleAType(), getSampleBType()], - }); - await runMigrations(); - - const sampleAObjs = range(5).map((number) => ({ - id: `a-${number}`, - type: 'sample_a', - attributes: { - keyword: `a_${number}`, - boolean: true, - }, - })); - - await savedObjectsRepository.bulkCreate(sampleAObjs); - - const sampleBObjs = range(5).map((number) => ({ - id: `b-${number}`, - type: 'sample_b', - attributes: { - text: `i am number ${number}`, - text2: `some static text`, - }, - })); - - await savedObjectsRepository.bulkCreate(sampleBObjs); - }; - - it('migrates the documents', async () => { - await createBaseline(); - - const typeA = getSampleAType(); - const typeB = getSampleBType(); - - // typeA -> we add a new field and bump the model version by one with a migration - - typeA.mappings.properties = { - ...typeA.mappings.properties, - someAddedField: { type: 'keyword' }, - }; - - typeA.modelVersions = { - ...typeA.modelVersions, - '2': { - changes: [ - { - type: 'data_backfill', - backfillFn: (doc) => { - return { - attributes: { - someAddedField: `${doc.attributes.keyword}-mig`, - }, - }; - }, - }, - { - type: 'mappings_addition', - addedMappings: { - someAddedField: { type: 'keyword' }, - }, - }, - ], - }, - }; - - // typeB -> we add two new model version with migrations - - typeB.modelVersions = { - ...typeB.modelVersions, - '2': { - changes: [ - { - type: 'data_backfill', - backfillFn: (doc) => { - return { - attributes: { - text2: `${doc.attributes.text2} - mig2`, - }, - }; - }, - }, - ], - }, - '3': { - changes: [ - { - type: 'data_backfill', - backfillFn: (doc) => { - return { - attributes: { - text2: `${doc.attributes.text2} - mig3`, - }, - }; - }, - }, - ], - }, - }; - - const { runMigrations, client, savedObjectsRepository } = await getKibanaMigratorTestKit({ - ...getBaseMigratorParams(), - logFilePath, - types: [typeA, typeB], - }); - - await runMigrations(); - - const indices = await client.indices.get({ index: '.kibana*' }); - expect(Object.keys(indices)).toEqual(['.kibana_1']); - - const index = indices['.kibana_1']; - const mappings = index.mappings ?? {}; - const mappingMeta = mappings._meta ?? {}; - - expect(mappings.properties).toEqual( - expect.objectContaining({ - sample_a: typeA.mappings, - sample_b: typeB.mappings, - }) - ); - - expect(mappingMeta.docVersions).toEqual({ - sample_a: '10.2.0', - sample_b: '10.3.0', - }); - - const { saved_objects: sampleADocs } = await savedObjectsRepository.find({ type: 'sample_a' }); - const { saved_objects: sampleBDocs } = await savedObjectsRepository.find({ type: 'sample_b' }); - - expect(sampleADocs).toHaveLength(5); - expect(sampleBDocs).toHaveLength(5); - - const sampleAData = sortBy(sampleADocs, 'id').map((object) => ({ - id: object.id, - type: object.type, - attributes: object.attributes, - })); - - expect(sampleAData).toEqual([ - { - id: 'a-0', - type: 'sample_a', - attributes: { boolean: true, keyword: 'a_0', someAddedField: 'a_0-mig' }, - }, - { - id: 'a-1', - type: 'sample_a', - attributes: { boolean: true, keyword: 'a_1', someAddedField: 'a_1-mig' }, - }, - { - id: 'a-2', - type: 'sample_a', - attributes: { boolean: true, keyword: 'a_2', someAddedField: 'a_2-mig' }, - }, - { - id: 'a-3', - type: 'sample_a', - attributes: { boolean: true, keyword: 'a_3', someAddedField: 'a_3-mig' }, - }, - { - id: 'a-4', - type: 'sample_a', - attributes: { boolean: true, keyword: 'a_4', someAddedField: 'a_4-mig' }, - }, - ]); - - const sampleBData = sortBy(sampleBDocs, 'id').map((object) => ({ - id: object.id, - type: object.type, - attributes: object.attributes, - })); - - expect(sampleBData).toEqual([ - { - id: 'b-0', - type: 'sample_b', - attributes: { text: 'i am number 0', text2: 'some static text - mig2 - mig3' }, - }, - { - id: 'b-1', - type: 'sample_b', - attributes: { text: 'i am number 1', text2: 'some static text - mig2 - mig3' }, - }, - { - id: 'b-2', - type: 'sample_b', - attributes: { text: 'i am number 2', text2: 'some static text - mig2 - mig3' }, - }, - { - id: 'b-3', - type: 'sample_b', - attributes: { text: 'i am number 3', text2: 'some static text - mig2 - mig3' }, - }, - { - id: 'b-4', - type: 'sample_b', - attributes: { text: 'i am number 4', text2: 'some static text - mig2 - mig3' }, - }, - ]); - - const records = await parseLogFile(logFilePath); - expect(records).toContainLogEntry('Starting to process 10 documents'); - expect(records).toContainLogEntry('Migration completed'); + createBasicDocumentsMigrationTest({ + startES: startElasticsearch, + logFilePath: Path.join(__dirname, 'basic_document_migration.test.log'), }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/zdt_1/standard_workflow.test.ts b/src/core/server/integration_tests/saved_objects/migrations/zdt_1/standard_workflow.test.ts index f8b0cfe78e2ed..8fb678e49dcc9 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/zdt_1/standard_workflow.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/zdt_1/standard_workflow.test.ts @@ -7,133 +7,13 @@ */ import Path from 'path'; -import fs from 'fs/promises'; -import { range } from 'lodash'; -import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; -import { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; import '../jest_matchers'; -import { getKibanaMigratorTestKit, startElasticsearch } from '../kibana_migrator_test_kit'; -import { delay, parseLogFile } from '../test_utils'; -import { - getBaseMigratorParams, - getSampleAType, - getSampleBType, - dummyModelVersion, -} from '../fixtures/zdt_base.fixtures'; +import { startElasticsearch } from '../kibana_migrator_test_kit'; +import { createStandardWorkflowTest } from '../shared_suites/zdt/standard_workflow'; -export const logFilePath = Path.join(__dirname, 'standard_workflow.test.log'); - -describe('ZDT upgrades - basic document migration', () => { - let esServer: TestElasticsearchUtils['es']; - - beforeAll(async () => { - await fs.unlink(logFilePath).catch(() => {}); - esServer = await startElasticsearch(); - }); - - afterAll(async () => { - await esServer?.stop(); - await delay(10); - }); - - const createBaseline = async () => { - const { runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({ - ...getBaseMigratorParams(), - types: [getSampleAType(), getSampleBType()], - }); - await runMigrations(); - - const sampleAObjs = range(5).map((number) => ({ - id: `a-${number}`, - type: 'sample_a', - attributes: { keyword: `a_${number}`, boolean: true }, - })); - - await savedObjectsRepository.bulkCreate(sampleAObjs); - - const sampleBObjs = range(5).map((number) => ({ - id: `b-${number}`, - type: 'sample_b', - attributes: { text: `i am number ${number}`, text2: `some static text` }, - })); - - await savedObjectsRepository.bulkCreate(sampleBObjs); - }; - - it('follows the expected stages and transitions', async () => { - await createBaseline(); - - const typeA = getSampleAType(); - const typeB = getSampleBType(); - - typeA.modelVersions = { - ...typeA.modelVersions, - '2': dummyModelVersion, - }; - - typeB.modelVersions = { - ...typeB.modelVersions, - '2': dummyModelVersion, - }; - - const { runMigrations } = await getKibanaMigratorTestKit({ - ...getBaseMigratorParams(), - logFilePath, - types: [typeA, typeB], - }); - - await runMigrations(); - - const records = await parseLogFile(logFilePath); - - expect(records).toContainLogEntry('INIT -> UPDATE_INDEX_MAPPINGS'); - expect(records).toContainLogEntry( - 'UPDATE_INDEX_MAPPINGS -> UPDATE_INDEX_MAPPINGS_WAIT_FOR_TASK' - ); - expect(records).toContainLogEntry( - 'UPDATE_INDEX_MAPPINGS_WAIT_FOR_TASK -> UPDATE_MAPPING_MODEL_VERSIONS' - ); - expect(records).toContainLogEntry('UPDATE_MAPPING_MODEL_VERSIONS -> INDEX_STATE_UPDATE_DONE'); - expect(records).toContainLogEntry('INDEX_STATE_UPDATE_DONE -> DOCUMENTS_UPDATE_INIT'); - expect(records).toContainLogEntry('DOCUMENTS_UPDATE_INIT -> SET_DOC_MIGRATION_STARTED'); - expect(records).toContainLogEntry( - 'SET_DOC_MIGRATION_STARTED -> SET_DOC_MIGRATION_STARTED_WAIT_FOR_INSTANCES' - ); - expect(records).toContainLogEntry( - 'SET_DOC_MIGRATION_STARTED_WAIT_FOR_INSTANCES -> CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS' - ); - expect(records).toContainLogEntry( - 'CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS -> CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS_WAIT_FOR_TASK' - ); - expect(records).toContainLogEntry( - 'CLEANUP_UNKNOWN_AND_EXCLUDED_DOCS_WAIT_FOR_TASK -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT' - ); - expect(records).toContainLogEntry( - 'OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT -> OUTDATED_DOCUMENTS_SEARCH_READ' - ); - expect(records).toContainLogEntry( - 'OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_TRANSFORM' - ); - expect(records).toContainLogEntry( - 'OUTDATED_DOCUMENTS_SEARCH_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_BULK_INDEX' - ); - expect(records).toContainLogEntry( - 'OUTDATED_DOCUMENTS_SEARCH_BULK_INDEX -> OUTDATED_DOCUMENTS_SEARCH_READ' - ); - expect(records).toContainLogEntry( - 'OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT' - ); - expect(records).toContainLogEntry( - 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT -> OUTDATED_DOCUMENTS_SEARCH_REFRESH' - ); - expect(records).toContainLogEntry( - 'OUTDATED_DOCUMENTS_SEARCH_REFRESH -> UPDATE_DOCUMENT_MODEL_VERSIONS' - ); - expect(records).toContainLogEntry( - 'UPDATE_DOCUMENT_MODEL_VERSIONS -> UPDATE_DOCUMENT_MODEL_VERSIONS_WAIT_FOR_INSTANCES' - ); - expect(records).toContainLogEntry('UPDATE_DOCUMENT_MODEL_VERSIONS_WAIT_FOR_INSTANCES -> DONE'); - - expect(records).toContainLogEntry('Migration completed'); +describe('ZDT upgrades - standard workflow', () => { + createStandardWorkflowTest({ + startES: startElasticsearch, + logFilePath: Path.join(__dirname, 'standard_workflow.test.log'), }); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/update.test.ts b/src/core/server/integration_tests/saved_objects/routes/update.test.ts index c261584217a37..e121fa7a43e0d 100644 --- a/src/core/server/integration_tests/saved_objects/routes/update.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/update.test.ts @@ -24,8 +24,8 @@ import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; const testTypes = [ - { name: 'index-pattern', hide: false }, - { name: 'hidden-type', hide: true }, + { name: 'index-pattern', hide: false }, // multi-namespace type + { name: 'hidden-type', hide: true }, // hidden { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, ]; @@ -117,7 +117,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { 'index-pattern', 'logstash-*', { title: 'Testing' }, - { version: 'foo' } + { version: 'foo', migrationVersionCompatibility: 'raw' } ); }); diff --git a/src/core/server/integration_tests/saved_objects/serverless/migrations/actions.test.ts b/src/core/server/integration_tests/saved_objects/serverless/migrations/actions.test.ts new file mode 100644 index 0000000000000..bbd4984eae0b8 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/serverless/migrations/actions.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createTestServerlessInstances } from '@kbn/core-test-helpers-kbn-server'; +import { runActionTestSuite } from '../../migrations/group3/actions/actions_test_suite'; + +const { startES } = createTestServerlessInstances({ + adjustTimeout: jest.setTimeout, +}); + +describe('Migration actions - serverless environment', () => { + runActionTestSuite({ + startEs: async () => { + const serverlessEs = await startES(); + const client = serverlessEs.getClient(); + return { + esServer: serverlessEs, + client, + }; + }, + environment: 'serverless', + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/serverless/migrations/basic_document_migration.test.ts b/src/core/server/integration_tests/saved_objects/serverless/migrations/basic_document_migration.test.ts new file mode 100644 index 0000000000000..cfc30c5bd2266 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/serverless/migrations/basic_document_migration.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import { createTestServerlessInstances } from '@kbn/core-test-helpers-kbn-server'; +import { createBasicDocumentsMigrationTest } from '../../migrations/shared_suites/zdt/basic_document_migration'; + +describe('serverless - ZDT upgrades - basic document migration', () => { + const startElasticsearch = async () => { + const { startES } = createTestServerlessInstances({ + adjustTimeout: jest.setTimeout, + }); + return await startES(); + }; + + createBasicDocumentsMigrationTest({ + startES: startElasticsearch, + logFilePath: Path.join(__dirname, 'basic_document_migration.test.log'), + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/serverless/migrations/smoke.test.ts b/src/core/server/integration_tests/saved_objects/serverless/migrations/smoke.test.ts index 940ea4a160cb6..01be93e7a296c 100644 --- a/src/core/server/integration_tests/saved_objects/serverless/migrations/smoke.test.ts +++ b/src/core/server/integration_tests/saved_objects/serverless/migrations/smoke.test.ts @@ -13,13 +13,11 @@ import { createTestServerlessInstances, } from '@kbn/core-test-helpers-kbn-server'; -/** - * Until we merge https://github.com/elastic/kibana/pull/162673 this test should remain skipped. - */ -describe.skip('smoke', () => { +describe('Basic smoke test', () => { let serverlessES: TestServerlessESUtils; let serverlessKibana: TestServerlessKibanaUtils; let root: TestServerlessKibanaUtils['root']; + beforeEach(async () => { const { startES, startKibana } = createTestServerlessInstances({ adjustTimeout: jest.setTimeout, @@ -28,11 +26,13 @@ describe.skip('smoke', () => { serverlessKibana = await startKibana(); root = serverlessKibana.root; }); + afterEach(async () => { await serverlessES?.stop(); await serverlessKibana?.stop(); }); - test('it can start Kibana and ES serverless', async () => { + + test('it can start Kibana running against serverless ES', async () => { const { body } = await request.get(root, '/api/status').expect(200); expect(body).toMatchObject({ status: { overall: { level: 'available' } } }); }); diff --git a/src/core/server/integration_tests/saved_objects/serverless/migrations/standard_workflow.test.ts b/src/core/server/integration_tests/saved_objects/serverless/migrations/standard_workflow.test.ts new file mode 100644 index 0000000000000..77368869e6e59 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/serverless/migrations/standard_workflow.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import { createTestServerlessInstances } from '@kbn/core-test-helpers-kbn-server'; +import { createStandardWorkflowTest } from '../../migrations/shared_suites/zdt/standard_workflow'; + +describe('serverless - ZDT upgrades - standard workflow', () => { + const startElasticsearch = async () => { + const { startES } = createTestServerlessInstances({ + adjustTimeout: jest.setTimeout, + }); + return await startES(); + }; + + createStandardWorkflowTest({ + startES: startElasticsearch, + logFilePath: Path.join(__dirname, 'standard_workflow.test.log'), + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts index db24bf5a10765..035da58c60fa1 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts @@ -30,6 +30,7 @@ import { declarePostPitRoute, declarePostUpdateByQueryRoute, declarePassthroughRoute, + declareIndexRoute, setProxyInterrupt, allCombinationsPermutations, } from './repository_with_proxy_utils'; @@ -113,6 +114,7 @@ describe('404s from proxies', () => { declarePostSearchRoute(hapiServer, esHostname, esPort, kbnIndexPath); declarePostPitRoute(hapiServer, esHostname, esPort, kbnIndexPath); declarePostUpdateByQueryRoute(hapiServer, esHostname, esPort, kbnIndexPath); + declareIndexRoute(hapiServer, esHostname, esPort, kbnIndexPath); }); // register index-agnostic routes @@ -396,7 +398,9 @@ describe('404s from proxies', () => { expect(genericNotFoundEsUnavailableError(myError, 'my_type', 'myTypeId1')); }); - it('returns an EsUnavailable error on `update` requests that are interrupted', async () => { + it('returns an EsUnavailable error on `update` requests that are interrupted during index', async () => { + setProxyInterrupt('update'); + let updateError; try { await repository.update('my_type', 'myTypeToUpdate', { @@ -406,9 +410,26 @@ describe('404s from proxies', () => { } catch (err) { updateError = err; } + expect(genericNotFoundEsUnavailableError(updateError)); }); + it('returns an EsUnavailable error on `update` requests that are interrupted during preflight', async () => { + setProxyInterrupt('updatePreflight'); + + let updateError; + try { + await repository.update('my_type', 'myTypeToUpdate', { + title: 'updated title', + }); + expect(false).toBe(true); // Should not get here (we expect the call to throw) + } catch (err) { + updateError = err; + } + + expect(genericNotFoundEsUnavailableError(updateError, 'my_type', 'myTypeToUpdate')); + }); + it('returns an EsUnavailable error on `bulkCreate` requests with a 404 proxy response and wrong product header', async () => { setProxyInterrupt('bulkCreate'); let bulkCreateError: any; diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts index 35b6b37b9c413..6f40d19f609d9 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts @@ -27,6 +27,8 @@ export const setProxyInterrupt = ( | 'openPit' | 'deleteByNamespace' | 'internalBulkResolve' + | 'update' + | 'updatePreflight' | null ) => (proxyInterrupt = testArg); @@ -63,7 +65,11 @@ export const declareGetRoute = ( path: `/${kbnIndex}/_doc/{type*}`, options: { handler: (req, h) => { - if (req.params.type === 'my_type:myTypeId1' || req.params.type === 'my_type:myType_123') { + if ( + req.params.type === 'my_type:myTypeId1' || + req.params.type === 'my_type:myType_123' || + proxyInterrupt === 'updatePreflight' + ) { return proxyResponseHandler(h, hostname, port); } else { return relayHandler(h, hostname, port); @@ -257,6 +263,31 @@ export const declarePostUpdateByQueryRoute = ( }, }); +// PUT _doc +export const declareIndexRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string, + kbnIndex: string +) => + hapiServer.route({ + method: ['PUT', 'POST'], + path: `/${kbnIndex}/_doc/{_id?}`, + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (proxyInterrupt === 'update') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); + // catch-all passthrough route export const declarePassthroughRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => hapiServer.route({ diff --git a/src/core/server/integration_tests/saved_objects/service/lib/update.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/update.test.ts new file mode 100644 index 0000000000000..e2b7d91355d81 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/service/lib/update.test.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import fs from 'fs/promises'; +import { pick } from 'lodash'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsType, SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import '../../migrations/jest_matchers'; +import { + getKibanaMigratorTestKit, + startElasticsearch, +} from '../../migrations/kibana_migrator_test_kit'; +import { delay } from '../../migrations/test_utils'; +import { getBaseMigratorParams } from '../../migrations/fixtures/zdt_base.fixtures'; + +export const logFilePath = Path.join(__dirname, 'update.test.log'); + +describe('SOR - update API', () => { + let esServer: TestElasticsearchUtils['es']; + + beforeAll(async () => { + await fs.unlink(logFilePath).catch(() => {}); + esServer = await startElasticsearch(); + }); + + const getType = (version: 'v1' | 'v2'): SavedObjectsType => { + const versionMap: SavedObjectsModelVersionMap = { + 1: { + changes: [], + schemas: { + forwardCompatibility: (attributes) => { + return pick(attributes, 'count'); + }, + }, + }, + }; + + if (version === 'v2') { + versionMap[2] = { + changes: [ + { + type: 'data_backfill', + backfillFn: (document) => { + return { attributes: { even: document.attributes.count % 2 === 0 } }; + }, + }, + ], + }; + } + + return { + name: 'my-test-type', + hidden: false, + namespaceType: 'agnostic', + mappings: { + dynamic: false, + properties: { + count: { type: 'integer' }, + ...(version === 'v2' ? { even: { type: 'boolean' } } : {}), + }, + }, + management: { + importableAndExportable: true, + }, + switchToModelVersionAt: '8.10.0', + modelVersions: versionMap, + }; + }; + + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); + + const setup = async () => { + const { runMigrations: runMigrationV1, savedObjectsRepository: repositoryV1 } = + await getKibanaMigratorTestKit({ + ...getBaseMigratorParams(), + types: [getType('v1')], + }); + await runMigrationV1(); + + const { + runMigrations: runMigrationV2, + savedObjectsRepository: repositoryV2, + client: esClient, + } = await getKibanaMigratorTestKit({ + ...getBaseMigratorParams(), + types: [getType('v2')], + }); + await runMigrationV2(); + + return { repositoryV1, repositoryV2, esClient }; + }; + + it('supports updates between older and newer versions', async () => { + const { repositoryV1, repositoryV2, esClient } = await setup(); + + await repositoryV1.create('my-test-type', { count: 12 }, { id: 'my-id' }); + + let document = await repositoryV2.get('my-test-type', 'my-id'); + + expect(document.attributes).toEqual({ + count: 12, + even: true, + }); + + await repositoryV2.update('my-test-type', 'my-id', { + count: 11, + even: false, + }); + + document = await repositoryV1.get('my-test-type', 'my-id'); + + expect(document.attributes).toEqual({ + count: 11, + }); + + await repositoryV1.update('my-test-type', 'my-id', { + count: 14, + }); + + document = await repositoryV2.get('my-test-type', 'my-id'); + + expect(document.attributes).toEqual({ + count: 14, + even: true, + }); + + const rawDoc = await fetchDoc(esClient, 'my-test-type', 'my-id'); + expect(rawDoc._source).toEqual( + expect.objectContaining({ + typeMigrationVersion: '10.1.0', + 'my-test-type': { + count: 14, + }, + }) + ); + }); + + const fetchDoc = async (client: ElasticsearchClient, type: string, id: string) => { + return await client.get({ + index: '.kibana', + id: `${type}:${id}`, + }); + }; +}); diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index c239333d00590..a89f994dc6bd0 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -78,14 +78,14 @@ export const DEV_ONLY_LICENSE_ALLOWED = ['MPL-2.0']; // there are some licenses which should not be globally allowed // but can be brought in on a per-package basis export const PER_PACKAGE_ALLOWED_LICENSES = { - 'openpgp@5.3.0': ['LGPL-3.0+'], + 'openpgp@5.10.1': ['LGPL-3.0+'], }; // Globally overrides a license for a given package@version export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.4.0': ['Elastic License 2.0'], - '@elastic/eui@87.2.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@88.3.0': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry 'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary }; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js index fa03617f5824f..e8953152a5932 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js @@ -2090,6 +2090,12 @@ ace.define( case 'p': next('p'); switch (ch) { + case 'a': + next('a'); + next('t'); + next('c'); + next('h'); + return 'patch'; case 'u': next('u'); next('t'); @@ -2106,6 +2112,12 @@ ace.define( case 'P': next('P'); switch (ch) { + case 'A': + next('A'); + next('T'); + next('C'); + next('H'); + return 'PATCH'; case 'U': next('U'); next('T'); @@ -2120,7 +2132,7 @@ ace.define( } break; default: - error('Expected one of GET/POST/PUT/DELETE/HEAD'); + error('Expected one of GET/POST/PUT/DELETE/HEAD/PATCH'); } }, value, // Place holder for the value function. @@ -2254,7 +2266,7 @@ ace.define( annotate('error', e.message); // snap const substring = text.substr(at); - const nextMatch = substring.search(/^POST|HEAD|GET|PUT|DELETE/m); + const nextMatch = substring.search(/^POST|HEAD|GET|PUT|DELETE|PATCH/m); if (nextMatch < 1) return; reset(at + nextMatch); } diff --git a/src/plugins/console/public/application/models/sense_editor/curl.ts b/src/plugins/console/public/application/models/sense_editor/curl.ts index 74cbebf051d03..894ee2bf70168 100644 --- a/src/plugins/console/public/application/models/sense_editor/curl.ts +++ b/src/plugins/console/public/application/models/sense_editor/curl.ts @@ -38,13 +38,13 @@ export function parseCURL(text: string) { const EscapedQuotes = /^((?:[^\\"']|\\.)+)/; const LooksLikeCurl = /^\s*curl\s+/; - const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/; + const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/; const HasProtocol = /[\s"']https?:\/\//; const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/; const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/; const CurlData = /^.+\s(--data|-d)\s*/; - const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/; + const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/; if (lines.length > 0 && ExecutionComment.test(lines[0])) { lines.shift(); diff --git a/src/plugins/console/public/application/models/sense_editor/integration.test.js b/src/plugins/console/public/application/models/sense_editor/integration.test.js index cd7e13d5c6a56..e47439a899edd 100644 --- a/src/plugins/console/public/application/models/sense_editor/integration.test.js +++ b/src/plugins/console/public/application/models/sense_editor/integration.test.js @@ -985,7 +985,7 @@ describe('Integration', () => { { name: 'Cursor rows after request end', cursor: { lineNumber: 5, column: 1 }, - autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'], + autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH'], prefixToAdd: '', suffixToAdd: ' ', }, diff --git a/src/plugins/console/public/lib/autocomplete/autocomplete.ts b/src/plugins/console/public/lib/autocomplete/autocomplete.ts index 970847ac6bedc..487ed95672d83 100644 --- a/src/plugins/console/public/lib/autocomplete/autocomplete.ts +++ b/src/plugins/console/public/lib/autocomplete/autocomplete.ts @@ -25,7 +25,7 @@ import * as utils from '../utils'; import { populateContext } from './engine'; import type { AutoCompleteContext, DataAutoCompleteRulesOneOf, ResultTerm } from './types'; -import { URL_PATH_END_MARKER } from './components'; +import { URL_PATH_END_MARKER, ConstantComponent } from './components'; let lastEvaluatedToken: Token | null = null; @@ -967,7 +967,7 @@ export default function ({ } function addMethodAutoCompleteSetToContext(context: AutoCompleteContext) { - context.autoCompleteSet = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'].map((m, i) => ({ + context.autoCompleteSet = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH'].map((m, i) => ({ name: m, score: -i, meta: i18n.translate('console.autocomplete.addMethodMetaText', { defaultMessage: 'method' }), @@ -980,10 +980,38 @@ export default function ({ context.token = ret.token; context.otherTokenValues = ret.otherTokenValues; context.urlTokenPath = ret.urlTokenPath; + const components = getTopLevelUrlCompleteComponents(context.method); - populateContext(ret.urlTokenPath, context, editor, true, components); + const { tokenPath, predicate } = (() => { + const lastUrlTokenPath = + Array.isArray(context.urlTokenPath) && context.urlTokenPath.length !== 0 + ? context.urlTokenPath[context.urlTokenPath.length - 1] + : null; + // Checking the last chunk of path like 'c,d,' of 'GET /a/b/c,d,' + if ( + Array.isArray(lastUrlTokenPath) && + // true if neither c nor d equals to every ConstantComponent's name (such as _search) + !_.find( + components, + (c) => c instanceof ConstantComponent && _.find(lastUrlTokenPath, (p) => c.name === p) + ) + ) { + // will simulate autocomplete on 'GET /a/b/' with a filter by index + return { + tokenPath: context.urlTokenPath.slice(0, -1), + predicate: (term) => term.meta === 'index', + }; + } else { + // will do nothing special + return { tokenPath: context.urlTokenPath, predicate: (term) => true }; + } + })(); - context.autoCompleteSet = addMetaToTermsList(context.autoCompleteSet!, 'endpoint'); + populateContext(tokenPath, context, editor, true, components); + context.autoCompleteSet = _.filter( + addMetaToTermsList(context.autoCompleteSet!, 'endpoint'), + predicate + ); } function addUrlParamsAutoCompleteSetToContext(context: AutoCompleteContext, pos: Position) { @@ -1056,6 +1084,12 @@ export default function ({ return context; } + const t = editor.getTokenAt(pos); + if (t && t.type === 'punctuation.end_triple_quote' && pos.column !== t.position.column + 3) { + // skip to populate context as the current position is not on the edge of end_triple_quote + return context; + } + // needed for scope linking + global term resolving context.endpointComponentResolver = getEndpointBodyCompleteComponents; context.globalComponentResolver = getGlobalAutocompleteComponents; @@ -1077,11 +1111,7 @@ export default function ({ tracer('has started evaluating current token', currentToken); if (!currentToken) { - if (pos.lineNumber === 1) { - lastEvaluatedToken = null; - tracer('not starting autocomplete due to invalid current token at line 1'); - return; - } + lastEvaluatedToken = null; currentToken = { position: { column: 0, lineNumber: 0 }, value: '', type: '' }; // empty row } @@ -1110,11 +1140,11 @@ export default function ({ if ( lastEvaluatedToken.position.column + 1 === currentToken.position.column && lastEvaluatedToken.position.lineNumber === currentToken.position.lineNumber && - lastEvaluatedToken.type === 'url.slash' && + (lastEvaluatedToken.type === 'url.slash' || lastEvaluatedToken.type === 'url.comma') && currentToken.type === 'url.part' && currentToken.value.length === 1 ) { - // do not suppress autocomplete for a single character immediately following a slash in URL + // do not suppress autocomplete for a single character immediately following a slash or comma in URL } else if ( lastEvaluatedToken.position.column < currentToken.position.column && lastEvaluatedToken.position.lineNumber === currentToken.position.lineNumber && diff --git a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js index f2666052b988f..8d6a0a8f60b12 100644 --- a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js +++ b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js @@ -33,7 +33,7 @@ export class UrlPatternMatcher { // We'll group endpoints by the methods which are attached to them, //to avoid suggesting endpoints that are incompatible with the //method that the user has entered. - ['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach((method) => { + ['HEAD', 'GET', 'PUT', 'POST', 'DELETE', 'PATCH'].forEach((method) => { this[method] = { rootComponent: new SharedComponent('ROOT'), parametrizedComponentFactories: parametrizedComponentFactories || { diff --git a/src/plugins/console/public/lib/curl_parsing/curl.js b/src/plugins/console/public/lib/curl_parsing/curl.js index 1ae6335f3249e..519cb3a3bbd0a 100644 --- a/src/plugins/console/public/lib/curl_parsing/curl.js +++ b/src/plugins/console/public/lib/curl_parsing/curl.js @@ -38,13 +38,13 @@ export function parseCURL(text) { const EscapedQuotes = /^((?:[^\\"']|\\.)+)/; const LooksLikeCurl = /^\s*curl\s+/; - const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/; + const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/; const HasProtocol = /[\s"']https?:\/\//; const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/; const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/; const CurlData = /^.+\s(--data|-d)\s*/; - const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/; + const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/; if (lines.length > 0 && ExecutionComment.test(lines[0])) { lines.shift(); diff --git a/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts b/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts index c8eceba98b52f..6a8ddc0053203 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts @@ -496,6 +496,17 @@ export const query = (specService: SpecDefinitionsService) => { format: 'dd/MM/yyyy||yyyy', }, }, + rule_query: { + __template: { + organic: { + query: {}, + }, + ruleset_id: '', + match_criteria: { + FIELD: 'VALUE', + }, + }, + }, span_first: { __template: spanFirstTemplate, match: SPAN_QUERIES, diff --git a/src/plugins/console/server/lib/spec_definitions/js/search.ts b/src/plugins/console/server/lib/spec_definitions/js/search.ts index 25e1a782fa0a6..6dc30ba7f8906 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/search.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/search.ts @@ -222,6 +222,37 @@ export const search = (specService: SpecDefinitionsService) => { timeout: '1s', version: { __one_of: [true, false] }, track_total_hits: { __one_of: [true, false] }, + knn: { + __template: { + field: '', + k: 10, + num_candidates: 100, + }, + __one_of: [ + { + field: '{field}', + filter: { __scope_link: 'GLOBAL.filter' }, + k: 10, + num_candidates: 100, + query_vector: [], + query_vector_builder: {}, + similarity: { __one_of: ['l2_norm', 'cosine', 'dot_product'] }, + boost: 1.0, + }, + [ + { + field: '{field}', + filter: { __scope_link: 'GLOBAL.filter' }, + k: 10, + num_candidates: 100, + query_vector: [], + query_vector_builder: {}, + similarity: { __one_of: ['l2_norm', 'cosine', 'dot_product'] }, + boost: 1.0, + }, + ], + ], + }, }, }); diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/query_ruleset.put.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/query_ruleset.put.json index d19b1f14f0f64..9b50197db9689 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/overrides/query_ruleset.put.json +++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/query_ruleset.put.json @@ -3,11 +3,11 @@ "data_autocomplete_rules": { "rules": [{ "rule_id": "", - "type": "", + "type": "pinned", "criteria": [{ - "type": "", + "type": { "__one_of": [ "exact", "fuzzy", "prefix", "suffix", "contains", "lt", "lte", "gt", "gte", "always" ]}, "metadata": "", - "value": "" + "values": [""] }], "actions": { "docs":[{ diff --git a/src/plugins/console/server/routes/api/console/proxy/body.test.ts b/src/plugins/console/server/routes/api/console/proxy/body.test.ts index 893f00f975e89..5500be776dcbc 100644 --- a/src/plugins/console/server/routes/api/console/proxy/body.test.ts +++ b/src/plugins/console/server/routes/api/console/proxy/body.test.ts @@ -90,5 +90,11 @@ describe('Console Proxy Route', () => { }); }); }); + describe('PATCH request', () => { + it('returns the exact body', async () => { + const { payload } = await request('PATCH', '/', 'foobar'); + expect(await readStream(payload)).toBe('foobar'); + }); + }); }); }); diff --git a/src/plugins/console/server/routes/api/console/proxy/validation_config.ts b/src/plugins/console/server/routes/api/console/proxy/validation_config.ts index 4492863a16bcb..9a3ee2efd66c1 100644 --- a/src/plugins/console/server/routes/api/console/proxy/validation_config.ts +++ b/src/plugins/console/server/routes/api/console/proxy/validation_config.ts @@ -13,11 +13,11 @@ export type Body = TypeOf; const acceptedHttpVerb = schema.string({ validate: (method) => { - return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE'].some( + return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'].some( (verb) => verb.toLowerCase() === method.toLowerCase() ) ? undefined - : `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE']. Received '${method}'.`; + : `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']. Received '${method}'.`; }, }); diff --git a/src/plugins/content_management/public/content_client/content_client.tsx b/src/plugins/content_management/public/content_client/content_client.tsx index e1d148b74760a..caf7181472f03 100644 --- a/src/plugins/content_management/public/content_client/content_client.tsx +++ b/src/plugins/content_management/public/content_client/content_client.tsx @@ -96,7 +96,7 @@ export class ContentClient { private readonly crudClientProvider: (contentType?: string) => CrudClient, private readonly contentTypeRegistry: ContentTypeRegistry ) { - this.queryClient = new QueryClient(); + this.queryClient = new QueryClient({ defaultOptions: { queries: { networkMode: 'always' } } }); this.queryOptionBuilder = createQueryOptionBuilder({ crudClientProvider: this.crudClientProvider, contentTypeRegistry: this.contentTypeRegistry, diff --git a/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx index 6344c768eae63..2bf18a70a97e7 100644 --- a/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx +++ b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx @@ -222,22 +222,22 @@ export class ControlGroupContainer extends Container< public async addDataControlFromField(controlProps: AddDataControlProps) { const panelState = await getDataControlPanelState(this.getInput(), controlProps); - return this.createAndSaveEmbeddable(panelState.type, panelState); + return this.createAndSaveEmbeddable(panelState.type, panelState, this.getInput().panels); } public addOptionsListControl(controlProps: AddOptionsListControlProps) { const panelState = getOptionsListPanelState(this.getInput(), controlProps); - return this.createAndSaveEmbeddable(panelState.type, panelState); + return this.createAndSaveEmbeddable(panelState.type, panelState, this.getInput().panels); } public addRangeSliderControl(controlProps: AddRangeSliderControlProps) { const panelState = getRangeSliderPanelState(this.getInput(), controlProps); - return this.createAndSaveEmbeddable(panelState.type, panelState); + return this.createAndSaveEmbeddable(panelState.type, panelState, this.getInput().panels); } public addTimeSliderControl() { const panelState = getTimeSliderPanelState(this.getInput()); - return this.createAndSaveEmbeddable(panelState.type, panelState); + return this.createAndSaveEmbeddable(panelState.type, panelState, this.getInput().panels); } public openAddDataControlFlyout = openAddDataControlFlyout; @@ -283,15 +283,19 @@ export class ControlGroupContainer extends Container< protected createNewPanelState( factory: EmbeddableFactory, - partial: Partial = {} - ): ControlPanelState { - const panelState = super.createNewPanelState(factory, partial); + partial: Partial = {}, + otherPanels: ControlGroupInput['panels'] + ) { + const { newPanel } = super.createNewPanelState(factory, partial); return { - order: getNextPanelOrder(this.getInput().panels), - width: this.getInput().defaultControlWidth, - grow: this.getInput().defaultControlGrow, - ...panelState, - } as ControlPanelState; + newPanel: { + order: getNextPanelOrder(this.getInput().panels), + width: this.getInput().defaultControlWidth, + grow: this.getInput().defaultControlGrow, + ...newPanel, + } as ControlPanelState, + otherPanels, + }; } protected onRemoveEmbeddable(idToRemove: string) { diff --git a/src/plugins/dashboard/jest_setup.ts b/src/plugins/dashboard/jest_setup.ts index 5683ecd4e288b..c6318bc3c4df6 100644 --- a/src/plugins/dashboard/jest_setup.ts +++ b/src/plugins/dashboard/jest_setup.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { pluginServices } from './public/services/plugin_services'; -import { registry } from './public/services/plugin_services.stub'; +import { setStubDashboardServices } from './public/mocks'; -pluginServices.setRegistry(registry.start({})); +setStubDashboardServices(); diff --git a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.test.tsx b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.test.tsx index 5ec0ac57c574b..76b62f28993ad 100644 --- a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.test.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.test.tsx @@ -47,11 +47,13 @@ beforeEach(async () => { .fn() .mockReturnValue(mockEmbeddableFactory); container = buildMockDashboard({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Kibanana', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), + overrides: { + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Kibanana', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, }, }); diff --git a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx index e028d8f387312..273a7c3040a36 100644 --- a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx @@ -22,10 +22,9 @@ import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { type DashboardPanelState } from '../../common'; import { pluginServices } from '../services/plugin_services'; -import { createPanelState } from '../dashboard_container/component/panel'; import { dashboardClonePanelActionStrings } from './_dashboard_actions_strings'; +import { placeClonePanel } from '../dashboard_container/component/panel_placement'; import { DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '../dashboard_container'; -import { placePanelBeside } from '../dashboard_container/component/panel/dashboard_panel_placement'; export const ACTION_CLONE_PANEL = 'clonePanel'; @@ -82,6 +81,7 @@ export class ClonePanelAction implements Action { throw new PanelNotFoundError(); } + // Clone panel input const clonedPanelState: PanelState = await (async () => { const newTitle = await this.getCloneTitle(embeddable, embeddable.getTitle() || ''); const id = uuidv4(); @@ -91,7 +91,7 @@ export class ClonePanelAction implements Action { explicitInput: { ...(await embeddable.getInputAsValueType()), hidePanelTitles: panelToClone.explicitInput.hidePanelTitles, - title: newTitle, + ...(newTitle ? { title: newTitle } : {}), id, }, }; @@ -110,18 +110,20 @@ export class ClonePanelAction implements Action { 'data-test-subj': 'addObjectToContainerSuccess', }); - const { otherPanels, newPanel } = createPanelState( - clonedPanelState, - dashboard.getInput().panels, - placePanelBeside, - { - width: panelToClone.gridData.w, - height: panelToClone.gridData.h, - currentPanels: dashboard.getInput().panels, - placeBesideId: panelToClone.explicitInput.id, - scrollToPanel: true, - } - ); + const { newPanelPlacement, otherPanels } = placeClonePanel({ + width: panelToClone.gridData.w, + height: panelToClone.gridData.h, + currentPanels: dashboard.getInput().panels, + placeBesideId: panelToClone.explicitInput.id, + }); + + const newPanel = { + ...clonedPanelState, + gridData: { + ...newPanelPlacement, + i: clonedPanelState.explicitInput.id, + }, + }; dashboard.updateInput({ panels: { diff --git a/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.test.tsx b/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.test.tsx index 877488c6d8041..194edc675b108 100644 --- a/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.test.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.test.tsx @@ -31,11 +31,13 @@ pluginServices.getServices().embeddable.getEmbeddableFactory = jest beforeEach(async () => { container = buildMockDashboard({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Sam', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), + overrides: { + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, }, }); diff --git a/src/plugins/dashboard/public/dashboard_actions/export_csv_action.test.tsx b/src/plugins/dashboard/public/dashboard_actions/export_csv_action.test.tsx index 350db8fad40b1..0fbbe9c76b2cf 100644 --- a/src/plugins/dashboard/public/dashboard_actions/export_csv_action.test.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/export_csv_action.test.tsx @@ -46,11 +46,13 @@ describe('Export CSV action', () => { }; container = buildMockDashboard({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Kibanana', id: '123' }, - type: CONTACT_CARD_EXPORTABLE_EMBEDDABLE, - }), + overrides: { + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Kibanana', id: '123' }, + type: CONTACT_CARD_EXPORTABLE_EMBEDDABLE, + }), + }, }, }); diff --git a/src/plugins/dashboard/public/dashboard_actions/replace_panel_action.test.tsx b/src/plugins/dashboard/public/dashboard_actions/replace_panel_action.test.tsx index 0829f89424ede..5873253e105d4 100644 --- a/src/plugins/dashboard/public/dashboard_actions/replace_panel_action.test.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/replace_panel_action.test.tsx @@ -29,11 +29,13 @@ let container: DashboardContainer; let embeddable: ContactCardEmbeddable; beforeEach(async () => { container = buildMockDashboard({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Sam', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), + overrides: { + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, }, }); diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app.scss b/src/plugins/dashboard/public/dashboard_app/_dashboard_app.scss index b1333d1c90623..c89337d29e720 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app.scss +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app.scss @@ -1,11 +1,5 @@ @import 'src/core/public/mixins'; -.dshAppWrapper { - display: flex; - flex: 1; - flex-direction: column; -} - .dshUnsavedListingItem { margin-top: $euiSizeM; } diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts index aa25647610e2b..6fefffcc1d668 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts @@ -25,6 +25,17 @@ export const dashboardReadonlyBadge = { }), }; +export const dashboardManagedBadge = { + getText: () => + i18n.translate('dashboard.badge.managed.text', { + defaultMessage: 'Managed', + }), + getTooltip: () => + i18n.translate('dashboard.badge.managed.tooltip', { + defaultMessage: 'This dashboard is system managed. Clone this dashboard to make changes.', + }), +}; + /** * @param title {string} the current title of the dashboard * @param viewMode {DashboardViewMode} the current mode. If in editing state, prepends 'Editing ' to the title. diff --git a/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx b/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx index daa6c94f7f60d..f9f3efdd299f3 100644 --- a/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx +++ b/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx @@ -185,7 +185,7 @@ export function DashboardApp({ }, [dashboardAPI, kbnUrlStateStorage, savedDashboardId]); return ( -

    + <> {showNoDataPage && ( setShowNoDataPage(false)} /> )} @@ -208,6 +208,6 @@ export function DashboardApp({ /> )} -
    + ); } diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/_dashboard_top_nav.scss b/src/plugins/dashboard/public/dashboard_app/top_nav/_dashboard_top_nav.scss index 1cc815d05e71a..e73de35dfc41d 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/_dashboard_top_nav.scss +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/_dashboard_top_nav.scss @@ -3,24 +3,7 @@ width: 100%; position: sticky; z-index: $euiZLevel2; + top: var(--euiFixedHeadersOffset, 0); background: $euiPageBackgroundColor; } - - &.kbnBody--noHeaderBanner { - &.kbnBody--chromeVisible .dashboardTopNav { - top: $kbnHeaderOffset; - } - &.kbnBody--chromeHidden .dashboardTopNav { - top: 0; - } - } - - &.kbnBody--hasHeaderBanner { - &.kbnBody--chromeVisible .dashboardTopNav { - top: $kbnHeaderOffsetWithBanner; - } - &.kbnBody--chromeHidden .dashboardTopNav { - top: $kbnHeaderBannerHeight; - } - } -} \ No newline at end of file +} diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx index 11347cf57cc40..b92091bb1376b 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx @@ -12,8 +12,9 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { useEuiTheme } from '@elastic/eui'; import { AddFromLibraryButton, Toolbar, ToolbarButton } from '@kbn/shared-ux-button-toolbar'; -import { EmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { EmbeddableFactory, EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; +import { isExplicitInputWithAttributes } from '@kbn/embeddable-plugin/public'; import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings'; import { EditorMenu } from './editor_menu'; @@ -83,15 +84,26 @@ export function DashboardEditingToolbar() { trackUiMetric(METRIC_TYPE.CLICK, embeddableFactory.type); } - let explicitInput: Awaited>; + let explicitInput: Partial; + let attributes: unknown; try { - explicitInput = await embeddableFactory.getExplicitInput(undefined, dashboard); + const explicitInputReturn = await embeddableFactory.getExplicitInput(undefined, dashboard); + if (isExplicitInputWithAttributes(explicitInputReturn)) { + explicitInput = explicitInputReturn.newInput; + attributes = explicitInputReturn.attributes; + } else { + explicitInput = explicitInputReturn; + } } catch (e) { // error likely means user canceled embeddable creation return; } - const newEmbeddable = await dashboard.addNewEmbeddable(embeddableFactory.type, explicitInput); + const newEmbeddable = await dashboard.addNewEmbeddable( + embeddableFactory.type, + explicitInput, + attributes + ); if (newEmbeddable) { dashboard.setScrollToPanelId(newEmbeddable.id); diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_top_nav.tsx index 8fbd259be6eec..c3f4913e1d684 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_top_nav.tsx @@ -24,6 +24,7 @@ import { leaveConfirmStrings, getDashboardBreadcrumb, unsavedChangesBadgeStrings, + dashboardManagedBadge, } from '../_dashboard_app_strings'; import { UI_SETTINGS } from '../../../common'; import { useDashboardAPI } from '../dashboard_app'; @@ -67,7 +68,7 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr navigation: { TopNavMenu }, embeddable: { getStateTransfer }, initializerContext: { allowByValueEmbeddables }, - dashboardCapabilities: { saveQuery: showSaveQuery }, + dashboardCapabilities: { saveQuery: showSaveQuery, showWriteControls }, } = pluginServices.getServices(); const isLabsEnabled = uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI); const { setHeaderActionMenu, onAppLeave } = useDashboardMountContext(); @@ -82,6 +83,7 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr const fullScreenMode = dashboard.select((state) => state.componentState.fullScreenMode); const savedQueryId = dashboard.select((state) => state.componentState.savedQueryId); const lastSavedId = dashboard.select((state) => state.componentState.lastSavedId); + const managed = dashboard.select((state) => state.componentState.managed); const viewMode = dashboard.select((state) => state.explicitInput.viewMode); const query = dashboard.select((state) => state.explicitInput.query); @@ -237,9 +239,8 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr }); const badges = useMemo(() => { - if (viewMode !== ViewMode.EDIT) return; const allBadges: TopNavMenuProps['badges'] = []; - if (hasUnsavedChanges) { + if (hasUnsavedChanges && viewMode === ViewMode.EDIT) { allBadges.push({ 'data-test-subj': 'dashboardUnsavedChangesBadge', badgeText: unsavedChangesBadgeStrings.getUnsavedChangedBadgeText(), @@ -251,7 +252,7 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr } as EuiToolTipProps, }); } - if (hasRunMigrations) { + if (hasRunMigrations && viewMode === ViewMode.EDIT) { allBadges.push({ 'data-test-subj': 'dashboardSaveRecommendedBadge', badgeText: unsavedChangesBadgeStrings.getHasRunMigrationsText(), @@ -264,8 +265,21 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr } as EuiToolTipProps, }); } + if (showWriteControls && managed) { + allBadges.push({ + 'data-test-subj': 'dashboardSaveRecommendedBadge', + badgeText: dashboardManagedBadge.getText(), + title: '', + color: 'primary', + iconType: 'glasses', + toolTipProps: { + content: dashboardManagedBadge.getTooltip(), + position: 'bottom', + } as EuiToolTipProps, + }); + } return allBadges; - }, [hasRunMigrations, hasUnsavedChanges, viewMode]); + }, [hasUnsavedChanges, viewMode, hasRunMigrations, showWriteControls, managed]); return (
    diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx index ed28b2727ca88..1f05f7cc54283 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx @@ -56,6 +56,7 @@ export const useDashboardMenuItems = ({ const lastSavedId = dashboard.select((state) => state.componentState.lastSavedId); const dashboardTitle = dashboard.select((state) => state.explicitInput.title); const viewMode = dashboard.select((state) => state.explicitInput.viewMode); + const managed = dashboard.select((state) => state.componentState.managed); const disableTopNav = isSaveInProgress || hasOverlays; /** @@ -265,7 +266,7 @@ export const useDashboardMenuItems = ({ const labsMenuItem = isLabsEnabled ? [menuItems.labs] : []; const shareMenuItem = share ? [menuItems.share] : []; const cloneMenuItem = showWriteControls ? [menuItems.clone] : []; - const editMenuItem = showWriteControls ? [menuItems.edit] : []; + const editMenuItem = showWriteControls && !managed ? [menuItems.edit] : []; return [ ...labsMenuItem, menuItems.fullScreen, @@ -274,7 +275,7 @@ export const useDashboardMenuItems = ({ resetChangesMenuItem, ...editMenuItem, ]; - }, [menuItems, share, showWriteControls, resetChangesMenuItem, isLabsEnabled]); + }, [isLabsEnabled, menuItems, share, showWriteControls, managed, resetChangesMenuItem]); const editModeTopNavConfig = useMemo(() => { const labsMenuItem = isLabsEnabled ? [menuItems.labs] : []; diff --git a/src/plugins/dashboard/public/dashboard_container/_dashboard_container.scss b/src/plugins/dashboard/public/dashboard_container/_dashboard_container.scss index aa5b5950a0d59..12c11f778d616 100644 --- a/src/plugins/dashboard/public/dashboard_container/_dashboard_container.scss +++ b/src/plugins/dashboard/public/dashboard_container/_dashboard_container.scss @@ -1,7 +1,6 @@ @import '../../../embeddable/public/variables'; @import './component/grid/index'; -@import './component/panel/index'; @import './component/viewport/index'; .dashboardContainer, .dashboardViewport { diff --git a/src/plugins/dashboard/public/dashboard_container/component/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/dashboard_container/component/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap index 46d3b77578b3b..6863540ad4a71 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/dashboard_container/component/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -7,7 +7,7 @@ exports[`DashboardEmptyScreen renders correctly with edit mode 1`] = `
    { function mountComponent(viewMode: ViewMode) { - const dashboardContainer = buildMockDashboard({ viewMode }); + const dashboardContainer = buildMockDashboard({ overrides: { viewMode } }); return mountWithIntl( diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/_dashboard_panel.scss b/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss similarity index 100% rename from src/plugins/dashboard/public/dashboard_container/component/panel/_dashboard_panel.scss rename to src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/_index.scss b/src/plugins/dashboard/public/dashboard_container/component/grid/_index.scss index eb393d7603b8a..cb324e984f7ef 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/_index.scss +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/_index.scss @@ -1 +1,2 @@ @import './dashboard_grid'; +@import './dashboard_panel'; \ No newline at end of file diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx index be21a9ba6645e..8c587bf175bc7 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx @@ -39,16 +39,18 @@ jest.mock('./dashboard_grid_item', () => { const createAndMountDashboardGrid = () => { const dashboardContainer = buildMockDashboard({ - panels: { - '1': { - gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' }, - type: CONTACT_CARD_EMBEDDABLE, - explicitInput: { id: '1' }, - }, - '2': { - gridData: { x: 6, y: 6, w: 6, h: 6, i: '2' }, - type: CONTACT_CARD_EMBEDDABLE, - explicitInput: { id: '2' }, + overrides: { + panels: { + '1': { + gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' }, + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { id: '1' }, + }, + '2': { + gridData: { x: 6, y: 6, w: 6, h: 6, i: '2' }, + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { id: '2' }, + }, }, }, }); diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx index 12cd26df28f18..28a4ccbb1c8a8 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx @@ -125,8 +125,7 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => { className={classes} width={viewportWidth} breakpoints={breakpoints} - onDragStop={onLayoutChange} - onResizeStop={onLayoutChange} + onLayoutChange={onLayoutChange} isResizable={!expandedPanelId} isDraggable={!expandedPanelId} rowHeight={DASHBOARD_GRID_HEIGHT} diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/_index.scss b/src/plugins/dashboard/public/dashboard_container/component/panel/_index.scss deleted file mode 100644 index 8212aad12abf1..0000000000000 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './dashboard_panel'; diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/create_panel_state.test.ts b/src/plugins/dashboard/public/dashboard_container/component/panel/create_panel_state.test.ts deleted file mode 100644 index acfec6de31d08..0000000000000 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/create_panel_state.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { DashboardPanelState } from '../../../../common'; -import { EmbeddableInput } from '@kbn/embeddable-plugin/public'; -import { CONTACT_CARD_EMBEDDABLE } from '@kbn/embeddable-plugin/public/lib/test_samples'; -import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../dashboard_constants'; - -import { createPanelState } from './create_panel_state'; - -interface TestInput extends EmbeddableInput { - test: string; -} -const panels: { [key: string]: DashboardPanelState } = {}; - -test('createPanelState adds a new panel state in 0,0 position', () => { - const { newPanel: panelState } = createPanelState( - { - type: CONTACT_CARD_EMBEDDABLE, - explicitInput: { test: 'hi', id: '123' }, - }, - panels - ); - expect(panelState.explicitInput.test).toBe('hi'); - expect(panelState.type).toBe(CONTACT_CARD_EMBEDDABLE); - expect(panelState.explicitInput.id).toBeDefined(); - expect(panelState.gridData.x).toBe(0); - expect(panelState.gridData.y).toBe(0); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); - - panels[panelState.explicitInput.id] = panelState; -}); - -test('createPanelState adds a second new panel state', () => { - const { newPanel: panelState } = createPanelState( - { type: CONTACT_CARD_EMBEDDABLE, explicitInput: { test: 'bye', id: '456' } }, - panels - ); - - expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH); - expect(panelState.gridData.y).toBe(0); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); - - panels[panelState.explicitInput.id] = panelState; -}); - -test('createPanelState adds a third new panel state', () => { - const { newPanel: panelState } = createPanelState( - { - type: CONTACT_CARD_EMBEDDABLE, - explicitInput: { test: 'bye', id: '789' }, - }, - panels - ); - expect(panelState.gridData.x).toBe(0); - expect(panelState.gridData.y).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); - - panels[panelState.explicitInput.id] = panelState; -}); - -test('createPanelState adds a new panel state in the top most position', () => { - delete panels['456']; - const { newPanel: panelState } = createPanelState( - { - type: CONTACT_CARD_EMBEDDABLE, - explicitInput: { test: 'bye', id: '987' }, - }, - panels - ); - expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH); - expect(panelState.gridData.y).toBe(0); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); -}); diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/create_panel_state.ts b/src/plugins/dashboard/public/dashboard_container/component/panel/create_panel_state.ts deleted file mode 100644 index 8f060f26cfe51..0000000000000 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/create_panel_state.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PanelState, EmbeddableInput } from '@kbn/embeddable-plugin/public'; - -import { - IPanelPlacementArgs, - findTopLeftMostOpenSpace, - PanelPlacementMethod, -} from './dashboard_panel_placement'; -import { DashboardPanelState } from '../../../../common'; -import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../dashboard_constants'; - -/** - * Creates and initializes a basic panel state. - */ -export function createPanelState< - TEmbeddableInput extends EmbeddableInput, - TPlacementMethodArgs extends IPanelPlacementArgs = IPanelPlacementArgs ->( - panelState: PanelState, - currentPanels: { [key: string]: DashboardPanelState }, - placementMethod?: PanelPlacementMethod, - placementArgs?: TPlacementMethodArgs -): { - newPanel: DashboardPanelState; - otherPanels: { [key: string]: DashboardPanelState }; -} { - const defaultPlacementArgs = { - width: DEFAULT_PANEL_WIDTH, - height: DEFAULT_PANEL_HEIGHT, - currentPanels, - }; - const finalPlacementArgs = placementArgs - ? { - ...defaultPlacementArgs, - ...placementArgs, - } - : defaultPlacementArgs; - - const { newPanelPlacement, otherPanels } = placementMethod - ? placementMethod(finalPlacementArgs as TPlacementMethodArgs) - : findTopLeftMostOpenSpace(defaultPlacementArgs); - - return { - newPanel: { - gridData: { - ...newPanelPlacement, - i: panelState.explicitInput.id, - }, - ...panelState, - }, - otherPanels, - }; -} diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts deleted file mode 100644 index 829b26072f0d9..0000000000000 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import { PanelNotFoundError } from '@kbn/embeddable-plugin/public'; -import { DashboardPanelState } from '../../../../common'; -import { GridData } from '../../../../common/content_management'; -import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants'; - -export type PanelPlacementMethod = ( - args: PlacementArgs -) => PanelPlacementMethodReturn; - -interface PanelPlacementMethodReturn { - newPanelPlacement: Omit; - otherPanels: { [key: string]: DashboardPanelState }; -} - -export interface IPanelPlacementArgs { - width: number; - height: number; - currentPanels: { [key: string]: DashboardPanelState }; - scrollToPanel?: boolean; -} - -export interface IPanelPlacementBesideArgs extends IPanelPlacementArgs { - placeBesideId: string; -} - -// Look for the smallest y and x value where the default panel will fit. -export function findTopLeftMostOpenSpace({ - width, - height, - currentPanels, -}: IPanelPlacementArgs): PanelPlacementMethodReturn { - let maxY = -1; - - const currentPanelsArray = Object.values(currentPanels); - currentPanelsArray.forEach((panel) => { - maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY); - }); - - // Handle case of empty grid. - if (maxY < 0) { - return { newPanelPlacement: { x: 0, y: 0, w: width, h: height }, otherPanels: currentPanels }; - } - - const grid = new Array(maxY); - for (let y = 0; y < maxY; y++) { - grid[y] = new Array(DASHBOARD_GRID_COLUMN_COUNT).fill(0); - } - - currentPanelsArray.forEach((panel) => { - for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) { - for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) { - const row = grid[y]; - if (row === undefined) { - throw new Error( - `Attempted to access a row that doesn't exist at ${y} for panel ${JSON.stringify( - panel - )}` - ); - } - grid[y][x] = 1; - } - } - }); - - for (let y = 0; y < maxY; y++) { - for (let x = 0; x < DASHBOARD_GRID_COLUMN_COUNT; x++) { - if (grid[y][x] === 1) { - // Space is filled - continue; - } else { - for (let h = y; h < Math.min(y + height, maxY); h++) { - for (let w = x; w < Math.min(x + width, DASHBOARD_GRID_COLUMN_COUNT); w++) { - const spaceIsEmpty = grid[h][w] === 0; - const fitsPanelWidth = w === x + width - 1; - // If the panel is taller than any other panel in the current grid, it can still fit in the space, hence - // we check the minimum of maxY and the panel height. - const fitsPanelHeight = h === Math.min(y + height - 1, maxY - 1); - - if (spaceIsEmpty && fitsPanelWidth && fitsPanelHeight) { - // Found space - return { - newPanelPlacement: { x, y, w: width, h: height }, - otherPanels: currentPanels, - }; - } else if (grid[h][w] === 1) { - // x, y spot doesn't work, break. - break; - } - } - } - } - } - } - return { newPanelPlacement: { x: 0, y: maxY, w: width, h: height }, otherPanels: currentPanels }; -} - -interface IplacementDirection { - grid: Omit; - fits: boolean; -} - -/** - * Compare grid data by an ending y coordinate. Grid data with a smaller ending y coordinate - * comes first. - * @param a - * @param b - */ -function comparePanels(a: GridData, b: GridData): number { - if (a.y + a.h < b.y + b.h) { - return -1; - } - if (a.y + a.h > b.y + b.h) { - return 1; - } - // a.y === b.y - if (a.x + a.w <= b.x + b.w) { - return -1; - } - return 1; -} - -export function placePanelBeside({ - width, - height, - currentPanels, - placeBesideId, -}: IPanelPlacementBesideArgs): PanelPlacementMethodReturn { - const panelToPlaceBeside = currentPanels[placeBesideId]; - if (!panelToPlaceBeside) { - throw new PanelNotFoundError(); - } - const beside = panelToPlaceBeside.gridData; - const otherPanelGridData: GridData[] = []; - _.forOwn(currentPanels, (panel: DashboardPanelState, key: string | undefined) => { - otherPanelGridData.push(panel.gridData); - }); - - const possiblePlacementDirections: IplacementDirection[] = [ - { grid: { x: beside.x + beside.w, y: beside.y, w: width, h: height }, fits: true }, // right - { grid: { x: 0, y: beside.y + beside.h, w: width, h: height }, fits: true }, // left side of next row - { grid: { x: beside.x, y: beside.y + beside.h, w: width, h: height }, fits: true }, // bottom - ]; - - // first, we check if there is place around the current panel - for (const direction of possiblePlacementDirections) { - if ( - direction.grid.x >= 0 && - direction.grid.x + direction.grid.w <= DASHBOARD_GRID_COLUMN_COUNT && - direction.grid.y >= 0 - ) { - const intersection = otherPanelGridData.some((currentPanelGrid: GridData) => { - return ( - direction.grid.x + direction.grid.w > currentPanelGrid.x && - direction.grid.x < currentPanelGrid.x + currentPanelGrid.w && - direction.grid.y < currentPanelGrid.y + currentPanelGrid.h && - direction.grid.y + direction.grid.h > currentPanelGrid.y - ); - }); - if (!intersection) { - return { newPanelPlacement: direction.grid, otherPanels: currentPanels }; - } - } else { - direction.fits = false; - } - } - // if we get here that means there is no blank space around the panel we are placing beside. This means it's time to mess up the dashboard's groove. Fun! - /** - * 1. sort the panels in the grid - * 2. place the cloned panel to the bottom - * 3. reposition the panels after the cloned panel in the grid - */ - const otherPanels = { ...currentPanels }; - const grid = otherPanelGridData.sort(comparePanels); - - let position = 0; - for (position; position < grid.length; position++) { - if (beside.i === grid[position].i) { - break; - } - } - const bottomPlacement = possiblePlacementDirections[2]; - // place to the bottom and move all other panels - let originalPositionInTheGrid = grid[position + 1].i; - const diff = - bottomPlacement.grid.y + - bottomPlacement.grid.h - - otherPanels[originalPositionInTheGrid].gridData.y; - - for (let j = position + 1; j < grid.length; j++) { - originalPositionInTheGrid = grid[j].i; - const movedPanel = _.cloneDeep(otherPanels[originalPositionInTheGrid]); - movedPanel.gridData.y = movedPanel.gridData.y + diff; - otherPanels[originalPositionInTheGrid] = movedPanel; - } - return { newPanelPlacement: bottomPlacement.grid, otherPanels }; -} diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/index.ts b/src/plugins/dashboard/public/dashboard_container/component/panel/index.ts deleted file mode 100644 index 015b31ed725d9..0000000000000 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { createPanelState } from './create_panel_state'; diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/index.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/index.ts new file mode 100644 index 0000000000000..8e7444712c281 --- /dev/null +++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { placePanel } from './place_panel'; + +export { placeClonePanel } from './place_clone_panel_strategy'; diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_clone_panel_strategy.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_clone_panel_strategy.ts new file mode 100644 index 0000000000000..affe85dff5d26 --- /dev/null +++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_clone_panel_strategy.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { cloneDeep, forOwn } from 'lodash'; +import { PanelNotFoundError } from '@kbn/embeddable-plugin/public'; + +import { DashboardPanelState } from '../../../../common'; +import { GridData } from '../../../../common/content_management'; +import { PanelPlacementProps, PanelPlacementReturn } from './types'; +import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants'; + +interface IplacementDirection { + grid: Omit; + fits: boolean; +} + +/** + * Compare grid data by an ending y coordinate. Grid data with a smaller ending y coordinate + * comes first. + * @param a + * @param b + */ +function comparePanels(a: GridData, b: GridData): number { + if (a.y + a.h < b.y + b.h) { + return -1; + } + if (a.y + a.h > b.y + b.h) { + return 1; + } + // a.y === b.y + if (a.x + a.w <= b.x + b.w) { + return -1; + } + return 1; +} + +export function placeClonePanel({ + width, + height, + currentPanels, + placeBesideId, +}: PanelPlacementProps & { placeBesideId: string }): PanelPlacementReturn { + const panelToPlaceBeside = currentPanels[placeBesideId]; + if (!panelToPlaceBeside) { + throw new PanelNotFoundError(); + } + const beside = panelToPlaceBeside.gridData; + const otherPanelGridData: GridData[] = []; + forOwn(currentPanels, (panel: DashboardPanelState, key: string | undefined) => { + otherPanelGridData.push(panel.gridData); + }); + + const possiblePlacementDirections: IplacementDirection[] = [ + { grid: { x: beside.x + beside.w, y: beside.y, w: width, h: height }, fits: true }, // right + { grid: { x: 0, y: beside.y + beside.h, w: width, h: height }, fits: true }, // left side of next row + { grid: { x: beside.x, y: beside.y + beside.h, w: width, h: height }, fits: true }, // bottom + ]; + + // first, we check if there is place around the current panel + for (const direction of possiblePlacementDirections) { + if ( + direction.grid.x >= 0 && + direction.grid.x + direction.grid.w <= DASHBOARD_GRID_COLUMN_COUNT && + direction.grid.y >= 0 + ) { + const intersection = otherPanelGridData.some((currentPanelGrid: GridData) => { + return ( + direction.grid.x + direction.grid.w > currentPanelGrid.x && + direction.grid.x < currentPanelGrid.x + currentPanelGrid.w && + direction.grid.y < currentPanelGrid.y + currentPanelGrid.h && + direction.grid.y + direction.grid.h > currentPanelGrid.y + ); + }); + if (!intersection) { + return { newPanelPlacement: direction.grid, otherPanels: currentPanels }; + } + } else { + direction.fits = false; + } + } + // if we get here that means there is no blank space around the panel we are placing beside. This means it's time to mess up the dashboard's groove. Fun! + /** + * 1. sort the panels in the grid + * 2. place the cloned panel to the bottom + * 3. reposition the panels after the cloned panel in the grid + */ + const otherPanels = { ...currentPanels }; + const grid = otherPanelGridData.sort(comparePanels); + + let position = 0; + for (position; position < grid.length; position++) { + if (beside.i === grid[position].i) { + break; + } + } + const bottomPlacement = possiblePlacementDirections[2]; + // place to the bottom and move all other panels + let originalPositionInTheGrid = grid[position + 1].i; + const diff = + bottomPlacement.grid.y + + bottomPlacement.grid.h - + otherPanels[originalPositionInTheGrid].gridData.y; + + for (let j = position + 1; j < grid.length; j++) { + originalPositionInTheGrid = grid[j].i; + const movedPanel = cloneDeep(otherPanels[originalPositionInTheGrid]); + movedPanel.gridData.y = movedPanel.gridData.y + diff; + otherPanels[originalPositionInTheGrid] = movedPanel; + } + return { newPanelPlacement: bottomPlacement.grid, otherPanels }; +} diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_new_panel_strategies.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_new_panel_strategies.ts new file mode 100644 index 0000000000000..8a8c8a83193eb --- /dev/null +++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_new_panel_strategies.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { cloneDeep } from 'lodash'; +import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants'; +import { PanelPlacementProps, PanelPlacementReturn } from './types'; + +export const panelPlacementStrategies = { + // Place on the very top of the Dashboard, add the height of this panel to all other panels. + placeAtTop: ({ width, height, currentPanels }: PanelPlacementProps): PanelPlacementReturn => { + const otherPanels = { ...currentPanels }; + for (const [id, panel] of Object.entries(currentPanels)) { + const currentPanel = cloneDeep(panel); + currentPanel.gridData.y = currentPanel.gridData.y + height; + otherPanels[id] = currentPanel; + } + return { + newPanelPlacement: { x: 0, y: 0, w: width, h: height }, + otherPanels, + }; + }, + + // Look for the smallest y and x value where the default panel will fit. + findTopLeftMostOpenSpace: ({ + width, + height, + currentPanels, + }: PanelPlacementProps): PanelPlacementReturn => { + let maxY = -1; + + const currentPanelsArray = Object.values(currentPanels); + currentPanelsArray.forEach((panel) => { + maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY); + }); + + // Handle case of empty grid. + if (maxY < 0) { + return { + newPanelPlacement: { x: 0, y: 0, w: width, h: height }, + otherPanels: currentPanels, + }; + } + + const grid = new Array(maxY); + for (let y = 0; y < maxY; y++) { + grid[y] = new Array(DASHBOARD_GRID_COLUMN_COUNT).fill(0); + } + + currentPanelsArray.forEach((panel) => { + for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) { + for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) { + const row = grid[y]; + if (row === undefined) { + throw new Error( + `Attempted to access a row that doesn't exist at ${y} for panel ${JSON.stringify( + panel + )}` + ); + } + grid[y][x] = 1; + } + } + }); + + for (let y = 0; y < maxY; y++) { + for (let x = 0; x < DASHBOARD_GRID_COLUMN_COUNT; x++) { + if (grid[y][x] === 1) { + // Space is filled + continue; + } else { + for (let h = y; h < Math.min(y + height, maxY); h++) { + for (let w = x; w < Math.min(x + width, DASHBOARD_GRID_COLUMN_COUNT); w++) { + const spaceIsEmpty = grid[h][w] === 0; + const fitsPanelWidth = w === x + width - 1; + // If the panel is taller than any other panel in the current grid, it can still fit in the space, hence + // we check the minimum of maxY and the panel height. + const fitsPanelHeight = h === Math.min(y + height - 1, maxY - 1); + + if (spaceIsEmpty && fitsPanelWidth && fitsPanelHeight) { + // Found space + return { + newPanelPlacement: { x, y, w: width, h: height }, + otherPanels: currentPanels, + }; + } else if (grid[h][w] === 1) { + // x, y spot doesn't work, break. + break; + } + } + } + } + } + } + return { + newPanelPlacement: { x: 0, y: maxY, w: width, h: height }, + otherPanels: currentPanels, + }; + }, +} as const; diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.test.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.test.ts new file mode 100644 index 0000000000000..24023ba92dbce --- /dev/null +++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.test.ts @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DashboardPanelState } from '../../../../common'; +import { EmbeddableFactory, EmbeddableInput } from '@kbn/embeddable-plugin/public'; +import { CONTACT_CARD_EMBEDDABLE } from '@kbn/embeddable-plugin/public/lib/test_samples'; +import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../dashboard_constants'; + +import { placePanel } from './place_panel'; +import { IProvidesPanelPlacementSettings } from './types'; + +interface TestInput extends EmbeddableInput { + test: string; +} +const panels: { [key: string]: DashboardPanelState } = {}; + +test('adds a new panel state in 0,0 position', () => { + const { newPanel: panelState } = placePanel( + {} as unknown as EmbeddableFactory, + { + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { test: 'hi', id: '123' }, + }, + panels + ); + expect(panelState.explicitInput.test).toBe('hi'); + expect(panelState.type).toBe(CONTACT_CARD_EMBEDDABLE); + expect(panelState.explicitInput.id).toBeDefined(); + expect(panelState.gridData.x).toBe(0); + expect(panelState.gridData.y).toBe(0); + expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); + + panels[panelState.explicitInput.id] = panelState; +}); + +test('adds a second new panel state', () => { + const { newPanel: panelState } = placePanel( + {} as unknown as EmbeddableFactory, + { type: CONTACT_CARD_EMBEDDABLE, explicitInput: { test: 'bye', id: '456' } }, + panels + ); + + expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH); + expect(panelState.gridData.y).toBe(0); + expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); + + panels[panelState.explicitInput.id] = panelState; +}); + +test('adds a third new panel state', () => { + const { newPanel: panelState } = placePanel( + {} as unknown as EmbeddableFactory, + { + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { test: 'bye', id: '789' }, + }, + panels + ); + expect(panelState.gridData.x).toBe(0); + expect(panelState.gridData.y).toBe(DEFAULT_PANEL_HEIGHT); + expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); + + panels[panelState.explicitInput.id] = panelState; +}); + +test('adds a new panel state in the top most position when it is open', () => { + // deleting panel 456 means that the top leftmost open position will be at the top of the Dashboard. + delete panels['456']; + const { newPanel: panelState } = placePanel( + {} as unknown as EmbeddableFactory, + { + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { test: 'bye', id: '987' }, + }, + panels + ); + expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH); + expect(panelState.gridData.y).toBe(0); + expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); + + // replace the topmost panel. + panels[panelState.explicitInput.id] = panelState; +}); + +test('adds a new panel state at the very top of the Dashboard with default sizing', () => { + const embeddableFactoryStub: IProvidesPanelPlacementSettings = { + getPanelPlacementSettings: jest.fn().mockImplementation(() => { + return { strategy: 'placeAtTop' }; + }), + }; + + const { newPanel: panelState } = placePanel( + embeddableFactoryStub as unknown as EmbeddableFactory, + { + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { test: 'wowee', id: '9001' }, + }, + panels + ); + expect(panelState.gridData.x).toBe(0); + expect(panelState.gridData.y).toBe(0); + expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); + + expect(embeddableFactoryStub.getPanelPlacementSettings).toHaveBeenCalledWith( + { id: '9001', test: 'wowee' }, + undefined + ); +}); + +test('adds a new panel state at the very top of the Dashboard with custom sizing', () => { + const embeddableFactoryStub: IProvidesPanelPlacementSettings = { + getPanelPlacementSettings: jest.fn().mockImplementation(() => { + return { strategy: 'placeAtTop', width: 10, height: 5 }; + }), + }; + + const { newPanel: panelState } = placePanel( + embeddableFactoryStub as unknown as EmbeddableFactory, + { + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { test: 'woweee', id: '9002' }, + }, + panels + ); + expect(panelState.gridData.x).toBe(0); + expect(panelState.gridData.y).toBe(0); + expect(panelState.gridData.h).toBe(5); + expect(panelState.gridData.w).toBe(10); + + expect(embeddableFactoryStub.getPanelPlacementSettings).toHaveBeenCalledWith( + { id: '9002', test: 'woweee' }, + undefined + ); +}); + +test('passes through given attributes', () => { + const embeddableFactoryStub: IProvidesPanelPlacementSettings = { + getPanelPlacementSettings: jest.fn().mockImplementation(() => { + return { strategy: 'placeAtTop', width: 10, height: 5 }; + }), + }; + + placePanel( + embeddableFactoryStub as unknown as EmbeddableFactory, + { + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { test: 'wow', id: '9004' }, + }, + panels, + { testAttr: 'hello' } + ); + + expect(embeddableFactoryStub.getPanelPlacementSettings).toHaveBeenCalledWith( + { id: '9004', test: 'wow' }, + { testAttr: 'hello' } + ); +}); diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.ts new file mode 100644 index 0000000000000..a65c4fca9c115 --- /dev/null +++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PanelState, EmbeddableInput, EmbeddableFactory } from '@kbn/embeddable-plugin/public'; + +import { DashboardPanelState } from '../../../../common'; +import { panelPlacementStrategies } from './place_new_panel_strategies'; +import { IProvidesPanelPlacementSettings, PanelPlacementSettings } from './types'; +import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../dashboard_constants'; + +export const providesPanelPlacementSettings = ( + value: unknown +): value is IProvidesPanelPlacementSettings => { + return Boolean((value as IProvidesPanelPlacementSettings).getPanelPlacementSettings); +}; + +export function placePanel( + factory: EmbeddableFactory, + newPanel: PanelState, + currentPanels: { [key: string]: DashboardPanelState }, + attributes?: unknown +): { + newPanel: DashboardPanelState; + otherPanels: { [key: string]: DashboardPanelState }; +} { + let placementSettings: PanelPlacementSettings = { + width: DEFAULT_PANEL_WIDTH, + height: DEFAULT_PANEL_HEIGHT, + strategy: 'findTopLeftMostOpenSpace', + }; + if (providesPanelPlacementSettings(factory)) { + placementSettings = { + ...placementSettings, + ...factory.getPanelPlacementSettings(newPanel.explicitInput, attributes), + }; + } + const { width, height, strategy } = placementSettings; + + const { newPanelPlacement, otherPanels } = panelPlacementStrategies[strategy]({ + currentPanels, + height, + width, + }); + + return { + newPanel: { + gridData: { + ...newPanelPlacement, + i: newPanel.explicitInput.id, + }, + ...newPanel, + }, + otherPanels, + }; +} diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/types.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/types.ts new file mode 100644 index 0000000000000..7fb20b469c1a9 --- /dev/null +++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/types.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EmbeddableInput } from '@kbn/embeddable-plugin/public'; +import { DashboardPanelState } from '../../../../common'; +import { GridData } from '../../../../common/content_management'; +import { panelPlacementStrategies } from './place_new_panel_strategies'; + +export type PanelPlacementStrategy = keyof typeof panelPlacementStrategies; + +export interface PanelPlacementSettings { + strategy: PanelPlacementStrategy; + height: number; + width: number; +} + +export interface PanelPlacementReturn { + newPanelPlacement: Omit; + otherPanels: { [key: string]: DashboardPanelState }; +} + +export interface PanelPlacementProps { + width: number; + height: number; + currentPanels: { [key: string]: DashboardPanelState }; +} + +export interface IProvidesPanelPlacementSettings< + InputType extends EmbeddableInput = EmbeddableInput, + AttributesType = unknown +> { + getPanelPlacementSettings: ( + input: InputType, + attributes?: AttributesType + ) => Partial; +} diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx index ae13c70a739d6..22b16e654e8e4 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx @@ -33,10 +33,11 @@ export function runSaveAs(this: DashboardContainer) { const { explicitInput: currentState, - componentState: { lastSavedId }, + componentState: { lastSavedId, managed }, } = this.getState(); return new Promise((resolve) => { + if (managed) resolve(undefined); const onSave = async ({ newTags, newTitle, @@ -132,9 +133,11 @@ export async function runQuickSave(this: DashboardContainer) { const { explicitInput: currentState, - componentState: { lastSavedId }, + componentState: { lastSavedId, managed }, } = this.getState(); + if (managed) return; + const saveResult = await saveDashboardState({ lastSavedId, currentState, diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index 43e8f5ea5933e..244e816620ae7 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -94,6 +94,21 @@ test('pulls state from dashboard saved object when given a saved object id', asy expect(dashboard!.getState().explicitInput.description).toBe(`wow would you look at that? Wow.`); }); +test('passes managed state from the saved object into the Dashboard component state', async () => { + pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest + .fn() + .mockResolvedValue({ + dashboardInput: { + ...DEFAULT_DASHBOARD_INPUT, + description: 'wow this description is okay', + }, + managed: true, + }); + const dashboard = await createDashboard({}, 0, 'what-an-id'); + expect(dashboard).toBeDefined(); + expect(dashboard!.getState().componentState.managed).toBe(true); +}); + test('pulls state from session storage which overrides state from saved object', async () => { pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest .fn() diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index fad615e481ef9..2403a569a80bb 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -29,6 +29,7 @@ import { syncUnifiedSearchState } from './unified_search/sync_dashboard_unified_ import { DEFAULT_DASHBOARD_INPUT, GLOBAL_STATE_STORAGE_KEY } from '../../../dashboard_constants'; import { startSyncingDashboardControlGroup } from './controls/dashboard_control_group_integration'; import { startDashboardSearchSessionIntegration } from './search_sessions/start_dashboard_search_session_integration'; +import { DashboardPublicState } from '../../types'; /** * Builds a new Dashboard from scratch. @@ -86,16 +87,27 @@ export const createDashboard = async ( // -------------------------------------------------------------------------------------- // Build and return the dashboard container. // -------------------------------------------------------------------------------------- + const initialComponentState: DashboardPublicState = { + lastSavedInput: savedObjectResult?.dashboardInput ?? { + ...DEFAULT_DASHBOARD_INPUT, + id: input.id, + }, + hasRunClientsideMigrations: savedObjectResult.anyMigrationRun, + isEmbeddedExternally: creationOptions?.isEmbeddedExternally, + animatePanelTransforms: false, // set panel transforms to false initially to avoid panels animating on initial render. + hasUnsavedChanges: false, // if there is initial unsaved changes, the initial diff will catch them. + managed: savedObjectResult.managed, + lastSavedId: savedObjectId, + }; + const dashboardContainer = new DashboardContainer( input, reduxEmbeddablePackage, searchSessionId, - savedObjectResult?.dashboardInput, - savedObjectResult.anyMigrationRun, dashboardCreationStartTime, undefined, creationOptions, - savedObjectId + initialComponentState ); dashboardContainerReady$.next(dashboardContainer); return dashboardContainer; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx index 67bb482b45676..651d71c106ced 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx @@ -43,11 +43,13 @@ pluginServices.getServices().embeddable.getEmbeddableFactory = jest test('DashboardContainer initializes embeddables', (done) => { const container = buildMockDashboard({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Sam', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), + overrides: { + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, }, }); @@ -94,11 +96,13 @@ test('DashboardContainer.replacePanel', (done) => { const ID = '123'; const container = buildMockDashboard({ - panels: { - [ID]: getSampleDashboardPanel({ - explicitInput: { firstName: 'Sam', id: ID }, - type: CONTACT_CARD_EMBEDDABLE, - }), + overrides: { + panels: { + [ID]: getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: ID }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, }, }); let counter = 0; @@ -134,11 +138,13 @@ test('DashboardContainer.replacePanel', (done) => { test('Container view mode change propagates to existing children', async () => { const container = buildMockDashboard({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Sam', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), + overrides: { + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, }, }); @@ -167,10 +173,15 @@ test('Container view mode change propagates to new children', async () => { test('searchSessionId propagates to children', async () => { const searchSessionId1 = 'searchSessionId1'; + const sampleInput = getSampleDashboardInput(); const container = new DashboardContainer( - getSampleDashboardInput(), + sampleInput, mockedReduxEmbeddablePackage, - searchSessionId1 + searchSessionId1, + 0, + undefined, + undefined, + { lastSavedInput: sampleInput } ); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -192,7 +203,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { uiActionsSetup.registerAction(editModeAction); uiActionsSetup.addTriggerAction(CONTEXT_MENU_TRIGGER, editModeAction); - const container = buildMockDashboard({ viewMode: ViewMode.VIEW }); + const container = buildMockDashboard({ overrides: { viewMode: ViewMode.VIEW } }); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -268,8 +279,10 @@ describe('getInheritedInput', () => { test('Should pass dashboard timeRange and timeslice to panel when panel does not have custom time range', async () => { const container = buildMockDashboard({ - timeRange: dashboardTimeRange, - timeslice: dashboardTimeslice, + overrides: { + timeRange: dashboardTimeRange, + timeslice: dashboardTimeslice, + }, }); const embeddable = await container.addNewEmbeddable( CONTACT_CARD_EMBEDDABLE, @@ -291,8 +304,10 @@ describe('getInheritedInput', () => { test('Should not pass dashboard timeRange and timeslice to panel when panel has custom time range', async () => { const container = buildMockDashboard({ - timeRange: dashboardTimeRange, - timeslice: dashboardTimeslice, + overrides: { + timeRange: dashboardTimeRange, + timeslice: dashboardTimeslice, + }, }); const embeddableTimeRange = { to: 'now', diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index 3027cddd167dd..71c707cc223d1 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -11,7 +11,6 @@ import { batch } from 'react-redux'; import { Subject, Subscription } from 'rxjs'; import React, { createContext, useContext } from 'react'; -import { ReduxToolsPackage, ReduxEmbeddableTools } from '@kbn/presentation-util-plugin/public'; import { ViewMode, Container, @@ -20,6 +19,10 @@ import { type EmbeddableOutput, type EmbeddableFactory, } from '@kbn/embeddable-plugin/public'; +import { + getDefaultControlGroupInput, + persistableControlGroupInputIsEqual, +} from '@kbn/controls-plugin/common'; import { I18nProvider } from '@kbn/i18n-react'; import { RefreshInterval } from '@kbn/data-plugin/public'; import type { Filter, TimeRange, Query } from '@kbn/es-query'; @@ -28,11 +31,8 @@ import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import type { ControlGroupContainer } from '@kbn/controls-plugin/public'; import type { KibanaExecutionContext, OverlayRef } from '@kbn/core/public'; -import { - getDefaultControlGroupInput, - persistableControlGroupInputIsEqual, -} from '@kbn/controls-plugin/common'; import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen'; +import { ReduxToolsPackage, ReduxEmbeddableTools } from '@kbn/presentation-util-plugin/public'; import { runClone, @@ -44,19 +44,24 @@ import { addOrUpdateEmbeddable, } from './api'; +import { + DashboardPublicState, + DashboardReduxState, + DashboardRenderPerformanceStats, +} from '../types'; import { DASHBOARD_CONTAINER_TYPE } from '../..'; -import { createPanelState } from '../component/panel'; +import { placePanel } from '../component/panel_placement'; import { pluginServices } from '../../services/plugin_services'; import { initializeDashboard } from './create/create_dashboard'; +import { DASHBOARD_LOADED_EVENT } from '../../dashboard_constants'; import { DashboardCreationOptions } from './dashboard_container_factory'; import { DashboardAnalyticsService } from '../../services/analytics/types'; import { DashboardViewport } from '../component/viewport/dashboard_viewport'; import { DashboardPanelState, DashboardContainerInput } from '../../../common'; -import { DashboardReduxState, DashboardRenderPerformanceStats } from '../types'; import { dashboardContainerReducers } from '../state/dashboard_container_reducers'; import { startDiffingDashboardState } from '../state/diffing/dashboard_diffing_integration'; -import { DASHBOARD_LOADED_EVENT, DEFAULT_DASHBOARD_INPUT } from '../../dashboard_constants'; import { combineDashboardFiltersWithControlGroupFilters } from './create/controls/dashboard_control_group_integration'; +import { DashboardCapabilitiesService } from '../../services/dashboard_capabilities/types'; export interface InheritedChildInput { filters: Filter[]; @@ -118,6 +123,7 @@ export class DashboardContainer extends Container >( factory: EmbeddableFactory, - partial: Partial = {} - ): DashboardPanelState { - const panelState = super.createNewPanelState(factory, partial); - const { newPanel } = createPanelState(panelState, this.input.panels); - return newPanel; + partial: Partial = {}, + attributes?: unknown + ): { + newPanel: DashboardPanelState; + otherPanels: DashboardContainerInput['panels']; + } { + const { newPanel } = super.createNewPanelState(factory, partial, attributes); + return placePanel(factory, newPanel, this.input.panels, attributes); } public render(dom: HTMLElement) { @@ -248,6 +246,19 @@ export class DashboardContainer extends Container): void { + // block the Dashboard from entering edit mode if this Dashboard is managed. + if ( + (this.getState().componentState.managed || !this.showWriteControls) && + changes.viewMode?.toLowerCase() === ViewMode.EDIT?.toLowerCase() + ) { + const { viewMode, ...rest } = changes; + super.updateInput(rest); + return; + } + super.updateInput(changes); + } + protected getInheritedInput(id: string): InheritedChildInput { const { query, @@ -394,6 +405,7 @@ export class DashboardContainer extends Container { this.dispatch.setLastSavedInput(loadDashboardReturn?.dashboardInput); + this.dispatch.setManaged(loadDashboardReturn?.managed); this.dispatch.setAnimatePanelTransforms(false); // prevents panels from animating on navigate. this.dispatch.setLastSavedId(newSavedObjectId); }); diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx index c85909f8881fc..5ba29031d5cb7 100644 --- a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx @@ -28,6 +28,7 @@ describe('dashboard renderer', () => { mockDashboardContainer = { destroy: jest.fn(), render: jest.fn(), + select: jest.fn(), navigateToDashboard: jest.fn().mockResolvedValue({}), } as unknown as DashboardContainer; mockDashboardFactory = { @@ -143,6 +144,7 @@ describe('dashboard renderer', () => { destroy: jest.fn(), render: jest.fn(), navigateToDashboard: jest.fn(), + select: jest.fn(), } as unknown as DashboardContainer; const mockSuccessFactory = { create: jest.fn().mockReturnValue(mockSuccessEmbeddable), @@ -211,4 +213,48 @@ describe('dashboard renderer', () => { // The shared UX not found prompt should be rendered. expect(wrapper!.find(NotFoundPrompt).exists()).toBeTruthy(); }); + + test('does not add a class to the parent element when expandedPanelId is undefined', async () => { + let wrapper: ReactWrapper; + await act(async () => { + wrapper = await mountWithIntl( +
    + +
    + ); + }); + await wrapper!.update(); + + expect( + wrapper!.find('#superParent').getDOMNode().classList.contains('dshDashboardViewportWrapper') + ).toBe(false); + }); + + test('adds a class to the parent element when expandedPanelId is truthy', async () => { + const mockSuccessEmbeddable = { + destroy: jest.fn(), + render: jest.fn(), + navigateToDashboard: jest.fn(), + select: jest.fn().mockReturnValue('WhatAnExpandedPanel'), + } as unknown as DashboardContainer; + const mockSuccessFactory = { + create: jest.fn().mockReturnValue(mockSuccessEmbeddable), + } as unknown as DashboardContainerFactory; + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockSuccessFactory); + + let wrapper: ReactWrapper; + await act(async () => { + wrapper = await mountWithIntl( +
    + +
    + ); + }); + + expect( + wrapper!.find('#superParent').getDOMNode().classList.contains('dshDashboardViewportWrapper') + ).toBe(true); + }); }); diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx index 0f3e1d5a08a7c..8bd064d268015 100644 --- a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx +++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx @@ -15,6 +15,7 @@ import React, { useEffect, forwardRef, useImperativeHandle, + useLayoutEffect, } from 'react'; import { v4 as uuidv4 } from 'uuid'; import classNames from 'classnames'; @@ -50,6 +51,7 @@ export interface DashboardRendererProps { export const DashboardRenderer = forwardRef( ({ savedObjectId, getCreationOptions, dashboardRedirect, showPlainSpinner }, ref) => { const dashboardRoot = useRef(null); + const dashboardViewport = useRef(null); const [loading, setLoading] = useState(true); const [screenshotMode, setScreenshotMode] = useState(false); const [dashboardContainer, setDashboardContainer] = useState(); @@ -170,6 +172,45 @@ export const DashboardRenderer = forwardRef; }; - return
    {renderDashboardContents()}
    ; + return ( +
    + {dashboardViewport?.current && + dashboardContainer && + !isErrorEmbeddable(dashboardContainer) && ( + + )} + {renderDashboardContents()} +
    + ); } ); + +/** + * Maximizing a panel in Dashboard only works if the parent div has a certain class. This + * small component listens to the Dashboard's expandedPanelId state and adds and removes + * the class to whichever element renders the Dashboard. + */ +const ParentClassController = ({ + dashboard, + viewportRef, +}: { + dashboard: DashboardContainer; + viewportRef: HTMLDivElement; +}) => { + const maximizedPanelId = dashboard.select((state) => state.componentState.expandedPanelId); + + useLayoutEffect(() => { + const parentDiv = viewportRef.parentElement; + if (!parentDiv) return; + + if (maximizedPanelId) { + parentDiv.classList.add('dshDashboardViewportWrapper'); + } else { + parentDiv.classList.remove('dshDashboardViewportWrapper'); + } + }, [maximizedPanelId, viewportRef.parentElement]); + return null; +}; diff --git a/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts b/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts index c201454eda21a..65660e1065f28 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts @@ -7,6 +7,7 @@ */ import { PayloadAction } from '@reduxjs/toolkit'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; import { DashboardReduxState, @@ -89,6 +90,11 @@ export const dashboardContainerReducers = { state: DashboardReduxState, action: PayloadAction ) => { + // Managed Dashboards cannot be put into edit mode. + if (state.componentState.managed) { + state.explicitInput.viewMode = ViewMode.VIEW; + return; + } state.explicitInput.viewMode = action.payload; }, @@ -103,6 +109,13 @@ export const dashboardContainerReducers = { state.explicitInput.title = action.payload; }, + setManaged: ( + state: DashboardReduxState, + action: PayloadAction + ) => { + state.componentState.managed = action.payload; + }, + // ------------------------------------------------------------------------------ // Unsaved Changes Reducers // ------------------------------------------------------------------------------ diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_utils.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_utils.ts index 0b6d2db559b5a..bb0c157017a12 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_utils.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_utils.ts @@ -56,7 +56,9 @@ export const getPanelLayoutsAreEqual = ( ]; for (const key of keys) { if (key === undefined) continue; - if (!defaultDiffFunction(originalObj[key], newObj[key])) differences[key] = newObj[key]; + if (!defaultDiffFunction(originalObj[key], newObj[key])) { + differences[key] = newObj[key]; + } } return differences; }; diff --git a/src/plugins/dashboard/public/dashboard_container/types.ts b/src/plugins/dashboard/public/dashboard_container/types.ts index 8bcc62a04995a..dd01f643b99fc 100644 --- a/src/plugins/dashboard/public/dashboard_container/types.ts +++ b/src/plugins/dashboard/public/dashboard_container/types.ts @@ -40,6 +40,7 @@ export interface DashboardPublicState { fullScreenMode?: boolean; savedQueryId?: string; lastSavedId?: string; + managed?: boolean; scrollToPanelId?: string; highlightPanelId?: string; } diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx index 4da2d77825f47..e7a26c4a6bcd2 100644 --- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx @@ -88,6 +88,7 @@ test('when showWriteControls is true, table list view is passed editing function createItem: expect.any(Function), deleteItems: expect.any(Function), editItem: expect.any(Function), + itemIsEditable: expect.any(Function), }), expect.any(Object) // react context ); diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx index 602ac6a1f4a3c..d16e8b4b7e3f8 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx @@ -149,6 +149,7 @@ describe('useDashboardListingTable', () => { initialPageSize: 5, listingLimit: 20, onFetchSuccess: expect.any(Function), + itemIsEditable: expect.any(Function), setPageDataTestSubject: expect.any(Function), title: 'Dashboard List', urlStateEnabled: false, diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx index 8f2fb7ac76cc9..8c933d0fb28b0 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx @@ -7,27 +7,28 @@ */ import React, { useCallback, useState, useMemo } from 'react'; -import type { SavedObjectsFindOptionsReference } from '@kbn/core/public'; + +import { ViewMode } from '@kbn/embeddable-plugin/public'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; +import type { SavedObjectsFindOptionsReference } from '@kbn/core/public'; +import { OpenContentEditorParams } from '@kbn/content-management-content-editor'; import { TableListViewTableProps } from '@kbn/content-management-table-list-view-table'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { OpenContentEditorParams } from '@kbn/content-management-content-editor'; -import { DashboardContainerInput } from '../../../common'; -import { DashboardListingEmptyPrompt } from '../dashboard_listing_empty_prompt'; -import { pluginServices } from '../../services/plugin_services'; import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_DELETE_TIME, SAVED_OBJECT_LOADED_TIME, } from '../../dashboard_constants'; -import { DashboardItem } from '../../../common/content_management'; import { dashboardListingErrorStrings, dashboardListingTableStrings, } from '../_dashboard_listing_strings'; -import { confirmCreateWithUnsaved } from '../confirm_overlays'; +import { DashboardContainerInput } from '../../../common'; import { DashboardSavedObjectUserContent } from '../types'; +import { confirmCreateWithUnsaved } from '../confirm_overlays'; +import { pluginServices } from '../../services/plugin_services'; +import { DashboardItem } from '../../../common/content_management'; +import { DashboardListingEmptyPrompt } from '../dashboard_listing_empty_prompt'; type GetDetailViewLink = TableListViewTableProps['getDetailViewLink']; @@ -42,6 +43,7 @@ const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUse id: hit.id, updatedAt: hit.updatedAt!, references: hit.references, + managed: hit.managed, attributes: { title, description, @@ -50,14 +52,16 @@ const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUse }; }; +type DashboardListingViewTableProps = Omit< + TableListViewTableProps, + 'tableCaption' +> & { title: string }; + interface UseDashboardListingTableReturnType { hasInitialFetchReturned: boolean; pageDataTestSubject: string | undefined; refreshUnsavedDashboards: () => void; - tableListViewTableProps: Omit< - TableListViewTableProps, - 'tableCaption' - > & { title: string }; + tableListViewTableProps: DashboardListingViewTableProps; unsavedDashboardIds: string[]; } @@ -269,7 +273,7 @@ export const useDashboardListingTable = ({ [getDashboardUrl] ); - const tableListViewTableProps = useMemo( + const tableListViewTableProps: DashboardListingViewTableProps = useMemo( () => ({ contentEditor: { isReadonly: !showWriteControls, @@ -279,6 +283,7 @@ export const useDashboardListingTable = ({ createItem: !showWriteControls || !showCreateDashboardButton ? undefined : createItem, deleteItems: !showWriteControls ? undefined : deleteItems, editItem: !showWriteControls ? undefined : editItem, + itemIsEditable: () => showWriteControls, emptyPrompt, entityName, entityNamePlural, diff --git a/src/plugins/dashboard/public/dashboard_listing/types.ts b/src/plugins/dashboard/public/dashboard_listing/types.ts index 18767c1c75c32..7ced1f4ddec5e 100644 --- a/src/plugins/dashboard/public/dashboard_listing/types.ts +++ b/src/plugins/dashboard/public/dashboard_listing/types.ts @@ -27,6 +27,7 @@ export type TableListViewApplicationService = DashboardApplicationService & { }; export interface DashboardSavedObjectUserContent extends UserContentCommonSchema { + managed?: boolean; attributes: { title: string; description?: string; diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 290f4b7c10f28..6882090df441a 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -13,6 +13,7 @@ export { createDashboardEditUrl, DASHBOARD_APP_ID, LEGACY_DASHBOARD_APP_ID, + DASHBOARD_GRID_COLUMN_COUNT, } from './dashboard_constants'; export { type DashboardAPI, diff --git a/src/plugins/dashboard/public/mocks.tsx b/src/plugins/dashboard/public/mocks.tsx index 69ec7efadf1ad..43436a42044b2 100644 --- a/src/plugins/dashboard/public/mocks.tsx +++ b/src/plugins/dashboard/public/mocks.tsx @@ -12,6 +12,8 @@ import { mockedReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/publ import { DashboardStart } from './plugin'; import { DashboardContainerInput, DashboardPanelState } from '../common'; import { DashboardContainer } from './dashboard_container/embeddable/dashboard_container'; +import { pluginServices } from './services/plugin_services'; +import { registry } from './services/plugin_services.stub'; export type Start = jest.Mocked; @@ -66,9 +68,23 @@ export function setupIntersectionObserverMock({ }); } -export function buildMockDashboard(overrides?: Partial) { +export function buildMockDashboard({ + overrides, + savedObjectId, +}: { + overrides?: Partial; + savedObjectId?: string; +} = {}) { const initialInput = getSampleDashboardInput(overrides); - const dashboardContainer = new DashboardContainer(initialInput, mockedReduxEmbeddablePackage); + const dashboardContainer = new DashboardContainer( + initialInput, + mockedReduxEmbeddablePackage, + undefined, + undefined, + undefined, + undefined, + { lastSavedInput: initialInput } + ); return dashboardContainer; } @@ -120,3 +136,7 @@ export function getSampleDashboardPanel { if (!references || references.length === 0) return rawAttributes; return injectReferences( @@ -182,6 +182,7 @@ export const loadDashboardState = async ({ ); return { + managed, resolveMeta, dashboardInput, anyMigrationRun, diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/types.ts b/src/plugins/dashboard/public/services/dashboard_content_management/types.ts index afb50781532cc..7eb9a0114bfec 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management/types.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/types.ts @@ -64,6 +64,7 @@ type DashboardResolveMeta = DashboardCrudTypes['GetOut']['meta']; export interface LoadDashboardReturn { dashboardFound: boolean; dashboardId?: string; + managed?: boolean; resolveMeta?: DashboardResolveMeta; dashboardInput: DashboardContainerInput; anyMigrationRun?: boolean; diff --git a/src/plugins/data/common/query/text_based_query_state_to_ast.test.ts b/src/plugins/data/common/query/text_based_query_state_to_ast.test.ts index 64f9c5ca59111..ee1e072e8a50f 100644 --- a/src/plugins/data/common/query/text_based_query_state_to_ast.test.ts +++ b/src/plugins/data/common/query/text_based_query_state_to_ast.test.ts @@ -54,4 +54,30 @@ describe('textBasedQueryStateToExpressionAst', () => { }) ); }); + + it('returns an object with the correct structure for an ES|QL query', async () => { + const actual = await textBasedQueryStateToExpressionAst({ + filters: [], + query: { sql: 'FROM foo' }, + time: { + from: 'now', + to: 'now+7d', + }, + }); + + expect(actual).toHaveProperty( + 'chain.1.arguments.timeRange.0.chain.0.arguments', + expect.objectContaining({ + from: ['now'], + to: ['now+7d'], + }) + ); + + expect(actual).toHaveProperty( + 'chain.2.arguments', + expect.objectContaining({ + query: ['FROM foo'], + }) + ); + }); }); diff --git a/src/plugins/data/common/query/text_based_query_state_to_ast.ts b/src/plugins/data/common/query/text_based_query_state_to_ast.ts index cb34d9c9c405c..e24cbbd0a7dab 100644 --- a/src/plugins/data/common/query/text_based_query_state_to_ast.ts +++ b/src/plugins/data/common/query/text_based_query_state_to_ast.ts @@ -49,12 +49,13 @@ export function textBasedQueryStateToExpressionAst({ if (query && isOfAggregateQueryType(query)) { const mode = getAggregateQueryMode(query); - // sql query - if (mode === 'sql' && 'sql' in query) { - const essql = aggregateQueryToAst(query, timeFieldName); + for (const esMode of ['sql', 'esql']) { + if (mode === esMode && esMode in query) { + const essql = aggregateQueryToAst(query, timeFieldName); - if (essql) { - ast.chain.push(essql); + if (essql) { + ast.chain.push(essql); + } } } } diff --git a/src/plugins/data/common/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts index 4812ac6f07c5f..f0b2b2702dbf9 100644 --- a/src/plugins/data/common/search/aggs/agg_type.ts +++ b/src/plugins/data/common/search/aggs/agg_type.ts @@ -30,7 +30,7 @@ type PostFlightRequestFn = ( inspectorRequestAdapter?: RequestAdapter, abortSignal?: AbortSignal, searchSessionId?: string, - disableShardFailureWarning?: boolean + disableWarningToasts?: boolean ) => Promise>; export interface AggTypeConfig< diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 981ff3664fbec..b68893847ed64 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -545,6 +545,119 @@ describe('Terms Agg Other bucket helper', () => { expect(agg).toEqual(false); }); + + test('returns true when nested filter agg has buckets', () => { + const aggConfigs = getAggConfigs([ + { + id: '0', + type: BUCKET_TYPES.FILTERS, + params: [ + { + input: { + language: 'kuery', + query: '', + }, + label: '', + }, + ], + }, + ...nestedTerm.aggs, + ]); + + const nestedTermResponseWithRootFilter = wrapResponse({ + '0': { + buckets: { + '*': { + '1': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 8325, + buckets: [ + { + '2': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 8325, + buckets: [ + { key: 'ios', doc_count: 2850 }, + { key: 'win xp', doc_count: 2830 }, + { key: '__missing__', doc_count: 1430 }, + ], + }, + key: 'US-with-dash', + doc_count: 2850, + }, + { + '2': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 8325, + buckets: [ + { key: 'ios', doc_count: 1850 }, + { key: 'win xp', doc_count: 1830 }, + { key: '__missing__', doc_count: 130 }, + ], + }, + key: 'IN-with-dash', + doc_count: 2830, + }, + ], + }, + doc_count: 1148, + }, + }, + }, + }); + + const otherAggConfig = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[2] as IBucketAggConfig, + enrichResponseWithSampling(nestedTermResponseWithRootFilter) + ); + + expect(otherAggConfig).toBeDefined(); + if (otherAggConfig) { + const expectedResponse = { + 'other-filter': { + aggs: undefined, + filters: { + filters: { + [`${SEP}*${SEP}IN-with-dash`]: { + bool: { + must: [], + filter: [ + { bool: { filter: [], must: [], must_not: [], should: [] } }, + { match_phrase: { 'geo.src': 'IN-with-dash' } }, + { exists: { field: 'machine.os.raw' } }, + ], + should: [], + must_not: [ + { match_phrase: { 'machine.os.raw': 'ios' } }, + { match_phrase: { 'machine.os.raw': 'win xp' } }, + ], + }, + }, + [`${SEP}*${SEP}US-with-dash`]: { + bool: { + must: [], + filter: [ + { bool: { filter: [], must: [], must_not: [], should: [] } }, + { match_phrase: { 'geo.src': 'US-with-dash' } }, + { exists: { field: 'machine.os.raw' } }, + ], + should: [], + must_not: [ + { match_phrase: { 'machine.os.raw': 'ios' } }, + { match_phrase: { 'machine.os.raw': 'win xp' } }, + ], + }, + }, + }, + }, + }, + }; + const resp = otherAggConfig(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + expect(topAgg).toEqual(expectedResponse); + } + }); }); describe(`mergeOtherBucketAggResponse${getTitlePostfix()}`, () => { diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts index 9737883733266..fb88fdbeaa4aa 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -206,7 +206,11 @@ export const buildOtherBucketAgg = ( ) => { // make sure there are actually results for the buckets const agg = aggregations[aggId]; - if (!agg || !agg.buckets.length) { + if ( + !agg || + // buckets can be either an array or an object in case there's also a filter at the same level + (Array.isArray(agg.buckets) ? !agg.buckets.length : !Object.values(agg.buckets).length) + ) { noAggBucketResults = true; return; } @@ -392,7 +396,7 @@ export const createOtherBucketPostFlightRequest = ( inspectorRequestAdapter, abortSignal, searchSessionId, - disableShardFailureWarning + disableWarningToasts ) => { if (!resp.aggregations) return resp; const nestedSearchSource = searchSource.createChild(); @@ -406,7 +410,7 @@ export const createOtherBucketPostFlightRequest = ( nestedSearchSource.fetch$({ abortSignal, sessionId: searchSessionId, - disableShardFailureWarning, + disableWarningToasts, inspector: { adapter: inspectorRequestAdapter, title: i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { diff --git a/src/plugins/data/common/search/expressions/aggregate_query_to_ast.test.ts b/src/plugins/data/common/search/expressions/aggregate_query_to_ast.test.ts index f292954feea82..0ded432eb0508 100644 --- a/src/plugins/data/common/search/expressions/aggregate_query_to_ast.test.ts +++ b/src/plugins/data/common/search/expressions/aggregate_query_to_ast.test.ts @@ -10,14 +10,14 @@ import { aggregateQueryToAst } from './aggregate_query_to_ast'; describe('aggregateQueryToAst', () => { it('should return a function', () => { - expect(aggregateQueryToAst({ sql: 'SELECT * from foo' })).toHaveProperty('type', 'function'); + expect(aggregateQueryToAst({ esql: 'from foo' })).toHaveProperty('type', 'function'); }); it('should forward arguments', () => { - expect(aggregateQueryToAst({ sql: 'SELECT * from foo' }, 'baz')).toHaveProperty( + expect(aggregateQueryToAst({ esql: 'from foo' }, 'baz')).toHaveProperty( 'arguments', expect.objectContaining({ - query: ['SELECT * from foo'], + query: ['from foo'], timeField: ['baz'], }) ); diff --git a/src/plugins/data/common/search/expressions/aggregate_query_to_ast.ts b/src/plugins/data/common/search/expressions/aggregate_query_to_ast.ts index 84e1e4e5f2262..6b69af873585b 100644 --- a/src/plugins/data/common/search/expressions/aggregate_query_to_ast.ts +++ b/src/plugins/data/common/search/expressions/aggregate_query_to_ast.ts @@ -5,10 +5,11 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { i18n } from '@kbn/i18n'; import { buildExpressionFunction, ExpressionAstFunction } from '@kbn/expressions-plugin/common'; import { AggregateQuery } from '../../query'; import { EssqlExpressionFunctionDefinition } from './essql'; +import { EsqlExpressionFunctionDefinition } from './esql'; export const aggregateQueryToAst = ( query: AggregateQuery, @@ -20,4 +21,11 @@ export const aggregateQueryToAst = ( timeField, }).toAst(); } + if ('esql' in query) { + return buildExpressionFunction('esql', { + query: query.esql, + timeField, + locale: i18n.getLocale(), + }).toAst(); + } }; diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts index e0555fbd24076..4c42fdc985ff4 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts @@ -53,7 +53,7 @@ describe('esaggs expression function - public', () => { query: undefined, searchSessionId: 'abc123', searchSourceService: searchSourceCommonMock, - disableShardWarnings: false, + disableWarningToasts: false, timeFields: ['@timestamp', 'utc_time'], timeRange: undefined, }; @@ -139,7 +139,7 @@ describe('esaggs expression function - public', () => { description: 'This request queries Elasticsearch to fetch the data for the visualization.', adapter: undefined, }, - disableShardFailureWarning: false, + disableWarningToasts: false, }); }); @@ -159,7 +159,7 @@ describe('esaggs expression function - public', () => { description: 'MyDescription', adapter: undefined, }, - disableShardFailureWarning: false, + disableWarningToasts: false, }); }); diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index 196ebfa55810f..7f0155d74082f 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -30,7 +30,7 @@ export interface RequestHandlerParams { searchSourceService: ISearchStartSearchSource; timeFields?: string[]; timeRange?: TimeRange; - disableShardWarnings?: boolean; + disableWarningToasts?: boolean; getNow?: () => Date; executionContext?: KibanaExecutionContext; title?: string; @@ -48,7 +48,7 @@ export const handleRequest = ({ searchSourceService, timeFields, timeRange, - disableShardWarnings, + disableWarningToasts, getNow, executionContext, title, @@ -110,7 +110,7 @@ export const handleRequest = ({ requestSearchSource .fetch$({ abortSignal, - disableShardFailureWarning: disableShardWarnings, + disableWarningToasts, sessionId: searchSessionId, inspector: { adapter: inspectorAdapters.requests, diff --git a/src/plugins/data/common/search/expressions/esdsl.ts b/src/plugins/data/common/search/expressions/esdsl.ts index 34a67223b4be5..cc0a84cd4a908 100644 --- a/src/plugins/data/common/search/expressions/esdsl.ts +++ b/src/plugins/data/common/search/expressions/esdsl.ts @@ -126,7 +126,7 @@ export const getEsdslFn = ({ }); try { - const { rawResponse } = await lastValueFrom( + const finalResponse = await lastValueFrom( search( { params: { @@ -141,14 +141,14 @@ export const getEsdslFn = ({ const stats: RequestStatistics = {}; - if (rawResponse?.took) { + if (finalResponse.rawResponse?.took) { stats.queryTime = { label: i18n.translate('data.search.es_search.queryTimeLabel', { defaultMessage: 'Query time', }), value: i18n.translate('data.search.es_search.queryTimeValue', { defaultMessage: '{queryTime}ms', - values: { queryTime: rawResponse.took }, + values: { queryTime: finalResponse.rawResponse.took }, }), description: i18n.translate('data.search.es_search.queryTimeDescription', { defaultMessage: @@ -158,12 +158,12 @@ export const getEsdslFn = ({ }; } - if (rawResponse?.hits) { + if (finalResponse.rawResponse?.hits) { stats.hitsTotal = { label: i18n.translate('data.search.es_search.hitsTotalLabel', { defaultMessage: 'Hits (total)', }), - value: `${rawResponse.hits.total}`, + value: `${finalResponse.rawResponse.hits.total}`, description: i18n.translate('data.search.es_search.hitsTotalDescription', { defaultMessage: 'The number of documents that match the query.', }), @@ -173,19 +173,19 @@ export const getEsdslFn = ({ label: i18n.translate('data.search.es_search.hitsLabel', { defaultMessage: 'Hits', }), - value: `${rawResponse.hits.hits.length}`, + value: `${finalResponse.rawResponse.hits.hits.length}`, description: i18n.translate('data.search.es_search.hitsDescription', { defaultMessage: 'The number of documents returned by the query.', }), }; } - request.stats(stats).ok({ json: rawResponse }); + request.stats(stats).ok({ json: finalResponse }); request.json(dsl); return { type: 'es_raw_response', - body: rawResponse, + body: finalResponse.rawResponse, }; } catch (e) { request.error({ json: e }); diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts new file mode 100644 index 0000000000000..b2d6a0458c63b --- /dev/null +++ b/src/plugins/data/common/search/expressions/esql.ts @@ -0,0 +1,260 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaRequest } from '@kbn/core/server'; +import { castEsToKbnFieldTypeName, ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; +import { i18n } from '@kbn/i18n'; +import type { + Datatable, + DatatableColumnType, + ExpressionFunctionDefinition, +} from '@kbn/expressions-plugin/common'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; + +import { zipObject } from 'lodash'; +import { Observable, defer, throwError } from 'rxjs'; +import { catchError, map, switchMap, tap } from 'rxjs/operators'; +import { buildEsQuery } from '@kbn/es-query'; +import { getEsQueryConfig } from '../../es_query'; +import { getTime } from '../../query'; +import { ESQL_SEARCH_STRATEGY, IKibanaSearchRequest, ISearchGeneric, KibanaContext } from '..'; +import { IKibanaSearchResponse } from '../types'; +import { UiSettingsCommon } from '../..'; + +type Input = KibanaContext | null; +type Output = Observable; + +interface Arguments { + query: string; + timezone?: string; + timeField?: string; + locale?: string; +} + +export type EsqlExpressionFunctionDefinition = ExpressionFunctionDefinition< + 'esql', + Input, + Arguments, + Output +>; + +interface EsqlFnArguments { + getStartDependencies(getKibanaRequest: () => KibanaRequest): Promise; +} + +interface EsqlStartDependencies { + search: ISearchGeneric; + uiSettings: UiSettingsCommon; +} + +function normalizeType(type: string): DatatableColumnType { + switch (type) { + case ES_FIELD_TYPES._INDEX: + case ES_FIELD_TYPES.GEO_POINT: + case ES_FIELD_TYPES.IP: + return KBN_FIELD_TYPES.STRING; + case '_version': + return KBN_FIELD_TYPES.NUMBER; + case 'datetime': + return KBN_FIELD_TYPES.DATE; + default: + return castEsToKbnFieldTypeName(type) as DatatableColumnType; + } +} + +function sanitize(value: string) { + return value.replace(/[\(\)]/g, '_'); +} + +interface ESQLSearchParams { + time_zone?: string; + query: string; + filter?: unknown; + locale?: string; +} + +interface ESQLSearchReponse { + columns?: Array<{ + name: string; + type: string; + }>; + values: unknown[][]; +} + +export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { + const essql: EsqlExpressionFunctionDefinition = { + name: 'esql', + type: 'datatable', + inputTypes: ['kibana_context', 'null'], + help: i18n.translate('data.search.esql.help', { + defaultMessage: 'Queries Elasticsearch using ES|QL.', + }), + args: { + query: { + aliases: ['_', 'q'], + types: ['string'], + help: i18n.translate('data.search.esql.query.help', { + defaultMessage: 'An ES|QL query.', + }), + }, + timezone: { + aliases: ['tz'], + types: ['string'], + default: 'UTC', + help: i18n.translate('data.search.esql.timezone.help', { + defaultMessage: + 'The timezone to use for date operations. Valid ISO8601 formats and UTC offsets both work.', + }), + }, + timeField: { + aliases: ['timeField'], + types: ['string'], + help: i18n.translate('data.search.essql.timeField.help', { + defaultMessage: 'The time field to use in the time range filter set in the context.', + }), + }, + locale: { + aliases: ['locale'], + types: ['string'], + help: i18n.translate('data.search.essql.locale.help', { + defaultMessage: 'The locale to use.', + }), + }, + }, + fn( + input, + { query, timezone, timeField, locale }, + { abortSignal, inspectorAdapters, getKibanaRequest } + ) { + return defer(() => + getStartDependencies(() => { + const request = getKibanaRequest?.(); + if (!request) { + throw new Error( + 'A KibanaRequest is required to run queries on the server. ' + + 'Please provide a request object to the expression execution params.' + ); + } + + return request; + }) + ).pipe( + switchMap(({ search, uiSettings }) => { + const params: ESQLSearchParams = { + query, + time_zone: timezone, + locale, + }; + if (input) { + const esQueryConfigs = getEsQueryConfig( + uiSettings as Parameters[0] + ); + const timeFilter = + input.timeRange && + getTime(undefined, input.timeRange, { + fieldName: timeField, + }); + + params.filter = buildEsQuery( + undefined, + input.query || [], + [...(input.filters ?? []), ...(timeFilter ? [timeFilter] : [])], + esQueryConfigs + ); + } + + let startTime = Date.now(); + const logInspectorRequest = () => { + if (!inspectorAdapters.requests) { + inspectorAdapters.requests = new RequestAdapter(); + } + + const request = inspectorAdapters.requests.start( + i18n.translate('data.search.dataRequest.title', { + defaultMessage: 'Data', + }), + { + description: i18n.translate('data.search.es_search.dataRequest.description', { + defaultMessage: + 'This request queries Elasticsearch to fetch the data for the visualization.', + }), + }, + startTime + ); + startTime = Date.now(); + + return request; + }; + + return search< + IKibanaSearchRequest, + IKibanaSearchResponse + >({ params }, { abortSignal, strategy: ESQL_SEARCH_STRATEGY }).pipe( + catchError((error) => { + if (!error.err) { + error.message = `Unexpected error from Elasticsearch: ${error.message}`; + } else { + const { type, reason } = error.err.attributes; + if (type === 'parsing_exception') { + error.message = `Couldn't parse Elasticsearch ES|QL query. You may need to add backticks to names containing special characters. Check your query and try again. Error: ${reason}`; + } else { + error.message = `Unexpected error from Elasticsearch: ${type} - ${reason}`; + } + } + + return throwError(() => error); + }), + tap({ + next(finalResponse) { + logInspectorRequest() + .stats({ + hits: { + label: i18n.translate('data.search.es_search.hitsLabel', { + defaultMessage: 'Hits', + }), + value: `${finalResponse.rawResponse.values.length}`, + description: i18n.translate('data.search.es_search.hitsDescription', { + defaultMessage: 'The number of documents returned by the query.', + }), + }, + }) + .json(params) + .ok({ json: finalResponse }); + }, + error(error) { + logInspectorRequest().json(params).error({ json: error }); + }, + }) + ); + }), + map(({ rawResponse: body, warning }) => { + const columns = + body.columns?.map(({ name, type }) => ({ + id: sanitize(name), + name: sanitize(name), + meta: { type: normalizeType(type) }, + })) ?? []; + const columnNames = columns.map(({ name }) => name); + const rows = body.values.map((row) => zipObject(columnNames, row)); + + return { + type: 'datatable', + meta: { + type: 'es_ql', + }, + columns, + rows, + warning, + } as Datatable; + }) + ); + }, + }; + + return essql; +}; diff --git a/src/plugins/data/common/search/expressions/essql.ts b/src/plugins/data/common/search/expressions/essql.ts index a5db4674a7d14..e93ee85441a22 100644 --- a/src/plugins/data/common/search/expressions/essql.ts +++ b/src/plugins/data/common/search/expressions/essql.ts @@ -217,14 +217,14 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => { return throwError(() => error); }), tap({ - next({ rawResponse, took }) { + next(finalResponse) { logInspectorRequest() .stats({ hits: { label: i18n.translate('data.search.es_search.hitsLabel', { defaultMessage: 'Hits', }), - value: `${rawResponse.rows.length}`, + value: `${finalResponse.rawResponse.rows.length}`, description: i18n.translate('data.search.es_search.hitsDescription', { defaultMessage: 'The number of documents returned by the query.', }), @@ -235,7 +235,7 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => { }), value: i18n.translate('data.search.es_search.queryTimeValue', { defaultMessage: '{queryTime}ms', - values: { queryTime: took }, + values: { queryTime: finalResponse.took }, }), description: i18n.translate('data.search.es_search.queryTimeDescription', { defaultMessage: @@ -245,10 +245,10 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => { }, }) .json(params) - .ok({ json: rawResponse }); + .ok({ json: finalResponse }); }, error(error) { - logInspectorRequest().error({ json: error }); + logInspectorRequest().json(params).error({ json: error }); }, }) ); diff --git a/src/plugins/data/common/search/expressions/kibana_context_type.ts b/src/plugins/data/common/search/expressions/kibana_context_type.ts index cec788d27d856..d6947d2d46ce3 100644 --- a/src/plugins/data/common/search/expressions/kibana_context_type.ts +++ b/src/plugins/data/common/search/expressions/kibana_context_type.ts @@ -15,7 +15,7 @@ export type ExecutionContextSearch = { filters?: Filter[]; query?: Query | Query[]; timeRange?: TimeRange; - disableShardWarnings?: boolean; + disableWarningToasts?: boolean; }; export type ExpressionValueSearchContext = ExpressionValueBoxed< diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index d0d103abe1ea2..50356da1a4655 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -18,3 +18,4 @@ export * from './strategies/es_search'; export * from './strategies/eql_search'; export * from './strategies/ese_search'; export * from './strategies/sql_search'; +export * from './strategies/esql_search'; diff --git a/src/plugins/data/common/search/poll_search.test.ts b/src/plugins/data/common/search/poll_search.test.ts index dbb8e5137de21..db39b5e187036 100644 --- a/src/plugins/data/common/search/poll_search.test.ts +++ b/src/plugins/data/common/search/poll_search.test.ts @@ -83,7 +83,7 @@ describe('pollSearch', () => { abortSignal: abortController.signal, }).toPromise(); - await new Promise((resolve) => setTimeout(resolve, 500)); + await new Promise((resolve) => setTimeout(resolve, 300)); abortController.abort(); await expect(poll).rejects.toThrow(AbortError); @@ -99,7 +99,7 @@ describe('pollSearch', () => { const cancelFn = jest.fn(); const subscription = pollSearch(searchFn, cancelFn).subscribe(() => {}); - await new Promise((resolve) => setTimeout(resolve, 500)); + await new Promise((resolve) => setTimeout(resolve, 300)); subscription.unsubscribe(); await new Promise((resolve) => setTimeout(resolve, 1000)); diff --git a/src/plugins/data/common/search/poll_search.ts b/src/plugins/data/common/search/poll_search.ts index a58fad3880c69..1a6819257ab67 100644 --- a/src/plugins/data/common/search/poll_search.ts +++ b/src/plugins/data/common/search/poll_search.ts @@ -22,6 +22,8 @@ export const pollSearch = ( else { // if static pollInterval is not provided, then use default back-off logic switch (true) { + case elapsedTime < 1500: + return 300; case elapsedTime < 5000: return 1000; case elapsedTime < 20000: diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 91eea3e50f881..f05f198451e0a 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -520,7 +520,7 @@ export class SearchSource { options.inspector?.adapter, options.abortSignal, options.sessionId, - options.disableShardFailureWarning + options.disableWarningToasts ); } } @@ -918,8 +918,12 @@ export class SearchSource { }; body.query = buildEsQuery(index, query, filters, esQueryConfigs); - // For testing shard failure messages in UI, uncomment the next block and switch to `kibana*` data view - // body.query = { + // For testing shard failure messages in the UI, follow these steps: + // 1. Add all three sample data sets (flights, ecommerce, logs) to Kibana. + // 2. Create a data view using the index pattern `kibana*` and don't use a timestamp field. + // 3. Uncomment the lines below, navigate to Discover, + // and switch to the data view created in step 2. + // body.query.bool.must.push({ // error_query: { // indices: [ // { @@ -930,7 +934,8 @@ export class SearchSource { // }, // ], // }, - // }; + // }); + // Alternatively you could also add this query via "Edit as Query DSL", then it needs no code to be changed if (highlightAll && body.query) { body.highlight = getHighlightRequest(getConfig(UI_SETTINGS.DOC_HIGHLIGHT)); diff --git a/src/plugins/data/common/search/search_source/types.ts b/src/plugins/data/common/search/search_source/types.ts index 140c2dd59a59d..60c0d3713ec64 100644 --- a/src/plugins/data/common/search/search_source/types.ts +++ b/src/plugins/data/common/search/search_source/types.ts @@ -249,7 +249,7 @@ export interface SearchSourceSearchOptions extends ISearchOptions { inspector?: IInspectorInfo; /** - * Disable default warnings of shard failures + * Set to true to disable warning toasts and customize warning display */ - disableShardFailureWarning?: boolean; + disableWarningToasts?: boolean; } diff --git a/src/plugins/data/common/search/strategies/esql_search/index.ts b/src/plugins/data/common/search/strategies/esql_search/index.ts new file mode 100644 index 0000000000000..12594660136d8 --- /dev/null +++ b/src/plugins/data/common/search/strategies/esql_search/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; diff --git a/src/plugins/data/common/search/strategies/esql_search/types.ts b/src/plugins/data/common/search/strategies/esql_search/types.ts new file mode 100644 index 0000000000000..d71da852e55de --- /dev/null +++ b/src/plugins/data/common/search/strategies/esql_search/types.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const ESQL_SEARCH_STRATEGY = 'esql'; diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index cedfa3ee02274..b2f818acaa0ac 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { ConnectionRequestParams } from '@elastic/transport'; import type { TransportRequestOptions } from '@elastic/elasticsearch'; import type { KibanaExecutionContext } from '@kbn/core/public'; import type { DataView } from '@kbn/data-views-plugin/common'; @@ -86,6 +87,11 @@ export interface IKibanaSearchResponse { * The raw response returned by the internal search method (usually the raw ES response) */ rawResponse: RawResponse; + + /** + * HTTP request parameters from elasticsearch transport client t + */ + requestParams?: ConnectionRequestParams; } export interface IKibanaSearchRequest { diff --git a/src/plugins/data/common/search/utils.test.ts b/src/plugins/data/common/search/utils.test.ts index 7d75f1b613369..cd8ba5340b352 100644 --- a/src/plugins/data/common/search/utils.test.ts +++ b/src/plugins/data/common/search/utils.test.ts @@ -24,6 +24,119 @@ describe('utils', () => { expect(isError).toBe(true); }); + it('returns `false` if the response is not running and partial and contains failure details', () => { + const isError = isErrorResponse({ + isPartial: true, + isRunning: false, + rawResponse: { + took: 7, + timed_out: false, + _shards: { + total: 2, + successful: 1, + skipped: 0, + failed: 1, + failures: [ + { + shard: 0, + index: 'remote:tmp-00002', + node: '9SNgMgppT2-6UHJNXwio3g', + reason: { + type: 'script_exception', + reason: 'runtime error', + script_stack: [ + 'org.elasticsearch.server@8.10.0/org.elasticsearch.search.lookup.LeafDocLookup.getFactoryForDoc(LeafDocLookup.java:148)', + 'org.elasticsearch.server@8.10.0/org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:191)', + 'org.elasticsearch.server@8.10.0/org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:32)', + "doc['bar'].value < 10", + ' ^---- HERE', + ], + script: "doc['bar'].value < 10", + lang: 'painless', + position: { + offset: 4, + start: 0, + end: 21, + }, + caused_by: { + type: 'illegal_argument_exception', + reason: 'No field found for [bar] in mapping', + }, + }, + }, + ], + }, + _clusters: { + total: 1, + successful: 1, + skipped: 0, + details: { + remote: { + status: 'partial', + indices: 'tmp-*', + took: 3, + timed_out: false, + _shards: { + total: 2, + successful: 1, + skipped: 0, + failed: 1, + }, + failures: [ + { + shard: 0, + index: 'remote:tmp-00002', + node: '9SNgMgppT2-6UHJNXwio3g', + reason: { + type: 'script_exception', + reason: 'runtime error', + script_stack: [ + 'org.elasticsearch.server@8.10.0/org.elasticsearch.search.lookup.LeafDocLookup.getFactoryForDoc(LeafDocLookup.java:148)', + 'org.elasticsearch.server@8.10.0/org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:191)', + 'org.elasticsearch.server@8.10.0/org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:32)', + "doc['bar'].value < 10", + ' ^---- HERE', + ], + script: "doc['bar'].value < 10", + lang: 'painless', + position: { + offset: 4, + start: 0, + end: 21, + }, + caused_by: { + type: 'illegal_argument_exception', + reason: 'No field found for [bar] in mapping', + }, + }, + }, + ], + }, + }, + }, + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 0, + hits: [ + { + _index: 'remote:tmp-00001', + _id: 'd8JNlYoBFqAcOBVnvdqx', + _score: 0, + _source: { + foo: 'bar', + bar: 1, + }, + }, + ], + }, + }, + }); + expect(isError).toBe(false); + }); + it('returns `false` if the response is running and partial', () => { const isError = isErrorResponse({ isPartial: true, @@ -66,6 +179,13 @@ describe('utils', () => { }); expect(isError).toBe(true); }); + + it('returns `true` if the response does not indicate isRunning', () => { + const isError = isCompleteResponse({ + rawResponse: {}, + }); + expect(isError).toBe(true); + }); }); describe('isPartialResponse', () => { diff --git a/src/plugins/data/common/search/utils.ts b/src/plugins/data/common/search/utils.ts index 88b3868c147f7..c8a6ca46e5919 100644 --- a/src/plugins/data/common/search/utils.ts +++ b/src/plugins/data/common/search/utils.ts @@ -11,17 +11,36 @@ import { AggTypesDependencies } from '..'; import type { IKibanaSearchResponse } from './types'; /** + * From https://github.com/elastic/elasticsearch/issues/55572: "When is_running is false, the query has stopped, which + * may happen due to ... the search failed, in which case is_partial is set to true to indicate that any results that + * may be included in the search response come only from a subset of the shards that the query should have hit." * @returns true if response had an error while executing in ES */ export const isErrorResponse = (response?: IKibanaSearchResponse) => { - return !response || !response.rawResponse || (!response.isRunning && !!response.isPartial); + return ( + !response || + !response.rawResponse || + (!response.isRunning && + !!response.isPartial && + // See https://github.com/elastic/elasticsearch/pull/97731. For CCS with ccs_minimize_roundtrips=true, isPartial + // is true if the search is complete but there are shard failures. In that case, the _clusters.details section + // will have information about those failures. This will also likely be the behavior of CCS with + // ccs_minimize_roundtrips=false and non-CCS after https://github.com/elastic/elasticsearch/issues/98913 is + // resolved. + !response.rawResponse?._clusters?.details) + ); }; /** * @returns true if response is completed successfully */ export const isCompleteResponse = (response?: IKibanaSearchResponse) => { - return Boolean(response && !response.isRunning && !response.isPartial); + // Some custom search strategies do not indicate whether they are still running. In this case, assume it is complete. + if (response && !response.hasOwnProperty('isRunning')) { + return true; + } + + return !isErrorResponse(response) && Boolean(response && !response.isRunning); }; /** diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts index 688f65038aa11..03cd77653becb 100644 --- a/src/plugins/data/config.ts +++ b/src/plugins/data/config.ts @@ -55,7 +55,7 @@ export const searchConfigSchema = schema.object({ * Block and wait until the search is completed up to the timeout (see es async_search's `wait_for_completion_timeout`) * TODO: we should optimize this as 100ms is likely not optimal (https://github.com/elastic/kibana/issues/143277) */ - waitForCompletion: schema.duration({ defaultValue: '100ms' }), + waitForCompletion: schema.duration({ defaultValue: '200ms' }), /** * How long the async search needs to be available after each search poll. Ongoing async searches and any saved search results are deleted after this period. * (see es async_search's `keep_alive`) @@ -71,7 +71,7 @@ export const searchConfigSchema = schema.object({ * How long to wait before polling the async_search after the previous poll response. * If not provided, then default dynamic interval with backoff is used. */ - pollInterval: schema.maybe(schema.number({ min: 1000 })), + pollInterval: schema.maybe(schema.number({ min: 200 })), }), aggs: schema.object({ shardDelay: schema.object({ diff --git a/src/plugins/data/public/incomplete_results_modal/__snapshots__/incomplete_results_modal.test.tsx.snap b/src/plugins/data/public/incomplete_results_modal/__snapshots__/incomplete_results_modal.test.tsx.snap new file mode 100644 index 0000000000000..666b87f998c3e --- /dev/null +++ b/src/plugins/data/public/incomplete_results_modal/__snapshots__/incomplete_results_modal.test.tsx.snap @@ -0,0 +1,271 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IncompleteResultsModal should render shard failures 1`] = ` + + + + + + + + + + , + "data-test-subj": "showClusterDetailsButton", + "id": "table", + "name": "Cluster details", + } + } + tabs={ + Array [ + Object { + "content": + + , + "data-test-subj": "showClusterDetailsButton", + "id": "table", + "name": "Cluster details", + }, + Object { + "content": + {} + , + "data-test-subj": "showRequestButton", + "id": "json-request", + "name": "Request", + }, + Object { + "content": + { + "_shards": { + "total": 4, + "successful": 3, + "skipped": 0, + "failed": 1, + "failures": [ + { + "shard": 0, + "index": "sample-01-rollup", + "node": "VFTFJxpHSdaoiGxJFLSExQ", + "reason": { + "type": "illegal_argument_exception", + "reason": "Field [kubernetes.container.memory.available.bytes] of type [aggregate_metric_double] is not supported for aggregation [percentiles]" + } + } + ] + } +} + , + "data-test-subj": "showResponseButton", + "id": "json-response", + "name": "Response", + }, + ] + } + /> + + + + + + + + + + +`; + +exports[`IncompleteResultsModal should render time out 1`] = ` + + + + + + + + + +

    + Request timed out +

    +
    + , + "data-test-subj": "showClusterDetailsButton", + "id": "table", + "name": "Cluster details", + } + } + tabs={ + Array [ + Object { + "content": + +

    + Request timed out +

    +
    +
    , + "data-test-subj": "showClusterDetailsButton", + "id": "table", + "name": "Cluster details", + }, + Object { + "content": + {} + , + "data-test-subj": "showRequestButton", + "id": "json-request", + "name": "Request", + }, + Object { + "content": + { + "timed_out": true, + "_shards": { + "total": 4, + "successful": 4, + "skipped": 0, + "failed": 0 + } +} + , + "data-test-subj": "showResponseButton", + "id": "json-response", + "name": "Response", + }, + ] + } + /> +
    + + + + + + + + +
    +`; diff --git a/src/plugins/data/public/incomplete_results_modal/_incomplete_results_modal.scss b/src/plugins/data/public/incomplete_results_modal/_incomplete_results_modal.scss new file mode 100644 index 0000000000000..e2ca0f8f9b3b6 --- /dev/null +++ b/src/plugins/data/public/incomplete_results_modal/_incomplete_results_modal.scss @@ -0,0 +1,15 @@ +// set width and height to fixed values to prevent resizing when you switch tabs +.incompleteResultsModal { + min-height: 75vh; + width: 768px; + + // show buttons at the bottom of the modal + .kbnOverlayMountWrapper { + flex-grow: 1; + } + + // smaller gap between the modal title and body + .euiModalHeader { + padding-bottom: 0; + } +} \ No newline at end of file diff --git a/src/plugins/data/public/incomplete_results_modal/incomplete_results_modal.test.tsx b/src/plugins/data/public/incomplete_results_modal/incomplete_results_modal.test.tsx new file mode 100644 index 0000000000000..6e90740cf9888 --- /dev/null +++ b/src/plugins/data/public/incomplete_results_modal/incomplete_results_modal.test.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SearchResponseIncompleteWarning } from '../search'; +import { IncompleteResultsModal } from './incomplete_results_modal'; + +describe('IncompleteResultsModal', () => { + test('should render shard failures', () => { + const component = shallow( + + } + onClose={jest.fn()} + /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('should render time out', () => { + const component = shallow( + + } + onClose={jest.fn()} + /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/data/public/incomplete_results_modal/incomplete_results_modal.tsx b/src/plugins/data/public/incomplete_results_modal/incomplete_results_modal.tsx new file mode 100644 index 0000000000000..eb07d8d60e517 --- /dev/null +++ b/src/plugins/data/public/incomplete_results_modal/incomplete_results_modal.tsx @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { + EuiCallOut, + EuiCodeBlock, + EuiTabbedContent, + EuiCopy, + EuiButton, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalFooter, + EuiButtonEmpty, +} from '@elastic/eui'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SearchRequest } from '..'; +import type { SearchResponseIncompleteWarning } from '../search'; +import { ShardFailureTable } from '../shard_failure_modal/shard_failure_table'; + +export interface Props { + onClose: () => void; + request: SearchRequest; + response: estypes.SearchResponse; + warning: SearchResponseIncompleteWarning; +} + +export function IncompleteResultsModal({ request, response, warning, onClose }: Props) { + const requestJSON = JSON.stringify(request, null, 2); + const responseJSON = JSON.stringify(response, null, 2); + + const tabs = [ + { + id: 'table', + name: i18n.translate( + 'data.search.searchSource.fetch.incompleteResultsModal.tabHeaderClusterDetails', + { + defaultMessage: 'Cluster details', + description: 'Name of the tab displaying cluster details', + } + ), + content: ( + <> + {response.timed_out ? ( + +

    + {i18n.translate( + 'data.search.searchSource.fetch.incompleteResultsModal.requestTimedOutMessage', + { + defaultMessage: 'Request timed out', + } + )} +

    +
    + ) : null} + + {response._shards.failures?.length ? ( + + ) : null} + + ), + ['data-test-subj']: 'showClusterDetailsButton', + }, + { + id: 'json-request', + name: i18n.translate( + 'data.search.searchSource.fetch.incompleteResultsModal.tabHeaderRequest', + { + defaultMessage: 'Request', + description: 'Name of the tab displaying the JSON request', + } + ), + content: ( + + {requestJSON} + + ), + ['data-test-subj']: 'showRequestButton', + }, + { + id: 'json-response', + name: i18n.translate( + 'data.search.searchSource.fetch.incompleteResultsModal.tabHeaderResponse', + { + defaultMessage: 'Response', + description: 'Name of the tab displaying the JSON response', + } + ), + content: ( + + {responseJSON} + + ), + ['data-test-subj']: 'showResponseButton', + }, + ]; + + return ( + + + + + + + + + + + + {(copy) => ( + + + + )} + + onClose()} fill data-test-subj="closeIncompleteResultsModal"> + + + + + ); +} diff --git a/src/plugins/data/public/incomplete_results_modal/index.tsx b/src/plugins/data/public/incomplete_results_modal/index.tsx new file mode 100644 index 0000000000000..9cb02367e67a5 --- /dev/null +++ b/src/plugins/data/public/incomplete_results_modal/index.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { OpenIncompleteResultsModalButtonProps } from './open_incomplete_results_modal_button'; + +const Fallback = () =>
    ; + +const LazyOpenModalButton = React.lazy(() => import('./open_incomplete_results_modal_button')); +export const OpenIncompleteResultsModalButton = (props: OpenIncompleteResultsModalButtonProps) => ( + }> + + +); diff --git a/src/plugins/data/public/incomplete_results_modal/open_incomplete_results_modal_button.tsx b/src/plugins/data/public/incomplete_results_modal/open_incomplete_results_modal_button.tsx new file mode 100644 index 0000000000000..648eca08d525b --- /dev/null +++ b/src/plugins/data/public/incomplete_results_modal/open_incomplete_results_modal_button.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiLink, EuiButton, EuiButtonProps } from '@elastic/eui'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { ThemeServiceStart } from '@kbn/core/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { getOverlays } from '../services'; +import type { SearchRequest } from '..'; +import { IncompleteResultsModal } from './incomplete_results_modal'; +import type { SearchResponseIncompleteWarning } from '../search'; +import './_incomplete_results_modal.scss'; + +// @internal +export interface OpenIncompleteResultsModalButtonProps { + theme: ThemeServiceStart; + warning: SearchResponseIncompleteWarning; + size?: EuiButtonProps['size']; + color?: EuiButtonProps['color']; + getRequestMeta: () => { + request: SearchRequest; + response: estypes.SearchResponse; + }; + isButtonEmpty?: boolean; +} + +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default function OpenIncompleteResultsModalButton({ + getRequestMeta, + theme, + warning, + size = 's', + color = 'warning', + isButtonEmpty = false, +}: OpenIncompleteResultsModalButtonProps) { + const onClick = useCallback(() => { + const { request, response } = getRequestMeta(); + const modal = getOverlays().openModal( + toMountPoint( + modal.close()} + />, + { theme$: theme.theme$ } + ), + { + className: 'incompleteResultsModal', + } + ); + }, [getRequestMeta, theme.theme$, warning]); + + const Component = isButtonEmpty ? EuiLink : EuiButton; + + return ( + + + + ); +} diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index d8aa9a35a29a7..48a2d9c10b71c 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -170,6 +170,7 @@ export type { Reason, WaitUntilNextSessionCompletesOptions, SearchResponseWarning, + SearchResponseIncompleteWarning, } from './search'; export { @@ -273,8 +274,7 @@ export type { } from './query'; // TODO: move to @kbn/search-response-warnings -export type { ShardFailureRequest } from './shard_failure_modal'; -export { ShardFailureOpenModalButton } from './shard_failure_modal'; +export { OpenIncompleteResultsModalButton } from './incomplete_results_modal'; export type { AggsStart } from './search/aggs'; diff --git a/src/plugins/data/public/search/expressions/esaggs.test.ts b/src/plugins/data/public/search/expressions/esaggs.test.ts index de35e0b238da9..cf3b04029a2c8 100644 --- a/src/plugins/data/public/search/expressions/esaggs.test.ts +++ b/src/plugins/data/public/search/expressions/esaggs.test.ts @@ -126,7 +126,7 @@ describe('esaggs expression function - public', () => { searchSessionId: 'abc123', searchSourceService: startDependencies.searchSource, timeFields: args.timeFields, - disableShardWarnings: false, + disableWarningToasts: false, timeRange: undefined, getNow: undefined, }); diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index eecaa5890ba35..5ae0b72e1fe58 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -61,7 +61,7 @@ export function getFunctionDefinition({ return { aggConfigs, indexPattern, searchSource, getNow, handleEsaggsRequest }; }).pipe( switchMap(({ aggConfigs, indexPattern, searchSource, getNow, handleEsaggsRequest }) => { - const { disableShardWarnings } = getSearchContext(); + const { disableWarningToasts } = getSearchContext(); return handleEsaggsRequest({ abortSignal, @@ -74,7 +74,7 @@ export function getFunctionDefinition({ searchSourceService: searchSource, timeFields: args.timeFields, timeRange: get(input, 'timeRange', undefined), - disableShardWarnings: (disableShardWarnings || false) as boolean, + disableWarningToasts: (disableWarningToasts || false) as boolean, getNow, executionContext: getExecutionContext(), }); diff --git a/src/plugins/data/public/search/expressions/esql.ts b/src/plugins/data/public/search/expressions/esql.ts new file mode 100644 index 0000000000000..443d4c595c742 --- /dev/null +++ b/src/plugins/data/public/search/expressions/esql.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StartServicesAccessor } from '@kbn/core/public'; +import { UiSettingsCommon } from '../../../common'; +import { DataPublicPluginStart, DataStartDependencies } from '../../types'; +import { getEsqlFn } from '../../../common/search/expressions/esql'; + +/** + * This is some glue code that takes in `core.getStartServices`, extracts the dependencies + * needed for this function, and wraps them behind a `getStartDependencies` function that + * is then called at runtime. + * + * We do this so that we can be explicit about exactly which dependencies the function + * requires, without cluttering up the top-level `plugin.ts` with this logic. It also + * makes testing the expression function a bit easier since `getStartDependencies` is + * the only thing you should need to mock. + * + * @param getStartServices - core's StartServicesAccessor for this plugin + * @internal + */ +export function getEsql({ + getStartServices, +}: { + getStartServices: StartServicesAccessor; +}) { + return getEsqlFn({ + async getStartDependencies() { + const [ + { uiSettings }, + , + { + nowProvider, + search: { search }, + }, + ] = await getStartServices(); + + return { nowProvider, search, uiSettings: uiSettings as unknown as UiSettingsCommon }; + }, + }); +} diff --git a/src/plugins/data/public/search/expressions/index.ts b/src/plugins/data/public/search/expressions/index.ts index cb07be49e8a62..74e947e2942bb 100644 --- a/src/plugins/data/public/search/expressions/index.ts +++ b/src/plugins/data/public/search/expressions/index.ts @@ -9,5 +9,6 @@ export * from './esaggs'; export * from './esdsl'; export * from './essql'; +export * from './esql'; export * from '../../../common/search/expressions'; export * from './eql'; diff --git a/src/plugins/data/public/search/fetch/extract_warnings.test.ts b/src/plugins/data/public/search/fetch/extract_warnings.test.ts index 28a45ca9e6d65..fed0969c2004f 100644 --- a/src/plugins/data/public/search/fetch/extract_warnings.test.ts +++ b/src/plugins/data/public/search/fetch/extract_warnings.test.ts @@ -10,121 +10,280 @@ import { estypes } from '@elastic/elasticsearch'; import { extractWarnings } from './extract_warnings'; describe('extract search response warnings', () => { - it('should extract warnings from response with shard failures', () => { - const response = { - took: 25, - timed_out: false, - _shards: { - total: 4, - successful: 2, - skipped: 0, - failed: 2, - failures: [ - { - shard: 0, - index: 'sample-01-rollup', - node: 'VFTFJxpHSdaoiGxJFLSExQ', - reason: { - type: 'illegal_argument_exception', - reason: - 'Field [kubernetes.container.memory.available.bytes] of type [aggregate_metric_double] is not supported for aggregation [percentiles]', + describe('single cluster', () => { + it('should extract incomplete warning from response with shard failures', () => { + const response = { + took: 25, + timed_out: false, + _shards: { + total: 4, + successful: 3, + skipped: 0, + failed: 1, + failures: [ + { + shard: 0, + index: 'sample-01-rollup', + node: 'VFTFJxpHSdaoiGxJFLSExQ', + reason: { + type: 'illegal_argument_exception', + reason: + 'Field [kubernetes.container.memory.available.bytes] of type [aggregate_metric_double] is not supported for aggregation [percentiles]', + }, + }, + ], + }, + hits: { total: 18239, max_score: null, hits: [] }, + aggregations: {}, + }; + + expect(extractWarnings(response)).toEqual([ + { + type: 'incomplete', + message: 'The data might be incomplete or wrong.', + clusters: { + '(local)': { + status: 'partial', + indices: '', + took: 25, + timed_out: false, + _shards: response._shards, + failures: response._shards.failures, }, }, - ], - }, - hits: { total: 18239, max_score: null, hits: [] }, - aggregations: {}, - }; + }, + ]); + }); - expect(extractWarnings(response)).toEqual([ - { - type: 'shard_failure', - message: '2 of 4 shards failed', - reason: { - type: 'illegal_argument_exception', - reason: - 'Field [kubernetes.container.memory.available.bytes] of type' + - ' [aggregate_metric_double] is not supported for aggregation [percentiles]', + it('should extract incomplete warning from response with time out', () => { + const response = { + took: 999, + timed_out: true, + _shards: {} as estypes.ShardStatistics, + hits: { hits: [] }, + }; + expect(extractWarnings(response)).toEqual([ + { + type: 'incomplete', + message: 'The data might be incomplete or wrong.', + clusters: { + '(local)': { + status: 'partial', + indices: '', + took: 999, + timed_out: true, + _shards: response._shards, + failures: response._shards.failures, + }, + }, }, - text: 'The data might be incomplete or wrong.', - }, - ]); - }); + ]); + }); - it('should extract timeout warning', () => { - const warnings = { - took: 999, - timed_out: true, - _shards: {} as estypes.ShardStatistics, - hits: { hits: [] }, - }; - expect(extractWarnings(warnings)).toEqual([ - { - type: 'timed_out', - message: 'Data might be incomplete because your request timed out', - }, - ]); - }); + it('should not include warnings when there are none', () => { + const warnings = extractWarnings({ + timed_out: false, + _shards: { + failed: 0, + total: 9000, + }, + } as estypes.SearchResponse); - it('should extract shards failed warnings', () => { - const warnings = { - _shards: { - failed: 77, - total: 79, - }, - } as estypes.SearchResponse; - expect(extractWarnings(warnings)).toEqual([ - { - type: 'shard_failure', - message: '77 of 79 shards failed', - reason: { type: 'generic_shard_warning' }, - text: 'The data might be incomplete or wrong.', - }, - ]); + expect(warnings).toEqual([]); + }); }); - it('should extract shards failed warning failure reason type', () => { - const warnings = extractWarnings({ - _shards: { - failed: 77, - total: 79, - }, - } as estypes.SearchResponse); - expect(warnings).toEqual([ - { - type: 'shard_failure', - message: '77 of 79 shards failed', - reason: { type: 'generic_shard_warning' }, - text: 'The data might be incomplete or wrong.', - }, - ]); - }); + describe('remote clusters', () => { + it('should extract incomplete warning from response with shard failures', () => { + const response = { + took: 25, + timed_out: false, + _shards: { + total: 4, + successful: 3, + skipped: 0, + failed: 1, + failures: [ + { + shard: 0, + index: 'remote1:.ds-kibana_sample_data_logs-2023.08.21-000001', + node: 'NVzFRd6SS4qT9o0k2vIzlg', + reason: { + type: 'query_shard_exception', + reason: + 'failed to create query: [.ds-kibana_sample_data_logs-2023.08.21-000001][0] local shard failure message 123', + index_uuid: 'z1sPO8E4TdWcijNgsL_BxQ', + index: 'remote1:.ds-kibana_sample_data_logs-2023.08.21-000001', + caused_by: { + type: 'runtime_exception', + reason: + 'runtime_exception: [.ds-kibana_sample_data_logs-2023.08.21-000001][0] local shard failure message 123', + }, + }, + }, + ], + }, + _clusters: { + total: 2, + successful: 2, + skipped: 0, + details: { + '(local)': { + status: 'successful', + indices: 'kibana_sample_data_logs,kibana_sample_data_flights', + took: 1, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 0, + failed: 0, + }, + }, + remote1: { + status: 'partial', + indices: 'kibana_sample_data_logs,kibana_sample_data_flights', + took: 5, + timed_out: false, + _shards: { + total: 2, + successful: 1, + skipped: 0, + failed: 1, + }, + failures: [ + { + shard: 0, + index: 'remote1:.ds-kibana_sample_data_logs-2023.08.21-000001', + node: 'NVzFRd6SS4qT9o0k2vIzlg', + reason: { + type: 'query_shard_exception', + reason: + 'failed to create query: [.ds-kibana_sample_data_logs-2023.08.21-000001][0] local shard failure message 123', + index_uuid: 'z1sPO8E4TdWcijNgsL_BxQ', + index: 'remote1:.ds-kibana_sample_data_logs-2023.08.21-000001', + caused_by: { + type: 'runtime_exception', + reason: + 'runtime_exception: [.ds-kibana_sample_data_logs-2023.08.21-000001][0] local shard failure message 123', + }, + }, + }, + ], + }, + }, + }, + hits: { total: 18239, max_score: null, hits: [] }, + aggregations: {}, + }; - it('extracts multiple warnings', () => { - const warnings = extractWarnings({ - timed_out: true, - _shards: { - failed: 77, - total: 79, - }, - } as estypes.SearchResponse); - const [shardFailures, timedOut] = [ - warnings.filter(({ type }) => type !== 'timed_out'), - warnings.filter(({ type }) => type === 'timed_out'), - ]; - expect(shardFailures[0]!.message).toBeDefined(); - expect(timedOut[0]!.message).toBeDefined(); - }); + expect(extractWarnings(response)).toEqual([ + { + type: 'incomplete', + message: 'The data might be incomplete or wrong.', + clusters: response._clusters.details, + }, + ]); + }); - it('should not include shardStats or types fields if there are no warnings', () => { - const warnings = extractWarnings({ - timed_out: false, - _shards: { - failed: 0, - total: 9000, - }, - } as estypes.SearchResponse); + it('should extract incomplete warning from response with time out', () => { + const response = { + took: 999, + timed_out: true, + _shards: { + total: 6, + successful: 6, + skipped: 0, + failed: 0, + }, + _clusters: { + total: 2, + successful: 2, + skipped: 0, + details: { + '(local)': { + status: 'successful', + indices: + 'kibana_sample_data_ecommerce,kibana_sample_data_logs,kibana_sample_data_flights', + took: 0, + timed_out: false, + _shards: { + total: 3, + successful: 3, + skipped: 0, + failed: 0, + }, + }, + remote1: { + status: 'partial', + indices: 'kibana_sample_data*', + took: 10005, + timed_out: true, + _shards: { + total: 3, + successful: 3, + skipped: 0, + failed: 0, + }, + }, + }, + }, + hits: { hits: [] }, + }; + expect(extractWarnings(response)).toEqual([ + { + type: 'incomplete', + message: 'The data might be incomplete or wrong.', + clusters: response._clusters.details, + }, + ]); + }); + + it('should not include warnings when there are none', () => { + const warnings = extractWarnings({ + took: 10, + timed_out: false, + _shards: { + total: 4, + successful: 4, + skipped: 0, + failed: 0, + }, + _clusters: { + total: 2, + successful: 2, + skipped: 0, + details: { + '(local)': { + status: 'successful', + indices: 'kibana_sample_data_logs,kibana_sample_data_flights', + took: 0, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 0, + failed: 0, + }, + }, + remote1: { + status: 'successful', + indices: 'kibana_sample_data_logs,kibana_sample_data_flights', + took: 1, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 0, + failed: 0, + }, + }, + }, + }, + hits: { hits: [] }, + } as estypes.SearchResponse); - expect(warnings).toEqual([]); + expect(warnings).toEqual([]); + }); }); }); diff --git a/src/plugins/data/public/search/fetch/extract_warnings.ts b/src/plugins/data/public/search/fetch/extract_warnings.ts index 7c574acba472c..34b30d5f971bf 100644 --- a/src/plugins/data/public/search/fetch/extract_warnings.ts +++ b/src/plugins/data/public/search/fetch/extract_warnings.ts @@ -8,6 +8,7 @@ import { estypes } from '@elastic/elasticsearch'; import { i18n } from '@kbn/i18n'; +import type { ClusterDetails } from '@kbn/es-types'; import { SearchResponseWarning } from '../types'; /** @@ -16,53 +17,38 @@ import { SearchResponseWarning } from '../types'; export function extractWarnings(rawResponse: estypes.SearchResponse): SearchResponseWarning[] { const warnings: SearchResponseWarning[] = []; - if (rawResponse.timed_out === true) { + const isPartial = rawResponse._clusters + ? Object.values( + ( + rawResponse._clusters as estypes.ClusterStatistics & { + details: Record; + } + ).details + ).some((clusterDetails) => clusterDetails.status !== 'successful') + : rawResponse.timed_out || rawResponse._shards.failed > 0; + if (isPartial) { warnings.push({ - type: 'timed_out', - message: i18n.translate('data.search.searchSource.fetch.requestTimedOutNotificationMessage', { - defaultMessage: 'Data might be incomplete because your request timed out', + type: 'incomplete', + message: i18n.translate('data.search.searchSource.fetch.incompleteResultsMessage', { + defaultMessage: 'The data might be incomplete or wrong.', }), - reason: undefined, // exists so that callers do not have to cast when working with shard warnings. - }); - } - - if (rawResponse._shards && rawResponse._shards.failed) { - const message = i18n.translate( - 'data.search.searchSource.fetch.shardsFailedNotificationMessage', - { - defaultMessage: '{shardsFailed} of {shardsTotal} shards failed', - values: { - shardsFailed: rawResponse._shards.failed, - shardsTotal: rawResponse._shards.total, - }, - } - ); - const text = i18n.translate( - 'data.search.searchSource.fetch.shardsFailedNotificationDescription', - { defaultMessage: 'The data might be incomplete or wrong.' } - ); - - if (rawResponse._shards.failures) { - rawResponse._shards.failures?.forEach((f) => { - warnings.push({ - type: 'shard_failure', - message, - text, - reason: { - type: f.reason.type, - reason: f.reason.reason, + clusters: rawResponse._clusters + ? ( + rawResponse._clusters as estypes.ClusterStatistics & { + details: Record; + } + ).details + : { + '(local)': { + status: 'partial', + indices: '', + took: rawResponse.took, + timed_out: rawResponse.timed_out, + _shards: rawResponse._shards, + failures: rawResponse._shards.failures, + }, }, - }); - }); - } else { - // unknown type and reason - warnings.push({ - type: 'shard_failure', - message, - text, - reason: { type: 'generic_shard_warning' }, - }); - } + }); } return warnings; diff --git a/src/plugins/data/public/search/fetch/handle_warnings.test.ts b/src/plugins/data/public/search/fetch/handle_warnings.test.ts deleted file mode 100644 index bc07eb3673991..0000000000000 --- a/src/plugins/data/public/search/fetch/handle_warnings.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { estypes } from '@elastic/elasticsearch'; -import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; -import { themeServiceMock } from '@kbn/core/public/mocks'; -import { setNotifications } from '../../services'; -import { SearchResponseWarning } from '../types'; -import { filterWarnings, handleWarnings } from './handle_warnings'; -import * as extract from './extract_warnings'; -import { SearchRequest } from '../../../common'; - -jest.mock('@kbn/i18n', () => { - return { - i18n: { - translate: (_id: string, { defaultMessage }: { defaultMessage: string }) => defaultMessage, - }, - }; -}); -jest.mock('./extract_warnings', () => ({ - extractWarnings: jest.fn(() => []), -})); - -const theme = themeServiceMock.createStartContract(); -const warnings: SearchResponseWarning[] = [ - { - type: 'timed_out' as const, - message: 'Something timed out!', - reason: undefined, - }, - { - type: 'shard_failure' as const, - message: 'Some shards failed!', - text: 'test text', - reason: { type: 'illegal_argument_exception', reason: 'Illegal argument! Go to jail!' }, - }, - { - type: 'shard_failure' as const, - message: 'Some shards failed!', - reason: { type: 'generic_shard_failure' }, - }, -]; - -const sessionId = 'abcd'; - -describe('Filtering and showing warnings', () => { - const notifications = notificationServiceMock.createStartContract(); - jest.useFakeTimers(); - - describe('handleWarnings', () => { - const request = { body: {} }; - beforeEach(() => { - jest.resetAllMocks(); - jest.advanceTimersByTime(30000); - setNotifications(notifications); - (notifications.toasts.addWarning as jest.Mock).mockReset(); - (extract.extractWarnings as jest.Mock).mockImplementation(() => warnings); - }); - - test('should notify if timed out', () => { - (extract.extractWarnings as jest.Mock).mockImplementation(() => [warnings[0]]); - const response = { rawResponse: { timed_out: true } } as unknown as estypes.SearchResponse; - handleWarnings({ request, response, theme }); - // test debounce, addWarning should only be called once - handleWarnings({ request, response, theme }); - - expect(notifications.toasts.addWarning).toBeCalledTimes(1); - expect(notifications.toasts.addWarning).toBeCalledWith({ title: 'Something timed out!' }); - - // test debounce, call addWarning again due to sessionId - handleWarnings({ request, response, theme, sessionId }); - expect(notifications.toasts.addWarning).toBeCalledTimes(2); - }); - - test('should notify if shards failed for unknown type/reason', () => { - (extract.extractWarnings as jest.Mock).mockImplementation(() => [warnings[2]]); - const response = { - rawResponse: { _shards: { failed: 1, total: 2, successful: 1, skipped: 1 } }, - } as unknown as estypes.SearchResponse; - handleWarnings({ request, response, theme }); - // test debounce, addWarning should only be called once - handleWarnings({ request, response, theme }); - - expect(notifications.toasts.addWarning).toBeCalledTimes(1); - expect(notifications.toasts.addWarning).toBeCalledWith({ title: 'Some shards failed!' }); - - // test debounce, call addWarning again due to sessionId - handleWarnings({ request, response, theme, sessionId }); - expect(notifications.toasts.addWarning).toBeCalledTimes(2); - }); - - test('should add mount point for shard modal failure button if warning.text is provided', () => { - (extract.extractWarnings as jest.Mock).mockImplementation(() => [warnings[1]]); - const response = { - rawResponse: { _shards: { failed: 1, total: 2, successful: 1, skipped: 1 } }, - } as unknown as estypes.SearchResponse; - handleWarnings({ request, response, theme }); - // test debounce, addWarning should only be called once - handleWarnings({ request, response, theme }); - - expect(notifications.toasts.addWarning).toBeCalledTimes(1); - expect(notifications.toasts.addWarning).toBeCalledWith({ - title: 'Some shards failed!', - text: expect.any(Function), - }); - - // test debounce, call addWarning again due to sessionId - handleWarnings({ request, response, theme, sessionId }); - expect(notifications.toasts.addWarning).toBeCalledTimes(2); - }); - - test('should notify once if the response contains multiple failures', () => { - (extract.extractWarnings as jest.Mock).mockImplementation(() => [warnings[1], warnings[2]]); - const response = { - rawResponse: { _shards: { failed: 1, total: 2, successful: 1, skipped: 1 } }, - } as unknown as estypes.SearchResponse; - handleWarnings({ request, response, theme }); - - expect(notifications.toasts.addWarning).toBeCalledTimes(1); - expect(notifications.toasts.addWarning).toBeCalledWith({ - title: 'Some shards failed!', - text: expect.any(Function), - }); - }); - - test('should notify once if the response contains some unfiltered failures', () => { - const callback = (warning: SearchResponseWarning) => - warning.reason?.type !== 'generic_shard_failure'; - const response = { - rawResponse: { _shards: { failed: 1, total: 2, successful: 1, skipped: 1 } }, - } as unknown as estypes.SearchResponse; - handleWarnings({ request, response, theme, callback }); - - expect(notifications.toasts.addWarning).toBeCalledTimes(1); - expect(notifications.toasts.addWarning).toBeCalledWith({ title: 'Some shards failed!' }); - }); - - test('should not notify if the response contains no unfiltered failures', () => { - const callback = () => true; - const response = { - rawResponse: { _shards: { failed: 1, total: 2, successful: 1, skipped: 1 } }, - } as unknown as estypes.SearchResponse; - handleWarnings({ request, response, theme, callback }); - - expect(notifications.toasts.addWarning).toBeCalledTimes(0); - }); - }); - - describe('filterWarnings', () => { - const callback = jest.fn(); - const request = {} as SearchRequest; - const response = {} as estypes.SearchResponse; - - beforeEach(() => { - callback.mockImplementation(() => { - throw new Error('not initialized'); - }); - }); - - it('filters out all', () => { - callback.mockImplementation(() => true); - expect(filterWarnings(warnings, callback, request, response, 'id')).toEqual([]); - }); - - it('filters out some', () => { - callback.mockImplementation( - (warning: SearchResponseWarning) => warning.reason?.type !== 'generic_shard_failure' - ); - expect(filterWarnings(warnings, callback, request, response, 'id')).toEqual([warnings[2]]); - }); - - it('filters out none', () => { - callback.mockImplementation(() => false); - expect(filterWarnings(warnings, callback, request, response, 'id')).toEqual(warnings); - }); - }); -}); diff --git a/src/plugins/data/public/search/fetch/handle_warnings.tsx b/src/plugins/data/public/search/fetch/handle_warnings.tsx index 3e1353ee2f9a7..3380ffe0c8c99 100644 --- a/src/plugins/data/public/search/fetch/handle_warnings.tsx +++ b/src/plugins/data/public/search/fetch/handle_warnings.tsx @@ -7,49 +7,23 @@ */ import { estypes } from '@elastic/elasticsearch'; -import { debounce } from 'lodash'; -import { EuiSpacer, EuiTextAlign } from '@elastic/eui'; +import { EuiTextAlign } from '@elastic/eui'; import { ThemeServiceStart } from '@kbn/core/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import React from 'react'; -import type { MountPoint } from '@kbn/core/public'; import { SearchRequest } from '..'; import { getNotifications } from '../../services'; -import { ShardFailureOpenModalButton, ShardFailureRequest } from '../../shard_failure_modal'; +import { OpenIncompleteResultsModalButton } from '../../incomplete_results_modal'; import { - SearchResponseShardFailureWarning, + SearchResponseIncompleteWarning, SearchResponseWarning, WarningHandlerCallback, } from '../types'; import { extractWarnings } from './extract_warnings'; -const getDebouncedWarning = () => { - const addWarning = () => { - const { toasts } = getNotifications(); - return debounce(toasts.addWarning.bind(toasts), 30000, { - leading: true, - }); - }; - const memory: Record> = {}; - - return ( - debounceKey: string, - title: string, - text?: string | MountPoint | undefined - ) => { - memory[debounceKey] = memory[debounceKey] || addWarning(); - return memory[debounceKey]({ title, text }); - }; -}; - -const debouncedWarningWithoutReason = getDebouncedWarning(); -const debouncedTimeoutWarning = getDebouncedWarning(); -const debouncedWarning = getDebouncedWarning(); - /** * @internal - * All warnings are expected to come from the same response. Therefore all "text" properties, which contain the - * response, will be the same. + * All warnings are expected to come from the same response. */ export function handleWarnings({ request, @@ -78,47 +52,29 @@ export function handleWarnings({ return; } - // timeout notification - const [timeout] = internal.filter((w) => w.type === 'timed_out'); - if (timeout) { - debouncedTimeoutWarning(sessionId + timeout.message, timeout.message); - } - - // shard warning failure notification - const shardFailures = internal.filter((w) => w.type === 'shard_failure'); - if (shardFailures.length === 0) { + // Incomplete data failure notification + const incompleteWarnings = internal.filter((w) => w.type === 'incomplete'); + if (incompleteWarnings.length === 0) { return; } - const [warning] = shardFailures as SearchResponseShardFailureWarning[]; - const title = warning.message; - - // if warning message contains text (warning response), show in ShardFailureOpenModalButton - if (warning.text) { - const text = toMountPoint( - <> - {warning.text} - - - ({ - request: request as ShardFailureRequest, - response, - })} - /> - - , + const [incompleteWarning] = incompleteWarnings as SearchResponseIncompleteWarning[]; + getNotifications().toasts.addWarning({ + title: incompleteWarning.message, + text: toMountPoint( + + ({ + request, + response, + })} + warning={incompleteWarning} + /> + , { theme$: theme.theme$ } - ); - - debouncedWarning(sessionId + warning.text, title, text); - return; - } - - // timeout warning, or shard warning with no failure reason - debouncedWarningWithoutReason(sessionId + title, title); + ), + }); } /** diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 46024da1096d0..fe193c867475f 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -10,6 +10,7 @@ export * from './expressions'; export type { SearchResponseWarning, + SearchResponseIncompleteWarning, ISearchSetup, ISearchStart, ISearchStartSearchSource, diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts index 00ed4226fea3d..414230b7e5add 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts @@ -29,6 +29,7 @@ import { takeUntil, tap, } from 'rxjs/operators'; +import type { ConnectionRequestParams } from '@elastic/transport'; import { PublicMethodsOf } from '@kbn/utility-types'; import type { HttpSetup, IHttpFetchError } from '@kbn/core-http-browser'; import { BfetchRequestError } from '@kbn/bfetch-plugin/public'; @@ -304,18 +305,38 @@ export class SearchInterceptor { const cancel = () => id && !isSavedToBackground && sendCancelRequest(); + // Async search requires a series of requests + // 1) POST //_async_search/ + // 2..n) GET /_async_search/ + // + // First request contains useful request params for tools like Inspector. + // Preserve and project first request params into responses. + let firstRequestParams: ConnectionRequestParams; + return pollSearch(search, cancel, { pollInterval: this.deps.searchConfig.asyncSearch.pollInterval, ...options, abortSignal: searchAbortController.getSignal(), }).pipe( tap((response) => { + if (!firstRequestParams && response.requestParams) { + firstRequestParams = response.requestParams; + } + id = response.id; if (isCompleteResponse(response)) { searchTracker?.complete(); } }), + map((response) => { + return firstRequestParams + ? { + ...response, + requestParams: firstRequestParams, + } + : response; + }), catchError((e: Error) => { searchTracker?.error(); cancel(); diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts index c94cde6b8f747..784a41a299503 100644 --- a/src/plugins/data/public/search/search_service.test.ts +++ b/src/plugins/data/public/search/search_service.test.ts @@ -142,7 +142,7 @@ describe('Search service', () => { expect(notifications.toasts.addWarning).toBeCalledTimes(1); expect(notifications.toasts.addWarning).toBeCalledWith({ - title: '2 of 4 shards failed', + title: 'The data might be incomplete or wrong.', text: expect.any(Function), }); }); @@ -155,90 +155,6 @@ describe('Search service', () => { expect(notifications.toasts.addWarning).toBeCalledTimes(0); }); - - it('will show single notification when some warnings are filtered', () => { - callback = (warning) => warning.reason?.type === 'illegal_argument_exception'; - shards.failures = [ - { - reason: { - type: 'illegal_argument_exception', - reason: 'reason of "illegal_argument_exception"', - }, - }, - { - reason: { - type: 'other_kind_of_exception', - reason: 'reason of other_kind_of_exception', - }, - }, - { reason: { type: 'fatal_warning', reason: 'this is a fatal warning message' } }, - ] as unknown as estypes.ShardFailure[]; - - const responder = inspector.adapter.start('request1'); - responder.ok(getMockResponseWithShards(shards)); - data.showWarnings(inspector.adapter, callback); - - expect(notifications.toasts.addWarning).toBeCalledTimes(1); - expect(notifications.toasts.addWarning).toBeCalledWith({ - title: '2 of 4 shards failed', - text: expect.any(Function), - }); - }); - - it('can show a timed_out warning', () => { - const responder = inspector.adapter.start('request1'); - shards = { total: 4, successful: 4, skipped: 0, failed: 0 }; - const response1 = getMockResponseWithShards(shards); - response1.json.rawResponse.timed_out = true; - responder.ok(response1); - data.showWarnings(inspector.adapter, callback); - - expect(notifications.toasts.addWarning).toBeCalledTimes(1); - expect(notifications.toasts.addWarning).toBeCalledWith({ - title: 'Data might be incomplete because your request timed out', - }); - }); - - it('can show two warnings if response has shard failures and also timed_out', () => { - const responder = inspector.adapter.start('request1'); - const response1 = getMockResponseWithShards(shards); - response1.json.rawResponse.timed_out = true; - responder.ok(response1); - data.showWarnings(inspector.adapter, callback); - - expect(notifications.toasts.addWarning).toBeCalledTimes(2); - expect(notifications.toasts.addWarning).nthCalledWith(1, { - title: 'Data might be incomplete because your request timed out', - }); - expect(notifications.toasts.addWarning).nthCalledWith(2, { - title: '2 of 4 shards failed', - text: expect.any(Function), - }); - }); - - it('will show multiple warnings when multiple responses have shard failures', () => { - const responder1 = inspector.adapter.start('request1'); - const responder2 = inspector.adapter.start('request2'); - responder1.ok(getMockResponseWithShards(shards)); - responder2.ok({ - json: { - rawResponse: { - timed_out: true, - }, - }, - }); - - data.showWarnings(inspector.adapter, callback); - - expect(notifications.toasts.addWarning).toBeCalledTimes(2); - expect(notifications.toasts.addWarning).nthCalledWith(1, { - title: '2 of 4 shards failed', - text: expect.any(Function), - }); - expect(notifications.toasts.addWarning).nthCalledWith(2, { - title: 'Data might be incomplete because your request timed out', - }); - }); }); }); }); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 4a16d9487d2ea..4631425696243 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -63,7 +63,7 @@ import { NowProviderInternalContract } from '../now_provider'; import { DataPublicPluginStart, DataStartDependencies } from '../types'; import { AggsService } from './aggs'; import { createUsageCollector, SearchUsageCollector } from './collectors'; -import { getEql, getEsaggs, getEsdsl, getEssql } from './expressions'; +import { getEql, getEsaggs, getEsdsl, getEssql, getEsql } from './expressions'; import { handleWarnings } from './fetch/handle_warnings'; import { ISearchInterceptor, SearchInterceptor } from './search_interceptor'; @@ -173,6 +173,11 @@ export class SearchService implements Plugin { getStartServices: StartServicesAccessor; }) ); + expressions.registerFunction( + getEsql({ getStartServices } as { + getStartServices: StartServicesAccessor; + }) + ); expressions.registerFunction( getEql({ getStartServices } as { getStartServices: StartServicesAccessor; @@ -238,7 +243,7 @@ export class SearchService implements Plugin { getConfig: uiSettings.get.bind(uiSettings), search, onResponse: (request, response, options) => { - if (!options.disableShardFailureWarning) { + if (!options.disableWarningToasts) { const { rawResponse } = response; handleWarnings({ diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index d1fde7bd4d7e6..2daceefeadb77 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -7,6 +7,7 @@ */ import { estypes } from '@elastic/elasticsearch'; +import type { ClusterDetails } from '@kbn/es-types'; import type { PackageInfo } from '@kbn/core/server'; import { DataViewsContract } from '@kbn/data-views-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/public'; @@ -96,63 +97,35 @@ export interface SearchServiceStartDependencies { } /** - * A warning object for a search response with internal ES timeouts + * A warning object for a search response with incomplete ES results + * ES returns incomplete results when: + * 1) Set timeout flag on search and the timeout expires on cluster + * 2) Some shard failures on a cluster + * 3) skipped remote(s) (skip_unavailable=true) + * a. all shards failed + * b. disconnected/not-connected * @public */ -export interface SearchResponseTimeoutWarning { +export interface SearchResponseIncompleteWarning { /** - * type: for sorting out timeout warnings + * type: for sorting out incomplete warnings */ - type: 'timed_out'; + type: 'incomplete'; /** * message: human-friendly message */ message: string; /** - * reason: not given for timeout. This exists so that callers do not have to cast when working with shard failure warnings. + * clusters: cluster details. */ - reason: undefined; -} - -/** - * A warning object for a search response with internal ES shard failures - * @public - */ -export interface SearchResponseShardFailureWarning { - /** - * type: for sorting out shard failure warnings - */ - type: 'shard_failure'; - /** - * message: human-friendly message - */ - message: string; - /** - * text: text to show in ShardFailureModal (optional) - */ - text?: string; - /** - * reason: ShardFailureReason from es client - */ - reason: { - /** - * type: failure code from Elasticsearch - */ - type: 'generic_shard_warning' | estypes.ShardFailure['reason']['type']; - /** - * reason: failure reason from Elasticsearch - */ - reason?: estypes.ShardFailure['reason']['reason']; - }; + clusters: Record; } /** * A warning object for a search response with warnings * @public */ -export type SearchResponseWarning = - | SearchResponseTimeoutWarning - | SearchResponseShardFailureWarning; +export type SearchResponseWarning = SearchResponseIncompleteWarning; /** * A callback function which can intercept warnings when passed to {@link showWarnings}. Pass `true` from the diff --git a/src/plugins/data/public/shard_failure_modal/__mocks__/shard_failure_request.ts b/src/plugins/data/public/shard_failure_modal/__mocks__/shard_failure_request.ts deleted file mode 100644 index c13ca9e71f48f..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/__mocks__/shard_failure_request.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ShardFailureRequest } from '../shard_failure_types'; -export const shardFailureRequest = { - version: true, - size: 500, - sort: [], - _source: { - excludes: [], - }, - stored_fields: ['*'], - script_fields: {}, - docvalue_fields: [], - query: {}, - highlight: {}, -} as ShardFailureRequest; diff --git a/src/plugins/data/public/shard_failure_modal/__mocks__/shard_failure_response.ts b/src/plugins/data/public/shard_failure_modal/__mocks__/shard_failure_response.ts index 50355a933ec5d..b45caefd5fe26 100644 --- a/src/plugins/data/public/shard_failure_modal/__mocks__/shard_failure_response.ts +++ b/src/plugins/data/public/shard_failure_modal/__mocks__/shard_failure_response.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { estypes } from '@elastic/elasticsearch'; export const shardFailureResponse: estypes.SearchResponse = { _shards: { @@ -33,4 +33,4 @@ export const shardFailureResponse: estypes.SearchResponse = { }, ], }, -} as any; +} as unknown as estypes.SearchResponse; diff --git a/src/plugins/data/public/shard_failure_modal/__snapshots__/shard_failure_description.test.tsx.snap b/src/plugins/data/public/shard_failure_modal/__snapshots__/shard_failure_description.test.tsx.snap index 56581d0f3f019..c8635d69e1fde 100644 --- a/src/plugins/data/public/shard_failure_modal/__snapshots__/shard_failure_description.test.tsx.snap +++ b/src/plugins/data/public/shard_failure_modal/__snapshots__/shard_failure_description.test.tsx.snap @@ -10,14 +10,18 @@ exports[`ShardFailureDescription renders matching snapshot given valid propertie grow={false} > - - - test - - - - , - "data-test-subj": "shardFailuresModalShardButton", - "id": "table", - "name": "Shard failures", - } - } - tabs={ - Array [ - Object { - "content": , - "data-test-subj": "shardFailuresModalShardButton", - "id": "table", - "name": "Shard failures", - }, - Object { - "content": - { - "version": true, - "size": 500, - "sort": [], - "_source": { - "excludes": [] - }, - "stored_fields": [ - "*" - ], - "script_fields": {}, - "docvalue_fields": [], - "query": {}, - "highlight": {} -} - , - "data-test-subj": "shardFailuresModalRequestButton", - "id": "json-request", - "name": "Request", - }, - Object { - "content": - { - "_shards": { - "total": 2, - "successful": 1, - "skipped": 0, - "failed": 1, - "failures": [ - { - "shard": 0, - "index": "repro2", - "node": "itsmeyournode", - "reason": { - "type": "script_exception", - "reason": "runtime error", - "script_stack": [ - "return doc['targetfield'].value;", - " ^---- HERE" - ], - "script": "return doc['targetfield'].value;", - "lang": "painless", - "caused_by": { - "type": "illegal_argument_exception", - "reason": "Gimme reason" - } - } - } - ] - } -} - , - "data-test-subj": "shardFailuresModalResponseButton", - "id": "json-response", - "name": "Response", - }, - ] - } - /> - - - - - - - - - - -`; diff --git a/src/plugins/data/public/shard_failure_modal/_shard_failure_modal.scss b/src/plugins/data/public/shard_failure_modal/_shard_failure_modal.scss deleted file mode 100644 index 0d428a5238248..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/_shard_failure_modal.scss +++ /dev/null @@ -1,39 +0,0 @@ -// set width and height to fixed values to prevent resizing when you switch tabs -.shardFailureModal { - min-height: 75vh; - width: 768px; - - // show buttons at the bottom of the modal - .kbnOverlayMountWrapper { - flex-grow: 1; - } - - // smaller gap between the modal title and body - .euiModalHeader { - padding-bottom: 0; - } -} - -.shardFailureModal__desc { - // set for IE11, since without it depending on the content the width of the list - // could be much higher than the available screenspace - max-width: 686px; -} - -.shardFailureModal__descTitle { - width: 12% !important; -} - -.shardFailureModal__descValue { - width: 88% !important; -} - -@include euiBreakpoint('xs','s') { - .shardFailureModal__descTitle { - width: 100% !important; - } - - .shardFailureModal__descValue { - width: 100% !important; - } -} diff --git a/src/plugins/data/public/shard_failure_modal/index.tsx b/src/plugins/data/public/shard_failure_modal/index.tsx deleted file mode 100644 index f600ca4368e48..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/index.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import type { ShardFailureOpenModalButtonProps } from './shard_failure_open_modal_button'; - -const Fallback = () =>
    ; - -const LazyShardFailureOpenModalButton = React.lazy( - () => import('./shard_failure_open_modal_button') -); -export const ShardFailureOpenModalButton = (props: ShardFailureOpenModalButtonProps) => ( - }> - - -); - -export type { ShardFailureRequest } from './shard_failure_types'; diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_description.test.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_description.test.tsx index 9664ca1f9f997..23a455ac04c3c 100644 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_description.test.tsx +++ b/src/plugins/data/public/shard_failure_modal/shard_failure_description.test.tsx @@ -14,13 +14,13 @@ import { shardFailureResponse } from './__mocks__/shard_failure_response'; describe('ShardFailureDescription', () => { it('renders matching snapshot given valid properties', () => { - const failure = (shardFailureResponse._shards as any).failures[0]; + const failure = shardFailureResponse._shards.failures![0]; const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); it('should show more details when button is pressed', async () => { - const failure = (shardFailureResponse._shards as any).failures[0]; + const failure = shardFailureResponse._shards.failures![0]; const component = shallowWithIntl(); await component.find(EuiButtonEmpty).simulate('click'); expect(component).toMatchSnapshot(); diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_description.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_description.tsx index 1cb1cc3695ea0..32b54a87294d3 100644 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_description.tsx +++ b/src/plugins/data/public/shard_failure_modal/shard_failure_description.tsx @@ -7,6 +7,7 @@ */ import React, { useState } from 'react'; +import { estypes } from '@elastic/elasticsearch'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { getFlattenedObject } from '@kbn/std'; @@ -17,7 +18,6 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { ShardFailure } from './shard_failure_types'; /** * Provides pretty formatting of a given key string @@ -47,7 +47,7 @@ export function formatValueByKey(value: unknown, key: string): string | JSX.Elem } } -export function ShardFailureDescription(props: ShardFailure) { +export function ShardFailureDescription(props: estypes.ShardFailure) { const [showDetails, setShowDetails] = useState(false); const flattendReason = getFlattenedObject(props.reason); @@ -70,7 +70,7 @@ export function ShardFailureDescription(props: ShardFailure) { title: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.indexTitle', { defaultMessage: 'Index', }), - description: props.index, + description: props.index ?? '', }, { title: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.reasonTypeTitle', { @@ -84,7 +84,7 @@ export function ShardFailureDescription(props: ShardFailure) { title: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.nodeTitle', { defaultMessage: 'Node', }), - description: props.node, + description: props.node ?? '', }, ...reasonItems, ] @@ -96,10 +96,9 @@ export function ShardFailureDescription(props: ShardFailure) { diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_modal.test.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_modal.test.tsx deleted file mode 100644 index d4b30d5a1923b..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_modal.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { shallowWithIntl } from '@kbn/test-jest-helpers'; -import { ShardFailureModal } from './shard_failure_modal'; -import { shardFailureRequest } from './__mocks__/shard_failure_request'; -import { shardFailureResponse } from './__mocks__/shard_failure_response'; - -describe('ShardFailureModal', () => { - it('renders matching snapshot given valid properties', () => { - const component = shallowWithIntl( - - ); - - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_modal.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_modal.tsx deleted file mode 100644 index 5dd7bebc2b77e..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_modal.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; -import { - EuiCodeBlock, - EuiTabbedContent, - EuiCopy, - EuiButton, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalFooter, - EuiButtonEmpty, - EuiCallOut, -} from '@elastic/eui'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ShardFailureTable } from './shard_failure_table'; -import { ShardFailureRequest } from './shard_failure_types'; - -export interface Props { - onClose: () => void; - request: ShardFailureRequest; - response: estypes.SearchResponse; - title: string; -} - -export function ShardFailureModal({ request, response, title, onClose }: Props) { - if ( - !response || - !response._shards || - !Array.isArray((response._shards as any).failures) || - !request - ) { - // this should never ever happen, but just in case - return ( - - The ShardFailureModal component received invalid properties - - ); - } - const failures = (response._shards as any).failures; - const requestJSON = JSON.stringify(request, null, 2); - const responseJSON = JSON.stringify(response, null, 2); - - const tabs = [ - { - id: 'table', - name: i18n.translate( - 'data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures', - { - defaultMessage: 'Shard failures', - description: 'Name of the tab displaying shard failures', - } - ), - content: , - ['data-test-subj']: 'shardFailuresModalShardButton', - }, - { - id: 'json-request', - name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest', { - defaultMessage: 'Request', - description: 'Name of the tab displaying the JSON request', - }), - content: ( - - {requestJSON} - - ), - ['data-test-subj']: 'shardFailuresModalRequestButton', - }, - { - id: 'json-response', - name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse', { - defaultMessage: 'Response', - description: 'Name of the tab displaying the JSON response', - }), - content: ( - - {responseJSON} - - ), - ['data-test-subj']: 'shardFailuresModalResponseButton', - }, - ]; - - return ( - - - - {title} - - - - - - - - {(copy) => ( - - - - )} - - onClose()} fill data-test-subj="closeShardFailureModal"> - - - - - ); -} diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.test.mocks.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.test.mocks.tsx deleted file mode 100644 index 948905cf1ce90..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.test.mocks.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { setOverlays } from '../services'; -import { OverlayStart } from '@kbn/core/public'; - -export const openModal = jest.fn(); - -setOverlays({ - openModal, -} as unknown as OverlayStart); diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.test.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.test.tsx deleted file mode 100644 index 00f0315e69275..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { openModal } from './shard_failure_open_modal_button.test.mocks'; -import React from 'react'; -import { themeServiceMock } from '@kbn/core/public/mocks'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import ShardFailureOpenModalButton from './shard_failure_open_modal_button'; -import { shardFailureRequest } from './__mocks__/shard_failure_request'; -import { shardFailureResponse } from './__mocks__/shard_failure_response'; -import { findTestSubject } from '@elastic/eui/lib/test'; - -const theme = themeServiceMock.createStartContract(); - -describe('ShardFailureOpenModalButton', () => { - it('triggers the openModal function when "Show details" button is clicked', () => { - const component = mountWithIntl( - ({ - request: shardFailureRequest, - response: shardFailureResponse, - })} - theme={theme} - title="test" - /> - ); - findTestSubject(component, 'openShardFailureModalBtn').simulate('click'); - expect(openModal).toHaveBeenCalled(); - }); -}); diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.tsx deleted file mode 100644 index 922aee3f49483..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_open_modal_button.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useCallback } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiLink, EuiButton, EuiButtonProps } from '@elastic/eui'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ThemeServiceStart } from '@kbn/core/public'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { getOverlays } from '../services'; -import { ShardFailureModal } from './shard_failure_modal'; -import type { ShardFailureRequest } from './shard_failure_types'; -import './_shard_failure_modal.scss'; - -// @internal -export interface ShardFailureOpenModalButtonProps { - theme: ThemeServiceStart; - title: string; - size?: EuiButtonProps['size']; - color?: EuiButtonProps['color']; - getRequestMeta: () => { - request: ShardFailureRequest; - response: estypes.SearchResponse; - }; - isButtonEmpty?: boolean; -} - -// Needed for React.lazy -// eslint-disable-next-line import/no-default-export -export default function ShardFailureOpenModalButton({ - getRequestMeta, - theme, - title, - size = 's', - color = 'warning', - isButtonEmpty = false, -}: ShardFailureOpenModalButtonProps) { - const onClick = useCallback(() => { - const { request, response } = getRequestMeta(); - const modal = getOverlays().openModal( - toMountPoint( - modal.close()} - />, - { theme$: theme.theme$ } - ), - { - className: 'shardFailureModal', - } - ); - }, [getRequestMeta, theme.theme$, title]); - - const Component = isButtonEmpty ? EuiLink : EuiButton; - - return ( - - - - ); -} diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_table.test.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_table.test.tsx index c4a53f850ab6a..567ad8ff80ac2 100644 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_table.test.tsx +++ b/src/plugins/data/public/shard_failure_modal/shard_failure_table.test.tsx @@ -10,12 +10,12 @@ import React from 'react'; import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ShardFailureTable } from './shard_failure_table'; import { shardFailureResponse } from './__mocks__/shard_failure_response'; -import { ShardFailure } from './shard_failure_types'; describe('ShardFailureTable', () => { it('renders matching snapshot given valid properties', () => { - const failures = (shardFailureResponse._shards as any).failures as ShardFailure[]; - const component = shallowWithIntl(); + const component = shallowWithIntl( + + ); expect(component).toMatchSnapshot(); }); }); diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_table.tsx b/src/plugins/data/public/shard_failure_modal/shard_failure_table.tsx index ab9c376157100..cb4fed32f11eb 100644 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_table.tsx +++ b/src/plugins/data/public/shard_failure_modal/shard_failure_table.tsx @@ -7,13 +7,13 @@ */ import React from 'react'; +import { estypes } from '@elastic/elasticsearch'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { EuiInMemoryTable, EuiInMemoryTableProps, euiScreenReaderOnly } from '@elastic/eui'; import { ShardFailureDescription } from './shard_failure_description'; -import { ShardFailure } from './shard_failure_types'; -export interface ListItem extends ShardFailure { +export interface ListItem extends estypes.ShardFailure { id: string; } @@ -24,7 +24,7 @@ const SORTING: EuiInMemoryTableProps['sorting'] = { }, }; -export function ShardFailureTable({ failures }: { failures: ShardFailure[] }) { +export function ShardFailureTable({ failures }: { failures: estypes.ShardFailure[] }) { const itemList = failures.map((failure, idx) => ({ ...{ id: String(idx) }, ...failure })); const columns = [ diff --git a/src/plugins/data/public/shard_failure_modal/shard_failure_types.ts b/src/plugins/data/public/shard_failure_modal/shard_failure_types.ts deleted file mode 100644 index c6533f9f0a850..0000000000000 --- a/src/plugins/data/public/shard_failure_modal/shard_failure_types.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -export interface ShardFailureRequest { - docvalue_fields: string[]; - _source: unknown; - query: unknown; - script_fields: unknown; - sort: unknown; - stored_fields: string[]; -} - -export interface ShardFailure { - index: string; - node: string; - reason: { - caused_by: { - reason: string; - type: string; - }; - reason: string; - lang?: estypes.ScriptLanguage; - script?: string; - script_stack?: string[]; - type: string; - }; - shard: number; -} diff --git a/src/plugins/data/server/search/expressions/esaggs.test.ts b/src/plugins/data/server/search/expressions/esaggs.test.ts index 9954fb2457968..da903f6ff7101 100644 --- a/src/plugins/data/server/search/expressions/esaggs.test.ts +++ b/src/plugins/data/server/search/expressions/esaggs.test.ts @@ -131,7 +131,7 @@ describe('esaggs expression function - server', () => { query: undefined, searchSessionId: 'abc123', searchSourceService: startDependencies.searchSource, - disableShardWarnings: false, + disableWarningToasts: false, timeFields: args.timeFields, timeRange: undefined, }); diff --git a/src/plugins/data/server/search/expressions/esaggs.ts b/src/plugins/data/server/search/expressions/esaggs.ts index eedd1db8d0308..2e1afc0350957 100644 --- a/src/plugins/data/server/search/expressions/esaggs.ts +++ b/src/plugins/data/server/search/expressions/esaggs.ts @@ -72,7 +72,7 @@ export function getFunctionDefinition({ query: get(input, 'query', undefined) as any, searchSessionId: getSearchSessionId(), searchSourceService: searchSource, - disableShardWarnings: false, + disableWarningToasts: false, timeFields: args.timeFields, timeRange: get(input, 'timeRange', undefined), }) diff --git a/src/plugins/data/server/search/expressions/esql.ts b/src/plugins/data/server/search/expressions/esql.ts new file mode 100644 index 0000000000000..93a4e136d88df --- /dev/null +++ b/src/plugins/data/server/search/expressions/esql.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StartServicesAccessor } from '@kbn/core/server'; +import { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { getEsqlFn } from '../../../common/search/expressions/esql'; + +/** + * This is some glue code that takes in `core.getStartServices`, extracts the dependencies + * needed for this function, and wraps them behind a `getStartDependencies` function that + * is then called at runtime. + * + * We do this so that we can be explicit about exactly which dependencies the function + * requires, without cluttering up the top-level `plugin.ts` with this logic. It also + * makes testing the expression function a bit easier since `getStartDependencies` is + * the only thing you should need to mock. + * + * @param getStartServices - core's StartServicesAccessor for this plugin + * @internal + */ +export function getEsql({ + getStartServices, +}: { + getStartServices: StartServicesAccessor; +}) { + return getEsqlFn({ + getStartDependencies: async (getKibanaRequest) => { + const [{ savedObjects, uiSettings }, , { search }] = await getStartServices(); + const request = getKibanaRequest(); + const savedObjectsClient = savedObjects.getScopedClient(request); + + return { + search: search.asScoped(request).search, + uiSettings: uiSettings.asScopedToClient(savedObjectsClient), + }; + }, + }); +} diff --git a/src/plugins/data/server/search/expressions/index.ts b/src/plugins/data/server/search/expressions/index.ts index 98a08e6383f34..e9a9ff316d137 100644 --- a/src/plugins/data/server/search/expressions/index.ts +++ b/src/plugins/data/server/search/expressions/index.ts @@ -9,4 +9,5 @@ export * from './esaggs'; export * from './esdsl'; export * from './essql'; +export * from './esql'; export * from './eql'; diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index 825b7eee3302f..f2cdc40594487 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -10,6 +10,7 @@ export * from './types'; export * from './strategies/es_search'; export * from './strategies/ese_search'; export * from './strategies/eql_search'; +export * from './strategies/esql_search'; export type { SearchUsage } from './collectors/search'; export { usageProvider, searchUsageObserver } from './collectors/search'; export * from './aggs'; diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts index 581920feef89d..7248206c8ee95 100644 --- a/src/plugins/data/server/search/routes/bsearch.ts +++ b/src/plugins/data/server/search/routes/bsearch.ts @@ -8,6 +8,7 @@ import { firstValueFrom } from 'rxjs'; import { catchError } from 'rxjs/operators'; +import { errors } from '@elastic/elasticsearch'; import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; import type { ExecutionContextSetup } from '@kbn/core/server'; import apm from 'elastic-apm-node'; @@ -47,6 +48,12 @@ export function registerBsearchRoute( message: err.message, statusCode: err.statusCode, attributes: err.errBody?.error, + // TODO remove 'instanceof errors.ResponseError' check when + // eql strategy throws KbnServerError (like all of the other strategies) + requestParams: + err instanceof errors.ResponseError + ? err.meta?.meta?.request?.params + : err.requestParams, }; }) ) diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index dd199403c53af..71a335ce51592 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -79,8 +79,9 @@ import { SearchSourceService, eqlRawResponse, SQL_SEARCH_STRATEGY, + ESQL_SEARCH_STRATEGY, } from '../../common/search'; -import { getEsaggs, getEsdsl, getEssql, getEql } from './expressions'; +import { getEsaggs, getEsdsl, getEssql, getEql, getEsql } from './expressions'; import { getShardDelayBucketAgg, SHARD_DELAY_AGG_NAME, @@ -95,6 +96,7 @@ import { NoSearchIdInSessionError } from './errors/no_search_id_in_session'; import { CachedUiSettingsClient } from './services'; import { sqlSearchStrategyProvider } from './strategies/sql_search'; import { searchSessionSavedObjectType } from './saved_objects'; +import { esqlSearchStrategyProvider } from './strategies/esql_search'; type StrategyMap = Record>; @@ -176,6 +178,7 @@ export class SearchService implements Plugin { usage ) ); + this.registerSearchStrategy(ESQL_SEARCH_STRATEGY, esqlSearchStrategyProvider(this.logger)); // We don't want to register this because we don't want the client to be able to access this // strategy, but we do want to expose it to other server-side plugins @@ -216,6 +219,7 @@ export class SearchService implements Plugin { expressions.registerFunction(getEsdsl({ getStartServices: core.getStartServices })); expressions.registerFunction(getEssql({ getStartServices: core.getStartServices })); expressions.registerFunction(getEql({ getStartServices: core.getStartServices })); + expressions.registerFunction(getEsql({ getStartServices: core.getStartServices })); expressions.registerFunction(cidrFunction); expressions.registerFunction(dateRangeFunction); expressions.registerFunction(extendedBoundsFunction); diff --git a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts index d6f5d948c784a..45a7b4d90cd41 100644 --- a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts @@ -77,7 +77,10 @@ export const eqlSearchStrategyProvider = ( meta: true, }); - return toEqlKibanaSearchResponse(response as TransportResult); + return toEqlKibanaSearchResponse( + response as TransportResult, + (response as TransportResult).meta?.request?.params + ); }; const cancel = async () => { diff --git a/src/plugins/data/server/search/strategies/eql_search/response_utils.ts b/src/plugins/data/server/search/strategies/eql_search/response_utils.ts index f9bdf5bc7de30..48c19c996fd52 100644 --- a/src/plugins/data/server/search/strategies/eql_search/response_utils.ts +++ b/src/plugins/data/server/search/strategies/eql_search/response_utils.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { ConnectionRequestParams } from '@elastic/transport'; import type { TransportResult } from '@elastic/elasticsearch'; import { EqlSearchResponse } from './types'; import { EqlSearchStrategyResponse } from '../../../../common'; @@ -15,12 +16,14 @@ import { EqlSearchStrategyResponse } from '../../../../common'; * (EQL does not provide _shard info, so total/loaded cannot be calculated.) */ export function toEqlKibanaSearchResponse( - response: TransportResult + response: TransportResult, + requestParams?: ConnectionRequestParams ): EqlSearchStrategyResponse { return { id: response.body.id, rawResponse: response, isPartial: response.body.is_partial, isRunning: response.body.is_running, + ...(requestParams ? { requestParams } : {}), }; } diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts index 15a6a4df7eed8..679bb5ae2a699 100644 --- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts @@ -113,7 +113,7 @@ describe('ES search strategy', () => { ) ); const [, searchOptions] = esClient.search.mock.calls[0]; - expect(searchOptions).toEqual({ signal: undefined, maxRetries: 5 }); + expect(searchOptions).toEqual({ signal: undefined, maxRetries: 5, meta: true }); }); it('can be aborted', async () => { @@ -131,7 +131,10 @@ describe('ES search strategy', () => { ...params, track_total_hits: true, }); - expect(esClient.search.mock.calls[0][1]).toEqual({ signal: expect.any(AbortSignal) }); + expect(esClient.search.mock.calls[0][1]).toEqual({ + signal: expect.any(AbortSignal), + meta: true, + }); }); it('throws normalized error if ResponseError is thrown', async () => { diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts index b2aed5804f248..1dc9beb565c79 100644 --- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts @@ -50,12 +50,13 @@ export const esSearchStrategyProvider = ( ...(terminateAfter ? { terminate_after: terminateAfter } : {}), ...requestParams, }; - const body = await esClient.asCurrentUser.search(params, { + const { body, meta } = await esClient.asCurrentUser.search(params, { signal: abortSignal, ...transport, + meta: true, }); const response = shimHitsTotal(body, options); - return toKibanaSearchResponse(response); + return toKibanaSearchResponse(response, meta?.request?.params); } catch (e) { throw getKbnServerError(e); } diff --git a/src/plugins/data/server/search/strategies/es_search/response_utils.ts b/src/plugins/data/server/search/strategies/es_search/response_utils.ts index 4773b6df3bbaf..6e364cbbc40bd 100644 --- a/src/plugins/data/server/search/strategies/es_search/response_utils.ts +++ b/src/plugins/data/server/search/strategies/es_search/response_utils.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { ConnectionRequestParams } from '@elastic/transport'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ISearchOptions } from '../../../../common'; @@ -24,11 +25,15 @@ export function getTotalLoaded(response: estypes.SearchResponse) { * Get the Kibana representation of this response (see `IKibanaSearchResponse`). * @internal */ -export function toKibanaSearchResponse(rawResponse: estypes.SearchResponse) { +export function toKibanaSearchResponse( + rawResponse: estypes.SearchResponse, + requestParams?: ConnectionRequestParams +) { return { rawResponse, isPartial: false, isRunning: false, + ...(requestParams ? { requestParams } : {}), ...getTotalLoaded(rawResponse), }; } diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts index 298933907b8bb..c8322d3083995 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts @@ -65,7 +65,7 @@ export const enhancedEsSearchStrategyProvider = ( ...(await getDefaultAsyncSubmitParams(uiSettingsClient, searchConfig, options)), ...request.params, }; - const { body, headers } = id + const { body, headers, meta } = id ? await client.asyncSearch.get( { ...params, id }, { ...options.transport, signal: options.abortSignal, meta: true } @@ -78,7 +78,11 @@ export const enhancedEsSearchStrategyProvider = ( const response = shimHitsTotal(body.response, options); - return toAsyncKibanaSearchResponse({ ...body, response }, headers?.warning); + return toAsyncKibanaSearchResponse( + { ...body, response }, + headers?.warning, + meta?.request?.params + ); }; const cancel = async () => { @@ -131,8 +135,10 @@ export const enhancedEsSearchStrategyProvider = ( ); const response = esResponse.body as estypes.SearchResponse; + const requestParams = esResponse.meta?.request?.params; return { rawResponse: shimHitsTotal(response, options), + ...(requestParams ? { requestParams } : {}), ...getTotalLoaded(response), }; } catch (e) { diff --git a/src/plugins/data/server/search/strategies/ese_search/response_utils.ts b/src/plugins/data/server/search/strategies/ese_search/response_utils.ts index c9390a1b381d5..5439e8a618dae 100644 --- a/src/plugins/data/server/search/strategies/ese_search/response_utils.ts +++ b/src/plugins/data/server/search/strategies/ese_search/response_utils.ts @@ -6,19 +6,25 @@ * Side Public License, v 1. */ +import type { ConnectionRequestParams } from '@elastic/transport'; import type { AsyncSearchResponse } from './types'; import { getTotalLoaded } from '../es_search'; /** * Get the Kibana representation of an async search response (see `IKibanaSearchResponse`). */ -export function toAsyncKibanaSearchResponse(response: AsyncSearchResponse, warning?: string) { +export function toAsyncKibanaSearchResponse( + response: AsyncSearchResponse, + warning?: string, + requestParams?: ConnectionRequestParams +) { return { id: response.id, rawResponse: response.response, isPartial: response.is_partial, isRunning: response.is_running, ...(warning ? { warning } : {}), + ...(requestParams ? { requestParams } : {}), ...getTotalLoaded(response.response), }; } diff --git a/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts new file mode 100644 index 0000000000000..e61feaba15668 --- /dev/null +++ b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { from } from 'rxjs'; +import type { Logger } from '@kbn/core/server'; +import { getKbnServerError, KbnServerError } from '@kbn/kibana-utils-plugin/server'; +import type { ISearchStrategy } from '../../types'; + +export const esqlSearchStrategyProvider = ( + logger: Logger, + useInternalUser: boolean = false +): ISearchStrategy => ({ + /** + * @param request + * @param options + * @param deps + * @throws `KbnServerError` + * @returns `Observable>` + */ + search: (request, { abortSignal, ...options }, { esClient, uiSettingsClient }) => { + // Only default index pattern type is supported here. + // See ese for other type support. + if (request.indexType) { + throw new KbnServerError(`Unsupported index pattern type ${request.indexType}`, 400); + } + + const search = async () => { + try { + const { terminateAfter, ...requestParams } = request.params ?? {}; + const { headers, body, meta } = await esClient.asCurrentUser.transport.request( + { + method: 'POST', + path: '/_query', + body: { + ...requestParams, + }, + }, + { + signal: abortSignal, + meta: true, + } + ); + const transportRequestParams = meta?.request?.params; + return { + rawResponse: body, + isPartial: false, + isRunning: false, + ...(transportRequestParams ? { requestParams: transportRequestParams } : {}), + warning: headers?.warning, + }; + } catch (e) { + throw getKbnServerError(e); + } + }; + + return from(search()); + }, +}); diff --git a/src/plugins/data/server/search/strategies/esql_search/index.ts b/src/plugins/data/server/search/strategies/esql_search/index.ts new file mode 100644 index 0000000000000..14fd011acc3d8 --- /dev/null +++ b/src/plugins/data/server/search/strategies/esql_search/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { esqlSearchStrategyProvider } from './esql_search_strategy'; diff --git a/src/plugins/data/server/search/strategies/sql_search/response_utils.ts b/src/plugins/data/server/search/strategies/sql_search/response_utils.ts index b859df9db4237..0f4fb3e275f0e 100644 --- a/src/plugins/data/server/search/strategies/sql_search/response_utils.ts +++ b/src/plugins/data/server/search/strategies/sql_search/response_utils.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { ConnectionRequestParams } from '@elastic/transport'; import { SqlQueryResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { SqlSearchStrategyResponse } from '../../../../common'; @@ -15,7 +16,8 @@ import { SqlSearchStrategyResponse } from '../../../../common'; export function toAsyncKibanaSearchResponse( response: SqlQueryResponse, startTime: number, - warning?: string + warning?: string, + requestParams?: ConnectionRequestParams ): SqlSearchStrategyResponse { return { id: response.id, @@ -24,5 +26,6 @@ export function toAsyncKibanaSearchResponse( isRunning: response.is_running, took: Date.now() - startTime, ...(warning ? { warning } : {}), + ...(requestParams ? { requestParams } : {}), }; } diff --git a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts index c8928a343eec5..b6207787d8fbb 100644 --- a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts @@ -9,6 +9,7 @@ import type { IncomingHttpHeaders } from 'http'; import type { IScopedClusterClient, Logger } from '@kbn/core/server'; import { catchError, tap } from 'rxjs/operators'; +import type { DiagnosticResult } from '@elastic/transport'; import { SqlQueryResponse } from '@elastic/elasticsearch/lib/api/types'; import { getKbnServerError } from '@kbn/kibana-utils-plugin/server'; import type { ISearchStrategy, SearchStrategyDependencies } from '../../types'; @@ -48,9 +49,10 @@ export const sqlSearchStrategyProvider = ( const { keep_cursor: keepCursor, ...params } = request.params ?? {}; let body: SqlQueryResponse; let headers: IncomingHttpHeaders; + let meta: DiagnosticResult['meta']; if (id) { - ({ body, headers } = await client.sql.getAsync( + ({ body, headers, meta } = await client.sql.getAsync( { format: params?.format ?? 'json', ...getDefaultAsyncGetParams(searchConfig, options), @@ -59,7 +61,7 @@ export const sqlSearchStrategyProvider = ( { ...options.transport, signal: options.abortSignal, meta: true } )); } else { - ({ headers, body } = await client.sql.query( + ({ headers, body, meta } = await client.sql.query( { format: params.format ?? 'json', ...getDefaultAsyncSubmitParams(searchConfig, options), @@ -79,7 +81,7 @@ export const sqlSearchStrategyProvider = ( } } - return toAsyncKibanaSearchResponse(body, startTime, headers?.warning); + return toAsyncKibanaSearchResponse(body, startTime, headers?.warning, meta?.request?.params); }; const cancel = async () => { diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index 9d46f36ffd29c..81820900557d7 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -49,7 +49,8 @@ "@kbn/core-saved-objects-server", "@kbn/core-saved-objects-utils-server", "@kbn/data-service", - "@kbn/react-kibana-context-render" + "@kbn/react-kibana-context-render", + "@kbn/es-types" ], "exclude": [ "target/**/*", diff --git a/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx b/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx index ee133328bb8ed..a75367fbb4d8a 100644 --- a/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx +++ b/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx @@ -67,11 +67,14 @@ const DataViewFlyoutContentContainer = ({ await dataViews.refreshFields(saveResponse); if (persist) { - const message = i18n.translate('indexPatternEditor.saved', { - defaultMessage: "Saved '{indexPatternName}'", - values: { indexPatternName: saveResponse.getName() }, + const title = i18n.translate('indexPatternEditor.saved', { + defaultMessage: 'Saved', + }); + const text = `'${saveResponse.getName()}'`; + notifications.toasts.addSuccess({ + title, + text, }); - notifications.toasts.addSuccess(message); } await onSave(saveResponse); } diff --git a/src/plugins/data_view_management/public/components/__snapshots__/utils.test.ts.snap b/src/plugins/data_view_management/public/components/__snapshots__/utils.test.ts.snap index 59bdcb5d4360e..5329da0bd89fb 100644 --- a/src/plugins/data_view_management/public/components/__snapshots__/utils.test.ts.snap +++ b/src/plugins/data_view_management/public/components/__snapshots__/utils.test.ts.snap @@ -11,6 +11,7 @@ Array [ "sort": "0test name", "tags": Array [ Object { + "data-test-subj": "default-tag", "key": "default", "name": "Default", }, diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx index adbdd1cd2681f..2ab485fdb1de1 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -77,7 +77,9 @@ export const EditIndexPattern = withRouter( indexPattern.fields.getAll().filter((field) => field.type === 'conflict') ); const [defaultIndex, setDefaultIndex] = useState(uiSettings.get('defaultIndex')); - const [tags, setTags] = useState>([]); + const [tags, setTags] = useState< + Array<{ key: string; 'data-test-subj': string; name: string }> + >([]); const [showEditDialog, setShowEditDialog] = useState(false); const [relationships, setRelationships] = useState([]); const [allowedTypes, setAllowedTypes] = useState([]); @@ -108,8 +110,10 @@ export const EditIndexPattern = withRouter( }, [indexPattern]); useEffect(() => { - setTags(getTags(indexPattern, indexPattern.id === defaultIndex)); - }, [defaultIndex, indexPattern]); + setTags( + getTags(indexPattern, indexPattern.id === defaultIndex, dataViews.getRollupsEnabled()) + ); + }, [defaultIndex, indexPattern, dataViews]); const setDefaultPattern = useCallback(() => { uiSettings.set('defaultIndex', indexPattern.id); @@ -125,7 +129,9 @@ export const EditIndexPattern = withRouter( }, }); - const isRollup = new URLSearchParams(useLocation().search).get('type') === 'rollup'; + const isRollup = + new URLSearchParams(useLocation().search).get('type') === 'rollup' && + dataViews.getRollupsEnabled(); const displayIndexPatternEditor = showEditDialog ? ( { @@ -237,7 +243,11 @@ export const EditIndexPattern = withRouter( {tags.map((tag) => ( {tag.key === 'default' ? ( - + {tag.name} ) : ( diff --git a/src/plugins/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx index 0d0215e6342d7..12d9e1773e138 100644 --- a/src/plugins/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx +++ b/src/plugins/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx @@ -164,7 +164,9 @@ export const IndexPatternTable = ({ chrome.docTitle.change(title); - const isRollup = new URLSearchParams(useLocation().search).get('type') === 'rollup'; + const isRollup = + new URLSearchParams(useLocation().search).get('type') === 'rollup' && + dataViews.getRollupsEnabled(); const ContextWrapper = useMemo( () => (spaces ? spaces.ui.components.getSpacesContextProvider : getEmptyFunctionComponent), diff --git a/src/plugins/data_view_management/public/components/utils.test.ts b/src/plugins/data_view_management/public/components/utils.test.ts index b5ea7b7dc240a..fedeb8b67ca15 100644 --- a/src/plugins/data_view_management/public/components/utils.test.ts +++ b/src/plugins/data_view_management/public/components/utils.test.ts @@ -24,6 +24,7 @@ const indexPatternContractMock = { ]) ), get: jest.fn().mockReturnValue(Promise.resolve({})), + getRollupsEnabled: jest.fn().mockReturnValue(true), } as unknown as jest.Mocked; test('getting index patterns', async () => { diff --git a/src/plugins/data_view_management/public/components/utils.ts b/src/plugins/data_view_management/public/components/utils.ts index 722ad5059a598..1818753162f6d 100644 --- a/src/plugins/data_view_management/public/components/utils.ts +++ b/src/plugins/data_view_management/public/components/utils.ts @@ -37,7 +37,7 @@ export async function getIndexPatterns(defaultIndex: string, dataViewsService: D const indexPatternsListItems = existingIndexPatterns.map((idxPattern) => { const { id, title, namespaces, name } = idxPattern; const isDefault = defaultIndex === id; - const tags = getTags(idxPattern, isDefault); + const tags = getTags(idxPattern, isDefault, dataViewsService.getRollupsEnabled()); const displayName = name ? name : title; return { @@ -68,18 +68,24 @@ export async function getIndexPatterns(defaultIndex: string, dataViewsService: D ); } -export const getTags = (indexPattern: DataViewListItem | DataView, isDefault: boolean) => { +export const getTags = ( + indexPattern: DataViewListItem | DataView, + isDefault: boolean, + rollupsEnabled: boolean +) => { const tags = []; if (isDefault) { tags.push({ key: 'default', name: defaultIndexPatternListName, + 'data-test-subj': 'default-tag', }); } - if (isRollup(indexPattern.type)) { + if (isRollup(indexPattern.type) && rollupsEnabled) { tags.push({ key: 'rollup', name: rollupIndexPatternListName, + 'data-test-subj': 'rollup-tag', }); } return tags; diff --git a/src/plugins/data_views/public/data_views_service_public.ts b/src/plugins/data_views/public/data_views_service_public.ts index 73f4fb6987fa0..565fc8dc16708 100644 --- a/src/plugins/data_views/public/data_views_service_public.ts +++ b/src/plugins/data_views/public/data_views_service_public.ts @@ -29,6 +29,8 @@ export interface DataViewsServicePublicDeps extends DataViewsServiceDeps { showAllIndices?: boolean; isRollupIndex: (indexName: string) => boolean; }) => Promise; + + getRollupsEnabled: () => boolean; scriptedFieldsEnabled: boolean; } @@ -45,6 +47,7 @@ export class DataViewsServicePublic extends DataViewsService { isRollupIndex: (indexName: string) => boolean; }) => Promise; public hasData: HasDataService; + private rollupsEnabled: boolean = false; public readonly scriptedFieldsEnabled: boolean; /** @@ -57,6 +60,11 @@ export class DataViewsServicePublic extends DataViewsService { this.getCanSaveSync = deps.getCanSaveSync; this.hasData = deps.hasData; this.getIndices = deps.getIndices; + this.rollupsEnabled = deps.getRollupsEnabled(); this.scriptedFieldsEnabled = deps.scriptedFieldsEnabled; } + + getRollupsEnabled() { + return this.rollupsEnabled; + } } diff --git a/src/plugins/data_views/public/mocks.ts b/src/plugins/data_views/public/mocks.ts index 2178cf4503003..e9d5b487b8b00 100644 --- a/src/plugins/data_views/public/mocks.ts +++ b/src/plugins/data_views/public/mocks.ts @@ -11,7 +11,9 @@ import { DataViewsPlugin, DataViewsContract } from '.'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; -const createSetupContract = (): Setup => ({}); +const createSetupContract = (): Setup => ({ + enableRollups: jest.fn(), +}); const createStartContract = (): Start => { return { diff --git a/src/plugins/data_views/public/plugin.ts b/src/plugins/data_views/public/plugin.ts index 98558c803da10..b6997b107410f 100644 --- a/src/plugins/data_views/public/plugin.ts +++ b/src/plugins/data_views/public/plugin.ts @@ -40,6 +40,7 @@ export class DataViewsPublicPlugin > { private readonly hasData = new HasData(); + private rollupsEnabled: boolean = false; constructor(private readonly initializerContext: PluginInitializerContext) {} @@ -59,7 +60,9 @@ export class DataViewsPublicPlugin }), }); - return {}; + return { + enableRollups: () => (this.rollupsEnabled = true), + }; } public start( @@ -96,6 +99,7 @@ export class DataViewsPublicPlugin getCanSaveAdvancedSettings: () => Promise.resolve(application.capabilities.advancedSettings.save === true), getIndices: (props) => getIndices({ ...props, http: core.http }), + getRollupsEnabled: () => this.rollupsEnabled, scriptedFieldsEnabled: config.scriptedFieldsEnabled === false ? false : true, // accounting for null value }); } diff --git a/src/plugins/data_views/public/types.ts b/src/plugins/data_views/public/types.ts index fde0828748f93..08c8eeb62fc2e 100644 --- a/src/plugins/data_views/public/types.ts +++ b/src/plugins/data_views/public/types.ts @@ -109,8 +109,9 @@ export interface DataViewsPublicStartDependencies { /** * Data plugin public Setup contract */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface DataViewsPublicPluginSetup {} +export interface DataViewsPublicPluginSetup { + enableRollups: () => void; +} export interface DataViewsServicePublic extends DataViewsServicePublicMethods { getCanSaveSync: () => boolean; @@ -120,6 +121,7 @@ export interface DataViewsServicePublic extends DataViewsServicePublicMethods { showAllIndices?: boolean; isRollupIndex: (indexName: string) => boolean; }) => Promise; + getRollupsEnabled: () => boolean; scriptedFieldsEnabled: boolean; } diff --git a/src/plugins/discover/common/constants.ts b/src/plugins/discover/common/constants.ts index 3cb696766b65f..8ac1280592ff6 100644 --- a/src/plugins/discover/common/constants.ts +++ b/src/plugins/discover/common/constants.ts @@ -6,12 +6,17 @@ * Side Public License, v 1. */ -export const MAX_LOADED_GRID_ROWS = 10000; +import { SAMPLE_ROWS_PER_PAGE_SETTING } from '@kbn/discover-utils'; +import { IUiSettingsClient } from '@kbn/core/public'; + export const DEFAULT_ROWS_PER_PAGE = 100; export const ROWS_PER_PAGE_OPTIONS = [10, 25, 50, DEFAULT_ROWS_PER_PAGE, 250, 500]; + export enum VIEW_MODE { DOCUMENT_LEVEL = 'documents', AGGREGATED_LEVEL = 'aggregated', } -export const DISABLE_SHARD_FAILURE_WARNING = true; +export const getDefaultRowsPerPage = (uiSettings: IUiSettingsClient): number => { + return parseInt(uiSettings.get(SAMPLE_ROWS_PER_PAGE_SETTING), 10) || DEFAULT_ROWS_PER_PAGE; +}; diff --git a/src/plugins/discover/kibana.jsonc b/src/plugins/discover/kibana.jsonc index 0778ebf666851..4a46dd14d689f 100644 --- a/src/plugins/discover/kibana.jsonc +++ b/src/plugins/discover/kibana.jsonc @@ -38,7 +38,8 @@ "savedObjectsTaggingOss", "lens", "serverless", - "noDataPage" + "noDataPage", + "globalSearch" ], "requiredBundles": ["kibanaUtils", "kibanaReact", "unifiedSearch"], "extraPublicDirs": ["common"] diff --git a/src/plugins/discover/public/__mocks__/__storybook_mocks__/with_discover_services.tsx b/src/plugins/discover/public/__mocks__/__storybook_mocks__/with_discover_services.tsx index 62b04533c2a41..cf280767e0d4b 100644 --- a/src/plugins/discover/public/__mocks__/__storybook_mocks__/with_discover_services.tsx +++ b/src/plugins/discover/public/__mocks__/__storybook_mocks__/with_discover_services.tsx @@ -21,7 +21,6 @@ import { SEARCH_FIELDS_FROM_SOURCE, SHOW_MULTIFIELDS, } from '@kbn/discover-utils'; -import { SIDEBAR_CLOSED_KEY } from '../../application/main/components/layout/discover_layout'; import { LocalStorageMock } from '../local_storage_mock'; import { DiscoverServices } from '../../build_services'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -73,9 +72,7 @@ export const services = { docLinks: { links: { discover: {} } }, theme, }, - storage: new LocalStorageMock({ - [SIDEBAR_CLOSED_KEY]: false, - }) as unknown as Storage, + storage: new LocalStorageMock({}) as unknown as Storage, data: { query: { timefilter: { diff --git a/src/plugins/discover/public/__mocks__/data_view_no_timefield.ts b/src/plugins/discover/public/__mocks__/data_view_no_timefield.ts new file mode 100644 index 0000000000000..ebfc810f1de28 --- /dev/null +++ b/src/plugins/discover/public/__mocks__/data_view_no_timefield.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataView } from '@kbn/data-views-plugin/public'; +import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; + +const fields = [ + { + name: '_index', + type: 'string', + scripted: false, + filterable: true, + }, + { + name: 'message', + displayName: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + { + name: 'extension', + displayName: 'extension', + type: 'string', + scripted: false, + filterable: true, + aggregatable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + scripted: false, + filterable: true, + aggregatable: true, + }, + { + name: 'scripted', + displayName: 'scripted', + type: 'number', + scripted: true, + filterable: false, + }, +] as DataView['fields']; + +export const dataViewWithNoTimefieldMock = buildDataViewMock({ + name: 'index-pattern-with-timefield', + fields, +}); diff --git a/src/plugins/discover/public/__mocks__/grid_context.ts b/src/plugins/discover/public/__mocks__/grid_context.ts deleted file mode 100644 index 8949945c2b0d8..0000000000000 --- a/src/plugins/discover/public/__mocks__/grid_context.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { DataView } from '@kbn/data-views-plugin/public'; -import { dataViewMock, esHitsMock } from '@kbn/discover-utils/src/__mocks__'; -import { dataViewComplexMock } from './data_view_complex'; -import { esHitsComplex } from './es_hits_complex'; -import { discoverServiceMock } from './services'; -import { GridContext } from '../components/discover_grid/discover_grid_context'; -import { convertValueToString } from '../utils/convert_value_to_string'; -import { buildDataTableRecord } from '@kbn/discover-utils'; -import type { EsHitRecord } from '@kbn/discover-utils/types'; - -const buildGridContext = (dataView: DataView, rows: EsHitRecord[]): GridContext => { - const usedRows = rows.map((row) => { - return buildDataTableRecord(row, dataView); - }); - - return { - expanded: undefined, - setExpanded: jest.fn(), - rows: usedRows, - onFilter: jest.fn(), - dataView, - isDarkMode: false, - selectedDocs: [], - setSelectedDocs: jest.fn(), - valueToStringConverter: (rowIndex, columnId, options) => - convertValueToString({ - rowIndex, - columnId, - fieldFormats: discoverServiceMock.fieldFormats, - rows: usedRows, - dataView, - options, - }), - }; -}; - -export const discoverGridContextMock = buildGridContext(dataViewMock, esHitsMock); - -export const discoverGridContextComplexMock = buildGridContext(dataViewComplexMock, esHitsComplex); diff --git a/src/plugins/discover/public/__mocks__/saved_search.ts b/src/plugins/discover/public/__mocks__/saved_search.ts index c6751b2990bc4..90a2a9c680825 100644 --- a/src/plugins/discover/public/__mocks__/saved_search.ts +++ b/src/plugins/discover/public/__mocks__/saved_search.ts @@ -27,11 +27,11 @@ export const savedSearchMockWithTimeFieldNew = { searchSource: createSearchSourceMock({ index: dataViewWithTimefieldMock }), } as unknown as SavedSearch; -export const savedSearchMockWithSQL = { - id: 'the-saved-search-id-sql', +export const savedSearchMockWithESQL = { + id: 'the-saved-search-id-esql', searchSource: createSearchSourceMock({ index: dataViewWithTimefieldMock, - query: { sql: 'SELECT * FROM "the-saved-search-id-sql"' }, + query: { esql: 'FROM "the-saved-search-id-esql"' }, }), } as unknown as SavedSearch; diff --git a/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx b/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx index 5cbb72f0602ee..747cd68837545 100644 --- a/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx +++ b/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx @@ -156,7 +156,7 @@ export function ActionBar({ {!isSuccessor && showWarning && } {!isSuccessor && showWarning && } - {!isSuccessor && } + ); } diff --git a/src/plugins/discover/public/application/context/context_app.scss b/src/plugins/discover/public/application/context/context_app.scss index 13593a7ed32dd..19ae9a7471302 100644 --- a/src/plugins/discover/public/application/context/context_app.scss +++ b/src/plugins/discover/public/application/context/context_app.scss @@ -17,8 +17,4 @@ &__cell--highlight { background-color: tintOrShade($euiColorPrimary, 90%, 70%); } - - .euiDataGridRowCell.euiDataGridRowCell--firstColumn { - padding: 0; - } } diff --git a/src/plugins/discover/public/application/context/context_app.tsx b/src/plugins/discover/public/application/context/context_app.tsx index c11a9187a937f..de1e0a23c4df2 100644 --- a/src/plugins/discover/public/application/context/context_app.tsx +++ b/src/plugins/discover/public/application/context/context_app.tsx @@ -10,23 +10,26 @@ import React, { Fragment, memo, useEffect, useRef, useMemo, useCallback } from ' import './context_app.scss'; import classNames from 'classnames'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiText, EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; +import { EuiText, EuiPage, EuiPageBody, EuiSpacer, useEuiPaddingSize } from '@elastic/eui'; +import { css } from '@emotion/react'; import { cloneDeep } from 'lodash'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import { generateFilters } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; -import { removeInterceptedWarningDuplicates } from '@kbn/search-response-warnings'; -import { DOC_TABLE_LEGACY, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { + DOC_TABLE_LEGACY, + SEARCH_FIELDS_FROM_SOURCE, + SORT_DEFAULT_ORDER_SETTING, +} from '@kbn/discover-utils'; +import { popularizeField, useColumns } from '@kbn/unified-data-table'; +import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import { ContextErrorMessage } from './components/context_error_message'; import { LoadingStatus } from './services/context_query_state'; import { AppState, GlobalState, isEqualFilters } from './services/context_state'; -import { useColumns } from '../../hooks/use_data_grid_columns'; import { useContextAppState } from './hooks/use_context_app_state'; import { useContextAppFetch } from './hooks/use_context_app_fetch'; -import { popularizeField } from '../../utils/popularize_field'; import { ContextAppContent } from './context_app_content'; import { SurrDocType } from './services/context'; import { useDiscoverServices } from '../../hooks/use_discover_services'; @@ -68,7 +71,7 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) => const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useColumns({ capabilities, - config: uiSettings, + defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING), dataView, dataViews, useNewFieldsApi, @@ -173,12 +176,11 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) => ); const interceptedWarnings = useMemo( - () => - removeInterceptedWarningDuplicates([ - ...(fetchedState.predecessorsInterceptedWarnings || []), - ...(fetchedState.anchorInterceptedWarnings || []), - ...(fetchedState.successorsInterceptedWarnings || []), - ]), + () => [ + ...(fetchedState.predecessorsInterceptedWarnings || []), + ...(fetchedState.anchorInterceptedWarnings || []), + ...(fetchedState.successorsInterceptedWarnings || []), + ], [ fetchedState.predecessorsInterceptedWarnings, fetchedState.anchorInterceptedWarnings, @@ -212,6 +214,8 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) => }; }; + const titlePadding = useEuiPaddingSize('m'); + return ( {fetchedState.anchorStatus.value === LoadingStatus.FAILED ? ( @@ -232,12 +236,16 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) => - - + { it('should render discover grid correctly', async () => { const component = await mountComponent({ isLegacy: false }); - expect(component.find(DiscoverGrid).length).toBe(1); + expect(component.find(UnifiedDataTable).length).toBe(1); }); }); diff --git a/src/plugins/discover/public/application/context/context_app_content.tsx b/src/plugins/discover/public/application/context/context_app_content.tsx index 7e07a594e53c6..6c566758c9d07 100644 --- a/src/plugins/discover/public/application/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.tsx @@ -8,7 +8,8 @@ import React, { Fragment, useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiSpacer, EuiText, useEuiPaddingSize } from '@elastic/eui'; +import { css } from '@emotion/react'; import type { DataView } from '@kbn/data-views-plugin/public'; import { SortDirection } from '@kbn/data-plugin/public'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; @@ -18,17 +19,25 @@ import { type SearchResponseInterceptedWarning, SearchResponseWarnings, } from '@kbn/search-response-warnings'; -import { CONTEXT_STEP_SETTING, DOC_HIDE_TIME_COLUMN_SETTING } from '@kbn/discover-utils'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { + CONTEXT_STEP_SETTING, + DOC_HIDE_TIME_COLUMN_SETTING, + MAX_DOC_FIELDS_DISPLAYED, + ROW_HEIGHT_OPTION, + SHOW_MULTIFIELDS, +} from '@kbn/discover-utils'; +import { DataLoadingState, UnifiedDataTable } from '@kbn/unified-data-table'; +import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { getDefaultRowsPerPage } from '../../../common/constants'; import { LoadingStatus } from './services/context_query_state'; import { ActionBar } from './components/action_bar/action_bar'; -import { DataLoadingState, DiscoverGrid } from '../../components/discover_grid/discover_grid'; import { AppState } from './services/context_state'; import { SurrDocType } from './services/context'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from './services/constants'; import { DocTableContext } from '../../components/doc_table/doc_table_context'; import { useDiscoverServices } from '../../hooks/use_discover_services'; -import { DiscoverGridFlyout } from '../../components/discover_grid/discover_grid_flyout'; +import { DiscoverGridFlyout } from '../../components/discover_grid_flyout'; +import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../../components/discover_tour'; export interface ContextAppContentProps { columns: string[]; @@ -57,7 +66,7 @@ export function clamp(value: number) { return Math.max(Math.min(MAX_CONTEXT_SIZE, value), MIN_CONTEXT_SIZE); } -const DiscoverGridMemoized = React.memo(DiscoverGrid); +const DiscoverGridMemoized = React.memo(UnifiedDataTable); const DocTableContextMemoized = React.memo(DocTableContext); const ActionBarMemoized = React.memo(ActionBar); @@ -121,29 +130,48 @@ export function ContextAppContent({ return [[dataView.timeFieldName!, SortDirection.desc]]; }, [dataView]); + const renderDocumentView = useCallback( + (hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + /> + ), + [addFilter, dataView, onAddColumn, onRemoveColumn] + ); + return ( - {!!interceptedWarnings?.length && ( - <> - - - - )} - - {loadingFeedback()} - + + {!!interceptedWarnings?.length && ( + <> + + + + )} + + {loadingFeedback()} + {isLegacy && rows && rows.length !== 0 && (
    )} - - + + + ); } + +const WrapperWithPadding: React.FC = ({ children }) => { + const padding = useEuiPaddingSize('s'); + + return ( +
    + {children} +
    + ); +}; diff --git a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx index 9ff64165f0f24..db0243e7f4ccf 100644 --- a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx +++ b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx @@ -19,15 +19,15 @@ import { mockSuccessorHits, } from '../__mocks__/use_context_app_fetch'; import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; -import { searchResponseWarningsMock } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; +import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; import { createContextSearchSourceStub } from '../services/_stubs'; import { DataView } from '@kbn/data-views-plugin/public'; import { themeServiceMock } from '@kbn/core/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -const mockInterceptedWarnings = searchResponseWarningsMock.map((originalWarning) => ({ - originalWarning, -})); +const mockInterceptedWarning = { + originalWarning: searchResponseIncompleteWarningLocalCluster, +}; const mockFilterManager = createFilterManagerMock(); @@ -44,9 +44,7 @@ jest.mock('../services/context', () => { } return { rows: type === 'predecessors' ? mockPredecessorHits : mockSuccessorHits, - interceptedWarnings: mockOverrideInterceptedWarnings - ? [mockInterceptedWarnings[type === 'predecessors' ? 0 : 1]] - : undefined, + interceptedWarnings: mockOverrideInterceptedWarnings ? [mockInterceptedWarning] : undefined, }; }, }; @@ -59,9 +57,7 @@ jest.mock('../services/anchor', () => ({ } return { anchorRow: mockAnchorHit, - interceptedWarnings: mockOverrideInterceptedWarnings - ? [mockInterceptedWarnings[2]] - : undefined, + interceptedWarnings: mockOverrideInterceptedWarnings ? [mockInterceptedWarning] : undefined, }; }, })); @@ -228,13 +224,11 @@ describe('test useContextAppFetch', () => { expect(result.current.fetchedState.predecessors).toEqual(mockPredecessorHits); expect(result.current.fetchedState.successors).toEqual(mockSuccessorHits); expect(result.current.fetchedState.predecessorsInterceptedWarnings).toEqual([ - mockInterceptedWarnings[0], + mockInterceptedWarning, ]); expect(result.current.fetchedState.successorsInterceptedWarnings).toEqual([ - mockInterceptedWarnings[1], - ]); - expect(result.current.fetchedState.anchorInterceptedWarnings).toEqual([ - mockInterceptedWarnings[2], + mockInterceptedWarning, ]); + expect(result.current.fetchedState.anchorInterceptedWarnings).toEqual([mockInterceptedWarning]); }); }); diff --git a/src/plugins/discover/public/application/context/services/anchor.test.ts b/src/plugins/discover/public/application/context/services/anchor.test.ts index 415b468f38afd..deb5a0ed5ca7a 100644 --- a/src/plugins/discover/public/application/context/services/anchor.test.ts +++ b/src/plugins/discover/public/application/context/services/anchor.test.ts @@ -10,7 +10,7 @@ import { SortDirection } from '@kbn/data-plugin/public'; import { createSearchSourceStub } from './_stubs'; import { fetchAnchor, updateSearchSource } from './anchor'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { searchResponseTimeoutWarningMock } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; +import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; import { savedSearchMock } from '../../../__mocks__/saved_search'; import { discoverServiceMock } from '../../../__mocks__/services'; @@ -206,7 +206,7 @@ describe('context app', function () { ).then(({ anchorRow, interceptedWarnings }) => { expect(anchorRow).toHaveProperty('raw._id', '1'); expect(anchorRow).toHaveProperty('isAnchor', true); - expect(interceptedWarnings).toBeUndefined(); + expect(interceptedWarnings).toEqual([]); }); }); @@ -216,20 +216,10 @@ describe('context app', function () { { _id: '3', _index: 't' }, ]); - const mockWarnings = [ - { - originalWarning: searchResponseTimeoutWarningMock, - }, - ]; - const services = discoverServiceMock; services.data.search.showWarnings = jest.fn((adapter, callback) => { // @ts-expect-error for empty meta - callback?.(mockWarnings[0].originalWarning, {}); - - // plus duplicates - // @ts-expect-error for empty meta - callback?.(mockWarnings[0].originalWarning, {}); + callback?.(searchResponseIncompleteWarningLocalCluster, {}); }); return fetchAnchor( @@ -242,7 +232,7 @@ describe('context app', function () { ).then(({ anchorRow, interceptedWarnings }) => { expect(anchorRow).toHaveProperty('raw._id', '1'); expect(anchorRow).toHaveProperty('isAnchor', true); - expect(interceptedWarnings).toEqual(mockWarnings); + expect(interceptedWarnings?.length).toBe(1); }); }); }); diff --git a/src/plugins/discover/public/application/context/services/anchor.ts b/src/plugins/discover/public/application/context/services/anchor.ts index daf99030201a4..f5d0f78a83441 100644 --- a/src/plugins/discover/public/application/context/services/anchor.ts +++ b/src/plugins/discover/public/application/context/services/anchor.ts @@ -17,7 +17,6 @@ import { type SearchResponseInterceptedWarning, } from '@kbn/search-response-warnings'; import type { DiscoverServices } from '../../../build_services'; -import { DISABLE_SHARD_FAILURE_WARNING } from '../../../../common/constants'; export async function fetchAnchor( anchorId: string, @@ -35,7 +34,7 @@ export async function fetchAnchor( const adapter = new RequestAdapter(); const { rawResponse } = await lastValueFrom( searchSource.fetch$({ - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, + disableWarningToasts: true, inspector: { adapter, title: 'anchor', @@ -56,9 +55,6 @@ export async function fetchAnchor( interceptedWarnings: getSearchResponseInterceptedWarnings({ services, adapter, - options: { - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, - }, }), }; } diff --git a/src/plugins/discover/public/application/context/services/context.successors.test.ts b/src/plugins/discover/public/application/context/services/context.successors.test.ts index 9c2a0120c2a72..54037a7071f06 100644 --- a/src/plugins/discover/public/application/context/services/context.successors.test.ts +++ b/src/plugins/discover/public/application/context/services/context.successors.test.ts @@ -16,6 +16,7 @@ import { Query } from '@kbn/es-query'; import { fetchSurroundingDocs, SurrDocType } from './context'; import { buildDataTableRecord, buildDataTableRecordList } from '@kbn/discover-utils'; import { discoverServiceMock } from '../../../__mocks__/services'; +import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; const MS_PER_DAY = 24 * 60 * 60 * 1000; const ANCHOR_TIMESTAMP = new Date(MS_PER_DAY).toJSON(); @@ -257,28 +258,13 @@ describe('context successors', function () { const removeFieldsSpy = mockSearchSource.removeField.withArgs('fieldsFromSource'); expect(removeFieldsSpy.calledOnce).toBe(true); expect(setFieldsSpy.calledOnce).toBe(true); - expect(interceptedWarnings).toBeUndefined(); + expect(interceptedWarnings).toEqual([]); } ); }); }); describe('function fetchSuccessors with shard failures', function () { - const mockWarnings = [ - { - originalWarning: { - message: 'Data might be incomplete because your request timed out 1', - type: 'timed_out', - }, - }, - { - originalWarning: { - message: 'Data might be incomplete because your request timed out 2', - type: 'timed_out', - }, - }, - ]; - beforeEach(() => { mockSearchSource = createContextSearchSourceStub('@timestamp'); @@ -288,11 +274,7 @@ describe('context successors', function () { createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, showWarnings: jest.fn((adapter, callback) => { - callback(mockWarnings[0].originalWarning, {}); - callback(mockWarnings[1].originalWarning, {}); - // plus duplicates - callback(mockWarnings[0].originalWarning, {}); - callback(mockWarnings[1].originalWarning, {}); + callback(searchResponseIncompleteWarningLocalCluster, {}); }), }, } as unknown as DataPublicPluginStart; @@ -345,7 +327,7 @@ describe('context successors', function () { buildDataTableRecordList(mockSearchSource._stubHits.slice(-3), dataView) ); expect(dataPluginMock.search.showWarnings).toHaveBeenCalledTimes(1); - expect(interceptedWarnings).toEqual(mockWarnings); + expect(interceptedWarnings?.length).toBe(1); } ); }); diff --git a/src/plugins/discover/public/application/context/services/context.ts b/src/plugins/discover/public/application/context/services/context.ts index df47356394821..67a477a8b9a03 100644 --- a/src/plugins/discover/public/application/context/services/context.ts +++ b/src/plugins/discover/public/application/context/services/context.ts @@ -9,10 +9,7 @@ import type { Filter } from '@kbn/es-query'; import { DataView } from '@kbn/data-views-plugin/public'; import { DataPublicPluginStart, ISearchSource } from '@kbn/data-plugin/public'; import type { DataTableRecord } from '@kbn/discover-utils/types'; -import { - removeInterceptedWarningDuplicates, - type SearchResponseInterceptedWarning, -} from '@kbn/search-response-warnings'; +import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings'; import { reverseSortDir, SortDirection } from '../utils/sorting'; import { convertIsoToMillis, extractNanos } from '../utils/date_conversion'; import { fetchHitsInInterval } from '../utils/fetch_hits_in_interval'; @@ -126,7 +123,7 @@ export async function fetchSurroundingDocs( return { rows, - interceptedWarnings: removeInterceptedWarningDuplicates(interceptedWarnings), + interceptedWarnings, }; } diff --git a/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts b/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts index c6fed56de4c19..d9a06e08fbada 100644 --- a/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts +++ b/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts @@ -17,7 +17,6 @@ import { import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { convertTimeValueToIso } from './date_conversion'; import { IntervalValue } from './generate_intervals'; -import { DISABLE_SHARD_FAILURE_WARNING } from '../../../../common/constants'; import type { SurrDocType } from '../services/context'; import type { DiscoverServices } from '../../../build_services'; @@ -91,7 +90,7 @@ export async function fetchHitsInInterval( .setField('sort', sort) .setField('version', true) .fetch$({ - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, + disableWarningToasts: true, inspector: { adapter, title: type, @@ -107,9 +106,6 @@ export async function fetchHitsInInterval( interceptedWarnings: getSearchResponseInterceptedWarnings({ services, adapter, - options: { - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, - }, }), }; } diff --git a/src/plugins/discover/public/application/doc/components/doc.tsx b/src/plugins/discover/public/application/doc/components/doc.tsx index 83c2c08eafa2e..5bf79863ecfbe 100644 --- a/src/plugins/discover/public/application/doc/components/doc.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.tsx @@ -50,7 +50,7 @@ export function Doc(props: DocProps) { values: { id: props.id }, })}
    - + {reqState === ElasticRequestState.NotFoundDataView && ( { const props = getPlainRecordLayoutProps(getDataViewMock(false)); return ( diff --git a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts index 8d95db014fdc8..d681365767ce8 100644 --- a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts +++ b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts @@ -159,7 +159,7 @@ export const getPlainRecordLayoutProps = (dataView: DataView) => { columns: ['name', 'message', 'bytes'], sort: [['date', 'desc']], query: { - sql: 'SELECT * FROM "kibana_sample_data_ecommerce"', + esql: 'FROM "kibana_sample_data_ecommerce"', }, filters: [], }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index 5019c3e135acc..ec809e2c2f760 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -21,29 +21,36 @@ import { SortOrder } from '@kbn/saved-search-plugin/public'; import { CellActionsProvider } from '@kbn/cell-actions'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { SearchResponseWarnings } from '@kbn/search-response-warnings'; +import { DataLoadingState, UnifiedDataTable, useColumns } from '@kbn/unified-data-table'; import { DOC_HIDE_TIME_COLUMN_SETTING, DOC_TABLE_LEGACY, HIDE_ANNOUNCEMENTS, + MAX_DOC_FIELDS_DISPLAYED, + ROW_HEIGHT_OPTION, SAMPLE_SIZE_SETTING, SEARCH_FIELDS_FROM_SOURCE, + SHOW_MULTIFIELDS, + SORT_DEFAULT_ORDER_SETTING, } from '@kbn/discover-utils'; import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { getDefaultRowsPerPage } from '../../../../../common/constants'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { useAppStateSelector } from '../../services/discover_app_state_container'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; -import { DataLoadingState, DiscoverGrid } from '../../../../components/discover_grid/discover_grid'; import { FetchStatus } from '../../../types'; -import { useColumns } from '../../../../hooks/use_data_grid_columns'; import { RecordRawType } from '../../services/discover_data_state_container'; import { DiscoverStateContainer } from '../../services/discover_state'; import { useDataState } from '../../hooks/use_data_state'; import { DocTableInfinite } from '../../../../components/doc_table/doc_table_infinite'; import { DocumentExplorerCallout } from '../document_explorer_callout'; import { DocumentExplorerUpdateCallout } from '../document_explorer_callout/document_explorer_update_callout'; -import { DiscoverTourProvider } from '../../../../components/discover_tour'; +import { + DISCOVER_TOUR_STEP_ANCHOR_IDS, + DiscoverTourProvider, +} from '../../../../components/discover_tour'; import { getRawRecordType } from '../../utils/get_raw_record_type'; -import { DiscoverGridFlyout } from '../../../../components/discover_grid/discover_grid_flyout'; +import { DiscoverGridFlyout } from '../../../../components/discover_grid_flyout'; import { useSavedSearchInitial } from '../../services/discover_state_provider'; import { useFetchMoreRecords } from './use_fetch_more_records'; @@ -56,7 +63,7 @@ const progressStyle = css` `; const DocTableInfiniteMemoized = React.memo(DocTableInfinite); -const DiscoverGridMemoized = React.memo(DiscoverGrid); +const DataGridMemoized = React.memo(UnifiedDataTable); // export needs for testing export const onResize = ( @@ -147,7 +154,7 @@ function DiscoverDocumentsComponent({ onSetColumns, } = useColumns({ capabilities, - config: uiSettings, + defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING), dataView, dataViews, setAppState: stateContainer.appState.update, @@ -184,10 +191,31 @@ function DiscoverDocumentsComponent({ const showTimeCol = useMemo( () => - !isTextBasedQuery && + // for ES|QL we want to show the time column only when is on Document view + (!isTextBasedQuery || !columns?.length) && !uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false) && !!dataView.timeFieldName, - [isTextBasedQuery, uiSettings, dataView.timeFieldName] + [isTextBasedQuery, columns, uiSettings, dataView.timeFieldName] + ); + + const renderDocumentView = useCallback( + (hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + query={query} + /> + ), + [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] ); if (isDataViewLoading || (isEmptyDataResult && isDataLoading)) { @@ -216,7 +244,7 @@ function DiscoverDocumentsComponent({ data-test-subj="dscInterceptedWarningsCallout" /> )} - {isLegacy && rows && rows.length && ( + {isLegacy && rows && rows.length > 0 && ( <> {!hideAnnouncements && } )} -
    +
    -
    diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.scss b/src/plugins/discover/public/application/main/components/layout/discover_layout.scss index f3f552c76299f..8f8f5b8ec8833 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.scss +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.scss @@ -32,18 +32,12 @@ discover-app { } .dscPageContent__wrapper { - padding: $euiSizeS $euiSizeS $euiSizeS 0; overflow: hidden; // Ensures horizontal scroll of table - - @include euiBreakpoint('xs', 's') { - padding: 0 $euiSizeS $euiSizeS; - } } .dscPageContent { position: relative; overflow: hidden; - border: $euiBorderThin; height: 100%; } diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx index b9ea55a8ba382..ac0906911dde8 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx @@ -12,7 +12,7 @@ import { EuiPageSidebar } from '@elastic/eui'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { Query, AggregateQuery } from '@kbn/es-query'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; -import { DiscoverLayout, SIDEBAR_CLOSED_KEY } from './discover_layout'; +import { DiscoverLayout } from './discover_layout'; import { dataViewMock, esHitsMock } from '@kbn/discover-utils/src/__mocks__'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { @@ -31,9 +31,7 @@ import { import { createDiscoverServicesMock } from '../../../../__mocks__/services'; import { FetchStatus } from '../../../types'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; -import { LocalStorageMock } from '../../../../__mocks__/local_storage_mock'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { DiscoverServices } from '../../../../build_services'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; import { createSearchSessionMock } from '../../../../__mocks__/search_session'; @@ -41,6 +39,9 @@ import { getSessionServiceMock } from '@kbn/data-plugin/public/search/session/mo import { DiscoverMainProvider } from '../../services/discover_state_provider'; import { act } from 'react-dom/test-utils'; import { ErrorCallout } from '../../../../components/common/error_callout'; +import * as localStorageModule from 'react-use/lib/useLocalStorage'; + +jest.spyOn(localStorageModule, 'default'); setHeaderActionMenuMounter(jest.fn()); @@ -57,12 +58,7 @@ async function mountComponent( }) as DataMain$ ) { const searchSourceMock = createSearchSourceMock({}); - const services = { - ...createDiscoverServicesMock(), - storage: new LocalStorageMock({ - [SIDEBAR_CLOSED_KEY]: prevSidebarClosed, - }) as unknown as Storage, - } as unknown as DiscoverServices; + const services = createDiscoverServicesMock(); const time = { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; services.data.query.timefilter.timefilter.getTime = () => time; (services.data.query.queryString.getDefaultQuery as jest.Mock).mockReturnValue({ @@ -77,6 +73,9 @@ async function mountComponent( (searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation( jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } })) ); + (localStorageModule.default as jest.Mock).mockImplementation( + jest.fn(() => [prevSidebarClosed, jest.fn()]) + ); const stateContainer = getDiscoverStateMock({ isTimeBased: true }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 550be02a6733d..3402bfbce1bcc 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -6,25 +6,31 @@ * Side Public License, v 1. */ import './discover_layout.scss'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { - EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiHideFor, EuiPage, EuiPageBody, EuiPanel, - EuiSpacer, + useEuiBackgroundColor, + useEuiTheme, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import classNames from 'classnames'; import { generateFilters } from '@kbn/data-plugin/public'; import { useDragDropContext } from '@kbn/dom-drag-drop'; import { DataViewField, DataViewType } from '@kbn/data-views-plugin/public'; -import { SEARCH_FIELDS_FROM_SOURCE, SHOW_FIELD_STATISTICS } from '@kbn/discover-utils'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { + SEARCH_FIELDS_FROM_SOURCE, + SHOW_FIELD_STATISTICS, + SORT_DEFAULT_ORDER_SETTING, +} from '@kbn/discover-utils'; +import { popularizeField, useColumns } from '@kbn/unified-data-table'; +import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import { useSavedSearchInitial } from '../../services/discover_state_provider'; import { DiscoverStateContainer } from '../../services/discover_state'; import { VIEW_MODE } from '../../../../../common/constants'; @@ -35,12 +41,10 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { DiscoverNoResults } from '../no_results'; import { LoadingSpinner } from '../loading_spinner/loading_spinner'; import { DiscoverSidebarResponsive } from '../sidebar'; -import { popularizeField } from '../../../../utils/popularize_field'; import { DiscoverTopNav } from '../top_nav/discover_topnav'; import { getResultState } from '../../utils/get_result_state'; import { DiscoverUninitialized } from '../uninitialized/uninitialized'; import { DataMainMsg, RecordRawType } from '../../services/discover_data_state_container'; -import { useColumns } from '../../../../hooks/use_data_grid_columns'; import { FetchStatus } from '../../../types'; import { useDataState } from '../../hooks/use_data_state'; import { getRawRecordType } from '../../utils/get_raw_record_type'; @@ -49,11 +53,6 @@ import { DiscoverHistogramLayout } from './discover_histogram_layout'; import { ErrorCallout } from '../../../../components/common/error_callout'; import { addLog } from '../../../../utils/add_log'; -/** - * Local storage key for sidebar persistence state - */ -export const SIDEBAR_CLOSED_KEY = 'discover:sidebarClosed'; - const SidebarMemoized = React.memo(DiscoverSidebarResponsive); const TopNavMemoized = React.memo(DiscoverTopNav); @@ -69,11 +68,12 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { data, uiSettings, filterManager, - storage, history, spaces, inspector, } = useDiscoverServices(); + const { euiTheme } = useEuiTheme(); + const pageBackgroundColor = useEuiBackgroundColor('plain'); const globalQueryState = data.query.getState(); const { main$ } = stateContainer.dataState.data$; const [query, savedQuery, columns, sort] = useAppStateSelector((state) => [ @@ -106,8 +106,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { return dataView.type !== DataViewType.ROLLUP && dataView.isTimeBased(); }, [dataView]); - const initialSidebarClosed = Boolean(storage.get(SIDEBAR_CLOSED_KEY)); - const [isSidebarClosed, setIsSidebarClosed] = useState(initialSidebarClosed); const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); const isPlainRecord = useMemo(() => getRawRecordType(query) === RecordRawType.PLAIN, [query]); @@ -127,7 +125,7 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { onRemoveColumn, } = useColumns({ capabilities, - config: uiSettings, + defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING), dataView, dataViews, setAppState: stateContainer.appState.update, @@ -169,12 +167,14 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { filterManager.setFilters(disabledFilters); }, [filterManager]); - const toggleSidebarCollapse = useCallback(() => { - storage.set(SIDEBAR_CLOSED_KEY, !isSidebarClosed); - setIsSidebarClosed(!isSidebarClosed); - }, [isSidebarClosed, storage]); - const contentCentered = resultState === 'uninitialized' || resultState === 'none'; + const documentState = useDataState(stateContainer.dataState.data$.documents$); + + const textBasedLanguageModeWarning = useMemo(() => { + if (isPlainRecord) { + return documentState.textBasedHeaderWarning; + } + }, [documentState.textBasedHeaderWarning, isPlainRecord]); const textBasedLanguageModeErrors = useMemo(() => { if (isPlainRecord) { @@ -230,7 +230,13 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { ]); return ( - +

    @@ -263,7 +270,7 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { spaces={spaces} history={history} /> - + - -
    - - -
    -
    +
    {resultState === 'none' ? ( @@ -324,7 +319,10 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { role="main" panelRef={resizeRef} paddingSize="none" + borderRadius="none" hasShadow={false} + hasBorder={false} + color="transparent" className={classNames('dscPageContent', { 'dscPageContent--centered': contentCentered, })} diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx index 91fe00263c1c4..e241a52b1d259 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { DragDrop, type DropType, DropOverlayWrapper } from '@kbn/dom-drag-drop'; +import useObservable from 'react-use/lib/useObservable'; import React, { useCallback } from 'react'; import { DataView } from '@kbn/data-views-plugin/common'; import { METRIC_TYPE } from '@kbn/analytics'; @@ -22,6 +23,7 @@ import { DiscoverDocuments } from './discover_documents'; import { DOCUMENTS_VIEW_CLICK, FIELD_STATISTICS_VIEW_CLICK } from '../field_stats_table/constants'; import { ErrorCallout } from '../../../../components/common/error_callout'; import { useDataState } from '../../hooks/use_data_state'; +import { SelectedVSAvailableCallout } from './selected_vs_available_callout'; const DROP_PROPS = { value: { @@ -75,6 +77,7 @@ export const DiscoverMainContent = ({ ); const dataState = useDataState(stateContainer.dataState.data$.main$); + const documents = useObservable(stateContainer.dataState.data$.documents$); const isDropAllowed = Boolean(onDropFieldToTable); return ( @@ -94,7 +97,6 @@ export const DiscoverMainContent = ({ data-test-subj="dscMainContent" > - {!isPlainRecord && ( )} + {viewMode === VIEW_MODE.DOCUMENT_LEVEL ? ( { + it('should render the callout if isPlainRecord is true and the selected columns are less than the available ones', async () => { + const component = mountWithIntl( + + ); + expect(component.find('[data-test-subj="dscSelectedColumnsCallout"]').exists()).toBe(true); + }); + + it('should not render the callout if isPlainRecord is false', async () => { + const component = mountWithIntl( + + ); + expect(component.find('[data-test-subj="dscSelectedColumnsCallout"]').exists()).toBe(false); + }); + + it('should not render the callout if isPlainRecord is true but the selected columns are equal with the available ones', async () => { + const component = mountWithIntl( + + ); + expect(component.find('[data-test-subj="dscSelectedColumnsCallout"]').exists()).toBe(false); + }); +}); diff --git a/src/plugins/discover/public/application/main/components/layout/selected_vs_available_callout.tsx b/src/plugins/discover/public/application/main/components/layout/selected_vs_available_callout.tsx new file mode 100644 index 0000000000000..ec05667e69b35 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/selected_vs_available_callout.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut } from '@elastic/eui'; +import type { DatatableColumn } from '@kbn/expressions-plugin/common'; + +interface SelectedVSAvailableCallout { + isPlainRecord: boolean; + selectedColumns: string[]; + textBasedQueryColumns?: DatatableColumn[]; +} + +export const SelectedVSAvailableCallout = ({ + isPlainRecord, + textBasedQueryColumns, + selectedColumns, +}: SelectedVSAvailableCallout) => { + return ( + <> + {isPlainRecord && + textBasedQueryColumns && + selectedColumns.length > 0 && + selectedColumns.length < textBasedQueryColumns.length && ( + + )} + + ); +}; diff --git a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx index 53e35d207507c..ae73126afde88 100644 --- a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx @@ -173,6 +173,12 @@ describe('useDiscoverHistogram', () => { 'totalHitsResult', ]); }); + + it('should return the isChartLoading params for text based languages', async () => { + const { hook } = await renderUseDiscoverHistogram({ isPlainRecord: true }); + const isChartLoading = hook.result.current.isChartLoading; + expect(isChartLoading).toBe(false); + }); }); describe('state', () => { @@ -390,6 +396,26 @@ describe('useDiscoverHistogram', () => { }); expect(mockCheckHitCount).not.toHaveBeenCalled(); }); + + it('should set isChartLoading to true for fetch start', async () => { + const fetch$ = new Subject<{ + options: { + reset: boolean; + fetchMore: boolean; + }; + searchSessionId: string; + }>(); + const stateContainer = getStateContainer(); + stateContainer.dataState.fetch$ = fetch$; + const { hook } = await renderUseDiscoverHistogram({ stateContainer, isPlainRecord: true }); + act(() => { + fetch$.next({ + options: { reset: false, fetchMore: false }, + searchSessionId: '1234', + }); + }); + expect(hook.result.current.isChartLoading).toBe(true); + }); }); describe('refetching', () => { diff --git a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts index 68d821580f5a4..764145d72aac1 100644 --- a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts +++ b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts @@ -59,6 +59,7 @@ export const useDiscoverHistogram = ({ */ const [unifiedHistogram, ref] = useState(); + const [isSuggestionLoading, setIsSuggestionLoading] = useState(false); const getCreationOptions = useCallback(() => { const { @@ -243,6 +244,26 @@ export const useDiscoverHistogram = ({ columns: savedSearchData$.documents$.getValue().textBasedQueryColumns ?? [], }); + useEffect(() => { + if (!isPlainRecord) { + return; + } + + const fetchStart = stateContainer.dataState.fetch$.subscribe(() => { + if (!skipRefetch.current) { + setIsSuggestionLoading(true); + } + }); + const fetchComplete = textBasedFetchComplete$.subscribe(() => { + setIsSuggestionLoading(false); + }); + + return () => { + fetchStart.unsubscribe(); + fetchComplete.unsubscribe(); + }; + }, [isPlainRecord, stateContainer.dataState.fetch$, textBasedFetchComplete$]); + /** * Data fetching */ @@ -319,6 +340,7 @@ export const useDiscoverHistogram = ({ onBrushEnd: histogramCustomization?.onBrushEnd, withDefaultActions: histogramCustomization?.withDefaultActions, disabledActions: histogramCustomization?.disabledActions, + isChartLoading: isSuggestionLoading, }; }; diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx index 633f082c4792b..076456fc3cd66 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx @@ -119,9 +119,9 @@ export const NoResultsSuggestions: React.FC = ({ return ( } - hasBorder + hasBorder={false} title={

    { + it('should render correctly in the ES|QL mode', async () => { const propsWithTextBasedMode = { ...props, columns: ['extension', 'bytes'], @@ -514,7 +514,7 @@ describe('discover responsive sidebar', function () { }) as DataDocuments$, }; const compInTextBasedMode = await mountComponent(propsWithTextBasedMode, { - query: { sql: 'SELECT * FROM `index`' }, + query: { esql: 'FROM `index`' }, }); await act(async () => { @@ -748,5 +748,14 @@ describe('discover responsive sidebar', function () { expect(comp.find('[data-test-subj="custom-data-view-picker"]').exists()).toBe(true); }); + + it('should allow to toggle sidebar', async function () { + const comp = await mountComponent(props); + expect(findTestSubject(comp, 'fieldList').exists()).toBe(true); + findTestSubject(comp, 'unifiedFieldListSidebar__toggle-collapse').simulate('click'); + expect(findTestSubject(comp, 'fieldList').exists()).toBe(false); + findTestSubject(comp, 'unifiedFieldListSidebar__toggle-expand').simulate('click'); + expect(findTestSubject(comp, 'fieldList').exists()).toBe(true); + }); }); }); diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx index f9a8b493a3f77..f8352850cb4d4 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx @@ -42,7 +42,10 @@ const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOpti return { originatingApp: PLUGIN_ID, localStorageKeyPrefix: 'discover', + compressed: true, + showSidebarToggleButton: true, disableFieldsExistenceAutoFetching: true, + buttonAddFieldVariant: 'toolbar', buttonPropsToTriggerFlyout: { contentProps: { id: DISCOVER_TOUR_STEP_ANCHOR_IDS.addFields, @@ -87,10 +90,6 @@ export interface DiscoverSidebarResponsiveProps { * hits fetched from ES, displayed in the doc table */ documents$: DataDocuments$; - /** - * Has been toggled closed - */ - isClosed?: boolean; /** * Callback function when selecting a field */ @@ -380,7 +379,6 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) ref={initializeUnifiedFieldListSidebarContainerApi} variant={fieldListVariant} getCreationOptions={getCreationOptions} - isSidebarCollapsed={props.isClosed} services={fieldListSidebarServices} dataView={selectedDataView} trackUiMetric={trackUiMetric} diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 66f566cff1673..a743d4c1abebd 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import type { Query, TimeRange, AggregateQuery } from '@kbn/es-query'; import { DataViewType, type DataView } from '@kbn/data-views-plugin/public'; import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; -import { ENABLE_SQL } from '@kbn/discover-utils'; +import { ENABLE_ESQL } from '@kbn/discover-utils'; import { useSavedSearchInitial } from '../../services/discover_state_provider'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -31,6 +31,7 @@ export interface DiscoverTopNavProps { stateContainer: DiscoverStateContainer; isPlainRecord: boolean; textBasedLanguageModeErrors?: Error; + textBasedLanguageModeWarning?: string; onFieldEdited: () => Promise; } @@ -42,6 +43,7 @@ export const DiscoverTopNav = ({ updateQuery, isPlainRecord, textBasedLanguageModeErrors, + textBasedLanguageModeWarning, onFieldEdited, }: DiscoverTopNavProps) => { const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews); @@ -164,10 +166,10 @@ export const DiscoverTopNav = ({ const setMenuMountPoint = useMemo(() => { return getHeaderActionMenuMounter(); }, []); - const isSQLModeEnabled = uiSettings.get(ENABLE_SQL); + const isESQLModeEnabled = uiSettings.get(ENABLE_ESQL); const supportedTextBasedLanguages = []; - if (isSQLModeEnabled) { - supportedTextBasedLanguages.push('SQL'); + if (isESQLModeEnabled) { + supportedTextBasedLanguages.push('ESQL'); } const dataViewPickerProps: DataViewPickerProps = { trigger: { @@ -233,6 +235,7 @@ export const DiscoverTopNav = ({ textBasedLanguageModeErrors={ textBasedLanguageModeErrors ? [textBasedLanguageModeErrors] : undefined } + textBasedLanguageModeWarning={textBasedLanguageModeWarning} onTextBasedSavedAndExit={onTextBasedSavedAndExit} prependFilterBar={ searchBarCustomization?.PrependFilterBar ? ( diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts index 44cf4c503be30..4e3e7068f883d 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts @@ -74,7 +74,7 @@ test('getTopNavLinks result', () => { `); }); -test('getTopNavLinks result for sql mode', () => { +test('getTopNavLinks result for ES|QL mode', () => { const topNavLinks = getTopNavLinks({ dataView: dataViewMock, onOpenInspector: jest.fn(), diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index 2b406e6a45682..ef2a6fb9ad28b 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -53,6 +53,7 @@ export const getTopNavLinks = ({ services, stateContainer: state, adHocDataViews, + isPlainRecord, }); }, testId: 'discoverAlertsButton', @@ -232,7 +233,6 @@ export const getTopNavLinks = ({ if ( services.triggersActionsUi && services.capabilities.management?.insightsAndAlerting?.triggersActions && - !isPlainRecord && !defaultMenu?.alertsItem?.disabled ) { entries.push({ data: alerts, order: defaultMenu?.alertsItem?.order ?? 400 }); diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx index 03e5712df5e2e..bea933950200d 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx @@ -13,10 +13,11 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { AlertsPopover } from './open_alerts_popover'; import { discoverServiceMock } from '../../../../__mocks__/services'; import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { dataViewWithNoTimefieldMock } from '../../../../__mocks__/data_view_no_timefield'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; -const mount = (dataView = dataViewMock) => { +const mount = (dataView = dataViewMock, isPlainRecord = false) => { const stateContainer = getDiscoverStateMock({ isTimeBased: true }); stateContainer.actions.setDataView(dataView); return mountWithIntl( @@ -25,6 +26,7 @@ const mount = (dataView = dataViewMock) => { stateContainer={stateContainer} anchorElement={document.createElement('div')} adHocDataViews={[]} + isPlainRecord={isPlainRecord} services={discoverServiceMock} onClose={jest.fn()} /> @@ -33,18 +35,42 @@ const mount = (dataView = dataViewMock) => { }; describe('OpenAlertsPopover', () => { - it('should render with the create search threshold rule button disabled if the data view has no time field', () => { - const component = mount(); - expect(findTestSubject(component, 'discoverCreateAlertButton').prop('disabled')).toBeTruthy(); - }); + describe('Dataview mode', () => { + it('should render with the create search threshold rule button disabled if the data view has no time field', () => { + const component = mount(); + expect(findTestSubject(component, 'discoverCreateAlertButton').prop('disabled')).toBeTruthy(); + }); + + it('should render with the create search threshold rule button enabled if the data view has a time field', () => { + const component = mount(dataViewWithTimefieldMock); + expect(findTestSubject(component, 'discoverCreateAlertButton').prop('disabled')).toBeFalsy(); + }); - it('should render with the create search threshold rule button enabled if the data view has a time field', () => { - const component = mount(dataViewWithTimefieldMock); - expect(findTestSubject(component, 'discoverCreateAlertButton').prop('disabled')).toBeFalsy(); + it('should render the manage rules and connectors link', () => { + const component = mount(); + expect(findTestSubject(component, 'discoverManageAlertsButton').exists()).toBeTruthy(); + }); }); - it('should render the manage rules and connectors link', () => { - const component = mount(); - expect(findTestSubject(component, 'discoverManageAlertsButton').exists()).toBeTruthy(); + describe('ES|QL mode', () => { + it('should render with the create search threshold rule button enabled if the data view has no timeFieldName but at least one time field', () => { + const component = mount(dataViewMock, true); + expect(findTestSubject(component, 'discoverCreateAlertButton').prop('disabled')).toBeFalsy(); + }); + + it('should render with the create search threshold rule button enabled if the data view has a time field', () => { + const component = mount(dataViewWithTimefieldMock, true); + expect(findTestSubject(component, 'discoverCreateAlertButton').prop('disabled')).toBeFalsy(); + }); + + it('should render with the create search threshold rule button disabled if the data view has no time fields at all', () => { + const component = mount(dataViewWithNoTimefieldMock, true); + expect(findTestSubject(component, 'discoverCreateAlertButton').prop('disabled')).toBeTruthy(); + }); + + it('should render the manage rules and connectors link', () => { + const component = mount(); + expect(findTestSubject(component, 'discoverManageAlertsButton').exists()).toBeTruthy(); + }); }); }); diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx index 75202710945dd..2993193ccc2bf 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx @@ -28,6 +28,7 @@ interface AlertsPopoverProps { savedQueryId?: string; adHocDataViews: DataView[]; services: DiscoverServices; + isPlainRecord?: boolean; } interface EsQueryAlertMetaData { @@ -41,8 +42,13 @@ export function AlertsPopover({ services, stateContainer, onClose: originalOnClose, + isPlainRecord, }: AlertsPopoverProps) { const dataView = stateContainer.internalState.getState().dataView; + const query = stateContainer.appState.getState().query; + const dateFields = dataView?.fields.getByType('date'); + const timeField = dataView?.timeFieldName || dateFields?.[0]?.name; + const { triggersActionsUi } = services; const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState(false); const onClose = useCallback(() => { @@ -54,6 +60,13 @@ export function AlertsPopover({ * Provides the default parameters used to initialize the new rule */ const getParams = useCallback(() => { + if (isPlainRecord) { + return { + searchType: 'esqlQuery', + esqlQuery: query, + timeField, + }; + } const savedQueryId = stateContainer.appState.getState().savedQuery; return { searchType: 'searchSource', @@ -62,7 +75,7 @@ export function AlertsPopover({ .searchSource.getSerializedFields(), savedQueryId, }; - }, [stateContainer]); + }, [isPlainRecord, stateContainer.appState, stateContainer.savedSearchState, query, timeField]); const discoverMetadata: EsQueryAlertMetaData = useMemo( () => ({ @@ -98,7 +111,14 @@ export function AlertsPopover({ }); }, [alertFlyoutVisible, triggersActionsUi, discoverMetadata, getParams, onClose, stateContainer]); - const hasTimeFieldName = Boolean(dataView?.timeFieldName); + const hasTimeFieldName: boolean = useMemo(() => { + if (!isPlainRecord) { + return Boolean(dataView?.timeFieldName); + } else { + return Boolean(timeField); + } + }, [dataView?.timeFieldName, isPlainRecord, timeField]); + const panels = [ { id: 'mainPanel', @@ -165,11 +185,13 @@ export function openAlertsPopover({ stateContainer, services, adHocDataViews, + isPlainRecord, }: { anchorElement: HTMLElement; stateContainer: DiscoverStateContainer; services: DiscoverServices; adHocDataViews: DataView[]; + isPlainRecord?: boolean; }) { if (isOpen) { closeAlertsPopover(); @@ -188,6 +210,7 @@ export function openAlertsPopover({ stateContainer={stateContainer} adHocDataViews={adHocDataViews} services={services} + isPlainRecord={isPlainRecord} /> diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts index 7b96c2673f3eb..80fe8b9920227 100644 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts @@ -26,7 +26,7 @@ import { import { filter } from 'rxjs/operators'; import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__'; import { buildDataTableRecord } from '@kbn/discover-utils'; -import { searchResponseWarningsMock } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; +import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; describe('test useSavedSearch message generators', () => { test('sendCompleteMsg', (done) => { @@ -103,15 +103,13 @@ describe('test useSavedSearch message generators', () => { if (value.fetchStatus !== FetchStatus.LOADING_MORE) { expect(value.fetchStatus).toBe(FetchStatus.COMPLETE); expect(value.result).toStrictEqual([...initialRecords, ...moreRecords]); - expect(value.interceptedWarnings).toHaveLength(searchResponseWarningsMock.length); + expect(value.interceptedWarnings).toHaveLength(1); done(); } }); sendLoadingMoreFinishedMsg(documents$, { moreRecords, - interceptedWarnings: searchResponseWarningsMock.map((warning) => ({ - originalWarning: warning, - })), + interceptedWarnings: [{ originalWarning: searchResponseIncompleteWarningLocalCluster }], }); }); test('sendLoadingMoreFinishedMsg after an exception', (done) => { @@ -121,9 +119,7 @@ describe('test useSavedSearch message generators', () => { const documents$ = new BehaviorSubject({ fetchStatus: FetchStatus.LOADING_MORE, result: initialRecords, - interceptedWarnings: searchResponseWarningsMock.map((warning) => ({ - originalWarning: warning, - })), + interceptedWarnings: [{ originalWarning: searchResponseIncompleteWarningLocalCluster }], }); documents$.subscribe((value) => { if (value.fetchStatus !== FetchStatus.LOADING_MORE) { diff --git a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.tsx b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.tsx index 125d47c087640..a6dcafad11757 100644 --- a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.tsx +++ b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.tsx @@ -50,7 +50,7 @@ function getHookProps( replaceUrlState, }; } -const query = { sql: 'SELECT * from the-data-view-title' }; +const query = { esql: 'from the-data-view-title' }; const msgComplete = { recordRawType: RecordRawType.PLAIN, fetchStatus: FetchStatus.PARTIAL, @@ -103,19 +103,16 @@ describe('useTextBasedQueryLanguage', () => { const { replaceUrlState, stateContainer } = renderHookWithContext(true); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); - expect(replaceUrlState).toHaveBeenCalledWith({ index: 'the-data-view-id' }); - - replaceUrlState.mockReset(); - - stateContainer.dataState.data$.documents$.next(msgComplete); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); - await waitFor(() => { expect(replaceUrlState).toHaveBeenCalledWith({ index: 'the-data-view-id', - columns: ['field1', 'field2'], }); }); + + replaceUrlState.mockReset(); + + stateContainer.dataState.data$.documents$.next(msgComplete); + expect(replaceUrlState).toHaveBeenCalledTimes(0); }); test('should change viewMode to DOCUMENT_LEVEL if it was AGGREGATED_LEVEL', async () => { const { replaceUrlState } = renderHookWithContext(false, { @@ -132,7 +129,7 @@ describe('useTextBasedQueryLanguage', () => { const { replaceUrlState, stateContainer } = renderHookWithContext(false); const documents$ = stateContainer.dataState.data$.documents$; stateContainer.dataState.data$.documents$.next(msgComplete); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); replaceUrlState.mockReset(); documents$.next({ @@ -145,7 +142,8 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-title' }, + // transformational command + query: { esql: 'from the-data-view-title | keep field1' }, }); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); @@ -156,13 +154,42 @@ describe('useTextBasedQueryLanguage', () => { }); }); }); + + test('changing a text based query with no transformational commands should only change dataview state when loading and finished', async () => { + const { replaceUrlState, stateContainer } = renderHookWithContext(false); + const documents$ = stateContainer.dataState.data$.documents$; + stateContainer.dataState.data$.documents$.next(msgComplete); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); + replaceUrlState.mockReset(); + + documents$.next({ + recordRawType: RecordRawType.PLAIN, + fetchStatus: FetchStatus.PARTIAL, + result: [ + { + id: '1', + raw: { field1: 1 }, + flattened: { field1: 1 }, + } as unknown as DataTableRecord, + ], + // non transformational command + query: { esql: 'from the-data-view-title | where field1 > 0' }, + }); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); + + await waitFor(() => { + expect(replaceUrlState).toHaveBeenCalledWith({ + index: 'the-data-view-id', + }); + }); + }); test('only changing a text based query with same result columns should not change columns', async () => { const { replaceUrlState, stateContainer } = renderHookWithContext(false); const documents$ = stateContainer.dataState.data$.documents$; documents$.next(msgComplete); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); replaceUrlState.mockReset(); documents$.next({ @@ -175,7 +202,7 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-title' }, + query: { esql: 'from the-data-view-title | keep field1' }, }); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); replaceUrlState.mockReset(); @@ -190,17 +217,17 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-title WHERE field1=1' }, + query: { esql: 'from the-data-view-title | keep field 1 | WHERE field1=1' }, }); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(0)); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); }); test('if its not a text based query coming along, it should be ignored', async () => { const { replaceUrlState, stateContainer } = renderHookWithContext(false); const documents$ = stateContainer.dataState.data$.documents$; documents$.next(msgComplete); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); replaceUrlState.mockReset(); documents$.next({ @@ -225,7 +252,7 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-title WHERE field1=1' }, + query: { esql: 'from the-data-view-title | keep field 1 | WHERE field1=1' }, }); await waitFor(() => { @@ -253,7 +280,7 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-title WHERE field1=1' }, + query: { esql: 'from the-data-view-title | keep field 1 | WHERE field1=1' }, }); documents$.next({ @@ -266,9 +293,68 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-title' }, + query: { esql: 'from the-data-view-title | keep field1' }, }); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); + expect(replaceUrlState).toHaveBeenCalledWith({ + columns: ['field1'], + }); + }); + + test('it should not overwrite existing state columns on initial fetch and non transformational commands', async () => { + const { replaceUrlState, stateContainer } = renderHookWithContext(false, { + columns: ['field1'], + index: 'the-data-view-id', + }); + const documents$ = stateContainer.dataState.data$.documents$; + + documents$.next({ + recordRawType: RecordRawType.PLAIN, + fetchStatus: FetchStatus.PARTIAL, + result: [ + { + id: '1', + raw: { field1: 1, field2: 2 }, + flattened: { field1: 1 }, + } as unknown as DataTableRecord, + ], + query: { esql: 'from the-data-view-title | WHERE field2=1' }, + }); + expect(replaceUrlState).toHaveBeenCalledTimes(0); + }); + + test('it should overwrite existing state columns on transitioning from a query with non transformational commands to a query with transformational', async () => { + const { replaceUrlState, stateContainer } = renderHookWithContext(false, { + index: 'the-data-view-id', + }); + const documents$ = stateContainer.dataState.data$.documents$; + + documents$.next({ + recordRawType: RecordRawType.PLAIN, + fetchStatus: FetchStatus.PARTIAL, + result: [ + { + id: '1', + raw: { field1: 1, field2: 2 }, + flattened: { field1: 1 }, + } as unknown as DataTableRecord, + ], + query: { esql: 'from the-data-view-title | WHERE field2=1' }, + }); + expect(replaceUrlState).toHaveBeenCalledTimes(0); + documents$.next({ + recordRawType: RecordRawType.PLAIN, + fetchStatus: FetchStatus.PARTIAL, + result: [ + { + id: '1', + raw: { field1: 1 }, + flattened: { field1: 1 }, + } as unknown as DataTableRecord, + ], + query: { esql: 'from the-data-view-title | keep field1' }, + }); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); expect(replaceUrlState).toHaveBeenCalledWith({ columns: ['field1'], }); @@ -284,9 +370,9 @@ describe('useTextBasedQueryLanguage', () => { documents$.next({ recordRawType: RecordRawType.PLAIN, fetchStatus: FetchStatus.LOADING, - query: { sql: 'SELECT * from the-data-view-title WHERE field1=2' }, + query: { esql: 'from the-data-view-title | WHERE field1=2' }, }); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); + expect(replaceUrlState).toHaveBeenCalledTimes(0); documents$.next({ recordRawType: RecordRawType.PLAIN, fetchStatus: FetchStatus.PARTIAL, @@ -297,9 +383,9 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT * from the-data-view-title WHERE field1=2' }, + query: { esql: 'from the-data-view-title | WHERE field1=2' }, }); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(2)); + expect(replaceUrlState).toHaveBeenCalledTimes(0); stateContainer.appState.getState = jest.fn(() => { return { columns: ['field1', 'field2'], index: 'the-data-view-id' }; }); @@ -308,7 +394,7 @@ describe('useTextBasedQueryLanguage', () => { documents$.next({ recordRawType: RecordRawType.PLAIN, fetchStatus: FetchStatus.LOADING, - query: { sql: 'SELECT field1; from the-data-view-title WHERE field1=2' }, + query: { esql: 'from the-data-view-title | keep field 1; | WHERE field1=2' }, }); documents$.next({ @@ -319,7 +405,7 @@ describe('useTextBasedQueryLanguage', () => { documents$.next({ recordRawType: RecordRawType.PLAIN, fetchStatus: FetchStatus.LOADING, - query: { sql: 'SELECT field1 from the-data-view-title' }, + query: { esql: 'from the-data-view-title | keep field1' }, }); documents$.next({ @@ -332,7 +418,7 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-title' }, + query: { esql: 'from the-data-view-title | keep field1' }, }); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); @@ -350,7 +436,7 @@ describe('useTextBasedQueryLanguage', () => { renderHook(() => useTextBasedQueryLanguage(props), { wrapper: getHookContext(stateContainer) }); documents$.next(msgComplete); - await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); replaceUrlState.mockReset(); documents$.next({ @@ -363,7 +449,7 @@ describe('useTextBasedQueryLanguage', () => { flattened: { field1: 1 }, } as unknown as DataTableRecord, ], - query: { sql: 'SELECT field1 from the-data-view-*' }, + query: { esql: 'from the-data-view-* | keep field1' }, }); props.stateContainer.actions.setDataView(dataViewAdHoc); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); diff --git a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts index 4939049ceedda..fb3725d12b1e1 100644 --- a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts +++ b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts @@ -6,12 +6,7 @@ * Side Public License, v 1. */ import { isEqual } from 'lodash'; -import { - isOfAggregateQueryType, - getIndexPatternFromSQLQuery, - AggregateQuery, - Query, -} from '@kbn/es-query'; +import { isOfAggregateQueryType, getAggregateQueryMode } from '@kbn/es-query'; import { useCallback, useEffect, useRef } from 'react'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; import { VIEW_MODE } from '@kbn/saved-search-plugin/public'; @@ -21,6 +16,8 @@ import { getValidViewMode } from '../utils/get_valid_view_mode'; import { FetchStatus } from '../../types'; const MAX_NUM_OF_COLUMNS = 50; +// For ES|QL we want in case of the following commands to display a table view, otherwise display a document view +const TRANSFORMATIONAL_COMMANDS = ['stats', 'project', 'keep']; /** * Hook to take care of text based query language state transformations when a new result is returned @@ -33,11 +30,14 @@ export function useTextBasedQueryLanguage({ stateContainer: DiscoverStateContainer; dataViews: DataViewsContract; }) { - const prev = useRef<{ query: AggregateQuery | Query | undefined; columns: string[] }>({ + const prev = useRef<{ + query: string; + columns: string[]; + }>({ columns: [], - query: undefined, + query: '', }); - const indexTitle = useRef(''); + const initialFetch = useRef(true); const savedSearch = useSavedSearchInitial(); const cleanup = useCallback(() => { @@ -45,9 +45,8 @@ export function useTextBasedQueryLanguage({ // cleanup when it's not a text based query lang prev.current = { columns: [], - query: undefined, + query: '', }; - indexTitle.current = ''; } }, []); @@ -63,51 +62,60 @@ export function useTextBasedQueryLanguage({ fetchStatus: FetchStatus.COMPLETE, }); }; - const { columns: stateColumns, index, viewMode } = stateContainer.appState.getState(); + const { index, viewMode } = stateContainer.appState.getState(); let nextColumns: string[] = []; const isTextBasedQueryLang = - recordRawType === 'plain' && isOfAggregateQueryType(query) && 'sql' in query; + recordRawType === 'plain' && + isOfAggregateQueryType(query) && + ('sql' in query || 'esql' in query); const hasResults = Boolean(next.result?.length); - const initialFetch = !prev.current.columns.length; + let queryHasTransformationalCommands = 'sql' in query; + if ('esql' in query) { + TRANSFORMATIONAL_COMMANDS.forEach((command: string) => { + if (query.esql.toLowerCase().includes(command)) { + queryHasTransformationalCommands = true; + return; + } + }); + } if (isTextBasedQueryLang) { + const language = getAggregateQueryMode(query); if (next.fetchStatus !== FetchStatus.PARTIAL) { return; } + const dataViewObj = stateContainer.internalState.getState().dataView!; + if (hasResults) { // check if state needs to contain column transformation due to a different columns in the resultset const firstRow = next.result![0]; const firstRowColumns = Object.keys(firstRow.raw).slice(0, MAX_NUM_OF_COLUMNS); - if ( - !isEqual(firstRowColumns, prev.current.columns) && - !isEqual(query, prev.current.query) - ) { - prev.current = { columns: firstRowColumns, query }; + if (!queryHasTransformationalCommands) { + nextColumns = []; + initialFetch.current = false; + } else { nextColumns = firstRowColumns; - } - - if (firstRowColumns && initialFetch) { - prev.current = { columns: firstRowColumns, query }; + if ( + initialFetch.current && + !prev.current.columns.length && + Boolean(dataViewObj?.id === index) + ) { + prev.current.columns = firstRowColumns; + } } } - const indexPatternFromQuery = getIndexPatternFromSQLQuery(query.sql); - - const dataViewObj = stateContainer.internalState.getState().dataView!; - - // don't set the columns on initial fetch, to prevent overwriting existing state - const addColumnsToState = Boolean( - nextColumns.length && (!initialFetch || !stateColumns?.length) - ); + const addColumnsToState = !isEqual(nextColumns, prev.current.columns); + const queryChanged = query[language] !== prev.current.query; // no need to reset index to state if it hasn't changed - const addDataViewToState = Boolean(dataViewObj?.id !== index) || initialFetch; - const queryChanged = indexPatternFromQuery !== indexTitle.current; - if (!addColumnsToState && !queryChanged) { + const addDataViewToState = Boolean(dataViewObj?.id !== index); + if (!queryChanged || (!addDataViewToState && !addColumnsToState)) { sendComplete(); return; } if (queryChanged) { - indexTitle.current = indexPatternFromQuery; + prev.current.query = query[language]; + prev.current.columns = nextColumns; } const nextState = { ...(addDataViewToState && { index: dataViewObj.id }), diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts index ea2d1d1f2324a..47cd216b1547e 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts @@ -23,12 +23,12 @@ import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { IKbnUrlStateStorage, ISyncStateRef, syncState } from '@kbn/kibana-utils-plugin/public'; import { isEqual } from 'lodash'; import { connectToQueryState, syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public'; +import type { UnifiedDataTableSettings } from '@kbn/unified-data-table'; import type { DiscoverServices } from '../../../build_services'; import { addLog } from '../../../utils/add_log'; import { cleanupUrlState } from '../utils/cleanup_url_state'; import { getStateDefaults } from '../utils/get_state_defaults'; import { handleSourceColumnState } from '../../../utils/state_helpers'; -import type { DiscoverGridSettings } from '../../../components/discover_grid/types'; export const APP_STATE_URL_KEY = '_a'; export interface DiscoverAppStateContainer extends ReduxLikeStateContainer { @@ -87,7 +87,7 @@ export interface DiscoverAppState { /** * Data Grid related state */ - grid?: DiscoverGridSettings; + grid?: UnifiedDataTableSettings; /** * Hide chart */ diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts index d3f600dcfaff8..516d81cc9c3f0 100644 --- a/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts @@ -10,7 +10,7 @@ import { waitFor } from '@testing-library/react'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__'; import { discoverServiceMock } from '../../../__mocks__/services'; -import { savedSearchMockWithSQL } from '../../../__mocks__/saved_search'; +import { savedSearchMockWithESQL } from '../../../__mocks__/saved_search'; import { FetchStatus } from '../../types'; import { setUrlTracker } from '../../../kibana_services'; import { urlTrackerMock } from '../../../__mocks__/url_tracker.mock'; @@ -104,10 +104,10 @@ describe('test getDataStateContainer', () => { test('useSavedSearch returns plain record raw type', async () => { const stateContainer = getDiscoverStateMock({ - savedSearch: savedSearchMockWithSQL, + savedSearch: savedSearchMockWithESQL, }); - stateContainer.savedSearchState.load = jest.fn().mockResolvedValue(savedSearchMockWithSQL); - await stateContainer.actions.loadSavedSearch({ savedSearchId: savedSearchMockWithSQL.id }); + stateContainer.savedSearchState.load = jest.fn().mockResolvedValue(savedSearchMockWithESQL); + await stateContainer.actions.loadSavedSearch({ savedSearchId: savedSearchMockWithESQL.id }); expect(stateContainer.dataState.data$.main$.getValue().recordRawType).toBe(RecordRawType.PLAIN); }); diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts index f243d9884ca9e..417fa6679501b 100644 --- a/src/plugins/discover/public/application/main/services/discover_data_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts @@ -57,7 +57,7 @@ export enum RecordRawType { */ DOCUMENT = 'document', /** - * Data returned e.g. SQL queries, flat structure + * Data returned e.g. ES|QL queries, flat structure * */ PLAIN = 'plain', } @@ -78,6 +78,7 @@ export interface DataMainMsg extends DataMsg { export interface DataDocumentsMsg extends DataMsg { result?: DataTableRecord[]; textBasedQueryColumns?: DatatableColumn[]; // columns from text-based request + textBasedHeaderWarning?: string; interceptedWarnings?: SearchResponseInterceptedWarning[]; // warnings (like shard failures) } diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index f402988942a10..bcd96315ce575 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -468,8 +468,8 @@ describe('Test discover state actions', () => { ); }); - test('loadSavedSearch without id containing sql, adding no warning toast with an invalid index', async () => { - const url = "/#?_a=(index:abcde,query:(sql:'Select * from test'))&_g=()"; + test('loadSavedSearch without id containing ES|QL, adding no warning toast with an invalid index', async () => { + const url = "/#?_a=(index:abcde,query:(esql:'FROM test'))&_g=()"; const { state } = await getState(url, { savedSearch: savedSearchMock, isEmptyUrl: false }); await state.actions.loadSavedSearch(); expect(discoverServiceMock.toastNotifications.addWarning).not.toHaveBeenCalled(); diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts index 7f19ec3a23695..6de9781b0b58b 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts @@ -22,21 +22,21 @@ import { SavedSearchData, } from '../services/discover_data_state_container'; import { fetchDocuments } from './fetch_documents'; -import { fetchSql } from './fetch_sql'; +import { fetchTextBased } from './fetch_text_based'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__'; -import { searchResponseWarningsMock } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; +import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings'; jest.mock('./fetch_documents', () => ({ fetchDocuments: jest.fn().mockResolvedValue([]), })); -jest.mock('./fetch_sql', () => ({ - fetchSql: jest.fn().mockResolvedValue([]), +jest.mock('./fetch_text_based', () => ({ + fetchTextBased: jest.fn().mockResolvedValue([]), })); const mockFetchDocuments = fetchDocuments as unknown as jest.MockedFunction; -const mockFetchSQL = fetchSql as unknown as jest.MockedFunction; +const mockfetchTextBased = fetchTextBased as unknown as jest.MockedFunction; function subjectCollector(subject: Subject): () => Promise { const promise = firstValueFrom( @@ -88,7 +88,7 @@ describe('test fetchAll', () => { }; mockFetchDocuments.mockReset().mockResolvedValue({ records: [] }); - mockFetchSQL.mockReset().mockResolvedValue({ records: [] }); + mockfetchTextBased.mockReset().mockResolvedValue({ records: [] }); }); test('changes of fetchStatus when starting with FetchStatus.UNINITIALIZED', async () => { @@ -246,18 +246,18 @@ describe('test fetchAll', () => { ]); }); - test('emits loading and documents on documents$ correctly for SQL query', async () => { + test('emits loading and documents on documents$ correctly for ES|QL query', async () => { const collect = subjectCollector(subjects.documents$); const hits = [ { _id: '1', _index: 'logs' }, { _id: '2', _index: 'logs' }, ]; const documents = hits.map((hit) => buildDataTableRecord(hit, dataViewMock)); - mockFetchSQL.mockResolvedValue({ + mockfetchTextBased.mockResolvedValue({ records: documents, textBasedQueryColumns: [{ id: '1', name: 'test1', meta: { type: 'number' } }], }); - const query = { sql: 'SELECT * from foo' }; + const query = { esql: 'from foo' }; deps = { abortController: new AbortController(), inspectorAdapters: { requests: new RequestAdapter() }, @@ -296,9 +296,11 @@ describe('test fetchAll', () => { const initialRecords = [records[0], records[1]]; const moreRecords = [records[2], records[3]]; - const interceptedWarnings = searchResponseWarningsMock.map((warning) => ({ - originalWarning: warning, - })); + const interceptedWarnings = [ + { + originalWarning: searchResponseIncompleteWarningLocalCluster, + }, + ]; test('should add more records', async () => { const collectDocuments = subjectCollector(subjects.documents$); diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.ts b/src/plugins/discover/public/application/main/utils/fetch_all.ts index bf2bc0c1eb1b0..ff754b065a130 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.ts @@ -27,7 +27,7 @@ import { fetchDocuments } from './fetch_documents'; import { FetchStatus } from '../../types'; import { DataMsg, RecordRawType, SavedSearchData } from '../services/discover_data_state_container'; import { DiscoverServices } from '../../../build_services'; -import { fetchSql } from './fetch_sql'; +import { fetchTextBased } from './fetch_text_based'; import { InternalState } from '../services/discover_internal_state_container'; export interface FetchDeps { @@ -70,10 +70,10 @@ export function fetchAll( const query = getAppState().query; const prevQuery = dataSubjects.documents$.getValue().query; const recordRawType = getRawRecordType(query); + const useTextbased = recordRawType === RecordRawType.PLAIN; if (reset) { sendResetMsg(dataSubjects, initialFetchStatus, recordRawType); } - const useSql = recordRawType === RecordRawType.PLAIN; if (recordRawType === RecordRawType.DOCUMENT) { // Update the base searchSource, base for all child fetches @@ -92,14 +92,14 @@ export function fetchAll( // Start fetching all required requests const response = - useSql && query - ? fetchSql(query, dataView, data, services.expressions, inspectorAdapters) + useTextbased && query + ? fetchTextBased(query, dataView, data, services.expressions, inspectorAdapters) : fetchDocuments(searchSource, fetchDeps); - const fetchType = useSql && query ? 'fetchSql' : 'fetchDocuments'; + const fetchType = useTextbased && query ? 'fetchTextBased' : 'fetchDocuments'; const startTime = window.performance.now(); // Handle results of the individual queries and forward the results to the corresponding dataSubjects response - .then(({ records, textBasedQueryColumns, interceptedWarnings }) => { + .then(({ records, textBasedQueryColumns, interceptedWarnings, textBasedHeaderWarning }) => { if (services.analytics) { const duration = window.performance.now() - startTime; reportPerformanceMetricEvent(services.analytics, { @@ -125,7 +125,7 @@ export function fetchAll( * So it takes too long, a bad user experience, also a potential flakniess in tests */ const fetchStatus = - useSql && (!prevQuery || !isEqual(query, prevQuery)) + useTextbased && (!prevQuery || !isEqual(query, prevQuery)) ? FetchStatus.PARTIAL : FetchStatus.COMPLETE; @@ -133,6 +133,7 @@ export function fetchAll( fetchStatus, result: records, textBasedQueryColumns, + textBasedHeaderWarning, interceptedWarnings, recordRawType, query, diff --git a/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts b/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts index f850b283b3491..cbc62a3cd6068 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts @@ -37,17 +37,20 @@ describe('test fetchDocuments', () => { const documents = hits.map((hit) => buildDataTableRecord(hit, dataViewMock)); savedSearchMock.searchSource.fetch$ = () => of({ rawResponse: { hits: { hits } } } as IKibanaSearchResponse>); - expect(fetchDocuments(savedSearchMock.searchSource, getDeps())).resolves.toEqual({ + expect(await fetchDocuments(savedSearchMock.searchSource, getDeps())).toEqual({ + interceptedWarnings: [], records: documents, }); }); - test('rejects on query failure', () => { + test('rejects on query failure', async () => { savedSearchMock.searchSource.fetch$ = () => throwErrorRx(() => new Error('Oh noes!')); - expect(fetchDocuments(savedSearchMock.searchSource, getDeps())).rejects.toEqual( - new Error('Oh noes!') - ); + try { + await fetchDocuments(savedSearchMock.searchSource, getDeps()); + } catch (e) { + expect(e).toEqual(new Error('Oh noes!')); + } }); test('passes a correct session id', async () => { @@ -66,7 +69,8 @@ describe('test fetchDocuments', () => { jest.spyOn(searchSourceRegular, 'fetch$'); - expect(fetchDocuments(searchSourceRegular, deps)).resolves.toEqual({ + expect(await fetchDocuments(searchSourceRegular, deps)).toEqual({ + interceptedWarnings: [], records: documents, }); @@ -84,7 +88,8 @@ describe('test fetchDocuments', () => { jest.spyOn(searchSourceForLoadMore, 'fetch$'); - expect(fetchDocuments(searchSourceForLoadMore, deps)).resolves.toEqual({ + expect(await fetchDocuments(searchSourceForLoadMore, deps)).toEqual({ + interceptedWarnings: [], records: documents, }); diff --git a/src/plugins/discover/public/application/main/utils/fetch_documents.ts b/src/plugins/discover/public/application/main/utils/fetch_documents.ts index 767163259304f..e849b347a22ff 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_documents.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_documents.ts @@ -12,8 +12,7 @@ import { isCompleteResponse, ISearchSource } from '@kbn/data-plugin/public'; import { SAMPLE_SIZE_SETTING, buildDataTableRecordList } from '@kbn/discover-utils'; import type { EsHitRecord } from '@kbn/discover-utils/types'; import { getSearchResponseInterceptedWarnings } from '@kbn/search-response-warnings'; -import type { RecordsFetchResponse } from '../../../types'; -import { DISABLE_SHARD_FAILURE_WARNING } from '../../../../common/constants'; +import type { RecordsFetchResponse } from '../../types'; import { FetchDeps } from './fetch_all'; /** @@ -60,7 +59,7 @@ export const fetchDocuments = ( }), }, executionContext, - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, + disableWarningToasts: true, }) .pipe( filter((res) => isCompleteResponse(res)), @@ -75,9 +74,6 @@ export const fetchDocuments = ( ? getSearchResponseInterceptedWarnings({ services, adapter, - options: { - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, - }, }) : []; diff --git a/src/plugins/discover/public/application/main/utils/fetch_sql.ts b/src/plugins/discover/public/application/main/utils/fetch_sql.ts deleted file mode 100644 index 73c716e8f9351..0000000000000 --- a/src/plugins/discover/public/application/main/utils/fetch_sql.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import { pluck } from 'rxjs/operators'; -import { lastValueFrom } from 'rxjs'; -import { Query, AggregateQuery, Filter } from '@kbn/es-query'; -import type { Adapters } from '@kbn/inspector-plugin/common'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; -import type { Datatable } from '@kbn/expressions-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/common'; -import { textBasedQueryStateToAstWithValidation } from '@kbn/data-plugin/common'; -import type { DataTableRecord } from '@kbn/discover-utils/types'; -import { RecordsFetchResponse } from '../../../types'; - -interface SQLErrorResponse { - error: { - message: string; - }; - type: 'error'; -} - -export function fetchSql( - query: Query | AggregateQuery, - dataView: DataView, - data: DataPublicPluginStart, - expressions: ExpressionsStart, - inspectorAdapters: Adapters, - filters?: Filter[], - inputQuery?: Query -): Promise { - const timeRange = data.query.timefilter.timefilter.getTime(); - return textBasedQueryStateToAstWithValidation({ - filters, - query, - time: timeRange, - dataView, - inputQuery, - }) - .then((ast) => { - if (ast) { - const execution = expressions.run(ast, null, { - inspectorAdapters, - }); - let finalData: DataTableRecord[] = []; - let textBasedQueryColumns: Datatable['columns'] | undefined; - let error: string | undefined; - execution.pipe(pluck('result')).subscribe((resp) => { - const response = resp as Datatable | SQLErrorResponse; - if (response.type === 'error') { - error = response.error.message; - } else { - const table = response as Datatable; - const rows = table?.rows ?? []; - textBasedQueryColumns = table?.columns ?? undefined; - finalData = rows.map( - (row: Record, idx: number) => - ({ - id: String(idx), - raw: row, - flattened: row, - } as unknown as DataTableRecord) - ); - } - }); - return lastValueFrom(execution).then(() => { - if (error) { - throw new Error(error); - } else { - return { - records: finalData || [], - textBasedQueryColumns, - }; - } - }); - } - return { - records: [] as DataTableRecord[], - textBasedQueryColumns: [], - }; - }) - .catch((err) => { - throw new Error(err.message); - }); -} diff --git a/src/plugins/discover/public/application/main/utils/fetch_text_based.ts b/src/plugins/discover/public/application/main/utils/fetch_text_based.ts new file mode 100644 index 0000000000000..6a164bfd8a5f8 --- /dev/null +++ b/src/plugins/discover/public/application/main/utils/fetch_text_based.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { pluck } from 'rxjs/operators'; +import { lastValueFrom } from 'rxjs'; +import { Query, AggregateQuery, Filter } from '@kbn/es-query'; +import type { Adapters } from '@kbn/inspector-plugin/common'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { Datatable } from '@kbn/expressions-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { textBasedQueryStateToAstWithValidation } from '@kbn/data-plugin/common'; +import type { DataTableRecord } from '@kbn/discover-utils/types'; +import type { RecordsFetchResponse } from '../../types'; + +interface TextBasedErrorResponse { + error: { + message: string; + }; + type: 'error'; +} + +export function fetchTextBased( + query: Query | AggregateQuery, + dataView: DataView, + data: DataPublicPluginStart, + expressions: ExpressionsStart, + inspectorAdapters: Adapters, + filters?: Filter[], + inputQuery?: Query +): Promise { + const timeRange = data.query.timefilter.timefilter.getTime(); + return textBasedQueryStateToAstWithValidation({ + filters, + query, + time: timeRange, + dataView, + inputQuery, + }) + .then((ast) => { + if (ast) { + const execution = expressions.run(ast, null, { + inspectorAdapters, + }); + let finalData: DataTableRecord[] = []; + let textBasedQueryColumns: Datatable['columns'] | undefined; + let error: string | undefined; + let textBasedHeaderWarning: string | undefined; + execution.pipe(pluck('result')).subscribe((resp) => { + const response = resp as Datatable | TextBasedErrorResponse; + if (response.type === 'error') { + error = response.error.message; + } else { + const table = response as Datatable; + const rows = table?.rows ?? []; + textBasedQueryColumns = table?.columns ?? undefined; + textBasedHeaderWarning = table.warning ?? undefined; + finalData = rows.map( + (row: Record, idx: number) => + ({ + id: String(idx), + raw: row, + flattened: row, + } as unknown as DataTableRecord) + ); + } + }); + return lastValueFrom(execution).then(() => { + if (error) { + throw new Error(error); + } else { + return { + records: finalData || [], + textBasedQueryColumns, + textBasedHeaderWarning, + }; + } + }); + } + return { + records: [] as DataTableRecord[], + textBasedQueryColumns: [], + textBasedHeaderWarning: undefined, + }; + }) + .catch((err) => { + throw new Error(err.message); + }); +} diff --git a/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.test.ts b/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.test.ts index 0b81061ab68ff..0dfbd84224f4d 100644 --- a/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.test.ts @@ -20,13 +20,13 @@ describe('getDataViewByTextBasedQueryLang', () => { }); const services = discoverServiceMock; it('returns the current dataview if is adhoc and query has not changed', async () => { - const query = { sql: 'Select * from data-view-ad-hoc-title' }; + const query = { esql: 'from data-view-ad-hoc-title' }; const dataView = await getDataViewByTextBasedQueryLang(query, dataViewAdHoc, services); expect(dataView).toStrictEqual(dataViewAdHoc); }); it('creates an adhoc dataview if the current dataview is persistent and query has not changed', async () => { - const query = { sql: 'Select * from the-data-view-title' }; + const query = { esql: 'from the-data-view-title' }; const dataView = await getDataViewByTextBasedQueryLang(query, dataViewMock, services); expect(dataView.isPersisted()).toEqual(false); expect(dataView.timeFieldName).toBe('@timestamp'); @@ -40,7 +40,7 @@ describe('getDataViewByTextBasedQueryLang', () => { title: 'test-1', timeFieldName: undefined, }); - const query = { sql: 'Select * from the-data-view-title' }; + const query = { esql: 'from the-data-view-title' }; const dataView = await getDataViewByTextBasedQueryLang(query, dataViewAdHoc, services); expect(dataView.isPersisted()).toEqual(false); expect(dataView.timeFieldName).toBeUndefined(); diff --git a/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.ts b/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.ts index 3b38b95dfceb1..09ac5f1e87686 100644 --- a/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.ts +++ b/src/plugins/discover/public/application/main/utils/get_data_view_by_text_based_query_lang.ts @@ -5,7 +5,11 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { AggregateQuery, getIndexPatternFromSQLQuery } from '@kbn/es-query'; +import { + AggregateQuery, + getIndexPatternFromSQLQuery, + getIndexPatternFromESQLQuery, +} from '@kbn/es-query'; import { DataView } from '@kbn/data-views-plugin/common'; import { DiscoverServices } from '../../../build_services'; @@ -14,9 +18,16 @@ export async function getDataViewByTextBasedQueryLang( currentDataView: DataView | undefined, services: DiscoverServices ) { - const text = 'sql' in query ? query.sql : undefined; + let indexPatternFromQuery = ''; + if ('sql' in query) { + indexPatternFromQuery = getIndexPatternFromSQLQuery(query.sql); + } + if ('esql' in query) { + indexPatternFromQuery = getIndexPatternFromESQLQuery(query.esql); + } + // we should find a better way to work with ESQL queries which dont need a dataview + if (!indexPatternFromQuery && currentDataView) return currentDataView; - const indexPatternFromQuery = getIndexPatternFromSQLQuery(text); if ( currentDataView?.isPersisted() || indexPatternFromQuery !== currentDataView?.getIndexPattern() diff --git a/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts b/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts index 146a5a80a125f..781cfef1387a9 100644 --- a/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts @@ -15,8 +15,8 @@ describe('getRawRecordType', () => { expect(mode).toEqual(RecordRawType.DOCUMENT); }); - it('returns sql for Query type query', () => { - const mode = getRawRecordType({ sql: 'SELECT * from foo' }); + it('returns esql for Query type query', () => { + const mode = getRawRecordType({ esql: 'from foo' }); expect(mode).toEqual(RecordRawType.PLAIN); }); diff --git a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts index 103520c71998b..19e9f6a64c88b 100644 --- a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts @@ -10,7 +10,7 @@ import { getStateDefaults } from './get_state_defaults'; import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import { VIEW_MODE } from '@kbn/saved-search-plugin/common'; import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; -import { savedSearchMock, savedSearchMockWithSQL } from '../../../__mocks__/saved_search'; +import { savedSearchMock, savedSearchMockWithESQL } from '../../../__mocks__/saved_search'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { discoverServiceMock } from '../../../__mocks__/services'; @@ -81,7 +81,7 @@ describe('getStateDefaults', () => { const actualForUndefinedViewMode = getStateDefaults({ services: discoverServiceMock, savedSearch: { - ...savedSearchMockWithSQL, + ...savedSearchMockWithESQL, viewMode: undefined, }, }); @@ -90,7 +90,7 @@ describe('getStateDefaults', () => { const actualForTextBasedWithInvalidViewMode = getStateDefaults({ services: discoverServiceMock, savedSearch: { - ...savedSearchMockWithSQL, + ...savedSearchMockWithESQL, viewMode: VIEW_MODE.AGGREGATED_LEVEL, }, }); @@ -99,7 +99,7 @@ describe('getStateDefaults', () => { const actualForTextBasedWithValidViewMode = getStateDefaults({ services: discoverServiceMock, savedSearch: { - ...savedSearchMockWithSQL, + ...savedSearchMockWithESQL, viewMode: VIEW_MODE.DOCUMENT_LEVEL, }, }); diff --git a/src/plugins/discover/public/application/main/utils/is_text_based_query.test.ts b/src/plugins/discover/public/application/main/utils/is_text_based_query.test.ts index 53e85216ba0bf..78f7b24d3c10a 100644 --- a/src/plugins/discover/public/application/main/utils/is_text_based_query.test.ts +++ b/src/plugins/discover/public/application/main/utils/is_text_based_query.test.ts @@ -12,6 +12,7 @@ describe('isTextBasedQuery', () => { it('should work correctly', () => { expect(isTextBasedQuery({ query: '', language: 'lucene' })).toEqual(false); expect(isTextBasedQuery({ sql: 'SELECT * from foo' })).toEqual(true); + expect(isTextBasedQuery({ esql: 'from foo' })).toEqual(true); expect(isTextBasedQuery()).toEqual(false); }); }); diff --git a/src/plugins/discover/public/application/types.ts b/src/plugins/discover/public/application/types.ts index 57677236cbf7a..3fc375a5ebcb7 100644 --- a/src/plugins/discover/public/application/types.ts +++ b/src/plugins/discover/public/application/types.ts @@ -6,6 +6,10 @@ * Side Public License, v 1. */ +import type { DatatableColumn } from '@kbn/expressions-plugin/common'; +import type { DataTableRecord } from '@kbn/discover-utils/types'; +import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings'; + export enum FetchStatus { UNINITIALIZED = 'uninitialized', LOADING = 'loading', @@ -16,3 +20,10 @@ export enum FetchStatus { } export type DiscoverDisplayMode = 'embedded' | 'standalone'; + +export interface RecordsFetchResponse { + records: DataTableRecord[]; + textBasedQueryColumns?: DatatableColumn[]; + textBasedHeaderWarning?: string; + interceptedWarnings?: SearchResponseInterceptedWarning[]; +} diff --git a/src/plugins/discover/public/application/view_alert/view_alert_route.tsx b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx index 48b7144b9115d..5d8383be5e935 100644 --- a/src/plugins/discover/public/application/view_alert/view_alert_route.tsx +++ b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx @@ -20,7 +20,7 @@ const isActualAlert = (queryParams: QueryParams): queryParams is NonNullableEntr }; export function ViewAlertRoute() { - const { core, data, locator, toastNotifications } = useDiscoverServices(); + const { core, data, locator, toastNotifications, dataViews } = useDiscoverServices(); const { id } = useParams<{ id: string }>(); const history = useHistory(); const { search } = useLocation(); @@ -46,7 +46,8 @@ export function ViewAlertRoute() { queryParams, toastNotifications, core, - data + data, + dataViews ); const navigateWithDiscoverState = (state: DiscoverAppLocatorParams) => { @@ -63,7 +64,17 @@ export function ViewAlertRoute() { .then(buildLocatorParams) .then(navigateWithDiscoverState) .catch(navigateToDiscoverRoot); - }, [core, data, history, id, locator, openActualAlert, queryParams, toastNotifications]); + }, [ + core, + data, + dataViews, + history, + id, + locator, + openActualAlert, + queryParams, + toastNotifications, + ]); return null; } diff --git a/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx b/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx index d8a7bfeae9b34..cc51f11885ea4 100644 --- a/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx +++ b/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx @@ -8,8 +8,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { getIndexPatternFromESQLQuery, type AggregateQuery } from '@kbn/es-query'; import { CoreStart, ToastsStart } from '@kbn/core/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; +import type { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { Rule } from '@kbn/alerting-plugin/common'; import type { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { ISearchSource, SerializedSearchSourceFields, getTime } from '@kbn/data-plugin/common'; @@ -21,6 +22,8 @@ import { DiscoverAppLocatorParams } from '../../../common/locator'; export interface SearchThresholdAlertParams extends RuleTypeParams { searchConfiguration: SerializedSearchSourceFields; + esqlQuery?: AggregateQuery; + timeField?: string; } export interface QueryParams { @@ -50,7 +53,8 @@ export const getAlertUtils = ( queryParams: QueryParams, toastNotifications: ToastsStart, core: CoreStart, - data: DataPublicPluginStart + data: DataPublicPluginStart, + dataViews: DataViewsPublicPluginStart ) => { const showDataViewFetchError = (alertId: string) => { const errorTitle = i18n.translate('discover.viewAlert.dataViewErrorTitle', { @@ -111,14 +115,31 @@ export const getAlertUtils = ( } }; - const buildLocatorParams = ({ + const buildLocatorParams = async ({ alert, searchSource, }: { alert: Rule; searchSource: ISearchSource; - }): DiscoverAppLocatorParams => { - const dataView = searchSource.getField('index'); + }): Promise => { + let dataView = searchSource.getField('index'); + let query = searchSource.getField('query') || data.query.queryString.getDefaultQuery(); + + // Dataview and query for ES|QL alerts + if ( + alert.params && + 'esqlQuery' in alert.params && + alert.params.esqlQuery && + 'esql' in alert.params.esqlQuery + ) { + query = alert.params.esqlQuery; + const indexPattern: string = getIndexPatternFromESQLQuery(alert.params.esqlQuery.esql); + dataView = await dataViews.create({ + title: indexPattern, + timeFieldName: alert.params.timeField, + }); + } + const timeFieldName = dataView?.timeFieldName; // data view fetch error if (!dataView || !timeFieldName) { @@ -131,7 +152,7 @@ export const getAlertUtils = ( : buildTimeRangeFilter(dataView, alert, timeFieldName); return { - query: searchSource.getField('query') || data.query.queryString.getDefaultQuery(), + query, dataViewSpec: dataView.toSpec(false), timeRange, filters: searchSource.getField('filter') as Filter[], diff --git a/src/plugins/discover/public/components/common/error_callout.tsx b/src/plugins/discover/public/components/common/error_callout.tsx index d0e914a81e851..84b5b979c0249 100644 --- a/src/plugins/discover/public/components/common/error_callout.tsx +++ b/src/plugins/discover/public/components/common/error_callout.tsx @@ -41,7 +41,7 @@ export const ErrorCallout = ({ const { euiTheme } = useEuiTheme(); const showErrorMessage = i18n.translate('discover.errorCalloutShowErrorMessage', { - defaultMessage: 'Show details', + defaultMessage: 'View details', }); const overrideDisplay = getSearchErrorOverrideDisplay({ diff --git a/src/plugins/discover/public/components/discover_container/discover_container.tsx b/src/plugins/discover/public/components/discover_container/discover_container.tsx index 28a596946bd18..8c879fdfef7bc 100644 --- a/src/plugins/discover/public/components/discover_container/discover_container.tsx +++ b/src/plugins/discover/public/components/discover_container/discover_container.tsx @@ -82,7 +82,11 @@ export const DiscoverContainerInternal = ({ css={discoverContainerWrapperCss} data-test-subj="discover-container-internal-wrapper" > - + []); -jest.mock('@kbn/cell-actions', () => ({ - ...jest.requireActual('@kbn/cell-actions'), - useDataGridColumnsCellActions: (prop: unknown) => mockUseDataGridColumnsCellActions(prop), -})); - -export const dataViewMock = buildDataViewMock({ - name: 'the-data-view', - fields: deepMockedFields, - timeFieldName: '@timestamp', -}); - -function getProps() { - const services = discoverServiceMock; - services.dataViewFieldEditor.userPermissions.editIndexPattern = jest.fn().mockReturnValue(true); - - return { - ariaLabelledBy: '', - columns: [], - dataView: dataViewMock, - loadingState: DataLoadingState.loaded, - expandedDoc: undefined, - onAddColumn: jest.fn(), - onFilter: jest.fn(), - onRemoveColumn: jest.fn(), - onResize: jest.fn(), - onSetColumns: jest.fn(), - onSort: jest.fn(), - rows: esHitsMock.map((hit) => buildDataTableRecord(hit, dataViewMock)), - sampleSize: 30, - searchDescription: '', - searchTitle: '', - setExpandedDoc: jest.fn(), - settings: {}, - showTimeCol: true, - sort: [], - useNewFieldsApi: true, - services, - }; -} - -async function getComponent(props: DiscoverGridProps = getProps()) { - const Proxy = (innerProps: DiscoverGridProps) => ( - - - - ); - - const component = mountWithIntl(); - await act(async () => { - // needed by cell actions to complete async loading - component.update(); - }); - return component; -} - -function getSelectedDocNr(component: ReactWrapper) { - const gridSelectionBtn = findTestSubject(component, 'dscGridSelectionBtn'); - if (!gridSelectionBtn.length) { - return 0; - } - const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-selected-documents'); - return Number(selectedNr); -} - -function getDisplayedDocNr(component: ReactWrapper) { - const gridSelectionBtn = findTestSubject(component, 'discoverDocTable'); - if (!gridSelectionBtn.length) { - return 0; - } - const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-document-number'); - return Number(selectedNr); -} - -async function toggleDocSelection( - component: ReactWrapper, - document: EsHitRecord -) { - act(() => { - const docId = getDocId(document); - findTestSubject(component, `dscGridSelectDoc-${docId}`).simulate('change'); - }); - component.update(); -} - -describe('DiscoverGrid', () => { - afterEach(async () => { - jest.clearAllMocks(); - }); - - describe('Document selection', () => { - let component: ReactWrapper; - beforeEach(async () => { - component = await getComponent(); - }); - - test('no documents are selected initially', async () => { - expect(getSelectedDocNr(component)).toBe(0); - expect(getDisplayedDocNr(component)).toBe(5); - }); - - test('Allows selection/deselection of multiple documents', async () => { - await toggleDocSelection(component, esHitsMock[0]); - expect(getSelectedDocNr(component)).toBe(1); - await toggleDocSelection(component, esHitsMock[1]); - expect(getSelectedDocNr(component)).toBe(2); - await toggleDocSelection(component, esHitsMock[1]); - expect(getSelectedDocNr(component)).toBe(1); - }); - - test('deselection of all selected documents', async () => { - await toggleDocSelection(component, esHitsMock[0]); - await toggleDocSelection(component, esHitsMock[1]); - expect(getSelectedDocNr(component)).toBe(2); - findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); - findTestSubject(component, 'dscGridClearSelectedDocuments').simulate('click'); - expect(getSelectedDocNr(component)).toBe(0); - }); - - test('showing only selected documents and undo selection', async () => { - await toggleDocSelection(component, esHitsMock[0]); - await toggleDocSelection(component, esHitsMock[1]); - expect(getSelectedDocNr(component)).toBe(2); - findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); - findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); - expect(getDisplayedDocNr(component)).toBe(2); - findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); - component.update(); - findTestSubject(component, 'dscGridShowAllDocuments').simulate('click'); - expect(getDisplayedDocNr(component)).toBe(5); - }); - - test('showing selected documents, underlying data changes, all documents are displayed, selection is gone', async () => { - await toggleDocSelection(component, esHitsMock[0]); - await toggleDocSelection(component, esHitsMock[1]); - expect(getSelectedDocNr(component)).toBe(2); - findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); - findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); - expect(getDisplayedDocNr(component)).toBe(2); - component.setProps({ - rows: [ - { - _index: 'i', - _id: '6', - _score: 1, - _source: { - date: '2020-20-02T12:12:12.128', - name: 'test6', - extension: 'doc', - bytes: 50, - }, - }, - ].map((row) => buildDataTableRecord(row, dataViewMock)), - }); - expect(getDisplayedDocNr(component)).toBe(1); - expect(getSelectedDocNr(component)).toBe(0); - }); - - test('showing only selected documents and remove filter deselecting each doc manually', async () => { - await toggleDocSelection(component, esHitsMock[0]); - findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); - findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); - expect(getDisplayedDocNr(component)).toBe(1); - await toggleDocSelection(component, esHitsMock[0]); - expect(getDisplayedDocNr(component)).toBe(5); - await toggleDocSelection(component, esHitsMock[0]); - expect(getDisplayedDocNr(component)).toBe(5); - }); - - test('copying selected documents to clipboard', async () => { - await toggleDocSelection(component, esHitsMock[0]); - findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); - expect(component.find(EuiCopy).prop('textToCopy')).toMatchInlineSnapshot( - `"[{\\"_index\\":\\"i\\",\\"_id\\":\\"1\\",\\"_score\\":1,\\"_type\\":\\"_doc\\",\\"_source\\":{\\"date\\":\\"2020-20-01T12:12:12.123\\",\\"message\\":\\"test1\\",\\"bytes\\":20}}]"` - ); - }); - }); - - describe('edit field button', () => { - it('should render the edit field button if onFieldEdited is provided', async () => { - const component = await getComponent({ - ...getProps(), - columns: ['message'], - onFieldEdited: jest.fn(), - }); - expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( - false - ); - findTestSubject(component, 'dataGridHeaderCell-message').find('button').simulate('click'); - expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( - true - ); - expect(findTestSubject(component, 'gridEditFieldButton').exists()).toBe(true); - }); - - it('should not render the edit field button if onFieldEdited is not provided', async () => { - const component = await getComponent({ - ...getProps(), - columns: ['message'], - }); - expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( - false - ); - findTestSubject(component, 'dataGridHeaderCell-message').find('button').simulate('click'); - expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe( - true - ); - expect(findTestSubject(component, 'gridEditFieldButton').exists()).toBe(false); - }); - }); - - describe('cellActionsTriggerId', () => { - it('should call useDataGridColumnsCellActions with empty params when no cellActionsTriggerId is provided', async () => { - await getComponent({ - ...getProps(), - columns: ['message'], - onFieldEdited: jest.fn(), - }); - expect(mockUseDataGridColumnsCellActions).toHaveBeenCalledWith( - expect.objectContaining({ - triggerId: undefined, - getCellValue: expect.any(Function), - fields: undefined, - }) - ); - }); - - it('should call useDataGridColumnsCellActions properly when cellActionsTriggerId defined', async () => { - await getComponent({ - ...getProps(), - columns: ['message'], - onFieldEdited: jest.fn(), - cellActionsTriggerId: 'test', - }); - expect(mockUseDataGridColumnsCellActions).toHaveBeenCalledWith( - expect.objectContaining({ - triggerId: 'test', - getCellValue: expect.any(Function), - fields: [ - dataViewMock.getFieldByName('@timestamp')?.toSpec(), - dataViewMock.getFieldByName('message')?.toSpec(), - ], - }) - ); - }); - }); - - describe('sorting', () => { - it('should enable in memory sorting with plain records', async () => { - const component = await getComponent({ - ...getProps(), - columns: ['message'], - isPlainRecord: true, - }); - - expect( - ( - findTestSubject(component, 'docTable') - .find('EuiDataGridInMemoryRenderer') - .first() - .props() as Record - ).inMemory - ).toMatchInlineSnapshot(` - Object { - "level": "sorting", - } - `); - }); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx deleted file mode 100644 index 662c5eb670d49..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ /dev/null @@ -1,753 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useCallback, useMemo, useState, useRef, useEffect } from 'react'; -import classnames from 'classnames'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { of } from 'rxjs'; -import useObservable from 'react-use/lib/useObservable'; -import './discover_grid.scss'; -import { - EuiDataGridSorting, - EuiDataGrid, - EuiScreenReaderOnly, - EuiSpacer, - EuiText, - htmlIdGenerator, - EuiLoadingSpinner, - EuiIcon, - EuiDataGridRefProps, - EuiDataGridInMemory, -} from '@elastic/eui'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import type { SortOrder } from '@kbn/saved-search-plugin/public'; -import { - useDataGridColumnsCellActions, - type UseDataGridColumnsCellActionsProps, -} from '@kbn/cell-actions'; -import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import type { ToastsStart, IUiSettingsClient, HttpStart, CoreStart } from '@kbn/core/public'; -import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; -import { Serializable } from '@kbn/utility-types'; -import type { DataTableRecord } from '@kbn/discover-utils/types'; -import { getShouldShowFieldHandler } from '@kbn/discover-utils'; -import { - DOC_HIDE_TIME_COLUMN_SETTING, - MAX_DOC_FIELDS_DISPLAYED, - SHOW_MULTIFIELDS, -} from '@kbn/discover-utils'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; -import { getSchemaDetectors } from './discover_grid_schema'; -import { DiscoverGridFlyout } from './discover_grid_flyout'; -import { DiscoverGridContext } from './discover_grid_context'; -import { getRenderCellValueFn } from './get_render_cell_value'; -import { DiscoverGridSettings } from './types'; -import { - getEuiGridColumns, - getLeadControlColumns, - getVisibleColumns, -} from './discover_grid_columns'; -import { GRID_STYLE, toolbarVisibility as toolbarVisibilityDefaults } from './constants'; -import { getDisplayedColumns } from '../../utils/columns'; -import { DiscoverGridDocumentToolbarBtn } from './discover_grid_document_selection'; -import { DiscoverGridFooter } from './discover_grid_footer'; -import type { ValueToStringConverter } from '../../types'; -import { useRowHeightsOptions } from '../../hooks/use_row_heights_options'; -import { convertValueToString } from '../../utils/convert_value_to_string'; -import { getRowsPerPageOptions, getDefaultRowsPerPage } from '../../utils/rows_per_page'; - -export enum DataLoadingState { - loading = 'loading', - loadingMore = 'loadingMore', - loaded = 'loaded', -} - -const themeDefault = { darkMode: false }; - -interface SortObj { - id: string; - direction: string; -} - -export interface DiscoverGridProps { - /** - * Determines which element labels the grid for ARIA - */ - ariaLabelledBy: string; - /** - * Optional class name to apply - */ - className?: string; - /** - * Determines which columns are displayed - */ - columns: string[]; - /** - * If set, the given document is displayed in a flyout - */ - expandedDoc?: DataTableRecord; - /** - * The used data view - */ - dataView: DataView; - /** - * Determines if data is currently loaded - */ - loadingState: DataLoadingState; - /** - * Function used to add a column in the document flyout - */ - onAddColumn: (column: string) => void; - /** - * Function to add a filter in the grid cell or document flyout - */ - onFilter: DocViewFilterFn; - /** - * Function used in the grid header and flyout to remove a column - * @param column - */ - onRemoveColumn: (column: string) => void; - /** - * Function triggered when a column is resized by the user - */ - onResize?: (colSettings: { columnId: string; width: number }) => void; - /** - * Function to set all columns - */ - onSetColumns: (columns: string[], hideTimeColumn: boolean) => void; - /** - * function to change sorting of the documents, skipped when isSortEnabled is set to false - */ - onSort?: (sort: string[][]) => void; - /** - * Array of documents provided by Elasticsearch - */ - rows?: DataTableRecord[]; - /** - * The max size of the documents returned by Elasticsearch - */ - sampleSize: number; - /** - * Function to set the expanded document, which is displayed in a flyout - */ - setExpandedDoc?: (doc?: DataTableRecord) => void; - /** - * Grid display settings persisted in Elasticsearch (e.g. column width) - */ - settings?: DiscoverGridSettings; - /** - * Saved search description - */ - searchDescription?: string; - /** - * Saved search title - */ - searchTitle?: string; - /** - * Determines whether the time columns should be displayed (legacy settings) - */ - showTimeCol: boolean; - /** - * Determines whether the full screen button should be displayed - */ - showFullScreenButton?: boolean; - /** - * Manage user sorting control - */ - isSortEnabled?: boolean; - /** - * Current sort setting - */ - sort: SortOrder[]; - /** - * How the data is fetched - */ - useNewFieldsApi: boolean; - /** - * Manage pagination control - */ - isPaginationEnabled?: boolean; - /** - * List of used control columns (available: 'openDetails', 'select') - */ - controlColumnIds?: string[]; - /** - * Row height from state - */ - rowHeightState?: number; - /** - * Update row height state - */ - onUpdateRowHeight?: (rowHeight: number) => void; - /** - * Is text base lang mode enabled - */ - isPlainRecord?: boolean; - /** - * Current state value for rowsPerPage - */ - rowsPerPageState?: number; - /** - * Update rows per page state - */ - onUpdateRowsPerPage?: (rowsPerPage: number) => void; - /** - * Callback to execute on edit runtime field - */ - onFieldEdited?: () => void; - /** - * Filters applied by saved search embeddable - */ - filters?: Filter[]; - /** - * Query applied by KQL bar or text based editor - */ - query?: Query | AggregateQuery; - /** - * Saved search id used for links to single doc and surrounding docs in the flyout - */ - savedSearchId?: string; - /** - * Document detail view component - */ - DocumentView?: typeof DiscoverGridFlyout; - /** - * Optional triggerId to retrieve the column cell actions that will override the default ones - */ - cellActionsTriggerId?: string; - /** - * Service dependencies - */ - services: { - core: CoreStart; - fieldFormats: FieldFormatsStart; - addBasePath: HttpStart['basePath']['prepend']; - uiSettings: IUiSettingsClient; - dataViewFieldEditor: DataViewFieldEditorStart; - toastNotifications: ToastsStart; - }; - /** - * Number total hits from ES - */ - totalHits?: number; - /** - * To fetch more - */ - onFetchMoreRecords?: () => void; -} - -export const EuiDataGridMemoized = React.memo(EuiDataGrid); - -const CONTROL_COLUMN_IDS_DEFAULT = ['openDetails', 'select']; - -export const DiscoverGrid = ({ - ariaLabelledBy, - columns, - dataView, - loadingState, - expandedDoc, - onAddColumn, - filters, - query, - savedSearchId, - onFilter, - onRemoveColumn, - onResize, - onSetColumns, - onSort, - rows, - sampleSize, - searchDescription, - searchTitle, - setExpandedDoc, - settings, - showTimeCol, - showFullScreenButton = true, - sort, - useNewFieldsApi, - isSortEnabled = true, - isPaginationEnabled = true, - controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, - cellActionsTriggerId, - className, - rowHeightState, - onUpdateRowHeight, - isPlainRecord = false, - rowsPerPageState, - onUpdateRowsPerPage, - onFieldEdited, - DocumentView, - services, - totalHits, - onFetchMoreRecords, -}: DiscoverGridProps) => { - const { fieldFormats, toastNotifications, dataViewFieldEditor, uiSettings } = services; - const { darkMode } = useObservable(services.core.theme?.theme$ ?? of(themeDefault), themeDefault); - const dataGridRef = useRef(null); - const [selectedDocs, setSelectedDocs] = useState([]); - const [isFilterActive, setIsFilterActive] = useState(false); - const displayedColumns = getDisplayedColumns(columns, dataView); - const defaultColumns = displayedColumns.includes('_source'); - const usedSelectedDocs = useMemo(() => { - if (!selectedDocs.length || !rows?.length) { - return []; - } - const idMap = rows.reduce((map, row) => map.set(row.id, true), new Map()); - // filter out selected docs that are no longer part of the current data - const result = selectedDocs.filter((docId) => idMap.get(docId)); - if (result.length === 0 && isFilterActive) { - setIsFilterActive(false); - } - return result; - }, [selectedDocs, rows, isFilterActive]); - - const displayedRows = useMemo(() => { - if (!rows) { - return []; - } - if (!isFilterActive || usedSelectedDocs.length === 0) { - return rows; - } - const rowsFiltered = rows.filter((row) => usedSelectedDocs.includes(row.id)); - if (!rowsFiltered.length) { - // in case the selected docs are no longer part of the sample of 500, show all docs - return rows; - } - return rowsFiltered; - }, [rows, usedSelectedDocs, isFilterActive]); - - const valueToStringConverter: ValueToStringConverter = useCallback( - (rowIndex, columnId, options) => { - return convertValueToString({ - rowIndex, - rows: displayedRows, - dataView, - columnId, - fieldFormats, - options, - }); - }, - [displayedRows, dataView, fieldFormats] - ); - - /** - * Pagination - */ - const defaultRowsPerPage = useMemo( - () => getDefaultRowsPerPage(services.uiSettings), - [services.uiSettings] - ); - const currentPageSize = - typeof rowsPerPageState === 'number' && rowsPerPageState > 0 - ? rowsPerPageState - : defaultRowsPerPage; - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: currentPageSize, - }); - const rowCount = useMemo(() => (displayedRows ? displayedRows.length : 0), [displayedRows]); - const pageCount = useMemo( - () => Math.ceil(rowCount / pagination.pageSize), - [rowCount, pagination] - ); - - const paginationObj = useMemo(() => { - const onChangeItemsPerPage = (pageSize: number) => { - onUpdateRowsPerPage?.(pageSize); - }; - - const onChangePage = (pageIndex: number) => - setPagination((paginationData) => ({ ...paginationData, pageIndex })); - - return isPaginationEnabled - ? { - onChangeItemsPerPage, - onChangePage, - pageIndex: pagination.pageIndex > pageCount - 1 ? 0 : pagination.pageIndex, - pageSize: pagination.pageSize, - pageSizeOptions: getRowsPerPageOptions(pagination.pageSize), - } - : undefined; - }, [pagination, pageCount, isPaginationEnabled, onUpdateRowsPerPage]); - - useEffect(() => { - setPagination((paginationData) => - paginationData.pageSize === currentPageSize - ? paginationData - : { ...paginationData, pageSize: currentPageSize } - ); - }, [currentPageSize, setPagination]); - - /** - * Sorting - */ - const sortingColumns = useMemo(() => sort.map(([id, direction]) => ({ id, direction })), [sort]); - - const [inmemorySortingColumns, setInmemorySortingColumns] = useState([]); - const onTableSort = useCallback( - (sortingColumnsData) => { - if (isSortEnabled) { - if (isPlainRecord) { - setInmemorySortingColumns(sortingColumnsData); - } else if (onSort) { - onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction])); - } - } - }, - [onSort, isSortEnabled, isPlainRecord, setInmemorySortingColumns] - ); - - const showMultiFields = services.uiSettings.get(SHOW_MULTIFIELDS); - - const shouldShowFieldHandler = useMemo(() => { - const dataViewFields = dataView.fields.getAll().map((fld) => fld.name); - return getShouldShowFieldHandler(dataViewFields, dataView, showMultiFields); - }, [dataView, showMultiFields]); - - /** - * Cell rendering - */ - const renderCellValue = useMemo( - () => - getRenderCellValueFn( - dataView, - displayedRows, - useNewFieldsApi, - shouldShowFieldHandler, - services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED), - () => dataGridRef.current?.closeCellPopover() - ), - [dataView, displayedRows, useNewFieldsApi, shouldShowFieldHandler, services.uiSettings] - ); - - /** - * Render variables - */ - const randomId = useMemo(() => htmlIdGenerator()(), []); - const closeFieldEditor = useRef<() => void | undefined>(); - - useEffect(() => { - return () => { - if (closeFieldEditor?.current) { - closeFieldEditor?.current(); - } - }; - }, []); - - const editField = useMemo( - () => - onFieldEdited - ? (fieldName: string) => { - closeFieldEditor.current = services.dataViewFieldEditor.openEditor({ - ctx: { - dataView, - }, - fieldName, - onSave: async () => { - await onFieldEdited(); - }, - }); - } - : undefined, - [dataView, onFieldEdited, services.dataViewFieldEditor] - ); - - const visibleColumns = useMemo( - () => getVisibleColumns(displayedColumns, dataView, showTimeCol) as string[], - [dataView, displayedColumns, showTimeCol] - ); - - const getCellValue = useCallback( - (fieldName, rowIndex) => - displayedRows[rowIndex % displayedRows.length].flattened[fieldName] as Serializable, - [displayedRows] - ); - - const cellActionsFields = useMemo( - () => - cellActionsTriggerId && !isPlainRecord - ? visibleColumns.map( - (columnName) => - dataView.getFieldByName(columnName)?.toSpec() ?? { - name: '', - type: '', - aggregatable: false, - searchable: false, - } - ) - : undefined, - [cellActionsTriggerId, isPlainRecord, visibleColumns, dataView] - ); - - const columnsCellActions = useDataGridColumnsCellActions({ - fields: cellActionsFields, - getCellValue, - triggerId: cellActionsTriggerId, - dataGridRef, - }); - - const euiGridColumns = useMemo( - () => - getEuiGridColumns({ - columns: visibleColumns, - columnsCellActions, - rowsCount: displayedRows.length, - settings, - dataView, - defaultColumns, - isSortEnabled, - isPlainRecord, - services: { - uiSettings, - toastNotifications, - }, - hasEditDataViewPermission: () => dataViewFieldEditor.userPermissions.editIndexPattern(), - valueToStringConverter, - onFilter, - editField, - }), - [ - onFilter, - visibleColumns, - columnsCellActions, - displayedRows, - dataView, - settings, - defaultColumns, - isSortEnabled, - isPlainRecord, - uiSettings, - toastNotifications, - dataViewFieldEditor, - valueToStringConverter, - editField, - ] - ); - - const hideTimeColumn = useMemo( - () => services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), - [services.uiSettings] - ); - const schemaDetectors = useMemo(() => getSchemaDetectors(), []); - const columnsVisibility = useMemo( - () => ({ - visibleColumns, - setVisibleColumns: (newColumns: string[]) => { - onSetColumns(newColumns, hideTimeColumn); - }, - }), - [visibleColumns, hideTimeColumn, onSetColumns] - ); - const sorting = useMemo(() => { - if (isSortEnabled) { - return { - columns: isPlainRecord ? inmemorySortingColumns : sortingColumns, - onSort: onTableSort, - }; - } - return { columns: sortingColumns, onSort: () => {} }; - }, [isSortEnabled, sortingColumns, isPlainRecord, inmemorySortingColumns, onTableSort]); - - const canSetExpandedDoc = Boolean(setExpandedDoc && DocumentView); - - const lead = useMemo( - () => - getLeadControlColumns(canSetExpandedDoc).filter(({ id }) => controlColumnIds.includes(id)), - [controlColumnIds, canSetExpandedDoc] - ); - - const additionalControls = useMemo( - () => - usedSelectedDocs.length ? ( - - ) : null, - [usedSelectedDocs, isFilterActive, rows, setIsFilterActive] - ); - - const showDisplaySelector = useMemo( - () => - !!onUpdateRowHeight - ? { - allowDensity: false, - allowRowHeight: true, - } - : undefined, - [onUpdateRowHeight] - ); - - const inMemory = useMemo(() => { - return isPlainRecord ? ({ level: 'sorting' } as EuiDataGridInMemory) : undefined; - }, [isPlainRecord]); - - const toolbarVisibility = useMemo( - () => - defaultColumns - ? { - ...toolbarVisibilityDefaults, - showColumnSelector: false, - showSortSelector: isSortEnabled, - additionalControls, - showDisplaySelector, - showFullScreenSelector: showFullScreenButton, - } - : { - ...toolbarVisibilityDefaults, - showSortSelector: isSortEnabled, - additionalControls, - showDisplaySelector, - showFullScreenSelector: showFullScreenButton, - }, - [defaultColumns, isSortEnabled, additionalControls, showDisplaySelector, showFullScreenButton] - ); - - const rowHeightsOptions = useRowHeightsOptions({ - rowHeightState, - onUpdateRowHeight, - }); - - const isRenderComplete = loadingState !== DataLoadingState.loading; - - if (!rowCount && loadingState === DataLoadingState.loading) { - return ( -
    - - - - - -
    - ); - } - - if (!rowCount) { - return ( -
    - - - - - -
    - ); - } - - return ( - { - setSelectedDocs(newSelectedDocs); - if (isFilterActive && newSelectedDocs.length === 0) { - setIsFilterActive(false); - } - }, - valueToStringConverter, - }} - > - -
    - -
    - {loadingState !== DataLoadingState.loading && - isPaginationEnabled && ( // we hide the footer for Surrounding Documents page - - )} - {searchTitle && ( - -

    - {searchDescription ? ( - - ) : ( - - )} -

    -
    - )} - {setExpandedDoc && expandedDoc && DocumentView && ( - setExpandedDoc(undefined)} - setExpandedDoc={setExpandedDoc} - query={query} - /> - )} -
    -
    - ); -}; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx deleted file mode 100644 index 079a26c265d27..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -const mockCopyToClipboard = jest.fn((value) => true); -jest.mock('@elastic/eui', () => { - const original = jest.requireActual('@elastic/eui'); - return { - ...original, - copyToClipboard: (value: string) => mockCopyToClipboard(value), - }; -}); - -jest.mock('../../hooks/use_discover_services', () => { - const services = { - toastNotifications: { - addInfo: jest.fn(), - }, - }; - const originalModule = jest.requireActual('../../hooks/use_discover_services'); - return { - ...originalModule, - useDiscoverServices: () => services, - }; -}); - -import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { FilterInBtn, FilterOutBtn, buildCellActions, CopyBtn } from './discover_grid_cell_actions'; -import { DiscoverGridContext } from './discover_grid_context'; -import { EuiButton } from '@elastic/eui'; -import { discoverGridContextMock } from '../../__mocks__/grid_context'; -import { DataViewField } from '@kbn/data-views-plugin/public'; - -describe('Discover cell actions ', function () { - it('should not show cell actions for unfilterable fields', async () => { - expect(buildCellActions({ name: 'foo', filterable: false } as DataViewField)).toEqual([ - CopyBtn, - ]); - }); - - it('should show filter actions for filterable fields', async () => { - expect(buildCellActions({ name: 'foo', filterable: true } as DataViewField, jest.fn())).toEqual( - [FilterInBtn, FilterOutBtn, CopyBtn] - ); - }); - - it('should show Copy action for _source field', async () => { - expect( - buildCellActions({ name: '_source', type: '_source', filterable: false } as DataViewField) - ).toEqual([CopyBtn]); - }); - - it('triggers filter function when FilterInBtn is clicked', async () => { - const component = mountWithIntl( - - } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - const button = findTestSubject(component, 'filterForButton'); - await button.simulate('click'); - expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith( - discoverGridContextMock.dataView.fields.getByName('extension'), - 'jpg', - '+' - ); - }); - it('triggers filter function when FilterInBtn is clicked for a non-provided value', async () => { - const component = mountWithIntl( - - } - rowIndex={0} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - const button = findTestSubject(component, 'filterForButton'); - await button.simulate('click'); - expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith( - discoverGridContextMock.dataView.fields.getByName('extension'), - undefined, - '+' - ); - }); - it('triggers filter function when FilterInBtn is clicked for an empty string value', async () => { - const component = mountWithIntl( - - } - rowIndex={4} - colIndex={1} - columnId="message" - isExpanded={false} - /> - - ); - const button = findTestSubject(component, 'filterForButton'); - await button.simulate('click'); - expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith( - discoverGridContextMock.dataView.fields.getByName('message'), - '', - '+' - ); - }); - it('triggers filter function when FilterOutBtn is clicked', async () => { - const component = mountWithIntl( - - } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - const button = findTestSubject(component, 'filterOutButton'); - await button.simulate('click'); - expect(discoverGridContextMock.onFilter).toHaveBeenCalledWith( - discoverGridContextMock.dataView.fields.getByName('extension'), - 'jpg', - '-' - ); - }); - it('triggers clipboard copy when CopyBtn is clicked', async () => { - const component = mountWithIntl( - - } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} - /> - - ); - const button = findTestSubject(component, 'copyClipboardButton'); - await button.simulate('click'); - expect(mockCopyToClipboard).toHaveBeenCalledWith('jpg'); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx deleted file mode 100644 index 32779230e1b7f..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useContext } from 'react'; -import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DataViewField } from '@kbn/data-views-plugin/public'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; -import { DiscoverGridContext, GridContext } from './discover_grid_context'; -import { useDiscoverServices } from '../../hooks/use_discover_services'; -import { copyValueToClipboard } from '../../utils/copy_value_to_clipboard'; - -function onFilterCell( - context: GridContext, - rowIndex: EuiDataGridColumnCellActionProps['rowIndex'], - columnId: EuiDataGridColumnCellActionProps['columnId'], - mode: '+' | '-' -) { - const row = context.rows[rowIndex]; - const value = row.flattened[columnId]; - const field = context.dataView.fields.getByName(columnId); - - if (field && context.onFilter) { - context.onFilter(field, value, mode); - } -} - -export const FilterInBtn = ({ - Component, - rowIndex, - columnId, -}: EuiDataGridColumnCellActionProps) => { - const context = useContext(DiscoverGridContext); - const buttonTitle = i18n.translate('discover.grid.filterForAria', { - defaultMessage: 'Filter for this {value}', - values: { value: columnId }, - }); - - return ( - { - onFilterCell(context, rowIndex, columnId, '+'); - }} - iconType="plusInCircle" - aria-label={buttonTitle} - title={buttonTitle} - data-test-subj="filterForButton" - > - {i18n.translate('discover.grid.filterFor', { - defaultMessage: 'Filter for', - })} - - ); -}; - -export const FilterOutBtn = ({ - Component, - rowIndex, - columnId, -}: EuiDataGridColumnCellActionProps) => { - const context = useContext(DiscoverGridContext); - const buttonTitle = i18n.translate('discover.grid.filterOutAria', { - defaultMessage: 'Filter out this {value}', - values: { value: columnId }, - }); - - return ( - { - onFilterCell(context, rowIndex, columnId, '-'); - }} - iconType="minusInCircle" - aria-label={buttonTitle} - title={buttonTitle} - data-test-subj="filterOutButton" - > - {i18n.translate('discover.grid.filterOut', { - defaultMessage: 'Filter out', - })} - - ); -}; - -export const CopyBtn = ({ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps) => { - const { valueToStringConverter } = useContext(DiscoverGridContext); - const { toastNotifications } = useDiscoverServices(); - - const buttonTitle = i18n.translate('discover.grid.copyClipboardButtonTitle', { - defaultMessage: 'Copy value of {column}', - values: { column: columnId }, - }); - - return ( - { - copyValueToClipboard({ - rowIndex, - columnId, - toastNotifications, - valueToStringConverter, - }); - }} - iconType="copyClipboard" - aria-label={buttonTitle} - title={buttonTitle} - data-test-subj="copyClipboardButton" - > - {i18n.translate('discover.grid.copyCellValueButton', { - defaultMessage: 'Copy value', - })} - - ); -}; - -export function buildCellActions(field: DataViewField, onFilter?: DocViewFilterFn) { - return [...(onFilter && field.filterable ? [FilterInBtn, FilterOutBtn] : []), CopyBtn]; -} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx deleted file mode 100644 index cea7f6c3505c9..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { getEuiGridColumns, getVisibleColumns } from './discover_grid_columns'; -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; -import { discoverGridContextMock } from '../../__mocks__/grid_context'; -import { discoverServiceMock } from '../../__mocks__/services'; - -const columns = ['extension', 'message']; -const columnsWithTimeCol = getVisibleColumns( - ['extension', 'message'], - dataViewWithTimefieldMock, - true -) as string[]; - -describe('Discover grid columns', function () { - describe('getEuiGridColumns', () => { - it('returns eui grid columns showing default columns', async () => { - const actual = getEuiGridColumns({ - columns, - settings: {}, - dataView: dataViewWithTimefieldMock, - defaultColumns: true, - isSortEnabled: true, - isPlainRecord: false, - valueToStringConverter: discoverGridContextMock.valueToStringConverter, - rowsCount: 100, - services: { - uiSettings: discoverServiceMock.uiSettings, - toastNotifications: discoverServiceMock.toastNotifications, - }, - hasEditDataViewPermission: () => - discoverServiceMock.dataViewFieldEditor.userPermissions.editIndexPattern(), - onFilter: () => {}, - }); - expect(actual).toMatchInlineSnapshot(` - Array [ - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": false, - "showMoveLeft": false, - "showMoveRight": false, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "displayAsText": "extension", - "id": "extension", - "isSortable": false, - "schema": "string", - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": false, - "showMoveLeft": false, - "showMoveRight": false, - }, - "cellActions": Array [ - [Function], - ], - "displayAsText": "message", - "id": "message", - "isSortable": false, - "schema": "string", - }, - ] - `); - }); - - it('returns eui grid columns with time column', async () => { - const actual = getEuiGridColumns({ - columns: columnsWithTimeCol, - settings: {}, - dataView: dataViewWithTimefieldMock, - defaultColumns: false, - isSortEnabled: true, - isPlainRecord: false, - valueToStringConverter: discoverGridContextMock.valueToStringConverter, - rowsCount: 100, - services: { - uiSettings: discoverServiceMock.uiSettings, - toastNotifications: discoverServiceMock.toastNotifications, - }, - hasEditDataViewPermission: () => - discoverServiceMock.dataViewFieldEditor.userPermissions.editIndexPattern(), - onFilter: () => {}, - }); - expect(actual).toMatchInlineSnapshot(` - Array [ - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": false, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "display":
    - - - timestamp - - - - -
    , - "displayAsText": "timestamp", - "id": "timestamp", - "initialWidth": 210, - "isSortable": true, - "schema": "datetime", - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": Object { - "iconType": "cross", - "label": "Remove column", - }, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "displayAsText": "extension", - "id": "extension", - "isSortable": false, - "schema": "string", - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": Object { - "iconType": "cross", - "label": "Remove column", - }, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - ], - "displayAsText": "message", - "id": "message", - "isSortable": false, - "schema": "string", - }, - ] - `); - }); - - it('returns eui grid with in memory sorting', async () => { - const actual = getEuiGridColumns({ - columns: columnsWithTimeCol, - settings: {}, - dataView: dataViewWithTimefieldMock, - defaultColumns: false, - isSortEnabled: true, - isPlainRecord: true, - valueToStringConverter: discoverGridContextMock.valueToStringConverter, - rowsCount: 100, - services: { - uiSettings: discoverServiceMock.uiSettings, - toastNotifications: discoverServiceMock.toastNotifications, - }, - hasEditDataViewPermission: () => - discoverServiceMock.dataViewFieldEditor.userPermissions.editIndexPattern(), - onFilter: () => {}, - }); - expect(actual).toMatchInlineSnapshot(` - Array [ - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": false, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "display":
    - - - timestamp - - - - -
    , - "displayAsText": "timestamp", - "id": "timestamp", - "initialWidth": 210, - "isSortable": true, - "schema": "datetime", - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": Object { - "iconType": "cross", - "label": "Remove column", - }, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "displayAsText": "extension", - "id": "extension", - "isSortable": true, - "schema": "string", - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": Object { - "iconType": "cross", - "label": "Remove column", - }, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - ], - "displayAsText": "message", - "id": "message", - "isSortable": true, - "schema": "string", - }, - ] - `); - }); - }); - - describe('getVisibleColumns', () => { - it('returns grid columns without time column when data view has no timestamp field', () => { - const actual = getVisibleColumns(['extension', 'message'], dataViewMock, true) as string[]; - expect(actual).toEqual(['extension', 'message']); - }); - - it('returns grid columns without time column when showTimeCol is falsy', () => { - const actual = getVisibleColumns( - ['extension', 'message'], - dataViewWithTimefieldMock, - false - ) as string[]; - expect(actual).toEqual(['extension', 'message']); - }); - - it('returns grid columns with time column when data view has timestamp field', () => { - const actual = getVisibleColumns( - ['extension', 'message'], - dataViewWithTimefieldMock, - true - ) as string[]; - expect(actual).toEqual(['timestamp', 'extension', 'message']); - }); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx deleted file mode 100644 index 8effacbf9427f..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { - type EuiDataGridColumn, - type EuiDataGridColumnCellAction, - EuiIcon, - EuiScreenReaderOnly, - EuiToolTip, -} from '@elastic/eui'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { ToastsStart, IUiSettingsClient } from '@kbn/core/public'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; -import { ExpandButton } from './discover_grid_expand_button'; -import { DiscoverGridSettings } from './types'; -import type { ValueToStringConverter } from '../../types'; -import { buildCellActions } from './discover_grid_cell_actions'; -import { getSchemaByKbnType } from './discover_grid_schema'; -import { SelectButton } from './discover_grid_document_selection'; -import { defaultTimeColumnWidth } from './constants'; -import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; -import { buildEditFieldButton } from './build_edit_field_button'; - -const openDetails = { - id: 'openDetails', - width: 24, - headerCellRender: () => ( - - - {i18n.translate('discover.controlColumnHeader', { - defaultMessage: 'Control column', - })} - - - ), - rowCellRender: ExpandButton, -}; - -const select = { - id: 'select', - width: 24, - rowCellRender: SelectButton, - headerCellRender: () => ( - - - {i18n.translate('discover.selectColumnHeader', { - defaultMessage: 'Select column', - })} - - - ), -}; - -export function getLeadControlColumns(canSetExpandedDoc: boolean) { - if (!canSetExpandedDoc) { - return [select]; - } - return [openDetails, select]; -} - -function buildEuiGridColumn({ - columnName, - columnWidth = 0, - dataView, - defaultColumns, - isSortEnabled, - isPlainRecord, - toastNotifications, - hasEditDataViewPermission, - valueToStringConverter, - rowsCount, - onFilter, - editField, - columnCellActions, -}: { - columnName: string; - columnWidth: number | undefined; - dataView: DataView; - defaultColumns: boolean; - isSortEnabled: boolean; - isPlainRecord?: boolean; - toastNotifications: ToastsStart; - hasEditDataViewPermission: () => boolean; - valueToStringConverter: ValueToStringConverter; - rowsCount: number; - onFilter?: DocViewFilterFn; - editField?: (fieldName: string) => void; - columnCellActions?: EuiDataGridColumnCellAction[]; -}) { - const dataViewField = dataView.getFieldByName(columnName); - const editFieldButton = - editField && - dataViewField && - buildEditFieldButton({ hasEditDataViewPermission, dataView, field: dataViewField, editField }); - const columnDisplayName = - columnName === '_source' - ? i18n.translate('discover.grid.documentHeader', { - defaultMessage: 'Document', - }) - : dataViewField?.displayName || columnName; - - let cellActions: EuiDataGridColumnCellAction[]; - if (columnCellActions?.length) { - cellActions = columnCellActions; - } else { - cellActions = dataViewField ? buildCellActions(dataViewField, onFilter) : []; - } - - const column: EuiDataGridColumn = { - id: columnName, - schema: getSchemaByKbnType(dataViewField?.type), - isSortable: isSortEnabled && (isPlainRecord || dataViewField?.sortable === true), - displayAsText: columnDisplayName, - actions: { - showHide: - defaultColumns || columnName === dataView.timeFieldName - ? false - : { - label: i18n.translate('discover.removeColumnLabel', { - defaultMessage: 'Remove column', - }), - iconType: 'cross', - }, - showMoveLeft: !defaultColumns, - showMoveRight: !defaultColumns, - additional: [ - ...(columnName === '__source' - ? [] - : [ - buildCopyColumnNameButton({ - columnDisplayName, - toastNotifications, - }), - ]), - buildCopyColumnValuesButton({ - columnId: columnName, - columnDisplayName, - toastNotifications, - rowsCount, - valueToStringConverter, - }), - ...(editFieldButton ? [editFieldButton] : []), - ], - }, - cellActions, - }; - - if (column.id === dataView.timeFieldName) { - const timeFieldName = dataViewField?.customLabel ?? dataView.timeFieldName; - const primaryTimeAriaLabel = i18n.translate( - 'discover.docTable.tableHeader.timeFieldIconTooltipAriaLabel', - { - defaultMessage: '{timeFieldName} - this field represents the time that events occurred.', - values: { timeFieldName }, - } - ); - const primaryTimeTooltip = i18n.translate( - 'discover.docTable.tableHeader.timeFieldIconTooltip', - { - defaultMessage: 'This field represents the time that events occurred.', - } - ); - - column.display = ( -
    - - <> - {timeFieldName} - - -
    - ); - column.initialWidth = defaultTimeColumnWidth; - } - if (columnWidth > 0) { - column.initialWidth = Number(columnWidth); - } - return column; -} - -export function getEuiGridColumns({ - columns, - columnsCellActions, - rowsCount, - settings, - dataView, - defaultColumns, - isSortEnabled, - isPlainRecord, - services, - hasEditDataViewPermission, - valueToStringConverter, - onFilter, - editField, -}: { - columns: string[]; - columnsCellActions?: EuiDataGridColumnCellAction[][]; - rowsCount: number; - settings: DiscoverGridSettings | undefined; - dataView: DataView; - defaultColumns: boolean; - isSortEnabled: boolean; - isPlainRecord?: boolean; - services: { - uiSettings: IUiSettingsClient; - toastNotifications: ToastsStart; - }; - hasEditDataViewPermission: () => boolean; - valueToStringConverter: ValueToStringConverter; - onFilter: DocViewFilterFn; - editField?: (fieldName: string) => void; -}) { - const getColWidth = (column: string) => settings?.columns?.[column]?.width ?? 0; - - return columns.map((column, columnIndex) => - buildEuiGridColumn({ - columnName: column, - columnCellActions: columnsCellActions?.[columnIndex], - columnWidth: getColWidth(column), - dataView, - defaultColumns, - isSortEnabled, - isPlainRecord, - toastNotifications: services.toastNotifications, - hasEditDataViewPermission, - valueToStringConverter, - rowsCount, - onFilter, - editField, - }) - ); -} - -export function getVisibleColumns(columns: string[], dataView: DataView, showTimeCol: boolean) { - const timeFieldName = dataView.timeFieldName; - - if (showTimeCol && timeFieldName && !columns.find((col) => col === timeFieldName)) { - return [timeFieldName, ...columns]; - } - - return columns; -} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_context.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_context.tsx deleted file mode 100644 index f2dfd540c7d05..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_context.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import type { DataTableRecord } from '@kbn/discover-utils/types'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; -import type { ValueToStringConverter } from '../../types'; - -export interface GridContext { - expanded?: DataTableRecord | undefined; - setExpanded?: (hit?: DataTableRecord) => void; - rows: DataTableRecord[]; - onFilter?: DocViewFilterFn; - dataView: DataView; - isDarkMode: boolean; - selectedDocs: string[]; - setSelectedDocs: (selected: string[]) => void; - valueToStringConverter: ValueToStringConverter; -} - -const defaultContext = {} as unknown as GridContext; - -export const DiscoverGridContext = React.createContext(defaultContext); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx deleted file mode 100644 index 7fda5b74ce550..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { DiscoverGridDocumentToolbarBtn, SelectButton } from './discover_grid_document_selection'; -import { discoverGridContextMock } from '../../__mocks__/grid_context'; -import { DiscoverGridContext } from './discover_grid_context'; -import { getDocId } from '@kbn/discover-utils'; - -describe('document selection', () => { - describe('getDocId', () => { - test('doc with custom routing', () => { - const doc = { - _id: 'test-id', - _index: 'test-indices', - _routing: 'why-not', - }; - expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::why-not"`); - }); - test('doc without custom routing', () => { - const doc = { - _id: 'test-id', - _index: 'test-indices', - }; - expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::"`); - }); - }); - - describe('SelectButton', () => { - test('is not checked', () => { - const contextMock = { - ...discoverGridContextMock, - }; - - const component = mountWithIntl( - - - - ); - - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - expect(checkBox.props().checked).toBeFalsy(); - }); - - test('is checked', () => { - const contextMock = { - ...discoverGridContextMock, - selectedDocs: ['i::1::'], - }; - - const component = mountWithIntl( - - - - ); - - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - expect(checkBox.props().checked).toBeTruthy(); - }); - - test('adding a selection', () => { - const contextMock = { - ...discoverGridContextMock, - }; - - const component = mountWithIntl( - - - - ); - - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - checkBox.simulate('change'); - expect(contextMock.setSelectedDocs).toHaveBeenCalledWith(['i::1::']); - }); - test('removing a selection', () => { - const contextMock = { - ...discoverGridContextMock, - selectedDocs: ['i::1::'], - }; - - const component = mountWithIntl( - - - - ); - - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - checkBox.simulate('change'); - expect(contextMock.setSelectedDocs).toHaveBeenCalledWith([]); - }); - }); - describe('DiscoverGridDocumentToolbarBtn', () => { - test('it renders a button clickable button', () => { - const props = { - isFilterActive: false, - rows: discoverGridContextMock.rows, - selectedDocs: ['i::1::'], - setIsFilterActive: jest.fn(), - setSelectedDocs: jest.fn(), - }; - const component = mountWithIntl(); - const button = findTestSubject(component, 'dscGridSelectionBtn'); - expect(button.length).toBe(1); - }); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx deleted file mode 100644 index f14ec323f8ca2..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import classNames from 'classnames'; -import { - EuiButtonEmpty, - EuiCheckbox, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiCopy, - EuiDataGridCellValueElementProps, - EuiPopover, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { euiDarkVars as themeDark, euiLightVars as themeLight } from '@kbn/ui-theme'; -import { i18n } from '@kbn/i18n'; -import type { DataTableRecord } from '@kbn/discover-utils/types'; -import { DiscoverGridContext } from './discover_grid_context'; - -export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { - const { selectedDocs, expanded, rows, isDarkMode, setSelectedDocs } = - useContext(DiscoverGridContext); - const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]); - const checked = useMemo(() => selectedDocs.includes(doc.id), [selectedDocs, doc.id]); - - const toggleDocumentSelectionLabel = i18n.translate('discover.grid.selectDoc', { - defaultMessage: `Select document '{rowNumber}'`, - values: { rowNumber: rowIndex + 1 }, - }); - - useEffect(() => { - if (expanded && doc && expanded.id === doc.id) { - setCellProps({ - style: { - backgroundColor: isDarkMode ? themeDark.euiColorHighlight : themeLight.euiColorHighlight, - }, - }); - } else { - setCellProps({ style: undefined }); - } - }, [expanded, doc, setCellProps, isDarkMode]); - - return ( - { - if (checked) { - const newSelection = selectedDocs.filter((docId) => docId !== doc.id); - setSelectedDocs(newSelection); - } else { - setSelectedDocs([...selectedDocs, doc.id]); - } - }} - /> - ); -}; - -export function DiscoverGridDocumentToolbarBtn({ - isFilterActive, - rows, - selectedDocs, - setIsFilterActive, - setSelectedDocs, -}: { - isFilterActive: boolean; - rows: DataTableRecord[]; - selectedDocs: string[]; - setIsFilterActive: (value: boolean) => void; - setSelectedDocs: (value: string[]) => void; -}) { - const [isSelectionPopoverOpen, setIsSelectionPopoverOpen] = useState(false); - - const getMenuItems = useCallback(() => { - return [ - isFilterActive ? ( - { - setIsSelectionPopoverOpen(false); - setIsFilterActive(false); - }} - > - - - ) : ( - { - setIsSelectionPopoverOpen(false); - setIsFilterActive(true); - }} - > - - - ), - selectedDocs.includes(row.id)).map((row) => row.raw) - ) - : '' - } - > - {(copy) => ( - - - - )} - , - { - setIsSelectionPopoverOpen(false); - setSelectedDocs([]); - setIsFilterActive(false); - }} - > - - , - ]; - }, [ - isFilterActive, - rows, - selectedDocs, - setIsFilterActive, - setIsSelectionPopoverOpen, - setSelectedDocs, - ]); - - const toggleSelectionToolbar = useCallback( - () => setIsSelectionPopoverOpen((prevIsOpen) => !prevIsOpen), - [] - ); - - return ( - setIsSelectionPopoverOpen(false)} - isOpen={isSelectionPopoverOpen} - panelPaddingSize="none" - button={ - - - - } - > - {isSelectionPopoverOpen && } - - ); -} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx deleted file mode 100644 index e95853b99b7b7..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { ExpandButton } from './discover_grid_expand_button'; -import { DiscoverGridContext } from './discover_grid_context'; -import { discoverGridContextMock } from '../../__mocks__/grid_context'; - -describe('Discover grid view button ', function () { - it('when no document is expanded, setExpanded is called with current document', async () => { - const contextMock = { - ...discoverGridContextMock, - }; - - const component = mountWithIntl( - - - - ); - const button = findTestSubject(component, 'docTableExpandToggleColumn'); - await button.simulate('click'); - expect(contextMock.setExpanded).toHaveBeenCalledWith(discoverGridContextMock.rows[0]); - }); - it('when the current document is expanded, setExpanded is called with undefined', async () => { - const contextMock = { - ...discoverGridContextMock, - expanded: discoverGridContextMock.rows[0], - }; - - const component = mountWithIntl( - - - - ); - const button = findTestSubject(component, 'docTableExpandToggleColumn'); - await button.simulate('click'); - expect(contextMock.setExpanded).toHaveBeenCalledWith(undefined); - }); - it('when another document is expanded, setExpanded is called with the current document', async () => { - const contextMock = { - ...discoverGridContextMock, - expanded: discoverGridContextMock.rows[0], - }; - - const component = mountWithIntl( - - - - ); - const button = findTestSubject(component, 'docTableExpandToggleColumn'); - await button.simulate('click'); - expect(contextMock.setExpanded).toHaveBeenCalledWith(discoverGridContextMock.rows[1]); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx deleted file mode 100644 index 1a127f3639432..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; -import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme'; -import { i18n } from '@kbn/i18n'; -import { DiscoverGridContext } from './discover_grid_context'; -import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../discover_tour'; - -/** - * Button to expand a given row - */ -export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { - const toolTipRef = useRef(null); - const [pressed, setPressed] = useState(false); - const { expanded, setExpanded, rows, isDarkMode } = useContext(DiscoverGridContext); - const current = rows[rowIndex]; - - useEffect(() => { - if (current.isAnchor) { - setCellProps({ - className: 'dscDocsGrid__cell--highlight', - }); - } else if (expanded && current && expanded.id === current.id) { - setCellProps({ - style: { - backgroundColor: isDarkMode ? themeDark.euiColorHighlight : themeLight.euiColorHighlight, - }, - }); - } else { - setCellProps({ style: undefined }); - } - }, [expanded, current, setCellProps, isDarkMode]); - - const isCurrentRowExpanded = current === expanded; - const buttonLabel = i18n.translate('discover.grid.viewDoc', { - defaultMessage: 'Toggle dialog with details', - }); - - const testSubj = current.isAnchor - ? 'docTableExpandToggleColumnAnchor' - : 'docTableExpandToggleColumn'; - - useEffect(() => { - if (!isCurrentRowExpanded && pressed) { - setPressed(false); - setTimeout(() => { - toolTipRef.current?.hideToolTip(); - }, 100); - } - }, [isCurrentRowExpanded, setPressed, pressed]); - - if (!setExpanded) { - return null; - } - - return ( - - { - const nextHit = isCurrentRowExpanded ? undefined : current; - toolTipRef.current?.hideToolTip(); - setPressed(Boolean(nextHit)); - setExpanded?.(nextHit); - }} - color={isCurrentRowExpanded ? 'primary' : 'text'} - iconType={isCurrentRowExpanded ? 'minimize' : 'expand'} - isSelected={isCurrentRowExpanded} - /> - - ); -}; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx deleted file mode 100644 index ab25ac97fdc3c..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useMemo, useCallback } from 'react'; -import { i18n } from '@kbn/i18n'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiIconTip, - EuiTitle, - EuiButtonEmpty, - EuiText, - EuiSpacer, - EuiPortal, - EuiPagination, - EuiHideFor, - keys, -} from '@elastic/eui'; -import type { Filter, Query, AggregateQuery } from '@kbn/es-query'; -import type { DataTableRecord } from '@kbn/discover-utils/types'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; -import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; -import { useNavigationProps } from '../../hooks/use_navigation_props'; -import { useDiscoverServices } from '../../hooks/use_discover_services'; -import { isTextBasedQuery } from '../../application/main/utils/is_text_based_query'; - -export interface DiscoverGridFlyoutProps { - savedSearchId?: string; - filters?: Filter[]; - query?: Query | AggregateQuery; - columns: string[]; - hit: DataTableRecord; - hits?: DataTableRecord[]; - dataView: DataView; - onAddColumn: (column: string) => void; - onClose: () => void; - onFilter?: DocViewFilterFn; - onRemoveColumn: (column: string) => void; - setExpandedDoc: (doc: DataTableRecord) => void; -} - -function getIndexByDocId(hits: DataTableRecord[], id: string) { - return hits.findIndex((h) => { - return h.id === id; - }); -} -/** - * Flyout displaying an expanded Elasticsearch document - */ -export function DiscoverGridFlyout({ - hit, - hits, - dataView, - columns, - savedSearchId, - filters, - query, - onFilter, - onClose, - onRemoveColumn, - onAddColumn, - setExpandedDoc, -}: DiscoverGridFlyoutProps) { - const services = useDiscoverServices(); - const isPlainRecord = isTextBasedQuery(query); - // Get actual hit with updated highlighted searches - const actualHit = useMemo(() => hits?.find(({ id }) => id === hit?.id) || hit, [hit, hits]); - const pageCount = useMemo(() => (hits ? hits.length : 0), [hits]); - const activePage = useMemo(() => { - const id = hit.id; - if (!hits || pageCount <= 1) { - return -1; - } - - return getIndexByDocId(hits, id); - }, [hits, hit, pageCount]); - - const setPage = useCallback( - (index: number) => { - if (hits && hits[index]) { - setExpandedDoc(hits[index]); - } - }, - [hits, setExpandedDoc] - ); - - const onKeyDown = useCallback( - (ev: React.KeyboardEvent) => { - if (ev.key === keys.ARROW_LEFT || ev.key === keys.ARROW_RIGHT) { - ev.preventDefault(); - ev.stopPropagation(); - setPage(activePage + (ev.key === keys.ARROW_RIGHT ? 1 : -1)); - } - }, - [activePage, setPage] - ); - - const { singleDocHref, contextViewHref, onOpenSingleDoc, onOpenContextView } = useNavigationProps( - { dataView, rowIndex: hit.raw._index, rowId: hit.raw._id, columns, filters, savedSearchId } - ); - - return ( - - - - -

    - {isPlainRecord - ? i18n.translate('discover.grid.tableRow.textBasedDetailHeading', { - defaultMessage: 'Expanded row', - }) - : i18n.translate('discover.grid.tableRow.detailHeading', { - defaultMessage: 'Expanded document', - })} -

    -
    - - - - {!isPlainRecord && ( - <> - - - - - {i18n.translate('discover.grid.tableRow.viewText', { - defaultMessage: 'View:', - })} - - - - - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - {i18n.translate('discover.grid.tableRow.viewSingleDocumentLinkTextSimple', { - defaultMessage: 'Single document', - })} - - - {dataView.isTimeBased() && dataView.id && ( - - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - {i18n.translate( - 'discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple', - { - defaultMessage: 'Surrounding documents', - } - )} - - - - - - - )} - - )} - {activePage !== -1 && ( - - - - )} - -
    - - { - onRemoveColumn(columnName); - services.toastNotifications.addSuccess( - i18n.translate('discover.grid.flyout.toastColumnRemoved', { - defaultMessage: `Column '{columnName}' was removed`, - values: { columnName }, - }) - ); - }} - onAddColumn={(columnName: string) => { - onAddColumn(columnName); - services.toastNotifications.addSuccess( - i18n.translate('discover.grid.flyout.toastColumnAdded', { - defaultMessage: `Column '{columnName}' was added`, - values: { columnName }, - }) - ); - }} - textBasedHits={isPlainRecord ? hits : undefined} - /> - -
    -
    - ); -} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_footer.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_footer.test.tsx deleted file mode 100644 index a4bfe84b68126..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_footer.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { DiscoverGridFooter } from './discover_grid_footer'; -import { discoverServiceMock } from '../../__mocks__/services'; - -describe('DiscoverGridFooter', function () { - it('should not render anything when not on the last page', async () => { - const component = mountWithIntl( - - - - ); - expect(component.isEmptyRender()).toBe(true); - }); - - it('should not render anything yet when all rows shown', async () => { - const component = mountWithIntl( - - - - ); - expect(component.isEmptyRender()).toBe(true); - }); - - it('should render a message for the last page', async () => { - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'discoverTableFooter').text()).toBe( - 'Search results are limited to 500 documents. Add more search terms to narrow your search.' - ); - expect(findTestSubject(component, 'dscGridSampleSizeFetchMoreLink').exists()).toBe(false); - }); - - it('should render a message and the button for the last page', async () => { - const mockLoadMore = jest.fn(); - - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'discoverTableFooter').text()).toBe( - 'Search results are limited to 500 documents.Load more' - ); - - const button = findTestSubject(component, 'dscGridSampleSizeFetchMoreLink'); - expect(button.exists()).toBe(true); - - button.simulate('click'); - - expect(mockLoadMore).toHaveBeenCalledTimes(1); - }); - - it('should render a disabled button when loading more', async () => { - const mockLoadMore = jest.fn(); - - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'discoverTableFooter').text()).toBe( - 'Search results are limited to 500 documents.Load more' - ); - - const button = findTestSubject(component, 'dscGridSampleSizeFetchMoreLink'); - expect(button.exists()).toBe(true); - expect(button.prop('disabled')).toBe(true); - - button.simulate('click'); - - expect(mockLoadMore).not.toHaveBeenCalled(); - }); - - it('should render a message when max total limit is reached', async () => { - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'discoverTableFooter').text()).toBe( - 'Search results are limited to 10000 documents. Add more search terms to narrow your search.' - ); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_footer.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_footer.tsx deleted file mode 100644 index 540c7102bd424..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_footer.tsx +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useEffect, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiButtonEmpty, EuiToolTip, useEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/react'; -import { i18n } from '@kbn/i18n'; -import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; -import { MAX_LOADED_GRID_ROWS } from '../../../common/constants'; -import { useDiscoverServices } from '../../hooks/use_discover_services'; - -export interface DiscoverGridFooterProps { - isLoadingMore?: boolean; - rowCount: number; - sampleSize: number; - pageIndex?: number; // starts from 0 - pageCount: number; - totalHits?: number; - onFetchMoreRecords?: () => void; -} - -export const DiscoverGridFooter: React.FC = (props) => { - const { - isLoadingMore, - rowCount, - sampleSize, - pageIndex, - pageCount, - totalHits = 0, - onFetchMoreRecords, - } = props; - const { data } = useDiscoverServices(); - const timefilter = data.query.timefilter.timefilter; - const [refreshInterval, setRefreshInterval] = useState(timefilter.getRefreshInterval()); - - useEffect(() => { - const subscriber = timefilter.getRefreshIntervalUpdate$().subscribe(() => { - setRefreshInterval(timefilter.getRefreshInterval()); - }); - - return () => subscriber.unsubscribe(); - }, [timefilter, setRefreshInterval]); - - const isRefreshIntervalOn = Boolean( - refreshInterval && refreshInterval.pause === false && refreshInterval.value > 0 - ); - - const { euiTheme } = useEuiTheme(); - const isOnLastPage = pageIndex === pageCount - 1 && rowCount < totalHits; - - if (!isOnLastPage) { - return null; - } - - // allow to fetch more records on Discover page - if (onFetchMoreRecords && typeof isLoadingMore === 'boolean') { - if (rowCount <= MAX_LOADED_GRID_ROWS - sampleSize) { - return ( - - - - - - - - ); - } - - return ; - } - - if (rowCount < totalHits) { - // show only a message for embeddable - return ; - } - - return null; -}; - -interface DiscoverGridFooterContainerProps extends DiscoverGridFooterProps { - hasButton: boolean; -} - -const DiscoverGridFooterContainer: React.FC = ({ - hasButton, - rowCount, - children, -}) => { - const { euiTheme } = useEuiTheme(); - const { fieldFormats } = useDiscoverServices(); - - const formattedRowCount = fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(rowCount); - - return ( -

    - - {hasButton ? ( - - ) : ( - - )} - - {children} -

    - ); -}; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_schema.ts b/src/plugins/discover/public/components/discover_grid/discover_grid_schema.ts deleted file mode 100644 index dde4be2e76841..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_schema.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; -import { kibanaJSON } from './constants'; - -export function getSchemaByKbnType(kbnType: string | undefined) { - // Default DataGrid schemas: boolean, numeric, datetime, json, currency, string - switch (kbnType) { - case KBN_FIELD_TYPES.IP: - case KBN_FIELD_TYPES.GEO_SHAPE: - case KBN_FIELD_TYPES.NUMBER: - return 'numeric'; - case KBN_FIELD_TYPES.BOOLEAN: - return 'boolean'; - case KBN_FIELD_TYPES.STRING: - return 'string'; - case KBN_FIELD_TYPES.DATE: - return 'datetime'; - default: - return kibanaJSON; - } -} - -export function getSchemaDetectors() { - return [ - { - type: kibanaJSON, - detector() { - return 0; // this schema is always explicitly defined - }, - sortTextAsc: '', - sortTextDesc: '', - icon: '', - color: '', - }, - ]; -} diff --git a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx deleted file mode 100644 index 536a84172a88c..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx +++ /dev/null @@ -1,903 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { of } from 'rxjs'; -import { shallow } from 'enzyme'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { getRenderCellValueFn } from './get_render_cell_value'; -import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { buildDataTableRecord } from '@kbn/discover-utils'; -import type { EsHitRecord } from '@kbn/discover-utils/types'; - -const mockServices = { - settings: { - client: { - get: (key: string) => key === 'discover:maxDocFieldsDisplayed' && 200, - }, - }, - uiSettings: { - get: (key: string) => key === 'discover:maxDocFieldsDisplayed' && 200, - }, - fieldFormats: { - getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => (value ? value : '-') })), - }, - theme: { - theme$: of({ darkMode: false }), - }, -}; - -jest.mock('../../hooks/use_discover_services', () => { - const originalModule = jest.requireActual('../../hooks/use_discover_services'); - return { - ...originalModule, - useDiscoverServices: () => mockServices, - }; -}); - -const rowsSource: EsHitRecord[] = [ - { - _id: '1', - _index: 'test', - _score: 1, - _source: { bytes: 100, extension: '.gz' }, - highlight: { - extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], - }, - }, -]; - -const rowsFields: EsHitRecord[] = [ - { - _id: '1', - _index: 'test', - _score: 1, - _source: undefined, - fields: { bytes: [100], extension: ['.gz'] }, - highlight: { - extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], - }, - }, -]; - -const rowsFieldsWithTopLevelObject: EsHitRecord[] = [ - { - _id: '1', - _index: 'test', - _score: 1, - _source: undefined, - fields: { 'object.value': [100], extension: ['.gz'] }, - highlight: { - extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], - }, - }, -]; - -const build = (hit: EsHitRecord) => buildDataTableRecord(hit, dataViewMock); - -describe('Discover grid cell rendering', function () { - it('renders bytes column correctly', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsSource.map(build), - false, - () => false, - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component.html()).toMatchInlineSnapshot( - `"100"` - ); - }); - - it('renders bytes column correctly using _source when details is true', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsSource.map(build), - false, - () => false, - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component.html()).toMatchInlineSnapshot( - `"
    100
    "` - ); - }); - - it('renders bytes column correctly using fields when details is true', () => { - const closePopoverMockFn = jest.fn(); - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFields.map(build), - false, - () => false, - 100, - closePopoverMockFn - ); - const component = mountWithIntl( - - ); - expect(component.html()).toMatchInlineSnapshot( - `"
    100
    "` - ); - findTestSubject(component, 'docTableClosePopover').simulate('click'); - expect(closePopoverMockFn).toHaveBeenCalledTimes(1); - }); - - it('renders _source column correctly', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsSource.map(build), - false, - (fieldName) => ['extension', 'bytes'].includes(fieldName), - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - extension - - - - bytesDisplayName - - - - _index - - - - _score - - - - `); - }); - - it('renders _source column correctly when isDetails is set to true', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsSource.map(build), - false, - () => false, - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - - - - - - - - - - - `); - }); - - it('renders fields-based column correctly', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFields.map(build), - true, - (fieldName) => ['extension', 'bytes'].includes(fieldName), - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - extension - - - - bytesDisplayName - - - - _index - - - - _score - - - - `); - }); - - it('limits amount of rendered items', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFields.map(build), - true, - (fieldName) => ['extension', 'bytes'].includes(fieldName), - // this is the number of rendered items - 1, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - extension - - - - bytesDisplayName - - - - _index - - - - _score - - - - `); - }); - - it('renders fields-based column correctly when isDetails is set to true', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFields.map(build), - true, - (fieldName) => false, - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - - - - - - - - - - - `); - }); - - it('collect object fields and renders them like _source', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFieldsWithTopLevelObject.map(build), - true, - (fieldName) => ['object.value', 'extension', 'bytes'].includes(fieldName), - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - object.value - - - - `); - }); - - it('collect object fields and renders them like _source with fallback for unmapped', () => { - (dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined); - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFieldsWithTopLevelObject.map(build), - true, - (fieldName) => ['extension', 'bytes', 'object.value'].includes(fieldName), - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - object.value - - - - `); - }); - - it('collect object fields and renders them as json in details', () => { - const closePopoverMockFn = jest.fn(); - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFieldsWithTopLevelObject.map(build), - true, - () => false, - 100, - closePopoverMockFn - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - - - - - - - - - - - - `); - }); - - it('renders a functional close button when CodeEditor is rendered', () => { - const closePopoverMockFn = jest.fn(); - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFieldsWithTopLevelObject.map(build), - true, - () => false, - 100, - closePopoverMockFn - ); - const component = mountWithIntl( - - - - ); - const gridSelectionBtn = findTestSubject(component, 'docTableClosePopover'); - gridSelectionBtn.simulate('click'); - expect(closePopoverMockFn).toHaveBeenCalledTimes(1); - }); - - it('does not collect subfields when the the column is unmapped but part of fields response', () => { - (dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined); - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFieldsWithTopLevelObject.map(build), - true, - () => false, - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - `); - }); - - it('renders correctly when invalid row is given', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsSource.map(build), - false, - () => false, - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component.html()).toMatchInlineSnapshot( - `"-"` - ); - }); - - it('renders correctly when invalid column is given', () => { - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsSource.map(build), - false, - () => false, - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component.html()).toMatchInlineSnapshot( - `"-"` - ); - }); - - it('renders unmapped fields correctly', () => { - (dataViewMock.getFieldByName as jest.Mock).mockReturnValueOnce(undefined); - const rowsFieldsUnmapped: EsHitRecord[] = [ - { - _id: '1', - _index: 'test', - _score: 1, - _source: undefined, - fields: { unmapped: ['.gz'] }, - highlight: { - extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], - }, - }, - ]; - const DiscoverGridCellValue = getRenderCellValueFn( - dataViewMock, - rowsFieldsUnmapped.map(build), - true, - (fieldName) => ['unmapped'].includes(fieldName), - 100, - jest.fn() - ); - const component = shallow( - - ); - expect(component).toMatchInlineSnapshot(` - - `); - - const componentWithDetails = shallow( - - ); - expect(componentWithDetails).toMatchInlineSnapshot(` - - - - - - - - - `); - }); -}); diff --git a/src/plugins/discover/public/components/discover_grid/types.ts b/src/plugins/discover/public/components/discover_grid/types.ts deleted file mode 100644 index 71d82e35126ac..0000000000000 --- a/src/plugins/discover/public/components/discover_grid/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * User configurable state of data grid, persisted in saved search - */ -export interface DiscoverGridSettings { - columns?: Record; -} - -export interface DiscoverGridSettingsColumn { - width?: number; -} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx similarity index 82% rename from src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx rename to src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx index d8691180dc727..d1ff933ecad8f 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx @@ -23,6 +23,18 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { setUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/plugin'; import { mockUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/__mocks__'; +import { FlyoutCustomization, useDiscoverCustomization } from '../../customizations'; +import { EuiFlexItem } from '@elastic/eui'; + +const mockFlyoutCustomization: FlyoutCustomization = { + id: 'flyout', + actions: {}, +}; + +jest.mock('../../customizations', () => ({ + ...jest.requireActual('../../customizations'), + useDiscoverCustomization: jest.fn(), +})); const waitNextTick = () => new Promise((resolve) => setTimeout(resolve, 0)); @@ -94,6 +106,13 @@ describe('Discover flyout', function () { return { component, props }; }; + beforeEach(() => { + mockFlyoutCustomization.actions.defaultActions = undefined; + jest.clearAllMocks(); + + (useDiscoverCustomization as jest.Mock).mockImplementation(() => mockFlyoutCustomization); + }); + it('should be rendered correctly using an data view without timefield', async () => { const { component, props } = await mountComponent({}); @@ -199,11 +218,52 @@ describe('Discover flyout', function () { it('should not render single/surrounding views for text based', async () => { const { component } = await mountComponent({ - query: { sql: 'Select * from indexpattern' }, + query: { esql: 'FROM indexpattern' }, }); const singleDocumentView = findTestSubject(component, 'docTableRowAction'); expect(singleDocumentView.length).toBeFalsy(); const flyoutTitle = findTestSubject(component, 'docTableRowDetailsTitle'); expect(flyoutTitle.text()).toBe('Expanded row'); }); + + describe('when customizations actions exists', () => { + it('should display actions added by getActionItems', async () => { + mockFlyoutCustomization.actions = { + getActionItems: jest.fn(() => [ + { + id: 'action-item-1', + enabled: true, + Content: () => Action 1, + }, + { + id: 'action-item-2', + enabled: true, + Content: () => Action 2, + }, + ]), + }; + + const { component } = await mountComponent({}); + + const action1 = findTestSubject(component, 'customActionItem1'); + const action2 = findTestSubject(component, 'customActionItem2'); + + expect(action1.text()).toBe('Action 1'); + expect(action2.text()).toBe('Action 2'); + }); + + it('should allow disabling default actions', async () => { + mockFlyoutCustomization.actions = { + defaultActions: { + viewSingleDocument: { disabled: true }, + viewSurroundingDocument: { disabled: true }, + }, + }; + + const { component } = await mountComponent({}); + + const singleDocumentView = findTestSubject(component, 'docTableRowAction'); + expect(singleDocumentView.length).toBeFalsy(); + }); + }); }); diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx new file mode 100644 index 0000000000000..7c0598abbfe18 --- /dev/null +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiSpacer, + EuiPortal, + EuiPagination, + keys, +} from '@elastic/eui'; +import type { Filter, Query, AggregateQuery } from '@kbn/es-query'; +import type { DataTableRecord } from '@kbn/discover-utils/types'; +import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; +import { useDiscoverServices } from '../../hooks/use_discover_services'; +import { isTextBasedQuery } from '../../application/main/utils/is_text_based_query'; +import { useFlyoutActions } from './use_flyout_actions'; + +export interface DiscoverGridFlyoutProps { + savedSearchId?: string; + filters?: Filter[]; + query?: Query | AggregateQuery; + columns: string[]; + hit: DataTableRecord; + hits?: DataTableRecord[]; + dataView: DataView; + onAddColumn: (column: string) => void; + onClose: () => void; + onFilter?: DocViewFilterFn; + onRemoveColumn: (column: string) => void; + setExpandedDoc: (doc?: DataTableRecord) => void; +} + +function getIndexByDocId(hits: DataTableRecord[], id: string) { + return hits.findIndex((h) => { + return h.id === id; + }); +} +/** + * Flyout displaying an expanded Elasticsearch document + */ +export function DiscoverGridFlyout({ + hit, + hits, + dataView, + columns, + savedSearchId, + filters, + query, + onFilter, + onClose, + onRemoveColumn, + onAddColumn, + setExpandedDoc, +}: DiscoverGridFlyoutProps) { + const services = useDiscoverServices(); + const isPlainRecord = isTextBasedQuery(query); + // Get actual hit with updated highlighted searches + const actualHit = useMemo(() => hits?.find(({ id }) => id === hit?.id) || hit, [hit, hits]); + const pageCount = useMemo(() => (hits ? hits.length : 0), [hits]); + const activePage = useMemo(() => { + const id = hit.id; + if (!hits || pageCount <= 1) { + return -1; + } + + return getIndexByDocId(hits, id); + }, [hits, hit, pageCount]); + + const setPage = useCallback( + (index: number) => { + if (hits && hits[index]) { + setExpandedDoc(hits[index]); + } + }, + [hits, setExpandedDoc] + ); + + const onKeyDown = useCallback( + (ev: React.KeyboardEvent) => { + if (ev.key === keys.ARROW_LEFT || ev.key === keys.ARROW_RIGHT) { + ev.preventDefault(); + ev.stopPropagation(); + setPage(activePage + (ev.key === keys.ARROW_RIGHT ? 1 : -1)); + } + }, + [activePage, setPage] + ); + + const { flyoutActions } = useFlyoutActions({ + dataView, + rowIndex: hit.raw._index, + rowId: hit.raw._id, + columns, + filters, + savedSearchId, + }); + + return ( + + + + +

    + {isPlainRecord + ? i18n.translate('discover.grid.tableRow.textBasedDetailHeading', { + defaultMessage: 'Expanded row', + }) + : i18n.translate('discover.grid.tableRow.detailHeading', { + defaultMessage: 'Expanded document', + })} +

    +
    + + + + {!isPlainRecord && + flyoutActions.map((action) => action.enabled && )} + {activePage !== -1 && ( + + + + )} + +
    + + { + onRemoveColumn(columnName); + services.toastNotifications.addSuccess( + i18n.translate('discover.grid.flyout.toastColumnRemoved', { + defaultMessage: `Column '{columnName}' was removed`, + values: { columnName }, + }) + ); + }} + onAddColumn={(columnName: string) => { + onAddColumn(columnName); + services.toastNotifications.addSuccess( + i18n.translate('discover.grid.flyout.toastColumnAdded', { + defaultMessage: `Column '{columnName}' was added`, + values: { columnName }, + }) + ); + }} + textBasedHits={isPlainRecord ? hits : undefined} + /> + +
    +
    + ); +} + +// eslint-disable-next-line import/no-default-export +export default DiscoverGridFlyout; diff --git a/src/plugins/discover/public/components/discover_grid_flyout/index.ts b/src/plugins/discover/public/components/discover_grid_flyout/index.ts new file mode 100644 index 0000000000000..da7fa274494b1 --- /dev/null +++ b/src/plugins/discover/public/components/discover_grid_flyout/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { withSuspense } from '@kbn/shared-ux-utility'; +import { lazy } from 'react'; +export type { DiscoverGridFlyoutProps } from './discover_grid_flyout'; + +export const DiscoverGridFlyout = withSuspense(lazy(() => import('./discover_grid_flyout'))); diff --git a/src/plugins/discover/public/components/discover_grid_flyout/use_flyout_actions.tsx b/src/plugins/discover/public/components/discover_grid_flyout/use_flyout_actions.tsx new file mode 100644 index 0000000000000..0dc9485b42a40 --- /dev/null +++ b/src/plugins/discover/public/components/discover_grid_flyout/use_flyout_actions.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHideFor, + EuiIconTip, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useDiscoverCustomization } from '../../customizations'; +import { UseNavigationProps, useNavigationProps } from '../../hooks/use_navigation_props'; + +interface FlyoutActionProps { + onClick: React.MouseEventHandler; + href: string; +} + +const staticViewDocumentItem = { + id: 'viewDocument', + enabled: true, + Content: () => , +}; + +export const useFlyoutActions = (navigationProps: UseNavigationProps) => { + const { dataView } = navigationProps; + const { singleDocHref, contextViewHref, onOpenSingleDoc, onOpenContextView } = + useNavigationProps(navigationProps); + + const flyoutCustomization = useDiscoverCustomization('flyout'); + + const { + viewSingleDocument = { disabled: false }, + viewSurroundingDocument = { disabled: false }, + } = flyoutCustomization?.actions?.defaultActions ?? {}; + const customActions = [...(flyoutCustomization?.actions?.getActionItems?.() ?? [])]; + + const flyoutActions = [ + { + id: 'singleDocument', + enabled: !viewSingleDocument.disabled, + Content: () => , + }, + { + id: 'surroundingDocument', + enabled: Boolean(!viewSurroundingDocument.disabled && dataView.isTimeBased() && dataView.id), + Content: () => , + }, + ...customActions, + ]; + + const hasEnabledActions = flyoutActions.some((action) => action.enabled); + + if (hasEnabledActions) { + flyoutActions.unshift(staticViewDocumentItem); + } + + return { flyoutActions, hasEnabledActions }; +}; + +const ViewDocument = () => { + return ( + + + + + {i18n.translate('discover.grid.tableRow.viewText', { + defaultMessage: 'View:', + })} + + + + + ); +}; + +const SingleDocument = (props: FlyoutActionProps) => { + return ( + + + {i18n.translate('discover.grid.tableRow.viewSingleDocumentLinkTextSimple', { + defaultMessage: 'Single document', + })} + + + ); +}; + +const SurroundingDocuments = (props: FlyoutActionProps) => { + return ( + + + + {i18n.translate('discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple', { + defaultMessage: 'Surrounding documents', + })} + + + + + + + ); +}; diff --git a/src/plugins/discover/public/components/doc_table/actions/columns.test.ts b/src/plugins/discover/public/components/doc_table/actions/columns.test.ts deleted file mode 100644 index c95ff0d8d7252..0000000000000 --- a/src/plugins/discover/public/components/doc_table/actions/columns.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getStateColumnActions } from './columns'; -import { configMock } from '../../../__mocks__/config'; -import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { dataViewsMock } from '../../../__mocks__/data_views'; -import { Capabilities } from '@kbn/core/types'; -import { DiscoverAppState } from '../../../application/main/services/discover_app_state_container'; - -function getStateColumnAction( - state: DiscoverAppState, - setAppState: (state: Partial) => void -) { - return getStateColumnActions({ - capabilities: { - discover: { - save: false, - }, - } as unknown as Capabilities, - config: configMock, - dataView: dataViewMock, - dataViews: dataViewsMock, - useNewFieldsApi: true, - setAppState, - columns: state.columns, - sort: state.sort, - }); -} - -describe('Test column actions', () => { - test('getStateColumnActions with empty state', () => { - const setAppState = jest.fn(); - const actions = getStateColumnAction({}, setAppState); - - actions.onAddColumn('_score'); - expect(setAppState).toHaveBeenCalledWith({ columns: ['_score'], sort: [['_score', 'desc']] }); - actions.onAddColumn('test'); - expect(setAppState).toHaveBeenCalledWith({ columns: ['test'] }); - }); - test('getStateColumnActions with columns and sort in state', () => { - const setAppState = jest.fn(); - const actions = getStateColumnAction( - { columns: ['first', 'second'], sort: [['first', 'desc']] }, - setAppState - ); - - actions.onAddColumn('_score'); - expect(setAppState).toHaveBeenCalledWith({ - columns: ['first', 'second', '_score'], - sort: [['first', 'desc']], - }); - setAppState.mockClear(); - actions.onAddColumn('third'); - expect(setAppState).toHaveBeenCalledWith({ - columns: ['first', 'second', 'third'], - sort: [['first', 'desc']], - }); - setAppState.mockClear(); - actions.onRemoveColumn('first'); - expect(setAppState).toHaveBeenCalledWith({ - columns: ['second'], - sort: [], - }); - setAppState.mockClear(); - actions.onSetColumns(['first', 'second', 'third'], true); - expect(setAppState).toHaveBeenCalledWith({ - columns: ['first', 'second', 'third'], - }); - setAppState.mockClear(); - - actions.onMoveColumn('second', 0); - expect(setAppState).toHaveBeenCalledWith({ - columns: ['second', 'first'], - }); - }); -}); diff --git a/src/plugins/discover/public/components/doc_table/actions/columns.ts b/src/plugins/discover/public/components/doc_table/actions/columns.ts deleted file mode 100644 index b45d95433165a..0000000000000 --- a/src/plugins/discover/public/components/doc_table/actions/columns.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import { Capabilities, IUiSettingsClient } from '@kbn/core/public'; -import { DataViewsContract } from '@kbn/data-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; -import { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils'; -import { DiscoverAppStateContainer } from '../../../application/main/services/discover_app_state_container'; -import { GetStateReturn as ContextGetStateReturn } from '../../../application/context/services/context_state'; -import { popularizeField } from '../../../utils/popularize_field'; - -/** - * Helper function to provide a fallback to a single _source column if the given array of columns - * is empty, and removes _source if there are more than 1 columns given - * @param columns - * @param useNewFieldsApi should a new fields API be used - */ -function buildColumns(columns: string[], useNewFieldsApi = false) { - if (columns.length > 1 && columns.indexOf('_source') !== -1) { - return columns.filter((col) => col !== '_source'); - } else if (columns.length !== 0) { - return columns; - } - return useNewFieldsApi ? [] : ['_source']; -} - -export function addColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) { - if (columns.includes(columnName)) { - return columns; - } - return buildColumns([...columns, columnName], useNewFieldsApi); -} - -export function removeColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) { - if (!columns.includes(columnName)) { - return columns; - } - return buildColumns( - columns.filter((col) => col !== columnName), - useNewFieldsApi - ); -} - -export function moveColumn(columns: string[], columnName: string, newIndex: number) { - if (newIndex < 0 || newIndex >= columns.length || !columns.includes(columnName)) { - return columns; - } - const modifiedColumns = [...columns]; - modifiedColumns.splice(modifiedColumns.indexOf(columnName), 1); // remove at old index - modifiedColumns.splice(newIndex, 0, columnName); // insert before new index - return modifiedColumns; -} - -export function getStateColumnActions({ - capabilities, - config, - dataView, - dataViews, - useNewFieldsApi, - setAppState, - columns, - sort, -}: { - capabilities: Capabilities; - config: IUiSettingsClient; - dataView: DataView; - dataViews: DataViewsContract; - useNewFieldsApi: boolean; - setAppState: DiscoverAppStateContainer['update'] | ContextGetStateReturn['setAppState']; - columns?: string[]; - sort: string[][] | undefined; -}) { - function onAddColumn(columnName: string) { - popularizeField(dataView, columnName, dataViews, capabilities); - const nextColumns = addColumn(columns || [], columnName, useNewFieldsApi); - const defaultOrder = config.get(SORT_DEFAULT_ORDER_SETTING); - const nextSort = columnName === '_score' && !sort?.length ? [['_score', defaultOrder]] : sort; - setAppState({ columns: nextColumns, sort: nextSort }); - } - - function onRemoveColumn(columnName: string) { - popularizeField(dataView, columnName, dataViews, capabilities); - const nextColumns = removeColumn(columns || [], columnName, useNewFieldsApi); - // The state's sort property is an array of [sortByColumn,sortDirection] - const nextSort = sort && sort.length ? sort.filter((subArr) => subArr[0] !== columnName) : []; - setAppState({ columns: nextColumns, sort: nextSort }); - } - - function onMoveColumn(columnName: string, newIndex: number) { - const nextColumns = moveColumn(columns || [], columnName, newIndex); - setAppState({ columns: nextColumns }); - } - - function onSetColumns(nextColumns: string[], hideTimeColumn: boolean) { - // The next line should be gone when classic table will be removed - const actualColumns = - !hideTimeColumn && dataView.timeFieldName && dataView.timeFieldName === nextColumns[0] - ? (nextColumns || []).slice(1) - : nextColumns; - - setAppState({ columns: actualColumns }); - } - return { - onAddColumn, - onRemoveColumn, - onMoveColumn, - onSetColumns, - }; -} diff --git a/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx b/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx index 18ba8817391ac..bd0e2e3439451 100644 --- a/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx +++ b/src/plugins/discover/public/components/doc_table/components/pager/tool_bar_pagination.tsx @@ -19,7 +19,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { euiLightVars } from '@kbn/ui-theme'; -import { getRowsPerPageOptions } from '../../../../utils/rows_per_page'; +import { getRowsPerPageOptions } from '@kbn/unified-data-table'; export const MAX_ROWS_PER_PAGE_OPTION = 100; diff --git a/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx index b897ea278ebab..b5674b6833ada 100644 --- a/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx +++ b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx @@ -6,11 +6,10 @@ * Side Public License, v 1. */ -import { EuiTabs, EuiTab, useEuiPaddingSize } from '@elastic/eui'; import React from 'react'; +import { EuiTab, EuiTabs, useEuiPaddingSize, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; import { SHOW_FIELD_STATISTICS } from '@kbn/discover-utils'; import { VIEW_MODE } from '../../../common/constants'; import { useDiscoverServices } from '../../hooks/use_discover_services'; @@ -22,11 +21,12 @@ export const DocumentViewModeToggle = ({ viewMode: VIEW_MODE; setDiscoverViewMode: (viewMode: VIEW_MODE) => void; }) => { + const { euiTheme } = useEuiTheme(); const { uiSettings } = useDiscoverServices(); const tabsCss = css` padding: 0 ${useEuiPaddingSize('s')}; - background-color: ${euiThemeVars.euiPageBackgroundColor}; + border-bottom: ${viewMode === VIEW_MODE.AGGREGATED_LEVEL ? euiTheme.border.thin : 'none'}; `; const showViewModeToggle = uiSettings.get(SHOW_FIELD_STATISTICS) ?? false; @@ -36,7 +36,7 @@ export const DocumentViewModeToggle = ({ } return ( - + setDiscoverViewMode(VIEW_MODE.DOCUMENT_LEVEL)} diff --git a/src/plugins/discover/public/customizations/customization_service.ts b/src/plugins/discover/public/customizations/customization_service.ts index 0b57ba37e07dc..15175c8bad1ae 100644 --- a/src/plugins/discover/public/customizations/customization_service.ts +++ b/src/plugins/discover/public/customizations/customization_service.ts @@ -8,12 +8,14 @@ import { filter, map, Observable, startWith, Subject } from 'rxjs'; import type { + FlyoutCustomization, SearchBarCustomization, TopNavCustomization, UnifiedHistogramCustomization, } from './customization_types'; export type DiscoverCustomization = + | FlyoutCustomization | SearchBarCustomization | TopNavCustomization | UnifiedHistogramCustomization; diff --git a/src/plugins/discover/public/customizations/customization_types/flyout_customization.ts b/src/plugins/discover/public/customizations/customization_types/flyout_customization.ts new file mode 100644 index 0000000000000..6ec0fe9dec60a --- /dev/null +++ b/src/plugins/discover/public/customizations/customization_types/flyout_customization.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface FlyoutDefaultActionItem { + disabled?: boolean; +} + +export interface FlyoutDefaultActions { + viewSingleDocument?: FlyoutDefaultActionItem; + viewSurroundingDocument?: FlyoutDefaultActionItem; +} + +export interface FlyoutActionItem { + id: string; + Content: React.ElementType; + enabled: boolean; +} + +export interface FlyoutCustomization { + id: 'flyout'; + actions: { + defaultActions?: FlyoutDefaultActions; + getActionItems?: () => FlyoutActionItem[]; + }; +} diff --git a/src/plugins/discover/public/customizations/customization_types/index.ts b/src/plugins/discover/public/customizations/customization_types/index.ts index e920a68574b93..effb7fccf207c 100644 --- a/src/plugins/discover/public/customizations/customization_types/index.ts +++ b/src/plugins/discover/public/customizations/customization_types/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +export * from './flyout_customization'; export * from './search_bar_customization'; export * from './top_nav_customization'; export * from './histogram_customization'; diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index e6caae3c4a7dd..6b4856d3a298f 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -59,30 +59,30 @@ import { SORT_DEFAULT_ORDER_SETTING, buildDataTableRecord, } from '@kbn/discover-utils'; -import { VIEW_MODE, DISABLE_SHARD_FAILURE_WARNING } from '../../common/constants'; +import type { UnifiedDataTableProps } from '@kbn/unified-data-table'; +import type { UnifiedDataTableSettings } from '@kbn/unified-data-table'; +import { columnActions } from '@kbn/unified-data-table'; +import { VIEW_MODE, getDefaultRowsPerPage } from '../../common/constants'; import type { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; import type { DiscoverServices } from '../build_services'; import { getSortForEmbeddable, SortPair } from '../utils/sorting'; import { SEARCH_EMBEDDABLE_TYPE, SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from './constants'; import { SavedSearchEmbeddableComponent } from './saved_search_embeddable_component'; -import * as columnActions from '../components/doc_table/actions/columns'; import { handleSourceColumnState } from '../utils/state_helpers'; -import type { DiscoverGridProps } from '../components/discover_grid/discover_grid'; -import type { DiscoverGridSettings } from '../components/discover_grid/types'; import type { DocTableProps } from '../components/doc_table/doc_table_wrapper'; import { updateSearchSource } from './utils/update_search_source'; import { FieldStatisticsTable } from '../application/main/components/field_stats_table'; +import { fetchTextBased } from '../application/main/utils/fetch_text_based'; import { isTextBasedQuery } from '../application/main/utils/is_text_based_query'; import { getValidViewMode } from '../application/main/utils/get_valid_view_mode'; -import { fetchSql } from '../application/main/utils/fetch_sql'; import { ADHOC_DATA_VIEW_RENDER_EVENT } from '../constants'; import { getDiscoverLocatorParams } from './get_discover_locator_params'; -export type SearchProps = Partial & +export type SearchProps = Partial & Partial & { savedSearchId?: string; filters?: Filter[]; - settings?: DiscoverGridSettings; + settings?: UnifiedDataTableSettings; description?: string; sharedItemTitle?: string; inspectorAdapters?: Adapters; @@ -321,12 +321,12 @@ export class SavedSearchEmbeddable const query = savedSearch.searchSource.getField('query'); const dataView = savedSearch.searchSource.getField('index')!; - const useSql = this.isTextBasedSearch(savedSearch); + const useTextBased = this.isTextBasedSearch(savedSearch); try { - // Request SQL data - if (useSql && query) { - const result = await fetchSql( + // Request text based data + if (useTextBased && query) { + const result = await fetchTextBased( savedSearch.searchSource.getField('query')!, dataView, this.services.data, @@ -367,7 +367,7 @@ export class SavedSearchEmbeddable }), }, executionContext, - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, + disableWarningToasts: true, }) ); @@ -375,9 +375,6 @@ export class SavedSearchEmbeddable searchProps.interceptedWarnings = getSearchResponseInterceptedWarnings({ services: this.services, adapter: this.inspectorAdapters.requests, - options: { - disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, - }, }); } @@ -585,7 +582,10 @@ export class SavedSearchEmbeddable searchProps.sharedItemTitle = this.panelTitle; searchProps.searchTitle = this.panelTitle; searchProps.rowHeightState = this.input.rowHeight || savedSearch.rowHeight; - searchProps.rowsPerPageState = this.input.rowsPerPage || savedSearch.rowsPerPage; + searchProps.rowsPerPageState = + this.input.rowsPerPage || + savedSearch.rowsPerPage || + getDefaultRowsPerPage(this.services.uiSettings); searchProps.filters = savedSearch.searchSource.getField('filter') as Filter[]; searchProps.savedSearchId = savedSearch.id; diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx index fd28f3114211f..f8c7aa39c1a7c 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable_component.tsx @@ -8,11 +8,8 @@ import React from 'react'; import { AggregateQuery, Query } from '@kbn/es-query'; -import { - DiscoverGridEmbeddable, - DiscoverGridEmbeddableProps, - DataLoadingState, -} from './saved_search_grid'; +import { DataLoadingState } from '@kbn/unified-data-table'; +import { DiscoverGridEmbeddable, DiscoverGridEmbeddableProps } from './saved_search_grid'; import { DiscoverDocTableEmbeddable } from '../components/doc_table/create_doc_table_embeddable'; import { DocTableEmbeddableProps } from '../components/doc_table/doc_table_embeddable'; import { isTextBasedQuery } from '../application/main/utils/is_text_based_query'; @@ -47,7 +44,7 @@ export function SavedSearchEmbeddableComponent({ loadingState={searchProps.isLoading ? DataLoadingState.loading : DataLoadingState.loaded} showFullScreenButton={false} query={query} - className="dscDiscoverGrid" + className="unifiedDataTable" /> ); } diff --git a/src/plugins/discover/public/embeddable/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/saved_search_grid.tsx index b818286660301..580a55534b573 100644 --- a/src/plugins/discover/public/embeddable/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_grid.tsx @@ -5,43 +5,80 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { memo, useState } from 'react'; +import React, { memo, useCallback, useState } from 'react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; +import { AggregateQuery, Query } from '@kbn/es-query'; import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings'; import { - DiscoverGrid, - DiscoverGridProps, - DataLoadingState as DiscoverDataLoadingState, -} from '../components/discover_grid/discover_grid'; + DataLoadingState as DiscoverGridLoadingState, + UnifiedDataTable, +} from '@kbn/unified-data-table'; +import type { UnifiedDataTableProps } from '@kbn/unified-data-table'; import './saved_search_grid.scss'; -import { DiscoverGridFlyout } from '../components/discover_grid/discover_grid_flyout'; +import { MAX_DOC_FIELDS_DISPLAYED, ROW_HEIGHT_OPTION, SHOW_MULTIFIELDS } from '@kbn/discover-utils'; +import { DiscoverGridFlyout } from '../components/discover_grid_flyout'; import { SavedSearchEmbeddableBase } from './saved_search_embeddable_base'; -export { DataLoadingState } from '../components/discover_grid/discover_grid'; +import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../components/discover_tour'; -export interface DiscoverGridEmbeddableProps extends DiscoverGridProps { +export interface DiscoverGridEmbeddableProps extends UnifiedDataTableProps { totalHitCount?: number; + query?: AggregateQuery | Query; interceptedWarnings?: SearchResponseInterceptedWarning[]; + onAddColumn: (column: string) => void; + onRemoveColumn: (column: string) => void; + savedSearchId?: string; } -export const DiscoverGridMemoized = memo(DiscoverGrid); +export const DataGridMemoized = memo(UnifiedDataTable); export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { const { interceptedWarnings, ...gridProps } = props; const [expandedDoc, setExpandedDoc] = useState(undefined); + const renderDocumentView = useCallback( + (hit: DataTableRecord, displayedRows: DataTableRecord[], displayedColumns: string[]) => ( + setExpandedDoc(undefined)} + setExpandedDoc={setExpandedDoc} + query={props.query} + /> + ), + [ + props.dataView, + props.onAddColumn, + props.onFilter, + props.onRemoveColumn, + props.query, + props.savedSearchId, + ] + ); + return ( - ); diff --git a/src/plugins/discover/public/global_search/search_provider.test.ts b/src/plugins/discover/public/global_search/search_provider.test.ts new file mode 100644 index 0000000000000..79ec890858c3f --- /dev/null +++ b/src/plugins/discover/public/global_search/search_provider.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { NEVER, lastValueFrom } from 'rxjs'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { ApplicationStart } from '@kbn/core/public'; +import { getESQLSearchProvider } from './search_provider'; +import { createDiscoverDataViewsMock } from '../__mocks__/data_views'; +import type { DiscoverAppLocator } from '../../common'; + +describe('ES|QL search provider', () => { + const uiCapabilitiesMock = new Promise((resolve) => { + resolve({ navLinks: { discover: true } } as unknown as ApplicationStart['capabilities']); + }); + const dataMock = new Promise((resolve) => { + resolve({ dataViews: createDiscoverDataViewsMock() } as unknown as DataPublicPluginStart); + }); + const locator = { + useUrl: jest.fn(() => ''), + navigate: jest.fn(), + getLocation: jest.fn(() => + Promise.resolve({ + app: 'discover', + path: '/test', + }) + ), + getRedirectUrl: jest.fn(() => ''), + } as unknown as DiscoverAppLocator; + test('returns score 100 if term is esql', async () => { + const esqlSearchProvider = getESQLSearchProvider(true, uiCapabilitiesMock, dataMock, locator); + const observable = esqlSearchProvider.find( + { term: 'esql' }, + { aborted$: NEVER, maxResults: 100, preference: '' } + ); + + await expect(lastValueFrom(observable)).resolves.toEqual([ + { + icon: 'logoKibana', + id: 'esql', + meta: { categoryId: 'kibana', categoryLabel: 'Analytics' }, + score: 100, + title: 'Create ES|QL queries', + type: 'application', + url: '/app/discover/test', + }, + ]); + }); + + test('returns score 90 if user tries to write es|ql', async () => { + const esqlSearchProvider = getESQLSearchProvider(true, uiCapabilitiesMock, dataMock, locator); + const observable = esqlSearchProvider.find( + { term: 'es|' }, + { aborted$: NEVER, maxResults: 100, preference: '' } + ); + + await expect(lastValueFrom(observable)).resolves.toEqual([ + { + icon: 'logoKibana', + id: 'esql', + meta: { categoryId: 'kibana', categoryLabel: 'Analytics' }, + score: 90, + title: 'Create ES|QL queries', + type: 'application', + url: '/app/discover/test', + }, + ]); + }); + + test('returns empty results if user tries to write something irrelevant', async () => { + const esqlSearchProvider = getESQLSearchProvider(true, uiCapabilitiesMock, dataMock, locator); + const observable = esqlSearchProvider.find( + { term: 'woof' }, + { aborted$: NEVER, maxResults: 100, preference: '' } + ); + + await expect(lastValueFrom(observable)).resolves.toEqual([]); + }); + + test('returns empty results if ESQL is disabled', async () => { + const esqlSearchProvider = getESQLSearchProvider(false, uiCapabilitiesMock, dataMock, locator); + const observable = esqlSearchProvider.find( + { term: 'esql' }, + { aborted$: NEVER, maxResults: 100, preference: '' } + ); + + await expect(lastValueFrom(observable)).resolves.toEqual([]); + }); + + test('returns empty results if no default dataview', async () => { + const dataViewMock = createDiscoverDataViewsMock(); + const updatedDataMock = new Promise((resolve) => { + resolve({ + dataViews: { ...dataViewMock, getDefaultDataView: jest.fn(() => undefined) }, + } as unknown as DataPublicPluginStart); + }); + const esqlSearchProvider = getESQLSearchProvider( + true, + uiCapabilitiesMock, + updatedDataMock, + locator + ); + const observable = esqlSearchProvider.find( + { term: 'woof' }, + { aborted$: NEVER, maxResults: 100, preference: '' } + ); + + await expect(lastValueFrom(observable)).resolves.toEqual([]); + }); +}); diff --git a/src/plugins/discover/public/global_search/search_provider.ts b/src/plugins/discover/public/global_search/search_provider.ts new file mode 100644 index 0000000000000..35362f519ab3d --- /dev/null +++ b/src/plugins/discover/public/global_search/search_provider.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ApplicationStart } from '@kbn/core/public'; +import { from, of } from 'rxjs'; +import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; +import type { GlobalSearchResultProvider } from '@kbn/global-search-plugin/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DiscoverAppLocator } from '../../common'; + +/** + * Global search provider adding an ES|QL and ESQL entry. + * This is necessary because ES|QL is part of the Discover application. + * + * It navigates to Discover with a default query extracted from the default dataview + */ +export const getESQLSearchProvider: ( + isESQLEnabled: boolean, + uiCapabilities: Promise, + data: Promise, + locator?: DiscoverAppLocator +) => GlobalSearchResultProvider = (isESQLEnabled, uiCapabilities, data, locator) => ({ + id: 'esql', + find: ({ term = '', types, tags }) => { + if (tags || (types && !types.includes('application')) || !locator || !isESQLEnabled) { + return of([]); + } + + return from( + Promise.all([uiCapabilities, data]).then(async ([{ navLinks }, { dataViews }]) => { + if (!navLinks.discover) { + return []; + } + const title = i18n.translate('discover.globalSearch.esqlSearchTitle', { + defaultMessage: 'Create ES|QL queries', + description: 'ES|QL is a product name and should not be translated', + }); + const defaultDataView = await dataViews.getDefaultDataView({ displayErrors: false }); + + if (!defaultDataView) { + return []; + } + + const params = { + query: { + esql: `from ${defaultDataView?.getIndexPattern()} | limit 10`, + }, + dataViewSpec: defaultDataView?.toSpec(), + }; + + const discoverLocation = await locator?.getLocation(params); + + term = term.toLowerCase(); + let score = 0; + + if (term === 'es|ql' || term === 'esql') { + score = 100; + } else if (term && ('es|ql'.includes(term) || 'esql'.includes(term))) { + score = 90; + } + + if (score === 0) return []; + + return [ + { + id: 'esql', + title, + type: 'application', + icon: 'logoKibana', + meta: { + categoryId: DEFAULT_APP_CATEGORIES.kibana.id, + categoryLabel: DEFAULT_APP_CATEGORIES.kibana.label, + }, + score, + url: `/app/${discoverLocation.app}${discoverLocation.path}`, + }, + ]; + }) + ); + }, + getSearchableTypes: () => ['application'], +}); diff --git a/src/plugins/discover/public/hooks/use_data_grid_columns.ts b/src/plugins/discover/public/hooks/use_data_grid_columns.ts deleted file mode 100644 index 22fc8e9836888..0000000000000 --- a/src/plugins/discover/public/hooks/use_data_grid_columns.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { useEffect, useMemo, useState } from 'react'; -import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; - -import { Capabilities, IUiSettingsClient } from '@kbn/core/public'; -import { isEqual } from 'lodash'; -import { DiscoverAppStateContainer } from '../application/main/services/discover_app_state_container'; -import { GetStateReturn as ContextGetStateReturn } from '../application/context/services/context_state'; -import { getStateColumnActions } from '../components/doc_table/actions/columns'; - -interface UseColumnsProps { - capabilities: Capabilities; - config: IUiSettingsClient; - dataView: DataView; - dataViews: DataViewsContract; - useNewFieldsApi: boolean; - setAppState: DiscoverAppStateContainer['update'] | ContextGetStateReturn['setAppState']; - columns?: string[]; - sort?: string[][]; -} - -export const useColumns = ({ - capabilities, - config, - dataView, - dataViews, - setAppState, - useNewFieldsApi, - columns, - sort, -}: UseColumnsProps) => { - const [usedColumns, setUsedColumns] = useState(getColumns(columns, useNewFieldsApi)); - useEffect(() => { - const nextColumns = getColumns(columns, useNewFieldsApi); - if (isEqual(usedColumns, nextColumns)) { - return; - } - setUsedColumns(nextColumns); - }, [columns, useNewFieldsApi, usedColumns]); - const { onAddColumn, onRemoveColumn, onSetColumns, onMoveColumn } = useMemo( - () => - getStateColumnActions({ - capabilities, - config, - dataView, - dataViews, - setAppState, - useNewFieldsApi, - columns: usedColumns, - sort, - }), - [capabilities, config, dataView, dataViews, setAppState, sort, useNewFieldsApi, usedColumns] - ); - - return { - columns: usedColumns, - onAddColumn, - onRemoveColumn, - onMoveColumn, - onSetColumns, - }; -}; - -function getColumns(columns: string[] | undefined, useNewFieldsApi: boolean) { - if (!columns) { - return []; - } - return useNewFieldsApi ? columns.filter((col) => col !== '_source') : columns; -} diff --git a/src/plugins/discover/public/hooks/use_row_heights_options.test.tsx b/src/plugins/discover/public/hooks/use_row_heights_options.test.tsx deleted file mode 100644 index 113f34ab723dd..0000000000000 --- a/src/plugins/discover/public/hooks/use_row_heights_options.test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { ReactNode } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { DiscoverServices } from '../build_services'; -import { LocalStorageMock } from '../__mocks__/local_storage_mock'; -import { uiSettingsMock } from '../__mocks__/ui_settings'; -import { useRowHeightsOptions } from './use_row_heights_options'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; - -const CONFIG_ROW_HEIGHT = 3; - -const getWrapper = (services: DiscoverServices) => { - return ({ children }: { children: ReactNode }) => ( - {children} - ); -}; - -describe('useRowHeightsOptions', () => { - test('should apply rowHeight from savedSearch', () => { - const { result } = renderHook( - () => { - return useRowHeightsOptions({ - rowHeightState: 2, - }); - }, - { - wrapper: getWrapper({ - uiSettings: uiSettingsMock, - storage: new LocalStorageMock({}) as unknown as Storage, - } as DiscoverServices), - } - ); - - expect(result.current.defaultHeight).toEqual({ lineCount: 2 }); - }); - - test('should apply rowHeight from local storage', () => { - const { result } = renderHook( - () => { - return useRowHeightsOptions({}); - }, - { - wrapper: getWrapper({ - uiSettings: uiSettingsMock, - storage: new LocalStorageMock({ - ['discover:dataGridRowHeight']: { - previousRowHeight: 5, - previousConfigRowHeight: 3, - }, - }) as unknown as Storage, - } as DiscoverServices), - } - ); - - expect(result.current.defaultHeight).toEqual({ lineCount: 5 }); - }); - - test('should apply rowHeight from uiSettings', () => { - const { result } = renderHook( - () => { - return useRowHeightsOptions({}); - }, - { - wrapper: getWrapper({ - uiSettings: uiSettingsMock, - storage: new LocalStorageMock({}) as unknown as Storage, - } as unknown as DiscoverServices), - } - ); - - expect(result.current.defaultHeight).toEqual({ - lineCount: CONFIG_ROW_HEIGHT, - }); - }); - - test('should apply rowHeight from uiSettings instead of local storage value, since uiSettings has been changed', () => { - const { result } = renderHook( - () => { - return useRowHeightsOptions({}); - }, - { - wrapper: getWrapper({ - uiSettings: uiSettingsMock, - storage: new LocalStorageMock({ - ['discover:dataGridRowHeight']: { - previousRowHeight: 4, - // different from uiSettings (config), now user changed it to 3, but prev was 4 - previousConfigRowHeight: 4, - }, - }) as unknown as Storage, - } as unknown as DiscoverServices), - } - ); - - expect(result.current.defaultHeight).toEqual({ - lineCount: CONFIG_ROW_HEIGHT, - }); - }); -}); diff --git a/src/plugins/discover/public/hooks/use_row_heights_options.ts b/src/plugins/discover/public/hooks/use_row_heights_options.ts deleted file mode 100644 index a9ef67ace530b..0000000000000 --- a/src/plugins/discover/public/hooks/use_row_heights_options.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { EuiDataGridRowHeightOption, EuiDataGridRowHeightsOptions } from '@elastic/eui'; -import { useMemo } from 'react'; -import { ROW_HEIGHT_OPTION } from '@kbn/discover-utils'; -import { isValidRowHeight } from '../utils/validate_row_height'; -import { useDiscoverServices } from './use_discover_services'; -import { - DataGridOptionsRecord, - getStoredRowHeight, - updateStoredRowHeight, -} from '../utils/row_heights'; - -interface UseRowHeightProps { - rowHeightState?: number; - onUpdateRowHeight?: (rowHeight: number) => void; -} - -/** - * Row height might be a value from -1 to 20 - * A value of -1 automatically adjusts the row height to fit the contents. - * A value of 0 displays the content in a single line. - * A value from 1 to 20 represents number of lines of Document explorer row to display. - */ -const SINGLE_ROW_HEIGHT_OPTION = 0; -const AUTO_ROW_HEIGHT_OPTION = -1; - -/** - * Converts rowHeight of EuiDataGrid to rowHeight number (-1 to 20) - */ -const serializeRowHeight = (rowHeight?: EuiDataGridRowHeightOption): number => { - if (rowHeight === 'auto') { - return AUTO_ROW_HEIGHT_OPTION; - } else if (typeof rowHeight === 'object' && rowHeight.lineCount) { - return rowHeight.lineCount; // custom - } - - return SINGLE_ROW_HEIGHT_OPTION; -}; - -/** - * Converts rowHeight number (-1 to 20) of EuiDataGrid rowHeight - */ -const deserializeRowHeight = (number: number): EuiDataGridRowHeightOption | undefined => { - if (number === AUTO_ROW_HEIGHT_OPTION) { - return 'auto'; - } else if (number === SINGLE_ROW_HEIGHT_OPTION) { - return undefined; - } - - return { lineCount: number }; // custom -}; - -export const useRowHeightsOptions = ({ rowHeightState, onUpdateRowHeight }: UseRowHeightProps) => { - const { storage, uiSettings } = useDiscoverServices(); - - return useMemo((): EuiDataGridRowHeightsOptions => { - const rowHeightFromLS = getStoredRowHeight(storage); - const configRowHeight = uiSettings.get(ROW_HEIGHT_OPTION); - - const configHasNotChanged = ( - localStorageRecord: DataGridOptionsRecord | null - ): localStorageRecord is DataGridOptionsRecord => - localStorageRecord !== null && configRowHeight === localStorageRecord.previousConfigRowHeight; - - let rowHeight; - if (isValidRowHeight(rowHeightState)) { - rowHeight = rowHeightState; - } else if (configHasNotChanged(rowHeightFromLS)) { - rowHeight = rowHeightFromLS.previousRowHeight; - } else { - rowHeight = configRowHeight; - } - - return { - defaultHeight: deserializeRowHeight(rowHeight), - lineHeight: '1.6em', - onChange: ({ defaultHeight: newRowHeight }: EuiDataGridRowHeightsOptions) => { - const newSerializedRowHeight = serializeRowHeight(newRowHeight); - updateStoredRowHeight(newSerializedRowHeight, configRowHeight, storage); - onUpdateRowHeight?.(newSerializedRowHeight); - }, - }; - }, [rowHeightState, uiSettings, storage, onUpdateRowHeight]); -}; diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 5af7c2bf1142a..42033c2ebfdb5 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -15,6 +15,7 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { ISearchEmbeddable, SearchInput } from './embeddable'; +export type { DiscoverAppState } from './application/main/services/discover_app_state_container'; export type { DiscoverStateContainer } from './application/main/services/discover_state'; export type { DiscoverContainerProps } from './components/discover_container'; export type { @@ -24,6 +25,7 @@ export type { RegisterCustomizationProfile, DiscoverCustomization, DiscoverCustomizationService, + FlyoutCustomization, SearchBarCustomization, UnifiedHistogramCustomization, TopNavCustomization, diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index e00d9d29516c7..a1208f5c84eb7 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -20,6 +20,7 @@ import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public'; import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import { SharePluginStart, SharePluginSetup } from '@kbn/share-plugin/public'; import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; @@ -43,7 +44,7 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import type { UnifiedDocViewerStart } from '@kbn/unified-doc-viewer-plugin/public'; import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; -import { TRUNCATE_MAX_HEIGHT } from '@kbn/discover-utils'; +import { TRUNCATE_MAX_HEIGHT, ENABLE_ESQL } from '@kbn/discover-utils'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; import { PLUGIN_ID } from '../common'; @@ -79,6 +80,7 @@ import { DiscoverContainerInternal, type DiscoverContainerProps, } from './components/discover_container'; +import { getESQLSearchProvider } from './global_search/search_provider'; /** * @public @@ -164,6 +166,7 @@ export interface DiscoverSetupPlugins { home?: HomePublicPluginSetup; data: DataPublicPluginSetup; expressions: ExpressionsSetup; + globalSearch?: GlobalSearchPluginSetup; } /** @@ -233,6 +236,27 @@ export class DiscoverPlugin ); } + if (plugins.globalSearch) { + const enableESQL = core.uiSettings.get(ENABLE_ESQL); + plugins.globalSearch.registerResultProvider( + getESQLSearchProvider( + enableESQL, + core.getStartServices().then( + ([ + { + application: { capabilities }, + }, + ]) => capabilities + ), + core.getStartServices().then((deps) => { + const { data } = deps[1]; + return data; + }), + this.locator + ) + ); + } + const { setTrackedUrl, restorePreviousUrl, diff --git a/src/plugins/discover/public/types.ts b/src/plugins/discover/public/types.ts deleted file mode 100644 index 051892902239d..0000000000000 --- a/src/plugins/discover/public/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { DatatableColumn } from '@kbn/expressions-plugin/common'; -import type { DataTableRecord } from '@kbn/discover-utils/types'; -import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings'; - -export type ValueToStringConverter = ( - rowIndex: number, - columnId: string, - options?: { compatibleWithCSV?: boolean } -) => { formattedString: string; withFormula: boolean }; - -export interface RecordsFetchResponse { - records: DataTableRecord[]; - textBasedQueryColumns?: DatatableColumn[]; - interceptedWarnings?: SearchResponseInterceptedWarning[]; -} diff --git a/src/plugins/discover/public/utils/columns.test.ts b/src/plugins/discover/public/utils/columns.test.ts deleted file mode 100644 index 5ef7d8fea450f..0000000000000 --- a/src/plugins/discover/public/utils/columns.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getDisplayedColumns } from './columns'; -import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; -import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; - -describe('getDisplayedColumns', () => { - test('returns default columns given a data view without timefield', async () => { - const result = getDisplayedColumns([], dataViewMock); - expect(result).toMatchInlineSnapshot(` - Array [ - "_source", - ] - `); - }); - test('returns default columns given a data view with timefield', async () => { - const result = getDisplayedColumns([], dataViewWithTimefieldMock); - expect(result).toMatchInlineSnapshot(` - Array [ - "_source", - ] - `); - }); - test('returns default columns when just timefield is in state', async () => { - const result = getDisplayedColumns(['timestamp'], dataViewWithTimefieldMock); - expect(result).toMatchInlineSnapshot(` - Array [ - "_source", - ] - `); - }); - test('returns columns given by argument, no fallback ', async () => { - const result = getDisplayedColumns(['test'], dataViewWithTimefieldMock); - expect(result).toMatchInlineSnapshot(` - Array [ - "test", - ] - `); - }); - test('returns the same instance of ["_source"] over multiple calls', async () => { - const result = getDisplayedColumns([], dataViewWithTimefieldMock); - const result2 = getDisplayedColumns([], dataViewWithTimefieldMock); - expect(result).toBe(result2); - }); -}); diff --git a/src/plugins/discover/public/utils/columns.ts b/src/plugins/discover/public/utils/columns.ts deleted file mode 100644 index 49e234b11decc..0000000000000 --- a/src/plugins/discover/public/utils/columns.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { DataView } from '@kbn/data-views-plugin/public'; - -// We store this outside the function as a constant, so we're not creating a new array every time -// the function is returning this. A changing array might cause the data grid to think it got -// new columns, and thus performing worse than using the same array over multiple renders. -const SOURCE_ONLY = ['_source']; - -/** - * Function to provide fallback when - * 1) no columns are given - * 2) Just one column is given, which is the configured timefields - */ -export function getDisplayedColumns(stateColumns: string[] = [], dataView: DataView) { - return stateColumns && - stateColumns.length > 0 && - // check if all columns where removed except the configured timeField (this can't be removed) - !(stateColumns.length === 1 && stateColumns[0] === dataView.timeFieldName) - ? stateColumns - : SOURCE_ONLY; -} diff --git a/src/plugins/discover/public/utils/convert_value_to_string.test.tsx b/src/plugins/discover/public/utils/convert_value_to_string.test.tsx deleted file mode 100644 index dd81ad621f182..0000000000000 --- a/src/plugins/discover/public/utils/convert_value_to_string.test.tsx +++ /dev/null @@ -1,585 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { discoverGridContextComplexMock, discoverGridContextMock } from '../__mocks__/grid_context'; -import { discoverServiceMock } from '../__mocks__/services'; -import { convertValueToString, convertNameToString } from './convert_value_to_string'; - -describe('convertValueToString', () => { - it('should convert a keyword value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'keyword_key', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('abcd1'); - }); - - it('should convert a text value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'text_message', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"Hi there! I am a sample string."'); - }); - - it('should convert a text value to text (not for CSV)', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'text_message', - rowIndex: 0, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe('Hi there! I am a sample string.'); - }); - - it('should convert a multiline text value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'text_message', - rowIndex: 1, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"I\'m multiline\n*&%$#@"'); - expect(result.withFormula).toBe(false); - }); - - it('should convert a number value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'number_price', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('10.99'); - }); - - it('should convert a date value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'date', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"2022-05-22T12:10:30.000Z"'); - }); - - it('should convert a date nanos value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'date_nanos', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"2022-01-01T12:10:30.123456789Z"'); - }); - - it('should convert a date nanos value to text (not for CSV)', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'date_nanos', - rowIndex: 0, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe('2022-01-01T12:10:30.123456789Z'); - }); - - it('should convert a boolean value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'bool_enabled', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('false'); - }); - - it('should convert a binary value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'binary_blob', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"U29tZSBiaW5hcnkgYmxvYg=="'); - }); - - it('should convert a binary value to text (not for CSV)', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'binary_blob', - rowIndex: 0, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe('U29tZSBiaW5hcnkgYmxvYg=='); - }); - - it('should convert an object value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'object_user.first', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('John'); - }); - - it('should convert a nested value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'nested_user', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe( - '{"last":["Smith"],"last.keyword":["Smith"],"first":["John"],"first.keyword":["John"]}, {"last":["White"],"last.keyword":["White"],"first":["Alice"],"first.keyword":["Alice"]}' - ); - }); - - it('should convert a flattened value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'flattened_labels', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('{"release":["v1.2.5","v1.3.0"],"priority":"urgent"}'); - }); - - it('should convert a range value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'range_time_frame', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe( - '{"gte":"2015-10-31 12:00:00","lte":"2015-11-01 00:00:00"}' - ); - }); - - it('should convert a rank features value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'rank_features', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('{"2star":100,"1star":10}'); - }); - - it('should convert a histogram value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'histogram', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('{"counts":[3,7,23,12,6],"values":[0.1,0.2,0.3,0.4,0.5]}'); - }); - - it('should convert a IP value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'ip_addr', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"192.168.1.1"'); - }); - - it('should convert a IP value to text (not for CSV)', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'ip_addr', - rowIndex: 0, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe('192.168.1.1'); - }); - - it('should convert a version value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'version', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"1.2.3"'); - }); - - it('should convert a version value to text (not for CSV)', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'version', - rowIndex: 0, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe('1.2.3'); - }); - - it('should convert a vector value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'vector', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('0.5, 10, 6'); - }); - - it('should convert a geo point value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'geo_point', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('{"coordinates":[-71.34,41.12],"type":"Point"}'); - }); - - it('should convert a geo point object value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'geo_point', - rowIndex: 1, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('{"coordinates":[-71.34,41.12],"type":"Point"}'); - }); - - it('should convert an array value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'array_tags', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('elasticsearch, wow'); - }); - - it('should convert a shape value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'geometry', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe( - '{"coordinates":[[[1000,-1001],[1001,-1001],[1001,-1000],[1000,-1000],[1000,-1001]]],"type":"Polygon"}' - ); - }); - - it('should convert a runtime value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'runtime_number', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('5.5'); - }); - - it('should convert a scripted value to text', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'scripted_string', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"hi there"'); - }); - - it('should convert a scripted value to text (not for CSV)', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'scripted_string', - rowIndex: 0, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe('hi there'); - }); - - it('should return an empty string and not fail', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'unknown', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe(''); - }); - - it('should return an empty string when rowIndex is out of range', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'unknown', - rowIndex: -1, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe(''); - }); - - it('should return _source value', () => { - const result = convertValueToString({ - rows: discoverGridContextMock.rows, - dataView: discoverGridContextMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: '_source', - rowIndex: 0, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe( - '{\n' + - ' "bytes": 20,\n' + - ' "date": "2020-20-01T12:12:12.123",\n' + - ' "message": "test1",\n' + - ' "_index": "i",\n' + - ' "_score": 1\n' + - '}' - ); - }); - - it('should return a formatted _source value', () => { - const result = convertValueToString({ - rows: discoverGridContextMock.rows, - dataView: discoverGridContextMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: '_source', - rowIndex: 0, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe( - '{"bytes":20,"date":"2020-20-01T12:12:12.123","message":"test1","_index":"i","_score":1}' - ); - }); - - it('should escape formula', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'array_tags', - rowIndex: 1, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result.formattedString).toBe('"\'=1+2\'"" ;,=1+2"'); - expect(result.withFormula).toBe(true); - - const result2 = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'scripted_string', - rowIndex: 1, - options: { - compatibleWithCSV: true, - }, - }); - - expect(result2.formattedString).toBe('"\'=1+2"";=1+2"'); - expect(result2.withFormula).toBe(true); - }); - - it('should not escape formulas when not for CSV', () => { - const result = convertValueToString({ - rows: discoverGridContextComplexMock.rows, - dataView: discoverGridContextComplexMock.dataView, - fieldFormats: discoverServiceMock.fieldFormats, - columnId: 'array_tags', - rowIndex: 1, - options: { - compatibleWithCSV: false, - }, - }); - - expect(result.formattedString).toBe('=1+2\'" ;,=1+2'); - expect(result.withFormula).toBe(true); - }); - - it('should return a formatted name', () => { - const result = convertNameToString('test'); - - expect(result.formattedString).toBe('test'); - expect(result.withFormula).toBe(false); - }); - - it('should return a formatted name when with a formula', () => { - const result = convertNameToString('=1+2";=1+2'); - - expect(result.formattedString).toBe('"\'=1+2"";=1+2"'); - expect(result.withFormula).toBe(true); - }); -}); diff --git a/src/plugins/discover/public/utils/row_heights.ts b/src/plugins/discover/public/utils/row_heights.ts deleted file mode 100644 index f1e096b76b9f9..0000000000000 --- a/src/plugins/discover/public/utils/row_heights.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { isValidRowHeight } from './validate_row_height'; - -export interface DataGridOptionsRecord { - previousRowHeight: number; - previousConfigRowHeight: number; -} - -const ROW_HEIGHT_KEY = 'discover:dataGridRowHeight'; - -export const getStoredRowHeight = (storage: Storage): DataGridOptionsRecord | null => { - const entry = storage.get(ROW_HEIGHT_KEY); - if ( - typeof entry === 'object' && - entry !== null && - isValidRowHeight(entry.previousRowHeight) && - isValidRowHeight(entry.previousConfigRowHeight) - ) { - return entry; - } - return null; -}; - -export const updateStoredRowHeight = ( - newRowHeight: number, - configRowHeight: number, - storage: Storage -) => { - storage.set(ROW_HEIGHT_KEY, { - previousRowHeight: newRowHeight, - previousConfigRowHeight: configRowHeight, - }); -}; diff --git a/src/plugins/discover/public/utils/rows_per_page.test.ts b/src/plugins/discover/public/utils/rows_per_page.test.ts deleted file mode 100644 index 25eddf9a44de2..0000000000000 --- a/src/plugins/discover/public/utils/rows_per_page.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { discoverServiceMock } from '../__mocks__/services'; -import { SAMPLE_ROWS_PER_PAGE_SETTING } from '@kbn/discover-utils'; -import { getRowsPerPageOptions, getDefaultRowsPerPage } from './rows_per_page'; - -const SORTED_OPTIONS = [10, 25, 50, 100, 250, 500]; - -describe('rows per page', () => { - describe('getRowsPerPageOptions', () => { - it('should return default options if not provided', () => { - expect(getRowsPerPageOptions()).toEqual(SORTED_OPTIONS); - }); - - it('should return default options if current value is one of them', () => { - expect(getRowsPerPageOptions(250)).toEqual(SORTED_OPTIONS); - }); - - it('should return extended options if current value is not one of them', () => { - expect(getRowsPerPageOptions(350)).toEqual([10, 25, 50, 100, 250, 350, 500]); - }); - }); - - describe('getDefaultRowsPerPage', () => { - it('should return a value from settings', () => { - expect(getDefaultRowsPerPage(discoverServiceMock.uiSettings)).toEqual(150); - expect(discoverServiceMock.uiSettings.get).toHaveBeenCalledWith(SAMPLE_ROWS_PER_PAGE_SETTING); - }); - - it('should return a default value', () => { - expect(getDefaultRowsPerPage({ ...discoverServiceMock.uiSettings, get: jest.fn() })).toEqual( - 100 - ); - }); - }); -}); diff --git a/src/plugins/discover/public/utils/rows_per_page.ts b/src/plugins/discover/public/utils/rows_per_page.ts deleted file mode 100644 index bc5f07f6253d3..0000000000000 --- a/src/plugins/discover/public/utils/rows_per_page.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { sortBy, uniq } from 'lodash'; -import { SAMPLE_ROWS_PER_PAGE_SETTING } from '@kbn/discover-utils'; -import { DEFAULT_ROWS_PER_PAGE, ROWS_PER_PAGE_OPTIONS } from '../../common/constants'; -import { DiscoverServices } from '../build_services'; - -export const getRowsPerPageOptions = (currentRowsPerPage?: number): number[] => { - return sortBy( - uniq( - typeof currentRowsPerPage === 'number' && currentRowsPerPage > 0 - ? [...ROWS_PER_PAGE_OPTIONS, currentRowsPerPage] - : ROWS_PER_PAGE_OPTIONS - ) - ); -}; - -export const getDefaultRowsPerPage = (uiSettings: DiscoverServices['uiSettings']): number => { - return parseInt(uiSettings.get(SAMPLE_ROWS_PER_PAGE_SETTING), 10) || DEFAULT_ROWS_PER_PAGE; -}; diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index bd06f793a6447..bc5ad09d260c4 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -30,7 +30,7 @@ import { TRUNCATE_MAX_HEIGHT, SHOW_FIELD_STATISTICS, ROW_HEIGHT_OPTION, - ENABLE_SQL, + ENABLE_ESQL, } from '@kbn/discover-utils'; import { DEFAULT_ROWS_PER_PAGE, ROWS_PER_PAGE_OPTIONS } from '../common/constants'; @@ -287,7 +287,7 @@ export const getUiSettings: (docLinks: DocLinksServiceSetup) => Record Record` + - i18n.translate('discover.advancedSettings.enableSQL.discussLinkText', { + i18n.translate('discover.advancedSettings.enableESQL.discussLinkText', { defaultMessage: 'discuss.elastic.co/c/elastic-stack/kibana', }) + '
    ', diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index 231dcc926b830..f7b1e45e9a608 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -63,7 +63,6 @@ "@kbn/core-application-browser", "@kbn/core-saved-objects-server", "@kbn/discover-utils", - "@kbn/field-types", "@kbn/search-response-warnings", "@kbn/content-management-plugin", "@kbn/unified-doc-viewer", @@ -71,7 +70,9 @@ "@kbn/serverless", "@kbn/react-kibana-mount", "@kbn/react-kibana-context-render", - "@kbn/no-data-page-plugin" + "@kbn/unified-data-table", + "@kbn/no-data-page-plugin", + "@kbn/global-search-plugin" ], "exclude": [ "target/**/*" diff --git a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx index 07765836057ff..f0554fed61782 100644 --- a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx @@ -103,7 +103,8 @@ export const AddPanelFlyout = ({ const embeddable = await container.addNewEmbeddable( factoryForSavedObjectType.type, - { savedObjectId: id } + { savedObjectId: id }, + savedObject.attributes ); onAddPanel?.(embeddable.id); diff --git a/src/plugins/embeddable/public/embeddable_panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/embeddable_panel/embeddable_panel.test.tsx index 30532a50e5cb3..20af7a59758b0 100644 --- a/src/plugins/embeddable/public/embeddable_panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/embeddable_panel/embeddable_panel.test.tsx @@ -31,6 +31,10 @@ import { EmbeddablePanel } from './embeddable_panel'; import { core, inspector } from '../kibana_services'; import { CONTEXT_MENU_TRIGGER, ViewMode } from '..'; import { UnwrappedEmbeddablePanelProps } from './types'; +import { + DESCRIPTIVE_CONTACT_CARD_EMBEDDABLE, + DescriptiveContactCardEmbeddableFactory, +} from '../lib/test_samples/embeddables/contact_card/descriptive_contact_card_embeddable_factory'; const actionRegistry = new Map(); const triggerRegistry = new Map(); @@ -46,11 +50,15 @@ const embeddableReactFactory = new ContactCardEmbeddableReactFactory( (() => null) as any, {} as any ); +const descriptiveEmbeddableFactory = new DescriptiveContactCardEmbeddableFactory( + (() => null) as any +); actionRegistry.set(editModeAction.id, new ActionInternal(editModeAction)); triggerRegistry.set(trigger.id, trigger); setup.registerEmbeddableFactory(embeddableFactory.type, embeddableFactory); setup.registerEmbeddableFactory(embeddableReactFactory.type, embeddableReactFactory); +setup.registerEmbeddableFactory(descriptiveEmbeddableFactory.type, descriptiveEmbeddableFactory); const start = doStart(); const getEmbeddableFactory = start.getEmbeddableFactory; @@ -69,7 +77,11 @@ const renderEmbeddableInPanel = async ( return wrapper!; }; -const setupContainerAndEmbeddable = async (viewMode?: ViewMode, hidePanelTitles?: boolean) => { +const setupContainerAndEmbeddable = async ( + embeddableType: string, + viewMode: ViewMode = ViewMode.VIEW, + hidePanelTitles?: boolean +) => { const container = new HelloWorldContainer( { id: '123', panels: {}, viewMode: viewMode ?? ViewMode.VIEW, hidePanelTitles }, { @@ -81,7 +93,7 @@ const setupContainerAndEmbeddable = async (viewMode?: ViewMode, hidePanelTitles? ContactCardEmbeddableInput, ContactCardEmbeddableOutput, ContactCardEmbeddable - >(CONTACT_CARD_EMBEDDABLE, { + >(embeddableType, { firstName: 'Jack', lastName: 'Orange', }); @@ -250,7 +262,7 @@ describe('Error states', () => { }); test('Render method is called on Embeddable', async () => { - const { embeddable } = await setupContainerAndEmbeddable(); + const { embeddable } = await setupContainerAndEmbeddable(CONTACT_CARD_EMBEDDABLE); jest.spyOn(embeddable, 'render'); await renderEmbeddableInPanel({ embeddable }); expect(embeddable.render).toHaveBeenCalledTimes(1); @@ -379,7 +391,7 @@ test('Notifications are not shown when hideNotifications is true', async () => { }); test('Edit mode actions are hidden if parent is in view mode', async () => { - const { embeddable } = await setupContainerAndEmbeddable(); + const { embeddable } = await setupContainerAndEmbeddable(CONTACT_CARD_EMBEDDABLE); const component = await renderEmbeddableInPanel({ embeddable }); @@ -395,7 +407,7 @@ test('Edit mode actions are hidden if parent is in view mode', async () => { }); test('Edit mode actions are shown in edit mode', async () => { - const { container, embeddable } = await setupContainerAndEmbeddable(); + const { container, embeddable } = await setupContainerAndEmbeddable(CONTACT_CARD_EMBEDDABLE); const component = await renderEmbeddableInPanel({ embeddable }); @@ -442,7 +454,11 @@ test('Edit mode actions are shown in edit mode', async () => { }); test('Panel title customize link does not exist in view mode', async () => { - const { embeddable } = await setupContainerAndEmbeddable(ViewMode.VIEW, false); + const { embeddable } = await setupContainerAndEmbeddable( + CONTACT_CARD_EMBEDDABLE, + ViewMode.VIEW, + false + ); const component = await renderEmbeddableInPanel({ embeddable }); @@ -454,7 +470,11 @@ test('Runs customize panel action on title click when in edit mode', async () => // spy on core openFlyout to check that the flyout is opened correctly. core.overlays.openFlyout = jest.fn(); - const { embeddable } = await setupContainerAndEmbeddable(ViewMode.EDIT, false); + const { embeddable } = await setupContainerAndEmbeddable( + CONTACT_CARD_EMBEDDABLE, + ViewMode.EDIT, + false + ); const component = await renderEmbeddableInPanel({ embeddable }); @@ -472,7 +492,16 @@ test('Runs customize panel action on title click when in edit mode', async () => }); test('Updates when hidePanelTitles is toggled', async () => { - const { container, embeddable } = await setupContainerAndEmbeddable(ViewMode.VIEW, false); + const { container, embeddable } = await setupContainerAndEmbeddable( + CONTACT_CARD_EMBEDDABLE, + ViewMode.VIEW, + false + ); + /** + * panel title will always show if a description is set so we explictily set the panel + * description so the embeddable description is not used + */ + embeddable.updateInput({ description: '' }); const component = await renderEmbeddableInPanel({ embeddable }); await component.update(); @@ -499,7 +528,11 @@ test('Updates when hidePanelTitles is toggled', async () => { }); test('Respects options from SelfStyledEmbeddable', async () => { - const { container, embeddable } = await setupContainerAndEmbeddable(ViewMode.VIEW, false); + const { container, embeddable } = await setupContainerAndEmbeddable( + CONTACT_CARD_EMBEDDABLE, + ViewMode.VIEW, + false + ); const selfStyledEmbeddable = embeddablePluginMock.mockSelfStyledEmbeddable(embeddable, { hideTitle: true, @@ -514,8 +547,24 @@ test('Respects options from SelfStyledEmbeddable', async () => { expect(title.length).toBe(0); }); +test('Shows icon in panel title when the embeddable has a description', async () => { + const { embeddable } = await setupContainerAndEmbeddable( + DESCRIPTIVE_CONTACT_CARD_EMBEDDABLE, + ViewMode.VIEW, + false + ); + const component = await renderEmbeddableInPanel({ embeddable }); + + const descriptionIcon = findTestSubject(component, 'embeddablePanelTitleDescriptionIcon'); + expect(descriptionIcon.length).toBe(1); +}); + test('Does not hide header when parent hide header option is false', async () => { - const { embeddable } = await setupContainerAndEmbeddable(ViewMode.VIEW, false); + const { embeddable } = await setupContainerAndEmbeddable( + CONTACT_CARD_EMBEDDABLE, + ViewMode.VIEW, + false + ); const component = await renderEmbeddableInPanel({ embeddable }); @@ -524,7 +573,11 @@ test('Does not hide header when parent hide header option is false', async () => }); test('Hides title when parent hide header option is true', async () => { - const { embeddable } = await setupContainerAndEmbeddable(ViewMode.VIEW, true); + const { embeddable } = await setupContainerAndEmbeddable( + CONTACT_CARD_EMBEDDABLE, + ViewMode.VIEW, + true + ); const component = await renderEmbeddableInPanel({ embeddable }); @@ -535,7 +588,11 @@ test('Hides title when parent hide header option is true', async () => { test('Should work in minimal way rendering only the inspector action', async () => { inspector.isAvailable = jest.fn(() => true); - const { embeddable } = await setupContainerAndEmbeddable(ViewMode.VIEW, true); + const { embeddable } = await setupContainerAndEmbeddable( + CONTACT_CARD_EMBEDDABLE, + ViewMode.VIEW, + true + ); const component = await renderEmbeddableInPanel({ embeddable }); diff --git a/src/plugins/embeddable/public/embeddable_panel/panel_actions/edit_panel_action/edit_panel_action.ts b/src/plugins/embeddable/public/embeddable_panel/panel_actions/edit_panel_action/edit_panel_action.ts index cbd2e5208e458..ddd9082d6b685 100644 --- a/src/plugins/embeddable/public/embeddable_panel/panel_actions/edit_panel_action/edit_panel_action.ts +++ b/src/plugins/embeddable/public/embeddable_panel/panel_actions/edit_panel_action/edit_panel_action.ts @@ -18,6 +18,7 @@ import { EmbeddableInput, EmbeddableEditorState, EmbeddableStateTransfer, + isExplicitInputWithAttributes, } from '../../../lib'; import { ViewMode } from '../../../lib/types'; import { EmbeddableStart } from '../../../plugin'; @@ -94,9 +95,15 @@ export class EditPanelAction implements Action { } const oldExplicitInput = embeddable.getExplicitInput(); - let newExplicitInput: Awaited>; + let newExplicitInput: Partial; try { - newExplicitInput = await factory.getExplicitInput(oldExplicitInput, embeddable.parent); + const explicitInputReturn = await factory.getExplicitInput( + oldExplicitInput, + embeddable.parent + ); + newExplicitInput = isExplicitInputWithAttributes(explicitInputReturn) + ? explicitInputReturn.newInput + : explicitInputReturn; } catch (e) { // error likely means user canceled editing return; diff --git a/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_header.tsx b/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_header.tsx index adefe08248e87..ee27bbca0605a 100644 --- a/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_header.tsx +++ b/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_header.tsx @@ -44,8 +44,8 @@ export const EmbeddablePanelHeader = ({ ); const title = embeddable.getTitle(); + const description = embeddable.getDescription(); const viewMode = useSelectFromEmbeddableInput('viewMode', embeddable); - const description = useSelectFromEmbeddableInput('description', embeddable); const hidePanelTitle = useSelectFromEmbeddableInput('hidePanelTitles', embeddable); const parentHidePanelTitle = useSelectFromEmbeddableInput('hidePanelTitles', embeddable.parent); diff --git a/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx b/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx index 968aa11de9c38..734b420d04052 100644 --- a/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx +++ b/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx @@ -65,7 +65,12 @@ export const EmbeddablePanelTitle = ({ anchorClassName="embPanel__titleTooltipAnchor" > - {titleComponent} + {titleComponent}{' '} + ); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 91e6efcdc41c8..0e3650ea8a8a4 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -76,6 +76,7 @@ export { EmbeddableRenderer, useEmbeddableFactory, isFilterableEmbeddable, + isExplicitInputWithAttributes, shouldFetch$, shouldRefreshFilterCompareOptions, PANEL_HOVER_TRIGGER, diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index eedf083561996..546c9a9a9bf7f 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -160,16 +160,20 @@ export abstract class Container< EEI extends EmbeddableInput = EmbeddableInput, EEO extends EmbeddableOutput = EmbeddableOutput, E extends IEmbeddable = IEmbeddable - >(type: string, explicitInput: Partial): Promise { + >(type: string, explicitInput: Partial, attributes?: unknown): Promise { const factory = this.getFactory(type) as EmbeddableFactory | undefined; if (!factory) { throw new EmbeddableFactoryNotFoundError(type); } - const panelState = this.createNewPanelState(factory, explicitInput); + const { newPanel, otherPanels } = this.createNewPanelState( + factory, + explicitInput, + attributes + ); - return this.createAndSaveEmbeddable(type, panelState); + return this.createAndSaveEmbeddable(type, newPanel, otherPanels); } public async replaceEmbeddable< @@ -342,8 +346,9 @@ export abstract class Container< TEmbeddable extends IEmbeddable >( factory: EmbeddableFactory, - partial: Partial = {} - ): PanelState { + partial: Partial = {}, + attributes?: unknown + ): { newPanel: PanelState; otherPanels: TContainerInput['panels'] } { const embeddableId = partial.id || uuidv4(); const explicitInput = this.createNewExplicitEmbeddableInput( @@ -353,12 +358,15 @@ export abstract class Container< ); return { - type: factory.type, - explicitInput: { - ...explicitInput, - id: embeddableId, - version: factory.latestVersion, - } as TEmbeddableInput, + newPanel: { + type: factory.type, + explicitInput: { + ...explicitInput, + id: embeddableId, + version: factory.latestVersion, + } as TEmbeddableInput, + }, + otherPanels: this.getInput().panels, }; } @@ -412,10 +420,10 @@ export abstract class Container< protected async createAndSaveEmbeddable< TEmbeddableInput extends EmbeddableInput = EmbeddableInput, TEmbeddable extends IEmbeddable = IEmbeddable - >(type: string, panelState: PanelState) { + >(type: string, panelState: PanelState, otherPanels: TContainerInput['panels']) { this.updateInput({ panels: { - ...this.input.panels, + ...otherPanels, [panelState.explicitInput.id]: panelState, }, } as Partial); diff --git a/src/plugins/embeddable/public/lib/containers/i_container.ts b/src/plugins/embeddable/public/lib/containers/i_container.ts index 34e7cc0593e64..53226e7d15146 100644 --- a/src/plugins/embeddable/public/lib/containers/i_container.ts +++ b/src/plugins/embeddable/public/lib/containers/i_container.ts @@ -96,7 +96,8 @@ export interface IContainer< E extends Embeddable = Embeddable >( type: string, - explicitInput: Partial + explicitInput: Partial, + attributes?: unknown ): Promise; replaceEmbeddable< diff --git a/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts index 472840208e139..50555601d4bca 100644 --- a/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts +++ b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts @@ -30,6 +30,7 @@ export const defaultEmbeddableFactoryProvider = < } const factory: EmbeddableFactory = { + ...def, latestVersion: def.latestVersion, isContainerType: def.isContainerType ?? false, canCreateNew: def.canCreateNew ? def.canCreateNew.bind(def) : () => true, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts index 5285786056468..a96287a61d0f3 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts @@ -24,6 +24,17 @@ export interface OutputSpec { [key: string]: PropertySpec; } +export interface ExplicitInputWithAttributes { + newInput: Partial; + attributes?: unknown; +} + +export const isExplicitInputWithAttributes = ( + value: ExplicitInputWithAttributes | Partial +): value is ExplicitInputWithAttributes => { + return Boolean((value as ExplicitInputWithAttributes).newInput); +}; + /** * EmbeddableFactories create and initialize an embeddable instance */ @@ -106,11 +117,14 @@ export interface EmbeddableFactory< * input passed down from the parent container. * * Can be used to edit an embeddable by re-requesting explicit input. Initial input can be provided to allow the editor to show the current state. + * + * If saved object information is needed for creation use-cases, getExplicitInput can also return an unknown typed attributes object which will be passed + * into the container's addNewEmbeddable function. */ getExplicitInput( initialInput?: Partial, parent?: IContainer - ): Promise>; + ): Promise | ExplicitInputWithAttributes>; /** * Creates a new embeddable instance based off the saved object id. diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx index f94bb99ada83c..47fc20241e97f 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { ReactNode } from 'react'; +import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { EuiText } from '@elastic/eui'; import { isPromise } from '@kbn/std'; @@ -21,83 +21,45 @@ interface Props { input?: EmbeddableInput; } -interface State { - node?: ReactNode; -} - -export class EmbeddableRoot extends React.Component { - private root?: React.RefObject; - private alreadyMounted: boolean = false; - - constructor(props: Props) { - super(props); - - this.root = React.createRef(); - this.state = {}; - } - - private updateNode = (node: MaybePromise) => { - if (isPromise(node)) { - node.then(this.updateNode); +export const EmbeddableRoot: React.FC = ({ embeddable, loading, error, input }) => { + const [node, setNode] = useState(); + const [embeddableHasMounted, setEmbeddableHasMounted] = useState(false); + const rootRef = useRef(null); + const updateNode = useCallback((newNode: MaybePromise) => { + if (isPromise(newNode)) { + newNode.then(updateNode); return; } - this.setState({ node }); - }; + setNode(newNode); + }, []); - public componentDidMount() { - if (!this.root?.current || !this.props.embeddable) { + useEffect(() => { + if (!rootRef.current || !embeddable) { return; } - this.alreadyMounted = true; - this.updateNode(this.props.embeddable.render(this.root.current) ?? undefined); - } + setEmbeddableHasMounted(true); + updateNode(embeddable.render(rootRef.current) ?? undefined); + embeddable.render(rootRef.current); + }, [updateNode, embeddable]); - public componentDidUpdate(prevProps?: Props) { - let justRendered = false; - if (this.root?.current && this.props.embeddable && !this.alreadyMounted) { - this.alreadyMounted = true; - this.updateNode(this.props.embeddable.render(this.root.current) ?? undefined); - justRendered = true; + useEffect(() => { + if (input && embeddable && embeddableHasMounted) { + embeddable.updateInput(input); } + }, [input, embeddable, embeddableHasMounted]); - if ( - !justRendered && - this.root && - this.root.current && - this.props.embeddable && - this.alreadyMounted && - this.props.input && - prevProps?.input !== this.props.input - ) { - this.props.embeddable.updateInput(this.props.input); - } - } - - public shouldComponentUpdate({ embeddable, error, input, loading }: Props, { node }: State) { - return Boolean( - error !== this.props.error || - loading !== this.props.loading || - embeddable !== this.props.embeddable || - (this.root && this.root.current && embeddable && !this.alreadyMounted) || - input !== this.props.input || - node !== this.state.node - ); - } - - public render() { - return ( - -
    {this.state.node}
    - {this.props.loading && } - {this.props.error && ( - - {({ message }) => {message}} - - )} -
    - ); - } -} + return ( + <> +
    {node}
    + {loading && } + {error && ( + + {({ message }) => {message}} + + )} + + ); +}; diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx index 0287b9d115827..9a8c11ecd266b 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx @@ -29,6 +29,7 @@ export interface ContactCardEmbeddableOutput extends EmbeddableOutput { export interface ContactCardEmbeddableOptions { execAction: UiActionsStart['executeTriggerActions']; + outputOverrides?: Partial; } function getFullName(input: ContactCardEmbeddableInput) { @@ -56,6 +57,7 @@ export class ContactCardEmbeddable extends Embeddable< fullName: getFullName(initialInput), originalLastName: initialInput.lastName, defaultTitle: `Hello ${getFullName(initialInput)}`, + ...options.outputOverrides, }, parent ); diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/descriptive_contact_card_embeddable_factory.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/descriptive_contact_card_embeddable_factory.ts new file mode 100644 index 0000000000000..046f14d060610 --- /dev/null +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/descriptive_contact_card_embeddable_factory.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; + +import { Container, EmbeddableFactoryDefinition } from '../../..'; +import { ContactCardEmbeddable, ContactCardEmbeddableInput } from './contact_card_embeddable'; + +export const DESCRIPTIVE_CONTACT_CARD_EMBEDDABLE = 'DESCRIPTIVE_CONTACT_CARD_EMBEDDABLE'; + +export class DescriptiveContactCardEmbeddableFactory + implements EmbeddableFactoryDefinition +{ + public readonly type = DESCRIPTIVE_CONTACT_CARD_EMBEDDABLE; + + constructor(protected readonly execTrigger: UiActionsStart['executeTriggerActions']) {} + + public async isEditable() { + return true; + } + + public getDisplayName() { + return 'descriptive contact card'; + } + + public create = async (initialInput: ContactCardEmbeddableInput, parent?: Container) => { + return new ContactCardEmbeddable( + initialInput, + { + execAction: this.execTrigger, + outputOverrides: { + defaultDescription: 'This is a family friend', + }, + }, + parent + ); + }; +} diff --git a/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx index df4c37a081973..c5cf9ba9dda09 100644 --- a/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx +++ b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx @@ -31,6 +31,7 @@ function JsonEditorComp({ defaultValue, codeEditorProps, error: propsError, + ...rest }: Props) { const { content, @@ -82,6 +83,7 @@ function JsonEditorComp({ isInvalid={typeof error === 'string'} error={error} fullWidth + {...rest} > { - const [coreStart, pluginsStart] = await core.getStartServices(); - - const eventAnnotationService = await new EventAnnotationService( - coreStart, - pluginsStart.contentManagement - ).getService(); - - const ids = await pluginsStart.dataViews.getIds(); - const dataViews = await Promise.all(ids.map((id) => pluginsStart.dataViews.get(id))); - - const services: EventAnnotationListingPageServices = { - core: coreStart, - savedObjectsTagging: pluginsStart.savedObjectsTagging, - eventAnnotationService, - PresentationUtilContextProvider: pluginsStart.presentationUtil.ContextProvider, - dataViews, - createDataView: pluginsStart.dataViews.create.bind(pluginsStart.dataViews), - queryInputServices: { - http: coreStart.http, - docLinks: coreStart.docLinks, - notifications: coreStart.notifications, - uiSettings: coreStart.uiSettings, - dataViews: pluginsStart.dataViews, - unifiedSearch: pluginsStart.unifiedSearch, - data: pluginsStart.data, - storage: new Storage(localStorage), - }, - }; - - const { getTableList } = await import('./get_table_list'); - return getTableList(props, services); - }, - }); } public start( diff --git a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts index f71e9cd72b43b..dcb25deb71140 100644 --- a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts +++ b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts @@ -121,7 +121,6 @@ export class EventAnnotationGroupStorage const transforms = getTransforms(cmServicesDefinition, requestVersion); const soClient = await savedObjectClientFromRequest(ctx); - // Save data in DB const { saved_object: savedObject, alias_purpose: aliasPurpose, diff --git a/src/plugins/event_annotation/tsconfig.json b/src/plugins/event_annotation/tsconfig.json index 0c279bcd92436..c57bb18e0e19f 100644 --- a/src/plugins/event_annotation/tsconfig.json +++ b/src/plugins/event_annotation/tsconfig.json @@ -34,13 +34,6 @@ "@kbn/core-saved-objects-api-server", "@kbn/event-annotation-components", "@kbn/event-annotation-common", - "@kbn/kibana-react-plugin", - "@kbn/content-management-table-list-view-table", - "@kbn/content-management-tabbed-table-list-view", - "@kbn/core-lifecycle-browser", - "@kbn/saved-objects-tagging-oss-plugin", - "@kbn/dom-drag-drop", - "@kbn/kibana-utils-plugin", "@kbn/content-management-utils" ], "exclude": [ diff --git a/src/plugins/event_annotation_listing/.i18nrc.json b/src/plugins/event_annotation_listing/.i18nrc.json new file mode 100755 index 0000000000000..752ded7bf7e66 --- /dev/null +++ b/src/plugins/event_annotation_listing/.i18nrc.json @@ -0,0 +1,6 @@ +{ + "prefix": "eventAnnotationsApplication", + "paths": { + "eventAnnotationsApplication": "." + } +} diff --git a/src/plugins/event_annotation_listing/README.md b/src/plugins/event_annotation_listing/README.md new file mode 100644 index 0000000000000..9580dd19d8531 --- /dev/null +++ b/src/plugins/event_annotation_listing/README.md @@ -0,0 +1,3 @@ +# Event Annotation listing + +This plugin contains the library listing page for event annotation groups. diff --git a/src/plugins/event_annotation_listing/jest.config.js b/src/plugins/event_annotation_listing/jest.config.js new file mode 100644 index 0000000000000..2c28dee174da1 --- /dev/null +++ b/src/plugins/event_annotation_listing/jest.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/event_annotation_listing'], + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/event_annotation_listing', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/plugins/event_annotation_listing/{common,public,server}/**/*.{ts,tsx}', + ], + setupFiles: ['jest-canvas-mock'], +}; diff --git a/src/plugins/event_annotation_listing/kibana.jsonc b/src/plugins/event_annotation_listing/kibana.jsonc new file mode 100644 index 0000000000000..e8f51a817b977 --- /dev/null +++ b/src/plugins/event_annotation_listing/kibana.jsonc @@ -0,0 +1,30 @@ +{ + "type": "plugin", + "id": "@kbn/event-annotation-listing-plugin", + "owner": "@elastic/kibana-visualizations", + "description": "The listing page for event annotations.", + "plugin": { + "id": "eventAnnotationListing", + "server": false, + "browser": true, + "requiredPlugins": [ + "savedObjectsManagement", + "eventAnnotation", + "data", + "presentationUtil", + "visualizations", + "dataViews", + "unifiedSearch", + "kibanaUtils", + "contentManagement", + ], + "optionalPlugins": [ + "savedObjectsTagging", + "lens", + ], + "requiredBundles": [ + "kibanaReact", + ], + "extraPublicDirs": [] + } +} diff --git a/packages/kbn-event-annotation-components/components/__snapshots__/group_editor_flyout.test.tsx.snap b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/__snapshots__/group_editor_flyout.test.tsx.snap similarity index 95% rename from packages/kbn-event-annotation-components/components/__snapshots__/group_editor_flyout.test.tsx.snap rename to src/plugins/event_annotation_listing/public/components/group_editor_flyout/__snapshots__/group_editor_flyout.test.tsx.snap index 49def88aecb70..6dc7b5ecdc2ae 100644 --- a/packages/kbn-event-annotation-components/components/__snapshots__/group_editor_flyout.test.tsx.snap +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/__snapshots__/group_editor_flyout.test.tsx.snap @@ -3,7 +3,6 @@ exports[`group editor flyout renders controls 1`] = ` Object { "TagSelector": [MockFunction], - "createDataView": [MockFunction], "dataViews": Array [ Object { "id": "some-id", @@ -29,6 +28,7 @@ Object { "tags": Array [], "title": "My group", }, + "isAdHocDataView": [Function], "queryInputServices": Object {}, "selectedAnnotation": undefined, "setSelectedAnnotation": [Function], diff --git a/packages/kbn-event-annotation-components/components/group_editor_controls/annotation_list.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/annotation_list.tsx similarity index 75% rename from packages/kbn-event-annotation-components/components/group_editor_controls/annotation_list.tsx rename to src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/annotation_list.tsx index 72a4546659420..176190d07374b 100644 --- a/packages/kbn-event-annotation-components/components/group_editor_controls/annotation_list.tsx +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/annotation_list.tsx @@ -24,7 +24,7 @@ import { euiThemeVars } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; import type { EventAnnotationConfig } from '@kbn/event-annotation-common'; import { createCopiedAnnotation } from '@kbn/event-annotation-common'; -import { getAnnotationAccessor } from '..'; +import { getAnnotationAccessor } from '@kbn/event-annotation-components'; export const AnnotationList = ({ annotations, @@ -40,7 +40,7 @@ export const AnnotationList = ({ setNewAnnotationId(uuidv4()); }, [annotations.length]); - const addAnnotationText = i18n.translate('eventAnnotationComponents.annotationList.add', { + const addAnnotationText = i18n.translate('eventAnnotationListing.annotationList.add', { defaultMessage: 'Add annotation', }); @@ -88,14 +88,26 @@ export const AnnotationList = ({ const [{ dragging }] = useDragDropContext(); return ( -
    +
    {annotations.map((annotation, index) => (
    selectAnnotation(annotation)} @@ -136,34 +148,28 @@ export const AnnotationList = ({ ))} -
    addNewAnnotation(sourceId)} > - addNewAnnotation(sourceId)} - > - addNewAnnotation()} - /> - -
    + addNewAnnotation()} + /> +
    ); }; diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.test.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.test.tsx new file mode 100644 index 0000000000000..d41045e7cee6a --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.test.tsx @@ -0,0 +1,221 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { ChangeEvent, FormEvent } from 'react'; +import type { EventAnnotationGroupConfig } from '@kbn/event-annotation-common'; +import { getDefaultManualAnnotation } from '@kbn/event-annotation-common'; +import { ReactWrapper } from 'enzyme'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { GroupEditorControls } from './group_editor_controls'; +import { EuiTextAreaProps, EuiTextProps } from '@elastic/eui'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { act } from 'react-dom/test-utils'; +import type { QueryInputServices } from '@kbn/visualization-ui-components'; +import { AnnotationEditorControls } from '@kbn/event-annotation-components'; + +jest.mock('@elastic/eui', () => { + return { + ...jest.requireActual('@elastic/eui'), + EuiDatePicker: () => <>, // for some reason this component caused an infinite loop when the props updated + }; +}); + +describe('event annotation group editor', () => { + const dataViewId = 'my-index-pattern'; + const adHocDataViewId = 'ad-hoc'; + const adHocDataViewSpec = { + id: adHocDataViewId, + title: 'Ad Hoc Data View', + }; + + const group: EventAnnotationGroupConfig = { + annotations: [], + description: '', + tags: [], + indexPatternId: dataViewId, + title: 'My group', + ignoreGlobalFilters: false, + dataViewSpec: adHocDataViewSpec, + }; + + let wrapper: ReactWrapper; + let updateMock: jest.Mock; + let setSelectedAnnotationMock: jest.Mock; + + const TagSelector = (_props: { onTagsSelected: (tags: string[]) => void }) =>
    ; + + beforeEach(async () => { + updateMock = jest.fn(); + setSelectedAnnotationMock = jest.fn(); + + wrapper = mountWithIntl( + false} + /> + ); + + await act(async () => { + await new Promise((resolve) => setImmediate(resolve)); + wrapper.update(); + }); + }); + + it('reports group updates', () => { + ( + wrapper.find( + "EuiFieldText[data-test-subj='annotationGroupTitle']" + ) as ReactWrapper + ).prop('onChange')!({ + target: { + value: 'im a new title!', + } as Partial as EventTarget, + } as FormEvent); + + ( + wrapper.find( + "EuiTextArea[data-test-subj='annotationGroupDescription']" + ) as ReactWrapper + ).prop('onChange')!({ + target: { + value: 'im a new description!', + }, + } as ChangeEvent); + + act(() => { + wrapper.find(TagSelector).prop('onTagsSelected')(['im a new tag!']); + }); + + // TODO - reenable data view selection tests when ENABLE_INDIVIDUAL_ANNOTATION_EDITING is set to true! + // this will happen in https://github.com/elastic/kibana/issues/158774 + + // const setDataViewId = (id: string) => + // ( + // wrapper.find( + // "EuiSelect[data-test-subj='annotationDataViewSelection']" + // ) as ReactWrapper + // ).prop('onChange')!({ target: { value: id } } as React.ChangeEvent); + + // setDataViewId(dataViewId); + // setDataViewId(adHocDataViewId); + + expect(updateMock.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "annotations": Array [], + "dataViewSpec": Object { + "id": "ad-hoc", + "title": "Ad Hoc Data View", + }, + "description": "", + "ignoreGlobalFilters": false, + "indexPatternId": "my-index-pattern", + "tags": Array [], + "title": "im a new title!", + }, + ], + Array [ + Object { + "annotations": Array [], + "dataViewSpec": Object { + "id": "ad-hoc", + "title": "Ad Hoc Data View", + }, + "description": "im a new description!", + "ignoreGlobalFilters": false, + "indexPatternId": "my-index-pattern", + "tags": Array [], + "title": "My group", + }, + ], + Array [ + Object { + "annotations": Array [], + "dataViewSpec": Object { + "id": "ad-hoc", + "title": "Ad Hoc Data View", + }, + "description": "", + "ignoreGlobalFilters": false, + "indexPatternId": "my-index-pattern", + "tags": Array [ + "im a new tag!", + ], + "title": "My group", + }, + ], + ] + `); + }); + + // it('adds a new annotation group', () => { + // act(() => { + // wrapper.find('button[data-test-subj="addAnnotation"]').simulate('click'); + // }); + + // expect(updateMock).toHaveBeenCalledTimes(2); + // const newAnnotations = (updateMock.mock.calls[0][0] as EventAnnotationGroupConfig).annotations; + // expect(newAnnotations.length).toBe(group.annotations.length + 1); + // expect(wrapper.exists(AnnotationEditorControls)); // annotation controls opened + // }); + + it('incorporates annotation updates into group', () => { + const annotations = [getDefaultManualAnnotation('1', ''), getDefaultManualAnnotation('2', '')]; + + act(() => { + wrapper.setProps({ + selectedAnnotation: annotations[0], + group: { ...group, annotations }, + }); + }); + + wrapper.find(AnnotationEditorControls).prop('onAnnotationChange')({ + ...annotations[0], + color: 'newColor', + }); + + expect(updateMock).toHaveBeenCalledTimes(1); + expect(updateMock.mock.calls[0][0].annotations[0].color).toBe('newColor'); + expect(setSelectedAnnotationMock).toHaveBeenCalledTimes(1); + }); + + it('removes an annotation from a group', () => { + const annotations = [getDefaultManualAnnotation('1', ''), getDefaultManualAnnotation('2', '')]; + + act(() => { + wrapper.setProps({ + group: { ...group, annotations }, + }); + }); + + act(() => { + wrapper + .find('button[data-test-subj="indexPattern-dimension-remove"]') + .last() + .simulate('click'); + }); + + expect(updateMock).toHaveBeenCalledTimes(1); + expect(updateMock.mock.calls[0][0].annotations).toEqual(annotations.slice(0, 1)); + }); +}); diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx new file mode 100644 index 0000000000000..329b5228048aa --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/group_editor_controls.tsx @@ -0,0 +1,254 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiFieldText, + EuiForm, + EuiFormRow, + EuiSelect, + EuiText, + EuiTextArea, + EuiTitle, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { SavedObjectsTaggingApiUiComponent } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { QueryInputServices } from '@kbn/visualization-ui-components'; +import React, { useCallback, useMemo } from 'react'; +import type { + EventAnnotationConfig, + EventAnnotationGroupConfig, +} from '@kbn/event-annotation-common'; +import { + EVENT_ANNOTATION_APP_NAME, + AnnotationEditorControls, +} from '@kbn/event-annotation-components'; +import { AnnotationList } from './annotation_list'; + +const isTitleValid = (title: string) => Boolean(title.length); + +const isDataViewValid = (dataView: DataView | undefined) => Boolean(dataView?.id); + +export const isGroupValid = (group: EventAnnotationGroupConfig, dataViews: DataView[]) => + isTitleValid(group.title) && + isDataViewValid(dataViews.find(({ id }) => id === group.indexPatternId)); + +export const GroupEditorControls = ({ + group, + update, + setSelectedAnnotation: _setSelectedAnnotation, + selectedAnnotation, + TagSelector, + dataViews, + queryInputServices, + showValidation, + isAdHocDataView, +}: { + group: EventAnnotationGroupConfig; + update: (group: EventAnnotationGroupConfig) => void; + selectedAnnotation: EventAnnotationConfig | undefined; + setSelectedAnnotation: (annotation: EventAnnotationConfig) => void; + TagSelector: SavedObjectsTaggingApiUiComponent['SavedObjectSaveModalTagSelector']; + dataViews: DataView[]; + queryInputServices: QueryInputServices; + showValidation: boolean; + isAdHocDataView: (id: string) => boolean; +}) => { + const setSelectedAnnotation = useCallback( + (newSelection: EventAnnotationConfig) => { + update({ + ...group, + annotations: group.annotations.map((annotation) => + annotation.id === newSelection.id ? newSelection : annotation + ), + }); + _setSelectedAnnotation(newSelection); + }, + [_setSelectedAnnotation, group, update] + ); + + const currentDataView = useMemo( + () => dataViews.find((dataView) => dataView.id === group.indexPatternId), + [dataViews, group.indexPatternId] + ); + + return !selectedAnnotation ? ( + <> + +

    + +

    +
    + + + + update({ + ...group, + title: value, + }) + } + /> + + + + + } + > + + update({ + ...group, + description: value, + }) + } + /> + + + + update({ + ...group, + tags, + }) + } + /> + + + ({ + value, + text: name ?? title, + }))} + value={isDataViewValid(currentDataView) ? group.indexPatternId : undefined} + hasNoInitialSelection={true} + onChange={({ target: { value } }) => { + const selectedDataView = dataViews.find(({ id }) => id === value); + + if (!selectedDataView?.id) { + return; + } + + update({ + ...group, + indexPatternId: value, + dataViewSpec: isAdHocDataView(selectedDataView.id) + ? selectedDataView.toSpec(false) + : undefined, + }); + }} + /> + + +
    + +

    + +

    +
    + + + {}} + update={(newAnnotations) => update({ ...group, annotations: newAnnotations })} + /> + + +
    + + ) : currentDataView ? ( + setSelectedAnnotation({ ...selectedAnnotation, ...changes })} + dataView={currentDataView} + getDefaultRangeEnd={(rangeStart) => rangeStart} + queryInputServices={queryInputServices} + appName={EVENT_ANNOTATION_APP_NAME} + /> + ) : null; +}; diff --git a/packages/kbn-event-annotation-components/components/group_editor_controls/index.ts b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/index.ts similarity index 100% rename from packages/kbn-event-annotation-components/components/group_editor_controls/index.ts rename to src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_controls/index.ts diff --git a/packages/kbn-event-annotation-components/components/group_editor_flyout.test.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_flyout.test.tsx similarity index 81% rename from packages/kbn-event-annotation-components/components/group_editor_flyout.test.tsx rename to src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_flyout.test.tsx index 72ba259e1ad49..8f6fcbeceaa6c 100644 --- a/packages/kbn-event-annotation-components/components/group_editor_flyout.test.tsx +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_flyout.test.tsx @@ -16,6 +16,7 @@ import { GroupEditorControls } from './group_editor_controls'; import { GroupEditorFlyout } from './group_editor_flyout'; import { DataView } from '@kbn/data-views-plugin/common'; import type { QueryInputServices } from '@kbn/visualization-ui-components'; +import { EmbeddableComponent } from '@kbn/lens-plugin/public'; const simulateButtonClick = (component: ShallowWrapper, selector: string) => { (component.find(selector) as ShallowWrapper[0]>).prop('onClick')!( @@ -27,6 +28,7 @@ const SELECTORS = { SAVE_BUTTON: '[data-test-subj="saveAnnotationGroup"]', CANCEL_BUTTON: '[data-test-subj="cancelGroupEdit"]', BACK_BUTTON: '[data-test-subj="backToGroupSettings"]', + TOP_BACK_BUTTON: '[data-test-subj="backToGroupSettingsTop"]', }; const assertGroupEditingState = (component: ShallowWrapper) => { @@ -59,14 +61,15 @@ describe('group editor flyout', () => { let onSave: jest.Mock; let onClose: jest.Mock; let updateGroup: jest.Mock; + const LensEmbeddableComponent: EmbeddableComponent = jest.fn(); - beforeEach(() => { + const mountComponent = (groupToUse: EventAnnotationGroupConfig) => { onSave = jest.fn(); onClose = jest.fn(); updateGroup = jest.fn(); - component = shallow( + return shallow( { savedObjectsTagging={mockTaggingApi} createDataView={jest.fn()} queryInputServices={{} as QueryInputServices} + LensEmbeddableComponent={LensEmbeddableComponent} + searchSessionId={'searchSessionId'} + refreshSearchSession={jest.fn()} + timePickerQuickRanges={[]} /> ); + }; + + beforeEach(() => { + component = mountComponent(group); }); it('renders controls', () => { @@ -112,17 +123,20 @@ describe('group editor flyout', () => { expect(updateGroup).toHaveBeenCalledWith(newGroup); }); - test('specific annotation editing', () => { - assertGroupEditingState(component); + test.each([SELECTORS.BACK_BUTTON, SELECTORS.TOP_BACK_BUTTON])( + 'specific annotation editing', + (backButtonSelector) => { + assertGroupEditingState(component); - component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation); + component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation); - assertAnnotationEditingState(component); + assertAnnotationEditingState(component); - component.find(SELECTORS.BACK_BUTTON).simulate('click'); + component.find(backButtonSelector).simulate('click'); - assertGroupEditingState(component); - }); + assertGroupEditingState(component); + } + ); it('removes active annotation instead of signaling close', () => { component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation); diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_flyout.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_flyout.tsx new file mode 100644 index 0000000000000..7bd15fcb16ddd --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_editor_flyout.tsx @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, + htmlIdGenerator, + useIsWithinBreakpoints, + EuiButtonIcon, +} from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; +import type { QueryInputServices } from '@kbn/visualization-ui-components'; +import type { + EventAnnotationConfig, + EventAnnotationGroupConfig, +} from '@kbn/event-annotation-common'; +import { css } from '@emotion/react'; +import type { EmbeddableComponent as LensEmbeddableComponent } from '@kbn/lens-plugin/public'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import { GroupEditorControls, isGroupValid } from './group_editor_controls'; +import { GroupPreview } from './group_preview'; + +export const GroupEditorFlyout = ({ + group, + updateGroup, + onClose: parentOnClose, + onSave, + savedObjectsTagging, + dataViews: globalDataViews, + createDataView, + LensEmbeddableComponent, + queryInputServices, + searchSessionId, + refreshSearchSession, + timePickerQuickRanges, +}: { + group: EventAnnotationGroupConfig; + updateGroup: (newGroup: EventAnnotationGroupConfig) => void; + onClose: () => void; + onSave: () => void; + savedObjectsTagging: SavedObjectsTaggingApi; + dataViews: DataView[]; + createDataView: (spec: DataViewSpec) => Promise; + LensEmbeddableComponent: LensEmbeddableComponent; + queryInputServices: QueryInputServices; + searchSessionId: string; + refreshSearchSession: () => void; + timePickerQuickRanges: Array<{ from: string; to: string; display: string }> | undefined; +}) => { + const flyoutHeadingId = useMemo(() => htmlIdGenerator()(), []); + const flyoutBodyOverflowRef = useRef(null); + useEffect(() => { + if (!flyoutBodyOverflowRef.current) { + flyoutBodyOverflowRef.current = document.querySelector('.euiFlyoutBody__overflow'); + } + }, []); + + const [hasAttemptedSave, setHasAttemptedSave] = useState(false); + + const resetContentScroll = useCallback( + () => flyoutBodyOverflowRef.current && flyoutBodyOverflowRef.current.scroll(0, 0), + [] + ); + + // save the spec for the life of the component since the user might change their mind after selecting another data view + const [adHocDataView, setAdHocDataView] = useState(); + + useEffect(() => { + if (group.dataViewSpec) { + createDataView(group.dataViewSpec).then(setAdHocDataView); + } + }, [createDataView, group.dataViewSpec]); + + const dataViews = useMemo(() => { + const items = [...globalDataViews]; + if (adHocDataView) { + items.push(adHocDataView); + } + return items; + }, [adHocDataView, globalDataViews]); + + const [selectedAnnotation, _setSelectedAnnotation] = useState(); + const setSelectedAnnotation = useCallback( + (newValue: EventAnnotationConfig | undefined) => { + if ((!newValue && selectedAnnotation) || (newValue && !selectedAnnotation)) + resetContentScroll(); + _setSelectedAnnotation(newValue); + }, + [resetContentScroll, selectedAnnotation] + ); + const onClose = () => (selectedAnnotation ? setSelectedAnnotation(undefined) : parentOnClose()); + + const showPreview = !useIsWithinBreakpoints(['xs', 's', 'm']); + + return ( + + + + + +

    + {selectedAnnotation ? ( + + + setSelectedAnnotation(undefined)} + data-test-subj="backToGroupSettingsTop" + /> + + + + + + ) : ( + + )} +

    +
    +
    + + + id === adHocDataView?.id} + /> + + + + + {selectedAnnotation ? ( + + setSelectedAnnotation(undefined)} + > + + + + ) : ( + <> + + + + + + + { + setHasAttemptedSave(true); + + if (isGroupValid(group, dataViews)) { + onSave(); + } + }} + > + + + + + )} + + +
    + {showPreview && ( + + + + )} +
    +
    + ); +}; diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx new file mode 100644 index 0000000000000..09c7c5a265355 --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx @@ -0,0 +1,307 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getDefaultManualAnnotation } from '@kbn/event-annotation-common'; +import type { EventAnnotationGroupConfig } from '@kbn/event-annotation-common'; +import React from 'react'; +import { + DataView, + DataViewField, + DataViewFieldMap, + IIndexPatternFieldList, +} from '@kbn/data-views-plugin/common'; +import { + EmbeddableComponent, + FieldBasedIndexPatternColumn, + TypedLensByValueInput, +} from '@kbn/lens-plugin/public'; +import { Datatable } from '@kbn/expressions-plugin/common'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import userEvent from '@testing-library/user-event'; +import { I18nProvider } from '@kbn/i18n-react'; +import { GroupPreview } from './group_preview'; +import { LensByValueInput } from '@kbn/lens-plugin/public/embeddable'; +import { DATA_LAYER_ID, DATE_HISTOGRAM_COLUMN_ID, getCurrentTimeField } from './lens_attributes'; +import moment from 'moment'; + +class EuiSuperDatePickerTestHarness { + public static get currentCommonlyUsedRange() { + return screen.queryByTestId('superDatePickerShowDatesButton')?.textContent ?? ''; + } + + // TODO - add assertion with date formatting + public static get currentRange() { + if (screen.queryByTestId('superDatePickerShowDatesButton')) { + // showing a commonly-used range + return { from: '', to: '' }; + } + + return { + from: screen.getByTestId('superDatePickerstartDatePopoverButton').textContent, + to: screen.getByTestId('superDatePickerendDatePopoverButton').textContent, + }; + } + + static togglePopover() { + userEvent.click(screen.getByRole('button', { name: 'Date quick select' })); + } + + static async selectCommonlyUsedRange(label: string) { + if (!screen.queryByText('Commonly used')) this.togglePopover(); + + // Using fireEvent here because userEvent erroneously claims that + // pointer-events is set to 'none'. + // + // I have verified that this fixed on the latest version of the @testing-library/user-event package + fireEvent.click(await screen.findByText(label)); + } + + static refresh() { + userEvent.click(screen.getByRole('button', { name: 'Refresh' })); + } +} + +describe('group editor preview', () => { + const annotation = getDefaultManualAnnotation('my-id', 'some-timestamp'); + + const group: EventAnnotationGroupConfig = { + annotations: [annotation], + description: '', + tags: [], + indexPatternId: 'some-id', + title: 'My group', + ignoreGlobalFilters: false, + }; + + const BRUSH_RANGE = [0, 100]; + + const LensEmbeddableComponent: EmbeddableComponent = (props) => ( +
    +
    {JSON.stringify(props.timeRange)}
    +
    {props.searchSessionId}
    +
    + {JSON.stringify((props as LensByValueInput).attributes)} +
    +
    + ); + + const getEmbeddableTimeRange = () => { + const serialized = screen.getByTestId('chartTimeRange').textContent; + return serialized ? JSON.parse(serialized) : null; + }; + + const getEmbeddableSearchSessionId = () => { + return screen.getByTestId('chartSearchSessionId').textContent; + }; + + const getLensAttributes = () => { + const serialized = screen.queryByTestId('lensAttributes')?.textContent; + return serialized ? JSON.parse(serialized) : null; + }; + + let rerender: (ui: React.ReactElement>) => void; + + const defaultProps: Parameters[0] = { + group, + dataViews: [ + { + id: 'some-id', + title: 'My Data View', + timeFieldName: '@timestamp', + fields: { + getByType: jest.fn(() => [ + { + type: 'date', + name: '@timestamp', + } as DataViewField, + { + type: 'date', + name: 'other-time-field', + } as DataViewField, + ]), + } as unknown as IIndexPatternFieldList & { toSpec: () => DataViewFieldMap }, + } as DataView, + { + id: 'a-different-id', + title: 'My Data View', + timeFieldName: 'other-time-field', + fields: { + getByType: jest.fn(() => [ + { + type: 'date', + name: '@timestamp', + } as DataViewField, + { + type: 'date', + name: 'other-time-field', + } as DataViewField, + ]), + } as unknown as IIndexPatternFieldList & { toSpec: () => DataViewFieldMap }, + } as DataView, + ], + LensEmbeddableComponent, + searchSessionId: 'some-search-session-id', + refreshSearchSession: jest.fn(), + timePickerQuickRanges: [{ from: 'now/d', to: 'now/d', display: 'Today' }], + }; + + beforeEach(() => { + const renderResult = render( + + + + ); + + rerender = renderResult.rerender; + }); + + it('updates the chart time range', async () => { + // default + expect(EuiSuperDatePickerTestHarness.currentCommonlyUsedRange).toBe('Last 15 minutes'); + expect(getEmbeddableTimeRange()).toEqual({ from: 'now-15m', to: 'now' }); + + // from time picker + await EuiSuperDatePickerTestHarness.selectCommonlyUsedRange('Today'); + + expect(EuiSuperDatePickerTestHarness.currentCommonlyUsedRange).toBe('Today'); + expect(getEmbeddableTimeRange()).toEqual({ from: 'now/d', to: 'now/d' }); + + // from chart brush + userEvent.click(screen.getByTestId('brushEnd')); + + const format = 'MMM D, YYYY @ HH:mm:ss.SSS'; // from https://github.com/elastic/eui/blob/6a30eba7c2a154691c96a1d17c8b2f3506d351a3/src/components/date_picker/super_date_picker/super_date_picker.tsx#L222; + expect(EuiSuperDatePickerTestHarness.currentRange).toEqual({ + from: moment(BRUSH_RANGE[0]).format(format), + to: moment(BRUSH_RANGE[1]).format(format), + }); + expect(getEmbeddableTimeRange()).toEqual({ + from: new Date(BRUSH_RANGE[0]).toISOString(), + to: new Date(BRUSH_RANGE[1]).toISOString(), + }); + }); + + it('updates the time field', async () => { + EuiSuperDatePickerTestHarness.togglePopover(); + + const select = screen.getByRole('combobox', { name: 'Time field' }); + + expect(select).toHaveValue('@timestamp'); + expect(getCurrentTimeField(getLensAttributes())).toBe('@timestamp'); + + userEvent.selectOptions(select, 'other-time-field'); + + expect(select).toHaveValue('other-time-field'); + + await waitFor(() => { + expect(getCurrentTimeField(getLensAttributes())).toBe('other-time-field'); + }); + }); + + it('refreshes the chart data', () => { + expect(defaultProps.refreshSearchSession).not.toHaveBeenCalled(); + expect(getEmbeddableSearchSessionId()).toBe(defaultProps.searchSessionId); + + EuiSuperDatePickerTestHarness.refresh(); + + expect(defaultProps.refreshSearchSession).toHaveBeenCalled(); + + rerender( + + + + ); + + expect(getEmbeddableSearchSessionId()).toBe('new-search-session-id'); + }); + + describe('data views', () => { + const assertDataView = (id: string, attributes: TypedLensByValueInput['attributes']) => + expect(attributes.references[0].id).toBe(id); + const assertTimeField = (fieldName: string, attributes: TypedLensByValueInput['attributes']) => + expect( + ( + attributes.state.datasourceStates.formBased.layers[DATA_LAYER_ID].columns[ + DATE_HISTOGRAM_COLUMN_ID + ] as FieldBasedIndexPatternColumn + ).sourceField + ).toBe(fieldName); + + it('uses correct data view', async () => { + assertDataView(group.indexPatternId, getLensAttributes()); + + rerender( + + + + ); + + await waitFor(() => { + assertDataView('a-different-id', getLensAttributes()); + assertTimeField('other-time-field', getLensAttributes()); + }); + }); + + it('supports ad-hoc data view', () => { + const adHocDataView = { + id: 'adhoc-1', + title: 'my-pattern*', + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: 'My ad-hoc data view', + }; + + rerender( + + + + ); + + waitFor(() => { + const attributes = getLensAttributes(); + expect(attributes.references).toHaveLength(0); + expect(attributes.state.adHocDataViews![adHocDataView.id!]).toEqual(adHocDataView); + expect(attributes.state.internalReferences).toHaveLength(1); + expect(attributes.state.internalReferences![0].id).toBe(adHocDataView.id); + }); + }); + + it('handles missing data view', async () => { + rerender( + + + + ); + + await waitFor(() => { + expect( + screen.getByRole('heading', { name: 'Select a valid data view' }) + ).toBeInTheDocument(); + }); + expect(getLensAttributes()).toBeNull(); // chart shouldn't be rendered + }); + }); +}); diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.tsx new file mode 100644 index 0000000000000..3ee1a9a11f5fc --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.tsx @@ -0,0 +1,260 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiSelect, + EuiSuperDatePicker, + EuiSuperDatePickerProps, + EuiTitle, +} from '@elastic/eui'; +import { TimeRange } from '@kbn/es-query'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import type { + EmbeddableComponent as LensEmbeddableComponent, + TypedLensByValueInput, +} from '@kbn/lens-plugin/public'; +import { EventAnnotationGroupConfig } from '@kbn/event-annotation-common'; +import { css } from '@emotion/react'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { getLensAttributes } from './lens_attributes'; + +export const GroupPreview = ({ + group, + dataViews, + LensEmbeddableComponent, + searchSessionId, + refreshSearchSession, + timePickerQuickRanges, +}: { + group: EventAnnotationGroupConfig; + dataViews: DataView[]; + LensEmbeddableComponent: LensEmbeddableComponent; + searchSessionId: string; + refreshSearchSession: () => void; + timePickerQuickRanges: Array<{ from: string; to: string; display: string }> | undefined; +}) => { + const [chartTimeRange, setChartTimeRange] = useState({ from: 'now-15m', to: 'now' }); + const commonlyUsedRanges = useMemo( + () => + timePickerQuickRanges?.map( + ({ from, to, display }: { from: string; to: string; display: string }) => { + return { + start: from, + end: to, + label: display, + }; + } + ) ?? [], + [timePickerQuickRanges] + ); + + const customQuickSelectRender = useCallback< + Required['customQuickSelectRender'] + >( + ({ quickSelect, commonlyUsedRanges: ranges, customQuickSelectPanels }) => + ( + <> + {customQuickSelectPanels} + {quickSelect} + {ranges} + + ) as React.ReactNode, + [] + ); + + const currentDataView = useMemo( + () => dataViews.find((dataView) => dataView.id === group.indexPatternId), + [dataViews, group.indexPatternId] + ); + + const timeFieldNames = useMemo( + () => currentDataView?.fields.getByType('date').map((field) => field.name) ?? [], + [currentDataView?.fields] + ); + + // We can assume that there is at least one time field because we don't allow annotation groups to be created without one + const defaultTimeFieldName = useMemo( + () => currentDataView?.timeFieldName ?? timeFieldNames[0], + [currentDataView?.timeFieldName, timeFieldNames] + ); + + const [currentTimeFieldName, setCurrentTimeFieldName] = useState(defaultTimeFieldName); + + const [lensAttributes, setLensAttributes] = useState( + getLensAttributes(group, currentTimeFieldName) + ); + + // we don't use currentDataView directly to hide/show the missing prompt because we want to delay + // the embeddable render until the lensAttributes have been updated in useDebounce + // in the case that the user selects a new data view + const [showMissingDataViewPrompt, setShowMissingDataViewPrompt] = useState( + !currentDataView + ); + + useEffect(() => { + setCurrentTimeFieldName(defaultTimeFieldName); + }, [defaultTimeFieldName]); + + useDebounce( + () => { + setLensAttributes(getLensAttributes(group, currentTimeFieldName)); + setShowMissingDataViewPrompt(!currentDataView); + }, + 250, + [group, currentTimeFieldName] + ); + + return ( + <> + + + + +

    + +

    +
    +
    + + setChartTimeRange({ from, to })} + onRefresh={({ start: from, end: to }) => { + setChartTimeRange({ from, to }); + refreshSearchSession(); + }} + start={chartTimeRange.from} + end={chartTimeRange.to} + compressed + commonlyUsedRanges={commonlyUsedRanges} + updateButtonProps={{ + iconOnly: true, + fill: false, + }} + customQuickSelectRender={customQuickSelectRender} + customQuickSelectPanels={[ + { + title: i18n.translate('eventAnnotationListing.timeField', { + defaultMessage: 'Time field', + }), + content: ( + ({ + text: name, + }))} + value={currentTimeFieldName} + onChange={(e) => setCurrentTimeFieldName(e.target.value)} + /> + ), + }, + ]} + /> + +
    +
    + + {!showMissingDataViewPrompt ? ( + + +
    div { + height: 400px; + width: 100%; + } + `} + > + + setChartTimeRange({ + from: new Date(range[0]).toISOString(), + to: new Date(range[1]).toISOString(), + }) + } + searchSessionId={searchSessionId} + /> +
    +
    +
    + ) : ( + + + + +

    + } + body={ +

    + +

    + } + /> + + + )} + + + ); +}; diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/index.ts b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/index.ts new file mode 100644 index 0000000000000..312fe66a3e406 --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { GroupEditorFlyout } from './group_editor_flyout'; diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/lens_attributes.ts b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/lens_attributes.ts new file mode 100644 index 0000000000000..7943b1d905b6a --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/lens_attributes.ts @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EventAnnotationGroupConfig } from '@kbn/event-annotation-common'; +import { FieldBasedIndexPatternColumn, TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { XYPersistedByValueAnnotationLayerConfig } from '@kbn/lens-plugin/public/async_services'; + +export const DATA_LAYER_ID = 'data-layer-id'; +export const DATE_HISTOGRAM_COLUMN_ID = 'date-histogram-column-id'; +const ANNOTATION_LAYER_ID = 'annotation-layer-id'; + +export const getLensAttributes = (group: EventAnnotationGroupConfig, timeField: string) => + ({ + title: 'Line visualization with annotation layer', // TODO - should this be translated? + description: '', + visualizationType: 'lnsXY', + type: 'lens', + state: { + visualization: { + legend: { + isVisible: true, + position: 'right', + }, + valueLabels: 'hide', + fittingFunction: 'None', + axisTitlesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + tickLabelsVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + labelsOrientation: { + x: 0, + yLeft: 0, + yRight: 0, + }, + gridlinesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + preferredSeriesType: 'line', + layers: [ + { + layerId: DATA_LAYER_ID, + accessors: ['a7264a99-cd42-4b3f-855f-05364df71a71'], + position: 'top', + seriesType: 'line', + showGridlines: false, + layerType: 'data', + xAccessor: [DATE_HISTOGRAM_COLUMN_ID], + }, + { + layerId: ANNOTATION_LAYER_ID, + layerType: 'annotations', + persistanceType: 'byValue', + ...group, + } as XYPersistedByValueAnnotationLayerConfig, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + [DATA_LAYER_ID]: { + columns: { + [DATE_HISTOGRAM_COLUMN_ID]: { + label: 'timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: timeField, + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + } as FieldBasedIndexPatternColumn, + 'a7264a99-cd42-4b3f-855f-05364df71a71': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: true, + }, + } as FieldBasedIndexPatternColumn, + }, + columnOrder: [DATE_HISTOGRAM_COLUMN_ID, 'a7264a99-cd42-4b3f-855f-05364df71a71'], + incompleteColumns: {}, + sampling: 1, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + + ...(group.dataViewSpec + ? { + internalReferences: [ + { + type: 'index-pattern', + id: group.dataViewSpec.id!, + name: `indexpattern-datasource-layer-${DATA_LAYER_ID}`, + }, + { + type: 'index-pattern', + id: group.dataViewSpec.id!, + name: `xy-visualization-layer-${ANNOTATION_LAYER_ID}`, + }, + ], + adHocDataViews: { + [group.dataViewSpec.id!]: group.dataViewSpec, + }, + } + : { internalReferences: [], adHocDataViews: {} }), + }, + references: group.dataViewSpec + ? [] + : [ + { + type: 'index-pattern', + id: group.indexPatternId, + name: `indexpattern-datasource-layer-${DATA_LAYER_ID}`, + }, + ], + } as TypedLensByValueInput['attributes']); + +export const getCurrentTimeField = (attributes: TypedLensByValueInput['attributes']) => { + return ( + attributes.state.datasourceStates.formBased.layers[DATA_LAYER_ID].columns[ + DATE_HISTOGRAM_COLUMN_ID + ] as FieldBasedIndexPatternColumn + ).sourceField; +}; diff --git a/packages/kbn-event-annotation-components/components/table_list.test.tsx b/src/plugins/event_annotation_listing/public/components/table_list.test.tsx similarity index 85% rename from packages/kbn-event-annotation-components/components/table_list.test.tsx rename to src/plugins/event_annotation_listing/public/components/table_list.test.tsx index d8e124e37ef6e..65970aeca3c7d 100644 --- a/packages/kbn-event-annotation-components/components/table_list.test.tsx +++ b/src/plugins/event_annotation_listing/public/components/table_list.test.tsx @@ -16,7 +16,7 @@ import { TableListViewTable, type UserContentCommonSchema, } from '@kbn/content-management-table-list-view-table'; -import type { EventAnnotationServiceType } from '../types'; +import type { EventAnnotationServiceType } from '@kbn/event-annotation-components/types'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { shallow, ShallowWrapper } from 'enzyme'; import { @@ -31,6 +31,7 @@ import { DataView } from '@kbn/data-views-plugin/common'; import { QueryInputServices } from '@kbn/visualization-ui-components'; import { toastsServiceMock } from '@kbn/core-notifications-browser-mocks/src/toasts_service.mock'; import { IToasts } from '@kbn/core-notifications-browser'; +import { ISessionService } from '@kbn/data-plugin/public'; describe('annotation list view', () => { const adHocDVId = 'ad-hoc'; @@ -51,6 +52,7 @@ describe('annotation list view', () => { let wrapper: ShallowWrapper; let mockEventAnnotationService: EventAnnotationServiceType; let mockToasts: IToasts; + const searchSessionStartMethod = jest.fn(() => 'some-session-id'); beforeEach(() => { mockEventAnnotationService = { @@ -95,6 +97,10 @@ describe('annotation list view', () => { queryInputServices={{} as QueryInputServices} toasts={mockToasts} navigateToLens={() => {}} + LensEmbeddableComponent={() =>
    } + sessionService={ + { start: searchSessionStartMethod } as Partial as ISessionService + } /> ); }); @@ -190,6 +196,7 @@ describe('annotation list view', () => { expect(mockEventAnnotationService.loadAnnotationGroup).toHaveBeenCalledWith('1234'); expect(wrapper.find(GroupEditorFlyout).exists()).toBeTruthy(); + expect(wrapper.find(GroupEditorFlyout).prop('searchSessionId')).toBe('some-session-id'); const updatedGroup = { ...group, tags: ['my-new-tag'] }; @@ -210,6 +217,24 @@ describe('annotation list view', () => { ); // (should refresh list) }); + it('refreshes the search session', async () => { + act(() => { + wrapper.find(TableListViewTable).prop('editItem')!({ + id: '1234', + } as UserContentCommonSchema); + }); + + // wait one tick to give promise time to settle + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(wrapper.find(GroupEditorFlyout).prop('searchSessionId')).toBe('some-session-id'); + + searchSessionStartMethod.mockReturnValue('new-session-id'); + wrapper.find(GroupEditorFlyout).prop('refreshSearchSession')(); + + expect(wrapper.find(GroupEditorFlyout).prop('searchSessionId')).toBe('new-session-id'); + }); + it('opens editor when title is clicked', async () => { act(() => { wrapper.find(TableListViewTable).prop('onClickTitle')!({ diff --git a/src/plugins/event_annotation_listing/public/components/table_list.tsx b/src/plugins/event_annotation_listing/public/components/table_list.tsx new file mode 100644 index 0000000000000..603d39e193360 --- /dev/null +++ b/src/plugins/event_annotation_listing/public/components/table_list.tsx @@ -0,0 +1,248 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback, useEffect, useState } from 'react'; +import { TableListViewTable } from '@kbn/content-management-table-list-view-table'; +import type { TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view'; +import { i18n } from '@kbn/i18n'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import type { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-browser'; +import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; +import type { QueryInputServices } from '@kbn/visualization-ui-components'; +import { IToasts } from '@kbn/core-notifications-browser'; +import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiText, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { EmbeddableComponent as LensEmbeddableComponent } from '@kbn/lens-plugin/public'; +import type { + EventAnnotationGroupConfig, + EventAnnotationGroupContent, +} from '@kbn/event-annotation-common'; +import { ISessionService, UI_SETTINGS } from '@kbn/data-plugin/public'; +import { EventAnnotationServiceType } from '@kbn/event-annotation-components'; +import { css } from '@emotion/react'; +import { GroupEditorFlyout } from './group_editor_flyout'; + +export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; +export const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; + +const getCustomColumn = (dataViews: DataView[]) => { + const dataViewNameMap = Object.fromEntries( + dataViews.map((dataView) => [dataView.id, dataView.name ?? dataView.title]) + ); + + return { + field: 'dataView', + name: i18n.translate('eventAnnotationListing.tableList.dataView', { + defaultMessage: 'Data view', + }), + sortable: false, + width: '150px', + render: (_field: string, record: EventAnnotationGroupContent) => ( +
    + {record.attributes.dataViewSpec + ? record.attributes.dataViewSpec.name + : dataViewNameMap[record.attributes.indexPatternId] ?? ( + + + ), + }} + /> + + )} +
    + ), + }; +}; + +export const EventAnnotationGroupTableList = ({ + uiSettings, + eventAnnotationService, + sessionService, + visualizeCapabilities, + savedObjectsTagging, + parentProps, + dataViews, + createDataView, + queryInputServices, + toasts, + navigateToLens, + LensEmbeddableComponent, +}: { + uiSettings: IUiSettingsClient; + eventAnnotationService: EventAnnotationServiceType; + sessionService: ISessionService; + visualizeCapabilities: Record>; + savedObjectsTagging: SavedObjectsTaggingApi; + parentProps: TableListTabParentProps; + dataViews: DataView[]; + createDataView: (spec: DataViewSpec) => Promise; + queryInputServices: QueryInputServices; + toasts: IToasts; + navigateToLens: () => void; + LensEmbeddableComponent: LensEmbeddableComponent; +}) => { + const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); + const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); + + const [searchSessionId, setSearchSessionId] = useState(sessionService.start()); + + const refreshSearchSession = useCallback(() => { + setSearchSessionId(sessionService.start()); + }, [sessionService]); + + useEffect(() => { + return () => { + sessionService.clear(); + }; + }, [sessionService]); + + const [refreshListBouncer, setRefreshListBouncer] = useState(false); + + const refreshList = useCallback(() => { + setRefreshListBouncer((prev) => !prev); + }, []); + + const fetchItems = useCallback( + ( + searchTerm: string, + { + references, + referencesToExclude, + }: { + references?: SavedObjectsFindOptionsReference[]; + referencesToExclude?: SavedObjectsFindOptionsReference[]; + } = {} + ) => { + // todo - allow page size changes + return eventAnnotationService.findAnnotationGroupContent( + searchTerm, + listingLimit, // TODO is this right? + references?.map(({ id }) => id), + referencesToExclude?.map(({ id }) => id) + ); + }, + [eventAnnotationService, listingLimit] + ); + + const editItem = useCallback( + ({ id }: EventAnnotationGroupContent) => { + if (visualizeCapabilities.save) { + eventAnnotationService + .loadAnnotationGroup(id) + .then((group) => setGroupToEditInfo({ group, id })); + } + }, + [eventAnnotationService, visualizeCapabilities.save] + ); + + const [groupToEditInfo, setGroupToEditInfo] = useState<{ + group: EventAnnotationGroupConfig; + id: string; + }>(); + + const flyout = groupToEditInfo ? ( + setGroupToEditInfo({ group: newGroup, id: groupToEditInfo.id })} + onClose={() => setGroupToEditInfo(undefined)} + onSave={() => + (groupToEditInfo.id + ? eventAnnotationService.updateAnnotationGroup(groupToEditInfo.group, groupToEditInfo.id) + : eventAnnotationService.createAnnotationGroup(groupToEditInfo.group) + ).then(() => { + setGroupToEditInfo(undefined); + toasts.addSuccess(`Saved "${groupToEditInfo.group.title}"`); + refreshList(); + }) + } + savedObjectsTagging={savedObjectsTagging} + dataViews={dataViews} + createDataView={createDataView} + LensEmbeddableComponent={LensEmbeddableComponent} + queryInputServices={queryInputServices} + searchSessionId={searchSessionId} + refreshSearchSession={refreshSearchSession} + timePickerQuickRanges={uiSettings.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES)} + /> + ) : undefined; + + return ( + <> + + id="eventAnnotation" + refreshListBouncer={refreshListBouncer} + tableCaption={i18n.translate('eventAnnotationListing.tableList.listTitle', { + defaultMessage: 'Annotation Library', + })} + findItems={fetchItems} + deleteItems={ + visualizeCapabilities.delete + ? (items) => eventAnnotationService.deleteAnnotationGroups(items.map(({ id }) => id)) + : undefined + } + editItem={editItem} + listingLimit={listingLimit} + initialPageSize={initialPageSize} + initialFilter={''} + customTableColumn={getCustomColumn(dataViews)} + emptyPrompt={ + +

    + +

    + + } + body={ +

    + +

    + } + actions={ + + + + } + iconType="flag" + /> + } + entityName={i18n.translate('eventAnnotationListing.tableList.entityName', { + defaultMessage: 'annotation group', + })} + entityNamePlural={i18n.translate('eventAnnotationListing.tableList.entityNamePlural', { + defaultMessage: 'annotation groups', + })} + onClickTitle={editItem} + {...parentProps} + /> + {flyout} + + ); +}; diff --git a/src/plugins/event_annotation_listing/public/constants.ts b/src/plugins/event_annotation_listing/public/constants.ts new file mode 100644 index 0000000000000..7bd5fb8c9c1a7 --- /dev/null +++ b/src/plugins/event_annotation_listing/public/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const EVENT_ANNOTATION_APP_NAME = 'annotations'; diff --git a/src/plugins/event_annotation/public/get_table_list.tsx b/src/plugins/event_annotation_listing/public/get_table_list.tsx similarity index 82% rename from src/plugins/event_annotation/public/get_table_list.tsx rename to src/plugins/event_annotation_listing/public/get_table_list.tsx index d05eba6a0ecc8..0bcffc803cc94 100644 --- a/src/plugins/event_annotation/public/get_table_list.tsx +++ b/src/plugins/event_annotation_listing/public/get_table_list.tsx @@ -16,8 +16,10 @@ import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plug import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; import type { QueryInputServices } from '@kbn/visualization-ui-components'; import { RootDragDropProvider } from '@kbn/dom-drag-drop'; -import { EventAnnotationGroupTableList } from '@kbn/event-annotation-components'; -import type { EventAnnotationServiceType } from '.'; +import type { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; +import type { EmbeddableComponent as LensEmbeddableComponent } from '@kbn/lens-plugin/public'; +import { ISessionService } from '@kbn/data-plugin/public'; +import { EventAnnotationGroupTableList } from './components/table_list'; export interface EventAnnotationListingPageServices { core: CoreStart; @@ -27,6 +29,8 @@ export interface EventAnnotationListingPageServices { dataViews: DataView[]; createDataView: (spec: DataViewSpec) => Promise; queryInputServices: QueryInputServices; + LensEmbeddableComponent: LensEmbeddableComponent; + sessionService: ISessionService; } export const getTableList = ( @@ -54,6 +58,8 @@ export const getTableList = ( createDataView={services.createDataView} queryInputServices={services.queryInputServices} navigateToLens={() => services.core.application.navigateToApp('lens')} + LensEmbeddableComponent={services.LensEmbeddableComponent} + sessionService={services.sessionService} /> diff --git a/src/plugins/event_annotation_listing/public/index.ts b/src/plugins/event_annotation_listing/public/index.ts new file mode 100644 index 0000000000000..b81bd6424ba7a --- /dev/null +++ b/src/plugins/event_annotation_listing/public/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EventAnnotationListingPlugin } from './plugin'; +export const plugin = () => new EventAnnotationListingPlugin(); +export type { + EventAnnotationListingPluginSetup as eventAnnotationListingPluginSetup, + EventAnnotationListingPluginStart as eventAnnotationListingPluginStart, +} from './plugin'; +export { + defaultAnnotationColor, + defaultAnnotationRangeColor, + isRangeAnnotationConfig, + isManualPointAnnotationConfig, + isQueryAnnotationConfig, +} from '@kbn/event-annotation-common'; +export { + AnnotationEditorControls, + annotationsIconSet, + getAnnotationAccessor, +} from '@kbn/event-annotation-components'; diff --git a/src/plugins/event_annotation_listing/public/plugin.ts b/src/plugins/event_annotation_listing/public/plugin.ts new file mode 100644 index 0000000000000..a5db1a5ae135f --- /dev/null +++ b/src/plugins/event_annotation_listing/public/plugin.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Plugin, CoreSetup, CoreStart } from '@kbn/core/public'; +import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; +import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public/types'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { EventAnnotationPluginStart } from '@kbn/event-annotation-plugin/public'; +import type { LensPublicStart } from '@kbn/lens-plugin/public'; +import type { EventAnnotationListingPageServices } from './get_table_list'; + +export interface EventAnnotationListingStartDependencies { + savedObjectsManagement: SavedObjectsManagementPluginStart; + eventAnnotation: EventAnnotationPluginStart; + data: DataPublicPluginStart; + savedObjectsTagging: SavedObjectTaggingPluginStart; + presentationUtil: PresentationUtilPluginStart; + dataViews: DataViewsPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + contentManagement: ContentManagementPublicStart; + lens: LensPublicStart; +} + +interface SetupDependencies { + visualizations: VisualizationsSetup; +} + +/** @public */ +export type EventAnnotationListingPluginStart = void; +export type EventAnnotationListingPluginSetup = void; + +/** @public */ +export class EventAnnotationListingPlugin + implements + Plugin< + EventAnnotationListingPluginSetup, + EventAnnotationListingPluginStart, + SetupDependencies, + EventAnnotationListingStartDependencies + > +{ + public setup( + core: CoreSetup, + dependencies: SetupDependencies + ) { + dependencies.visualizations.listingViewRegistry.add({ + title: i18n.translate('eventAnnotationListing.listingViewTitle', { + defaultMessage: 'Annotation groups', + }), + id: 'annotations', + getTableList: async (props) => { + const [coreStart, pluginsStart] = await core.getStartServices(); + + const eventAnnotationService = await pluginsStart.eventAnnotation.getService(); + + const ids = await pluginsStart.dataViews.getIds(); + const dataViews = await Promise.all(ids.map((id) => pluginsStart.dataViews.get(id))); + + const services: EventAnnotationListingPageServices = { + core: coreStart, + LensEmbeddableComponent: pluginsStart.lens.EmbeddableComponent, + savedObjectsTagging: pluginsStart.savedObjectsTagging, + eventAnnotationService, + PresentationUtilContextProvider: pluginsStart.presentationUtil.ContextProvider, + dataViews, + createDataView: pluginsStart.dataViews.create.bind(pluginsStart.dataViews), + sessionService: pluginsStart.data.search.session, + queryInputServices: { + http: coreStart.http, + docLinks: coreStart.docLinks, + notifications: coreStart.notifications, + uiSettings: coreStart.uiSettings, + dataViews: pluginsStart.dataViews, + unifiedSearch: pluginsStart.unifiedSearch, + data: pluginsStart.data, + storage: new Storage(localStorage), + }, + }; + + const { getTableList } = await import('./get_table_list'); + return getTableList(props, services); + }, + }); + } + + public start(core: CoreStart, plugins: object): void { + // nothing to do here + } +} diff --git a/src/plugins/event_annotation_listing/tsconfig.json b/src/plugins/event_annotation_listing/tsconfig.json new file mode 100644 index 0000000000000..7d9a6b8d9b3be --- /dev/null +++ b/src/plugins/event_annotation_listing/tsconfig.json @@ -0,0 +1,48 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/data-plugin", + "@kbn/i18n", + "@kbn/data-views-plugin", + "@kbn/saved-objects-management-plugin", + "@kbn/saved-objects-tagging-plugin", + "@kbn/presentation-util-plugin", + "@kbn/visualizations-plugin", + "@kbn/data-views-plugin", + "@kbn/visualization-ui-components", + "@kbn/dom-drag-drop", + "@kbn/i18n-react", + "@kbn/saved-objects-tagging-oss-plugin", + "@kbn/kibana-react-plugin", + "@kbn/core-lifecycle-browser", + "@kbn/kibana-utils-plugin", + "@kbn/unified-search-plugin", + "@kbn/content-management-table-list-view-table", + "@kbn/content-management-tabbed-table-list-view", + "@kbn/content-management-plugin", + "@kbn/event-annotation-plugin", + "@kbn/event-annotation-components", + "@kbn/event-annotation-common", + "@kbn/lens-plugin", + "@kbn/ui-theme", + "@kbn/test-jest-helpers", + "@kbn/expressions-plugin", + "@kbn/es-query", + "@kbn/core-ui-settings-browser", + "@kbn/core-notifications-browser-mocks", + "@kbn/core-notifications-browser", + "@kbn/core-saved-objects-api-browser" + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts index 38b21addd5968..fbba26f3d4dc8 100644 --- a/src/plugins/expressions/common/expression_types/specs/datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts @@ -131,6 +131,7 @@ export interface Datatable { columns: DatatableColumn[]; meta?: DatatableMeta; rows: DatatableRow[]; + warning?: string; } export interface SerializedDatatable extends Datatable { diff --git a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts index 3d22f6156a966..a376fa61b3754 100644 --- a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts +++ b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts @@ -53,6 +53,8 @@ export interface FeatureCatalogueSolution { readonly path: string; /** An ordinal used to sort solutions relative to one another for display on the home page */ readonly order: number; + /** Optional function to control visibility of this solution. */ + readonly isVisible?: (capabilities: Capabilities) => boolean; } export class FeatureCatalogueRegistry { @@ -116,7 +118,10 @@ export class FeatureCatalogueRegistry { } const capabilities = this.capabilities; return [...this.solutions.values()] - .filter((solution) => capabilities.catalogue[solution.id] !== false) + .filter( + (solution) => + solution.isVisible?.(capabilities) ?? capabilities.catalogue[solution.id] !== false + ) .sort(compareByKey('title')); } diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/flights.json.gz b/src/plugins/home/server/services/sample_data/data_sets/flights/flights.json.gz index 1f6e10099416a..12f069db82226 100644 Binary files a/src/plugins/home/server/services/sample_data/data_sets/flights/flights.json.gz and b/src/plugins/home/server/services/sample_data/data_sets/flights/flights.json.gz differ diff --git a/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts b/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts index af3108ddafce9..361a9c74c0090 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts @@ -159,8 +159,14 @@ describe('SampleDataInstaller', () => { expect(esClient.asCurrentUser.indices.create).toHaveBeenCalledWith({ index: 'kibana_sample_data_test_single_data_index', body: { - settings: { index: { number_of_shards: 1, auto_expand_replicas: '0-1' } }, - mappings: { properties: { someField: { type: 'keyword' } } }, + mappings: { + properties: { + someField: { type: 'keyword' }, + }, + }, + settings: { + index: {}, + }, }, }); }); diff --git a/src/plugins/home/server/services/sample_data/sample_data_installer.ts b/src/plugins/home/server/services/sample_data/sample_data_installer.ts index 958f952fdd5a1..3f165a4a0e219 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_installer.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_installer.ts @@ -155,13 +155,13 @@ export class SampleDataInstaller { private async installDataIndex(dataset: SampleDatasetSchema, dataIndex: DataIndexSchema) { const index = createIndexName(dataset.id, dataIndex.id); + try { if (dataIndex.isDataStream) { const request = { name: index, body: { template: { - settings: { number_of_shards: 1, auto_expand_replicas: '0-1' }, mappings: { properties: dataIndex.fields }, }, index_patterns: [index], @@ -180,8 +180,6 @@ export class SampleDataInstaller { settings: { index: { ...dataIndex.indexSettings, - number_of_shards: 1, - auto_expand_replicas: '0-1', }, }, mappings: { properties: dataIndex.fields }, diff --git a/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.test.ts b/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.test.ts new file mode 100644 index 0000000000000..37b7a8dd6f283 --- /dev/null +++ b/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.test.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { moveRequestParamsToTopLevel } from './move_request_params_to_top_level'; +import { RequestStatus } from './types'; + +describe('moveRequestParamsToTopLevel', () => { + test('should move request meta from error response', () => { + expect( + moveRequestParamsToTopLevel(RequestStatus.ERROR, { + json: { + attributes: {}, + err: { + message: 'simulated error', + requestParams: { + method: 'POST', + path: '/_query', + }, + }, + }, + time: 1, + }) + ).toEqual({ + json: { + attributes: {}, + err: { + message: 'simulated error', + }, + }, + requestParams: { + method: 'POST', + path: '/_query', + }, + time: 1, + }); + }); + + test('should move request meta from ok response', () => { + expect( + moveRequestParamsToTopLevel(RequestStatus.OK, { + json: { + rawResponse: {}, + requestParams: { + method: 'POST', + path: '/_query', + }, + }, + time: 1, + }) + ).toEqual({ + json: { + rawResponse: {}, + }, + requestParams: { + method: 'POST', + path: '/_query', + }, + time: 1, + }); + }); +}); diff --git a/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.ts b/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.ts new file mode 100644 index 0000000000000..a00a2d90559c7 --- /dev/null +++ b/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ConnectionRequestParams } from '@elastic/transport'; +import { RequestStatus, Response } from './types'; + +interface ErrorResponse { + [key: string]: unknown; + err?: { + [key: string]: unknown; + requestParams?: ConnectionRequestParams; + }; +} + +interface OkResponse { + [key: string]: unknown; + requestParams?: ConnectionRequestParams; +} + +export function moveRequestParamsToTopLevel(status: RequestStatus, response: Response) { + if (status === RequestStatus.ERROR) { + const requestParams = (response.json as ErrorResponse)?.err?.requestParams; + if (!requestParams) { + return response; + } + + const json = { + ...response.json, + err: { ...(response.json as ErrorResponse).err }, + }; + delete json.err.requestParams; + return { + ...response, + json, + requestParams, + }; + } + + const requestParams = (response.json as OkResponse)?.requestParams; + if (!requestParams) { + return response; + } + + const json = { ...response.json } as OkResponse; + delete json.requestParams; + return { + ...response, + json, + requestParams, + }; +} diff --git a/src/plugins/inspector/common/adapters/request/request_responder.ts b/src/plugins/inspector/common/adapters/request/request_responder.ts index 1d3a999e4834d..cf3a4b6c223da 100644 --- a/src/plugins/inspector/common/adapters/request/request_responder.ts +++ b/src/plugins/inspector/common/adapters/request/request_responder.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { Request, RequestStatistics, RequestStatus, Response } from './types'; +import { moveRequestParamsToTopLevel } from './move_request_params_to_top_level'; /** * An API to specify information about a specific request that will be logged. @@ -53,7 +54,7 @@ export class RequestResponder { public finish(status: RequestStatus, response: Response): void { this.request.time = response.time ?? Date.now() - this.request.startTime; this.request.status = status; - this.request.response = response; + this.request.response = moveRequestParamsToTopLevel(status, response); this.onChange(); } diff --git a/src/plugins/inspector/common/adapters/request/types.ts b/src/plugins/inspector/common/adapters/request/types.ts index 4e6a8d324559f..d00e1304f74f5 100644 --- a/src/plugins/inspector/common/adapters/request/types.ts +++ b/src/plugins/inspector/common/adapters/request/types.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import type { ConnectionRequestParams } from '@elastic/transport'; + /** * The status a request can have. */ @@ -52,6 +54,8 @@ export interface RequestStatistic { } export interface Response { + // TODO replace object with IKibanaSearchResponse once IKibanaSearchResponse is seperated from data plugin. json?: object; + requestParams?: ConnectionRequestParams; time?: number; } diff --git a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx index 5ab50ba33a514..58f5dd44f3f11 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx @@ -12,6 +12,7 @@ /* eslint-disable @elastic/eui/href-or-on-click */ import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import type { ConnectionRequestParams } from '@elastic/transport'; import { i18n } from '@kbn/i18n'; import { XJsonLang } from '@kbn/monaco'; import { compressToEncodedURIComponent } from 'lz-string'; @@ -21,6 +22,7 @@ import { InspectorPluginStartDeps } from '../../../../plugin'; interface RequestCodeViewerProps { indexPattern?: string; + requestParams?: ConnectionRequestParams; json: string; } @@ -39,19 +41,37 @@ const openInSearchProfilerLabel = i18n.translate('inspector.requests.openInSearc /** * @internal */ -export const RequestCodeViewer = ({ indexPattern, json }: RequestCodeViewerProps) => { +export const RequestCodeViewer = ({ + indexPattern, + requestParams, + json, +}: RequestCodeViewerProps) => { const { services } = useKibana(); const navigateToUrl = services.application?.navigateToUrl; - const devToolsDataUri = compressToEncodedURIComponent(`GET ${indexPattern}/_search\n${json}`); + function getValue() { + if (!requestParams) { + return json; + } + + const fullPath = requestParams.querystring + ? `${requestParams.path}?${requestParams.querystring}` + : requestParams.path; + + return `${requestParams.method} ${fullPath}\n${json}`; + } + + const value = getValue(); + + const devToolsDataUri = compressToEncodedURIComponent(value); const consoleHref = services.share.url.locators .get('CONSOLE_APP_LOCATOR') ?.useUrl({ loadFrom: `data:text/plain,${devToolsDataUri}` }); // Check if both the Dev Tools UI and the Console UI are enabled. const canShowDevTools = services.application?.capabilities?.dev_tools.show && consoleHref !== undefined; - const shouldShowDevToolsLink = !!(indexPattern && canShowDevTools); + const shouldShowDevToolsLink = !!(requestParams && canShowDevTools); const handleDevToolsLinkClick = useCallback( () => consoleHref && navigateToUrl && navigateToUrl(consoleHref), [consoleHref, navigateToUrl] @@ -135,7 +155,7 @@ export const RequestCodeViewer = ({ indexPattern, json }: RequestCodeViewerProps { return ( ); diff --git a/src/plugins/interactive_setup/public/progress_indicator.tsx b/src/plugins/interactive_setup/public/progress_indicator.tsx index 9fee7e6da7110..5da14e8081555 100644 --- a/src/plugins/interactive_setup/public/progress_indicator.tsx +++ b/src/plugins/interactive_setup/public/progress_indicator.tsx @@ -27,14 +27,7 @@ function isKibanaPastPreboot(response?: Response, body?: StatusResponse) { if (!response?.headers.get('content-type')?.includes('application/json')) { return false; } - - return ( - // Status endpoint may require authentication after `preboot` stage. - response?.status === 401 || - // We're only interested in the availability of the critical core services. - (body?.status?.core?.elasticsearch?.level === 'available' && - body?.status?.core?.savedObjects?.level === 'available') - ); + return body?.status?.overall?.level === 'available'; } export const ProgressIndicator: FunctionComponent = ({ onSuccess }) => { diff --git a/src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_dashboards_dark.svg similarity index 100% rename from src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg rename to src/plugins/kibana_overview/public/assets/kibana_dashboards_dark.svg diff --git a/src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg b/src/plugins/kibana_overview/public/assets/kibana_dashboards_light.svg similarity index 100% rename from src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg rename to src/plugins/kibana_overview/public/assets/kibana_dashboards_light.svg diff --git a/src/plugins/kibana_overview/public/components/overview/overview.tsx b/src/plugins/kibana_overview/public/components/overview/overview.tsx index f6d97d54681e8..0df0e583699f7 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.tsx @@ -162,7 +162,7 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => }; // Dashboard and discover are displayed in larger cards - const mainApps = ['dashboard', 'discover']; + const mainApps = ['dashboards', 'discover']; const remainingApps = kibanaApps.map(({ id }) => id).filter((id) => !mainApps.includes(id)); const onDataViewCreated = () => { diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 2b43cbe6c5a1d..c7ec7deaa16a9 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -137,7 +137,7 @@ export const applicationUsageSchema = { enterpriseSearchContent: commonSchema, enterpriseSearchAnalytics: commonSchema, enterpriseSearchApplications: commonSchema, - enterpriseSearchEsre: commonSchema, + enterpriseSearchAISearch: commonSchema, enterpriseSearchVectorSearch: commonSchema, enterpriseSearchElasticsearch: commonSchema, appSearch: commonSchema, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 9822d2985bced..f17f1e1cc42e8 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -501,7 +501,7 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'discover:enableSql': { + 'discover:enableESQL': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 1b5f84019eb7b..902190f0cf675 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -34,7 +34,7 @@ export interface UsageStats { 'discover:searchFieldsFromSource': boolean; 'discover:showFieldStatistics': boolean; 'discover:showMultiFields': boolean; - 'discover:enableSql': boolean; + 'discover:enableESQL': boolean; 'discover:maxDocFieldsDisplayed': number; 'securitySolution:rulesTableRefresh': string; 'observability:enableInspectEsQueries': boolean; diff --git a/src/plugins/kibana_utils/server/report_server_error.ts b/src/plugins/kibana_utils/server/report_server_error.ts index 0fcc0c34cc4a9..a9fd5d9265bd3 100644 --- a/src/plugins/kibana_utils/server/report_server_error.ts +++ b/src/plugins/kibana_utils/server/report_server_error.ts @@ -7,14 +7,22 @@ */ import { errors } from '@elastic/elasticsearch'; +import type { ConnectionRequestParams } from '@elastic/transport'; import { KibanaResponseFactory } from '@kbn/core/server'; import { KbnError } from '../common'; export class KbnServerError extends KbnError { public errBody?: Record; - constructor(message: string, public readonly statusCode: number, errBody?: Record) { + public requestParams?: ConnectionRequestParams; + constructor( + message: string, + public readonly statusCode: number, + errBody?: Record, + requestParams?: ConnectionRequestParams + ) { super(message); this.errBody = errBody; + this.requestParams = requestParams; } } @@ -28,7 +36,8 @@ export function getKbnServerError(e: Error) { return new KbnServerError( e.message ?? 'Unknown error', e instanceof errors.ResponseError ? e.statusCode! : 500, - e instanceof errors.ResponseError ? e.body : undefined + e instanceof errors.ResponseError ? e.body : undefined, + e instanceof errors.ResponseError ? e.meta?.meta?.request?.params : undefined ); } @@ -43,6 +52,7 @@ export function reportServerError(res: KibanaResponseFactory, err: KbnServerErro body: { message: err.message, attributes: err.errBody?.error, + ...(err.requestParams ? { requestParams: err.requestParams } : {}), }, }); } diff --git a/src/plugins/management/kibana.jsonc b/src/plugins/management/kibana.jsonc index b180cd74fa3d3..1c5f3ebc4bf36 100644 --- a/src/plugins/management/kibana.jsonc +++ b/src/plugins/management/kibana.jsonc @@ -10,7 +10,8 @@ "share" ], "optionalPlugins": [ - "home" + "home", + "serverless" ], "requiredBundles": [ "kibanaReact", diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index f272b84aa2e0a..d4ef130eb4de0 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { BehaviorSubject } from 'rxjs'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; +import { ServerlessPluginStart } from '@kbn/serverless/public'; import { CoreSetup, CoreStart, @@ -39,6 +40,7 @@ interface ManagementSetupDependencies { interface ManagementStartDependencies { share: SharePluginStart; + serverless?: ServerlessPluginStart; } export class ManagementPlugin @@ -122,13 +124,21 @@ export class ManagementPlugin updater$: this.appUpdater, async mount(params: AppMountParameters) { const { renderApp } = await import('./application'); - const [coreStart] = await core.getStartServices(); + const [coreStart, deps] = await core.getStartServices(); return renderApp(params, { sections: getSectionsServiceStartPrivate(), kibanaVersion, coreStart, - setBreadcrumbs: coreStart.chrome.setBreadcrumbs, + setBreadcrumbs: (newBreadcrumbs) => { + if (deps.serverless) { + // drop the root management breadcrumb in serverless because it comes from the navigation tree + const [, ...trailingBreadcrumbs] = newBreadcrumbs; + deps.serverless.setBreadcrumbs(trailingBreadcrumbs); + } else { + coreStart.chrome.setBreadcrumbs(newBreadcrumbs); + } + }, isSidebarEnabled$: managementPlugin.isSidebarEnabled$, cardsNavigationConfig$: managementPlugin.cardsNavigationConfig$, landingPageRedirect$: managementPlugin.landingPageRedirect$, diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json index 3ad07523cecb0..77c3752e7b0c3 100644 --- a/src/plugins/management/tsconfig.json +++ b/src/plugins/management/tsconfig.json @@ -25,7 +25,8 @@ "@kbn/test-jest-helpers", "@kbn/config-schema", "@kbn/core-application-browser", - "@kbn/core-http-browser" + "@kbn/core-http-browser", + "@kbn/serverless" ], "exclude": [ "target/**/*" diff --git a/src/plugins/navigation_embeddable/common/mocks.tsx b/src/plugins/navigation_embeddable/common/mocks.tsx new file mode 100644 index 0000000000000..18e529d195b1e --- /dev/null +++ b/src/plugins/navigation_embeddable/common/mocks.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { buildMockDashboard } from '@kbn/dashboard-plugin/public/mocks'; +import { DashboardContainerInput } from '@kbn/dashboard-plugin/common'; +import { NavigationEmbeddableByValueInput } from '../public/embeddable/types'; +import { NavigationEmbeddableFactoryDefinition } from '../public'; +import { NavigationEmbeddableAttributes } from './content_management'; + +jest.mock('../public/services/attribute_service', () => { + return { + getNavigationEmbeddableAttributeService: jest.fn(() => { + return { + saveMethod: jest.fn(), + unwrapMethod: jest.fn(), + checkForDuplicateTitle: jest.fn(), + unwrapAttributes: jest.fn((attributes: NavigationEmbeddableByValueInput) => + Promise.resolve(attributes) + ), + wrapAttributes: jest.fn((attributes: NavigationEmbeddableAttributes) => + Promise.resolve(attributes) + ), + }; + }), + }; +}); + +export const mockNavigationEmbeddableInput = ( + partial?: Partial +): NavigationEmbeddableByValueInput => ({ + id: 'mocked_links_panel', + attributes: { + title: 'mocked_links', + }, + ...(partial ?? {}), +}); + +export const mockNavigationEmbeddable = async ({ + explicitInput, + dashboardExplicitInput, +}: { + explicitInput?: Partial; + dashboardExplicitInput?: Partial; +}) => { + const dashboardContainer = buildMockDashboard({ + overrides: dashboardExplicitInput, + savedObjectId: '123', + }); + const navigationEmbeddableFactoryStub = new NavigationEmbeddableFactoryDefinition(); + + const navigationEmbeddable = await navigationEmbeddableFactoryStub.create( + mockNavigationEmbeddableInput(explicitInput), + dashboardContainer + ); + + return navigationEmbeddable; +}; diff --git a/src/plugins/navigation_embeddable/jest.config.js b/src/plugins/navigation_embeddable/jest.config.js index d864b4a234669..208efe01a7815 100644 --- a/src/plugins/navigation_embeddable/jest.config.js +++ b/src/plugins/navigation_embeddable/jest.config.js @@ -15,4 +15,5 @@ module.exports = { collectCoverageFrom: [ '/src/plugins/navigation_embeddable/{common,public,server}/**/*.{ts,tsx}', ], + setupFiles: ['/src/plugins/navigation_embeddable/jest_setup.ts'], }; diff --git a/src/plugins/navigation_embeddable/jest_setup.ts b/src/plugins/navigation_embeddable/jest_setup.ts new file mode 100644 index 0000000000000..5ea91dfb399f4 --- /dev/null +++ b/src/plugins/navigation_embeddable/jest_setup.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { setStubDashboardServices } from '@kbn/dashboard-plugin/public/mocks'; +import { setStubKibanaServices } from './public/mocks'; + +setStubKibanaServices(); +setStubDashboardServices(); diff --git a/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.test.tsx b/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.test.tsx new file mode 100644 index 0000000000000..7c47314c952a1 --- /dev/null +++ b/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.test.tsx @@ -0,0 +1,227 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import userEvent from '@testing-library/user-event'; +import { createEvent, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS } from '@kbn/presentation-util-plugin/public'; +import { DashboardLinkStrings } from './dashboard_link_strings'; +import { + NavigationEmbeddable, + NavigationEmbeddableContext, +} from '../../embeddable/navigation_embeddable'; +import { mockNavigationEmbeddable } from '../../../common/mocks'; +import { NAV_VERTICAL_LAYOUT } from '../../../common/content_management'; +import { DashboardLinkComponent } from './dashboard_link_component'; +import { fetchDashboard, getDashboardHref, getDashboardLocator } from './dashboard_link_tools'; +import { coreServices } from '../../services/kibana_services'; + +jest.mock('./dashboard_link_tools'); + +describe('Dashboard link component', () => { + const mockDashboards = [ + { + id: '456', + status: 'success', + attributes: { + title: 'another dashboard', + description: 'something awesome', + panelsJSON: [], + timeRestore: false, + version: '1', + }, + }, + { + id: '123', + status: 'success', + attributes: { + title: 'current dashboard', + description: '', + panelsJSON: [], + timeRestore: false, + version: '1', + }, + }, + ]; + + const defaultLinkInfo = { + destination: '456', + order: 1, + id: 'foo', + type: 'dashboardLink' as const, + }; + + let navEmbeddable: NavigationEmbeddable; + beforeEach(async () => { + window.open = jest.fn(); + (fetchDashboard as jest.Mock).mockResolvedValue(mockDashboards[0]); + (getDashboardLocator as jest.Mock).mockResolvedValue({ + app: 'dashboard', + path: '/dashboardItem/456', + state: {}, + }); + (getDashboardHref as jest.Mock).mockReturnValue('https://my-kibana.com/dashboard/123'); + navEmbeddable = await mockNavigationEmbeddable({ + dashboardExplicitInput: mockDashboards[1].attributes, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('by default uses navigateToApp to open in same tab', async () => { + render( + + + + ); + + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + expect(fetchDashboard).toHaveBeenCalledWith(defaultLinkInfo.destination); + expect(getDashboardLocator).toHaveBeenCalledTimes(1); + expect(getDashboardLocator).toHaveBeenCalledWith({ + link: { + ...defaultLinkInfo, + options: DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, + }, + navEmbeddable, + }); + + const link = await screen.findByTestId('dashboardLink--foo'); + expect(link).toHaveTextContent('another dashboard'); + await userEvent.click(link); + expect(coreServices.application.navigateToApp).toBeCalledTimes(1); + expect(coreServices.application.navigateToApp).toBeCalledWith('dashboard', { + path: '/dashboardItem/456', + state: {}, + }); + }); + + test('modified click does not trigger event.preventDefault', async () => { + render( + + + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + const link = await screen.findByTestId('dashboardLink--foo'); + const clickEvent = createEvent.click(link, { ctrlKey: true }); + const preventDefault = jest.spyOn(clickEvent, 'preventDefault'); + fireEvent(link, clickEvent); + expect(preventDefault).toHaveBeenCalledTimes(0); + }); + + test('openInNewTab uses window.open, not navigateToApp', async () => { + const linkInfo = { + ...defaultLinkInfo, + options: { ...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, openInNewTab: true }, + }; + render( + + + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + expect(fetchDashboard).toHaveBeenCalledWith(linkInfo.destination); + expect(getDashboardLocator).toHaveBeenCalledWith({ link: linkInfo, navEmbeddable }); + const link = await screen.findByTestId('dashboardLink--foo'); + expect(link).toBeInTheDocument(); + await userEvent.click(link); + expect(coreServices.application.navigateToApp).toBeCalledTimes(0); + expect(window.open).toHaveBeenCalledWith('https://my-kibana.com/dashboard/123', '_blank'); + }); + + test('passes linkOptions to getDashboardLocator', async () => { + const linkInfo = { + ...defaultLinkInfo, + options: { + ...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, + useCurrentFilters: false, + useCurrentTimeRange: false, + useCurrentDateRange: false, + }, + }; + render( + + + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + expect(getDashboardLocator).toHaveBeenCalledWith({ link: linkInfo, navEmbeddable }); + }); + + test('shows an error when fetchDashboard fails', async () => { + (fetchDashboard as jest.Mock).mockRejectedValue(new Error('some error')); + const linkInfo = { + ...defaultLinkInfo, + id: 'notfound', + }; + render( + + + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + const link = await screen.findByTestId('dashboardLink--notfound--error'); + expect(link).toHaveTextContent(DashboardLinkStrings.getDashboardErrorLabel()); + }); + + test('current dashboard is not a clickable href', async () => { + const linkInfo = { + ...defaultLinkInfo, + destination: '123', + id: 'bar', + }; + render( + + + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + const link = await screen.findByTestId('dashboardLink--bar'); + expect(link).toHaveTextContent('current dashboard'); + await userEvent.click(link); + expect(coreServices.application.navigateToApp).toBeCalledTimes(0); + expect(window.open).toBeCalledTimes(0); + }); + + test('shows dashboard title and description in tooltip', async () => { + render( + + + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + const link = await screen.findByTestId('dashboardLink--foo'); + await userEvent.hover(link); + const tooltip = await screen.findByTestId('dashboardLink--foo--tooltip'); + expect(tooltip).toHaveTextContent('another dashboard'); // title + expect(tooltip).toHaveTextContent('something awesome'); // description + }); + + test('can override link label', async () => { + const label = 'my custom label'; + const linkInfo = { + ...defaultLinkInfo, + label, + }; + render( + + + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(1)); + const link = await screen.findByTestId('dashboardLink--foo'); + expect(link).toHaveTextContent(label); + await userEvent.hover(link); + const tooltip = await screen.findByTestId('dashboardLink--foo--tooltip'); + expect(tooltip).toHaveTextContent(label); + }); +}); diff --git a/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx b/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx index 47b127754b0fc..cf5040c18bf80 100644 --- a/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx +++ b/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx @@ -155,7 +155,11 @@ export const DashboardLinkComponent = ({ return loadingDestinationDashboard ? ( @@ -172,6 +176,7 @@ export const DashboardLinkComponent = ({ position: layout === NAV_VERTICAL_LAYOUT ? 'right' : 'bottom', repositionOnScroll: true, delay: 'long', + 'data-test-subj': `dashboardLink--${link.id}--tooltip`, }} iconType={error ? 'warning' : undefined} iconProps={{ className: 'dashboardLinkIcon' }} @@ -182,6 +187,7 @@ export const DashboardLinkComponent = ({ 'dashboardLinkError--noLabel': !link.label, })} label={linkLabel} + data-test-subj={error ? `dashboardLink--${link.id}--error` : `dashboardLink--${link.id}`} /> ); }; diff --git a/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor.test.tsx b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor.test.tsx new file mode 100644 index 0000000000000..250695cfc0a1d --- /dev/null +++ b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor.test.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen, waitFor } from '@testing-library/react'; +import NavigationEmbeddablePanelEditor from './navigation_embeddable_panel_editor'; +import { NavEmbeddableStrings } from '../navigation_embeddable_strings'; +import { NavigationEmbeddableLink, NAV_VERTICAL_LAYOUT } from '../../../common/content_management'; +import { fetchDashboard } from '../dashboard_link/dashboard_link_tools'; + +jest.mock('../dashboard_link/dashboard_link_tools', () => { + return { + fetchDashboard: jest.fn().mockImplementation((id: string) => + Promise.resolve({ + id, + status: 'success', + attributes: { + title: `dashboard #${id}`, + description: '', + panelsJSON: [], + timeRestore: false, + version: '1', + }, + references: [], + }) + ), + }; +}); + +describe('NavigationEmbeddablePanelEditor', () => { + const defaultProps = { + onSaveToLibrary: jest.fn().mockImplementation(() => Promise.resolve()), + onAddToDashboard: jest.fn(), + onClose: jest.fn(), + isByReference: false, + }; + + const someLinks: NavigationEmbeddableLink[] = [ + { + id: 'foo', + type: 'dashboardLink' as const, + order: 1, + destination: '123', + }, + { + id: 'bar', + type: 'dashboardLink' as const, + order: 4, + destination: '456', + }, + { + id: 'bizz', + type: 'externalLink' as const, + order: 3, + destination: 'http://example.com', + }, + { + id: 'buzz', + type: 'externalLink' as const, + order: 2, + destination: 'http://elastic.co', + }, + ]; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('shows empty state with no links', async () => { + render(); + expect(screen.getByTestId('navEmbeddable--panelEditor--title')).toHaveTextContent( + NavEmbeddableStrings.editor.panelEditor.getCreateFlyoutTitle() + ); + expect(screen.getByTestId('navEmbeddable--panelEditor--emptyPrompt')).toBeInTheDocument(); + expect(screen.getByTestId('navEmbeddable--panelEditor--saveBtn')).toBeDisabled(); + + await userEvent.click(screen.getByTestId('navEmbeddable--panelEditor--closeBtn')); + expect(defaultProps.onClose).toHaveBeenCalledTimes(1); + }); + + test('shows links in order', async () => { + const expectedLinkIds = [...someLinks].sort((a, b) => a.order - b.order).map(({ id }) => id); + render(); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(2)); + expect(screen.getByTestId('navEmbeddable--panelEditor--title')).toHaveTextContent( + NavEmbeddableStrings.editor.panelEditor.getEditFlyoutTitle() + ); + const draggableLinks = screen.getAllByTestId('navEmbeddable--panelEditor--draggableLink'); + expect(draggableLinks.length).toEqual(4); + + draggableLinks.forEach((link, idx) => { + expect(link).toHaveAttribute('data-rfd-draggable-id', expectedLinkIds[idx]); + }); + }); + + test('saving by reference panels calls onSaveToLibrary', async () => { + const orderedLinks = [...someLinks].sort((a, b) => a.order - b.order); + render( + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(2)); + const saveButton = screen.getByTestId('navEmbeddable--panelEditor--saveBtn'); + await userEvent.click(saveButton); + await waitFor(() => expect(defaultProps.onSaveToLibrary).toHaveBeenCalledTimes(1)); + expect(defaultProps.onSaveToLibrary).toHaveBeenCalledWith(orderedLinks, NAV_VERTICAL_LAYOUT); + }); + + test('saving by value panel calls onAddToDashboard', async () => { + const orderedLinks = [...someLinks].sort((a, b) => a.order - b.order); + render( + + ); + await waitFor(() => expect(fetchDashboard).toHaveBeenCalledTimes(2)); + const saveButton = screen.getByTestId('navEmbeddable--panelEditor--saveBtn'); + await userEvent.click(saveButton); + expect(defaultProps.onAddToDashboard).toHaveBeenCalledTimes(1); + expect(defaultProps.onAddToDashboard).toHaveBeenCalledWith(orderedLinks, NAV_VERTICAL_LAYOUT); + }); +}); diff --git a/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor.tsx b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor.tsx index da59d720145bd..3028c22fa48b3 100644 --- a/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor.tsx +++ b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor.tsx @@ -124,13 +124,16 @@ const NavigationEmbeddablePanelEditor = ({ setOrderedLinks( orderedLinks.map((link) => { if (link.id === linkToEdit.id) { - return { ...newLink, order: linkToEdit.order }; + return { ...newLink, order: linkToEdit.order } as NavigationEmbeddableLink; } return link; }) ); } else { - setOrderedLinks([...orderedLinks, { ...newLink, order: orderedLinks.length }]); + setOrderedLinks([ + ...orderedLinks, + { ...newLink, order: orderedLinks.length } as NavigationEmbeddableLink, + ]); } } }, @@ -156,7 +159,7 @@ const NavigationEmbeddablePanelEditor = ({ <>
    - +

    {isEditingExisting ? NavEmbeddableStrings.editor.panelEditor.getEditFlyoutTitle() @@ -166,25 +169,25 @@ const NavigationEmbeddablePanelEditor = ({ - {hasZeroLinks ? ( - addOrEditLink()} /> - ) : ( - <> - - { - setCurrentLayout(id as NavigationLayoutType); - }} - legend={NavEmbeddableStrings.editor.panelEditor.getLayoutSettingsLegend()} - /> - - - {/* Needs to be surrounded by a div rather than a fragment so the EuiFormRow can respond - to the focus of the inner elements */} -
    + + { + setCurrentLayout(id as NavigationLayoutType); + }} + legend={NavEmbeddableStrings.editor.panelEditor.getLayoutSettingsLegend()} + /> + + + {/* Needs to be surrounded by a div rather than a fragment so the EuiFormRow can respond + to the focus of the inner elements */} +
    + {hasZeroLinks ? ( + addOrEditLink()} /> + ) : ( + <> {(provided) => ( {NavEmbeddableStrings.editor.getAddButtonLabel()} -
    -
    - - )} + + )} +
    +
    - + {NavEmbeddableStrings.editor.getCancelButtonLabel()} @@ -240,12 +249,14 @@ const NavigationEmbeddablePanelEditor = ({ setSaveByReference(!saveByReference)} + data-test-subj="navEmbeddable--panelEditor--saveByReferenceSwitch" /> @@ -254,11 +265,13 @@ const NavigationEmbeddablePanelEditor = ({ { if (saveByReference) { setIsSaving(true); diff --git a/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_empty_prompt.tsx b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_empty_prompt.tsx index 71083dbf87449..1fd18f4730837 100644 --- a/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_empty_prompt.tsx +++ b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_empty_prompt.tsx @@ -7,47 +7,23 @@ */ import React from 'react'; -import useObservable from 'react-use/lib/useObservable'; -import { - EuiText, - EuiImage, - EuiPanel, - EuiSpacer, - EuiButton, - EuiEmptyPrompt, - EuiFormRow, -} from '@elastic/eui'; +import { EuiText, EuiPanel, EuiSpacer, EuiButton, EuiEmptyPrompt, EuiFormRow } from '@elastic/eui'; -import { coreServices } from '../../services/kibana_services'; import { NavEmbeddableStrings } from '../navigation_embeddable_strings'; -import noLinksIllustrationDark from '../../assets/empty_links_dark.svg'; -import noLinksIllustrationLight from '../../assets/empty_links_light.svg'; - -import './navigation_embeddable_editor.scss'; - export const NavigationEmbeddablePanelEditorEmptyPrompt = ({ addLink, }: { addLink: () => Promise; }) => { - const isDarkTheme = useObservable(coreServices.theme.theme$)?.darkMode; - return ( - + - } + hasShadow={false} + paddingSize="none" body={ <> diff --git a/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_link.tsx b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_link.tsx index f73aec1104653..4d1fdf2636adc 100644 --- a/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_link.tsx +++ b/src/plugins/navigation_embeddable/public/components/editor/navigation_embeddable_panel_editor_link.tsx @@ -49,7 +49,7 @@ export const NavigationEmbeddablePanelEditorLink = ({ const { value: linkLabel, loading: linkLabelLoading } = useAsync(async () => { if (!link.destination) { - setDestinationError(DashboardLinkStrings.getDashboardErrorLabel()); + setDestinationError(new Error(DashboardLinkStrings.getDashboardErrorLabel())); return; } @@ -134,6 +134,7 @@ export const NavigationEmbeddablePanelEditorLink = ({ hasShadow={false} color={destinationError ? 'warning' : 'plain'} className={`navEmbeddableLinkPanel ${destinationError ? 'linkError' : ''}`} + data-test-subj={`panelEditorLink${linkLabelLoading ? '--loading' : ''}`} > diff --git a/src/plugins/navigation_embeddable/public/components/external_link/external_link_component.test.tsx b/src/plugins/navigation_embeddable/public/components/external_link/external_link_component.test.tsx new file mode 100644 index 0000000000000..1945c9e762a7d --- /dev/null +++ b/src/plugins/navigation_embeddable/public/components/external_link/external_link_component.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import userEvent from '@testing-library/user-event'; +import { createEvent, fireEvent, render, screen } from '@testing-library/react'; +import { + NavigationEmbeddable, + NavigationEmbeddableContext, +} from '../../embeddable/navigation_embeddable'; +import { mockNavigationEmbeddable } from '../../../common/mocks'; +import { NAV_VERTICAL_LAYOUT } from '../../../common/content_management'; +import { ExternalLinkComponent } from './external_link_component'; +import { coreServices } from '../../services/kibana_services'; +import { DEFAULT_URL_DRILLDOWN_OPTIONS } from '@kbn/ui-actions-enhanced-plugin/public'; + +describe('external link component', () => { + const defaultLinkInfo = { + destination: 'https://example.com', + order: 1, + id: 'foo', + type: 'externalLink' as const, + }; + + let navEmbeddable: NavigationEmbeddable; + beforeEach(async () => { + window.open = jest.fn(); + navEmbeddable = await mockNavigationEmbeddable({}); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('by default opens in new tab', async () => { + render( + + + + ); + + const link = await screen.findByTestId('externalLink--foo'); + expect(link).toBeInTheDocument(); + await userEvent.click(link); + expect(window.open).toHaveBeenCalledWith('https://example.com', '_blank'); + }); + + test('modified click does not trigger event.preventDefault', async () => { + const linkInfo = { + ...defaultLinkInfo, + options: { ...DEFAULT_URL_DRILLDOWN_OPTIONS, openInNewTab: false }, + }; + render( + + + + ); + const link = await screen.findByTestId('externalLink--foo'); + expect(link).toHaveTextContent('https://example.com'); + const clickEvent = createEvent.click(link, { ctrlKey: true }); + const preventDefault = jest.spyOn(clickEvent, 'preventDefault'); + fireEvent(link, clickEvent); + expect(preventDefault).toHaveBeenCalledTimes(0); + }); + + test('uses navigateToUrl when openInNewTab is false', async () => { + const linkInfo = { + ...defaultLinkInfo, + options: { ...DEFAULT_URL_DRILLDOWN_OPTIONS, openInNewTab: false }, + }; + render( + + + + ); + const link = await screen.findByTestId('externalLink--foo'); + await userEvent.click(link); + expect(coreServices.application.navigateToUrl).toBeCalledTimes(1); + expect(coreServices.application.navigateToUrl).toBeCalledWith('https://example.com'); + }); + + test('disables link when url validation fails', async () => { + const linkInfo = { + ...defaultLinkInfo, + destination: 'file://buzz', + }; + render( + + + + ); + const link = await screen.findByTestId('externalLink--foo--error'); + expect(link).toBeDisabled(); + /** + * TODO: We should test the tooltip content, but the component is disabled + * so it has pointer-events: none. This means we can not use userEvent.hover(). + * See https://testing-library.com/docs/ecosystem-user-event#pointer-events-options + */ + }); +}); diff --git a/src/plugins/navigation_embeddable/public/components/external_link/external_link_component.tsx b/src/plugins/navigation_embeddable/public/components/external_link/external_link_component.tsx index 68886610f94e6..379c04105a81f 100644 --- a/src/plugins/navigation_embeddable/public/components/external_link/external_link_component.tsx +++ b/src/plugins/navigation_embeddable/public/components/external_link/external_link_component.tsx @@ -81,10 +81,12 @@ export const ExternalLinkComponent = ({ position: layout === NAV_VERTICAL_LAYOUT ? 'right' : 'bottom', repositionOnScroll: true, delay: 'long', + 'data-test-subj': `externalLink--${link.id}--tooltip`, }} iconType={error ? 'warning' : undefined} id={`externalLink--${link.id}`} label={link.label || link.destination} + data-test-subj={error ? `externalLink--${link.id}--error` : `externalLink--${link.id}`} href={destination} target={linkOptions.openInNewTab ? '_blank' : ''} extraAction={ diff --git a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts index efeee7235a89e..7723eb1e5d8c0 100644 --- a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts +++ b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts @@ -9,6 +9,10 @@ import { i18n } from '@kbn/i18n'; export const NavEmbeddableStrings = { + getDescription: () => + i18n.translate('navigationEmbeddable.description', { + defaultMessage: 'Use links to navigate to commonly used dashboards and websites.', + }), editor: { getAddButtonLabel: () => i18n.translate('navigationEmbeddable.editor.addButtonLabel', { @@ -37,7 +41,7 @@ export const NavEmbeddableStrings = { }), getEmptyLinksMessage: () => i18n.translate('navigationEmbeddable.panelEditor.emptyLinksMessage', { - defaultMessage: 'Use links to navigate to commonly used dashboards and websites.', + defaultMessage: "You haven't added any links yet.", }), getEmptyLinksTooltip: () => i18n.translate('navigationEmbeddable.panelEditor.emptyLinksTooltip', { diff --git a/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx b/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx index 14e3974473d94..e51606927a912 100644 --- a/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx +++ b/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx @@ -18,6 +18,7 @@ import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_conta import { NavigationEmbeddableInput, NavigationEmbeddableByReferenceInput, + NavigationEmbeddableEditorFlyoutReturn, } from '../embeddable/types'; import { coreServices } from '../services/kibana_services'; import { runSaveToLibrary } from '../content_management/save_to_library'; @@ -41,7 +42,7 @@ const NavigationEmbeddablePanelEditor = withSuspense( export async function openEditorFlyout( initialInput: NavigationEmbeddableInput, parentDashboard?: DashboardContainer -): Promise> { +): Promise { const attributeService = getNavigationEmbeddableAttributeService(); const { attributes } = await attributeService.unwrapAttributes(initialInput); const isByReference = attributeService.inputIsRefType(initialInput); @@ -64,7 +65,12 @@ export async function openEditorFlyout( if (!updatedInput) { return; } - resolve(updatedInput); + resolve({ + newInput: updatedInput, + + // pass attributes via attributes so that the Dashboard can choose the right panel size. + attributes: newAttributes, + }); parentDashboard?.reload(); editorFlyout.close(); }; @@ -73,15 +79,21 @@ export async function openEditorFlyout( newLinks: NavigationEmbeddableLink[], newLayout: NavigationLayoutType ) => { + const newAttributes = { + ...attributes, + links: newLinks, + layout: newLayout, + }; const newInput: NavigationEmbeddableInput = { ...initialInput, - attributes: { - ...attributes, - links: newLinks, - layout: newLayout, - }, + attributes: newAttributes, }; - resolve(newInput); + resolve({ + newInput, + + // pass attributes so that the Dashboard can choose the right panel size. + attributes: newAttributes, + }); parentDashboard?.reload(); editorFlyout.close(); }; diff --git a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.test.ts b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.test.ts new file mode 100644 index 0000000000000..c1dc4deee581f --- /dev/null +++ b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NavigationEmbeddableFactoryDefinition } from './navigation_embeddable_factory'; +import { NavigationEmbeddableInput } from './types'; + +describe('navigationEmbeddableFactory', () => { + test('returns an empty object when not given proper meta information', () => { + const navigationEmbeddableFactory = new NavigationEmbeddableFactoryDefinition(); + const settings = navigationEmbeddableFactory.getPanelPlacementSettings( + {} as unknown as NavigationEmbeddableInput, + {} + ); + expect(settings.height).toBeUndefined(); + expect(settings.width).toBeUndefined(); + expect(settings.strategy).toBeUndefined(); + }); + + test('returns a horizontal layout', () => { + const navigationEmbeddableFactory = new NavigationEmbeddableFactoryDefinition(); + const settings = navigationEmbeddableFactory.getPanelPlacementSettings( + {} as unknown as NavigationEmbeddableInput, + { layout: 'horizontal', links: [] } + ); + expect(settings.height).toBe(4); + expect(settings.width).toBe(48); + expect(settings.strategy).toBe('placeAtTop'); + }); + + test('returns a vertical layout with the appropriate height', () => { + const navigationEmbeddableFactory = new NavigationEmbeddableFactoryDefinition(); + const settings = navigationEmbeddableFactory.getPanelPlacementSettings( + {} as unknown as NavigationEmbeddableInput, + { + layout: 'vertical', + links: [ + { type: 'dashboardLink', destination: 'superDashboard1' }, + { type: 'dashboardLink', destination: 'superDashboard2' }, + { type: 'dashboardLink', destination: 'superDashboard3' }, + ], + } + ); + expect(settings.height).toBe(7); // 4 base plus 3 for each link. + expect(settings.width).toBe(8); + expect(settings.strategy).toBe('placeAtTop'); + }); +}); diff --git a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts index 21d5b1d93cb31..dcd8e51382b06 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts +++ b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts @@ -14,12 +14,26 @@ import { import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public'; import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container'; -import { NavigationEmbeddableByReferenceInput, NavigationEmbeddableInput } from './types'; +import { IProvidesPanelPlacementSettings } from '@kbn/dashboard-plugin/public/dashboard_container/component/panel_placement/types'; +import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; +import { + MigrateFunctionsObject, + GetMigrationFunctionObjectFn, +} from '@kbn/kibana-utils-plugin/common'; +import { UiActionsPresentableGrouping } from '@kbn/ui-actions-plugin/public'; +import { DASHBOARD_GRID_COLUMN_COUNT } from '@kbn/dashboard-plugin/public'; +import { + NavigationEmbeddableInput, + NavigationEmbeddableByReferenceInput, + NavigationEmbeddableEditorFlyoutReturn, +} from './types'; +import { extract, inject } from '../../common/embeddable'; import { APP_ICON, APP_NAME, CONTENT_ID } from '../../common'; import type { NavigationEmbeddable } from './navigation_embeddable'; +import { NavigationEmbeddableAttributes } from '../../common/content_management'; +import { NavEmbeddableStrings } from '../components/navigation_embeddable_strings'; import { getNavigationEmbeddableAttributeService } from '../services/attribute_service'; import { coreServices, untilPluginStartServicesReady } from '../services/kibana_services'; -import { extract, inject } from '../../common/embeddable'; export type NavigationEmbeddableFactory = EmbeddableFactory; @@ -29,9 +43,29 @@ const getDefaultNavigationEmbeddableInput = (): Partial { + return ( + attributes !== undefined && + Boolean( + (attributes as NavigationEmbeddableAttributes).layout || + (attributes as NavigationEmbeddableAttributes).links + ) + ); +}; + export class NavigationEmbeddableFactoryDefinition - implements EmbeddableFactoryDefinition + implements + EmbeddableFactoryDefinition, + IProvidesPanelPlacementSettings { + latestVersion?: string | undefined; + telemetry?: + | ((state: EmbeddableStateWithType, stats: Record) => Record) + | undefined; + migrations?: MigrateFunctionsObject | GetMigrationFunctionObjectFn | undefined; + grouping?: UiActionsPresentableGrouping | undefined; public readonly type = CONTENT_ID; public readonly isContainerType = false; @@ -42,6 +76,21 @@ export class NavigationEmbeddableFactoryDefinition getIconForSavedObject: () => APP_ICON, }; + public getPanelPlacementSettings: IProvidesPanelPlacementSettings< + NavigationEmbeddableInput, + NavigationEmbeddableAttributes | unknown + >['getPanelPlacementSettings'] = (input, attributes) => { + if (!isNavigationEmbeddableAttributes(attributes) || !attributes.layout) { + // if we have no information about the layout of this nav embeddable defer to default panel size and placement. + return {}; + } + + const isHorizontal = attributes.layout === 'horizontal'; + const width = isHorizontal ? DASHBOARD_GRID_COLUMN_COUNT : 8; + const height = isHorizontal ? 4 : (attributes.links?.length ?? 1 * 3) + 4; + return { width, height, strategy: 'placeAtTop' }; + }; + public async isEditable() { await untilPluginStartServicesReady(); return Boolean(coreServices.application.capabilities.dashboard?.showWriteControls); @@ -85,12 +134,12 @@ export class NavigationEmbeddableFactoryDefinition public async getExplicitInput( initialInput: NavigationEmbeddableInput, parent?: DashboardContainer - ): Promise> { - if (!parent) return {}; + ): Promise { + if (!parent) return { newInput: {} }; const { openEditorFlyout } = await import('../editor/open_editor_flyout'); - const input = await openEditorFlyout( + const { newInput, attributes } = await openEditorFlyout( { ...getDefaultNavigationEmbeddableInput(), ...initialInput, @@ -98,7 +147,7 @@ export class NavigationEmbeddableFactoryDefinition parent ); - return input; + return { newInput, attributes }; } public getDisplayName() { @@ -109,6 +158,10 @@ export class NavigationEmbeddableFactoryDefinition return 'link'; } + public getDescription() { + return NavEmbeddableStrings.getDescription(); + } + inject = inject; extract = extract; diff --git a/src/plugins/navigation_embeddable/public/embeddable/types.ts b/src/plugins/navigation_embeddable/public/embeddable/types.ts index 8744dd613c4b5..2804712da504d 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/types.ts +++ b/src/plugins/navigation_embeddable/public/embeddable/types.ts @@ -65,6 +65,11 @@ export const NavigationLinkInfo: { }, }; +export interface NavigationEmbeddableEditorFlyoutReturn { + attributes?: unknown; + newInput: Partial; +} + export type NavigationEmbeddableByValueInput = { attributes: NavigationEmbeddableAttributes; } & EmbeddableInput; diff --git a/src/plugins/navigation_embeddable/public/mocks.tsx b/src/plugins/navigation_embeddable/public/mocks.tsx new file mode 100644 index 0000000000000..7268893babcba --- /dev/null +++ b/src/plugins/navigation_embeddable/public/mocks.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import { dashboardPluginMock } from '@kbn/dashboard-plugin/public/mocks'; +import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; +import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks'; +import { setKibanaServices } from './services/kibana_services'; + +export const setStubKibanaServices = () => { + const core = coreMock.createStart(); + + setKibanaServices(core, { + dashboard: dashboardPluginMock.createStartContract(), + embeddable: embeddablePluginMock.createStartContract(), + contentManagement: contentManagementMock.createStartContract(), + }); +}; diff --git a/src/plugins/navigation_embeddable/tsconfig.json b/src/plugins/navigation_embeddable/tsconfig.json index 72001ee53bef8..8a0b7e037928c 100644 --- a/src/plugins/navigation_embeddable/tsconfig.json +++ b/src/plugins/navigation_embeddable/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["public/**/*", "common/**/*", "server/**/*", "public/**/*.json"], + "include": ["*.ts", "public/**/*", "common/**/*", "server/**/*", "public/**/*.json"], "kbn_references": [ "@kbn/core", "@kbn/i18n", @@ -24,7 +24,8 @@ "@kbn/es-query", "@kbn/share-plugin", "@kbn/kibana-utils-plugin", - "@kbn/utility-types" + "@kbn/utility-types", + "@kbn/ui-actions-plugin" ], "exclude": ["target/**/*"] } diff --git a/src/plugins/presentation_util/public/components/dashboard_picker.tsx b/src/plugins/presentation_util/public/components/dashboard_picker.tsx index 47ba570765028..0207fe1dc3458 100644 --- a/src/plugins/presentation_util/public/components/dashboard_picker.tsx +++ b/src/plugins/presentation_util/public/components/dashboard_picker.tsx @@ -52,7 +52,7 @@ export function DashboardPicker(props: DashboardPickerProps) { if (objects) { setDashboardOptions( objects - .filter((d) => !props.idsToOmit || !props.idsToOmit.includes(d.id)) + .filter((d) => !d.managed && !(props.idsToOmit ?? []).includes(d.id)) .map((d) => ({ value: d.id, label: d.attributes.title, diff --git a/src/plugins/saved_search/common/content_management/v1/cm_services.ts b/src/plugins/saved_search/common/content_management/v1/cm_services.ts index a59645b3ad4ac..781f111b18bfb 100644 --- a/src/plugins/saved_search/common/content_management/v1/cm_services.ts +++ b/src/plugins/saved_search/common/content_management/v1/cm_services.ts @@ -16,7 +16,7 @@ import { createResultSchema, } from '@kbn/content-management-utils'; -const sortSchema = schema.arrayOf(schema.string(), { minSize: 2, maxSize: 2 }); +const sortSchema = schema.arrayOf(schema.string(), { maxSize: 2 }); const savedSearchAttributesSchema = schema.object( { diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 7beb57daee234..bc8686101eb21 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -2360,7 +2360,7 @@ } } }, - "enterpriseSearchEsre": { + "enterpriseSearchAISearch": { "properties": { "appId": { "type": "keyword", @@ -9941,7 +9941,7 @@ "description": "Non-default value of setting." } }, - "discover:enableSql": { + "discover:enableESQL": { "type": "boolean", "_meta": { "description": "Non-default value of setting." diff --git a/src/plugins/text_based_languages/kibana.jsonc b/src/plugins/text_based_languages/kibana.jsonc index 472560aa3b488..f22a87185b54c 100644 --- a/src/plugins/text_based_languages/kibana.jsonc +++ b/src/plugins/text_based_languages/kibana.jsonc @@ -6,6 +6,14 @@ "id": "textBasedLanguages", "server": false, "browser": true, + "optionalPlugins": [ + "indexManagement" + ], + "requiredPlugins": [ + "data", + "expressions", + "dataViews" + ], "requiredBundles": [ "kibanaReact", ] diff --git a/src/plugins/text_based_languages/public/create_editor.tsx b/src/plugins/text_based_languages/public/create_editor.tsx index 1fefff765f9b2..534d86420b74d 100644 --- a/src/plugins/text_based_languages/public/create_editor.tsx +++ b/src/plugins/text_based_languages/public/create_editor.tsx @@ -27,7 +27,7 @@ export const TextBasedLangEditor = (props: TextBasedLanguagesEditorProps) => { return ( diff --git a/src/plugins/text_based_languages/public/kibana_services.ts b/src/plugins/text_based_languages/public/kibana_services.ts index 01f0dd4a823d3..8592904a69370 100644 --- a/src/plugins/text_based_languages/public/kibana_services.ts +++ b/src/plugins/text_based_languages/public/kibana_services.ts @@ -7,17 +7,25 @@ */ import { BehaviorSubject } from 'rxjs'; - -import { CoreStart } from '@kbn/core/public'; +import type { CoreStart } from '@kbn/core/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import { IndexManagementPluginSetup } from '@kbn/index-management-plugin/public'; export let core: CoreStart; -const servicesReady$ = new BehaviorSubject<{ core: CoreStart; darkMode: boolean } | undefined>( - undefined -); +interface ServiceDeps { + core: CoreStart; + darkMode: boolean; + dataViews: DataViewsPublicPluginStart; + expressions: ExpressionsStart; + indexManagementApiService?: IndexManagementPluginSetup['apiService']; +} + +const servicesReady$ = new BehaviorSubject(undefined); export const untilPluginStartServicesReady = () => { if (servicesReady$.value) return Promise.resolve(servicesReady$.value); - return new Promise<{ core: CoreStart; darkMode: boolean }>((resolve) => { + return new Promise((resolve) => { const subscription = servicesReady$.subscribe((deps) => { if (deps) { subscription.unsubscribe(); @@ -27,9 +35,20 @@ export const untilPluginStartServicesReady = () => { }); }; -export const setKibanaServices = (kibanaCore: CoreStart) => { +export const setKibanaServices = ( + kibanaCore: CoreStart, + dataViews: DataViewsPublicPluginStart, + expressions: ExpressionsStart, + indexManagement?: IndexManagementPluginSetup +) => { core = kibanaCore; core.theme.theme$.subscribe(({ darkMode }) => { - servicesReady$.next({ core, darkMode }); + servicesReady$.next({ + core, + darkMode, + dataViews, + expressions, + indexManagementApiService: indexManagement?.apiService, + }); }); }; diff --git a/src/plugins/text_based_languages/public/plugin.ts b/src/plugins/text_based_languages/public/plugin.ts index f983baf517f8c..d496bdfe30f99 100755 --- a/src/plugins/text_based_languages/public/plugin.ts +++ b/src/plugins/text_based_languages/public/plugin.ts @@ -6,16 +6,31 @@ * Side Public License, v 1. */ -import { Plugin, CoreStart } from '@kbn/core/public'; +import type { Plugin, CoreStart, CoreSetup } from '@kbn/core/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { IndexManagementPluginSetup } from '@kbn/index-management-plugin/public'; import { setKibanaServices } from './kibana_services'; +interface TextBasedLanguagesPluginStart { + dataViews: DataViewsPublicPluginStart; + expressions: ExpressionsStart; +} + +interface TextBasedLanguagesPluginSetup { + indexManagement: IndexManagementPluginSetup; +} + export class TextBasedLanguagesPlugin implements Plugin<{}, void> { - public setup() { + private indexManagement?: IndexManagementPluginSetup; + + public setup(_: CoreSetup, { indexManagement }: TextBasedLanguagesPluginSetup) { + this.indexManagement = indexManagement; return {}; } - public start(core: CoreStart): void { - setKibanaServices(core); + public start(core: CoreStart, { dataViews, expressions }: TextBasedLanguagesPluginStart): void { + setKibanaServices(core, dataViews, expressions, this.indexManagement); } public stop() {} diff --git a/src/plugins/text_based_languages/tsconfig.json b/src/plugins/text_based_languages/tsconfig.json index d8b5cbd5e965b..152a2aba25c6b 100644 --- a/src/plugins/text_based_languages/tsconfig.json +++ b/src/plugins/text_based_languages/tsconfig.json @@ -13,6 +13,9 @@ "@kbn/text-based-editor", "@kbn/kibana-react-plugin", "@kbn/core", + "@kbn/expressions-plugin", + "@kbn/data-views-plugin", + "@kbn/index-management-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx index 733f9040b3b3a..26c771e405be8 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx @@ -16,8 +16,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { ElasticRequestState } from '@kbn/unified-doc-viewer'; import { DOC_TABLE_LEGACY, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils'; -import { useEsDocSearch } from '../../hooks'; -import { useUnifiedDocViewerServices } from '../../hooks'; +import { useEsDocSearch, useUnifiedDocViewerServices } from '../../hooks'; import { getHeight } from './get_height'; import { JSONCodeEditorCommonMemoized } from '../json_code_editor'; diff --git a/src/plugins/unified_histogram/public/__mocks__/suggestions.ts b/src/plugins/unified_histogram/public/__mocks__/suggestions.ts index 0a92393f60ec3..bed2eee388cde 100644 --- a/src/plugins/unified_histogram/public/__mocks__/suggestions.ts +++ b/src/plugins/unified_histogram/public/__mocks__/suggestions.ts @@ -39,7 +39,7 @@ export const currentSuggestionMock = { '46aa21fa-b747-4543-bf90-0b40007c546d': { index: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', query: { - sql: 'SELECT Dest, AvgTicketPrice FROM "kibana_sample_data_flights"', + esql: 'FROM kibana_sample_data_flights | keep Dest, AvgTicketPrice', }, columns: [ { @@ -141,7 +141,7 @@ export const currentSuggestionMock = { fieldName: '', contextualFields: ['Dest', 'AvgTicketPrice'], query: { - sql: 'SELECT Dest, AvgTicketPrice FROM "kibana_sample_data_flights"', + esql: 'FROM "kibana_sample_data_flights"', }, }, }, @@ -178,7 +178,7 @@ export const allSuggestionsMock = [ '2513a3d4-ad9d-48ea-bd58-8b6419ab97e6': { index: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', query: { - sql: 'SELECT Dest, AvgTicketPrice FROM "kibana_sample_data_flights"', + esql: 'FROM "kibana_sample_data_flights"', }, columns: [ { @@ -281,7 +281,7 @@ export const allSuggestionsMock = [ fieldName: '', contextualFields: ['Dest', 'AvgTicketPrice'], query: { - sql: 'SELECT Dest, AvgTicketPrice FROM "kibana_sample_data_flights"', + esql: 'FROM "kibana_sample_data_flights"', }, }, }, diff --git a/src/plugins/unified_histogram/public/chart/chart.test.tsx b/src/plugins/unified_histogram/public/chart/chart.test.tsx index 3ce4f829ebac3..d561a3310ceae 100644 --- a/src/plugins/unified_histogram/public/chart/chart.test.tsx +++ b/src/plugins/unified_histogram/public/chart/chart.test.tsx @@ -43,6 +43,7 @@ async function mountComponent({ allSuggestions, isPlainRecord, hasDashboardPermissions, + isChartLoading, }: { noChart?: boolean; noHits?: boolean; @@ -54,6 +55,7 @@ async function mountComponent({ allSuggestions?: Suggestion[]; isPlainRecord?: boolean; hasDashboardPermissions?: boolean; + isChartLoading?: boolean; } = {}) { (searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation( jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: noHits ? 0 : 2 } } })) @@ -98,6 +100,7 @@ async function mountComponent({ breakdown: noBreakdown ? undefined : { field: undefined }, currentSuggestion, allSuggestions, + isChartLoading: Boolean(isChartLoading), isPlainRecord, appendHistogram, onResetChartHeight: jest.fn(), @@ -173,6 +176,11 @@ describe('Chart', () => { expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeTruthy(); }); + test('render progress bar when text based and request is loading', async () => { + const component = await mountComponent({ isPlainRecord: true, isChartLoading: true }); + expect(component.find('[data-test-subj="unifiedHistogramProgressBar"]').exists()).toBeTruthy(); + }); + test('triggers onEditVisualization on click', async () => { expect(mockUseEditVisualization).not.toHaveBeenCalled(); const component = await mountComponent(); diff --git a/src/plugins/unified_histogram/public/chart/chart.tsx b/src/plugins/unified_histogram/public/chart/chart.tsx index a9baa653ecee4..56b6ed223df2b 100644 --- a/src/plugins/unified_histogram/public/chart/chart.tsx +++ b/src/plugins/unified_histogram/public/chart/chart.tsx @@ -14,6 +14,7 @@ import { EuiFlexItem, EuiPopover, EuiToolTip, + EuiProgress, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { EmbeddableComponentProps, Suggestion } from '@kbn/lens-plugin/public'; @@ -69,6 +70,8 @@ export interface ChartProps { disabledActions?: LensEmbeddableInput['disabledActions']; input$?: UnifiedHistogramInput$; lensTablesAdapter?: Record; + isOnHistogramMode?: boolean; + isChartLoading?: boolean; onResetChartHeight?: () => void; onChartHiddenChange?: (chartHidden: boolean) => void; onTimeIntervalChange?: (timeInterval: string) => void; @@ -105,6 +108,8 @@ export function Chart({ disabledActions, input$: originalInput$, lensTablesAdapter, + isOnHistogramMode, + isChartLoading, onResetChartHeight, onChartHiddenChange, onTimeIntervalChange, @@ -414,6 +419,14 @@ export function Chart({ })} css={histogramCss} > + {isChartLoading && ( + + )} { isPlainRecord: true, lensTablesAdapter: lensTablesAdapterMock, query: { - sql: 'Select * from test', + esql: 'from test', }, }} /> diff --git a/src/plugins/unified_histogram/public/chart/histogram.test.tsx b/src/plugins/unified_histogram/public/chart/histogram.test.tsx index ef77af13209c0..78f06687a0e7e 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.test.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.test.tsx @@ -38,7 +38,7 @@ const getMockLensAttributes = () => suggestion: undefined, }); -function mountComponent() { +function mountComponent(isPlainRecord = false, hasLensSuggestions = false) { const services = unifiedHistogramServicesMock; services.data.query.timefilter.timefilter.getAbsoluteTime = () => { return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; @@ -51,7 +51,8 @@ function mountComponent() { request: { searchSessionId: '123', }, - hasLensSuggestions: false, + hasLensSuggestions, + isPlainRecord, hits: { status: UnifiedHistogramFetchStatus.loading, total: undefined, @@ -237,4 +238,76 @@ describe('Histogram', () => { ); expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters }); }); + + it('should execute onLoad correctly for textbased language and no Lens suggestions', async () => { + const { component, props } = mountComponent(true, false); + const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; + const onLoad = component.find(embeddable).props().onLoad; + const adapters = createDefaultInspectorAdapters(); + adapters.tables.tables.layerId = { + meta: { type: 'es_ql' }, + columns: [ + { + id: 'rows', + name: 'rows', + meta: { + type: 'number', + dimensionName: 'Vertical axis', + }, + }, + ], + rows: [ + { + rows: 16, + }, + { + rows: 4, + }, + ], + } as any; + act(() => { + onLoad(false, adapters); + }); + expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( + UnifiedHistogramFetchStatus.complete, + 20 + ); + expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters }); + }); + + it('should execute onLoad correctly for textbased language and Lens suggestions', async () => { + const { component, props } = mountComponent(true, true); + const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; + const onLoad = component.find(embeddable).props().onLoad; + const adapters = createDefaultInspectorAdapters(); + adapters.tables.tables.layerId = { + meta: { type: 'es_ql' }, + columns: [ + { + id: 'rows', + name: 'rows', + meta: { + type: 'number', + dimensionName: 'Vertical axis', + }, + }, + ], + rows: [ + { + var0: 5584.925311203319, + }, + { + var0: 6788.7777444444, + }, + ], + } as any; + act(() => { + onLoad(false, adapters); + }); + expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( + UnifiedHistogramFetchStatus.complete, + 2 + ); + expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters }); + }); }); diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index ffc9cb82515a5..0046e0b6a87bd 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -10,7 +10,7 @@ import { useEuiTheme, useResizeObserver } from '@elastic/eui'; import { css } from '@emotion/react'; import React, { useState, useRef, useEffect } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; +import type { DefaultInspectorAdapters, Datatable } from '@kbn/expressions-plugin/common'; import type { IKibanaSearchResponse } from '@kbn/data-plugin/public'; import type { estypes } from '@elastic/elasticsearch'; import type { TimeRange } from '@kbn/es-query'; @@ -53,6 +53,29 @@ export interface HistogramProps { withDefaultActions: EmbeddableComponentProps['withDefaultActions']; } +const computeTotalHits = ( + hasLensSuggestions: boolean, + adapterTables: + | { + [key: string]: Datatable; + } + | undefined, + isPlainRecord?: boolean +) => { + if (isPlainRecord && hasLensSuggestions) { + return Object.values(adapterTables ?? {})?.[0]?.rows?.length; + } else if (isPlainRecord && !hasLensSuggestions) { + // ES|QL histogram case + let rowsCount = 0; + Object.values(adapterTables ?? {})?.[0]?.rows.forEach((r) => { + rowsCount += r.rows; + }); + return rowsCount; + } else { + return adapterTables?.unifiedHistogram?.meta?.statistics?.totalCount; + } +}; + export function Histogram({ services: { data, lens, uiSettings }, dataView, @@ -113,10 +136,7 @@ export function Histogram({ } const adapterTables = adapters?.tables?.tables; - const totalHits = - isPlainRecord && hasLensSuggestions - ? Object.values(adapterTables ?? {})?.[0]?.rows?.length - : adapterTables?.unifiedHistogram?.meta?.statistics?.totalCount; + const totalHits = computeTotalHits(hasLensSuggestions, adapterTables, isPlainRecord); onTotalHitsChange?.( isLoading ? UnifiedHistogramFetchStatus.loading : UnifiedHistogramFetchStatus.complete, diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts index 187bf76751007..e6f3aeaa3a002 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts @@ -123,7 +123,7 @@ describe('useTotalHits', () => { ...getDeps(), isPlainRecord: true, onTotalHitsChange, - query: { sql: 'select * from test' }, + query: { esql: 'from test' }, }; renderHook(() => useTotalHits(deps)); expect(onTotalHitsChange).toBeCalledTimes(1); diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts index c260d3171697b..2beed34b3d919 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts @@ -206,7 +206,7 @@ const fetchTotalHitsSearchSource = async ({ executionContext: { description: 'fetch total hits', }, - disableShardFailureWarning: true, // TODO: show warnings as a badge next to total hits number + disableWarningToasts: true, // TODO: show warnings as a badge next to total hits number }) .pipe( filter((res) => isCompleteResponse(res)), diff --git a/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts b/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts index f54b1b5b5c456..2a4523d152066 100644 --- a/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts +++ b/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts @@ -636,7 +636,7 @@ describe('getLensAttributes', () => { }, "fieldName": "", "query": Object { - "sql": "SELECT Dest, AvgTicketPrice FROM \\"kibana_sample_data_flights\\"", + "esql": "FROM \\"kibana_sample_data_flights\\"", }, }, "layers": Object { @@ -675,7 +675,7 @@ describe('getLensAttributes', () => { ], "index": "d3d7af60-4c81-11e8-b3d7-01146121b73d", "query": Object { - "sql": "SELECT Dest, AvgTicketPrice FROM \\"kibana_sample_data_flights\\"", + "esql": "FROM kibana_sample_data_flights | keep Dest, AvgTicketPrice", }, "timeField": "timestamp", }, diff --git a/src/plugins/unified_histogram/public/container/container.tsx b/src/plugins/unified_histogram/public/container/container.tsx index 311a380790dec..74ecf5837c02e 100644 --- a/src/plugins/unified_histogram/public/container/container.tsx +++ b/src/plugins/unified_histogram/public/container/container.tsx @@ -42,6 +42,7 @@ export type UnifiedHistogramContainerProps = { | Promise; searchSessionId?: UnifiedHistogramRequestContext['searchSessionId']; requestAdapter?: UnifiedHistogramRequestContext['adapter']; + isChartLoading?: boolean; } & Pick< UnifiedHistogramLayoutProps, | 'services' @@ -124,8 +125,7 @@ export const UnifiedHistogramContainer = forwardRef< ), }); }, [input$, stateService]); - - const { dataView, query, searchSessionId, requestAdapter } = containerProps; + const { dataView, query, searchSessionId, requestAdapter, isChartLoading } = containerProps; const currentSuggestion = useStateSelector(stateService?.state$, currentSuggestionSelector); const topPanelHeight = useStateSelector(stateService?.state$, topPanelHeightSelector); const stateProps = useStateProps({ @@ -147,6 +147,7 @@ export const UnifiedHistogramContainer = forwardRef< {...layoutProps} {...stateProps} currentSuggestion={currentSuggestion} + isChartLoading={Boolean(isChartLoading)} topPanelHeight={topPanelHeight} input$={input$} lensSuggestionsApi={lensSuggestionsApi} diff --git a/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts b/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts index c0eeb9448eee7..c9ab0d5220aed 100644 --- a/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts +++ b/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts @@ -136,13 +136,13 @@ describe('useStateProps', () => { `); }); - it('should return the correct props when an SQL query is used', () => { + it('should return the correct props when an ES|QL query is used', () => { const stateService = getStateService({ initialState }); const { result } = renderHook(() => useStateProps({ stateService, dataView: dataViewWithTimefieldMock, - query: { sql: 'SELECT * FROM index' }, + query: { esql: 'FROM index' }, requestAdapter: new RequestAdapter(), searchSessionId: '123', }) @@ -222,7 +222,7 @@ describe('useStateProps', () => { useStateProps({ stateService, dataView: dataViewWithTimefieldMock, - query: { sql: 'SELECT * FROM index' }, + query: { esql: 'FROM index' }, requestAdapter: new RequestAdapter(), searchSessionId: '123', }) diff --git a/src/plugins/unified_histogram/public/container/services/state_service.ts b/src/plugins/unified_histogram/public/container/services/state_service.ts index 1fd0905d86c7a..4cb2950763094 100644 --- a/src/plugins/unified_histogram/public/container/services/state_service.ts +++ b/src/plugins/unified_histogram/public/container/services/state_service.ts @@ -190,7 +190,6 @@ export const createStateService = ( setCurrentSuggestion: (suggestion: Suggestion | undefined) => { updateState({ currentSuggestion: suggestion }); }, - setTimeInterval: (timeInterval: string) => { updateState({ timeInterval }); }, diff --git a/src/plugins/unified_histogram/public/layout/hooks/compute_interval.test.ts b/src/plugins/unified_histogram/public/layout/hooks/compute_interval.test.ts new file mode 100644 index 0000000000000..04a94d172d1ac --- /dev/null +++ b/src/plugins/unified_histogram/public/layout/hooks/compute_interval.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { calculateBounds } from '@kbn/data-plugin/public'; +import { computeInterval } from './compute_interval'; + +describe('computeInterval', () => { + const dataMock = dataPluginMock.createStartContract(); + dataMock.query.timefilter.timefilter.getTime = () => { + return { from: '1991-03-29T08:04:00.694Z', to: '2021-03-29T07:04:00.695Z' }; + }; + dataMock.query.timefilter.timefilter.calculateBounds = (timeRange) => { + return calculateBounds(timeRange); + }; + + it('should return correct interval for 24 hours timerange', () => { + expect( + computeInterval( + { + from: '2023-08-15T10:00:00.000Z', + to: '2023-08-16T10:17:34.591Z', + }, + dataMock + ) + ).toEqual('30 minute'); + }); + + it('should return correct interval for 7 days timerange', () => { + expect( + computeInterval( + { + from: '2023-08-08T21:00:00.000Z', + to: '2023-08-16T10:18:56.569Z', + }, + dataMock + ) + ).toEqual('3 hour'); + }); + + it('should return correct interval for 1 month timerange', () => { + expect( + computeInterval( + { + from: '2023-07-16T21:00:00.000Z', + to: '2023-08-16T10:19:43.573Z', + }, + dataMock + ) + ).toEqual('12 hour'); + }); + + it('should return correct interval for 1 year timerange', () => { + expect( + computeInterval( + { + from: '2022-08-15T21:00:00.000Z', + to: '2023-08-16T10:21:18.589Z', + }, + dataMock + ) + ).toEqual('1 week'); + }); +}); diff --git a/src/plugins/unified_histogram/public/layout/hooks/compute_interval.ts b/src/plugins/unified_histogram/public/layout/hooks/compute_interval.ts new file mode 100644 index 0000000000000..bf6270e8255c7 --- /dev/null +++ b/src/plugins/unified_histogram/public/layout/hooks/compute_interval.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { TimeRange } from '@kbn/es-query'; + +// follows the same logic with vega auto_date function +// we could move to a package and reuse in the future +const barTarget = 50; // same as vega +const roundInterval = (interval: number) => { + { + switch (true) { + case interval <= 500: // <= 0.5s + return '100 millisecond'; + case interval <= 5000: // <= 5s + return '1 second'; + case interval <= 7500: // <= 7.5s + return '5 second'; + case interval <= 15000: // <= 15s + return '10 second'; + case interval <= 45000: // <= 45s + return '30 second'; + case interval <= 180000: // <= 3m + return '1 minute'; + case interval <= 450000: // <= 9m + return '5 minute'; + case interval <= 1200000: // <= 20m + return '10 minute'; + case interval <= 2700000: // <= 45m + return '30 minute'; + case interval <= 7200000: // <= 2h + return '1 hour'; + case interval <= 21600000: // <= 6h + return '3 hour'; + case interval <= 86400000: // <= 24h + return '12 hour'; + case interval <= 604800000: // <= 1w + return '24 hour'; + case interval <= 1814400000: // <= 3w + return '1 week'; + case interval < 3628800000: // < 2y + return '30 day'; + default: + return '1 year'; + } + } +}; + +export const computeInterval = (timeRange: TimeRange, data: DataPublicPluginStart): string => { + const bounds = data.query.timefilter.timefilter.calculateBounds(timeRange!); + const min = bounds.min!.valueOf(); + const max = bounds.max!.valueOf(); + const interval = (max - min) / barTarget; + return roundInterval(interval); +}; diff --git a/src/plugins/unified_histogram/public/layout/hooks/use_lens_suggestions.test.ts b/src/plugins/unified_histogram/public/layout/hooks/use_lens_suggestions.test.ts new file mode 100644 index 0000000000000..d891c62df3514 --- /dev/null +++ b/src/plugins/unified_histogram/public/layout/hooks/use_lens_suggestions.test.ts @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { renderHook } from '@testing-library/react-hooks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { calculateBounds } from '@kbn/data-plugin/public'; +import { deepMockedFields, buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { allSuggestionsMock } from '../../__mocks__/suggestions'; +import { useLensSuggestions } from './use_lens_suggestions'; + +describe('useLensSuggestions', () => { + const dataMock = dataPluginMock.createStartContract(); + dataMock.query.timefilter.timefilter.calculateBounds = (timeRange) => { + return calculateBounds(timeRange); + }; + const dataViewMock = buildDataViewMock({ + name: 'the-data-view', + fields: deepMockedFields, + timeFieldName: '@timestamp', + }); + + test('should return empty suggestions for non aggregate query', async () => { + const { result } = renderHook(() => { + return useLensSuggestions({ + dataView: dataViewMock, + query: undefined, + isPlainRecord: false, + data: dataMock, + lensSuggestionsApi: jest.fn(), + }); + }); + const current = result.current; + expect(current).toStrictEqual({ + allSuggestions: [], + currentSuggestion: undefined, + isOnHistogramMode: false, + suggestionUnsupported: false, + }); + }); + + test('should return suggestions for aggregate query', async () => { + const { result } = renderHook(() => { + return useLensSuggestions({ + dataView: dataViewMock, + query: { esql: 'from the-data-view | stats maxB = max(bytes)' }, + isPlainRecord: true, + columns: [ + { + id: 'var0', + name: 'var0', + meta: { + type: 'number', + }, + }, + ], + data: dataMock, + lensSuggestionsApi: jest.fn(() => allSuggestionsMock), + }); + }); + const current = result.current; + expect(current).toStrictEqual({ + allSuggestions: allSuggestionsMock, + currentSuggestion: allSuggestionsMock[0], + isOnHistogramMode: false, + suggestionUnsupported: false, + }); + }); + + test('should return suggestionUnsupported if no timerange is provided and no suggestions returned by the api', async () => { + const { result } = renderHook(() => { + return useLensSuggestions({ + dataView: dataViewMock, + query: { esql: 'from the-data-view | stats maxB = max(bytes)' }, + isPlainRecord: true, + columns: [ + { + id: 'var0', + name: 'var0', + meta: { + type: 'number', + }, + }, + ], + data: dataMock, + lensSuggestionsApi: jest.fn(), + }); + }); + const current = result.current; + expect(current).toStrictEqual({ + allSuggestions: [], + currentSuggestion: undefined, + isOnHistogramMode: false, + suggestionUnsupported: true, + }); + }); + + test('should return histogramSuggestion if no suggestions returned by the api', async () => { + const firstMockReturn = undefined; + const secondMockReturn = allSuggestionsMock; + const lensSuggestionsApi = jest + .fn() + .mockReturnValueOnce(firstMockReturn) // will return to firstMockReturn object firstly + .mockReturnValueOnce(secondMockReturn); // will return to secondMockReturn object secondly + + const { result } = renderHook(() => { + return useLensSuggestions({ + dataView: dataViewMock, + query: { esql: 'from the-data-view | limit 100' }, + isPlainRecord: true, + columns: [ + { + id: 'var0', + name: 'var0', + meta: { + type: 'number', + }, + }, + ], + data: dataMock, + lensSuggestionsApi, + timeRange: { + from: '2023-09-03T08:00:00.000Z', + to: '2023-09-04T08:56:28.274Z', + }, + }); + }); + const current = result.current; + expect(current).toStrictEqual({ + allSuggestions: [], + currentSuggestion: allSuggestionsMock[0], + isOnHistogramMode: true, + suggestionUnsupported: false, + }); + }); + + test('should not return histogramSuggestion if no suggestions returned by the api and transformational commands', async () => { + const firstMockReturn = undefined; + const secondMockReturn = allSuggestionsMock; + const lensSuggestionsApi = jest + .fn() + .mockReturnValueOnce(firstMockReturn) // will return to firstMockReturn object firstly + .mockReturnValueOnce(secondMockReturn); // will return to secondMockReturn object secondly + + const { result } = renderHook(() => { + return useLensSuggestions({ + dataView: dataViewMock, + query: { esql: 'from the-data-view | limit 100 | keep @timestamp' }, + isPlainRecord: true, + columns: [ + { + id: 'var0', + name: 'var0', + meta: { + type: 'number', + }, + }, + ], + data: dataMock, + lensSuggestionsApi, + timeRange: { + from: '2023-09-03T08:00:00.000Z', + to: '2023-09-04T08:56:28.274Z', + }, + }); + }); + const current = result.current; + expect(current).toStrictEqual({ + allSuggestions: [], + currentSuggestion: undefined, + isOnHistogramMode: false, + suggestionUnsupported: true, + }); + }); +}); diff --git a/src/plugins/unified_histogram/public/layout/hooks/use_lens_suggestions.ts b/src/plugins/unified_histogram/public/layout/hooks/use_lens_suggestions.ts index e4936b9345a6d..b6d02753d140a 100644 --- a/src/plugins/unified_histogram/public/layout/hooks/use_lens_suggestions.ts +++ b/src/plugins/unified_histogram/public/layout/hooks/use_lens_suggestions.ts @@ -5,13 +5,21 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import { DataView } from '@kbn/data-views-plugin/common'; -import { AggregateQuery, isOfAggregateQueryType, Query } from '@kbn/es-query'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { + AggregateQuery, + isOfAggregateQueryType, + getAggregateQueryMode, + Query, + TimeRange, +} from '@kbn/es-query'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { LensSuggestionsApi, Suggestion } from '@kbn/lens-plugin/public'; import { isEqual } from 'lodash'; import { useEffect, useMemo, useRef, useState } from 'react'; +import { computeInterval } from './compute_interval'; +const TRANSFORMATIONAL_COMMANDS = ['stats', 'project', 'keep']; export const useLensSuggestions = ({ dataView, @@ -19,6 +27,8 @@ export const useLensSuggestions = ({ originalSuggestion, isPlainRecord, columns, + data, + timeRange, lensSuggestionsApi, onSuggestionChange, }: { @@ -27,6 +37,8 @@ export const useLensSuggestions = ({ originalSuggestion?: Suggestion; isPlainRecord?: boolean; columns?: DatatableColumn[]; + data: DataPublicPluginStart; + timeRange?: TimeRange; lensSuggestionsApi: LensSuggestionsApi; onSuggestionChange?: (suggestion: Suggestion | undefined) => void; }) => { @@ -50,6 +62,63 @@ export const useLensSuggestions = ({ const currentSuggestion = originalSuggestion ?? suggestions.firstSuggestion; const suggestionDeps = useRef(getSuggestionDeps({ dataView, query, columns })); + const histogramSuggestion = useMemo(() => { + if ( + !currentSuggestion && + dataView.isTimeBased() && + query && + isOfAggregateQueryType(query) && + getAggregateQueryMode(query) === 'esql' && + timeRange + ) { + let queryHasTransformationalCommands = false; + if ('esql' in query) { + TRANSFORMATIONAL_COMMANDS.forEach((command: string) => { + if (query.esql.toLowerCase().includes(command)) { + queryHasTransformationalCommands = true; + return; + } + }); + } + + if (queryHasTransformationalCommands) return undefined; + + const interval = computeInterval(timeRange, data); + const language = getAggregateQueryMode(query); + const histogramQuery = `${query[language]} | eval uniqueName = 1 + | EVAL timestamp=DATE_TRUNC(${interval}, ${dataView.timeFieldName}) | stats rows = count(uniqueName) by timestamp | rename timestamp as \`${dataView.timeFieldName} every ${interval}\``; + const context = { + dataViewSpec: dataView?.toSpec(), + fieldName: '', + textBasedColumns: [ + { + id: `${dataView.timeFieldName} every ${interval}`, + name: `${dataView.timeFieldName} every ${interval}`, + meta: { + type: 'date', + }, + }, + { + id: 'rows', + name: 'rows', + meta: { + type: 'number', + }, + }, + ] as DatatableColumn[], + query: { + esql: histogramQuery, + }, + }; + const sug = lensSuggestionsApi(context, dataView, ['lnsDatatable']) ?? []; + if (sug.length) { + return sug[0]; + } + return undefined; + } + return undefined; + }, [currentSuggestion, dataView, query, timeRange, data, lensSuggestionsApi]); + useEffect(() => { const newSuggestionsDeps = getSuggestionDeps({ dataView, query, columns }); @@ -70,8 +139,9 @@ export const useLensSuggestions = ({ return { allSuggestions, - currentSuggestion, - suggestionUnsupported: !currentSuggestion && !dataView.isTimeBased(), + currentSuggestion: histogramSuggestion ?? currentSuggestion, + suggestionUnsupported: !currentSuggestion && !histogramSuggestion && isPlainRecord, + isOnHistogramMode: Boolean(histogramSuggestion), }; }; diff --git a/src/plugins/unified_histogram/public/layout/layout.test.tsx b/src/plugins/unified_histogram/public/layout/layout.test.tsx index ee51ad99b4053..f75fa0b1d4b9c 100644 --- a/src/plugins/unified_histogram/public/layout/layout.test.tsx +++ b/src/plugins/unified_histogram/public/layout/layout.test.tsx @@ -77,6 +77,7 @@ describe('Layout', () => { to: '2020-05-14T11:20:13.590', }} lensSuggestionsApi={jest.fn()} + isChartLoading={false} {...rest} /> ); diff --git a/src/plugins/unified_histogram/public/layout/layout.tsx b/src/plugins/unified_histogram/public/layout/layout.tsx index d0414f0682489..e3c80679c5c3f 100644 --- a/src/plugins/unified_histogram/public/layout/layout.tsx +++ b/src/plugins/unified_histogram/public/layout/layout.tsx @@ -120,6 +120,10 @@ export interface UnifiedHistogramLayoutProps extends PropsWithChildren * Input observable */ input$?: UnifiedHistogramInput$; + /** + * Flag indicating that the chart is currently loading + */ + isChartLoading: boolean; /** * The Lens suggestions API */ @@ -174,6 +178,7 @@ export const UnifiedHistogramLayout = ({ query, filters, currentSuggestion: originalSuggestion, + isChartLoading, isPlainRecord, timeRange, relativeTimeRange, @@ -203,18 +208,20 @@ export const UnifiedHistogramLayout = ({ children, withDefaultActions, }: UnifiedHistogramLayoutProps) => { - const { allSuggestions, currentSuggestion, suggestionUnsupported } = useLensSuggestions({ - dataView, - query, - originalSuggestion, - isPlainRecord, - columns, - lensSuggestionsApi, - onSuggestionChange, - }); + const { allSuggestions, currentSuggestion, suggestionUnsupported, isOnHistogramMode } = + useLensSuggestions({ + dataView, + query, + originalSuggestion, + isPlainRecord, + columns, + timeRange, + data: services.data, + lensSuggestionsApi, + onSuggestionChange, + }); const chart = suggestionUnsupported ? undefined : originalChart; - const topPanelNode = useMemo( () => createHtmlPortalNode({ attributes: { class: 'eui-fullHeight' } }), [] @@ -267,12 +274,13 @@ export const UnifiedHistogramLayout = ({ request={request} hits={hits} currentSuggestion={currentSuggestion} + isChartLoading={isChartLoading} allSuggestions={allSuggestions} isPlainRecord={isPlainRecord} chart={chart} breakdown={breakdown} appendHitsCounter={appendHitsCounter} - appendHistogram={showFixedPanels ? : } + appendHistogram={} disableAutoFetching={disableAutoFetching} disableTriggers={disableTriggers} disabledActions={disabledActions} @@ -287,6 +295,7 @@ export const UnifiedHistogramLayout = ({ onFilter={onFilter} onBrushEnd={onBrushEnd} lensTablesAdapter={lensTablesAdapter} + isOnHistogramMode={isOnHistogramMode} withDefaultActions={withDefaultActions} /> diff --git a/src/plugins/unified_histogram/public/panels/panels_resizable.tsx b/src/plugins/unified_histogram/public/panels/panels_resizable.tsx index 773ebe172b25e..9f8fd5338a38f 100644 --- a/src/plugins/unified_histogram/public/panels/panels_resizable.tsx +++ b/src/plugins/unified_histogram/public/panels/panels_resizable.tsx @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -import { - EuiResizableContainer, - useEuiTheme, - useGeneratedHtmlId, - useResizeObserver, -} from '@elastic/eui'; +import { EuiResizableContainer, useGeneratedHtmlId, useResizeObserver } from '@elastic/eui'; import type { ResizeTrigger } from '@elastic/eui/src/components/resizable_container/types'; import { css } from '@emotion/react'; import { isEqual, round } from 'lodash'; @@ -162,12 +157,6 @@ export const PanelsResizable = ({ disableResizeWithPortalsHack(); }, [disableResizeWithPortalsHack, resizeWithPortalsHackIsResizing]); - const { euiTheme } = useEuiTheme(); - const buttonCss = css` - margin-top: -${euiTheme.size.base}; - margin-bottom: 0; - `; - return ( + .add('with dataviewPicker with ESQL', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', @@ -547,66 +547,66 @@ storiesOf('SearchBar', module) onChangeDataView: action('onChangeDataView'), onAddField: action('onAddField'), onDataViewCreated: action('onDataViewCreated'), - textBasedLanguages: ['SQL'], + textBasedLanguages: ['ESQL'], }, } as SearchBarProps) ) - .add('with dataviewPicker with SQL and sql query', () => + .add('with dataviewPicker with ESQL and ESQL query', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', trigger: { 'data-test-subj': 'dataView-switch-link', - label: 'SQL', - title: 'SQL', + label: 'ESQL', + title: 'ESQL', }, onChangeDataView: action('onChangeDataView'), onAddField: action('onAddField'), onDataViewCreated: action('onDataViewCreated'), - textBasedLanguages: ['SQL'], + textBasedLanguages: ['ESQL'], }, - query: { sql: 'SELECT field1, field2 FROM DATAVIEW' }, + query: { esql: 'from dataview | project field1, field2' }, } as unknown as SearchBarProps) ) - .add('with dataviewPicker with SQL and large sql query', () => + .add('with dataviewPicker with ESQL and large ESQL query', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', trigger: { 'data-test-subj': 'dataView-switch-link', - label: 'SQL', - title: 'SQL', + label: 'ESQL', + title: 'ESQL', }, onChangeDataView: action('onChangeDataView'), onAddField: action('onAddField'), onDataViewCreated: action('onDataViewCreated'), - textBasedLanguages: ['SQL'], + textBasedLanguages: ['ESQL'], }, query: { - sql: 'SELECT field1, field2, field 3, field 4, field 5 FROM DATAVIEW WHERE field5 IS NOT NULL AND field4 IS NULL', + esql: 'from dataview | project field1, field2, field 3, field 4, field 5 | where field5 > 5 | stats var = avg(field3)', }, } as unknown as SearchBarProps) ) - .add('with dataviewPicker with SQL and errors in sql query', () => + .add('with dataviewPicker with ESQL and errors in ESQL query', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', trigger: { 'data-test-subj': 'dataView-switch-link', - label: 'SQL', - title: 'SQL', + label: 'ESQL', + title: 'ESQL', }, onChangeDataView: action('onChangeDataView'), onAddField: action('onAddField'), onDataViewCreated: action('onDataViewCreated'), - textBasedLanguages: ['SQL'], + textBasedLanguages: ['ESQL'], }, textBasedLanguageModeErrors: [ new Error( - '[essql] > Unexpected error from Elasticsearch: verification_exception - Found 1 problem line 1:16: Unknown column [field10]' + '[esql] > Unexpected error from Elasticsearch: verification_exception - Found 1 problem line 1:16: Unknown column [field10]' ), ], - query: { sql: 'SELECT field1, field10 FROM DATAVIEW' }, + query: { esql: 'from dataview | project field10' }, } as unknown as SearchBarProps) ) .add('in disabled state', () => diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx index be359f25a6496..7492018b3a6c2 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx @@ -137,8 +137,8 @@ describe('DataView component', () => { wrapDataViewComponentInContext( { ...props, - textBasedLanguages: [TextBasedLanguages.ESQL, TextBasedLanguages.SQL], - textBasedLanguage: TextBasedLanguages.SQL, + textBasedLanguages: [TextBasedLanguages.ESQL], + textBasedLanguage: TextBasedLanguages.ESQL, }, false ) diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index 72f777cfcae93..4c4a81371e052 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -11,6 +11,8 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiPopover, + EuiPanel, + EuiBadge, EuiHorizontalRule, EuiButton, EuiContextMenuPanel, @@ -25,12 +27,13 @@ import { EuiButtonEmpty, EuiToolTip, } from '@elastic/eui'; +import { METRIC_TYPE } from '@kbn/analytics'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { AggregateQuery, getLanguageDisplayName } from '@kbn/es-query'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { IUnifiedSearchPluginServices } from '../types'; -import type { DataViewPickerPropsExtended } from './data_view_picker'; +import { type DataViewPickerPropsExtended } from './data_view_picker'; import type { DataViewListItemEnhanced } from './dataview_list'; -import type { TextBasedLanguagesListProps } from './text_languages_list'; import type { TextBasedLanguagesTransitionModalProps } from './text_languages_transition_modal'; import adhoc from './assets/adhoc.svg'; import { changeDataViewStyles } from './change_dataview.styles'; @@ -52,13 +55,6 @@ export const TextBasedLanguagesTransitionModal = ( ); -const LazyTextBasedLanguagesList = React.lazy(() => import('./text_languages_list')); -export const TextBasedLanguagesList = (props: TextBasedLanguagesListProps) => ( - }> - - -); - const mapAdHocDataView = (adHocDataView: DataView) => { return { title: adHocDataView.title, @@ -97,7 +93,9 @@ export function ChangeDataView({ const [selectedDataViewId, setSelectedDataViewId] = useState(currentDataViewId); const kibana = useKibana(); - const { application, data, storage, dataViews, dataViewEditor } = kibana.services; + const { application, data, storage, dataViews, dataViewEditor, appName, usageCollection } = + kibana.services; + const reportUiCounter = usageCollection?.reportUiCounter.bind(usageCollection, appName); const styles = changeDataViewStyles({ fullWidth: trigger.fullWidth }); const [isTextLangTransitionModalDismissed, setIsTextLangTransitionModalDismissed] = useState(() => Boolean(storage.get(TEXT_LANG_TRANSITION_MODAL_KEY)) @@ -122,7 +120,7 @@ export function ChangeDataView({ useEffect(() => { if (textBasedLanguage) { - setTriggerLabel(textBasedLanguage.toUpperCase()); + setTriggerLabel(getLanguageDisplayName(textBasedLanguage).toUpperCase()); } else { setTriggerLabel(trigger.label); } @@ -245,7 +243,8 @@ export function ChangeDataView({ 'unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning', { defaultMessage: - "Switching data views removes the current SQL query. Save this search to ensure you don't lose work.", + "Switching data views removes the current {textBasedLanguage} query. Save this search to ensure you don't lose work.", + values: { textBasedLanguage }, } )} > @@ -335,42 +334,24 @@ export function ChangeDataView({ if (textBasedLanguages?.length) { panelItems.push( , - - - -
    - {i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesLabel', - { - defaultMessage: 'Text-based query languages', - } - )} -
    -
    -
    -
    , - { - setTriggerLabel(lang); - setPopoverIsOpen(false); - setIsTextBasedLangSelected(true); - // also update the query with the sql query - onTextLangQuerySubmit?.({ sql: `SELECT * FROM "${trigger.title}"` }); - }} - /> + + onTextBasedSubmit({ esql: `from ${trigger.title} | limit 10` })} + data-test-subj="select-text-based-language-panel" + > + {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTryLabel', { + defaultMessage: 'Try ES|QL', + })} + + {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel', { + defaultMessage: 'Technical preview', + })} + + + ); } @@ -384,6 +365,15 @@ export function ChangeDataView({ setIsTextLangTransitionModalDismissed(true); }, [storage]); + const onTextBasedSubmit = useCallback( + (q: AggregateQuery) => { + onTextLangQuerySubmit?.(q); + setPopoverIsOpen(false); + reportUiCounter?.(METRIC_TYPE.CLICK, `esql:unified_search_clicked`); + }, + [onTextLangQuerySubmit, reportUiCounter] + ); + const cleanup = useCallback( (shouldDismissModal: boolean) => { setIsTextLangTransitionModalVisible(false); @@ -434,6 +424,7 @@ export function ChangeDataView({ ); } diff --git a/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx b/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx index 77fe947061480..c02908007200f 100644 --- a/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx +++ b/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx @@ -35,7 +35,6 @@ export const ExploreMatchingButton = ({ alignItems="center" gutterSize="none" justifyContent="spaceBetween" - data-test-subj="select-text-based-language-panel" css={css` margin: ${euiTheme.size.s}; margin-bottom: 0; diff --git a/src/plugins/unified_search/public/dataview_picker/text_languages_list.test.tsx b/src/plugins/unified_search/public/dataview_picker/text_languages_list.test.tsx deleted file mode 100644 index deb619b236338..0000000000000 --- a/src/plugins/unified_search/public/dataview_picker/text_languages_list.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { MouseEvent } from 'react'; -import { EuiSelectable } from '@elastic/eui'; -import { act } from 'react-dom/test-utils'; -import { ShallowWrapper } from 'enzyme'; -import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; -import TextBasedLanguagesList, { TextBasedLanguagesListProps } from './text_languages_list'; -import { TextBasedLanguages } from './data_view_picker'; - -function getTextLanguagesPickerList(instance: ShallowWrapper) { - return instance.find(EuiSelectable).first(); -} - -function getTextLanguagesPickerOptions(instance: ShallowWrapper) { - return getTextLanguagesPickerList(instance).prop('options'); -} - -function selectTextLanguagePickerOption(instance: ShallowWrapper, selectedLabel: string) { - const event = {} as MouseEvent; - const options: Array<{ label: string; checked?: 'on' | 'off' }> = getTextLanguagesPickerOptions( - instance - ).map((option: { label: string }) => - option.label === selectedLabel - ? { ...option, checked: 'on' } - : { ...option, checked: undefined } - ); - const selectedOption = { label: selectedLabel }; - return getTextLanguagesPickerList(instance).prop('onChange')!(options, event, selectedOption); -} - -describe('Text based languages list component', () => { - const changeLanguageSpy = jest.fn(); - let props: TextBasedLanguagesListProps; - beforeEach(() => { - props = { - selectedOption: 'ESQL', - onChange: changeLanguageSpy, - textBasedLanguages: [TextBasedLanguages.ESQL, TextBasedLanguages.SQL], - }; - }); - it('should trigger the onChange if a new language is selected', async () => { - const component = shallow(); - await act(async () => { - selectTextLanguagePickerOption(component, 'SQL'); - }); - expect(changeLanguageSpy).toHaveBeenCalled(); - }); - - it('should list all languages', () => { - const component = shallow(); - - expect(getTextLanguagesPickerOptions(component)!.map((option: any) => option.label)).toEqual([ - 'ESQL', - 'SQL', - ]); - }); -}); diff --git a/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx b/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx deleted file mode 100644 index ac4717ad40d48..0000000000000 --- a/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiSelectable, EuiPanel, EuiBadge } from '@elastic/eui'; -import { TextBasedLanguages } from './data_view_picker'; - -export interface TextBasedLanguagesListProps { - textBasedLanguages: TextBasedLanguages[]; - onChange: (lang: string) => void; - selectedOption: string; -} - -// Needed for React.lazy -// eslint-disable-next-line import/no-default-export -export default function TextBasedLanguagesList({ - textBasedLanguages, - onChange, - selectedOption, -}: TextBasedLanguagesListProps) { - return ( - - key="textbasedLanguages-options" - data-test-subj="text-based-languages-switcher" - singleSelection="always" - options={textBasedLanguages.map((lang) => ({ - key: lang, - label: lang, - value: lang, - checked: lang === selectedOption ? 'on' : undefined, - append: ( - - {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel', { - defaultMessage: 'Technical preview', - })} - - ), - }))} - onChange={(choices) => { - const choice = choices.find(({ checked }) => checked) as unknown as { - value: string; - }; - onChange(choice.value); - }} - > - {(list) => ( - - {list} - - )} - - ); -} diff --git a/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx b/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx index f7eb0c4a74a22..c59599907456b 100644 --- a/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx +++ b/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.tsx @@ -25,18 +25,21 @@ import { export interface TextBasedLanguagesTransitionModalProps { closeModal: (dismissFlag: boolean, needsSave?: boolean) => void; setIsTextLangTransitionModalVisible: (flag: boolean) => void; + textBasedLanguage?: string; } // Needed for React.lazy // eslint-disable-next-line import/no-default-export export default function TextBasedLanguagesTransitionModal({ closeModal, setIsTextLangTransitionModalVisible, + textBasedLanguage, }: TextBasedLanguagesTransitionModalProps) { const [dismissModalChecked, setDismissModalChecked] = useState(false); const onTransitionModalDismiss = useCallback((e) => { setDismissModalChecked(e.target.checked); }, []); + const language = textBasedLanguage?.toUpperCase(); return ( setIsTextLangTransitionModalVisible(false)} style={{ width: 700 }}> @@ -56,7 +59,8 @@ export default function TextBasedLanguagesTransitionModal({ 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody', { defaultMessage: - "Switching data views removes the current SQL query. Save this search to ensure you don't lose work.", + "Switching data views removes the current {language} query. Save this search to ensure you don't lose work.", + values: { language }, } )}
    diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx index 04379d34a3946..aa941fe395733 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx @@ -20,6 +20,7 @@ export interface GenericComboBoxProps { searchValue: string, OPTION_CONTENT_CLASSNAME: string ) => React.ReactNode; + inputRef?: ((instance: HTMLInputElement | null) => void) | undefined; [propName: string]: any; } diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx index ae87024bb61a9..0a466c61770ce 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx @@ -31,12 +31,8 @@ const COMBOBOX_PADDINGS = 10; const DEFAULT_FONT = '14px Inter'; class PhraseValueInputUI extends PhraseSuggestorUI { - comboBoxRef: React.RefObject; - - constructor(props: PhraseValueInputProps) { - super(props); - this.comboBoxRef = React.createRef(); - } + comboBoxWrapperRef = React.createRef(); + inputRef: HTMLInputElement | null = null; public render() { return ( @@ -69,8 +65,11 @@ class PhraseValueInputUI extends PhraseSuggestorUI { const valueAsStr = String(value); const options = value ? uniq([valueAsStr, ...suggestions]) : suggestions; return ( -
    +
    { + this.inputRef = ref; + }} isDisabled={this.props.disabled} fullWidth={fullWidth} compressed={this.props.compressed} @@ -85,7 +84,13 @@ class PhraseValueInputUI extends PhraseSuggestorUI { options={options} getLabel={(option) => option} selectedOptions={value ? [valueAsStr] : []} - onChange={([newValue = '']) => onChange(newValue)} + onChange={([newValue = '']) => { + onChange(newValue); + setTimeout(() => { + // Note: requires a tick skip to correctly blur element focus + this.inputRef?.blur(); + }); + }} onSearchChange={this.onSearchChange} singleSelection={{ asPlainText: true }} onCreateOption={onChange} @@ -98,7 +103,7 @@ class PhraseValueInputUI extends PhraseSuggestorUI { defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH} defaultFont={DEFAULT_FONT} comboboxPaddings={COMBOBOX_PADDINGS} - comboBoxRef={this.comboBoxRef} + comboBoxWrapperRef={this.comboBoxWrapperRef} label={option.label} search={searchValue} /> diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx index 336849c4ee65a..b31e6aad7d438 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx @@ -33,12 +33,7 @@ const COMBOBOX_PADDINGS = 20; const DEFAULT_FONT = '14px Inter'; class PhrasesValuesInputUI extends PhraseSuggestorUI { - comboBoxRef: React.RefObject; - - constructor(props: PhrasesValuesInputProps) { - super(props); - this.comboBoxRef = React.createRef(); - } + comboBoxWrapperRef = React.createRef(); public render() { const { suggestions } = this.state; @@ -46,7 +41,7 @@ class PhrasesValuesInputUI extends PhraseSuggestorUI { const options = values ? uniq([...values, ...suggestions]) : suggestions; return ( -
    +
    { defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH} defaultFont={DEFAULT_FONT} comboboxPaddings={COMBOBOX_PADDINGS} - comboBoxRef={this.comboBoxRef} + comboBoxWrapperRef={this.comboBoxWrapperRef} label={option.label} search={searchValue} /> diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx index 47ea4cbf2c0eb..08236041ab93a 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx @@ -6,18 +6,16 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { mount } from 'enzyme'; import { TruncatedLabel } from './truncated_label'; describe('truncated_label', () => { - const defaultProps = { + const defaultProps: ComponentProps = { defaultFont: '14px Inter', - // jest-canvas-mock mocks measureText as the number of string characters, thats why the width is so low - width: 30, defaultComboboxWidth: 130, comboboxPaddings: 100, - comboBoxRef: React.createRef(), + comboBoxWrapperRef: React.createRef(), search: '', label: 'example_field', }; diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx index 9f268e46d7929..21304ad244edf 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx @@ -15,7 +15,7 @@ import { throttle } from 'lodash'; interface TruncatedLabelProps { label: string; search: string; - comboBoxRef: RefObject; + comboBoxWrapperRef: RefObject; defaultFont: string; defaultComboboxWidth: number; comboboxPaddings: number; @@ -56,7 +56,7 @@ const truncateLabel = ( export const TruncatedLabel = React.memo(function TruncatedLabel({ label, - comboBoxRef, + comboBoxWrapperRef, search, defaultFont, defaultComboboxWidth, @@ -69,15 +69,14 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({ width: defaultComboboxWidth - comboboxPaddings, font: defaultFont, }); - const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => { - if (comboBoxRef.current) { + if (comboBoxWrapperRef.current) { const current = { ...labelProps, - width: comboBoxRef.current?.clientWidth - comboboxPaddings, + width: comboBoxWrapperRef.current.clientWidth - comboboxPaddings, }; if (shouldRecomputeAll) { - current.font = window.getComputedStyle(comboBoxRef.current).font; + current.font = window.getComputedStyle(comboBoxWrapperRef.current).font; } setLabelProps(current); } @@ -88,7 +87,7 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({ }, 50); useEffectOnce(() => { - if (comboBoxRef.current) { + if (comboBoxWrapperRef.current) { handleResize(undefined, true); } diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx b/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx index 3dceef3ef52c9..8c3dc65758c29 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx +++ b/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx @@ -43,7 +43,8 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps) const { disabled, suggestionsAbstraction } = useContext(FiltersBuilderContextType); const fields = dataView ? getFilterableFields(dataView) : []; const id = useGeneratedHtmlId({ prefix: 'fieldInput' }); - const comboBoxRef = useRef(null); + const comboBoxWrapperRef = useRef(null); + const inputRef = useRef(null); const onFieldChange = useCallback( ([selectedField]: DataViewField[]) => { @@ -77,12 +78,25 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps) ({ label }) => fields[optionFields.findIndex((optionField) => optionField.label === label)] ); onFieldChange(newValues); + + setTimeout(() => { + // Note: requires a tick skip to correctly blur element focus + inputRef?.current?.blur(); + }); + }; + + const handleFocus: React.FocusEventHandler = () => { + // Force focus on input due to https://github.com/elastic/eui/issues/7170 + inputRef?.current?.focus(); }; return ( -
    +
    { + inputRef.current = ref; + }} options={euiOptions} selectedOptions={selectedEuiOptions} onChange={onComboBoxChange} @@ -94,6 +108,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps) isClearable={false} compressed fullWidth + onFocus={handleFocus} data-test-subj="filterFieldSuggestionList" renderOption={(option, searchValue) => ( @@ -105,7 +120,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps) defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH} defaultFont={DEFAULT_FONT} comboboxPaddings={COMBOBOX_PADDINGS} - comboBoxRef={comboBoxRef} + comboBoxWrapperRef={comboBoxWrapperRef} label={option.label} search={searchValue} /> diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts index 5ca888735f5ad..6ec0ac9ab7058 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts +++ b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts @@ -22,6 +22,13 @@ export const cursorOrCss = css` export const fieldAndParamCss = (euiTheme: EuiThemeComputed) => css` min-width: calc(${euiTheme.size.xl} * 5); + flex-grow: 1; + .euiFormRow { + max-width: 800px; + } + &:focus-within { + flex-grow: 4; + } `; export const operationCss = (euiTheme: EuiThemeComputed) => css` diff --git a/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx b/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx index 7cd8b9d0251d8..f7148db93ce19 100644 --- a/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx @@ -14,10 +14,7 @@ import { EuiComboBox, EuiComboBoxProps } from '@elastic/eui'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; export type IndexPatternSelectProps = Required< - Omit< - EuiComboBoxProps, - 'isLoading' | 'onSearchChange' | 'options' | 'selectedOptions' | 'onChange' - >, + Omit, 'onSearchChange' | 'options' | 'selectedOptions' | 'onChange'>, 'placeholder' > & { onChange: (indexPatternId?: string) => void; @@ -155,7 +152,7 @@ export default class IndexPatternSelect extends Component { if (enableTooltip && query && isOfAggregateQueryType(query)) { const textBasedLanguage = getAggregateQueryMode(query); + const displayName = getLanguageDisplayName(textBasedLanguage); return ( {children} @@ -138,6 +144,7 @@ export interface QueryBarTopRowProps onFiltersUpdated?: (filters: Filter[]) => void; dataViewPickerComponentProps?: DataViewPickerProps; textBasedLanguageModeErrors?: Error[]; + textBasedLanguageModeWarning?: string; onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void; filterBar?: React.ReactNode; showDatePickerAsBadge?: boolean; @@ -648,6 +655,7 @@ export const QueryBarTopRow = React.memo( expandCodeEditor={(status: boolean) => setCodeEditorIsExpanded(status)} isCodeEditorExpanded={codeEditorIsExpanded} errors={props.textBasedLanguageModeErrors} + warning={props.textBasedLanguageModeWarning} detectTimestamp={detectTimestamp} onTextLangQuerySubmit={() => onSubmit({ diff --git a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx index 0879a12f31cef..8ffca3fbb0bda 100644 --- a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx @@ -247,6 +247,7 @@ export function createSearchBar({ {...overrideDefaultBehaviors(props)} dataViewPickerComponentProps={props.dataViewPickerComponentProps} textBasedLanguageModeErrors={props.textBasedLanguageModeErrors} + textBasedLanguageModeWarning={props.textBasedLanguageModeWarning} onTextBasedSavedAndExit={props.onTextBasedSavedAndExit} displayStyle={props.displayStyle} isScreenshotMode={isScreenshotMode} diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index aa84d4136ed36..b2d3b5abc966c 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -104,6 +104,7 @@ export interface SearchBarOwnProps { fillSubmitButton?: boolean; dataViewPickerComponentProps?: DataViewPickerProps; textBasedLanguageModeErrors?: Error[]; + textBasedLanguageModeWarning?: string; onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void; showSubmitButton?: boolean; submitButtonStyle?: QueryBarTopRowProps['submitButtonStyle']; @@ -597,6 +598,7 @@ class SearchBarUI extends C onFiltersUpdated={this.props.onFiltersUpdated} dataViewPickerComponentProps={this.props.dataViewPickerComponentProps} textBasedLanguageModeErrors={this.props.textBasedLanguageModeErrors} + textBasedLanguageModeWarning={this.props.textBasedLanguageModeWarning} onTextBasedSavedAndExit={this.props.onTextBasedSavedAndExit} showDatePickerAsBadge={this.shouldShowDatePickerAsBadge()} filterBar={filterBar} diff --git a/src/plugins/vis_default_editor/public/components/controls/percentiles.tsx b/src/plugins/vis_default_editor/public/components/controls/percentiles.tsx index 839d97c51228d..85bf77567c1cd 100644 --- a/src/plugins/vis_default_editor/public/components/controls/percentiles.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/percentiles.tsx @@ -42,6 +42,7 @@ function PercentilesEditor({ id={`visEditorPercentileLabel${agg.id}`} isInvalid={showValidation ? !isValid : false} display="rowCompressed" + data-test-subj="visEditorPercentile" > ({ - getFieldNameFromField: jest.fn(() => mockGetFieldNameFromField()), - getLabel: jest.fn(() => mockGetLabel()), - getLabelForPercentile: jest.fn(() => mockGetLabelForPercentile()), -})); +jest.mock('../utils', () => { + const utils = jest.requireActual('../utils'); + return { + ...utils, + getFieldNameFromField: jest.fn(() => mockGetFieldNameFromField()), + getLabel: jest.fn(() => mockGetLabel()), + getLabelForPercentile: jest.fn(() => mockGetLabelForPercentile()), + }; +}); describe('convertToPercentileColumn', () => { const visType = 'heatmap'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts index 9989db1c5dda7..43229a610b041 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts @@ -9,7 +9,7 @@ import { METRIC_TYPES } from '@kbn/data-plugin/common'; import { SchemaConfig } from '../../..'; import { isFieldValid, PercentileParams } from '../..'; -import { getFieldNameFromField, getLabelForPercentile } from '../utils'; +import { getAggIdAndValue, getFieldNameFromField, getLabelForPercentile } from '../utils'; import { createColumn, getFormat } from './column'; import { PercentileColumn, CommonColumnConverterArgs } from './types'; import { SUPPORTED_METRICS } from './supported_metrics'; @@ -40,7 +40,7 @@ const getPercent = ( const { percents } = aggParams; - const [, percentStr] = aggId.split('.'); + const [, percentStr] = getAggIdAndValue(aggId); const percent = Number(percentStr); if (!percents || !percents.length || percentStr === '' || isNaN(percent)) { diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts index 72cd07ba03f7c..2c36925b74d3b 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts @@ -19,14 +19,18 @@ const mockIsStdDevAgg = jest.fn(); const mockGetFieldByName = jest.fn(); const originalGetFieldByName = stubLogstashDataView.getFieldByName; -jest.mock('../utils', () => ({ - getFieldNameFromField: jest.fn((field) => field), - getMetricFromParentPipelineAgg: jest.fn(() => mockGetMetricFromParentPipelineAgg()), - isPercentileAgg: jest.fn(() => mockIsPercentileAgg()), - isPercentileRankAgg: jest.fn(() => mockIsPercentileRankAgg()), - isPipeline: jest.fn(() => mockIsPipeline()), - isStdDevAgg: jest.fn(() => mockIsStdDevAgg()), -})); +jest.mock('../utils', () => { + const utils = jest.requireActual('../utils'); + return { + ...utils, + getFieldNameFromField: jest.fn((field) => field), + getMetricFromParentPipelineAgg: jest.fn(() => mockGetMetricFromParentPipelineAgg()), + isPercentileAgg: jest.fn(() => mockIsPercentileAgg()), + isPercentileRankAgg: jest.fn(() => mockIsPercentileRankAgg()), + isPipeline: jest.fn(() => mockIsPipeline()), + isStdDevAgg: jest.fn(() => mockIsStdDevAgg()), + }; +}); const dataView = stubLogstashDataView; const visType = 'heatmap'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts index 4492cd58ac230..c2e30425e9488 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts @@ -12,6 +12,7 @@ import { Operations } from '../../constants'; import { isMetricWithField, getStdDeviationFormula, ExtendedColumnConverterArgs } from '../convert'; import { getFormulaFromMetric, SUPPORTED_METRICS } from '../convert/supported_metrics'; import { + getAggIdAndValue, getFieldNameFromField, getMetricFromParentPipelineAgg, isPercentileAgg, @@ -125,7 +126,7 @@ const getFormulaForPercentile = ( selector: string, reducedTimeRange?: string ) => { - const percentile = Number(agg.aggId?.split('.')[1]); + const percentile = Number(getAggIdAndValue(agg.aggId)[1]); const op = SUPPORTED_METRICS[agg.aggType]; if (!isValidAgg(visType, agg, dataView) || !op) { return null; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts index ce50312d92cf3..e323b2169f519 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts @@ -199,3 +199,17 @@ export const getMetricFromParentPipelineAgg = ( return metric as SchemaConfig; }; + +const aggIdWithDecimalsRegexp = /^(\w)+\['([0-9]+\.[0-9]+)'\]$/; + +export const getAggIdAndValue = (aggId?: string) => { + if (!aggId) { + return []; + } + // agg value contains decimals + if (/\['/.test(aggId)) { + const [_, id, value] = aggId.match(aggIdWithDecimalsRegexp) || []; + return [id, value]; + } + return aggId.split('.'); +}; diff --git a/src/plugins/visualizations/public/content_management/visualization_client.ts b/src/plugins/visualizations/public/content_management/visualization_client.ts index 38eec4141a045..957c9db39a9c4 100644 --- a/src/plugins/visualizations/public/content_management/visualization_client.ts +++ b/src/plugins/visualizations/public/content_management/visualization_client.ts @@ -66,6 +66,13 @@ const deleteVisualization = async (id: string) => { }; const search = async (query: SearchQuery = {}, options?: VisualizationSearchQuery) => { + if (options && options.types && options.types.length > 1) { + const { types } = options; + return getContentManagement().client.mSearch({ + contentTypes: types.map((type) => ({ contentTypeId: type })), + query, + }); + } return getContentManagement().client.search({ contentTypeId: 'visualization', query, diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts index aa338db367988..9b4f9d718804c 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -40,9 +40,13 @@ jest.mock('../../common/convert_to_lens/lib/buckets', () => ({ convertBucketToColumns: jest.fn(() => mockConvertBucketToColumns()), })); -jest.mock('../../common/convert_to_lens/lib/utils', () => ({ - getCustomBucketsFromSiblingAggs: jest.fn(() => mockGetCutomBucketsFromSiblingAggs()), -})); +jest.mock('../../common/convert_to_lens/lib/utils', () => { + const utils = jest.requireActual('../../common/convert_to_lens/lib/utils'); + return { + ...utils, + getCustomBucketsFromSiblingAggs: jest.fn(() => mockGetCutomBucketsFromSiblingAggs()), + }; +}); jest.mock('../vis_schemas', () => ({ getVisSchemas: jest.fn(() => mockGetVisSchemas()), diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 1b44f7cdffda1..886be04bb654a 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -10,7 +10,10 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { IAggConfig, METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; import { AggBasedColumn, PercentageModeConfig, SchemaConfig } from '../../common'; import { convertMetricToColumns } from '../../common/convert_to_lens/lib/metrics'; -import { getCustomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; +import { + getAggIdAndValue, + getCustomBucketsFromSiblingAggs, +} from '../../common/convert_to_lens/lib/utils'; import { BucketColumn } from '../../common/convert_to_lens/lib'; import type { Vis } from '../types'; import { getVisSchemas, Schemas } from '../vis_schemas'; @@ -178,11 +181,12 @@ export const getColumnsFromVis = ( if (series && series.length) { for (const { metrics: metricAggIds } of series) { + const metricAggIdsLookup = new Set(metricAggIds); const metrics = aggs.filter( - (agg) => agg.aggId && metricAggIds.includes(agg.aggId.split('.')[0]) + (agg) => agg.aggId && metricAggIdsLookup.has(getAggIdAndValue(agg.aggId)[0]) ); const customBucketsForLayer = customBucketsWithMetricIds.filter((c) => - c.metricIds.some((m) => metricAggIds.includes(m)) + c.metricIds.some((m) => metricAggIdsLookup.has(m)) ); const layer = createLayer( vis.type.name, diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index 9d34d2fb26ca6..735c5e6572d43 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -19,6 +19,7 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { TimefilterContract } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { Warnings } from '@kbn/charts-plugin/public'; +import { hasUnsupportedDownsampledAggregationFailure } from '@kbn/search-response-warnings'; import { Adapters, AttributeService, @@ -351,11 +352,13 @@ export class VisualizeEmbeddable this.deps .start() .plugins.data.search.showWarnings(this.getInspectorAdapters()!.requests!, (warning) => { - if ( - warning.type === 'shard_failure' && - warning.reason.type === 'unsupported_aggregation_on_downsampled_index' - ) { - warnings.push(warning.reason.reason || warning.message); + if (hasUnsupportedDownsampledAggregationFailure(warning)) { + warnings.push( + i18n.translate('visualizations.embeddable.tsdbRollupWarning', { + defaultMessage: + 'Visualization uses a function that is unsupported by rolled up data. Select a different function or change the time range.', + }) + ); return true; } if (this.vis.type.suppressWarnings?.()) { @@ -582,7 +585,7 @@ export class VisualizeEmbeddable timeRange: this.timeRange, query: this.input.query, filters: this.input.filters, - disableShardWarnings: true, + disableWarningToasts: true, }, variables: { embeddableTitle: this.getTitle(), diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index b34cfd1c9a622..88e3f6e5fbf58 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -24,7 +24,15 @@ export { getVisSchemas } from './vis_schemas'; /** @public types */ export type { VisualizationsSetup, VisualizationsStart }; export { VisGroups } from './vis_types/vis_groups_enum'; -export type { BaseVisType, VisTypeAlias, VisTypeDefinition, Schema, ISchemas } from './vis_types'; +export type { + BaseVisType, + VisTypeAlias, + VisTypeDefinition, + Schema, + ISchemas, + VisualizationClient, + SerializableAttributes, +} from './vis_types'; export type { Vis, SerializedVis, SerializedVisData, VisData } from './vis'; export type VisualizeEmbeddableFactoryContract = PublicContract; export type VisualizeEmbeddableContract = PublicContract; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index d2805b43ed468..cd840302ff01d 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -333,6 +333,7 @@ export class VisualizationsPlugin unifiedSearch: pluginsStart.unifiedSearch, serverless: pluginsStart.serverless, noDataPage: pluginsStart.noDataPage, + contentManagement: pluginsStart.contentManagement, }; params.element.classList.add('visAppWrapper'); diff --git a/src/plugins/visualizations/public/utils/saved_objects_utils/update_basic_attributes.ts b/src/plugins/visualizations/public/utils/saved_objects_utils/update_basic_attributes.ts index ca6158422f58a..417b3a52b5bed 100644 --- a/src/plugins/visualizations/public/utils/saved_objects_utils/update_basic_attributes.ts +++ b/src/plugins/visualizations/public/utils/saved_objects_utils/update_basic_attributes.ts @@ -9,12 +9,42 @@ import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { OverlayStart } from '@kbn/core-overlays-browser'; +import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import { extractReferences } from '../saved_visualization_references'; import { visualizationsClient } from '../../content_management'; +import { TypesStart } from '../../vis_types'; interface UpdateBasicSoAttributesDependencies { savedObjectsTagging?: SavedObjectsTaggingApi; overlays: OverlayStart; + typesService: TypesStart; + contentManagement: ContentManagementPublicStart; +} + +function getClientForType( + type: string, + typesService: TypesStart, + contentManagement: ContentManagementPublicStart +) { + const visAliases = typesService.getAliases(); + return ( + visAliases + .find((v) => v.appExtensions?.visualizations.docTypes.includes(type)) + ?.appExtensions?.visualizations.client(contentManagement) || visualizationsClient + ); +} + +function getAdditionalOptionsForUpdate( + type: string, + typesService: TypesStart, + method: 'update' | 'create' +) { + const visAliases = typesService.getAliases(); + const aliasType = visAliases.find((v) => v.appExtensions?.visualizations.docTypes.includes(type)); + if (!aliasType) { + return { overwrite: true }; + } + return aliasType?.appExtensions?.visualizations?.clientOptions?.[method]; } export const updateBasicSoAttributes = async ( @@ -27,7 +57,9 @@ export const updateBasicSoAttributes = async ( }, dependencies: UpdateBasicSoAttributesDependencies ) => { - const so = await visualizationsClient.get(soId); + const client = getClientForType(type, dependencies.typesService, dependencies.contentManagement); + + const so = await client.get(soId); const extractedReferences = extractReferences({ attributes: so.item.attributes, references: so.item.references, @@ -48,14 +80,14 @@ export const updateBasicSoAttributes = async ( ); } - return await visualizationsClient.update({ + return await client.update({ id: soId, data: { ...attributes, }, options: { - overwrite: true, references, + ...getAdditionalOptionsForUpdate(type, dependencies.typesService, 'update'), }, }); }; diff --git a/src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.ts b/src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.ts index db19f6fc8aaa1..8945da771db7f 100644 --- a/src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.ts +++ b/src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.ts @@ -12,17 +12,17 @@ import { injectSearchSourceReferences, SerializedSearchSourceFields, } from '@kbn/data-plugin/public'; -import { SerializableRecord } from '@kbn/utility-types'; import { SavedVisState, VisSavedObject } from '../../types'; import { extractTimeSeriesReferences, injectTimeSeriesReferences } from './timeseries_references'; import { extractControlsReferences, injectControlsReferences } from './controls_references'; +import type { SerializableAttributes } from '../../vis_types/vis_type_alias_registry'; export function extractReferences({ attributes, references = [], }: { - attributes: SerializableRecord; + attributes: SerializableAttributes; references: SavedObjectReference[]; }) { const updatedAttributes = { ...attributes }; diff --git a/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts b/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts index d5b26fe455ac6..01475fe483c80 100644 --- a/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts +++ b/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts @@ -73,6 +73,7 @@ jest.mock('../services', () => ({ update: mockUpdateContent, get: mockGetContent, search: mockFindContent, + mSearch: mockFindContent, }, })), })); @@ -358,10 +359,11 @@ describe('saved_visualize_utils', () => { expect(mockFindContent.mock.calls).toMatchObject([ [ { - options: { - types: ['bazdoc', 'etc', 'visualization'], - searchFields: ['baz', 'bing', 'title^3', 'description'], - }, + contentTypes: [ + { contentTypeId: 'bazdoc' }, + { contentTypeId: 'etc' }, + { contentTypeId: 'visualization' }, + ], }, ], ]); @@ -395,10 +397,12 @@ describe('saved_visualize_utils', () => { expect(mockFindContent.mock.calls).toMatchObject([ [ { - options: { - types: ['bazdoc', 'bar', 'visualization', 'foo'], - searchFields: ['baz', 'bing', 'barfield', 'foofield', 'title^3', 'description'], - }, + contentTypes: [ + { contentTypeId: 'bazdoc' }, + { contentTypeId: 'bar' }, + { contentTypeId: 'visualization' }, + { contentTypeId: 'foo' }, + ], }, ], ]); diff --git a/src/plugins/visualizations/public/vis_types/index.ts b/src/plugins/visualizations/public/vis_types/index.ts index 365f0d51bf4f3..d595586df2d47 100644 --- a/src/plugins/visualizations/public/vis_types/index.ts +++ b/src/plugins/visualizations/public/vis_types/index.ts @@ -11,3 +11,4 @@ export { Schemas } from './schemas'; export { VisGroups } from './vis_groups_enum'; export { BaseVisType } from './base_vis_type'; export type { VisTypeDefinition, ISchemas, Schema } from './types'; +export type { VisualizationClient, SerializableAttributes } from './vis_type_alias_registry'; diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index 61e36c931390e..2a46b28f06dd9 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -6,6 +6,13 @@ * Side Public License, v 1. */ +import { SearchQuery } from '@kbn/content-management-plugin/common'; +import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import { + ContentManagementCrudTypes, + SavedObjectCreateOptions, + SavedObjectUpdateOptions, +} from '@kbn/content-management-utils'; import type { SimpleSavedObject } from '@kbn/core/public'; import { BaseVisType } from './base_vis_type'; @@ -27,9 +34,54 @@ export interface VisualizationListItem { type?: BaseVisType | string; } +export interface SerializableAttributes { + [key: string]: unknown; +} + +export type GenericVisualizationCrudTypes< + ContentType extends string, + Attr extends SerializableAttributes +> = ContentManagementCrudTypes< + ContentType, + Attr, + Pick, + Pick, + object +>; + +export interface VisualizationClient< + ContentType extends string = string, + Attr extends SerializableAttributes = SerializableAttributes +> { + get: (id: string) => Promise['GetOut']>; + create: ( + visualization: Omit< + GenericVisualizationCrudTypes['CreateIn'], + 'contentTypeId' + > + ) => Promise['CreateOut']>; + update: ( + visualization: Omit< + GenericVisualizationCrudTypes['UpdateIn'], + 'contentTypeId' + > + ) => Promise['UpdateOut']>; + delete: (id: string) => Promise['DeleteOut']>; + search: ( + query: SearchQuery, + options?: object + ) => Promise['SearchOut']>; +} + export interface VisualizationsAppExtension { docTypes: string[]; searchFields?: string[]; + /** let each visualization client pass its own custom options if required */ + clientOptions?: { + update?: { overwrite?: boolean; [otherOption: string]: unknown }; + create?: { [otherOption: string]: unknown }; + }; + client: (contentManagement: ContentManagementPublicStart) => VisualizationClient; toListItem: (savedObject: SimpleSavedObject) => VisualizationListItem; } diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx index 30bb05c7b43d6..d4beb45c4e248 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx @@ -93,7 +93,7 @@ type CustomTableViewProps = Pick< | 'editItem' | 'contentEditor' | 'emptyPrompt' - | 'showEditActionForItem' + | 'itemIsEditable' >; const useTableListViewProps = ( @@ -109,6 +109,7 @@ const useTableListViewProps = ( overlays, toastNotifications, visualizeCapabilities, + contentManagement, }, } = useKibana(); @@ -176,11 +177,16 @@ const useTableListViewProps = ( description: args.description ?? '', tags: args.tags, }, - { overlays, savedObjectsTagging } + { + overlays, + savedObjectsTagging, + typesService: getTypes(), + contentManagement, + } ); } }, - [overlays, savedObjectsTagging] + [overlays, savedObjectsTagging, contentManagement] ); const contentEditorValidators: OpenContentEditorParams['customValidators'] = useMemo( @@ -251,8 +257,7 @@ const useTableListViewProps = ( editItem, emptyPrompt: noItemsFragment, createItem: createNewVis, - showEditActionForItem: ({ attributes: { readOnly } }) => - visualizeCapabilities.save && !readOnly, + itemIsEditable: ({ attributes: { readOnly } }) => visualizeCapabilities.save && !readOnly, }; return props; diff --git a/src/plugins/visualizations/public/visualize_app/types.ts b/src/plugins/visualizations/public/visualize_app/types.ts index 77f743aaeef77..abc55e3e671fe 100644 --- a/src/plugins/visualizations/public/visualize_app/types.ts +++ b/src/plugins/visualizations/public/visualize_app/types.ts @@ -42,6 +42,7 @@ import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plug import type { SavedSearch, SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; +import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import type { Vis, VisualizeEmbeddableContract, @@ -119,6 +120,7 @@ export interface VisualizeServices extends CoreStart { unifiedSearch: UnifiedSearchPublicPluginStart; serverless?: ServerlessPluginStart; noDataPage?: NoDataPagePluginStart; + contentManagement: ContentManagementPublicStart; } export interface VisInstance { diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json index e643d9fa1fd61..a835f3151c60c 100644 --- a/src/plugins/visualizations/tsconfig.json +++ b/src/plugins/visualizations/tsconfig.json @@ -63,7 +63,8 @@ "@kbn/content-management-table-list-view", "@kbn/content-management-utils", "@kbn/serverless", - "@kbn/no-data-page-plugin" + "@kbn/no-data-page-plugin", + "@kbn/search-response-warnings" ], "exclude": [ "target/**/*", diff --git a/test/api_integration/apis/event_annotations/event_annotations.ts b/test/api_integration/apis/event_annotations/event_annotations.ts index fda97881f3d9b..fc4a313279b02 100644 --- a/test/api_integration/apis/event_annotations/event_annotations.ts +++ b/test/api_integration/apis/event_annotations/event_annotations.ts @@ -10,18 +10,29 @@ import expect from '@kbn/expect'; import type { EventAnnotationGroupSavedObjectAttributes, EventAnnotationGroupCreateIn, + EventAnnotationGroupCreateOut, EventAnnotationGroupUpdateIn, EventAnnotationGroupSearchIn, + EventAnnotationGroupSearchOut, + EventAnnotationGroupGetIn, + EventAnnotationGroupGetOut, + EventAnnotationGroupDeleteIn, + EventAnnotationGroupDeleteOut, } from '@kbn/event-annotation-plugin/common'; import { CONTENT_ID } from '@kbn/event-annotation-plugin/common'; +import { EVENT_ANNOTATION_GROUP_TYPE } from '@kbn/event-annotation-common'; import { FtrProviderContext } from '../../ftr_provider_context'; const CONTENT_ENDPOINT = '/api/content_management/rpc'; const API_VERSION = 1; -const EXISTING_ID_1 = 'fcebef20-3ba4-11ee-85d3-3dd00bdd66ef'; // from loaded archive -const EXISTING_ID_2 = '0d1aa670-3baf-11ee-a4a7-c11cb33a9549'; // from loaded archive +// IDs come from from loaded archive +const EXISTING_ID_1 = '46c2a460-4e77-11ee-bb97-116581699678'; +const EXISTING_ID_2 = '425d2760-4e77-11ee-bb97-116581699678'; +const DESCRIPTION_2 = 'i am a description you can search for!'; +const EXISTING_ID_3 = '3905a4d0-4e77-11ee-bb97-116581699678'; +const TAG_ID = '36a8f020-4e77-11ee-bb97-116581699678'; const DEFAULT_EVENT_ANNOTATION_GROUP: EventAnnotationGroupSavedObjectAttributes = { title: 'a group', @@ -56,6 +67,8 @@ export default function ({ getService }: FtrProviderContext) { describe('group API', () => { before(async () => { + await kibanaServer.savedObjects.clean({ types: [EVENT_ANNOTATION_GROUP_TYPE] }); + await kibanaServer.importExport.load( 'test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json' ); @@ -65,10 +78,79 @@ export default function ({ getService }: FtrProviderContext) { await kibanaServer.importExport.unload( 'test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json' ); + + await kibanaServer.savedObjects.clean({ types: [EVENT_ANNOTATION_GROUP_TYPE] }); + }); + + describe('get', () => { + it(`should retrieve an existing group`, async () => { + const payload: EventAnnotationGroupGetIn = { + contentTypeId: CONTENT_ID, + id: EXISTING_ID_1, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/get`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(200); + + const result = resp.body.result.result as EventAnnotationGroupGetOut; + + expect(result.item.id).to.be(EXISTING_ID_1); + expect(result.meta.outcome).to.be('exactMatch'); + expect(result.item.references.length).to.be(1); + expect(result.item.attributes).to.eql({ + annotations: [ + { + filter: { + language: 'kuery', + query: + 'agent.keyword : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" ', + type: 'kibana_query', + }, + icon: 'triangle', + id: 'fdede168-eff1-400f-b106-0f62061f5099', + key: { + type: 'point_in_time', + }, + label: 'Event', + timeField: 'timestamp', + type: 'query', + }, + ], + dataViewSpec: null, + description: '', + ignoreGlobalFilters: true, + title: 'group3', + }); + }); + + it(`should reject a group that does not exist`, async () => { + const payload: EventAnnotationGroupGetIn = { + contentTypeId: CONTENT_ID, + id: 'does-not-exist', + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/get`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(404); + + expect(resp.body).to.eql({ + error: 'Not Found', + message: 'Saved object [event-annotation-group/does-not-exist] not found', + statusCode: 404, + }); + }); }); describe('search', () => { - // TODO test tag searching, ordering, pagination, etc + const performSearch = (payload: EventAnnotationGroupSearchIn) => + supertest.post(`${CONTENT_ENDPOINT}/search`).set('kbn-xsrf', 'kibana').send(payload); it(`should retrieve existing groups`, async () => { const payload: EventAnnotationGroupSearchIn = { @@ -83,19 +165,172 @@ export default function ({ getService }: FtrProviderContext) { version: API_VERSION, }; + const resp = await performSearch(payload).expect(200); + + const results = resp.body.result.result.hits; + expect(results.length).to.be(3); + expect(results.map(({ id }: { id: string }) => id)).to.eql([ + EXISTING_ID_1, + EXISTING_ID_2, + EXISTING_ID_3, + ]); + }); + + it(`should filter by tag`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1000, + tags: { + included: [TAG_ID], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(2); + expect( + result.hits.every(({ references }) => references.map(({ id }) => id).includes(TAG_ID)) + ).to.be(true); + }); + + it(`should filter by text`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1000, + text: DESCRIPTION_2, + tags: { + included: [], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(1); + expect(result.hits[0].id).to.be(EXISTING_ID_2); + }); + + it(`should paginate`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1, + cursor: '1', + tags: { + included: [], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(1); + expect(result.hits[0].id).to.be(EXISTING_ID_1); + expect(result.pagination.total).to.be(3); + + // get second page + payload.query.cursor = '2'; + + const resp2 = await performSearch(payload).expect(200); + + const result2 = resp2.body.result.result as EventAnnotationGroupSearchOut; + expect(result2.hits.length).to.be(1); + expect(result2.hits[0].id).to.be(EXISTING_ID_2); + expect(result2.pagination.total).to.be(3); + + // get third page + payload.query.cursor = '3'; + + const resp3 = await performSearch(payload).expect(200); + + const result3 = resp3.body.result.result as EventAnnotationGroupSearchOut; + expect(result3.hits.length).to.be(1); + expect(result3.hits[0].id).to.be(EXISTING_ID_3); + expect(result3.pagination.total).to.be(3); + }); + }); + + describe('create', () => { + it(`should create a new group`, async () => { + const payload: EventAnnotationGroupCreateIn = { + contentTypeId: CONTENT_ID, + data: DEFAULT_EVENT_ANNOTATION_GROUP, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + const resp = await supertest - .post(`${CONTENT_ENDPOINT}/search`) + .post(`${CONTENT_ENDPOINT}/create`) .set('kbn-xsrf', 'kibana') .send(payload) .expect(200); - const results = resp.body.result.result.hits; - expect(results.length).to.be(2); - expect(results.map(({ id }: { id: string }) => id)).to.eql([EXISTING_ID_2, EXISTING_ID_1]); + const result = resp.body.result.result as EventAnnotationGroupCreateOut; + + expect(result.item.attributes).to.eql(DEFAULT_EVENT_ANNOTATION_GROUP); + expect(result.item.id).to.be.a('string'); + expect(result.item.namespaces).to.eql(['default']); + }); + + it(`should reject malformed groups`, async () => { + const badGroups = [ + // extra property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + extraProp: 'some-value', + }, + // missing title + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + title: undefined, + }, + // wrong type for property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + ignoreGlobalFilters: 'not-a-boolean', + }, + ] as unknown as EventAnnotationGroupSavedObjectAttributes[]; // (coerce the types because these are intentionally malformed) + + const expectedMessages = [ + 'Invalid data. [extraProp]: definition for this key is missing', + 'Invalid data. [title]: expected value of type [string] but got [undefined]', + 'Invalid data. [ignoreGlobalFilters]: expected value of type [boolean] but got [string]', + ]; + + for (let i = 0; i < badGroups.length; i++) { + const payload: EventAnnotationGroupCreateIn = { + contentTypeId: CONTENT_ID, + data: badGroups[i], + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/create`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(400); + + expect(resp.body.message).to.be(expectedMessages[i]); + } }); - }); - describe('create', () => { it(`should require dataViewSpec to be specified`, async () => { const createWithDataViewSpec = (dataViewSpec: any) => { const payload: EventAnnotationGroupCreateIn = { @@ -127,6 +362,81 @@ export default function ({ getService }: FtrProviderContext) { }); describe('update', () => { + it(`should update a group`, async () => { + const payload: EventAnnotationGroupUpdateIn = { + contentTypeId: CONTENT_ID, + data: { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + description: 'updated description', + }, + id: EXISTING_ID_1, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/update`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(200); + + const result = resp.body.result.result as EventAnnotationGroupCreateOut; + + expect(result.item.attributes).to.eql({ + ...DEFAULT_EVENT_ANNOTATION_GROUP, + description: 'updated description', + }); + expect(result.item.id).to.be(EXISTING_ID_1); + }); + + it(`should reject malformed groups`, async () => { + const badGroups = [ + // extra property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + extraProp: 'some-value', + }, + // missing title + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + title: undefined, + }, + // wrong type for property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + ignoreGlobalFilters: 'not-a-boolean', + }, + ] as unknown as EventAnnotationGroupSavedObjectAttributes[]; // (coerce the types because these are intentionally malformed) + + const expectedMessages = [ + 'Invalid data. [extraProp]: definition for this key is missing', + 'Invalid data. [title]: expected value of type [string] but got [undefined]', + 'Invalid data. [ignoreGlobalFilters]: expected value of type [boolean] but got [string]', + ]; + + for (let i = 0; i < badGroups.length; i++) { + const payload: EventAnnotationGroupUpdateIn = { + contentTypeId: CONTENT_ID, + data: badGroups[i], + id: EXISTING_ID_1, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/update`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(400); + + expect(resp.body.message).to.be(expectedMessages[i]); + } + }); + it(`should require dataViewSpec to be specified`, async () => { const updateWithDataViewSpec = (dataViewSpec: any) => { const payload: EventAnnotationGroupUpdateIn = { @@ -158,6 +468,34 @@ export default function ({ getService }: FtrProviderContext) { }); }); - // TODO - delete + describe('delete', () => { + const deleteGroupByID = (id: string) => { + const payload: EventAnnotationGroupDeleteIn = { + contentTypeId: CONTENT_ID, + id, + version: API_VERSION, + }; + + return supertest.post(`${CONTENT_ENDPOINT}/delete`).set('kbn-xsrf', 'kibana').send(payload); + }; + + it(`should delete a group`, async () => { + const resp = await deleteGroupByID(EXISTING_ID_1).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupDeleteOut; + + expect(result.success).to.be(true); + }); + + it(`should reject deleting a group that does not exist`, async () => { + const resp = await deleteGroupByID('does-not-exist').expect(404); + + expect(resp.body).to.eql({ + error: 'Not Found', + message: 'Saved object [event-annotation-group/does-not-exist] not found', + statusCode: 404, + }); + }); + }); }); } diff --git a/test/api_integration/apis/home/sample_data.ts b/test/api_integration/apis/home/sample_data.ts index b26fe638e1224..6037773c577b0 100644 --- a/test/api_integration/apis/home/sample_data.ts +++ b/test/api_integration/apis/home/sample_data.ts @@ -17,7 +17,11 @@ export default function ({ getService }: FtrProviderContext) { const MILLISECOND_IN_WEEK = 1000 * 60 * 60 * 24 * 7; const SPACES = ['default', 'other']; - const FLIGHTS_OVERVIEW_DASHBOARD_ID = '7adfa750-4c81-11e8-b3d7-01146121b73d'; // default ID of the flights overview dashboard + /** + * default ID of the flights overview dashboard + * @see src/plugins/home/server/services/sample_data/data_sets/flights/index.ts + */ + const FLIGHTS_OVERVIEW_DASHBOARD_ID = '7adfa750-4c81-11e8-b3d7-01146121b73d'; const FLIGHTS_CANVAS_APPLINK_PATH = '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0'; // includes default ID of the flights canvas applink path @@ -25,8 +29,7 @@ export default function ({ getService }: FtrProviderContext) { return appLinks.some((item) => item.path === path); }; - // Failing: See https://github.com/elastic/kibana/issues/164568 - describe.skip('sample data apis', () => { + describe('sample data apis', () => { before(async () => { await esArchiver.emptyKibanaIndex(); }); @@ -64,7 +67,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(resp.body).to.eql({ - elasticsearchIndicesCreated: { kibana_sample_data_flights: 13059 }, + elasticsearchIndicesCreated: { kibana_sample_data_flights: 13014 }, kibanaSavedObjectsLoaded: 8, }); }); @@ -73,9 +76,7 @@ export default function ({ getService }: FtrProviderContext) { it('should load elasticsearch index containing sample data with dates relative to current time', async () => { const resp = await es.search<{ timestamp: string }>({ index: 'kibana_sample_data_flights', - body: { - sort: [{ timestamp: { order: 'desc' } }], - }, + sort: [{ timestamp: { order: 'desc' } }], }); const doc = resp.hits.hits[0]; @@ -91,9 +92,7 @@ export default function ({ getService }: FtrProviderContext) { const resp = await es.search<{ timestamp: string }>({ index: 'kibana_sample_data_flights', - body: { - sort: [{ timestamp: { order: 'desc' } }], - }, + sort: [{ timestamp: { order: 'desc' } }], }); const doc = resp.hits.hits[0]; diff --git a/test/api_integration/apis/search/bsearch.ts b/test/api_integration/apis/search/bsearch.ts index 9ce10dc38a643..58f0765a53913 100644 --- a/test/api_integration/apis/search/bsearch.ts +++ b/test/api_integration/apis/search/bsearch.ts @@ -232,6 +232,351 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + + describe('request meta', () => { + describe('es', () => { + it(`should return request meta`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + query: { + match_all: {}, + }, + }, + }, + }, + options: { + strategy: 'es', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].result).to.have.property('requestParams'); + expect(jsonBody[0].result.requestParams.method).to.be('POST'); + expect(jsonBody[0].result.requestParams.path).to.be('/.kibana/_search'); + expect(jsonBody[0].result.requestParams.querystring).to.be('ignore_unavailable=true'); + }); + + it(`should return request meta when request fails`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + query: { + bool: { + filter: [ + { + error_query: { + indices: [ + { + error_type: 'exception', + message: 'simulated failure', + name: '.kibana', + }, + ], + }, + }, + ], + }, + }, + }, + }, + }, + options: { + strategy: 'es', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].error).to.have.property('requestParams'); + expect(jsonBody[0].error.requestParams.method).to.be('POST'); + expect(jsonBody[0].error.requestParams.path).to.be('/.kibana/_search'); + expect(jsonBody[0].error.requestParams.querystring).to.be('ignore_unavailable=true'); + }); + }); + + describe('ese', () => { + it(`should return request meta`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + query: { + match_all: {}, + }, + }, + }, + }, + options: { + strategy: 'ese', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].result).to.have.property('requestParams'); + expect(jsonBody[0].result.requestParams.method).to.be('POST'); + expect(jsonBody[0].result.requestParams.path).to.be('/.kibana/_async_search'); + expect(jsonBody[0].result.requestParams.querystring).to.be( + 'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true' + ); + }); + + it(`should return request meta when request fails`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + bool: { + filter: [ + { + error_query: { + indices: [ + { + error_type: 'exception', + message: 'simulated failure', + name: '.kibana', + }, + ], + }, + }, + ], + }, + }, + }, + }, + options: { + strategy: 'ese', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].error).to.have.property('requestParams'); + expect(jsonBody[0].error.requestParams.method).to.be('POST'); + expect(jsonBody[0].error.requestParams.path).to.be('/.kibana/_async_search'); + expect(jsonBody[0].error.requestParams.querystring).to.be( + 'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true' + ); + }); + }); + + describe('esql', () => { + it(`should return request meta`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + query: 'from .kibana | limit 1', + }, + }, + options: { + strategy: 'esql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].result).to.have.property('requestParams'); + expect(jsonBody[0].result.requestParams.method).to.be('POST'); + expect(jsonBody[0].result.requestParams.path).to.be('/_query'); + expect(jsonBody[0].result.requestParams.querystring).to.be(''); + }); + + it(`should return request meta when request fails`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + query: 'fro .kibana | limit 1', + }, + }, + options: { + strategy: 'esql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].error).to.have.property('requestParams'); + expect(jsonBody[0].error.requestParams.method).to.be('POST'); + expect(jsonBody[0].error.requestParams.path).to.be('/_query'); + expect(jsonBody[0].error.requestParams.querystring).to.be(''); + }); + }); + + describe('sql', () => { + it(`should return request meta`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + query: 'SELECT * FROM ".kibana" LIMIT 1', + }, + }, + options: { + strategy: 'sql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].result).to.have.property('requestParams'); + expect(jsonBody[0].result.requestParams.method).to.be('POST'); + expect(jsonBody[0].result.requestParams.path).to.be('/_sql'); + expect(jsonBody[0].result.requestParams.querystring).to.be('format=json'); + }); + + it(`should return request meta when request fails`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + query: 'SELEC * FROM ".kibana" LIMIT 1', + }, + }, + options: { + strategy: 'sql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].error).to.have.property('requestParams'); + expect(jsonBody[0].error.requestParams.method).to.be('POST'); + expect(jsonBody[0].error.requestParams.path).to.be('/_sql'); + expect(jsonBody[0].error.requestParams.querystring).to.be('format=json'); + }); + }); + + describe('eql', () => { + it(`should return request meta`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + query: 'any where true', + timestamp_field: 'created_at', + }, + }, + options: { + strategy: 'eql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].result).to.have.property('requestParams'); + expect(jsonBody[0].result.requestParams.method).to.be('POST'); + expect(jsonBody[0].result.requestParams.path).to.be('/.kibana/_eql/search'); + expect(jsonBody[0].result.requestParams.querystring).to.be('ignore_unavailable=true'); + }); + + it(`should return request meta when request fails`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + query: 'any where true', + }, + }, + options: { + strategy: 'eql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].error).to.have.property('requestParams'); + expect(jsonBody[0].error.requestParams.method).to.be('POST'); + expect(jsonBody[0].error.requestParams.path).to.be('/.kibana/_eql/search'); + expect(jsonBody[0].error.requestParams.querystring).to.be('ignore_unavailable=true'); + }); + }); + }); }); }); } diff --git a/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json b/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json index ed574c749518c..4732d061c6930 100644 --- a/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json +++ b/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json @@ -7,33 +7,31 @@ "title": "kibana_sample_data_logs" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T19:49:25.494Z", + "created_at": "2023-09-07T17:23:20.906Z", "id": "90943e30-9a47-11e8-b64d-95841ca0b247", "managed": false, "references": [], "type": "index-pattern", "typeMigrationVersion": "8.0.0", - "updated_at": "2023-08-15T19:49:25.494Z", - "version": "WzIyLDFd" + "updated_at": "2023-09-07T17:23:20.906Z", + "version": "WzEwMiwxXQ==" } { "attributes": { "annotations": [ { - "color": "#6092c0", "filter": { "language": "kuery", "query": "agent.keyword : \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\" ", "type": "kibana_query" }, - "icon": "asterisk", - "id": "499ee351-f541-46e0-b327-b3dcae91aff5", + "icon": "triangle", + "id": "fdede168-eff1-400f-b106-0f62061f5099", "key": { "type": "point_in_time" }, "label": "Event", - "lineStyle": "dashed", "timeField": "timestamp", "type": "query" } @@ -41,11 +39,11 @@ "dataViewSpec": null, "description": "", "ignoreGlobalFilters": true, - "title": "Another group" + "title": "group3" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T21:02:32.023Z", - "id": "0d1aa670-3baf-11ee-a4a7-c11cb33a9549", + "created_at": "2023-09-08T18:41:09.030Z", + "id": "46c2a460-4e77-11ee-bb97-116581699678", "managed": false, "references": [ { @@ -55,8 +53,25 @@ } ], "type": "event-annotation-group", - "updated_at": "2023-08-15T22:15:43.724Z", - "version": "WzU4LDFd" + "updated_at": "2023-09-08T18:42:48.080Z", + "version": "WzE2OCwxXQ==" +} + +{ + "attributes": { + "color": "#dac7c4", + "description": "a tag to filter by", + "name": "tag" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-08T18:40:42.018Z", + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "managed": false, + "references": [], + "type": "tag", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-09-08T18:40:42.018Z", + "version": "WzI3NDUsMV0=" } { @@ -64,9 +79,49 @@ "annotations": [ { "icon": "triangle", - "id": "1d9627a8-11dc-44f1-badb-4d40a80b6bee", + "id": "3d9f03ca-36aa-4ebb-aab8-efef1591c6d5", "key": { - "timestamp": "2023-08-10T15:00:00.000Z", + "timestamp": "2023-09-08T18:32:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "i am a description you can search for!", + "ignoreGlobalFilters": true, + "title": "group2" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-08T18:41:01.654Z", + "id": "425d2760-4e77-11ee-bb97-116581699678", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-08T18:41:01.654Z", + "version": "WzE2NCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "f7be288b-8adf-48b9-89d4-267db4863a3d", + "key": { + "timestamp": "2023-09-08T18:32:00.000Z", "type": "point_in_time" }, "label": "Event", @@ -76,20 +131,25 @@ "dataViewSpec": null, "description": "", "ignoreGlobalFilters": true, - "title": "A group" + "title": "group1" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T19:50:29.907Z", - "id": "fcebef20-3ba4-11ee-85d3-3dd00bdd66ef", + "created_at": "2023-09-08T18:40:45.981Z", + "id": "3905a4d0-4e77-11ee-bb97-116581699678", "managed": false, "references": [ { "id": "90943e30-9a47-11e8-b64d-95841ca0b247", "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" } ], "type": "event-annotation-group", - "updated_at": "2023-08-15T22:13:19.290Z", - "version": "WzU0LDFd" + "updated_at": "2023-09-08T18:40:45.981Z", + "version": "WzE2MiwxXQ==" } \ No newline at end of file diff --git a/test/api_integration/services/supertest.ts b/test/api_integration/services/supertest.ts index 6680c7b203bd5..baf42703c64a0 100644 --- a/test/api_integration/services/supertest.ts +++ b/test/api_integration/services/supertest.ts @@ -22,10 +22,14 @@ export function KibanaSupertestProvider({ getService }: FtrProviderContext) { export function ElasticsearchSupertestProvider({ getService }: FtrProviderContext) { const config = getService('config'); const esServerConfig = config.get('servers.elasticsearch'); + + // For stateful tests, use system indices user so tests can write to system indices + // For serverless tests, we don't have a system indices user, so we're using the default superuser const elasticSearchServerUrl = formatUrl({ ...esServerConfig, - // Use system indices user so tests can write to system indices - auth: `${systemIndicesSuperuser.username}:${systemIndicesSuperuser.password}`, + ...(config.get('serverless') + ? [] + : { auth: `${systemIndicesSuperuser.username}:${systemIndicesSuperuser.password}` }), }); let agentOptions = {}; diff --git a/test/examples/search/warnings.ts b/test/examples/search/warnings.ts index 24656ed68285c..f1856f0b0e611 100644 --- a/test/examples/search/warnings.ts +++ b/test/examples/search/warnings.ts @@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); - describe('handling warnings with search source fetch', function () { + // Failing: See https://github.com/elastic/kibana/issues/166484 + describe.skip('handling warnings with search source fetch', function () { const dataViewTitle = 'sample-01,sample-01-rollup'; const fromTime = 'Jun 17, 2022 @ 00:00:00.000'; const toTime = 'Jun 23, 2022 @ 00:00:00.000'; @@ -100,7 +101,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.clearAllToasts(); }); - it('shows shard failure warning notifications by default', async () => { + it('should show search warnings as toasts', async () => { await testSubjects.click('searchSourceWithOther'); // wait for response - toasts appear before the response is rendered @@ -113,26 +114,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // toasts const toasts = await find.allByCssSelector(toastsSelector); expect(toasts.length).to.be(2); - const expects = ['2 of 4 shards failed', 'Query result']; + const expects = ['The data might be incomplete or wrong.', 'Query result']; await asyncForEach(toasts, async (t, index) => { expect(await t.getVisibleText()).to.eql(expects[index]); }); // click "see full error" button in the toast - const [openShardModalButton] = await testSubjects.findAll('openShardFailureModalBtn'); + const [openShardModalButton] = await testSubjects.findAll('openIncompleteResultsModalBtn'); await openShardModalButton.click(); - const modalHeader = await testSubjects.find('shardFailureModalTitle'); - expect(await modalHeader.getVisibleText()).to.be('2 of 4 shards failed'); + // request - await testSubjects.click('shardFailuresModalRequestButton'); - const requestBlock = await testSubjects.find('shardsFailedModalRequestBlock'); + await testSubjects.click('showRequestButton'); + const requestBlock = await testSubjects.find('incompleteResultsModalRequestBlock'); expect(await requestBlock.getVisibleText()).to.contain(testRollupField); // response - await testSubjects.click('shardFailuresModalResponseButton'); - const responseBlock = await testSubjects.find('shardsFailedModalResponseBlock'); + await testSubjects.click('showResponseButton'); + const responseBlock = await testSubjects.find('incompleteResultsModalResponseBlock'); expect(await responseBlock.getVisibleText()).to.contain(shardFailureReason); - await testSubjects.click('closeShardFailureModal'); + await testSubjects.click('closeIncompleteResultsModal'); // response tab assert(response && response._shards.failures); @@ -150,7 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(warnings).to.eql([]); }); - it('able to handle shard failure warnings and prevent default notifications', async () => { + it('should show search warnings in results tab', async () => { await testSubjects.click('searchSourceWithoutOther'); // wait for toasts - toasts appear after the response is rendered @@ -159,48 +159,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { toasts = await find.allByCssSelector(toastsSelector); expect(toasts.length).to.be(2); }); - const expects = ['Query result', '2 of 4 shards failed']; + const expects = ['The data might be incomplete or wrong.', 'Query result']; await asyncForEach(toasts, async (t, index) => { expect(await t.getVisibleText()).to.eql(expects[index]); }); - // click "see full error" button in the toast - const [openShardModalButton] = await testSubjects.findAll('openShardFailureModalBtn'); - await openShardModalButton.click(); - const modalHeader = await testSubjects.find('shardFailureModalTitle'); - expect(await modalHeader.getVisibleText()).to.be('2 of 4 shards failed'); - // request - await testSubjects.click('shardFailuresModalRequestButton'); - const requestBlock = await testSubjects.find('shardsFailedModalRequestBlock'); - expect(await requestBlock.getVisibleText()).to.contain(testRollupField); - // response - await testSubjects.click('shardFailuresModalResponseButton'); - const responseBlock = await testSubjects.find('shardsFailedModalResponseBlock'); - expect(await responseBlock.getVisibleText()).to.contain(shardFailureReason); - - await testSubjects.click('closeShardFailureModal'); - - // response tab - const response = await getTestJson('responseTab', 'responseCodeBlock'); - expect(response._shards.total).to.be(4); - expect(response._shards.successful).to.be(2); - expect(response._shards.skipped).to.be(0); - expect(response._shards.failed).to.be(2); - expect(response._shards.failures.length).to.equal(1); - expect(response._shards.failures[0].index).to.equal(testRollupIndex); - expect(response._shards.failures[0].reason.type).to.equal(shardFailureType); - expect(response._shards.failures[0].reason.reason).to.equal(shardFailureReason); - // warnings tab const warnings = await getTestJson('warningsTab', 'warningsCodeBlock'); - expect(warnings).to.eql([ - { - type: 'shard_failure', - message: '2 of 4 shards failed', - reason: { reason: shardFailureReason, type: shardFailureType }, - text: 'The data might be incomplete or wrong.', - }, - ]); + expect(warnings.length).to.be(1); + expect(warnings[0].type).to.be('incomplete'); }); }); } diff --git a/test/functional/apps/console/_autocomplete.ts b/test/functional/apps/console/_autocomplete.ts index af5e9e6741197..54e35bff86ee9 100644 --- a/test/functional/apps/console/_autocomplete.ts +++ b/test/functional/apps/console/_autocomplete.ts @@ -14,7 +14,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'console']); + const PageObjects = getPageObjects(['common', 'console', 'header']); const find = getService('find'); describe('console autocomplete feature', function describeIndexTests() { @@ -44,11 +44,160 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(PageObjects.console.isAutocompleteVisible()).to.be.eql(true); }); + // FLAKY: https://github.com/elastic/kibana/issues/165465 + describe.skip('Autocomplete behavior', () => { + beforeEach(async () => { + await PageObjects.console.clearTextArea(); + await PageObjects.console.pressEnter(); + }); + + it('HTTP methods', async () => { + const suggestions = { + G: ['GET'], + P: ['PUT', 'POST', 'PATCH'], + D: ['DELETE'], + H: ['HEAD'], + }; + for (const [char, methods] of Object.entries(suggestions)) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.enterText(char); + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.isAutocompleteVisible() + ); + expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(true); + + for (const [i, method] of methods.entries()) { + expect(await PageObjects.console.getAutocompleteSuggestion(i)).to.be.eql(method); + } + + await PageObjects.console.pressEscape(); + await PageObjects.console.pressEnter(); + } + }); + + it('ES API endpoints', async () => { + const suggestions = { + 'GET _': ['_alias', '_all'], + 'PUT _': ['_all'], + 'POST _': ['_aliases', '_all'], + 'DELETE _': ['_all'], + 'HEAD _': ['_alias', '_all'], + }; + for (const [text, endpoints] of Object.entries(suggestions)) { + for (const char of text) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.enterText(char); + } + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.isAutocompleteVisible() + ); + expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(true); + + for (const [i, endpoint] of endpoints.entries()) { + expect(await PageObjects.console.getAutocompleteSuggestion(i)).to.be.eql(endpoint); + } + + await PageObjects.console.pressEscape(); + await PageObjects.console.pressEnter(); + } + }); + + it('JSON autocompletion with placeholder fields', async () => { + await PageObjects.console.enterText('GET _search\n{'); + await PageObjects.console.pressEnter(); + + for (const char of '"ag') { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.enterText(char); + } + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.isAutocompleteVisible() + ); + expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(true); + await PageObjects.console.pressEnter(); + await PageObjects.console.sleepForDebouncePeriod(); + + expect(await PageObjects.console.getAllVisibleText()).to.be.eql( + ` +GET _search +{ + "aggs": { + "NAME": { + "AGG_TYPE": {} + } + } +} +`.replace(/\n/g, '') + ); + // cursor should be located between '"' and 'N' + expect(await PageObjects.console.getCurrentLineNumber()).to.be.eql(5); + + await PageObjects.console.pressDown(); + await PageObjects.console.pressRight(); + await PageObjects.console.pressRight(); + for (let i = 0; i < 8; i++) { + await PageObjects.console.pressRight(true); // select 'AGG_TYPE' + } + + for (const char of 'ter') { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.enterText(char); + } + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.isAutocompleteVisible() + ); + expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(true); + await PageObjects.console.pressEnter(); + await PageObjects.console.sleepForDebouncePeriod(); + + expect(await PageObjects.console.getAllVisibleText()).to.be.eql( + ` +GET _search +{ + "aggs": { + "NAME": { + "terms": { + "field": "" + } + } + } +} +`.replace(/\n/g, '') + ); + }); + + it('Dynamic autocomplete', async () => { + await PageObjects.console.enterRequest('POST test/_doc\n{}'); + await PageObjects.console.clickPlay(); + + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.console.getResponseStatus()).to.be('201'); + + await PageObjects.console.pressEnter(); + for (const char of 'POST t') { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.enterText(char); + } + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.isAutocompleteVisible() + ); + expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(true); + + expect(await PageObjects.console.getAutocompleteSuggestion(0)).to.be.eql('test'); + }); + }); + // FLAKY: https://github.com/elastic/kibana/issues/164584 describe.skip('anti-regression watchdogs', () => { beforeEach(async () => { await PageObjects.console.clearTextArea(); - await PageObjects.console.pressEnter(); }); it('should suppress auto-complete on arrow keys', async () => { @@ -85,6 +234,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dELETE dELETe dELEtE dELEte dELeTE dELeTe dELetE dELete dElETE dElETe dElEtE dElEte dEleTE dEleTe dEletE dElete deLETE deLETe deLEtE deLEte deLeTE deLeTe deLetE deLete delETE delETe delEtE delEte deleTE deleTe deletE delete HEAD HEAd HEaD HEad HeAD HeAd HeaD Head hEAD hEAd hEaD hEad heAD heAd heaD head + PATCH PATCh PATcH PATch PAtCH PAtCh PAtcH PAtch PaTCH PaTCh PaTcH PaTch PatCH PatCh PatcH Patch pATCH pATCh pATcH + pATch pAtCH pAtCh pAtcH pAtch paTCH paTCh paTcH paTch patCH patCh patcH patch `.split(/\s+/m) ), 20 // 20 of 112 (approx. one-fifth) should be enough for testing @@ -92,7 +243,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { for (const method of methods) { await PageObjects.console.clearTextArea(); - await PageObjects.console.pressEnter(); for (const char of method.slice(0, -1)) { await PageObjects.console.sleepForDebouncePeriod(); @@ -123,7 +273,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { for (const char of ['/', '_']) { await PageObjects.console.sleepForDebouncePeriod(); log.debug('Key type "%s"', char); - await PageObjects.console.enterText(char); // e.g. 'GET .kibana/' -> 'GET .kibana/_' + await PageObjects.console.enterText(char); // i.e. 'GET .kibana/' -> 'GET .kibana/_' } await retry.waitFor('autocomplete to be visible', () => @@ -131,6 +281,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(true); }); + + it('should activate auto-complete for multiple indices after comma in URL', async () => { + await PageObjects.console.enterText('GET /_cat/indices/.kibana'); + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type ","'); + await PageObjects.console.enterText(','); // i.e. 'GET /_cat/indices/.kibana,' + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type Ctrl+SPACE'); + await PageObjects.console.pressCtrlSpace(); + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.isAutocompleteVisible() + ); + expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(true); + }); + + it('should not activate auto-complete after comma following endpoint in URL', async () => { + await PageObjects.console.enterText('GET _search'); + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type ","'); + await PageObjects.console.enterText(','); // i.e. 'GET _search,' + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type Ctrl+SPACE'); + await PageObjects.console.pressCtrlSpace(); + + expect(await PageObjects.console.isAutocompleteVisible()).to.be.eql(false); + }); }); describe('with a missing comma in query', () => { diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index 091ad8ee5a2e8..b8324ce58ce6c 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -68,6 +68,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(initialSize.width).to.be.greaterThan(afterSize.width); }); + it('should return statusCode 400 to unsupported HTTP verbs', async () => { + const expectedResponseContains = '"statusCode": 400'; + await PageObjects.console.enterRequest('\n OPTIONS /'); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.getResponse(); + log.debug(actualResponse); + expect(actualResponse).to.contain(expectedResponseContains); + + expect(await PageObjects.console.hasSuccessBadge()).to.be(false); + }); + }); + describe('with kbn: prefix in request', () => { before(async () => { await PageObjects.console.clearTextArea(); diff --git a/test/functional/apps/context/_discover_navigation.ts b/test/functional/apps/context/_discover_navigation.ts index 99e27b52983a0..54c743df37309 100644 --- a/test/functional/apps/context/_discover_navigation.ts +++ b/test/functional/apps/context/_discover_navigation.ts @@ -134,7 +134,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.saveSearch('my search'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/context/classic/_discover_navigation.ts b/test/functional/apps/context/classic/_discover_navigation.ts index b0b7f672a75b3..d20de752061e9 100644 --- a/test/functional/apps/context/classic/_discover_navigation.ts +++ b/test/functional/apps/context/classic/_discover_navigation.ts @@ -136,7 +136,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.saveSearch('my classic search'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts b/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts index bc79023bd51b7..9531606e649f3 100644 --- a/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts +++ b/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('ensure toolbar popover closes on add', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.switchToEditMode(); await dashboardAddPanel.clickEditorMenuButton(); @@ -44,7 +44,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('add new visualization link', () => { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); }); diff --git a/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts b/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts index 73e97aab3cb36..87c19402a51be 100644 --- a/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts +++ b/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts index 51669b395b6f1..902e76342ec98 100644 --- a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -71,7 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboards'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.waitForRenderComplete(); await validateQueryAndFilter(); @@ -138,7 +138,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboards'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('few panels'); const currentPanelCount = await PageObjects.dashboard.getPanelCount(); expect(currentPanelCount).to.eql(unsavedPanelCount); diff --git a/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts b/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts index 7ed4cba6c7089..f11ca330bf178 100644 --- a/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts +++ b/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); @@ -86,7 +86,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { redirectToOrigin: false, }); await PageObjects.visualize.notLinkedToOriginatingApp(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); it('loses originatingApp connection after first save when redirectToOrigin is false', async () => { diff --git a/test/functional/apps/dashboard/group1/edit_visualizations.js b/test/functional/apps/dashboard/group1/edit_visualizations.js index d4de54586b731..20234ca1f8055 100644 --- a/test/functional/apps/dashboard/group1/edit_visualizations.js +++ b/test/functional/apps/dashboard/group1/edit_visualizations.js @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }) { }); it('visualize app menu navigates to the visualize listing page if the last opened visualization was linked to dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); @@ -133,7 +133,7 @@ export default function ({ getService, getPageObjects }) { describe('by value', () => { it('save and return button returns to dashboard after editing visualization with changes saved', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await createMarkdownVis(); diff --git a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts index 85277e63d6f6c..71da8fadea4af 100644 --- a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts +++ b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', 'doc_table:legacy': false, }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group1/embeddable_rendering.ts b/test/functional/apps/dashboard/group1/embeddable_rendering.ts index d7addf89ac404..45408a8846c17 100644 --- a/test/functional/apps/dashboard/group1/embeddable_rendering.ts +++ b/test/functional/apps/dashboard/group1/embeddable_rendering.ts @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); await PageObjects.common.setTime({ from, to }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); await elasticChart.setNewChartUiDebugFlag(true); diff --git a/test/functional/apps/dashboard/group1/url_field_formatter.ts b/test/functional/apps/dashboard/group1/url_field_formatter.ts index 688499a36e34c..d2a4be0598a35 100644 --- a/test/functional/apps/dashboard/group1/url_field_formatter.ts +++ b/test/functional/apps/dashboard/group1/url_field_formatter.ts @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('applied on dashboard', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.loadSavedDashboard('dashboard with table'); await dashboard.waitForRenderComplete(); const fieldLink = await visChart.getFieldLinkInVisTable(`${fieldName}: Descending`); diff --git a/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts b/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts index 1244e179f7f6a..da9660ac4f4cb 100644 --- a/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts +++ b/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/test/functional/apps/dashboard/group2/dashboard_filtering.ts b/test/functional/apps/dashboard/group2/dashboard_filtering.ts index 1fb70ef508c2b..24f276b831036 100644 --- a/test/functional/apps/dashboard/group2/dashboard_filtering.ts +++ b/test/functional/apps/dashboard/group2/dashboard_filtering.ts @@ -66,7 +66,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.gotoDashboardLandingPage(); }); diff --git a/test/functional/apps/dashboard/group2/full_screen_mode.ts b/test/functional/apps/dashboard/group2/full_screen_mode.ts index 53cb707961ea6..23be5e4b7afe6 100644 --- a/test/functional/apps/dashboard/group2/full_screen_mode.ts +++ b/test/functional/apps/dashboard/group2/full_screen_mode.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); }); diff --git a/test/functional/apps/dashboard/group2/panel_expand_toggle.ts b/test/functional/apps/dashboard/group2/panel_expand_toggle.ts index f33280ba7bb79..99d09a5f42e7e 100644 --- a/test/functional/apps/dashboard/group2/panel_expand_toggle.ts +++ b/test/functional/apps/dashboard/group2/panel_expand_toggle.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); }); diff --git a/test/functional/apps/dashboard/group3/copy_panel_to.ts b/test/functional/apps/dashboard/group3/copy_panel_to.ts index 1f40f780a5398..81c5406426127 100644 --- a/test/functional/apps/dashboard/group3/copy_panel_to.ts +++ b/test/functional/apps/dashboard/group3/copy_panel_to.ts @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard(fewPanelsTitle); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/dashboard/group4/dashboard_empty.ts b/test/functional/apps/dashboard/group4/dashboard_empty.ts index 02437b0685694..03a9d965d589b 100644 --- a/test/functional/apps/dashboard/group4/dashboard_empty.ts +++ b/test/functional/apps/dashboard/group4/dashboard_empty.ts @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); log.debug('load kibana with no data'); await kibanaServer.importExport.unload(kbnDirectory); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { @@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); // create the new data view from the dashboards/create route in order to test that the dashboard is loaded properly as soon as the data view is created... - await PageObjects.common.navigateToApp('dashboard', { hash: '/create' }); + await PageObjects.common.navigateToApp('dashboards', { hash: '/create' }); const button = await testSubjects.find('createDataViewButton'); button.click(); diff --git a/test/functional/apps/dashboard/group4/dashboard_listing.ts b/test/functional/apps/dashboard/group4/dashboard_listing.ts index d3cde4e185b9f..ed8cc60cb5884 100644 --- a/test/functional/apps/dashboard/group4/dashboard_listing.ts +++ b/test/functional/apps/dashboard/group4/dashboard_listing.ts @@ -14,8 +14,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['dashboard', 'header', 'common']); const browser = getService('browser'); const listingTable = getService('listingTable'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); const dashboardAddPanel = getService('dashboardAddPanel'); describe('dashboard listing page', function describeIndexTests() { @@ -217,12 +215,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.gotoDashboardLandingPage(); await listingTable.searchForItemWithName(`${dashboardName}-editMetaData`); - await testSubjects.click('inspect-action'); - await testSubjects.setValue('nameInput', 'new title'); - await testSubjects.setValue('descriptionInput', 'new description'); - await retry.try(async () => { - await testSubjects.click('saveButton'); - await testSubjects.missingOrFail('flyoutTitle'); + await listingTable.inspectVisualization(); + await listingTable.editVisualizationDetails({ + title: 'new title', + description: 'new description', }); await listingTable.searchAndExpectItemsCount('dashboard', 'new title', 1); diff --git a/test/functional/apps/dashboard/group4/dashboard_time.ts b/test/functional/apps/dashboard/group4/dashboard_time.ts index e1dbefa63ac74..2b35c5e78f331 100644 --- a/test/functional/apps/dashboard/group4/dashboard_time.ts +++ b/test/functional/apps/dashboard/group4/dashboard_time.ts @@ -24,7 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); describe('dashboard without stored timed', () => { diff --git a/test/functional/apps/dashboard/group5/dashboard_back_button.ts b/test/functional/apps/dashboard/group5/dashboard_back_button.ts index 1fd9614d2421a..c8fbf1c6da411 100644 --- a/test/functional/apps/dashboard/group5/dashboard_back_button.ts +++ b/test/functional/apps/dashboard/group5/dashboard_back_button.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/test/functional/apps/dashboard/group5/dashboard_error_handling.ts b/test/functional/apps/dashboard/group5/dashboard_error_handling.ts index a3265fdcc7f9d..ab8e8ac76f85b 100644 --- a/test/functional/apps/dashboard/group5/dashboard_error_handling.ts +++ b/test/functional/apps/dashboard/group5/dashboard_error_handling.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/dashboard_error_cases.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/test/functional/apps/dashboard/group5/dashboard_query_bar.ts b/test/functional/apps/dashboard/group5/dashboard_query_bar.ts index 010aec9607816..a3bfcea2eaa2a 100644 --- a/test/functional/apps/dashboard/group5/dashboard_query_bar.ts +++ b/test/functional/apps/dashboard/group5/dashboard_query_bar.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dashboard with filter'); }); diff --git a/test/functional/apps/dashboard/group5/dashboard_settings.ts b/test/functional/apps/dashboard/group5/dashboard_settings.ts index bbfb867cdf176..ae0e727814eef 100644 --- a/test/functional/apps/dashboard/group5/dashboard_settings.ts +++ b/test/functional/apps/dashboard/group5/dashboard_settings.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); diff --git a/test/functional/apps/dashboard/group5/data_shared_attributes.ts b/test/functional/apps/dashboard/group5/data_shared_attributes.ts index 71d8a16b2f7d8..3202d418bd512 100644 --- a/test/functional/apps/dashboard/group5/data_shared_attributes.ts +++ b/test/functional/apps/dashboard/group5/data_shared_attributes.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dashboard with everything'); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/dashboard/group5/embed_mode.ts b/test/functional/apps/dashboard/group5/embed_mode.ts index 16934fc9101a8..3c2cfbae77a9f 100644 --- a/test/functional/apps/dashboard/group5/embed_mode.ts +++ b/test/functional/apps/dashboard/group5/embed_mode.ts @@ -49,7 +49,7 @@ export default function ({ await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dashboard with everything'); diff --git a/test/functional/apps/dashboard/group5/empty_dashboard.ts b/test/functional/apps/dashboard/group5/empty_dashboard.ts index 49fa50c075427..6939833a80086 100644 --- a/test/functional/apps/dashboard/group5/empty_dashboard.ts +++ b/test/functional/apps/dashboard/group5/empty_dashboard.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/test/functional/apps/dashboard/group5/legacy_urls.ts b/test/functional/apps/dashboard/group5/legacy_urls.ts index 54834bf8969b7..03dabfe87ba2f 100644 --- a/test/functional/apps/dashboard/group5/legacy_urls.ts +++ b/test/functional/apps/dashboard/group5/legacy_urls.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie'); await PageObjects.dashboard.saveDashboard('legacyTest', { waitDialogIsClosed: true }); @@ -109,7 +109,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('resolves markdown link from dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addVisualization('legacy url markdown'); (await find.byLinkText('abc')).click(); diff --git a/test/functional/apps/dashboard/group5/saved_search_embeddable.ts b/test/functional/apps/dashboard/group5/saved_search_embeddable.ts index c409f3d802276..f20172d10ed5c 100644 --- a/test/functional/apps/dashboard/group5/saved_search_embeddable.ts +++ b/test/functional/apps/dashboard/group5/saved_search_embeddable.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); await PageObjects.common.setTime({ from, to }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group5/share.ts b/test/functional/apps/dashboard/group5/share.ts index a6d9289313e62..45bb5cd80c508 100644 --- a/test/functional/apps/dashboard/group5/share.ts +++ b/test/functional/apps/dashboard/group5/share.ts @@ -109,7 +109,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const from = 'Sep 19, 2017 @ 06:31:44.000'; const to = 'Sep 23, 2018 @ 18:31:44.000'; await PageObjects.common.setTime({ from, to }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); diff --git a/test/functional/apps/dashboard/group6/dashboard_grid.ts b/test/functional/apps/dashboard/group6/dashboard_grid.ts index 90e2187e19eb4..c48a2973acc46 100644 --- a/test/functional/apps/dashboard/group6/dashboard_grid.ts +++ b/test/functional/apps/dashboard/group6/dashboard_grid.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); diff --git a/test/functional/apps/dashboard/group6/dashboard_saved_query.ts b/test/functional/apps/dashboard/group6/dashboard_saved_query.ts index 1dad54234e8a3..5b00fe2caa01b 100644 --- a/test/functional/apps/dashboard/group6/dashboard_saved_query.ts +++ b/test/functional/apps/dashboard/group6/dashboard_saved_query.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/test/functional/apps/dashboard/group6/dashboard_snapshots.ts b/test/functional/apps/dashboard/group6/dashboard_snapshots.ts index 99f7a7fe2654a..401c1fa649006 100644 --- a/test/functional/apps/dashboard/group6/dashboard_snapshots.ts +++ b/test/functional/apps/dashboard/group6/dashboard_snapshots.ts @@ -43,7 +43,7 @@ export default function ({ await browser.setScreenshotSize(1000, 500); // adding this navigate adds the timestamp hash to the url which invalidates previous // session. If we don't do this, the colors on the visualizations are different and the screenshots won't match. - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async function () { diff --git a/test/functional/apps/dashboard/group6/embeddable_library.ts b/test/functional/apps/dashboard/group6/embeddable_library.ts index 472a2a890c978..55e7ae7cee13a 100644 --- a/test/functional/apps/dashboard/group6/embeddable_library.ts +++ b/test/functional/apps/dashboard/group6/embeddable_library.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/test/functional/apps/dashboard/group6/view_edit.ts b/test/functional/apps/dashboard/group6/view_edit.ts index dfd62eeaa6cb3..a2ef56700357f 100644 --- a/test/functional/apps/dashboard/group6/view_edit.ts +++ b/test/functional/apps/dashboard/group6/view_edit.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts b/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts index 55d4e160a2e9c..7696b6a6f4762 100644 --- a/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts +++ b/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts @@ -55,7 +55,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); /* then, create our testing dashboard */ - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); await timePicker.setDefaultDataRange(); diff --git a/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts b/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts index e07d335236f79..aa3ae89015c5e 100644 --- a/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts +++ b/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts @@ -14,15 +14,11 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); - const { dashboardControls, common, dashboard } = getPageObjects([ - 'dashboardControls', - 'dashboard', - 'common', - ]); + const { dashboardControls, dashboard } = getPageObjects(['dashboardControls', 'dashboard']); describe('Dashboard control group settings', () => { before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); await dashboard.saveDashboard('Test Control Group Settings'); diff --git a/test/functional/apps/dashboard_elements/controls/common/index.ts b/test/functional/apps/dashboard_elements/controls/common/index.ts index 99bd616de4607..8a9a7b8a54834 100644 --- a/test/functional/apps/dashboard_elements/controls/common/index.ts +++ b/test/functional/apps/dashboard_elements/controls/common/index.ts @@ -13,11 +13,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const { dashboardControls, common, dashboard } = getPageObjects([ - 'dashboardControls', - 'dashboard', - 'common', - ]); + const { dashboardControls, dashboard } = getPageObjects(['dashboardControls', 'dashboard']); async function setup() { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); @@ -31,9 +27,9 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid }); // enable the controls lab and navigate to the dashboard listing page to start - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboardControls.enableControlsLab(); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); } diff --git a/test/functional/apps/dashboard_elements/controls/common/range_slider.ts b/test/functional/apps/dashboard_elements/controls/common/range_slider.ts index 21bdb4f897603..b97acde63f40b 100644 --- a/test/functional/apps/dashboard_elements/controls/common/range_slider.ts +++ b/test/functional/apps/dashboard_elements/controls/common/range_slider.ts @@ -49,9 +49,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { from: 'Oct 22, 2018 @ 00:00:00.000', to: 'Dec 3, 2018 @ 00:00:00.000', }); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboardControls.enableControlsLab(); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts b/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts index dbc8682caf63e..5c065f7f000f5 100644 --- a/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts +++ b/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let controlId: string; before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader', 'animals']); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard_elements/controls/common/time_slider.ts b/test/functional/apps/dashboard_elements/controls/common/time_slider.ts index 7e266376f8e74..520ef6560735f 100644 --- a/test/functional/apps/dashboard_elements/controls/common/time_slider.ts +++ b/test/functional/apps/dashboard_elements/controls/common/time_slider.ts @@ -17,12 +17,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); - const { dashboardControls, discover, timePicker, common, dashboard } = getPageObjects([ + const { dashboardControls, discover, timePicker, dashboard } = getPageObjects([ 'dashboardControls', 'discover', 'timePicker', 'dashboard', - 'common', ]); describe('Time Slider Control', async () => { @@ -55,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('create, edit, and delete', async () => { before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); @@ -132,7 +131,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('panel interactions', async () => { describe('saved search', async () => { before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.loadSavedDashboard('timeslider and saved search'); await dashboard.waitForRenderComplete(); }); diff --git a/test/functional/apps/dashboard_elements/controls/options_list/index.ts b/test/functional/apps/dashboard_elements/controls/options_list/index.ts index 2e1d803349936..966dbd91b7705 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/index.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/index.ts @@ -16,7 +16,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const { timePicker, dashboard, common } = getPageObjects(['timePicker', 'dashboard', 'common']); + const { timePicker, dashboard } = getPageObjects(['timePicker', 'dashboard']); const setup = async () => { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); @@ -29,7 +29,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); await timePicker.setDefaultDataRange(); diff --git a/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts b/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts index 84af82f349713..91774aee02f2d 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await setAllowExpensiveQueries(false); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.clickNewDashboard(); await dashboard.ensureDashboardIsInEditMode(); await timePicker.setDefaultDataRange(); diff --git a/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts b/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts index fd506429ba54f..16d0b2cd5fc2d 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts @@ -39,7 +39,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let controlId: string; const returnToDashboard = async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await header.waitUntilLoadingHasFinished(); await elasticChart.setNewChartUiDebugFlag(); await dashboard.loadSavedDashboard(OPTIONS_LIST_DASHBOARD_NAME); diff --git a/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts b/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts index caf229e8335bb..6fc586fb35bba 100644 --- a/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts +++ b/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.switchToEditMode(); }); diff --git a/test/functional/apps/discover/classic/_doc_table.ts b/test/functional/apps/discover/classic/_doc_table.ts index 2a0fbebe8cf13..7ab640cf42ee1 100644 --- a/test/functional/apps/discover/classic/_doc_table.ts +++ b/test/functional/apps/discover/classic/_doc_table.ts @@ -28,6 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', hideAnnouncements: true, + 'doc_table:legacy': true, }; const testSubjects = getService('testSubjects'); @@ -75,7 +76,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('classic table in window 900x700', async function () { before(async () => { - await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); await browser.setWindowSize(900, 700); await PageObjects.common.navigateToApp('discover'); await PageObjects.discover.waitUntilSearchingHasFinished(); @@ -95,7 +95,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('classic table in window 600x700', async function () { before(async () => { - await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); await browser.setWindowSize(600, 700); await PageObjects.common.navigateToApp('discover'); await PageObjects.discover.waitUntilSearchingHasFinished(); @@ -115,7 +114,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('legacy', async function () { before(async () => { - await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); await PageObjects.common.navigateToApp('discover'); await PageObjects.discover.waitUntilSearchingHasFinished(); }); diff --git a/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts b/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts index 8961af57a4ad2..2fb99d9ebb43f 100644 --- a/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts +++ b/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts @@ -44,7 +44,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/discover/group1/_date_nanos_mixed.ts b/test/functional/apps/discover/group1/_date_nanos_mixed.ts index dab0003a63f07..df45356a69789 100644 --- a/test/functional/apps/discover/group1/_date_nanos_mixed.ts +++ b/test/functional/apps/discover/group1/_date_nanos_mixed.ts @@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); const kibanaServer = getService('kibanaServer'); const security = getService('security'); + const browser = getService('browser'); const from = 'Jan 1, 2019 @ 00:00:00.000'; const to = 'Jan 1, 2019 @ 23:59:59.999'; @@ -25,7 +26,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/date_nanos_mixed' ); - await kibanaServer.uiSettings.replace({ defaultIndex: 'timestamp-*' }); + await kibanaServer.uiSettings.replace({ + defaultIndex: 'timestamp-*', + hideAnnouncements: true, // should be enough vertical space to render rows + }); + await browser.setWindowSize(1200, 900); await security.testUser.setRoles(['kibana_admin', 'kibana_date_nanos_mixed']); await PageObjects.common.setTime({ from, to }); await PageObjects.common.navigateToApp('discover'); diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index a3cf7b2d29fcf..286a30a6bb227 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -31,7 +31,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: 'logstash-*', }; - describe('discover test', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/146223 + describe.skip('discover test', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); diff --git a/test/functional/apps/discover/group1/_discover_histogram.ts b/test/functional/apps/discover/group1/_discover_histogram.ts index c19005bfb12ff..6e30f75f90a10 100644 --- a/test/functional/apps/discover/group1/_discover_histogram.ts +++ b/test/functional/apps/discover/group1/_discover_histogram.ts @@ -15,7 +15,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']); + const PageObjects = getPageObjects([ + 'timePicker', + 'dashboard', + 'settings', + 'discover', + 'common', + 'header', + ]); const defaultSettings = { defaultIndex: 'long-window-logstash-*', 'dateFormat:tz': 'Europe/Berlin', @@ -88,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return actualCount <= expectedCount; }); const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); - expect(Math.round(newDurationHours)).to.be(26); + expect(Math.round(newDurationHours)).to.be(24); // might fail if histogram's width changes await retry.waitFor('doc table containing the documents of the brushed range', async () => { const rowData = await PageObjects.discover.getDocTableField(1); @@ -253,7 +260,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // go to dashboard - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.header.waitUntilLoadingHasFinished(); // go to discover diff --git a/test/functional/apps/discover/group2/_adhoc_data_views.ts b/test/functional/apps/discover/group2/_adhoc_data_views.ts index 299efefc12fb8..04880c1cfbc72 100644 --- a/test/functional/apps/discover/group2/_adhoc_data_views.ts +++ b/test/functional/apps/discover/group2/_adhoc_data_views.ts @@ -170,7 +170,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); // open searches on dashboard - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/discover/group2/_chart_hidden.ts b/test/functional/apps/discover/group2/_chart_hidden.ts index 40f50a73ac4df..6bee290df896d 100644 --- a/test/functional/apps/discover/group2/_chart_hidden.ts +++ b/test/functional/apps/discover/group2/_chart_hidden.ts @@ -14,7 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker', 'dashboard']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.toggleChartVisibility(); expect(await PageObjects.discover.isChartVisible()).to.be(false); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/discover/group2/_data_grid_context.ts b/test/functional/apps/discover/group2/_data_grid_context.ts index 1b43de8a72353..ae5030f226b82 100644 --- a/test/functional/apps/discover/group2/_data_grid_context.ts +++ b/test/functional/apps/discover/group2/_data_grid_context.ts @@ -30,7 +30,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'header', 'unifiedFieldList', ]); - const defaultSettings = { defaultIndex: 'logstash-*' }; + const defaultSettings = { + defaultIndex: 'logstash-*', + 'discover:rowHeightOption': 0, // single line + }; const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -99,7 +102,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.saveSearch('my search'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/discover/group2/_data_grid_doc_table.ts b/test/functional/apps/discover/group2/_data_grid_doc_table.ts index dd2481b13ad9f..5aedb67b6d5e7 100644 --- a/test/functional/apps/discover/group2/_data_grid_doc_table.ts +++ b/test/functional/apps/discover/group2/_data_grid_doc_table.ts @@ -28,6 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); const defaultSettings = { defaultIndex: 'logstash-*', + 'discover:rowHeightOption': 0, // single line }; const testSubjects = getService('testSubjects'); const security = getService('security'); @@ -119,7 +120,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.saveSearch('expand-cell-search'); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/discover/group2/_data_grid_footer.ts b/test/functional/apps/discover/group2/_data_grid_footer.ts index a1d2ff0294d23..4cc7b96a52607 100644 --- a/test/functional/apps/discover/group2/_data_grid_footer.ts +++ b/test/functional/apps/discover/group2/_data_grid_footer.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; -const FOOTER_SELECTOR = 'discoverTableFooter'; +const FOOTER_SELECTOR = 'unifiedDataTableFooter'; const LOAD_MORE_SELECTOR = 'dscGridSampleSizeFetchMoreLink'; export default function ({ getService, getPageObjects }: FtrProviderContext) { diff --git a/test/functional/apps/discover/group2/_data_grid_pagination.ts b/test/functional/apps/discover/group2/_data_grid_pagination.ts index 7da02308c73be..4c0a3aa53759e 100644 --- a/test/functional/apps/discover/group2/_data_grid_pagination.ts +++ b/test/functional/apps/discover/group2/_data_grid_pagination.ts @@ -15,7 +15,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const dataGrid = getService('dataGrid'); const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']); - const defaultSettings = { defaultIndex: 'logstash-*' }; + const defaultSettings = { + defaultIndex: 'logstash-*', + 'discover:rowHeightOption': 0, // single line + }; const testSubjects = getService('testSubjects'); const retry = getService('retry'); const security = getService('security'); @@ -52,18 +55,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show footer only for the last page', async () => { // footer is not shown - await testSubjects.missingOrFail('discoverTableFooter'); + await testSubjects.missingOrFail('unifiedDataTableFooter'); // go to next page await testSubjects.click('pagination-button-next'); // footer is not shown yet await retry.try(async function () { - await testSubjects.missingOrFail('discoverTableFooter'); + await testSubjects.missingOrFail('unifiedDataTableFooter'); }); // go to the last page await testSubjects.click('pagination-button-4'); // footer is shown now await retry.try(async function () { - await testSubjects.existOrFail('discoverTableFooter'); + await testSubjects.existOrFail('unifiedDataTableFooter'); }); }); @@ -80,7 +83,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.try(async function () { return !testSubjects.exists('pagination-button-1'); // only page 0 is left }); - await testSubjects.existOrFail('discoverTableFooter'); + await testSubjects.existOrFail('unifiedDataTableFooter'); }); it('should render exact number of rows which where configured in the saved search or in settings', async () => { diff --git a/test/functional/apps/discover/group2/_data_grid_row_height.ts b/test/functional/apps/discover/group2/_data_grid_row_height.ts new file mode 100644 index 0000000000000..2c385b67aaa02 --- /dev/null +++ b/test/functional/apps/discover/group2/_data_grid_row_height.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const dataGrid = getService('dataGrid'); + const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']); + const defaultSettings = { defaultIndex: 'logstash-*' }; + const security = getService('security'); + + describe('discover data grid row height', function describeIndexTests() { + before(async () => { + await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); + await browser.setWindowSize(1200, 2000); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + }); + + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.uiSettings.replace({}); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + beforeEach(async function () { + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update(defaultSettings); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + it('should use the default row height', async () => { + const rows = await dataGrid.getDocTableRows(); + expect(rows.length).to.be.above(0); + + await dataGrid.clickGridSettings(); + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Auto fit'); + }); + + it('should allow to change row height and reset it', async () => { + await dataGrid.clickGridSettings(); + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Auto fit'); + + await dataGrid.changeRowHeightValue('Single'); + + // toggle the popover + await dataGrid.clickGridSettings(); + await dataGrid.clickGridSettings(); + + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Single'); + + await dataGrid.resetRowHeightValue(); + + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Auto fit'); + + await dataGrid.changeRowHeightValue('Custom'); + + await dataGrid.resetRowHeightValue(); + + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Auto fit'); + }); + + it('should persist the selection after reloading the page', async () => { + await dataGrid.clickGridSettings(); + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Auto fit'); + + await dataGrid.changeRowHeightValue('Single'); + + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Single'); + + await browser.refresh(); + + await PageObjects.discover.waitUntilSearchingHasFinished(); + await dataGrid.clickGridSettings(); + + expect(await dataGrid.getCurrentRowHeightValue()).to.be('Single'); + }); + }); +} diff --git a/test/functional/apps/discover/group2/_esql_view.ts b/test/functional/apps/discover/group2/_esql_view.ts new file mode 100644 index 0000000000000..212420b56317d --- /dev/null +++ b/test/functional/apps/discover/group2/_esql_view.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const log = getService('log'); + const dataGrid = getService('dataGrid'); + const testSubjects = getService('testSubjects'); + const monacoEditor = getService('monacoEditor'); + const security = getService('security'); + const PageObjects = getPageObjects([ + 'common', + 'discover', + 'header', + 'timePicker', + 'unifiedFieldList', + ]); + + const defaultSettings = { + defaultIndex: 'logstash-*', + 'discover:enableESQL': true, + }; + + describe('discover esql view', async function () { + before(async () => { + await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); + log.debug('load kibana index with default index pattern'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + // and load a set of makelogs data + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + }); + + describe('test', () => { + it('should render esql view correctly', async function () { + await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); + + expect(await testSubjects.exists('showQueryBarMenu')).to.be(true); + expect(await testSubjects.exists('superDatePickerToggleQuickMenuButton')).to.be(true); + expect(await testSubjects.exists('addFilter')).to.be(true); + expect(await testSubjects.exists('dscViewModeDocumentButton')).to.be(true); + expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); + expect(await testSubjects.exists('unifiedHistogramQueryHits')).to.be(true); + expect(await testSubjects.exists('discoverAlertsButton')).to.be(true); + expect(await testSubjects.exists('shareTopNavButton')).to.be(true); + expect(await testSubjects.exists('docTableExpandToggleColumn')).to.be(true); + expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(true); + expect(await testSubjects.exists('fieldListFiltersFieldSearch')).to.be(true); + expect(await testSubjects.exists('fieldListFiltersFieldTypeFilterToggle')).to.be(true); + await testSubjects.click('field-@message-showDetails'); + expect(await testSubjects.exists('discoverFieldListPanelEdit-@message')).to.be(true); + + await PageObjects.discover.selectTextBaseLang(); + await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); + + expect(await testSubjects.exists('fieldListFiltersFieldSearch')).to.be(true); + expect(await testSubjects.exists('TextBasedLangEditor')).to.be(true); + expect(await testSubjects.exists('superDatePickerToggleQuickMenuButton')).to.be(true); + + expect(await testSubjects.exists('showQueryBarMenu')).to.be(false); + expect(await testSubjects.exists('addFilter')).to.be(false); + expect(await testSubjects.exists('dscViewModeDocumentButton')).to.be(false); + // when Lens suggests a table, we render an ESQL based histogram + expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); + expect(await testSubjects.exists('unifiedHistogramQueryHits')).to.be(true); + expect(await testSubjects.exists('discoverAlertsButton')).to.be(true); + expect(await testSubjects.exists('shareTopNavButton')).to.be(true); + expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(false); + expect(await testSubjects.exists('docTableExpandToggleColumn')).to.be(true); + expect(await testSubjects.exists('fieldListFiltersFieldTypeFilterToggle')).to.be(true); + await testSubjects.click('field-@message-showDetails'); + expect(await testSubjects.exists('discoverFieldListPanelEditItem')).to.be(false); + }); + + it('should perform test query correctly', async function () { + await PageObjects.discover.selectTextBaseLang(); + const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + + await monacoEditor.setCodeEditorValue(testQuery); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + // here Lens suggests a XY so it is rendered + expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); + expect(await testSubjects.exists('xyVisChart')).to.be(true); + const cell = await dataGrid.getCellElement(0, 2); + expect(await cell.getVisibleText()).to.be('1'); + }); + + it('should render when switching to a time range with no data, then back to a time range with data', async () => { + await PageObjects.discover.selectTextBaseLang(); + const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + await monacoEditor.setCodeEditorValue(testQuery); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + let cell = await dataGrid.getCellElement(0, 2); + expect(await cell.getVisibleText()).to.be('1'); + await PageObjects.timePicker.setAbsoluteRange( + 'Sep 19, 2015 @ 06:31:44.000', + 'Sep 19, 2015 @ 06:31:44.000' + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await testSubjects.exists('discoverNoResults')).to.be(true); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + cell = await dataGrid.getCellElement(0, 2); + expect(await cell.getVisibleText()).to.be('1'); + }); + + it('should query an index pattern that doesnt translate to a dataview correctly', async function () { + await PageObjects.discover.selectTextBaseLang(); + const testQuery = `from logstash* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + + await monacoEditor.setCodeEditorValue(testQuery); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const cell = await dataGrid.getCellElement(0, 2); + expect(await cell.getVisibleText()).to.be('1'); + }); + }); + }); +} diff --git a/test/functional/apps/discover/group2/_sql_view.ts b/test/functional/apps/discover/group2/_sql_view.ts deleted file mode 100644 index 95ce1516728d2..0000000000000 --- a/test/functional/apps/discover/group2/_sql_view.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; - -import { FtrProviderContext } from '../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const log = getService('log'); - const dataGrid = getService('dataGrid'); - const testSubjects = getService('testSubjects'); - const monacoEditor = getService('monacoEditor'); - const security = getService('security'); - const PageObjects = getPageObjects([ - 'common', - 'discover', - 'header', - 'timePicker', - 'unifiedFieldList', - ]); - - const defaultSettings = { - defaultIndex: 'logstash-*', - 'discover:enableSql': true, - }; - - describe('discover sql view', async function () { - before(async () => { - await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); - log.debug('load kibana index with default index pattern'); - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - // and load a set of makelogs data - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - await kibanaServer.uiSettings.replace(defaultSettings); - await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - }); - - describe('test', () => { - it('should render sql view correctly', async function () { - await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - - expect(await testSubjects.exists('showQueryBarMenu')).to.be(true); - expect(await testSubjects.exists('superDatePickerToggleQuickMenuButton')).to.be(true); - expect(await testSubjects.exists('addFilter')).to.be(true); - expect(await testSubjects.exists('dscViewModeDocumentButton')).to.be(true); - expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); - expect(await testSubjects.exists('unifiedHistogramQueryHits')).to.be(true); - expect(await testSubjects.exists('discoverAlertsButton')).to.be(true); - expect(await testSubjects.exists('shareTopNavButton')).to.be(true); - expect(await testSubjects.exists('docTableExpandToggleColumn')).to.be(true); - expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(true); - expect(await testSubjects.exists('fieldListFiltersFieldSearch')).to.be(true); - expect(await testSubjects.exists('fieldListFiltersFieldTypeFilterToggle')).to.be(true); - await testSubjects.click('field-@message-showDetails'); - expect(await testSubjects.exists('discoverFieldListPanelEdit-@message')).to.be(true); - - await PageObjects.discover.selectTextBaseLang('SQL'); - await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - - expect(await testSubjects.exists('fieldListFiltersFieldSearch')).to.be(true); - expect(await testSubjects.exists('TextBasedLangEditor')).to.be(true); - expect(await testSubjects.exists('superDatePickerToggleQuickMenuButton')).to.be(true); - - expect(await testSubjects.exists('showQueryBarMenu')).to.be(false); - expect(await testSubjects.exists('addFilter')).to.be(false); - expect(await testSubjects.exists('dscViewModeDocumentButton')).to.be(false); - // when Lens suggests a table, we render the histogram - expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); - expect(await testSubjects.exists('unifiedHistogramQueryHits')).to.be(true); - expect(await testSubjects.exists('discoverAlertsButton')).to.be(false); - expect(await testSubjects.exists('shareTopNavButton')).to.be(true); - expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(true); - expect(await testSubjects.exists('docTableExpandToggleColumn')).to.be(true); - expect(await testSubjects.exists('fieldListFiltersFieldTypeFilterToggle')).to.be(true); - await testSubjects.click('field-@message-showDetails'); - expect(await testSubjects.exists('discoverFieldListPanelEditItem')).to.be(false); - }); - - it('should perform test query correctly', async function () { - await PageObjects.discover.selectTextBaseLang('SQL'); - const testQuery = `SELECT "@tags", geo.dest, count(*) occurred FROM "logstash-*" - GROUP BY "@tags", geo.dest - HAVING occurred > 20 - ORDER BY occurred DESC`; - - await monacoEditor.setCodeEditorValue(testQuery); - await testSubjects.click('querySubmitButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - // here Lens suggests a heatmap so it is rendered - expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); - expect(await testSubjects.exists('heatmapChart')).to.be(true); - const cell = await dataGrid.getCellElement(0, 4); - expect(await cell.getVisibleText()).to.be('2269'); - }); - - it('should render when switching to a time range with no data, then back to a time range with data', async () => { - await PageObjects.discover.selectTextBaseLang('SQL'); - const testQuery = `SELECT "@tags", geo.dest, count(*) occurred FROM "logstash-*" - GROUP BY "@tags", geo.dest - HAVING occurred > 20 - ORDER BY occurred DESC`; - await monacoEditor.setCodeEditorValue(testQuery); - await testSubjects.click('querySubmitButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - let cell = await dataGrid.getCellElement(0, 4); - expect(await cell.getVisibleText()).to.be('2269'); - await PageObjects.timePicker.setAbsoluteRange( - 'Sep 19, 2015 @ 06:31:44.000', - 'Sep 19, 2015 @ 06:31:44.000' - ); - await PageObjects.header.waitUntilLoadingHasFinished(); - expect(await testSubjects.exists('discoverNoResults')).to.be(true); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.header.waitUntilLoadingHasFinished(); - cell = await dataGrid.getCellElement(0, 4); - expect(await cell.getVisibleText()).to.be('2269'); - }); - - it('should query an index pattern that doesnt translate to a dataview correctly', async function () { - await PageObjects.discover.selectTextBaseLang('SQL'); - const testQuery = `SELECT "@tags", geo.dest, count(*) occurred FROM "logstash*" - GROUP BY "@tags", geo.dest - HAVING occurred > 20 - ORDER BY occurred DESC`; - - await monacoEditor.setCodeEditorValue(testQuery); - await testSubjects.click('querySubmitButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - const cell = await dataGrid.getCellElement(0, 4); - expect(await cell.getVisibleText()).to.be('2269'); - }); - }); - }); -} diff --git a/test/functional/apps/discover/group2/index.ts b/test/functional/apps/discover/group2/index.ts index 17562157f444e..3d4103c6de85b 100644 --- a/test/functional/apps/discover/group2/index.ts +++ b/test/functional/apps/discover/group2/index.ts @@ -29,10 +29,11 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_data_grid_row_navigation')); loadTestFile(require.resolve('./_data_grid_doc_table')); loadTestFile(require.resolve('./_data_grid_copy_to_clipboard')); + loadTestFile(require.resolve('./_data_grid_row_height')); loadTestFile(require.resolve('./_data_grid_pagination')); loadTestFile(require.resolve('./_data_grid_footer')); loadTestFile(require.resolve('./_adhoc_data_views')); - loadTestFile(require.resolve('./_sql_view')); + loadTestFile(require.resolve('./_esql_view')); loadTestFile(require.resolve('./_indexpattern_with_unmapped_fields')); loadTestFile(require.resolve('./_runtime_fields_editor')); loadTestFile(require.resolve('./_huge_fields')); diff --git a/test/functional/apps/discover/group3/_request_counts.ts b/test/functional/apps/discover/group3/_request_counts.ts index c1224596d3e00..fdee64ada9965 100644 --- a/test/functional/apps/discover/group3/_request_counts.ts +++ b/test/functional/apps/discover/group3/_request_counts.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', 'bfetch:disable': true, - 'discover:enableSql': true, + 'discover:enableESQL': true, }); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); }); @@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); }); - const getSearchCount = async (type: 'ese' | 'sql') => { + const getSearchCount = async (type: 'ese' | 'esql') => { const requests = await browser.execute(() => performance .getEntries() @@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await elasticChart.canvasExists(); }; - const expectSearches = async (type: 'ese' | 'sql', expected: number, cb: Function) => { + const expectSearches = async (type: 'ese' | 'esql', expected: number, cb: Function) => { await browser.execute(async () => { performance.clearResourceTimings(); }); @@ -86,12 +86,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { savedSearch, query1, query2, + savedSearchesRequests, setQuery, }: { - type: 'ese' | 'sql'; + type: 'ese' | 'esql'; savedSearch: string; query1: string; query2: string; + savedSearchesRequests?: number; setQuery: (query: string) => Promise; }) => { it('should send 2 search requests (documents + chart) on page load', async () => { @@ -143,8 +145,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Sep 23, 2015 @ 00:00:00.000' ); await waitForLoadingToFinish(); + // TODO: Check why the request happens 4 times in case of opening a saved search + // https://github.com/elastic/kibana/issues/165192 // creating the saved search - await expectSearches(type, 2, async () => { + await expectSearches(type, savedSearchesRequests ?? 2, async () => { await PageObjects.discover.saveSearch(savedSearch); }); // resetting the saved search @@ -160,7 +164,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await waitForLoadingToFinish(); }); // loading the saved search - await expectSearches(type, 2, async () => { + // TODO: https://github.com/elastic/kibana/issues/165192 + await expectSearches(type, savedSearchesRequests ?? 2, async () => { await PageObjects.discover.loadSavedSearch(savedSearch); }); }); @@ -218,21 +223,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('SQL mode', () => { - const type = 'sql'; + describe('ES|QL mode', () => { + const type = 'esql'; beforeEach(async () => { - await PageObjects.discover.selectTextBaseLang('SQL'); - monacoEditor.setCodeEditorValue('SELECT count(*) FROM "logstash-*" WHERE bytes > 1000'); + await PageObjects.discover.selectTextBaseLang(); + monacoEditor.setCodeEditorValue( + 'from logstash-* | where bytes > 1000 | stats countB = count(bytes)' + ); await queryBar.clickQuerySubmitButton(); await waitForLoadingToFinish(); }); getSharedTests({ type, - savedSearch: 'sql test', - query1: 'SELECT type, count(*) FROM "logstash-*" WHERE bytes > 1000 GROUP BY type', - query2: 'SELECT type, count(*) FROM "logstash-*" WHERE bytes < 2000 GROUP BY type', + savedSearch: 'esql test', + query1: 'from logstash-* | where bytes > 1000 | stats countB = count(bytes) ', + query2: 'from logstash-* | where bytes < 2000 | stats countB = count(bytes) ', + savedSearchesRequests: 4, setQuery: (query) => monacoEditor.setCodeEditorValue(query), }); }); diff --git a/test/functional/apps/discover/group3/_sidebar.ts b/test/functional/apps/discover/group3/_sidebar.ts index 265466b5c67f9..6a09524777487 100644 --- a/test/functional/apps/discover/group3/_sidebar.ts +++ b/test/functional/apps/discover/group3/_sidebar.ts @@ -96,7 +96,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show filters by type in text-based view', async function () { - await kibanaServer.uiSettings.update({ 'discover:enableSql': true }); + await kibanaServer.uiSettings.update({ 'discover:enableESQL': true }); await browser.refresh(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); @@ -105,7 +105,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(options).to.have.length(6); await PageObjects.unifiedFieldList.closeSidebarFieldFilter(); - await PageObjects.discover.selectTextBaseLang('SQL'); + await PageObjects.discover.selectTextBaseLang(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); await PageObjects.unifiedFieldList.openSidebarFieldFilter(); @@ -113,7 +113,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(options).to.have.length(3); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '50 selected fields. 51 available fields.' + '82 available fields.' ); await testSubjects.click('typeFilter-number'); @@ -121,7 +121,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('updates', async () => { return ( (await PageObjects.unifiedFieldList.getSidebarAriaDescription()) === - '6 selected fields. 6 available fields.' + '6 available fields.' ); }); }); @@ -214,16 +214,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('collapse expand', function () { it('should initially be expanded', async function () { await testSubjects.existOrFail('discover-sidebar'); + await testSubjects.existOrFail('fieldList'); }); it('should collapse when clicked', async function () { await PageObjects.discover.toggleSidebarCollapse(); - await testSubjects.missingOrFail('discover-sidebar'); + await testSubjects.existOrFail('discover-sidebar'); + await testSubjects.missingOrFail('fieldList'); }); it('should expand when clicked', async function () { await PageObjects.discover.toggleSidebarCollapse(); await testSubjects.existOrFail('discover-sidebar'); + await testSubjects.existOrFail('fieldList'); }); }); @@ -366,7 +369,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show selected and available fields in text-based mode', async function () { - await kibanaServer.uiSettings.update({ 'discover:enableSql': true }); + await kibanaServer.uiSettings.update({ 'discover:enableESQL': true }); await browser.refresh(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); @@ -375,24 +378,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { INITIAL_FIELD_LIST_SUMMARY ); - await PageObjects.discover.selectTextBaseLang('SQL'); + await PageObjects.discover.selectTextBaseLang(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '50 selected fields. 51 available fields.' + '82 available fields.' ); await PageObjects.unifiedFieldList.clickFieldListItemRemove('extension'); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '49 selected fields. 51 available fields.' + '82 available fields.' ); - const testQuery = `SELECT "@tags", geo.dest, count(*) occurred FROM "logstash-*" - GROUP BY "@tags", geo.dest - HAVING occurred > 20 - ORDER BY occurred DESC`; + const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); @@ -400,11 +400,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '3 selected fields. 3 available fields.' + '2 selected fields. 2 available fields.' ); expect( (await PageObjects.unifiedFieldList.getSidebarSectionFieldNames('selected')).join(', ') - ).to.be('@tags, geo.dest, occurred'); + ).to.be('countB, geo.dest'); await PageObjects.unifiedSearch.switchDataView( 'discover-dataView-switch-link', diff --git a/test/functional/apps/kibana_overview/_analytics.ts b/test/functional/apps/kibana_overview/_analytics.ts index 7b9d352e60e31..1c4897d5830ac 100644 --- a/test/functional/apps/kibana_overview/_analytics.ts +++ b/test/functional/apps/kibana_overview/_analytics.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - const apps = ['dashboard', 'discover', 'canvas', 'maps', 'ml']; + const apps = ['dashboards', 'discover', 'canvas', 'maps', 'ml']; it('should display Analytics apps cards', async () => { const kbnOverviewAppsCards = await find.allByCssSelector('.kbnOverviewApps__item'); diff --git a/test/functional/apps/management/_kibana_settings.ts b/test/functional/apps/management/_kibana_settings.ts index d459643849fbc..875635cbd6a09 100644 --- a/test/functional/apps/management/_kibana_settings.ts +++ b/test/functional/apps/management/_kibana_settings.ts @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('when false, dashboard state is unhashed', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultAbsoluteRange(); const globalState = await getStateFromUrl(); @@ -73,7 +73,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('when true, dashboard state is hashed', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultAbsoluteRange(); const globalState = await getStateFromUrl(); diff --git a/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts index d62bdd86ecf9b..50f855056c6cd 100644 --- a/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts +++ b/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts @@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // test to cover bug #54548 - add this visualization to a dashboard and filter it('should add to dashboard and allow filtering', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addVisualization(vizName1); diff --git a/test/functional/apps/visualize/group2/_inspector.ts b/test/functional/apps/visualize/group2/_inspector.ts index 80cfc42ab3cd6..9baa3b1bc820d 100644 --- a/test/functional/apps/visualize/group2/_inspector.ts +++ b/test/functional/apps/visualize/group2/_inspector.ts @@ -41,11 +41,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await inspector.open(); await inspector.openInspectorRequestsView(); - const requestTab = await inspector.getOpenRequestDetailRequestButton(); - await requestTab.click(); - const requestJSON = JSON.parse(await monacoEditor.getCodeEditorValue(1)); - - expect(requestJSON.aggs['2'].max).property('missing', 10); + const { body } = await inspector.getRequest(1); + expect(body.aggs['2'].max).property('missing', 10); }); after(async () => { diff --git a/test/functional/apps/visualize/group3/_add_to_dashboard.ts b/test/functional/apps/visualize/group3/_add_to_dashboard.ts index 896826c2caa07..ad14c89b1e830 100644 --- a/test/functional/apps/visualize/group3/_add_to_dashboard.ts +++ b/test/functional/apps/visualize/group3/_add_to_dashboard.ts @@ -158,7 +158,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a new metric to an existing dashboard by value', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); @@ -188,7 +188,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a new metric to an existing dashboard by reference', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); @@ -220,7 +220,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a existing metric to an existing dashboard by value', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); @@ -265,7 +265,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a existing metric to an existing dashboard by reference', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); diff --git a/test/functional/apps/visualize/group3/_annotation_listing.ts b/test/functional/apps/visualize/group3/_annotation_listing.ts new file mode 100644 index 0000000000000..cdb0cb615be47 --- /dev/null +++ b/test/functional/apps/visualize/group3/_annotation_listing.ts @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'annotationEditor']); + const listingTable = getService('listingTable'); + const kibanaServer = getService('kibanaServer'); + const find = getService('find'); + const retry = getService('retry'); + const log = getService('log'); + + describe('annotation listing page', function () { + before(async function () { + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/annotation_listing_page_search' + ); + // we need to test the missing data view scenario, so delete one of them + // (can't just omit it from the archive because Kibana won't import with broken references) + log.info(`deleting one data view to replicate missing data view scenario...`); + await kibanaServer.request({ + method: 'DELETE', + path: `/api/data_views/data_view/data-view-to-delete`, + }); + + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.visualize.selectAnnotationsTab(); + }); + + after(async function () { + log.info(`unloading annotations and data views`); + await kibanaServer.importExport.unload( + 'test/functional/fixtures/kbn_archiver/annotation_listing_page_search' + ); + }); + + describe('search', function () { + afterEach(async function () { + await listingTable.clearSearchFilter(); + }); + + describe('by text', () => { + it('matches on the first word', async function () { + await listingTable.searchForItemWithName('search'); + await listingTable.expectItemsCount('eventAnnotation', 1); + }); + + it('matches the second word', async function () { + await listingTable.searchForItemWithName('for'); + await listingTable.expectItemsCount('eventAnnotation', 1); + }); + + it('matches the second word prefix', async function () { + await listingTable.searchForItemWithName('fo'); + await listingTable.expectItemsCount('eventAnnotation', 1); + }); + + it('does not match mid word', async function () { + await listingTable.searchForItemWithName('earc'); + // custom timeout so this test moves faster + await listingTable.expectItemsCount('eventAnnotation', 0, 1000); + }); + + it('is case insensitive', async function () { + await listingTable.searchForItemWithName('SEARCH'); + await listingTable.expectItemsCount('eventAnnotation', 1); + }); + + it('is using AND operator', async function () { + await listingTable.searchForItemWithName('search banana'); + // custom timeout so this test moves faster + await listingTable.expectItemsCount('eventAnnotation', 0, 1000); + }); + + it('matches on description', async function () { + await listingTable.searchForItemWithName('i have a description'); + await listingTable.expectItemsCount('eventAnnotation', 1); + }); + }); + + describe('by tag', () => { + it('filters by tag', async () => { + await listingTable.selectFilterTags('tag'); + await listingTable.expectItemsCount('eventAnnotation', 7); + }); + }); + }); + + describe('delete', function () { + it('deletes some groups', async function () { + await listingTable.deleteItem('to delete 1'); + await listingTable.deleteItem('to delete 2'); + await listingTable.searchForItemWithName('to delete'); + await listingTable.expectItemsCount('eventAnnotation', 0, 1000); + await listingTable.clearSearchFilter(); + }); + }); + + describe('edit', function () { + it('edits group metadata', async function () { + await listingTable.clickItemLink('eventAnnotation', 'group 3'); + await PageObjects.annotationEditor.editGroupMetadata({ + title: 'edited title', + description: 'edited description', + }); + await PageObjects.annotationEditor.saveGroup(); + + await listingTable.searchForItemWithName('edited title'); + await listingTable.expectItemsCount('eventAnnotation', 1); + + await listingTable.searchForItemWithName('edited description'); + await listingTable.expectItemsCount('eventAnnotation', 1); + }); + + describe('individual annotations', () => { + it('edits an existing annotation', async function () { + await listingTable.clickItemLink('eventAnnotation', 'edited title'); + expect(await PageObjects.annotationEditor.getAnnotationCount()).to.be(1); + + await PageObjects.annotationEditor.openAnnotation(); + await PageObjects.annotationEditor.configureAnnotation({ + query: 'my query', + lineThickness: 5, + color: '#FF0000', + }); + }); + + it('adds a new annotation', async function () { + await PageObjects.annotationEditor.addAnnotation({ + query: 'other query', + lineThickness: 3, + color: '#00FF00', + }); + + retry.try(async () => { + expect(await PageObjects.annotationEditor.getAnnotationCount()).to.be(2); + }); + }); + + it('removes an annotation', async function () { + await PageObjects.annotationEditor.removeAnnotation(); + + await retry.try(async () => { + expect(await PageObjects.annotationEditor.getAnnotationCount()).to.be(1); + }); + + await PageObjects.annotationEditor.saveGroup(); + await listingTable.clearSearchFilter(); + }); + }); + + describe.skip('data view switching', () => { + it('recovers from missing data view', async () => { + await listingTable.clickItemLink('eventAnnotation', 'missing data view'); + + await retry.try(async () => { + expect(await PageObjects.annotationEditor.showingMissingDataViewPrompt()).to.be(true); + }); + + await retry.try(async () => { + await PageObjects.annotationEditor.editGroupMetadata({ + dataView: 'logs*', + }); + expect(await PageObjects.annotationEditor.showingMissingDataViewPrompt()).to.be(false); + expect(await find.byCssSelector('canvas')).to.be.ok(); + }); + + await PageObjects.annotationEditor.saveGroup(); + }); + + it('recovers from missing field in data view', () => {}); + }); + }); + }); +} diff --git a/test/functional/apps/visualize/group3/_visualize_listing.ts b/test/functional/apps/visualize/group3/_visualize_listing.ts index ad370939f2260..7adc5e088ce7c 100644 --- a/test/functional/apps/visualize/group3/_visualize_listing.ts +++ b/test/functional/apps/visualize/group3/_visualize_listing.ts @@ -84,5 +84,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await listingTable.expectItemsCount('visualize', 0); }); }); + + describe('Edit', () => { + before(async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + }); + + it('should edit the title and description of a visualization', async () => { + await listingTable.searchForItemWithName('Hello'); + await listingTable.inspectVisualization(); + await listingTable.editVisualizationDetails({ + title: 'new title', + description: 'new description', + }); + await listingTable.searchForItemWithName('new title'); + await listingTable.expectItemsCount('visualize', 1); + }); + }); }); } diff --git a/test/functional/apps/visualize/group3/index.ts b/test/functional/apps/visualize/group3/index.ts index 317e003d4ea01..b62d9569ac2c9 100644 --- a/test/functional/apps/visualize/group3/index.ts +++ b/test/functional/apps/visualize/group3/index.ts @@ -30,5 +30,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_visualize_listing')); loadTestFile(require.resolve('./_add_to_dashboard.ts')); loadTestFile(require.resolve('./_pie_chart')); + loadTestFile(require.resolve('./_annotation_listing')); }); } diff --git a/test/functional/apps/visualize/group5/_tsvb_time_series.ts b/test/functional/apps/visualize/group5/_tsvb_time_series.ts index eec30c52018a7..dbddadbe4a746 100644 --- a/test/functional/apps/visualize/group5/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/group5/_tsvb_time_series.ts @@ -195,7 +195,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const cleanup = async () => { const discardDashboardPromptButton = 'discardDashboardPromptButton'; - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); if (await testSubjects.exists(discardDashboardPromptButton)) { await dashboard.clickUnsavedChangesDiscard(discardDashboardPromptButton, true); } diff --git a/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json b/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json new file mode 100644 index 0000000000000..34f4fb8ed1b48 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json @@ -0,0 +1,832 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "logs*", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "logs*", + "typeMeta": "{}" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-07T17:23:20.906Z", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "managed": false, + "references": [], + "type": "index-pattern", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-09-11T15:50:59.227Z", + "version": "WzIyNywxXQ==" +} + +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{\"hour_of_day\":{}}", + "fields": "[]", + "name": "To Delete!", + "runtimeFieldMap": "{\"hour_of_day\":{\"type\":\"long\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getHour());\"}}}", + "sourceFilters": "[]", + "timeFieldName": "timestamp", + "title": "kibana_sample_data_logs", + "typeMeta": "{}" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-07T17:23:20.906Z", + "id": "data-view-to-delete", + "managed": false, + "references": [], + "type": "index-pattern", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-09-11T15:50:59.227Z", + "version": "WzIyNywxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 19" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T14:00:20.704Z", + "id": "b6071e00-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T14:00:20.704Z", + "version": "WzMxNywxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "i have a description", + "ignoreGlobalFilters": true, + "title": "group 21" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T14:00:12.994Z", + "id": "b16eaa20-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T14:00:12.994Z", + "version": "WzMxNiwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "search for me" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T14:01:40.768Z", + "id": "e5bfc2f0-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T14:02:00.597Z", + "version": "WzMyMCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "to delete 1" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:53.977Z", + "id": "a618e690-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T14:00:06.429Z", + "version": "WzMxNSwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "to delete 2" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:47.961Z", + "id": "a282ee90-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:47.961Z", + "version": "WzMxMiwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 16" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:42.118Z", + "id": "9f075c60-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:42.118Z", + "version": "WzMxMSwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "missing data view" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:36.447Z", + "id": "9ba608f0-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "data-view-to-delete", + "name": "event-annotation-group_dataView-ref-data-view-to-delete", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:36.447Z", + "version": "WzMxMCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 14" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:30.997Z", + "id": "98666e50-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:30.997Z", + "version": "WzMwOSwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 13" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:25.669Z", + "id": "95397150-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:25.669Z", + "version": "WzMwOCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 12" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:20.653Z", + "id": "923c0fd0-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:20.653Z", + "version": "WzMwNywxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 11" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:14.944Z", + "id": "8ed4f000-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:14.944Z", + "version": "WzMwNiwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 10" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:09.600Z", + "id": "8ba58200-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:09.600Z", + "version": "WzMwNSwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 9" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:59:02.338Z", + "id": "87514310-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:59:02.338Z", + "version": "WzMwNCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 8" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:55.464Z", + "id": "83388680-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:55.464Z", + "version": "WzMwMywxXQ==" +} + +{ + "attributes": { + "color": "#dac7c4", + "description": "a tag to filter by", + "name": "tag" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-08T18:40:42.018Z", + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "managed": false, + "references": [], + "type": "tag", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-09-08T18:40:42.018Z", + "version": "WzI3NDUsMV0=" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 7" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:46.671Z", + "id": "7dfad1f0-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:46.671Z", + "version": "WzMwMiwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 6" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:37.886Z", + "id": "78be55e0-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:37.886Z", + "version": "WzMwMSwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 5" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:32.312Z", + "id": "756bcf80-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:32.312Z", + "version": "WzMwMCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 4" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:26.947Z", + "id": "72392d30-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:26.947Z", + "version": "WzI5OSwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 3" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:21.136Z", + "id": "6ec27d00-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:21.136Z", + "version": "WzI5OCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 2" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:15.691Z", + "id": "6b83a5b0-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:15.691Z", + "version": "WzI5NywxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "timestamp": "2023-09-13T16:30:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "group 1" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-12T13:58:07.740Z", + "id": "66c66bc0-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-12T13:58:07.740Z", + "version": "WzI5NiwxXQ==" +} \ No newline at end of file diff --git a/test/functional/page_objects/annotation_library_editor_page.ts b/test/functional/page_objects/annotation_library_editor_page.ts new file mode 100644 index 0000000000000..f5c66b9f6c1e9 --- /dev/null +++ b/test/functional/page_objects/annotation_library_editor_page.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrService } from '../ftr_provider_context'; + +export class AnnotationEditorPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + + /** + * Fills out group metadata + */ + public async editGroupMetadata(metadata: { + title?: string; + description?: string; + dataView?: string; + }) { + if (metadata.title) { + await this.testSubjects.setValue('annotationGroupTitle', metadata.title); + } + + if (metadata.description) { + await this.testSubjects.setValue('annotationGroupDescription', metadata.description); + } + + if (metadata.dataView) { + await this.testSubjects.setValue('annotationDataViewSelection', metadata.dataView); + } + } + + public async saveGroup() { + await this.testSubjects.click('saveAnnotationGroup'); + } + + public async getAnnotationCount() { + const triggers = await this.testSubjects.findAll('lns-dimensionTrigger'); + return triggers.length; + } + + public async openAnnotation() { + await this.testSubjects.click('lns-dimensionTrigger'); + } + + public async configureAnnotation(config: { + query: string; + lineThickness: number; + color: string; + }) { + await this.testSubjects.click('lnsXY_annotation_query'); + + const queryInput = await this.testSubjects.find('annotation-query-based-query-input'); + await queryInput.type(config.query); + + await this.testSubjects.setValue('lnsXYThickness', '' + config.lineThickness); + + await this.testSubjects.setValue( + 'euiColorPickerAnchor indexPattern-dimension-colorPicker', + config.color + ); + + await this.retry.waitFor('annotation editor UI to close', async () => { + await this.testSubjects.click('backToGroupSettings'); + return !(await this.testSubjects.exists('backToGroupSettings')); + }); + } + + public async addAnnotation(config: { query: string; lineThickness: number; color: string }) { + await this.testSubjects.click('addAnnotation'); + await this.configureAnnotation(config); + } + + public async removeAnnotation() { + await this.testSubjects.click('indexPattern-dimension-remove'); + } + + public async showingMissingDataViewPrompt() { + return await this.testSubjects.exists('missingDataViewPrompt'); + } +} diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 1424d952653cd..61297aea12e4e 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -243,6 +243,14 @@ export class CommonPageObject extends FtrService { } = {} ) { let appUrl: string; + + // See https://github.com/elastic/kibana/pull/164376 + if (appName === 'canvas' && !path) { + throw new Error( + 'This causes flaky test failures. Use Canvas page object goToListingPage instead' + ); + } + if (this.config.has(['apps', appName])) { // Legacy applications const appConfig = this.config.get(['apps', appName]); @@ -299,7 +307,7 @@ export class CommonPageObject extends FtrService { const navSuccessful = currentUrl .replace(':80/', '/') .replace(':443/', '/') - .startsWith(appUrl); + .startsWith(appUrl.replace(':80/', '/').replace(':443/', '/')); if (!navSuccessful) { const msg = `App failed to load: ${appName} in ${this.defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`; diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 9efe80741621e..a48578a60bc17 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -150,6 +150,18 @@ export class ConsolePageObject extends FtrService { return !attribute.includes('display: none;'); } + public async getAutocompleteSuggestion(index: number = 0) { + const children1 = await this.find + .allByCssSelector('.ace_autocomplete .ace_line :nth-child(1)') + .catch(() => null); + const children2 = await this.find + .allByCssSelector('.ace_autocomplete .ace_line :nth-child(2)') + .catch(() => null); + if (!children1 || !children2) return null; + + return (await children1[index].getVisibleText()) + (await children2[index].getVisibleText()); + } + public async enterRequest(request: string = '\nGET _search') { const textArea = await this.getEditorTextArea(); await textArea.pressKeys(request); @@ -206,24 +218,24 @@ export class ConsolePageObject extends FtrService { await textArea.pressKeys(Key.ESCAPE); } - public async pressDown() { + public async pressDown(shift: boolean = false) { const textArea = await this.testSubjects.find('console-textarea'); - await textArea.pressKeys(Key.DOWN); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.DOWN] : Key.DOWN); } - public async pressLeft() { + public async pressLeft(shift: boolean = false) { const textArea = await this.testSubjects.find('console-textarea'); - await textArea.pressKeys(Key.LEFT); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.LEFT] : Key.LEFT); } - public async pressRight() { + public async pressRight(shift: boolean = false) { const textArea = await this.testSubjects.find('console-textarea'); - await textArea.pressKeys(Key.RIGHT); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.RIGHT] : Key.RIGHT); } - public async pressUp() { + public async pressUp(shift: boolean = false) { const textArea = await this.testSubjects.find('console-textarea'); - await textArea.pressKeys(Key.UP); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.UP] : Key.UP); } public async clearTextArea() { @@ -417,6 +429,14 @@ export class ConsolePageObject extends FtrService { await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], '/']); } + public async pressCtrlSpace() { + const textArea = await this.testSubjects.find('console-textarea'); + await textArea.pressKeys([ + Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], + Key.SPACE, + ]); + } + public async clickContextMenu() { const contextMenu = await this.testSubjects.find('toggleConsoleMenu'); await contextMenu.click(); diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 36da398b59d1f..6ff48c6b0cfbe 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -51,12 +51,18 @@ export class DashboardPageObject extends FtrService { ? 'test/functional/fixtures/kbn_archiver/ccs/dashboard/legacy/legacy.json' : 'test/functional/fixtures/kbn_archiver/dashboard/legacy/legacy.json'; + public readonly APP_ID = 'dashboards'; + async initTests({ kibanaIndex = this.kibanaIndex, defaultIndex = this.logstashIndex } = {}) { this.log.debug('load kibana index with visualizations and log data'); await this.kibanaServer.savedObjects.cleanStandardList(); await this.kibanaServer.importExport.load(kibanaIndex); await this.kibanaServer.uiSettings.replace({ defaultIndex }); - await this.common.navigateToApp('dashboard'); + await this.navigateToApp(); + } + + public async navigateToApp() { + await this.common.navigateToApp(this.APP_ID); } public async expectAppStateRemovedFromURL() { diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 7ddd4f303d975..d36cd4b56b129 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -370,13 +370,14 @@ export class DiscoverPageObject extends FtrService { } public async toggleSidebarCollapse() { - return await this.testSubjects.click('collapseSideBarButton'); + return await this.testSubjects.click('unifiedFieldListSidebar__toggle'); } public async closeSidebar() { await this.retry.tryForTime(2 * 1000, async () => { - await this.toggleSidebarCollapse(); - await this.testSubjects.missingOrFail('discover-sidebar'); + await this.testSubjects.click('unifiedFieldListSidebar__toggle-collapse'); + await this.testSubjects.missingOrFail('unifiedFieldListSidebar__toggle-collapse'); + await this.testSubjects.missingOrFail('fieldList'); }); } @@ -500,11 +501,9 @@ export class DiscoverPageObject extends FtrService { return items; } - public async selectTextBaseLang(lang: 'SQL') { + public async selectTextBaseLang() { await this.testSubjects.click('discover-dataView-switch-link'); - await this.find.clickByCssSelector( - `[data-test-subj="text-based-languages-switcher"] [title="${lang}"]` - ); + await this.testSubjects.click('select-text-based-language-panel'); await this.header.waitUntilLoadingHasFinished(); } diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index ac942c70b5d90..61eb1f40c49e7 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -53,7 +53,9 @@ export class HomePageObject extends FtrService { } async isWelcomeInterstitialDisplayed() { - return await this.testSubjects.isDisplayed('homeWelcomeInterstitial'); + // give the interstitial enough time to fade in + await new Promise((resolve) => setTimeout(resolve, 500)); + return await this.testSubjects.isDisplayed('homeWelcomeInterstitial', 2000); } async isGuidedOnboardingLandingDisplayed() { diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index 63cd23e6e718f..9a2312e0fedee 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -34,8 +34,10 @@ import { DashboardPageControls } from './dashboard_page_controls'; import { UnifiedSearchPageObject } from './unified_search_page'; import { UnifiedFieldListPageObject } from './unified_field_list'; import { FilesManagementPageObject } from './files_management'; +import { AnnotationEditorPageObject } from './annotation_library_editor_page'; export const pageObjects = { + annotationEditor: AnnotationEditorPageObject, common: CommonPageObject, console: ConsolePageObject, context: ContextPageObject, diff --git a/test/functional/page_objects/time_to_visualize_page.ts b/test/functional/page_objects/time_to_visualize_page.ts index 91864a7995779..280a02354424c 100644 --- a/test/functional/page_objects/time_to_visualize_page.ts +++ b/test/functional/page_objects/time_to_visualize_page.ts @@ -42,7 +42,7 @@ export class TimeToVisualizePageObject extends FtrService { } public async resetNewDashboard() { - await this.common.navigateToApp('dashboard'); + await this.dashboard.navigateToApp(); await this.dashboard.gotoDashboardLandingPage(); await this.dashboard.clickNewDashboard(false); } diff --git a/test/functional/page_objects/unified_field_list.ts b/test/functional/page_objects/unified_field_list.ts index ec62cc4e358d8..5e306239dfdcd 100644 --- a/test/functional/page_objects/unified_field_list.ts +++ b/test/functional/page_objects/unified_field_list.ts @@ -84,7 +84,7 @@ export class UnifiedFieldListPageObject extends FtrService { public async toggleSidebarSection(sectionName: SidebarSectionName) { return await this.find.clickByCssSelector( - `${this.getSidebarSectionSelector(sectionName, true)} .euiAccordion__iconButton` + `${this.getSidebarSectionSelector(sectionName, true)} .euiAccordion__arrow` ); } @@ -95,6 +95,7 @@ export class UnifiedFieldListPageObject extends FtrService { } public async clickFieldListItem(field: string) { + await this.testSubjects.moveMouseTo(`field-${field}`); await this.testSubjects.click(`field-${field}`); await this.waitUntilFieldPopoverIsOpen(); diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index e85f560fec909..f6f4f121ad11a 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -211,10 +211,21 @@ export class VisualizeEditorPageObject extends FtrService { const input = await this.find.byCssSelector( '[data-test-subj="visEditorPercentileRanks"] input' ); + this.log.debug(`Setting percentile rank value of ${newValue}`); await input.clearValue(); await input.type(newValue); } + public async setPercentileValue(newValue: string, index: number = 0) { + const correctIndex = index * 2 + 1; + const input = await this.find.byCssSelector( + `[data-test-subj="visEditorPercentile"]>div:nth-child(2)>div:nth-child(${correctIndex}) input` + ); + this.log.debug(`Setting percentile value at ${index}th input of ${newValue}`); + await input.clearValueWithKeyboard(); + await input.type(newValue, { charByChar: true }); + } + public async clickEditorSidebarCollapse() { await this.testSubjects.click('collapseSideBarButton'); } diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index fd46e5ac1448b..0a1798442f360 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -73,6 +73,11 @@ class BrowserService extends FtrService { return await this.driver.manage().window().getRect(); } + public async getWindowInnerSize(): Promise<{ height: number; width: number }> { + const JS_GET_INNER_WIDTH = 'return { width: window.innerWidth, height: window.innerHeight };'; + return await this.driver.executeScript(JS_GET_INNER_WIDTH); + } + /** * Sets the dimensions of a window. * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Window.html @@ -659,6 +664,12 @@ class BrowserService extends FtrService { this.log.error( `WebDriver session is no longer valid.\nProbably Chrome process crashed when it tried to use more memory than what was available.` ); + // TODO: Remove this after a while. We are enabling richer logs in order to try catch the real error cause. + this.log.error( + `Original Error Logging.\n Name: ${err.name};\n Message: ${err.message};\n Stack: ${ + err.stack + }\n RemoteStack: ${(err as NoSuchSessionError).remoteStacktrace}` + ); } return false; } diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index e54a1caa08d26..9cbce20b05068 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -285,9 +285,9 @@ export class TestSubjects extends FtrService { return await element.isEnabled(); } - public async isDisplayed(selector: string): Promise { + public async isDisplayed(selector: string, timeout?: number): Promise { this.log.debug(`TestSubjects.isDisplayed(${selector})`); - const element = await this.find(selector); + const element = await this.find(selector, timeout); return await element.isDisplayed(); } diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 1f022c890b724..1bdc1b070dc33 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -304,6 +304,27 @@ export class DataGridService extends FtrService { await this.testSubjects.click('gridEditFieldButton'); } + public async clickGridSettings() { + await this.testSubjects.click('dataGridDisplaySelectorButton'); + } + + public async getCurrentRowHeightValue() { + const buttonGroup = await this.testSubjects.find('rowHeightButtonGroup'); + return ( + await buttonGroup.findByCssSelector('.euiButtonGroupButton-isSelected') + ).getVisibleText(); + } + + public async changeRowHeightValue(newValue: string) { + const buttonGroup = await this.testSubjects.find('rowHeightButtonGroup'); + const option = await buttonGroup.findByCssSelector(`[data-text="${newValue}"]`); + await option.click(); + } + + public async resetRowHeightValue() { + await this.testSubjects.click('resetDisplaySelector'); + } + public async getDetailsRow(): Promise { const detailRows = await this.getDetailsRows(); return detailRows[0]; diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index 6222405aa6dae..7313187047a18 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -299,6 +299,21 @@ export class InspectorService extends FtrService { return this.testSubjects.find('inspectorRequestDetailResponse'); } + public async getRequest( + codeEditorIndex: number = 0 + ): Promise<{ command: string; body: Record }> { + await (await this.getOpenRequestDetailRequestButton()).click(); + + await this.monacoEditor.waitCodeEditorReady('inspectorRequestCodeViewerContainer'); + const requestString = await this.monacoEditor.getCodeEditorValue(codeEditorIndex); + this.log.debug('Request string from inspector:', requestString); + const openBraceIndex = requestString.indexOf('{'); + return { + command: openBraceIndex >= 0 ? requestString.substring(0, openBraceIndex).trim() : '', + body: openBraceIndex >= 0 ? JSON.parse(requestString.substring(openBraceIndex)) : {}, + }; + } + public async getResponse(): Promise> { await (await this.getOpenRequestDetailResponseButton()).click(); diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index 7c2ecbbda89fa..486d49a3f7c75 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -10,7 +10,12 @@ import expect from '@kbn/expect'; import { FtrService } from '../ftr_provider_context'; type AppName = keyof typeof PREFIX_MAP; -const PREFIX_MAP = { visualize: 'vis', dashboard: 'dashboard', map: 'map' }; +const PREFIX_MAP = { + visualize: 'vis', + dashboard: 'dashboard', + map: 'map', + eventAnnotation: 'eventAnnotation', +}; export class ListingTableService extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); @@ -154,13 +159,43 @@ export class ListingTableService extends FtrService { return visualizationNames; } + /** + * Open the inspect flyout + */ + public async inspectVisualization(index: number = 0) { + const inspectButtons = await this.testSubjects.findAll('inspect-action'); + await inspectButtons[index].click(); + } + + /** + * Edit Visualization title and description in the flyout + */ + public async editVisualizationDetails( + { title, description }: { title?: string; description?: string } = {}, + shouldSave: boolean = true + ) { + if (title) { + await this.testSubjects.setValue('nameInput', title); + } + if (description) { + await this.testSubjects.setValue('descriptionInput', description); + } + if (shouldSave) { + await this.retry.try(async () => { + await this.testSubjects.click('saveButton'); + await this.testSubjects.missingOrFail('flyoutTitle'); + }); + } + } + /** * Returns items count on landing page */ - public async expectItemsCount(appName: AppName, count: number) { + public async expectItemsCount(appName: AppName, count: number, findTimeout?: number) { await this.retry.try(async () => { const elements = await this.find.allByCssSelector( - `[data-test-subj^="${PREFIX_MAP[appName]}ListingTitleLink"]` + `[data-test-subj^="${PREFIX_MAP[appName]}ListingTitleLink"]`, + findTimeout ?? 10000 ); expect(elements.length).to.equal(count); }); diff --git a/test/interpreter_functional/test_suites/run_pipeline/index.ts b/test/interpreter_functional/test_suites/run_pipeline/index.ts index 5287e765c1dab..bebd25c379bee 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/index.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/index.ts @@ -16,7 +16,8 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'header']); - describe('runPipeline', function () { + // Failing: See https://github.com/elastic/kibana/issues/60194 + describe.skip('runPipeline', function () { this.tags(['skipFirefox']); before(async () => { diff --git a/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts b/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts index 1fa902ed6d15a..8d558dafa68f5 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts @@ -39,7 +39,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); }; - describe('application deep links navigation', function describeDeepLinksTests() { + // Failing: See https://github.com/elastic/kibana/issues/166893 + describe.skip('application deep links navigation', function describeDeepLinksTests() { before(async () => { await esArchiver.emptyKibanaIndex(); await PageObjects.common.navigateToApp('dl'); diff --git a/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts index b994e8d60249b..987796be3a6a7 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts @@ -25,7 +25,9 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); - describe('application using leave confirmation', () => { + // Failing: See https://github.com/elastic/kibana/issues/75963 + // Failing: See https://github.com/elastic/kibana/issues/166838 + describe.skip('application using leave confirmation', () => { describe('when navigating to another app', () => { it('prevents navigation if user click cancel on the confirmation dialog', async () => { await PageObjects.common.navigateToApp('appleave1'); diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index df4ac37b96464..22ad0b8372900 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -49,11 +49,12 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const navigateTo = async (path: string) => await browser.navigateTo(`${deployment.getHostPort()}${path}`); - // FLAKY: https://github.com/elastic/kibana/issues/127545 + // Failing: See https://github.com/elastic/kibana/issues/166677 describe.skip('ui applications', function describeIndexTests() { before(async () => { await esArchiver.emptyKibanaIndex(); await PageObjects.common.navigateToApp('foo'); + await PageObjects.common.dismissBanner(); }); it('starts on home page', async () => { @@ -126,7 +127,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide expect(await testSubjects.exists('headerGlobalNav')).to.be(false); const wrapperHeight = await getAppWrapperHeight(); - const windowHeight = (await browser.getWindowSize()).height; + const windowHeight = (await browser.getWindowInnerSize()).height; expect(wrapperHeight).to.eql(windowHeight); }); @@ -136,7 +137,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide expect(await testSubjects.exists('headerGlobalNav')).to.be(true); const wrapperHeight = await getAppWrapperHeight(); - const windowHeight = (await browser.getWindowSize()).height; + const windowHeight = (await browser.getWindowInnerSize()).height; expect(wrapperHeight).to.be.below(windowHeight); }); }); diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 49951b9a3100a..f3f6d8b8ac4e9 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -230,6 +230,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.cloud.projects_url (any)', // It's a string (any because schema.conditional) // can't be used to infer urls or customer id from the outside 'xpack.cloud.serverless.project_id (string)', + 'xpack.cloud.serverless.project_name (string)', 'xpack.discoverEnhanced.actions.exploreDataInChart.enabled (boolean)', 'xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled (boolean)', 'xpack.fleet.agents.enabled (boolean)', @@ -237,6 +238,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.fleet.internal.activeAgentsSoftLimit (number)', 'xpack.fleet.internal.disableProxies (boolean)', 'xpack.fleet.internal.fleetServerStandalone (boolean)', + 'xpack.fleet.internal.onlyAllowAgentUpgradeToKnownVersions (boolean)', 'xpack.fleet.developer.maxAgentPoliciesWithInactivityTimeout (number)', 'xpack.global_search.search_timeout (duration)', 'xpack.graph.canEditDrillDownUrls (boolean)', diff --git a/test/scripts/jenkins_fleet_cypress.sh b/test/scripts/jenkins_fleet_cypress.sh index a6d9557812374..e43259c1c1c3f 100755 --- a/test/scripts/jenkins_fleet_cypress.sh +++ b/test/scripts/jenkins_fleet_cypress.sh @@ -5,10 +5,8 @@ source test/scripts/jenkins_test_setup_xpack.sh echo " -> Running fleet cypress tests" cd "$XPACK_DIR" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --config test/fleet_cypress/cli_config.ts +cd x-pack/plugins/fleet +yarn --cwd x-pack/plugins/fleet cypress:run echo "" echo "" diff --git a/test/tsconfig.json b/test/tsconfig.json index 56d4f185930e7..d71c7f9c8ffb1 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -68,6 +68,7 @@ "@kbn/core-saved-objects-server", "@kbn/discover-plugin", "@kbn/core-http-common", - "@kbn/event-annotation-plugin" + "@kbn/event-annotation-plugin", + "@kbn/event-annotation-common" ] } diff --git a/tsconfig.base.json b/tsconfig.base.json index d61f37117abcc..130c6e525fefc 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -588,6 +588,8 @@ "@kbn/crypto-browser/*": ["packages/kbn-crypto-browser/*"], "@kbn/custom-branding-plugin": ["x-pack/plugins/custom_branding"], "@kbn/custom-branding-plugin/*": ["x-pack/plugins/custom_branding/*"], + "@kbn/custom-integrations": ["packages/kbn-custom-integrations"], + "@kbn/custom-integrations/*": ["packages/kbn-custom-integrations/*"], "@kbn/custom-integrations-plugin": ["src/plugins/custom_integrations"], "@kbn/custom-integrations-plugin/*": ["src/plugins/custom_integrations/*"], "@kbn/cypress-config": ["packages/kbn-cypress-config"], @@ -720,6 +722,8 @@ "@kbn/event-annotation-common/*": ["packages/kbn-event-annotation-common/*"], "@kbn/event-annotation-components": ["packages/kbn-event-annotation-components"], "@kbn/event-annotation-components/*": ["packages/kbn-event-annotation-components/*"], + "@kbn/event-annotation-listing-plugin": ["src/plugins/event_annotation_listing"], + "@kbn/event-annotation-listing-plugin/*": ["src/plugins/event_annotation_listing/*"], "@kbn/event-annotation-plugin": ["src/plugins/event_annotation"], "@kbn/event-annotation-plugin/*": ["src/plugins/event_annotation/*"], "@kbn/event-log-fixture-plugin": ["x-pack/test/plugin_api_integration/plugins/event_log"], @@ -954,8 +958,20 @@ "@kbn/management-cards-navigation/*": ["packages/kbn-management/cards_navigation/*"], "@kbn/management-plugin": ["src/plugins/management"], "@kbn/management-plugin/*": ["src/plugins/management/*"], + "@kbn/management-settings-components-field-input": ["packages/kbn-management/settings/components/field_input"], + "@kbn/management-settings-components-field-input/*": ["packages/kbn-management/settings/components/field_input/*"], + "@kbn/management-settings-components-field-row": ["packages/kbn-management/settings/components/field_row"], + "@kbn/management-settings-components-field-row/*": ["packages/kbn-management/settings/components/field_row/*"], + "@kbn/management-settings-field-definition": ["packages/kbn-management/settings/field_definition"], + "@kbn/management-settings-field-definition/*": ["packages/kbn-management/settings/field_definition/*"], + "@kbn/management-settings-ids": ["packages/kbn-management/settings/setting_ids"], + "@kbn/management-settings-ids/*": ["packages/kbn-management/settings/setting_ids/*"], "@kbn/management-settings-section-registry": ["packages/kbn-management/settings/section_registry"], "@kbn/management-settings-section-registry/*": ["packages/kbn-management/settings/section_registry/*"], + "@kbn/management-settings-types": ["packages/kbn-management/settings/types"], + "@kbn/management-settings-types/*": ["packages/kbn-management/settings/types/*"], + "@kbn/management-settings-utilities": ["packages/kbn-management/settings/utilities"], + "@kbn/management-settings-utilities/*": ["packages/kbn-management/settings/utilities/*"], "@kbn/management-storybook-config": ["packages/kbn-management/storybook/config"], "@kbn/management-storybook-config/*": ["packages/kbn-management/storybook/config/*"], "@kbn/management-test-plugin": ["test/plugin_functional/plugins/management_test_plugin"], @@ -970,6 +986,8 @@ "@kbn/maps-plugin/*": ["x-pack/plugins/maps/*"], "@kbn/maps-vector-tile-utils": ["x-pack/packages/maps/vector_tile_utils"], "@kbn/maps-vector-tile-utils/*": ["x-pack/packages/maps/vector_tile_utils/*"], + "@kbn/metrics-data-access-plugin": ["x-pack/plugins/metrics_data_access"], + "@kbn/metrics-data-access-plugin/*": ["x-pack/plugins/metrics_data_access/*"], "@kbn/ml-agg-utils": ["x-pack/packages/ml/agg_utils"], "@kbn/ml-agg-utils/*": ["x-pack/packages/ml/agg_utils/*"], "@kbn/ml-anomaly-utils": ["x-pack/packages/ml/anomaly_utils"], @@ -1086,8 +1104,12 @@ "@kbn/preboot-example-plugin/*": ["examples/preboot_example/*"], "@kbn/presentation-util-plugin": ["src/plugins/presentation_util"], "@kbn/presentation-util-plugin/*": ["src/plugins/presentation_util/*"], + "@kbn/profiling-data-access-plugin": ["x-pack/plugins/profiling_data_access"], + "@kbn/profiling-data-access-plugin/*": ["x-pack/plugins/profiling_data_access/*"], "@kbn/profiling-plugin": ["x-pack/plugins/profiling"], "@kbn/profiling-plugin/*": ["x-pack/plugins/profiling/*"], + "@kbn/profiling-utils": ["packages/kbn-profiling-utils"], + "@kbn/profiling-utils/*": ["packages/kbn-profiling-utils/*"], "@kbn/random-sampling": ["x-pack/packages/kbn-random-sampling"], "@kbn/random-sampling/*": ["x-pack/packages/kbn-random-sampling/*"], "@kbn/react-field": ["packages/kbn-react-field"], @@ -1186,6 +1208,8 @@ "@kbn/screenshotting-plugin/*": ["x-pack/plugins/screenshotting/*"], "@kbn/search-api-panels": ["packages/kbn-search-api-panels"], "@kbn/search-api-panels/*": ["packages/kbn-search-api-panels/*"], + "@kbn/search-connectors": ["packages/kbn-search-connectors"], + "@kbn/search-connectors/*": ["packages/kbn-search-connectors/*"], "@kbn/search-examples-plugin": ["examples/search_examples"], "@kbn/search-examples-plugin/*": ["examples/search_examples/*"], "@kbn/search-response-warnings": ["packages/kbn-search-response-warnings"], @@ -1258,12 +1282,20 @@ "@kbn/server-route-repository/*": ["packages/kbn-server-route-repository/*"], "@kbn/serverless": ["x-pack/plugins/serverless"], "@kbn/serverless/*": ["x-pack/plugins/serverless/*"], + "@kbn/serverless-common-settings": ["packages/serverless/settings/common"], + "@kbn/serverless-common-settings/*": ["packages/serverless/settings/common/*"], "@kbn/serverless-observability": ["x-pack/plugins/serverless_observability"], "@kbn/serverless-observability/*": ["x-pack/plugins/serverless_observability/*"], + "@kbn/serverless-observability-settings": ["packages/serverless/settings/observability_project"], + "@kbn/serverless-observability-settings/*": ["packages/serverless/settings/observability_project/*"], "@kbn/serverless-project-switcher": ["packages/serverless/project_switcher"], "@kbn/serverless-project-switcher/*": ["packages/serverless/project_switcher/*"], "@kbn/serverless-search": ["x-pack/plugins/serverless_search"], "@kbn/serverless-search/*": ["x-pack/plugins/serverless_search/*"], + "@kbn/serverless-search-settings": ["packages/serverless/settings/search_project"], + "@kbn/serverless-search-settings/*": ["packages/serverless/settings/search_project/*"], + "@kbn/serverless-security-settings": ["packages/serverless/settings/security_project"], + "@kbn/serverless-security-settings/*": ["packages/serverless/settings/security_project/*"], "@kbn/serverless-storybook-config": ["packages/serverless/storybook/config"], "@kbn/serverless-storybook-config/*": ["packages/serverless/storybook/config/*"], "@kbn/serverless-types": ["packages/serverless/types"], @@ -1412,6 +1444,8 @@ "@kbn/stdio-dev-helpers/*": ["packages/kbn-stdio-dev-helpers/*"], "@kbn/storybook": ["packages/kbn-storybook"], "@kbn/storybook/*": ["packages/kbn-storybook/*"], + "@kbn/subscription-tracking": ["packages/kbn-subscription-tracking"], + "@kbn/subscription-tracking/*": ["packages/kbn-subscription-tracking/*"], "@kbn/synthetics-plugin": ["x-pack/plugins/synthetics"], "@kbn/synthetics-plugin/*": ["x-pack/plugins/synthetics/*"], "@kbn/task-manager-fixture-plugin": ["x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture"], @@ -1496,6 +1530,8 @@ "@kbn/ui-shared-deps-src/*": ["packages/kbn-ui-shared-deps-src/*"], "@kbn/ui-theme": ["packages/kbn-ui-theme"], "@kbn/ui-theme/*": ["packages/kbn-ui-theme/*"], + "@kbn/unified-data-table": ["packages/kbn-unified-data-table"], + "@kbn/unified-data-table/*": ["packages/kbn-unified-data-table/*"], "@kbn/unified-doc-viewer": ["packages/kbn-unified-doc-viewer"], "@kbn/unified-doc-viewer/*": ["packages/kbn-unified-doc-viewer/*"], "@kbn/unified-doc-viewer-examples": ["examples/unified_doc_viewer"], @@ -1580,6 +1616,8 @@ "@kbn/web-worker-stub/*": ["packages/kbn-web-worker-stub/*"], "@kbn/whereis-pkg-cli": ["packages/kbn-whereis-pkg-cli"], "@kbn/whereis-pkg-cli/*": ["packages/kbn-whereis-pkg-cli/*"], + "@kbn/xstate-utils": ["packages/kbn-xstate-utils"], + "@kbn/xstate-utils/*": ["packages/kbn-xstate-utils/*"], "@kbn/yarn-lock-validator": ["packages/kbn-yarn-lock-validator"], "@kbn/yarn-lock-validator/*": ["packages/kbn-yarn-lock-validator/*"], // END AUTOMATED PACKAGE LISTING diff --git a/typings/@hello-pangea/dnd/index.d.ts b/typings/@hello-pangea/dnd/index.d.ts new file mode 100644 index 0000000000000..242c3278b8a4b --- /dev/null +++ b/typings/@hello-pangea/dnd/index.d.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PreDragActions } from '@hello-pangea/dnd'; + +declare module '@hello-pangea/dnd' { + export * from '@hello-pangea/dnd'; + + // TODO: This type can be imported directly from @hello-pangea/dnd once on v16.3.0 (which requires Webpack v5) + // and this entire file/folder can be deleted at that point + export type FluidDragActions = ReturnType; +} diff --git a/versions.json b/versions.json index aeaf855d329d7..c1f94aa9f8e37 100644 --- a/versions.json +++ b/versions.json @@ -8,19 +8,13 @@ "currentMinor": true }, { - "version": "8.10.0", + "version": "8.10.3", "branch": "8.10", "currentMajor": true, "previousMinor": true }, { - "version": "8.9.2", - "branch": "8.9", - "currentMajor": true, - "previousMinor": false - }, - { - "version": "7.17.13", + "version": "7.17.14", "branch": "7.17", "previousMajor": true } diff --git a/x-pack/examples/alerting_example/public/components/page.tsx b/x-pack/examples/alerting_example/public/components/page.tsx index 2e27e6db00014..da20349a2a879 100644 --- a/x-pack/examples/alerting_example/public/components/page.tsx +++ b/x-pack/examples/alerting_example/public/components/page.tsx @@ -10,13 +10,12 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiPageBody, - EuiPageContent_Deprecated as EuiPageContent, - EuiPageContentBody_Deprecated as EuiPageContentBody, EuiPageHeader, EuiPageHeaderSection, EuiTitle, EuiBreadcrumbs, EuiSpacer, + EuiPageSection, } from '@elastic/eui'; type PageProps = RouteComponentProps & { @@ -54,9 +53,7 @@ export const Page = withRouter(({ title, crumb, children, isHome = false, histor - - {children} - + {children} ); }); diff --git a/x-pack/examples/reporting_example/public/containers/capture_test.tsx b/x-pack/examples/reporting_example/public/containers/capture_test.tsx index c74e65df3df79..5ff5a1d6a7a03 100644 --- a/x-pack/examples/reporting_example/public/containers/capture_test.tsx +++ b/x-pack/examples/reporting_example/public/containers/capture_test.tsx @@ -19,8 +19,7 @@ import { EuiPage, EuiPageHeader, EuiPageBody, - EuiPageContent_Deprecated as EuiPageContent, - EuiPageContentBody_Deprecated as EuiPageContentBody, + EuiPageSection, } from '@elastic/eui'; import { TestImageA } from '../components'; @@ -62,7 +61,7 @@ export const CaptureTest: FunctionComponent = () => { return ( - + @@ -75,16 +74,16 @@ export const CaptureTest: FunctionComponent = () => { - - - tab.id === tabToRender) : undefined - } - /> - - + + + + tab.id === tabToRender) : undefined + } + /> + ); diff --git a/x-pack/examples/triggers_actions_ui_example/public/components/page.tsx b/x-pack/examples/triggers_actions_ui_example/public/components/page.tsx index e59d04ffe085e..4a16528bc2d06 100644 --- a/x-pack/examples/triggers_actions_ui_example/public/components/page.tsx +++ b/x-pack/examples/triggers_actions_ui_example/public/components/page.tsx @@ -8,16 +8,7 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; -import { - EuiPageContent_Deprecated as EuiPageContent, - EuiPageContentBody_Deprecated as EuiPageContentBody, - EuiPageBody, - EuiPageHeader, - EuiPageHeaderSection, - EuiTitle, - EuiBreadcrumbs, - EuiSpacer, -} from '@elastic/eui'; +import { EuiPageTemplate, EuiTitle, EuiBreadcrumbs } from '@elastic/eui'; interface PageProps { title: string; @@ -48,19 +39,14 @@ export const Page: React.FC = (props) => { } return ( - - - - -

    {title}

    -
    -
    -
    - - - - {children} - -
    + + + +

    {title}

    +
    + +
    + {children} +
    ); }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx index c8624e365419d..511b5aa585af0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx @@ -7,9 +7,9 @@ import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; -import { HttpSetup } from '@kbn/core-http-browser'; -import type { Message } from '../assistant_context/types'; -import { Conversation } from '../assistant_context/types'; +import { HttpSetup, IHttpFetchError } from '@kbn/core-http-browser'; + +import type { Conversation, Message } from '../assistant_context/types'; import { API_ERROR } from './translations'; import { MODEL_GPT_3_5_TURBO } from '../connectorland/models/model_selector/model_selector'; @@ -86,3 +86,121 @@ export const fetchConnectorExecuteAction = async ({ return API_ERROR; } }; + +export interface GetKnowledgeBaseStatusParams { + http: HttpSetup; + resource?: string; + signal?: AbortSignal | undefined; +} + +export interface GetKnowledgeBaseStatusResponse { + elser_exists: boolean; + esql_exists?: boolean; + index_exists: boolean; + pipeline_exists: boolean; +} + +/** + * API call for getting the status of the Knowledge Base. Provide + * a resource to include the status of that specific resource. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {string} [options.resource] - Resource to get the status of, otherwise status of overall KB + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {Promise} + */ +export const getKnowledgeBaseStatus = async ({ + http, + resource, + signal, +}: GetKnowledgeBaseStatusParams): Promise => { + try { + const path = `/internal/elastic_assistant/knowledge_base/${resource || ''}`; + const response = await http.fetch(path, { + method: 'GET', + signal, + }); + + return response as GetKnowledgeBaseStatusResponse; + } catch (error) { + return error as IHttpFetchError; + } +}; + +export interface PostKnowledgeBaseParams { + http: HttpSetup; + resource?: string; + signal?: AbortSignal | undefined; +} + +export interface PostKnowledgeBaseResponse { + success: boolean; +} + +/** + * API call for setting up the Knowledge Base. Provide a resource to set up a specific resource. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {string} [options.resource] - Resource to be added to the KB, otherwise sets up the base KB + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {Promise} + */ +export const postKnowledgeBase = async ({ + http, + resource, + signal, +}: PostKnowledgeBaseParams): Promise => { + try { + const path = `/internal/elastic_assistant/knowledge_base/${resource || ''}`; + const response = await http.fetch(path, { + method: 'POST', + signal, + }); + + return response as PostKnowledgeBaseResponse; + } catch (error) { + return error as IHttpFetchError; + } +}; + +export interface DeleteKnowledgeBaseParams { + http: HttpSetup; + resource?: string; + signal?: AbortSignal | undefined; +} + +export interface DeleteKnowledgeBaseResponse { + success: boolean; +} + +/** + * API call for deleting the Knowledge Base. Provide a resource to delete that specific resource. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {string} [options.resource] - Resource to be deleted from the KB, otherwise delete the entire KB + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {Promise} + */ +export const deleteKnowledgeBase = async ({ + http, + resource, + signal, +}: DeleteKnowledgeBaseParams): Promise => { + try { + const path = `/internal/elastic_assistant/knowledge_base/${resource || ''}`; + const response = await http.fetch(path, { + method: 'DELETE', + signal, + }); + + return response as DeleteKnowledgeBaseResponse; + } catch (error) { + return error as IHttpFetchError; + } +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx index ff96f8b66c92b..972d3d9099cd0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx @@ -23,7 +23,7 @@ describe('AssistantOverlay', () => { it('renders when isAssistantEnabled prop is true and keyboard shortcut is pressed', () => { const { getByTestId } = render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -34,7 +34,7 @@ describe('AssistantOverlay', () => { it('modal closes when close button is clicked', () => { const { getByLabelText, queryByTestId } = render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -47,7 +47,7 @@ describe('AssistantOverlay', () => { it('Assistant invoked from shortcut tracking happens on modal open only (not close)', () => { render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -63,7 +63,7 @@ describe('AssistantOverlay', () => { it('modal closes when shortcut is pressed and modal is already open', () => { const { queryByTestId } = render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -75,7 +75,7 @@ describe('AssistantOverlay', () => { it('modal does not open when incorrect shortcut is pressed', () => { const { queryByTestId } = render( - + ); fireEvent.keyDown(document, { key: 'a', ctrlKey: true }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx index 43635fba95df5..ac72fc27dd891 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx @@ -22,15 +22,12 @@ const StyledEuiModal = styled(EuiModal)` min-width: 95vw; min-height: 25vh; `; -interface Props { - isAssistantEnabled: boolean; -} /** * Modal container for Elastic AI Assistant conversations, receiving the page contents as context, plus whatever * component currently has focus and any specific context it may provide through the SAssInterface. */ -export const AssistantOverlay = React.memo(({ isAssistantEnabled }) => { +export const AssistantOverlay = React.memo(() => { const [isModalVisible, setIsModalVisible] = useState(false); const [conversationId, setConversationId] = useState( WELCOME_CONVERSATION_TITLE @@ -103,11 +100,7 @@ export const AssistantOverlay = React.memo(({ isAssistantEnabled }) => { <> {isModalVisible && ( - + )} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts index ede5fd879664f..165dc20ef6f12 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts @@ -17,7 +17,7 @@ export const SETTINGS_TITLE = i18n.translate( export const SETTINGS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.conversations.settings.settingsDescription', { - defaultMessage: 'Create and manage conversations with the Elastic AI Assistant', + defaultMessage: 'Create and manage conversations with the Elastic AI Assistant.', } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx index fcd203ce0cd97..acb41a9575581 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx @@ -22,7 +22,7 @@ import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations'; import { useLocalStorage } from 'react-use'; import { PromptEditor } from './prompt_editor'; import { QuickPrompts } from './quick_prompts/quick_prompts'; -import { TestProviders } from '../mock/test_providers/test_providers'; +import { mockAssistantAvailability, TestProviders } from '../mock/test_providers/test_providers'; jest.mock('../connectorland/use_load_connectors'); jest.mock('../connectorland/connector_setup'); @@ -46,10 +46,10 @@ const getInitialConversations = (): Record => ({ }, }); -const renderAssistant = (extraProps = {}) => +const renderAssistant = (extraProps = {}, providerProps = {}) => render( - - + + ); @@ -110,9 +110,10 @@ describe('Assistant', () => { data: connectors, } as unknown as UseQueryResult); - const { getByLabelText } = render( - ({ + const { getByLabelText } = renderAssistant( + {}, + { + getInitialConversations: () => ({ [WELCOME_CONVERSATION_TITLE]: { id: WELCOME_CONVERSATION_TITLE, messages: [], @@ -124,10 +125,8 @@ describe('Assistant', () => { apiConfig: {}, excludeFromLastConversationStorage: true, }, - })} - > - - + }), + } ); expect(persistToLocalStorage).toHaveBeenCalled(); @@ -176,4 +175,16 @@ describe('Assistant', () => { expect(persistToLocalStorage).toHaveBeenLastCalledWith(WELCOME_CONVERSATION_TITLE); }); }); + + describe('when not authorized', () => { + it('should be disabled', async () => { + const { queryByTestId } = renderAssistant( + {}, + { + assistantAvailability: { ...mockAssistantAvailability, isAssistantEnabled: false }, + } + ); + expect(queryByTestId('prompt-textarea')).toHaveProperty('disabled'); + }); + }); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx index 4ea1ed240870d..d119526a198c5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx @@ -51,7 +51,6 @@ import { ConnectorMissingCallout } from '../connectorland/connector_missing_call export interface Props { conversationId?: string; - isAssistantEnabled: boolean; promptContextId?: string; shouldRefocusPrompt?: boolean; showTitle?: boolean; @@ -64,7 +63,6 @@ export interface Props { */ const AssistantComponent: React.FC = ({ conversationId, - isAssistantEnabled, promptContextId = '', shouldRefocusPrompt = false, showTitle = true, @@ -73,6 +71,7 @@ const AssistantComponent: React.FC = ({ const { assistantTelemetry, augmentMessageCodeBlocks, + assistantAvailability: { isAssistantEnabled }, conversations, defaultAllow, defaultAllowReplacement, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts index 99852b06e0562..06544b98a68bd 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts @@ -17,7 +17,7 @@ export const SETTINGS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.settingsDescription', { defaultMessage: - 'Create and manage System Prompts. System Prompts are configurable chunks of context that are always sent for a given conversations.', + 'Create and manage System Prompts. System Prompts are configurable chunks of context that are always sent for a given conversation.', } ); export const ADD_SYSTEM_PROMPT_MODAL_TITLE = i18n.translate( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx index ded138fcc2b62..4474973b3ce62 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx @@ -5,19 +5,130 @@ * 2.0. */ -import React from 'react'; -import { EuiFormRow, EuiTitle, EuiText, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { + EuiFormRow, + EuiTitle, + EuiText, + EuiTextColor, + EuiHorizontalRule, + EuiLoadingSpinner, + EuiSpacer, + EuiSwitch, + EuiToolTip, + EuiSwitchEvent, + EuiLink, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import * as i18n from './translations'; +import { useKnowledgeBaseStatus } from '../../../knowledge_base/use_knowledge_base_status/use_knowledge_base_status'; +import { useAssistantContext } from '../../../assistant_context'; +import { useSetupKnowledgeBase } from '../../../knowledge_base/use_setup_knowledge_base/use_setup_knowledge_base'; +import { useDeleteKnowledgeBase } from '../../../knowledge_base/use_delete_knowledge_base/use_delete_knowledge_base'; +const ESQL_RESOURCE = 'esql'; interface Props { onAdvancedSettingsChange?: () => void; } /** - * Advanced Settings -- your catch-all container for settings that don't have a home elsewhere. + * Advanced Settings -- enable and disable LangChain integration, Knowledge Base, and ESQL KB Documents */ export const AdvancedSettings: React.FC = React.memo(({ onAdvancedSettingsChange }) => { + const { http, assistantLangChain } = useAssistantContext(); + const { + data: kbStatus, + isLoading, + isFetching, + } = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE }); + const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http }); + const { mutate: deleteKB, isLoading: isDeletingUpKB } = useDeleteKnowledgeBase({ http }); + + const [isLangChainEnabled, setIsLangChainEnabled] = useState(assistantLangChain); + const isKnowledgeBaseEnabled = + (kbStatus?.index_exists && kbStatus?.pipeline_exists && kbStatus?.elser_exists) ?? false; + const isESQLEnabled = kbStatus?.esql_exists ?? false; + + const isLoadingKb = isLoading || isFetching || isSettingUpKB || isDeletingUpKB; + const isKnowledgeBaseAvailable = isLangChainEnabled && kbStatus?.elser_exists; + const isESQLAvailable = isLangChainEnabled && isKnowledgeBaseAvailable && isKnowledgeBaseEnabled; + + const onEnableKnowledgeBaseChange = useCallback( + (event: EuiSwitchEvent) => { + if (event.target.checked) { + setupKB(); + } else { + deleteKB(); + } + }, + [deleteKB, setupKB] + ); + + const onEnableESQLChange = useCallback( + (event: EuiSwitchEvent) => { + if (event.target.checked) { + setupKB(ESQL_RESOURCE); + } else { + deleteKB(ESQL_RESOURCE); + } + }, + [deleteKB, setupKB] + ); + + const langchainSwitch = useMemo(() => { + return ( + setIsLangChainEnabled(!isLangChainEnabled)} + showLabel={false} + /> + ); + }, [isLangChainEnabled]); + + const knowledgeBaseSwitch = useMemo(() => { + return isLoadingKb ? ( + + ) : ( + + + + ); + }, [isLoadingKb, isKnowledgeBaseAvailable, isKnowledgeBaseEnabled, onEnableKnowledgeBaseChange]); + + const esqlSwitch = useMemo(() => { + return isLoadingKb ? ( + + ) : ( + + + + ); + }, [isLoadingKb, isESQLAvailable, isESQLEnabled, onEnableESQLChange]); + return ( <> @@ -29,15 +140,52 @@ export const AdvancedSettings: React.FC = React.memo(({ onAdvancedSetting - - <>{'Disable LocalStorage'} + + {langchainSwitch} - - <>{'Clear LocalStorage'} + + {i18n.LANNGCHAIN_DESCRIPTION} + + + + {knowledgeBaseSwitch} - - <>{'Reset Something Else'} + + + + {i18n.KNOWLEDGE_BASE_DESCRIPTION_ELSER_LEARN_MORE} + + ), + }} + /> + + + + + {esqlSwitch} + + {i18n.ESQL_DESCRIPTION} + ); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts index 64642e97b8cc5..ca849f0a6f7c5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts @@ -16,6 +16,64 @@ export const SETTINGS_TITLE = i18n.translate( export const SETTINGS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.advancedSettings.settingsDescription', { - defaultMessage: "They're not further along, they just have a different set of problems.", + defaultMessage: 'Additional knobs and dials for the Elastic AI Assistant.', + } +); + +export const LANNGCHAIN_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.langChainLabel', + { + defaultMessage: 'Experimental LangChain Integration', + } +); + +export const LANNGCHAIN_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.langChainDescription', + { + defaultMessage: + 'Enables advanced features and workflows like the Knowledge Base, Functions, Memories, and advanced agent and chain configurations. ', + } +); + +export const KNOWLEDGE_BASE_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseLabel', + { + defaultMessage: 'Knowledge Base', + } +); + +export const KNOWLEDGE_BASE_LABEL_TOOLTIP = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseLabelTooltip', + { + defaultMessage: 'Requires ELSER to be configured and started.', + } +); + +export const KNOWLEDGE_BASE_DESCRIPTION_ELSER_LEARN_MORE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseElserLearnMoreDescription', + { + defaultMessage: 'Learn more.', + } +); + +export const ESQL_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlLabel', + { + defaultMessage: 'ES|QL Knowledge Base Documents', + } +); + +export const ESQL_LABEL_TOOLTIP = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlTooltip', + { + defaultMessage: 'Requires `Knowledge Base` to be enabled.', + } +); + +export const ESQL_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlDescription', + { + defaultMessage: + 'Loads ES|QL documentation and language files into the Knowledge Base for use in generating ES|QL queries.', } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx index 96a4de67e171c..23712d8e74402 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx @@ -44,7 +44,6 @@ export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const; export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; -export const FUNCTIONS_TAB = 'FUNCTIONS_TAB' as const; export const ADVANCED_TAB = 'ADVANCED_TAB' as const; export type SettingsTabs = @@ -52,7 +51,6 @@ export type SettingsTabs = | typeof QUICK_PROMPTS_TAB | typeof SYSTEM_PROMPTS_TAB | typeof ANONYMIZATION_TAB - | typeof FUNCTIONS_TAB | typeof ADVANCED_TAB; interface Props { defaultConnectorId?: string; @@ -78,8 +76,13 @@ export const AssistantSettings: React.FC = React.memo( selectedConversation: defaultSelectedConversation, setSelectedConversationId, }) => { - const { actionTypeRegistry, http, selectedSettingsTab, setSelectedSettingsTab } = - useAssistantContext(); + const { + actionTypeRegistry, + assistantLangChain, + http, + selectedSettingsTab, + setSelectedSettingsTab, + } = useAssistantContext(); const { conversationSettings, defaultAllow, @@ -235,6 +238,16 @@ export const AssistantSettings: React.FC = React.memo( > + {assistantLangChain && ( + setSelectedSettingsTab(ADVANCED_TAB)} + > + + + )} @@ -287,7 +300,6 @@ export const AssistantSettings: React.FC = React.memo( setUpdatedDefaultAllowReplacement={setUpdatedDefaultAllowReplacement} /> )} - {selectedSettingsTab === FUNCTIONS_TAB && <>} {selectedSettingsTab === ADVANCED_TAB && } { + const invalidateKnowledgeBaseStatus = useInvalidateKnowledgeBaseStatus(); + return useMutation( + DELETE_KNOWLEDGE_BASE_MUTATION_KEY, + (resource?: string | void) => { + // Optional params workaround: see: https://github.com/TanStack/query/issues/1077#issuecomment-1431247266 + return deleteKnowledgeBase({ http, resource: resource ?? undefined }); + }, + { + onError: (error: IHttpFetchError) => { + if (error.name !== 'AbortError') { + toasts?.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: i18n.translate('xpack.elasticAssistant.knowledgeBase.deleteError', { + defaultMessage: 'Error deleting Knowledge Base', + }), + } + ); + } + }, + onSettled: () => { + invalidateKnowledgeBaseStatus(); + }, + } + ); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status/use_knowledge_base_status.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status/use_knowledge_base_status.tsx new file mode 100644 index 0000000000000..c8849b01bb5b9 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status/use_knowledge_base_status.tsx @@ -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 type { UseQueryResult } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import type { IToasts } from '@kbn/core-notifications-browser'; +import { i18n } from '@kbn/i18n'; +import { useCallback } from 'react'; +import { getKnowledgeBaseStatus } from '../../assistant/api'; + +const KNOWLEDGE_BASE_STATUS_QUERY_KEY = ['elastic-assistant', 'knowledge-base-status']; + +export interface UseKnowledgeBaseStatusParams { + http: HttpSetup; + resource?: string; + toasts?: IToasts; +} + +export interface GetKnowledgeBaseStatusResponse { + elser_exists: boolean; + esql_exists?: boolean; + index_exists: boolean; + pipeline_exists: boolean; +} + +/** + * Hook for getting the status of the Knowledge Base. Provide a resource name to include + * the status for that specific resource within the KB. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {IToasts} [options.toasts] - IToasts + * + * @returns {useQuery} hook for getting the status of the Knowledge Base + */ +export const useKnowledgeBaseStatus = ({ + http, + resource, + toasts, +}: UseKnowledgeBaseStatusParams): UseQueryResult< + GetKnowledgeBaseStatusResponse, + IHttpFetchError +> => { + return useQuery( + KNOWLEDGE_BASE_STATUS_QUERY_KEY, + async ({ signal }) => { + return getKnowledgeBaseStatus({ http, resource, signal }); + }, + { + retry: false, + keepPreviousData: true, + // Deprecated, hoist to `queryCache` w/in `QueryClient. See: https://stackoverflow.com/a/76961109 + onError: (error: IHttpFetchError) => { + if (error.name !== 'AbortError') { + toasts?.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: i18n.translate('xpack.elasticAssistant.knowledgeBase.statusError', { + defaultMessage: 'Error fetching Knowledge Base Status', + }), + } + ); + } + }, + } + ); +}; + +/** + * Use this hook to invalidate the Knowledge Base Status cache. For example, + * Knowledge Base actions setting up, adding resources, or deleting should lead + * to cache invalidation. + * + * @returns {Function} - Function to invalidate the Knowledge Base Status cache + */ +export const useInvalidateKnowledgeBaseStatus = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries(KNOWLEDGE_BASE_STATUS_QUERY_KEY, { + refetchType: 'active', + }); + }, [queryClient]); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base/use_setup_knowledge_base.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base/use_setup_knowledge_base.tsx new file mode 100644 index 0000000000000..67f12cf0e5296 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base/use_setup_knowledge_base.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 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 { useMutation } from '@tanstack/react-query'; +import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import type { IToasts } from '@kbn/core-notifications-browser'; +import { i18n } from '@kbn/i18n'; +import { postKnowledgeBase } from '../../assistant/api'; +import { useInvalidateKnowledgeBaseStatus } from '../use_knowledge_base_status/use_knowledge_base_status'; + +const SETUP_KNOWLEDGE_BASE_MUTATION_KEY = ['elastic-assistant', 'post-knowledge-base']; + +export interface UseSetupKnowledgeBaseParams { + http: HttpSetup; + toasts?: IToasts; +} + +/** + * Hook for setting up the Knowledge Base. Provide a resource name to set + * up a specific part of the KB. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {IToasts} [options.toasts] - IToasts + * + * @returns {useMutation} mutation hook for setting up the Knowledge Base + */ +export const useSetupKnowledgeBase = ({ http, toasts }: UseSetupKnowledgeBaseParams) => { + const invalidateKnowledgeBaseStatus = useInvalidateKnowledgeBaseStatus(); + + return useMutation( + SETUP_KNOWLEDGE_BASE_MUTATION_KEY, + (resource?: string | void) => { + // Optional params workaround: see: https://github.com/TanStack/query/issues/1077#issuecomment-1431247266 + return postKnowledgeBase({ http, resource: resource ?? undefined }); + }, + { + onError: (error: IHttpFetchError) => { + if (error.name !== 'AbortError') { + toasts?.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: i18n.translate('xpack.elasticAssistant.knowledgeBase.setupError', { + defaultMessage: 'Error setting up Knowledge Base', + }), + } + ); + } + }, + onSettled: () => { + invalidateKnowledgeBaseStatus(); + }, + } + ); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index 484dd316cc0ac..057db39f66ba2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -29,7 +29,7 @@ window.HTMLElement.prototype.scrollIntoView = jest.fn(); const mockGetInitialConversations = () => ({}); -const mockAssistantAvailability: AssistantAvailability = { +export const mockAssistantAvailability: AssistantAvailability = { hasAssistantPrivilege: false, hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, diff --git a/x-pack/packages/kbn-elastic-assistant/index.ts b/x-pack/packages/kbn-elastic-assistant/index.ts index 1353db4908006..775b43952495c 100644 --- a/x-pack/packages/kbn-elastic-assistant/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/index.ts @@ -11,7 +11,7 @@ // happens in the root of your app. Optionally provide a custom title for the assistant: /** provides context (from the app) to the assistant, and injects Kibana services, like `http` */ -export { AssistantProvider } from './impl/assistant_context'; +export { AssistantProvider, useAssistantContext } from './impl/assistant_context'; // Step 2: Add the `AssistantOverlay` component to your app. This component displays the assistant // overlay in a modal, bound to a shortcut key: @@ -133,3 +133,10 @@ export type { PromptContextTemplate } from './impl/assistant/prompt_context/type * can be displayed when corresponding PromptContext's are registered. */ export type { QuickPrompt } from './impl/assistant/quick_prompts/types'; + +/** + * Knowledge Base API Responses + */ +export type { DeleteKnowledgeBaseResponse } from './impl/assistant/api'; +export type { GetKnowledgeBaseStatusResponse } from './impl/assistant/api'; +export type { PostKnowledgeBaseResponse } from './impl/assistant/api'; diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts index 17b8e86d9fc5a..706213003250b 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts @@ -158,6 +158,10 @@ const findSLOResponseSchema = t.type({ results: t.array(sloWithSummaryResponseSchema), }); +const deleteSLOInstancesParamsSchema = t.type({ + body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: t.string })) }), +}); + const fetchHistoricalSummaryParamsSchema = t.type({ body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: allOrAnyString })) }), }); @@ -239,6 +243,9 @@ type UpdateSLOResponse = t.OutputOf; type FindSLOParams = t.TypeOf; type FindSLOResponse = t.OutputOf; +type DeleteSLOInstancesInput = t.OutputOf; +type DeleteSLOInstancesParams = t.TypeOf; + type FetchHistoricalSummaryParams = t.TypeOf; type FetchHistoricalSummaryResponse = t.OutputOf; type HistoricalSummaryResponse = t.OutputOf; @@ -269,6 +276,7 @@ type KQLCustomIndicator = t.OutputOf; export { createSLOParamsSchema, deleteSLOParamsSchema, + deleteSLOInstancesParamsSchema, findSLOParamsSchema, findSLOResponseSchema, getPreviewDataParamsSchema, @@ -294,6 +302,8 @@ export type { CreateSLOInput, CreateSLOParams, CreateSLOResponse, + DeleteSLOInstancesInput, + DeleteSLOInstancesParams, FindSLOParams, FindSLOResponse, GetPreviewDataParams, diff --git a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx index ee8922a8e7631..e3e5a1de3d65f 100644 --- a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx +++ b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx @@ -72,6 +72,10 @@ export interface FullTimeRangeSelectorProps { * @param value - The time field range response. */ apiPath?: SetFullTimeRangeApiPath; + /** + * Optional flag to disable the frozen data tier choice. + */ + hideFrozenDataTierChoice?: boolean; } /** @@ -92,10 +96,12 @@ export const FullTimeRangeSelector: FC = (props) => disabled, callback, apiPath, + hideFrozenDataTierChoice = false, } = props; const { http, notifications: { toasts }, + isServerless, } = useDatePickerContext(); // wrapper around setFullTimeRange to allow for the calling of the optional callBack prop @@ -107,7 +113,9 @@ export const FullTimeRangeSelector: FC = (props) => toasts, http, query, - frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE, + isServerless || hideFrozenDataTierChoice + ? false + : frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE, apiPath ); if (typeof callback === 'function' && fullTimeRange !== undefined) { @@ -123,7 +131,18 @@ export const FullTimeRangeSelector: FC = (props) => ) ); } - }, [callback, dataView, frozenDataPreference, http, query, timefilter, toasts, apiPath]); + }, [ + timefilter, + dataView, + toasts, + http, + query, + isServerless, + hideFrozenDataTierChoice, + frozenDataPreference, + apiPath, + callback, + ]); const [isPopoverOpen, setPopover] = useState(false); @@ -210,31 +229,33 @@ export const FullTimeRangeSelector: FC = (props) => />
    - - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - panelPaddingSize="none" - anchorPosition="downRight" - > - {popoverContent} - - + {isServerless || hideFrozenDataTierChoice ? null : ( + + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downRight" + > + {popoverContent} + + + )}
    ); }; diff --git a/x-pack/packages/ml/date_picker/src/hooks/use_date_picker_context.tsx b/x-pack/packages/ml/date_picker/src/hooks/use_date_picker_context.tsx index 28ad6a12de745..60b1c66f95984 100644 --- a/x-pack/packages/ml/date_picker/src/hooks/use_date_picker_context.tsx +++ b/x-pack/packages/ml/date_picker/src/hooks/use_date_picker_context.tsx @@ -44,6 +44,10 @@ export interface DatePickerDependencies { * Internationalisation service */ i18n: I18nStart; + /** + * Optional flag to indicate whether kibana is running in serverless + */ + isServerless?: boolean; } /** diff --git a/x-pack/packages/security-solution/features/src/constants.ts b/x-pack/packages/security-solution/features/src/constants.ts index 2054749d0eabb..c92376fd36209 100644 --- a/x-pack/packages/security-solution/features/src/constants.ts +++ b/x-pack/packages/security-solution/features/src/constants.ts @@ -15,6 +15,9 @@ export const ASSISTANT_FEATURE_ID = 'securitySolutionAssistant' as const; // Same as the plugin id defined by Cloud Security Posture export const CLOUD_POSTURE_APP_ID = 'csp' as const; +// Same as the plugin id defined by Defend for containers (cloud_defend) +export const CLOUD_DEFEND_APP_ID = 'cloudDefend' as const; + /** * Id for the notifications alerting type * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function diff --git a/x-pack/packages/security-solution/features/src/security/kibana_features.ts b/x-pack/packages/security-solution/features/src/security/kibana_features.ts index 34252ec1a35be..f4176dfa53719 100644 --- a/x-pack/packages/security-solution/features/src/security/kibana_features.ts +++ b/x-pack/packages/security-solution/features/src/security/kibana_features.ts @@ -18,7 +18,13 @@ import { THRESHOLD_RULE_TYPE_ID, } from '@kbn/securitysolution-rules'; import type { BaseKibanaFeatureConfig } from '../types'; -import { APP_ID, SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID, CLOUD_POSTURE_APP_ID } from '../constants'; +import { + APP_ID, + SERVER_APP_ID, + LEGACY_NOTIFICATIONS_ID, + CLOUD_POSTURE_APP_ID, + CLOUD_DEFEND_APP_ID, +} from '../constants'; import type { SecurityFeatureParams } from './types'; const SECURITY_RULE_TYPES = [ @@ -44,7 +50,7 @@ export const getSecurityBaseKibanaFeature = ({ ), order: 1100, category: DEFAULT_APP_CATEGORIES.security, - app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], + app: [APP_ID, CLOUD_POSTURE_APP_ID, CLOUD_DEFEND_APP_ID, 'kibana'], catalogue: [APP_ID], management: { insightsAndAlerting: ['triggersActions'], @@ -52,7 +58,7 @@ export const getSecurityBaseKibanaFeature = ({ alerting: SECURITY_RULE_TYPES, privileges: { all: { - app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], + app: [APP_ID, CLOUD_POSTURE_APP_ID, CLOUD_DEFEND_APP_ID, 'kibana'], catalogue: [APP_ID], api: [ APP_ID, @@ -62,6 +68,8 @@ export const getSecurityBaseKibanaFeature = ({ 'rac', 'cloud-security-posture-all', 'cloud-security-posture-read', + 'cloud-defend-all', + 'cloud-defend-read', ], savedObject: { all: ['alert', ...savedObjects], @@ -81,9 +89,9 @@ export const getSecurityBaseKibanaFeature = ({ ui: ['show', 'crud'], }, read: { - app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], + app: [APP_ID, CLOUD_POSTURE_APP_ID, CLOUD_DEFEND_APP_ID, 'kibana'], catalogue: [APP_ID], - api: [APP_ID, 'lists-read', 'rac', 'cloud-security-posture-read'], + api: [APP_ID, 'lists-read', 'rac', 'cloud-security-posture-read', 'cloud-defend-read'], savedObject: { all: [], read: [...savedObjects], diff --git a/x-pack/packages/security-solution/features/src/security/kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/security/kibana_sub_features.ts index 86cbf89f26a6f..d9a090f1313f0 100644 --- a/x-pack/packages/security-solution/features/src/security/kibana_sub_features.ts +++ b/x-pack/packages/security-solution/features/src/security/kibana_sub_features.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { SubFeatureConfig } from '@kbn/features-plugin/common'; import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants'; import { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from '../app_features_privileges'; + import { SecuritySubFeatureId } from '../app_features_keys'; import { APP_ID } from '../constants'; import type { SecurityFeatureParams } from './types'; @@ -320,7 +321,7 @@ const policyManagementSubFeature: SubFeatureConfig = { includeIn: 'none', name: 'All', savedObject: { - all: [], + all: ['policy-settings-protection-updates-note'], read: [], }, ui: ['writePolicyManagement', 'readPolicyManagement'], @@ -332,7 +333,7 @@ const policyManagementSubFeature: SubFeatureConfig = { name: 'Read', savedObject: { all: [], - read: [], + read: ['policy-settings-protection-updates-note'], }, ui: ['readPolicyManagement'], }, diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_icons.tsx b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_icons.tsx index b81b25144fcd0..f2b8247840cac 100644 --- a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_icons.tsx +++ b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_icons.tsx @@ -81,9 +81,7 @@ export const LandingLinkIcon: React.FC = React.memo(functi - - {description} - + {description} {children} diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images.tsx b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images.tsx index 18c8ef07c10cb..20caecd5a4e2c 100644 --- a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images.tsx +++ b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images.tsx @@ -89,7 +89,7 @@ export const LandingLinksImages: React.FC = React.memo( {isBeta && }
    - + {description} diff --git a/x-pack/packages/security-solution/navigation/src/links.tsx b/x-pack/packages/security-solution/navigation/src/links.tsx index 97434162b9879..172d7361a3420 100644 --- a/x-pack/packages/security-solution/navigation/src/links.tsx +++ b/x-pack/packages/security-solution/navigation/src/links.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { HTMLAttributeAnchorTarget } from 'react'; import React, { type MouseEventHandler, type MouseEvent, useCallback } from 'react'; import { EuiButton, EuiLink, type EuiLinkProps } from '@elastic/eui'; import { useGetAppUrl, useNavigateTo } from './navigation'; @@ -13,6 +14,7 @@ export interface BaseLinkProps { id: string; path?: string; urlState?: string; + target?: HTMLAttributeAnchorTarget | undefined; } export type GetLinkUrlProps = BaseLinkProps & { absolute?: boolean }; @@ -26,7 +28,16 @@ export type WrappedLinkProps = BaseLinkProps & { **/ onClick?: MouseEventHandler; }; -export type GetLinkProps = (params: WrappedLinkProps) => LinkProps; +export type GetLinkProps = ( + params: WrappedLinkProps & { + /** + * Optional `overrideNavigation` boolean prop. + * It overrides the default browser navigation action with history navigation using kibana tools. + * It is `true` by default. + **/ + overrideNavigation?: boolean; + } +) => LinkProps; export interface LinkProps { onClick: MouseEventHandler; @@ -60,7 +71,7 @@ export const useGetLinkProps = (): GetLinkProps => { const { navigateTo } = useNavigateTo(); const getLinkProps = useCallback( - ({ id, path, urlState, onClick: onClickProps }) => { + ({ id, path, urlState, onClick: onClickProps, overrideNavigation = true }) => { const url = getLinkUrl({ id, path, urlState }); return { href: url, @@ -71,8 +82,10 @@ export const useGetLinkProps = (): GetLinkProps => { if (onClickProps) { onClickProps(ev); } - ev.preventDefault(); - navigateTo({ url }); + if (overrideNavigation) { + ev.preventDefault(); + navigateTo({ url }); + } }, }; }, @@ -90,7 +103,13 @@ export const withLink = >( ): React.FC & WrappedLinkProps> => React.memo(function WithLink({ id, path, urlState, onClick: _onClick, ...rest }) { const getLink = useGetLinkProps(); - const { onClick, href } = getLink({ id, path, urlState, onClick: _onClick }); + const { onClick, href } = getLink({ + id, + path, + urlState, + onClick: _onClick, + ...(rest.target === '_blank' && { overrideNavigation: false }), + }); return ; }); diff --git a/x-pack/packages/security-solution/navigation/src/navigation.test.ts b/x-pack/packages/security-solution/navigation/src/navigation.test.ts index 2875f79956332..ab9ab891aaef8 100644 --- a/x-pack/packages/security-solution/navigation/src/navigation.test.ts +++ b/x-pack/packages/security-solution/navigation/src/navigation.test.ts @@ -7,6 +7,7 @@ import { useGetAppUrl, useNavigateTo } from './navigation'; import { mockGetUrlForApp, mockNavigateToApp, mockNavigateToUrl } from '../mocks/context'; import { renderHook } from '@testing-library/react-hooks'; +import { fireEvent } from '@testing-library/dom'; jest.mock('./context'); @@ -61,5 +62,22 @@ describe('yourFile', () => { expect(mockNavigateToApp).not.toHaveBeenCalled(); expect(mockNavigateToUrl).toHaveBeenCalledWith(URL); }); + + it('navigates restoring the scroll', async () => { + const { result } = renderHook(useNavigateTo); + const { navigateTo } = result.current; + + const currentScrollY = 100; + window.scrollY = currentScrollY; + window.scrollTo = jest.fn(); + + navigateTo({ url: URL, restoreScroll: true }); + + // Simulates the browser scroll reset event + fireEvent(window, new Event('scroll')); + + expect(window.scrollTo).toHaveBeenCalledTimes(1); + expect(window.scrollTo).toHaveBeenCalledWith(0, currentScrollY); + }); }); }); diff --git a/x-pack/packages/security-solution/navigation/src/navigation.ts b/x-pack/packages/security-solution/navigation/src/navigation.ts index bb8e6ae5a6e4e..7474baf1fd3ba 100644 --- a/x-pack/packages/security-solution/navigation/src/navigation.ts +++ b/x-pack/packages/security-solution/navigation/src/navigation.ts @@ -34,6 +34,11 @@ export type NavigateTo = ( param: { url?: string; appId?: string; + /** + * Browsers will reset the scroll position to 0 when navigating to a new page. + * This option will prevent that from happening. + */ + restoreScroll?: boolean; } & NavigateToAppOptions ) => void; /** @@ -44,7 +49,10 @@ export const useNavigateTo = () => { const { navigateToApp, navigateToUrl } = useNavigationContext().application; const navigateTo = useCallback( - ({ url, appId = SECURITY_UI_APP_ID, ...options }) => { + ({ url, appId = SECURITY_UI_APP_ID, restoreScroll, ...options }) => { + if (restoreScroll) { + addScrollRestoration(); + } if (url) { navigateToUrl(url); } else { @@ -56,6 +64,16 @@ export const useNavigateTo = () => { return { navigateTo }; }; +/** + * Expects the browser scroll reset event to be fired after the navigation, + * then restores the previous scroll position. + */ +const addScrollRestoration = () => { + const scrollY = window.scrollY; + const handler = () => window.scrollTo(0, scrollY); + window.addEventListener('scroll', handler, { once: true }); +}; + /** * Returns `navigateTo` and `getAppUrl` navigation hooks */ diff --git a/x-pack/performance/journeys/tsdb_logs_data_visualizer.ts b/x-pack/performance/journeys/tsdb_logs_data_visualizer.ts new file mode 100644 index 0000000000000..1c398e8511cdc --- /dev/null +++ b/x-pack/performance/journeys/tsdb_logs_data_visualizer.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +export const journey = new Journey({ + kbnArchives: ['test/functional/fixtures/kbn_archiver/kibana_sample_data_logs_tsdb'], + esArchives: ['test/functional/fixtures/es_archiver/kibana_sample_data_logs_tsdb'], +}) + .step('Go to Data Visualizer', async ({ page, kbnUrl, kibanaPage }) => { + await page.goto(kbnUrl.get(`app/ml/datavisualizer`)); + await kibanaPage.waitForHeader(); + await page.waitForSelector(subj('mlDataVisualizerCardIndexData')); + await page.waitForSelector(subj('globalLoadingIndicator-hidden')); + }) + .step('Go to data view selection', async ({ page }) => { + const createButtons = page.locator(subj('mlDataVisualizerSelectIndexButton')); + await createButtons.first().click(); + await page.waitForSelector(subj('savedObjectsFinderTable')); + }) + .step('Go to Index data visualizer', async ({ page, kibanaPage }) => { + await page.click(subj('savedObjectTitlekibana_sample_data_logstsdb')); + await page.click(subj('mlDatePickerButtonUseFullData')); + await kibanaPage.waitForHeader(); + await page.waitForSelector(subj('dataVisualizerTable-loaded'), { timeout: 60000 }); + await page.waitForSelector(subj('globalLoadingIndicator-hidden'), { timeout: 60000 }); + }); diff --git a/x-pack/plugins/actions/docs/openapi/bundled.json b/x-pack/plugins/actions/docs/openapi/bundled.json index 67191da3842d6..f79bb3ca9c0fe 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.json +++ b/x-pack/plugins/actions/docs/openapi/bundled.json @@ -60,6 +60,9 @@ { "$ref": "#/components/schemas/create_connector_request_cases_webhook" }, + { + "$ref": "#/components/schemas/create_connector_request_d3security" + }, { "$ref": "#/components/schemas/create_connector_request_email" }, @@ -120,11 +123,17 @@ } }, "examples": { + "createEmailConnectorRequest": { + "$ref": "#/components/examples/create_email_connector_request" + }, "createIndexConnectorRequest": { "$ref": "#/components/examples/create_index_connector_request" }, "createWebhookConnectorRequest": { "$ref": "#/components/examples/create_webhook_connector_request" + }, + "createXmattersConnectorRequest": { + "$ref": "#/components/examples/create_xmatters_connector_request" } } } @@ -139,11 +148,17 @@ "$ref": "#/components/schemas/connector_response_properties" }, "examples": { + "createEmailConnectorResponse": { + "$ref": "#/components/examples/create_email_connector_response" + }, "createIndexConnectorResponse": { "$ref": "#/components/examples/create_index_connector_response" }, "createWebhookConnectorResponse": { "$ref": "#/components/examples/create_webhook_connector_response" + }, + "createXmattersConnectorResponse": { + "$ref": "#/components/examples/create_xmatters_connector_response" } } } @@ -323,6 +338,9 @@ { "$ref": "#/components/schemas/create_connector_request_cases_webhook" }, + { + "$ref": "#/components/schemas/create_connector_request_d3security" + }, { "$ref": "#/components/schemas/create_connector_request_email" }, @@ -445,6 +463,15 @@ { "$ref": "#/components/schemas/update_connector_request_cases_webhook" }, + { + "$ref": "#/components/schemas/update_connector_request_d3security" + }, + { + "$ref": "#/components/schemas/update_connector_request_email" + }, + { + "$ref": "#/components/schemas/create_connector_request_genai" + }, { "$ref": "#/components/schemas/update_connector_request_index" }, @@ -454,6 +481,9 @@ { "$ref": "#/components/schemas/update_connector_request_opsgenie" }, + { + "$ref": "#/components/schemas/update_connector_request_pagerduty" + }, { "$ref": "#/components/schemas/update_connector_request_resilient" }, @@ -474,6 +504,15 @@ }, { "$ref": "#/components/schemas/update_connector_request_swimlane" + }, + { + "$ref": "#/components/schemas/update_connector_request_teams" + }, + { + "$ref": "#/components/schemas/update_connector_request_webhook" + }, + { + "$ref": "#/components/schemas/update_connector_request_xmatters" } ] }, @@ -1548,17 +1587,141 @@ } } }, + "config_properties_d3security": { + "title": "Connector request properties for a D3 Security connector", + "description": "Defines properties for connectors when type is `.d3security`.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "description": "The D3 Security API request URL. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts.\n" + } + } + }, + "secrets_properties_d3security": { + "title": "Connector secrets properties for a D3 Security connector", + "description": "Defines secrets for connectors when type is `.d3security`.", + "required": [ + "token" + ], + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "The D3 Security token." + } + } + }, + "create_connector_request_d3security": { + "title": "Create D3 Security connector request", + "description": "The connector uses axios to send a POST request to a D3 Security endpoint.\n", + "type": "object", + "required": [ + "config", + "connector_type_id", + "name", + "secrets" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_d3security" + }, + "connector_type_id": { + "type": "string", + "description": "The type of connector.", + "enum": [ + ".d3security" + ], + "example": ".d3security" + }, + "name": { + "type": "string", + "description": "The display name for the connector.", + "example": "my-connector" + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_d3security" + } + } + }, "config_properties_email": { "title": "Connector request properties for an email connector", "description": "Defines properties for connectors when type is `.email`.", + "required": [ + "from" + ], "type": "object", - "additionalProperties": true + "properties": { + "clientId": { + "description": "The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required.\n", + "type": "string", + "nullable": true + }, + "from": { + "description": "The from address for all emails sent by the connector. It must be specified in `user@host-name` format.\n", + "type": "string" + }, + "hasAuth": { + "description": "Specifies whether a user and password are required inside the secrets configuration.\n", + "default": true, + "type": "boolean" + }, + "host": { + "description": "The host name of the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. \n", + "type": "string" + }, + "oauthTokenUrl": { + "type": "string", + "nullable": true + }, + "port": { + "description": "The port to connect to on the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. \n", + "type": "integer" + }, + "secure": { + "description": "Specifies whether the connection to the service provider will use TLS. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored.\n", + "type": "boolean" + }, + "service": { + "description": "The name of the email service.\n", + "type": "string", + "enum": [ + "elastic_cloud", + "exchange_server", + "gmail", + "other", + "outlook365", + "ses" + ] + }, + "tenantId": { + "description": "The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required.\n", + "type": "string", + "nullable": true + } + } }, "secrets_properties_email": { "title": "Connector secrets properties for an email connector", "description": "Defines secrets for connectors when type is `.email`.", "type": "object", - "additionalProperties": true + "properties": { + "clientSecret": { + "type": "string", + "description": "The Microsoft Exchange Client secret for OAuth 2.0 client credentials authentication. It must be URL-encoded. If `service` is `exchange_server`, this property is required.\n" + }, + "password": { + "type": "string", + "description": "The password for HTTP basic authentication. If `hasAuth` is set to `true`, this property is required.\n" + }, + "user": { + "type": "string", + "description": "The username for HTTP basic authentication. If `hasAuth` is set to `true`, this property is required.\n" + } + } }, "create_connector_request_email": { "title": "Create email connector request", @@ -1595,16 +1758,54 @@ "config_properties_genai": { "title": "Connector request properties for a generative AI connector", "description": "Defines properties for connectors when type is `.gen-ai`.", - "type": "object", - "properties": { - "apiProvider": { - "type": "string", - "description": "The OpenAI API provider." + "oneOf": [ + { + "type": "object", + "required": [ + "apiProvider", + "apiUrl" + ], + "properties": { + "apiProvider": { + "type": "string", + "description": "The OpenAI API provider.", + "enum": [ + "Azure OpenAI" + ] + }, + "apiUrl": { + "type": "string", + "description": "The OpenAI API endpoint." + } + } }, - "apiUrl": { - "type": "string", - "description": "The OpenAI API endpoint." + { + "type": "object", + "required": [ + "apiProvider", + "apiUrl" + ], + "properties": { + "apiProvider": { + "type": "string", + "description": "The OpenAI API provider.", + "enum": [ + "OpenAI" + ] + }, + "apiUrl": { + "type": "string", + "description": "The OpenAI API endpoint." + }, + "defaultModel": { + "type": "string", + "description": "The default model to use for requests." + } + } } + ], + "discriminator": { + "propertyName": "apiProvider" } }, "secrets_properties_genai": { @@ -1659,7 +1860,7 @@ "type": "object", "properties": { "executionTimeField": { - "description": "Specifies a field that will contain the time the alert condition was detected.", + "description": "A field that indicates when the document was indexed.", "default": null, "type": "string", "nullable": true @@ -1837,13 +2038,28 @@ "title": "Connector request properties for a PagerDuty connector", "description": "Defines properties for connectors when type is `.pagerduty`.", "type": "object", - "additionalProperties": true + "properties": { + "apiUrl": { + "description": "The PagerDuty event URL.", + "type": "string", + "nullable": true, + "example": "https://events.pagerduty.com/v2/enqueue" + } + } }, "secrets_properties_pagerduty": { "title": "Connector secrets properties for a PagerDuty connector", "description": "Defines secrets for connectors when type is `.pagerduty`.", "type": "object", - "additionalProperties": true + "required": [ + "routingKey" + ], + "properties": { + "routingKey": { + "description": "A 32 character PagerDuty Integration Key for an integration on a service.\n", + "type": "string" + } + } }, "create_connector_request_pagerduty": { "title": "Create PagerDuty connector request", @@ -2531,7 +2747,15 @@ "title": "Connector secrets properties for a Microsoft Teams connector", "description": "Defines secrets for connectors when type is `.teams`.", "type": "object", - "additionalProperties": true + "required": [ + "webhookUrl" + ], + "properties": { + "webhookUrl": { + "type": "string", + "description": "The URL of the incoming webhook. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts.\n" + } + } }, "create_connector_request_teams": { "title": "Create Microsoft Teams connector request", @@ -2725,16 +2949,40 @@ } }, "config_properties_xmatters": { - "title": "Connector request properties for a xMatters connector", + "title": "Connector request properties for an xMatters connector", "description": "Defines properties for connectors when type is `.xmatters`.", "type": "object", - "additionalProperties": true + "properties": { + "configUrl": { + "description": "The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when `usesBasic` is `true`.\n", + "type": "string", + "nullable": true + }, + "usesBasic": { + "description": "Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`).", + "type": "boolean", + "default": true + } + } }, "secrets_properties_xmatters": { "title": "Connector secrets properties for an xMatters connector", "description": "Defines secrets for connectors when type is `.xmatters`.", "type": "object", - "additionalProperties": true + "properties": { + "password": { + "description": "A user name for HTTP basic authentication. It is applicable only when `usesBasic` is `true`.\n", + "type": "string" + }, + "secretsUrl": { + "description": "The request URL for the Elastic Alerts trigger in xMatters with the API key included in the URL. It is applicable only when `usesBasic` is `false`.\n", + "type": "string" + }, + "user": { + "description": "A password for HTTP basic authentication. It is applicable only when `usesBasic` is `true`.\n", + "type": "string" + } + } }, "create_connector_request_xmatters": { "title": "Create xMatters connector request", @@ -2832,6 +3080,50 @@ } } }, + "connector_response_properties_d3security": { + "title": "Connector response properties for a D3 Security connector", + "type": "object", + "required": [ + "config", + "connector_type_id", + "id", + "is_deprecated", + "is_preconfigured", + "name" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_d3security" + }, + "connector_type_id": { + "type": "string", + "description": "The type of connector.", + "enum": [ + ".d3security" + ] + }, + "id": { + "type": "string", + "description": "The identifier for the connector." + }, + "is_deprecated": { + "$ref": "#/components/schemas/is_deprecated" + }, + "is_missing_secrets": { + "$ref": "#/components/schemas/is_missing_secrets" + }, + "is_preconfigured": { + "$ref": "#/components/schemas/is_preconfigured" + }, + "is_system_action": { + "$ref": "#/components/schemas/is_system_action" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + } + } + }, "connector_response_properties_email": { "title": "Connector response properties for an email connector", "type": "object", @@ -3408,6 +3700,9 @@ "name" ], "properties": { + "config": { + "type": "object" + }, "connector_type_id": { "type": "string", "description": "The type of connector.", @@ -3576,6 +3871,9 @@ { "$ref": "#/components/schemas/connector_response_properties_cases_webhook" }, + { + "$ref": "#/components/schemas/connector_response_properties_d3security" + }, { "$ref": "#/components/schemas/connector_response_properties_email" }, @@ -3653,6 +3951,47 @@ } } }, + "update_connector_request_d3security": { + "title": "Update D3 Security connector request", + "type": "object", + "required": [ + "config", + "name", + "secrets" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_d3security" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_d3security" + } + } + }, + "update_connector_request_email": { + "title": "Update email connector request", + "type": "object", + "required": [ + "config", + "name" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_email" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_email" + } + } + }, "update_connector_request_index": { "title": "Update index connector request", "type": "object", @@ -3712,6 +4051,27 @@ } } }, + "update_connector_request_pagerduty": { + "title": "Update PagerDuty connector request", + "type": "object", + "required": [ + "config", + "name", + "secrets" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_pagerduty" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_pagerduty" + } + } + }, "update_connector_request_resilient": { "title": "Update IBM Resilient connector request", "type": "object", @@ -3844,12 +4204,72 @@ } } }, + "update_connector_request_teams": { + "title": "Update Microsoft Teams connector request", + "type": "object", + "required": [ + "name", + "secrets" + ], + "properties": { + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_teams" + } + } + }, + "update_connector_request_webhook": { + "title": "Update Webhook connector request", + "type": "object", + "required": [ + "config", + "name", + "secrets" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_webhook" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_webhook" + } + } + }, + "update_connector_request_xmatters": { + "title": "Update xMatters connector request", + "type": "object", + "required": [ + "config", + "name", + "secrets" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_xmatters" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_xmatters" + } + } + }, "connector_types": { "title": "Connector types", "type": "string", "description": "The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`.", "enum": [ ".cases-webhook", + ".d3security", ".email", ".gen-ai", ".index", @@ -3862,6 +4282,7 @@ ".servicenow-sir", ".server-log", ".slack", + ".slack_api", ".swimlane", ".teams", ".tines", @@ -4595,6 +5016,25 @@ } }, "examples": { + "create_email_connector_request": { + "summary": "Create an email connector.", + "value": { + "name": "email-connector-1", + "connector_type_id": ".email", + "config": { + "from": "tester@example.com", + "hasAuth": true, + "host": "https://example.com", + "port": 1025, + "secure": false, + "service": "other" + }, + "secrets": { + "user": "username", + "password": "password" + } + } + }, "create_index_connector_request": { "summary": "Create an index connector.", "value": { @@ -4623,6 +5063,42 @@ } } }, + "create_xmatters_connector_request": { + "summary": "Create an xMatters connector with URL authentication.", + "value": { + "name": "my-xmatters-connector", + "connector_type_id": ".xmatters", + "config": { + "usesBasic": false + }, + "secrets": { + "secretsUrl": "https://example.com?apiKey=xxxxx" + } + } + }, + "create_email_connector_response": { + "summary": "A new email connector.", + "value": { + "id": "90a82c60-478f-11ee-a343-f98a117c727f", + "connector_type_id": ".email", + "name": "email-connector-1", + "config": { + "from": "tester@example.com", + "service": "other", + "host": "https://example.com", + "port": 1025, + "secure": false, + "hasAuth": true, + "tenantId": null, + "clientId": null, + "oauthTokenUrl": null + }, + "is_preconfigured": false, + "is_deprecated": false, + "is_missing_secrets": false, + "is_system_action": false + } + }, "create_index_connector_response": { "summary": "A new index connector.", "value": { @@ -4661,6 +5137,22 @@ "is_system_action": false } }, + "create_xmatters_connector_response": { + "summary": "A new xMatters connector.", + "value": { + "id": "4d2d8da0-4d1f-11ee-9367-577408be4681", + "name": "my-xmatters-connector", + "config": { + "usesBasic": false, + "configUrl": null + }, + "connector_type_id": ".xmatters", + "is_preconfigured": false, + "is_deprecated": false, + "is_missing_secrets": false, + "is_system_action": false + } + }, "get_connector_response": { "summary": "A list of connector types", "value": { diff --git a/x-pack/plugins/actions/docs/openapi/bundled.yaml b/x-pack/plugins/actions/docs/openapi/bundled.yaml index 5da86f4563a6c..ed97fb7f31233 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.yaml +++ b/x-pack/plugins/actions/docs/openapi/bundled.yaml @@ -38,6 +38,7 @@ paths: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/create_connector_request_cases_webhook' + - $ref: '#/components/schemas/create_connector_request_d3security' - $ref: '#/components/schemas/create_connector_request_email' - $ref: '#/components/schemas/create_connector_request_genai' - $ref: '#/components/schemas/create_connector_request_index' @@ -59,10 +60,14 @@ paths: discriminator: propertyName: connector_type_id examples: + createEmailConnectorRequest: + $ref: '#/components/examples/create_email_connector_request' createIndexConnectorRequest: $ref: '#/components/examples/create_index_connector_request' createWebhookConnectorRequest: $ref: '#/components/examples/create_webhook_connector_request' + createXmattersConnectorRequest: + $ref: '#/components/examples/create_xmatters_connector_request' responses: '200': description: Indicates a successful call. @@ -71,10 +76,14 @@ paths: schema: $ref: '#/components/schemas/connector_response_properties' examples: + createEmailConnectorResponse: + $ref: '#/components/examples/create_email_connector_response' createIndexConnectorResponse: $ref: '#/components/examples/create_index_connector_response' createWebhookConnectorResponse: $ref: '#/components/examples/create_webhook_connector_response' + createXmattersConnectorResponse: + $ref: '#/components/examples/create_xmatters_connector_response' '401': $ref: '#/components/responses/401' servers: @@ -182,6 +191,7 @@ paths: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/create_connector_request_cases_webhook' + - $ref: '#/components/schemas/create_connector_request_d3security' - $ref: '#/components/schemas/create_connector_request_email' - $ref: '#/components/schemas/create_connector_request_genai' - $ref: '#/components/schemas/create_connector_request_index' @@ -239,9 +249,13 @@ paths: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/update_connector_request_cases_webhook' + - $ref: '#/components/schemas/update_connector_request_d3security' + - $ref: '#/components/schemas/update_connector_request_email' + - $ref: '#/components/schemas/create_connector_request_genai' - $ref: '#/components/schemas/update_connector_request_index' - $ref: '#/components/schemas/update_connector_request_jira' - $ref: '#/components/schemas/update_connector_request_opsgenie' + - $ref: '#/components/schemas/update_connector_request_pagerduty' - $ref: '#/components/schemas/update_connector_request_resilient' - $ref: '#/components/schemas/update_connector_request_serverlog' - $ref: '#/components/schemas/update_connector_request_servicenow' @@ -249,6 +263,9 @@ paths: - $ref: '#/components/schemas/update_connector_request_slack_api' - $ref: '#/components/schemas/update_connector_request_slack_webhook' - $ref: '#/components/schemas/update_connector_request_swimlane' + - $ref: '#/components/schemas/update_connector_request_teams' + - $ref: '#/components/schemas/update_connector_request_webhook' + - $ref: '#/components/schemas/update_connector_request_xmatters' examples: updateIndexConnectorRequest: $ref: '#/components/examples/update_index_connector_request' @@ -941,16 +958,121 @@ components: example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_cases_webhook' + config_properties_d3security: + title: Connector request properties for a D3 Security connector + description: Defines properties for connectors when type is `.d3security`. + type: object + required: + - url + properties: + url: + type: string + description: | + The D3 Security API request URL. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. + secrets_properties_d3security: + title: Connector secrets properties for a D3 Security connector + description: Defines secrets for connectors when type is `.d3security`. + required: + - token + type: object + properties: + token: + type: string + description: The D3 Security token. + create_connector_request_d3security: + title: Create D3 Security connector request + description: | + The connector uses axios to send a POST request to a D3 Security endpoint. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/config_properties_d3security' + connector_type_id: + type: string + description: The type of connector. + enum: + - .d3security + example: .d3security + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/secrets_properties_d3security' config_properties_email: title: Connector request properties for an email connector description: Defines properties for connectors when type is `.email`. + required: + - from type: object - additionalProperties: true + properties: + clientId: + description: | + The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. + type: string + nullable: true + from: + description: | + The from address for all emails sent by the connector. It must be specified in `user@host-name` format. + type: string + hasAuth: + description: | + Specifies whether a user and password are required inside the secrets configuration. + default: true + type: boolean + host: + description: | + The host name of the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. + type: string + oauthTokenUrl: + type: string + nullable: true + port: + description: | + The port to connect to on the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. + type: integer + secure: + description: | + Specifies whether the connection to the service provider will use TLS. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. + type: boolean + service: + description: | + The name of the email service. + type: string + enum: + - elastic_cloud + - exchange_server + - gmail + - other + - outlook365 + - ses + tenantId: + description: | + The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. + type: string + nullable: true secrets_properties_email: title: Connector secrets properties for an email connector description: Defines secrets for connectors when type is `.email`. type: object - additionalProperties: true + properties: + clientSecret: + type: string + description: | + The Microsoft Exchange Client secret for OAuth 2.0 client credentials authentication. It must be URL-encoded. If `service` is `exchange_server`, this property is required. + password: + type: string + description: | + The password for HTTP basic authentication. If `hasAuth` is set to `true`, this property is required. + user: + type: string + description: | + The username for HTTP basic authentication. If `hasAuth` is set to `true`, this property is required. create_connector_request_email: title: Create email connector request description: | @@ -979,14 +1101,38 @@ components: config_properties_genai: title: Connector request properties for a generative AI connector description: Defines properties for connectors when type is `.gen-ai`. - type: object - properties: - apiProvider: - type: string - description: The OpenAI API provider. - apiUrl: - type: string - description: The OpenAI API endpoint. + oneOf: + - type: object + required: + - apiProvider + - apiUrl + properties: + apiProvider: + type: string + description: The OpenAI API provider. + enum: + - Azure OpenAI + apiUrl: + type: string + description: The OpenAI API endpoint. + - type: object + required: + - apiProvider + - apiUrl + properties: + apiProvider: + type: string + description: The OpenAI API provider. + enum: + - OpenAI + apiUrl: + type: string + description: The OpenAI API endpoint. + defaultModel: + type: string + description: The default model to use for requests. + discriminator: + propertyName: apiProvider secrets_properties_genai: title: Connector secrets properties for a generative AI connector description: Defines secrets for connectors when type is `.gen-ai`. @@ -1028,7 +1174,7 @@ components: type: object properties: executionTimeField: - description: Specifies a field that will contain the time the alert condition was detected. + description: A field that indicates when the document was indexed. default: null type: string nullable: true @@ -1162,12 +1308,23 @@ components: title: Connector request properties for a PagerDuty connector description: Defines properties for connectors when type is `.pagerduty`. type: object - additionalProperties: true + properties: + apiUrl: + description: The PagerDuty event URL. + type: string + nullable: true + example: https://events.pagerduty.com/v2/enqueue secrets_properties_pagerduty: title: Connector secrets properties for a PagerDuty connector description: Defines secrets for connectors when type is `.pagerduty`. type: object - additionalProperties: true + required: + - routingKey + properties: + routingKey: + description: | + A 32 character PagerDuty Integration Key for an integration on a service. + type: string create_connector_request_pagerduty: title: Create PagerDuty connector request description: | @@ -1696,7 +1853,13 @@ components: title: Connector secrets properties for a Microsoft Teams connector description: Defines secrets for connectors when type is `.teams`. type: object - additionalProperties: true + required: + - webhookUrl + properties: + webhookUrl: + type: string + description: | + The URL of the incoming webhook. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. create_connector_request_teams: title: Create Microsoft Teams connector request description: The Microsoft Teams connector uses Incoming Webhooks. @@ -1854,15 +2017,36 @@ components: secrets: $ref: '#/components/schemas/secrets_properties_webhook' config_properties_xmatters: - title: Connector request properties for a xMatters connector + title: Connector request properties for an xMatters connector description: Defines properties for connectors when type is `.xmatters`. type: object - additionalProperties: true + properties: + configUrl: + description: | + The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when `usesBasic` is `true`. + type: string + nullable: true + usesBasic: + description: Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`). + type: boolean + default: true secrets_properties_xmatters: title: Connector secrets properties for an xMatters connector description: Defines secrets for connectors when type is `.xmatters`. type: object - additionalProperties: true + properties: + password: + description: | + A user name for HTTP basic authentication. It is applicable only when `usesBasic` is `true`. + type: string + secretsUrl: + description: | + The request URL for the Elastic Alerts trigger in xMatters with the API key included in the URL. It is applicable only when `usesBasic` is `false`. + type: string + user: + description: | + A password for HTTP basic authentication. It is applicable only when `usesBasic` is `true`. + type: string create_connector_request_xmatters: title: Create xMatters connector request description: | @@ -1936,6 +2120,38 @@ components: name: type: string description: The display name for the connector. + connector_response_properties_d3security: + title: Connector response properties for a D3 Security connector + type: object + required: + - config + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/config_properties_d3security' + connector_type_id: + type: string + description: The type of connector. + enum: + - .d3security + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/is_preconfigured' + is_system_action: + $ref: '#/components/schemas/is_system_action' + name: + type: string + description: The display name for the connector. connector_response_properties_email: title: Connector response properties for an email connector type: object @@ -2357,6 +2573,8 @@ components: - is_preconfigured - name properties: + config: + type: object connector_type_id: type: string description: The type of connector. @@ -2477,6 +2695,7 @@ components: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/connector_response_properties_cases_webhook' + - $ref: '#/components/schemas/connector_response_properties_d3security' - $ref: '#/components/schemas/connector_response_properties_email' - $ref: '#/components/schemas/connector_response_properties_index' - $ref: '#/components/schemas/connector_response_properties_jira' @@ -2511,6 +2730,35 @@ components: example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_cases_webhook' + update_connector_request_d3security: + title: Update D3 Security connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/config_properties_d3security' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/secrets_properties_d3security' + update_connector_request_email: + title: Update email connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/config_properties_email' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/secrets_properties_email' update_connector_request_index: title: Update index connector request type: object @@ -2553,6 +2801,21 @@ components: description: The display name for the connector. secrets: $ref: '#/components/schemas/secrets_properties_opsgenie' + update_connector_request_pagerduty: + title: Update PagerDuty connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/config_properties_pagerduty' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/secrets_properties_pagerduty' update_connector_request_resilient: title: Update IBM Resilient connector request type: object @@ -2647,12 +2910,55 @@ components: example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_swimlane' + update_connector_request_teams: + title: Update Microsoft Teams connector request + type: object + required: + - name + - secrets + properties: + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/secrets_properties_teams' + update_connector_request_webhook: + title: Update Webhook connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/config_properties_webhook' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/secrets_properties_webhook' + update_connector_request_xmatters: + title: Update xMatters connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/config_properties_xmatters' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/secrets_properties_xmatters' connector_types: title: Connector types type: string description: The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`. enum: - .cases-webhook + - .d3security - .email - .gen-ai - .index @@ -2665,6 +2971,7 @@ components: - .servicenow-sir - .server-log - .slack + - .slack_api - .swimlane - .teams - .tines @@ -3192,6 +3499,21 @@ components: name: type: string examples: + create_email_connector_request: + summary: Create an email connector. + value: + name: email-connector-1 + connector_type_id: .email + config: + from: tester@example.com + hasAuth: true + host: https://example.com + port: 1025 + secure: false + service: other + secrets: + user: username + password: password create_index_connector_request: summary: Create an index connector. value: @@ -3213,6 +3535,35 @@ components: crt: QmFnIEF0dH... key: LS0tLS1CRUdJ... password: my-passphrase + create_xmatters_connector_request: + summary: Create an xMatters connector with URL authentication. + value: + name: my-xmatters-connector + connector_type_id: .xmatters + config: + usesBasic: false + secrets: + secretsUrl: https://example.com?apiKey=xxxxx + create_email_connector_response: + summary: A new email connector. + value: + id: 90a82c60-478f-11ee-a343-f98a117c727f + connector_type_id: .email + name: email-connector-1 + config: + from: tester@example.com + service: other + host: https://example.com + port: 1025 + secure: false + hasAuth: true + tenantId: null + clientId: null + oauthTokenUrl: null + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false create_index_connector_response: summary: A new index connector. value: @@ -3245,6 +3596,19 @@ components: is_deprecated: false is_missing_secrets: false is_system_action: false + create_xmatters_connector_response: + summary: A new xMatters connector. + value: + id: 4d2d8da0-4d1f-11ee-9367-577408be4681 + name: my-xmatters-connector + config: + usesBasic: false + configUrl: null + connector_type_id: .xmatters + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false get_connector_response: summary: A list of connector types value: diff --git a/x-pack/plugins/actions/docs/openapi/components/examples/create_email_connector_request.yaml b/x-pack/plugins/actions/docs/openapi/components/examples/create_email_connector_request.yaml new file mode 100644 index 0000000000000..df37ace78fb4b --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/examples/create_email_connector_request.yaml @@ -0,0 +1,14 @@ +summary: Create an email connector. +value: + name: email-connector-1 + connector_type_id: .email + config: + from: tester@example.com + hasAuth: true + host: https://example.com + port: 1025 + secure: false + service: other + secrets: + user: username + password: password \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/examples/create_email_connector_response.yaml b/x-pack/plugins/actions/docs/openapi/components/examples/create_email_connector_response.yaml new file mode 100644 index 0000000000000..b8405a1d61123 --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/examples/create_email_connector_response.yaml @@ -0,0 +1,19 @@ +summary: A new email connector. +value: + id: 90a82c60-478f-11ee-a343-f98a117c727f + connector_type_id: .email + name: email-connector-1 + config: + from: tester@example.com + service: other + host: https://example.com + port: 1025 + secure: false + hasAuth: true + tenantId: null + clientId: null + oauthTokenUrl: null + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/examples/create_xmatters_connector_request.yaml b/x-pack/plugins/actions/docs/openapi/components/examples/create_xmatters_connector_request.yaml new file mode 100644 index 0000000000000..818e8695bb1bb --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/examples/create_xmatters_connector_request.yaml @@ -0,0 +1,8 @@ +summary: Create an xMatters connector with URL authentication. +value: + name: my-xmatters-connector + connector_type_id: .xmatters + config: + usesBasic: false + secrets: + secretsUrl: https://example.com?apiKey=xxxxx \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/examples/create_xmatters_connector_response.yaml b/x-pack/plugins/actions/docs/openapi/components/examples/create_xmatters_connector_response.yaml new file mode 100644 index 0000000000000..c681cf31b9c47 --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/examples/create_xmatters_connector_response.yaml @@ -0,0 +1,12 @@ +summary: A new xMatters connector. +value: + id: 4d2d8da0-4d1f-11ee-9367-577408be4681 + name: my-xmatters-connector + config: + usesBasic: false + configUrl: null + connector_type_id: .xmatters + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_d3security.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_d3security.yaml new file mode 100644 index 0000000000000..770e052783118 --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_d3security.yaml @@ -0,0 +1,11 @@ +title: Connector request properties for a D3 Security connector +description: Defines properties for connectors when type is `.d3security`. +type: object +required: + - url +properties: + url: + type: string + description: > + The D3 Security API request URL. + If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml index d87c36be08936..6d3618e2bba27 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml @@ -1,5 +1,59 @@ title: Connector request properties for an email connector description: Defines properties for connectors when type is `.email`. +required: + - from type: object -additionalProperties: true -# TO-DO: Add the properties for this connector. \ No newline at end of file +properties: + clientId: + description: > + The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. + If `service` is `exchange_server`, this property is required. + type: string + nullable: true + from: + description: > + The from address for all emails sent by the connector. It must be specified in `user@host-name` format. + type: string + hasAuth: + description: > + Specifies whether a user and password are required inside the secrets configuration. + default: true + type: boolean + host: + description: > + The host name of the service provider. + If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. + If `service` is `other`, this property must be defined. + type: string + oauthTokenUrl: + # description: TBD + type: string + nullable: true + port: + description: > + The port to connect to on the service provider. + If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. + If `service` is `other`, this property must be defined. + type: integer + secure: + description: > + Specifies whether the connection to the service provider will use TLS. + If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. + type: boolean + service: + description: > + The name of the email service. + type: string + enum: + - elastic_cloud + - exchange_server + - gmail + - other + - outlook365 + - ses + tenantId: + description: > + The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. + If `service` is `exchange_server`, this property is required. + type: string + nullable: true \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_genai.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_genai.yaml index 6732d4efbbf3b..ca4388303fd2c 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_genai.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_genai.yaml @@ -1,10 +1,32 @@ title: Connector request properties for a generative AI connector description: Defines properties for connectors when type is `.gen-ai`. -type: object -properties: - apiProvider: +oneOf: + - type: object + required: + - apiProvider + - apiUrl + properties: + apiProvider: type: string description: The OpenAI API provider. - apiUrl: + enum: ['Azure OpenAI'] + apiUrl: type: string - description: The OpenAI API endpoint. \ No newline at end of file + description: The OpenAI API endpoint. + - type: object + required: + - apiProvider + - apiUrl + properties: + apiProvider: + type: string + description: The OpenAI API provider. + enum: ['OpenAI'] + apiUrl: + type: string + description: The OpenAI API endpoint. + defaultModel: + type: string + description: The default model to use for requests. +discriminator: + propertyName: apiProvider \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml index c82f775fe15dc..6c335b166d20a 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml @@ -5,7 +5,7 @@ description: Defines properties for connectors when type is `.index`. type: object properties: executionTimeField: - description: Specifies a field that will contain the time the alert condition was detected. + description: A field that indicates when the document was indexed. default: null type: string nullable: true diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml index c9a98a9619d85..562557f548ece 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml @@ -1,5 +1,9 @@ title: Connector request properties for a PagerDuty connector description: Defines properties for connectors when type is `.pagerduty`. type: object -additionalProperties: true -# TO-DO: Add the properties for this connector. \ No newline at end of file +properties: + apiUrl: + description: The PagerDuty event URL. + type: string + nullable: true + example: https://events.pagerduty.com/v2/enqueue \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml index 6625eb09b4d35..350e96f3aa63d 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml @@ -1,5 +1,15 @@ -title: Connector request properties for a xMatters connector +title: Connector request properties for an xMatters connector description: Defines properties for connectors when type is `.xmatters`. type: object -additionalProperties: true -# TO-DO: Add the properties for this connector. \ No newline at end of file +properties: + configUrl: + description: > + The request URL for the Elastic Alerts trigger in xMatters. + It is applicable only when `usesBasic` is `true`. + type: string + nullable: true + usesBasic: + description: Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`). + type: boolean + default: true + \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml index ef72d88e31480..334fe3fa5cdb3 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml @@ -2,6 +2,7 @@ title: Connector response properties description: The properties vary depending on the connector type. oneOf: - $ref: 'connector_response_properties_cases_webhook.yaml' + - $ref: 'connector_response_properties_d3security.yaml' - $ref: 'connector_response_properties_email.yaml' - $ref: 'connector_response_properties_index.yaml' - $ref: 'connector_response_properties_jira.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_d3security.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_d3security.yaml new file mode 100644 index 0000000000000..694b7c011b84a --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_d3security.yaml @@ -0,0 +1,31 @@ +title: Connector response properties for a D3 Security connector +type: object +required: + - config + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name +properties: + config: + $ref: 'config_properties_d3security.yaml' + connector_type_id: + type: string + description: The type of connector. + enum: + - .d3security + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: 'is_deprecated.yaml' + is_missing_secrets: + $ref: 'is_missing_secrets.yaml' + is_preconfigured: + $ref: 'is_preconfigured.yaml' + is_system_action: + $ref: 'is_system_action.yaml' + name: + type: string + description: The display name for the connector. diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_teams.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_teams.yaml index 3d082bfdf7821..3e0dc777efa98 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_teams.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_teams.yaml @@ -7,6 +7,8 @@ required: - is_preconfigured - name properties: + config: + type: object connector_type_id: type: string description: The type of connector. diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml index 8b08285f9a0e8..2bbc9f5dabac4 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml @@ -3,6 +3,7 @@ type: string description: The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`. enum: - .cases-webhook + - .d3security - .email - .gen-ai - .index @@ -15,6 +16,7 @@ enum: - .servicenow-sir - .server-log - .slack + - .slack_api - .swimlane - .teams - .tines diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_d3security.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_d3security.yaml new file mode 100644 index 0000000000000..39cdda80b7dd2 --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_d3security.yaml @@ -0,0 +1,24 @@ +title: Create D3 Security connector request +description: > + The connector uses axios to send a POST request to a D3 Security endpoint. +type: object +required: + - config + - connector_type_id + - name + - secrets +properties: + config: + $ref: 'config_properties_d3security.yaml' + connector_type_id: + type: string + description: The type of connector. + enum: + - .d3security + example: .d3security + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: 'secrets_properties_d3security.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_d3security.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_d3security.yaml new file mode 100644 index 0000000000000..136d393e7970d --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_d3security.yaml @@ -0,0 +1,9 @@ +title: Connector secrets properties for a D3 Security connector +description: Defines secrets for connectors when type is `.d3security`. +required: + - token +type: object +properties: + token: + type: string + description: The D3 Security token. diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_email.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_email.yaml index 04a3526b72ce3..dbfca0230c79a 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_email.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_email.yaml @@ -1,5 +1,20 @@ title: Connector secrets properties for an email connector description: Defines secrets for connectors when type is `.email`. type: object -additionalProperties: true -# TO-DO: Add the properties for this connector. \ No newline at end of file +properties: + clientSecret: + type: string + description: > + The Microsoft Exchange Client secret for OAuth 2.0 client credentials authentication. + It must be URL-encoded. + If `service` is `exchange_server`, this property is required. + password: + type: string + description: > + The password for HTTP basic authentication. + If `hasAuth` is set to `true`, this property is required. + user: + type: string + description: > + The username for HTTP basic authentication. + If `hasAuth` is set to `true`, this property is required. \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_pagerduty.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_pagerduty.yaml index 14d0c6fbefd69..d4259fe45a0f4 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_pagerduty.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_pagerduty.yaml @@ -1,5 +1,10 @@ title: Connector secrets properties for a PagerDuty connector description: Defines secrets for connectors when type is `.pagerduty`. type: object -additionalProperties: true -# TO-DO: Add the properties for this connector. \ No newline at end of file +required: + - routingKey +properties: + routingKey: + description: > + A 32 character PagerDuty Integration Key for an integration on a service. + type: string \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_teams.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_teams.yaml index f5e3aa51c7528..6af6278220287 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_teams.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_teams.yaml @@ -1,5 +1,11 @@ title: Connector secrets properties for a Microsoft Teams connector description: Defines secrets for connectors when type is `.teams`. type: object -additionalProperties: true -# TO-DO: Add the properties for this connector. \ No newline at end of file +required: + - webhookUrl +properties: + webhookUrl: + type: string + description: > + The URL of the incoming webhook. + If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_xmatters.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_xmatters.yaml index 67071884663dd..0d9622a3bbd3f 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_xmatters.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_xmatters.yaml @@ -1,5 +1,19 @@ title: Connector secrets properties for an xMatters connector description: Defines secrets for connectors when type is `.xmatters`. type: object -additionalProperties: true -# TO-DO: Add the properties for this connector. \ No newline at end of file +properties: + password: + description: > + A user name for HTTP basic authentication. + It is applicable only when `usesBasic` is `true`. + type: string + secretsUrl: + description: > + The request URL for the Elastic Alerts trigger in xMatters with the API key included in the URL. + It is applicable only when `usesBasic` is `false`. + type: string + user: + description: > + A password for HTTP basic authentication. + It is applicable only when `usesBasic` is `true`. + type: string diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_d3security.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_d3security.yaml new file mode 100644 index 0000000000000..a79a6b4e541dd --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_d3security.yaml @@ -0,0 +1,14 @@ +title: Update D3 Security connector request +type: object +required: + - config + - name + - secrets +properties: + config: + $ref: 'config_properties_d3security.yaml' + name: + type: string + description: The display name for the connector. + secrets: + $ref: 'secrets_properties_d3security.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_genai.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_genai.yaml new file mode 100644 index 0000000000000..a7fbf5cb7bcfa --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_genai.yaml @@ -0,0 +1,13 @@ +title: Update generative AI connector request +type: object +required: + - config + - name +properties: + config: + $ref: 'config_properties_genai.yaml' + name: + type: string + description: The display name for the connector. + secrets: + $ref: 'secrets_properties_genai.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml index d8d8735db82d8..f32def50706b2 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml @@ -17,6 +17,7 @@ post: description: The properties vary depending on the connector type. oneOf: - $ref: '../components/schemas/create_connector_request_cases_webhook.yaml' + - $ref: '../components/schemas/create_connector_request_d3security.yaml' - $ref: '../components/schemas/create_connector_request_email.yaml' - $ref: '../components/schemas/create_connector_request_genai.yaml' - $ref: '../components/schemas/create_connector_request_index.yaml' @@ -38,10 +39,14 @@ post: discriminator: propertyName: connector_type_id examples: + createEmailConnectorRequest: + $ref: '../components/examples/create_email_connector_request.yaml' createIndexConnectorRequest: $ref: '../components/examples/create_index_connector_request.yaml' createWebhookConnectorRequest: $ref: '../components/examples/create_webhook_connector_request.yaml' + createXmattersConnectorRequest: + $ref: '../components/examples/create_xmatters_connector_request.yaml' responses: '200': description: Indicates a successful call. @@ -50,10 +55,14 @@ post: schema: $ref: '../components/schemas/connector_response_properties.yaml' examples: + createEmailConnectorResponse: + $ref: '../components/examples/create_email_connector_response.yaml' createIndexConnectorResponse: $ref: '../components/examples/create_index_connector_response.yaml' createWebhookConnectorResponse: $ref: '../components/examples/create_webhook_connector_response.yaml' + createXmattersConnectorResponse: + $ref: '../components/examples/create_xmatters_connector_response.yaml' '401': $ref: '../components/responses/401.yaml' servers: diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml index 9b6279bcb18a7..c6a2447c251a2 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml @@ -101,6 +101,7 @@ post: description: The properties vary depending on the connector type. oneOf: - $ref: '../components/schemas/create_connector_request_cases_webhook.yaml' + - $ref: '../components/schemas/create_connector_request_d3security.yaml' - $ref: '../components/schemas/create_connector_request_email.yaml' - $ref: '../components/schemas/create_connector_request_genai.yaml' - $ref: '../components/schemas/create_connector_request_index.yaml' @@ -159,12 +160,13 @@ put: description: The properties vary depending on the connector type. oneOf: - $ref: '../components/schemas/update_connector_request_cases_webhook.yaml' -# - $ref: '../components/schemas/update_connector_request_email.yaml' -# - $ref: '../components/schemas/create_connector_request_genai.yaml' + - $ref: '../components/schemas/update_connector_request_d3security.yaml' + - $ref: '../components/schemas/update_connector_request_email.yaml' + - $ref: '../components/schemas/create_connector_request_genai.yaml' - $ref: '../components/schemas/update_connector_request_index.yaml' - $ref: '../components/schemas/update_connector_request_jira.yaml' - $ref: '../components/schemas/update_connector_request_opsgenie.yaml' -# - $ref: '../components/schemas/update_connector_request_pagerduty.yaml' + - $ref: '../components/schemas/update_connector_request_pagerduty.yaml' - $ref: '../components/schemas/update_connector_request_resilient.yaml' - $ref: '../components/schemas/update_connector_request_serverlog.yaml' - $ref: '../components/schemas/update_connector_request_servicenow.yaml' @@ -172,10 +174,10 @@ put: - $ref: '../components/schemas/update_connector_request_slack_api.yaml' - $ref: '../components/schemas/update_connector_request_slack_webhook.yaml' - $ref: '../components/schemas/update_connector_request_swimlane.yaml' -# - $ref: '../components/schemas/update_connector_request_teams.yaml' + - $ref: '../components/schemas/update_connector_request_teams.yaml' # - $ref: '../components/schemas/update_connector_request_tines.yaml' -# - $ref: '../components/schemas/update_connector_request_webhook.yaml' -# - $ref: '../components/schemas/update_connector_request_xmatters.yaml' + - $ref: '../components/schemas/update_connector_request_webhook.yaml' + - $ref: '../components/schemas/update_connector_request_xmatters.yaml' examples: updateIndexConnectorRequest: $ref: '../components/examples/update_index_connector_request.yaml' diff --git a/x-pack/plugins/actions/kibana.jsonc b/x-pack/plugins/actions/kibana.jsonc index 9152e64ba898a..78f66742c2a03 100644 --- a/x-pack/plugins/actions/kibana.jsonc +++ b/x-pack/plugins/actions/kibana.jsonc @@ -21,7 +21,8 @@ "usageCollection", "spaces", "security", - "monitoringCollection" + "monitoringCollection", + "serverless" ], "extraPublicDirs": [ "common" diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts index fa7410bb71178..35860bdfb0c53 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts @@ -37,7 +37,7 @@ import { ActionsAuthorization } from '../authorization/actions_authorization'; import { getAuthorizationModeBySource, AuthorizationMode, - getBulkAuthorizationModeBySource, + bulkGetAuthorizationModeBySource, } from '../authorization/get_authorization_mode_by_source'; import { actionsAuthorizationMock } from '../authorization/actions_authorization.mock'; import { trackLegacyRBACExemption } from '../lib/track_legacy_rbac_exemption'; @@ -71,7 +71,7 @@ jest.mock('../authorization/get_authorization_mode_by_source', () => { getAuthorizationModeBySource: jest.fn(() => { return 1; }), - getBulkAuthorizationModeBySource: jest.fn(() => { + bulkGetAuthorizationModeBySource: jest.fn(() => { return 1; }), AuthorizationMode: { @@ -3007,8 +3007,8 @@ describe('execute()', () => { describe('bulkEnqueueExecution()', () => { describe('authorization', () => { - test('ensures user is authorised to excecute actions', async () => { - (getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { + test('ensures user is authorised to execute actions', async () => { + (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { return { [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }; }); await actionsClient.bulkEnqueueExecution([ @@ -3019,6 +3019,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, { id: uuidv4(), @@ -3027,6 +3028,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '456def', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, ]); expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ @@ -3035,7 +3037,7 @@ describe('bulkEnqueueExecution()', () => { }); test('throws when user is not authorised to create the type of action', async () => { - (getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { + (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { return { [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }; }); authorization.ensureAuthorized.mockRejectedValue( @@ -3051,6 +3053,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, { id: uuidv4(), @@ -3059,6 +3062,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '456def', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, ]) ).rejects.toMatchInlineSnapshot(`[Error: Unauthorized to execute all actions]`); @@ -3069,7 +3073,7 @@ describe('bulkEnqueueExecution()', () => { }); test('tracks legacy RBAC', async () => { - (getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { + (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { return { [AuthorizationMode.RBAC]: 0, [AuthorizationMode.Legacy]: 2 }; }); @@ -3081,6 +3085,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, { id: uuidv4(), @@ -3089,6 +3094,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '456def', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, ]); @@ -3101,7 +3107,7 @@ describe('bulkEnqueueExecution()', () => { }); test('calls the bulkExecutionEnqueuer with the appropriate parameters', async () => { - (getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { + (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { return { [AuthorizationMode.RBAC]: 0, [AuthorizationMode.Legacy]: 0 }; }); const opts = [ @@ -3112,6 +3118,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, { id: uuidv4(), @@ -3120,6 +3127,7 @@ describe('bulkEnqueueExecution()', () => { executionId: '456def', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'my-action-type', }, ]; await expect(actionsClient.bulkEnqueueExecution(opts)).resolves.toMatchInlineSnapshot( diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.ts b/x-pack/plugins/actions/server/actions_client/actions_client.ts index 392b5ec7354b6..f5cf395721795 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.ts @@ -11,7 +11,7 @@ import url from 'url'; import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { i18n } from '@kbn/i18n'; -import { omitBy, isUndefined } from 'lodash'; +import { omitBy, isUndefined, compact } from 'lodash'; import { IScopedClusterClient, SavedObjectsClientContract, @@ -55,11 +55,12 @@ import { ExecutionEnqueuer, ExecuteOptions as EnqueueExecutionOptions, BulkExecutionEnqueuer, + ExecutionResponse, } from '../create_execute_function'; import { ActionsAuthorization } from '../authorization/actions_authorization'; import { getAuthorizationModeBySource, - getBulkAuthorizationModeBySource, + bulkGetAuthorizationModeBySource, AuthorizationMode, } from '../authorization/get_authorization_mode_by_source'; import { connectorAuditEvent, ConnectorAuditAction } from '../lib/audit_events'; @@ -114,7 +115,7 @@ export interface ConstructorOptions { inMemoryConnectors: InMemoryConnector[]; actionExecutor: ActionExecutorContract; ephemeralExecutionEnqueuer: ExecutionEnqueuer; - bulkExecutionEnqueuer: BulkExecutionEnqueuer; + bulkExecutionEnqueuer: BulkExecutionEnqueuer; request: KibanaRequest; authorization: ActionsAuthorization; auditLogger?: AuditLogger; @@ -139,7 +140,7 @@ export interface ActionsClientContext { request: KibanaRequest; authorization: ActionsAuthorization; ephemeralExecutionEnqueuer: ExecutionEnqueuer; - bulkExecutionEnqueuer: BulkExecutionEnqueuer; + bulkExecutionEnqueuer: BulkExecutionEnqueuer; auditLogger?: AuditLogger; usageCounter?: UsageCounter; connectorTokenClient: ConnectorTokenClientContract; @@ -766,19 +767,19 @@ export class ActionsClient { }); } - public async bulkEnqueueExecution(options: EnqueueExecutionOptions[]): Promise { - const sources: Array> = []; - options.forEach((option) => { - if (option.source) { - sources.push(option.source); - } - }); + public async bulkEnqueueExecution( + options: EnqueueExecutionOptions[] + ): Promise { + const sources: Array> = compact( + (options ?? []).map((option) => option.source) + ); - const authCounts = await getBulkAuthorizationModeBySource( + const authModes = await bulkGetAuthorizationModeBySource( + this.context.logger, this.context.unsecuredSavedObjectsClient, sources ); - if (authCounts[AuthorizationMode.RBAC] > 0) { + if (authModes[AuthorizationMode.RBAC] > 0) { /** * For scheduled executions the additional authorization check * for system actions (kibana privileges) will be performed @@ -786,11 +787,11 @@ export class ActionsClient { */ await this.context.authorization.ensureAuthorized({ operation: 'execute' }); } - if (authCounts[AuthorizationMode.Legacy] > 0) { + if (authModes[AuthorizationMode.Legacy] > 0) { trackLegacyRBACExemption( 'bulkEnqueueExecution', this.context.usageCounter, - authCounts[AuthorizationMode.Legacy] + authModes[AuthorizationMode.Legacy] ); } return this.context.bulkExecutionEnqueuer(this.context.unsecuredSavedObjectsClient, options); diff --git a/x-pack/plugins/actions/server/actions_config.mock.ts b/x-pack/plugins/actions/server/actions_config.mock.ts index 95c6ec1c0cc11..49f1a807bce9f 100644 --- a/x-pack/plugins/actions/server/actions_config.mock.ts +++ b/x-pack/plugins/actions/server/actions_config.mock.ts @@ -28,6 +28,7 @@ const createActionsConfigMock = () => { validateEmailAddresses: jest.fn().mockReturnValue(undefined), getMaxAttempts: jest.fn().mockReturnValue(3), enableFooterInEmail: jest.fn().mockReturnValue(true), + getMaxQueued: jest.fn().mockReturnValue(1000), }; return mocked; }; diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index e1b761ee30001..d19fcbc363b88 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -563,3 +563,20 @@ describe('getMaxAttempts()', () => { expect(maxAttempts).toEqual(3); }); }); + +describe('getMaxQueued()', () => { + test('returns the queued actions max defined in config', () => { + const acu = getActionsConfigurationUtilities({ + ...defaultActionsConfig, + queued: { max: 1 }, + }); + const max = acu.getMaxQueued(); + expect(max).toEqual(1); + }); + + test('returns the default queued actions max', () => { + const acu = getActionsConfigurationUtilities(defaultActionsConfig); + const max = acu.getMaxQueued(); + expect(max).toEqual(1000000); + }); +}); diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts index 78e62684ad898..240a65228b4dc 100644 --- a/x-pack/plugins/actions/server/actions_config.ts +++ b/x-pack/plugins/actions/server/actions_config.ts @@ -11,7 +11,13 @@ import url from 'url'; import { curry } from 'lodash'; import { pipe } from 'fp-ts/lib/pipeable'; -import { ActionsConfig, AllowedHosts, EnabledActionTypes, CustomHostSettings } from './config'; +import { + ActionsConfig, + AllowedHosts, + EnabledActionTypes, + CustomHostSettings, + DEFAULT_QUEUED_MAX, +} from './config'; import { getCanonicalCustomHostUrl } from './lib/custom_host_settings'; import { ActionTypeDisabledError } from './lib'; import { ProxySettings, ResponseSettings, SSLSettings } from './types'; @@ -54,6 +60,7 @@ export interface ActionsConfigurationUtilities { options?: ValidateEmailAddressesOptions ): string | undefined; enableFooterInEmail: () => boolean; + getMaxQueued: () => number; } function allowListErrorMessage(field: AllowListingField, value: string) { @@ -217,5 +224,6 @@ export function getActionsConfigurationUtilities( ); }, enableFooterInEmail: () => config.enableFooterInEmail, + getMaxQueued: () => config.queued?.max || DEFAULT_QUEUED_MAX, }; } diff --git a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts index 7e2ceb5b653c8..2e264300490f8 100644 --- a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts +++ b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts @@ -41,7 +41,7 @@ jest.mock('../../../../authorization/get_authorization_mode_by_source', () => { getAuthorizationModeBySource: jest.fn(() => { return 1; }), - getBulkAuthorizationModeBySource: jest.fn(() => { + bulkGetAuthorizationModeBySource: jest.fn(() => { return 1; }), AuthorizationMode: { diff --git a/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.test.ts b/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.test.ts index 199d8a97bd623..7ba856b91fb97 100644 --- a/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.test.ts +++ b/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.test.ts @@ -7,14 +7,16 @@ import { getAuthorizationModeBySource, - getBulkAuthorizationModeBySource, + bulkGetAuthorizationModeBySource, AuthorizationMode, } from './get_authorization_mode_by_source'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { loggingSystemMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; import { v4 as uuidv4 } from 'uuid'; -import { asSavedObjectExecutionSource } from '../lib'; +import { asHttpRequestExecutionSource, asSavedObjectExecutionSource } from '../lib'; +import { KibanaRequest, Logger } from '@kbn/core/server'; const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); +const logger = loggingSystemMock.create().get() as jest.Mocked; describe(`#getAuthorizationModeBySource`, () => { test('should return RBAC if no source is provided', async () => { @@ -37,7 +39,7 @@ describe(`#getAuthorizationModeBySource`, () => { test('should return RBAC if source alert is not marked as legacy', async () => { const id = uuidv4(); - unsecuredSavedObjectsClient.get.mockResolvedValue(mockAlert({ id })); + unsecuredSavedObjectsClient.get.mockResolvedValue(mockRuleSO({ id })); expect( await getAuthorizationModeBySource( unsecuredSavedObjectsClient, @@ -52,7 +54,7 @@ describe(`#getAuthorizationModeBySource`, () => { test('should return Legacy if source alert is marked as legacy', async () => { const id = uuidv4(); unsecuredSavedObjectsClient.get.mockResolvedValue( - mockAlert({ id, attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }) + mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }) ); expect( await getAuthorizationModeBySource( @@ -68,7 +70,7 @@ describe(`#getAuthorizationModeBySource`, () => { test('should return RBAC if source alert is marked as modern', async () => { const id = uuidv4(); unsecuredSavedObjectsClient.get.mockResolvedValue( - mockAlert({ id, attributes: { meta: { versionApiKeyLastmodified: '7.10.0' } } }) + mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: '7.10.0' } } }) ); expect( await getAuthorizationModeBySource( @@ -83,7 +85,7 @@ describe(`#getAuthorizationModeBySource`, () => { test('should return RBAC if source alert doesnt have a last modified version', async () => { const id = uuidv4(); - unsecuredSavedObjectsClient.get.mockResolvedValue(mockAlert({ id, attributes: { meta: {} } })); + unsecuredSavedObjectsClient.get.mockResolvedValue(mockRuleSO({ id, attributes: { meta: {} } })); expect( await getAuthorizationModeBySource( unsecuredSavedObjectsClient, @@ -96,32 +98,71 @@ describe(`#getAuthorizationModeBySource`, () => { }); }); -describe(`#getBulkAuthorizationModeBySource`, () => { - test('should return RBAC if no source is provided', async () => { - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [] }); - expect(await getBulkAuthorizationModeBySource(unsecuredSavedObjectsClient)).toEqual({ +describe(`#bulkGetAuthorizationModeBySource`, () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('should return RBAC if no sources are provided', async () => { + expect(await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient)).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0, }); + expect(unsecuredSavedObjectsClient.bulkGet).not.toHaveBeenCalled(); }); - test('should return RBAC if source is not an alert', async () => { - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [] }); + test('should return RBAC if no alert sources are provided', async () => { expect( - await getBulkAuthorizationModeBySource(unsecuredSavedObjectsClient, [ + await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ asSavedObjectExecutionSource({ type: 'action', id: uuidv4(), }), + asHttpRequestExecutionSource({} as KibanaRequest), ]) ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }); + + expect(unsecuredSavedObjectsClient.bulkGet).not.toHaveBeenCalled(); + }); + + test('should consolidate duplicate alert sources', async () => { + unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ + saved_objects: [mockRuleSO({ id: '1' }), mockRuleSO({ id: '2' })], + }); + expect( + await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ + asSavedObjectExecutionSource({ + type: 'alert', + id: '1', + }), + asSavedObjectExecutionSource({ + type: 'alert', + id: '1', + }), + asSavedObjectExecutionSource({ + type: 'alert', + id: '2', + }), + ]) + ).toEqual({ [AuthorizationMode.RBAC]: 2, [AuthorizationMode.Legacy]: 0 }); + + expect(unsecuredSavedObjectsClient.bulkGet).toHaveBeenCalledWith([ + { + type: 'alert', + id: '1', + }, + { + type: 'alert', + id: '2', + }, + ]); }); test('should return RBAC if source alert is not marked as legacy', async () => { const id = uuidv4(); - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [mockAlert({ id })] }); + unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [mockRuleSO({ id })] }); expect( - await getBulkAuthorizationModeBySource(unsecuredSavedObjectsClient, [ + await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ asSavedObjectExecutionSource({ type: 'alert', id, @@ -134,11 +175,11 @@ describe(`#getBulkAuthorizationModeBySource`, () => { const id = uuidv4(); unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [ - mockAlert({ id, attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }), + mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }), ], }); expect( - await getBulkAuthorizationModeBySource(unsecuredSavedObjectsClient, [ + await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ asSavedObjectExecutionSource({ type: 'alert', id, @@ -151,11 +192,11 @@ describe(`#getBulkAuthorizationModeBySource`, () => { const id = uuidv4(); unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [ - mockAlert({ id, attributes: { meta: { versionApiKeyLastmodified: '7.10.0' } } }), + mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: '7.10.0' } } }), ], }); expect( - await getBulkAuthorizationModeBySource(unsecuredSavedObjectsClient, [ + await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ asSavedObjectExecutionSource({ type: 'alert', id, @@ -167,10 +208,10 @@ describe(`#getBulkAuthorizationModeBySource`, () => { test('should return RBAC if source alert doesnt have a last modified version', async () => { const id = uuidv4(); unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ - saved_objects: [mockAlert({ id, attributes: { meta: {} } })], + saved_objects: [mockRuleSO({ id, attributes: { meta: {} } })], }); expect( - await getBulkAuthorizationModeBySource(unsecuredSavedObjectsClient, [ + await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ asSavedObjectExecutionSource({ type: 'alert', id, @@ -178,9 +219,39 @@ describe(`#getBulkAuthorizationModeBySource`, () => { ]) ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }); }); + + test('should return RBAC and log warning if error getting source alert', async () => { + unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ + saved_objects: [ + mockRuleSO({ id: '1', attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }), + // @ts-expect-error + { + id: '2', + type: 'alert', + error: { statusCode: 404, error: 'failed to get', message: 'fail' }, + }, + ], + }); + expect( + await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ + asSavedObjectExecutionSource({ + type: 'alert', + id: '1', + }), + asSavedObjectExecutionSource({ + type: 'alert', + id: '2', + }), + ]) + ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 1 }); + + expect(logger.warn).toHaveBeenCalledWith( + `Error retrieving saved object [alert/2] - fail - default to using RBAC authorization mode.` + ); + }); }); -const mockAlert = (overrides: Record = {}) => ({ +const mockRuleSO = (overrides: Record = {}) => ({ id: '1', type: 'alert', attributes: { diff --git a/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts b/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts index 7980db61ee31c..ace66798b24ba 100644 --- a/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts +++ b/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { SavedObjectsClientContract } from '@kbn/core/server'; -import { get } from 'lodash'; +import { Logger, SavedObjectsClientContract } from '@kbn/core/server'; import { ActionExecutionSource, isSavedObjectExecutionSource } from '../lib'; import { ALERT_SAVED_OBJECT_TYPE } from '../constants/saved_objects'; +import { SavedObjectExecutionSource } from '../lib/action_execution_source'; const LEGACY_VERSION = 'pre-7.10.0'; @@ -34,36 +34,57 @@ export async function getAuthorizationModeBySource( : AuthorizationMode.RBAC; } -export async function getBulkAuthorizationModeBySource( +export async function bulkGetAuthorizationModeBySource( + logger: Logger, unsecuredSavedObjectsClient: SavedObjectsClientContract, executionSources: Array> = [] -): Promise> { - const count = { [AuthorizationMode.Legacy]: 0, [AuthorizationMode.RBAC]: 0 }; - if (executionSources.length === 0) { - count[AuthorizationMode.RBAC] = 1; - return count; +): Promise> { + const authModes = { [AuthorizationMode.Legacy]: 0, [AuthorizationMode.RBAC]: 0 }; + + const alertSavedObjectExecutionSources: SavedObjectExecutionSource[] = executionSources.filter( + (source) => + isSavedObjectExecutionSource(source) && source?.source?.type === ALERT_SAVED_OBJECT_TYPE + ) as SavedObjectExecutionSource[]; + + // If no ALERT_SAVED_OBJECT_TYPE source, default to RBAC + if (alertSavedObjectExecutionSources.length === 0) { + authModes[AuthorizationMode.RBAC] = 1; + return authModes; } - const alerts = await unsecuredSavedObjectsClient.bulkGet<{ + + // Collect the unique rule IDs for ALERT_SAVED_OBJECT_TYPE sources and bulk get the associated SOs + const rulesIds = new Set( + alertSavedObjectExecutionSources.map((source: SavedObjectExecutionSource) => source?.source?.id) + ); + + // Get rule saved objects to determine whether to use RBAC or legacy authorization source + const ruleSOs = await unsecuredSavedObjectsClient.bulkGet<{ meta?: { versionApiKeyLastmodified?: string; }; }>( - executionSources.map((es) => ({ + [...rulesIds].map((id) => ({ type: ALERT_SAVED_OBJECT_TYPE, - id: get(es, 'source.id'), + id, })) ); - const legacyVersions = alerts.saved_objects.reduce>((acc, so) => { - acc[so.id] = so.attributes.meta?.versionApiKeyLastmodified === LEGACY_VERSION; - return acc; - }, {}); - return executionSources.reduce((acc, es) => { - const isAlertSavedObject = - isSavedObjectExecutionSource(es) && es.source?.type === ALERT_SAVED_OBJECT_TYPE; - const isLegacyVersion = legacyVersions[get(es, 'source.id')]; - const key = - isAlertSavedObject && isLegacyVersion ? AuthorizationMode.Legacy : AuthorizationMode.RBAC; - acc[key]++; + + return ruleSOs.saved_objects.reduce((acc, ruleSO) => { + if (ruleSO.error) { + logger.warn( + `Error retrieving saved object [${ruleSO.type}/${ruleSO.id}] - ${ruleSO.error?.message} - default to using RBAC authorization mode.` + ); + // If there's an error retrieving the saved object, default to RBAC auth mode to avoid privilege de-escalation + authModes[AuthorizationMode.RBAC]++; + } else { + // Check whether this is a legacy rule + const isLegacy = ruleSO.attributes?.meta?.versionApiKeyLastmodified === LEGACY_VERSION; + if (isLegacy) { + authModes[AuthorizationMode.Legacy]++; + } else { + authModes[AuthorizationMode.RBAC]++; + } + } return acc; - }, count); + }, authModes); } diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 9a620d1452f23..a0b6c23883993 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -19,6 +19,9 @@ export enum EnabledActionTypes { const MAX_MAX_ATTEMPTS = 10; const MIN_MAX_ATTEMPTS = 1; +const MIN_QUEUED_MAX = 1; +export const DEFAULT_QUEUED_MAX = 1000000; + const preconfiguredActionSchema = schema.object({ name: schema.string({ minLength: 1 }), actionTypeId: schema.string({ minLength: 1 }), @@ -64,6 +67,15 @@ const connectorTypeSchema = schema.object({ maxAttempts: schema.maybe(schema.number({ min: MIN_MAX_ATTEMPTS, max: MAX_MAX_ATTEMPTS })), }); +// We leverage enabledActionTypes list by allowing the other plugins to overwrite it by using "setEnabledConnectorTypes" in the plugin setup. +// The list can be overwritten only if it's not already been set in the config. +const enabledConnectorTypesSchema = schema.arrayOf( + schema.oneOf([schema.string(), schema.literal(EnabledActionTypes.Any)]), + { + defaultValue: [AllowedHosts.Any], + } +); + export const configSchema = schema.object({ allowedHosts: schema.arrayOf( schema.oneOf([schema.string({ hostname: true }), schema.literal(AllowedHosts.Any)]), @@ -71,12 +83,7 @@ export const configSchema = schema.object({ defaultValue: [AllowedHosts.Any], } ), - enabledActionTypes: schema.arrayOf( - schema.oneOf([schema.string(), schema.literal(EnabledActionTypes.Any)]), - { - defaultValue: [AllowedHosts.Any], - } - ), + enabledActionTypes: enabledConnectorTypesSchema, preconfiguredAlertHistoryEsIndex: schema.boolean({ defaultValue: false }), preconfigured: schema.recordOf(schema.string(), preconfiguredActionSchema, { defaultValue: {}, @@ -126,9 +133,15 @@ export const configSchema = schema.object({ }) ), enableFooterInEmail: schema.boolean({ defaultValue: true }), + queued: schema.maybe( + schema.object({ + max: schema.maybe(schema.number({ min: MIN_QUEUED_MAX, defaultValue: DEFAULT_QUEUED_MAX })), + }) + ), }); export type ActionsConfig = TypeOf; +export type EnabledConnectorTypes = TypeOf; // It would be nicer to add the proxyBypassHosts / proxyOnlyHosts restriction on // simultaneous usage in the config validator directly, but there's no good way to express diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index 72903ca433b4e..162297a9a55cf 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -15,12 +15,24 @@ import { asHttpRequestExecutionSource, asSavedObjectExecutionSource, } from './lib/action_execution_source'; +import { actionsConfigMock } from './actions_config.mock'; const mockTaskManager = taskManagerMock.createStart(); const savedObjectsClient = savedObjectsClientMock.create(); const request = {} as KibanaRequest; +const mockActionsConfig = actionsConfigMock.create(); -beforeEach(() => jest.resetAllMocks()); +beforeEach(() => { + jest.resetAllMocks(); + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 0, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValue(10); +}); describe('bulkExecute()', () => { test('schedules the action with all given parameters', async () => { @@ -30,6 +42,7 @@ describe('bulkExecute()', () => { actionTypeRegistry, isESOCanEncrypt: true, inMemoryConnectors: [], + configurationUtilities: mockActionsConfig, }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ @@ -63,6 +76,7 @@ describe('bulkExecute()', () => { executionId: '123abc', apiKey: Buffer.from('123:abc').toString('base64'), source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -118,6 +132,7 @@ describe('bulkExecute()', () => { actionTypeRegistry, isESOCanEncrypt: true, inMemoryConnectors: [], + configurationUtilities: mockActionsConfig, }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ @@ -153,6 +168,7 @@ describe('bulkExecute()', () => { consumer: 'test-consumer', apiKey: Buffer.from('123:abc').toString('base64'), source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -209,6 +225,7 @@ describe('bulkExecute()', () => { actionTypeRegistry, isESOCanEncrypt: true, inMemoryConnectors: [], + configurationUtilities: mockActionsConfig, }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ @@ -248,6 +265,7 @@ describe('bulkExecute()', () => { typeId: 'some-typeId', }, ], + actionTypeId: 'mock-action', }, ]); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( @@ -304,6 +322,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); const source = { type: 'alert', id: uuidv4() }; @@ -339,6 +358,7 @@ describe('bulkExecute()', () => { executionId: '123abc', apiKey: Buffer.from('123:abc').toString('base64'), source: asSavedObjectExecutionSource(source), + actionTypeId: 'mock-action', }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -401,6 +421,7 @@ describe('bulkExecute()', () => { isSystemAction: true, }, ], + configurationUtilities: mockActionsConfig, }); const source = { type: 'alert', id: uuidv4() }; @@ -436,6 +457,7 @@ describe('bulkExecute()', () => { executionId: 'system-connector-.casesabc', apiKey: Buffer.from('system-connector-test.system-action:abc').toString('base64'), source: asSavedObjectExecutionSource(source), + actionTypeId: 'mock-action', }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -498,6 +520,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); const source = { type: 'alert', id: uuidv4() }; @@ -541,6 +564,7 @@ describe('bulkExecute()', () => { typeId: 'some-typeId', }, ], + actionTypeId: 'mock-action', }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -616,6 +640,7 @@ describe('bulkExecute()', () => { isSystemAction: true, }, ], + configurationUtilities: mockActionsConfig, }); const source = { type: 'alert', id: uuidv4() }; @@ -659,6 +684,7 @@ describe('bulkExecute()', () => { typeId: 'some-typeId', }, ], + actionTypeId: 'mock-action', }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -723,6 +749,7 @@ describe('bulkExecute()', () => { isESOCanEncrypt: false, actionTypeRegistry: actionTypeRegistryMock.create(), inMemoryConnectors: [], + configurationUtilities: mockActionsConfig, }); await expect( executeFn(savedObjectsClient, [ @@ -733,6 +760,7 @@ describe('bulkExecute()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -746,6 +774,7 @@ describe('bulkExecute()', () => { isESOCanEncrypt: true, actionTypeRegistry: actionTypeRegistryMock.create(), inMemoryConnectors: [], + configurationUtilities: mockActionsConfig, }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ @@ -770,6 +799,7 @@ describe('bulkExecute()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -784,6 +814,7 @@ describe('bulkExecute()', () => { isESOCanEncrypt: true, actionTypeRegistry: mockedActionTypeRegistry, inMemoryConnectors: [], + configurationUtilities: mockActionsConfig, }); mockedActionTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => { throw new Error('Fail'); @@ -810,6 +841,7 @@ describe('bulkExecute()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); @@ -833,6 +865,7 @@ describe('bulkExecute()', () => { isSystemAction: false, }, ], + configurationUtilities: mockActionsConfig, }); mockedActionTypeRegistry.isActionExecutable.mockImplementation(() => true); savedObjectsClient.bulkGet.mockResolvedValueOnce({ @@ -868,6 +901,7 @@ describe('bulkExecute()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', }, ]); @@ -892,6 +926,7 @@ describe('bulkExecute()', () => { isSystemAction: true, }, ], + configurationUtilities: mockActionsConfig, }); mockedActionTypeRegistry.isActionExecutable.mockImplementation(() => true); savedObjectsClient.bulkGet.mockResolvedValueOnce({ @@ -927,9 +962,64 @@ describe('bulkExecute()', () => { executionId: '123abc', apiKey: null, source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', }, ]); expect(mockedActionTypeRegistry.ensureActionTypeEnabled).not.toHaveBeenCalled(); }); + + test('returns queuedActionsLimitError response when the max number of queued actions has been reached', async () => { + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 2, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValueOnce(2); + const executeFn = createBulkExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + actionTypeRegistry: actionTypeRegistryMock.create(), + isESOCanEncrypt: true, + inMemoryConnectors: [], + configurationUtilities: mockActionsConfig, + }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [], + }); + savedObjectsClient.bulkCreate.mockResolvedValueOnce({ + saved_objects: [], + }); + expect( + await executeFn(savedObjectsClient, [ + { + id: '123', + params: { baz: false }, + spaceId: 'default', + executionId: '123abc', + apiKey: null, + source: asHttpRequestExecutionSource(request), + actionTypeId: 'mock-action', + }, + ]) + ).toMatchInlineSnapshot(` + Object { + "errors": true, + "items": Array [ + Object { + "actionTypeId": "mock-action", + "id": "123", + "response": "queuedActionsLimitError", + }, + ], + } + `); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [], + ] + `); + }); }); diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 3b4233ddf5710..04d029f83c577 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -16,12 +16,15 @@ import { import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; +import { ActionsConfigurationUtilities } from './actions_config'; +import { hasReachedTheQueuedActionsLimit } from './lib/has_reached_queued_actions_limit'; interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; isESOCanEncrypt: boolean; actionTypeRegistry: ActionTypeRegistryContract; inMemoryConnectors: InMemoryConnector[]; + configurationUtilities: ActionsConfigurationUtilities; } export interface ExecuteOptions @@ -30,6 +33,7 @@ export interface ExecuteOptions spaceId: string; apiKey: string | null; executionId: string; + actionTypeId: string; } interface ActionTaskParams @@ -54,12 +58,29 @@ export type BulkExecutionEnqueuer = ( actionsToExectute: ExecuteOptions[] ) => Promise; +export enum ExecutionResponseType { + SUCCESS = 'success', + QUEUED_ACTIONS_LIMIT_ERROR = 'queuedActionsLimitError', +} + +export interface ExecutionResponse { + errors: boolean; + items: ExecutionResponseItem[]; +} + +export interface ExecutionResponseItem { + id: string; + actionTypeId: string; + response: ExecutionResponseType; +} + export function createBulkExecutionEnqueuerFunction({ taskManager, actionTypeRegistry, isESOCanEncrypt, inMemoryConnectors, -}: CreateExecuteFunctionOptions): BulkExecutionEnqueuer { + configurationUtilities, +}: CreateExecuteFunctionOptions): BulkExecutionEnqueuer { return async function execute( unsecuredSavedObjectsClient: SavedObjectsClientContract, actionsToExecute: ExecuteOptions[] @@ -70,6 +91,19 @@ export function createBulkExecutionEnqueuerFunction({ ); } + const { hasReachedLimit, numberOverLimit } = await hasReachedTheQueuedActionsLimit( + taskManager, + configurationUtilities, + actionsToExecute.length + ); + let actionsOverLimit: ExecuteOptions[] = []; + if (hasReachedLimit) { + actionsOverLimit = actionsToExecute.splice( + actionsToExecute.length - numberOverLimit, + numberOverLimit + ); + } + const actionTypeIds: Record = {}; const spaceIds: Record = {}; const connectorIsInMemory: Record = {}; @@ -144,6 +178,22 @@ export function createBulkExecutionEnqueuerFunction({ }; }); await taskManager.bulkSchedule(taskInstances); + return { + errors: actionsOverLimit.length > 0, + items: actionsToExecute + .map((a) => ({ + id: a.id, + actionTypeId: a.actionTypeId, + response: ExecutionResponseType.SUCCESS, + })) + .concat( + actionsOverLimit.map((a) => ({ + id: a.id, + actionTypeId: a.actionTypeId, + response: ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR, + })) + ), + }; }; } diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts index 2bbfab40b7318..bce0647389396 100644 --- a/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts @@ -14,11 +14,23 @@ import { asNotificationExecutionSource, asSavedObjectExecutionSource, } from './lib/action_execution_source'; +import { actionsConfigMock } from './actions_config.mock'; const mockTaskManager = taskManagerMock.createStart(); const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); +const mockActionsConfig = actionsConfigMock.create(); -beforeEach(() => jest.resetAllMocks()); +beforeEach(() => { + jest.resetAllMocks(); + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 0, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValue(10); +}); describe('bulkExecute()', () => { test.each([ @@ -42,6 +54,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ @@ -154,6 +167,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ @@ -278,6 +292,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ @@ -426,6 +441,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); await expect( executeFn(internalSavedObjectsRepository, [ @@ -468,6 +484,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); mockedConnectorTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => { throw new Error('Fail'); @@ -521,6 +538,7 @@ describe('bulkExecute()', () => { secrets: {}, }, ], + configurationUtilities: mockActionsConfig, }); await expect( executeFn(internalSavedObjectsRepository, [ @@ -540,4 +558,57 @@ describe('bulkExecute()', () => { ); } ); + + test.each([ + [true, false], + [false, true], + ])( + 'returns queuedActionsLimitError response when the max number of queued actions has been reached: %s, isSystemAction: %s', + async (isPreconfigured, isSystemAction) => { + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 2, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValueOnce(2); + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + inMemoryConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured, + isDeprecated: false, + isSystemAction, + name: 'x', + secrets: {}, + }, + ], + configurationUtilities: mockActionsConfig, + }); + + internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ + saved_objects: [], + }); + expect( + await executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), + }, + ]) + ).toEqual({ errors: true, items: [{ id: '123', response: 'queuedActionsLimitError' }] }); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [], + ] + `); + } + ); }); diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts index 585f442c68e2f..a64a2494e5077 100644 --- a/x-pack/plugins/actions/server/create_unsecured_execute_function.ts +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts @@ -14,6 +14,9 @@ import { import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; +import { ExecutionResponseItem, ExecutionResponseType } from './create_execute_function'; +import { ActionsConfigurationUtilities } from './actions_config'; +import { hasReachedTheQueuedActionsLimit } from './lib/has_reached_queued_actions_limit'; // This allowlist should only contain connector types that don't require API keys for // execution. @@ -22,6 +25,7 @@ interface CreateBulkUnsecuredExecuteFunctionOptions { taskManager: TaskManagerStartContract; connectorTypeRegistry: ConnectorTypeRegistryContract; inMemoryConnectors: InMemoryConnector[]; + configurationUtilities: ActionsConfigurationUtilities; } export interface ExecuteOptions @@ -29,6 +33,11 @@ export interface ExecuteOptions id: string; } +export interface ExecutionResponse { + errors: boolean; + items: ExecutionResponseItem[]; +} + interface ActionTaskParams extends Pick { apiKey: string | null; @@ -43,11 +52,25 @@ export function createBulkUnsecuredExecutionEnqueuerFunction({ taskManager, connectorTypeRegistry, inMemoryConnectors, -}: CreateBulkUnsecuredExecuteFunctionOptions): BulkUnsecuredExecutionEnqueuer { + configurationUtilities, +}: CreateBulkUnsecuredExecuteFunctionOptions): BulkUnsecuredExecutionEnqueuer { return async function execute( internalSavedObjectsRepository: ISavedObjectsRepository, actionsToExecute: ExecuteOptions[] ) { + const { hasReachedLimit, numberOverLimit } = await hasReachedTheQueuedActionsLimit( + taskManager, + configurationUtilities, + actionsToExecute.length + ); + let actionsOverLimit: ExecuteOptions[] = []; + if (hasReachedLimit) { + actionsOverLimit = actionsToExecute.splice( + actionsToExecute.length - numberOverLimit, + numberOverLimit + ); + } + const connectorTypeIds: Record = {}; const connectorIds = [...new Set(actionsToExecute.map((action) => action.id))]; @@ -131,6 +154,23 @@ export function createBulkUnsecuredExecutionEnqueuerFunction({ }; }); await taskManager.bulkSchedule(taskInstances); + + return { + errors: actionsOverLimit.length > 0, + items: actionsToExecute + .map((a) => ({ + id: a.id, + response: ExecutionResponseType.SUCCESS, + actionTypeId: connectorTypeIds[a.id], + })) + .concat( + actionsOverLimit.map((a) => ({ + id: a.id, + response: ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR, + actionTypeId: connectorTypeIds[a.id], + })) + ), + }; }; } diff --git a/x-pack/plugins/actions/server/lib/axios_utils.test.ts b/x-pack/plugins/actions/server/lib/axios_utils.test.ts index 1f94e610f60bf..7423042e68228 100644 --- a/x-pack/plugins/actions/server/lib/axios_utils.test.ts +++ b/x-pack/plugins/actions/server/lib/axios_utils.test.ts @@ -66,7 +66,6 @@ describe('request', () => { expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'get', - data: {}, httpAgent: undefined, httpsAgent: expect.any(HttpsAgent), proxy: false, @@ -100,7 +99,6 @@ describe('request', () => { expect(axiosMock).toHaveBeenCalledWith(TestUrl, { method: 'get', - data: {}, httpAgent, httpsAgent, proxy: false, @@ -132,7 +130,6 @@ describe('request', () => { expect(axiosMock).toHaveBeenCalledWith('https://testProxy', { method: 'get', - data: {}, httpAgent: undefined, httpsAgent: expect.any(HttpsAgent), proxy: false, diff --git a/x-pack/plugins/actions/server/lib/axios_utils.ts b/x-pack/plugins/actions/server/lib/axios_utils.ts index 181893db15f0c..163eac3eada2b 100644 --- a/x-pack/plugins/actions/server/lib/axios_utils.ts +++ b/x-pack/plugins/actions/server/lib/axios_utils.ts @@ -51,7 +51,7 @@ export const request = async ({ ...config, method, headers, - data: data ?? {}, + ...(data ? { data } : {}), // use httpAgent and httpsAgent and set axios proxy: false, to be able to handle fail on invalid certs httpAgent, httpsAgent, diff --git a/x-pack/plugins/actions/server/lib/has_reached_queued_action_limit.test.ts b/x-pack/plugins/actions/server/lib/has_reached_queued_action_limit.test.ts new file mode 100644 index 0000000000000..67772d33f872e --- /dev/null +++ b/x-pack/plugins/actions/server/lib/has_reached_queued_action_limit.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { actionsConfigMock } from '../actions_config.mock'; +import { hasReachedTheQueuedActionsLimit } from './has_reached_queued_actions_limit'; + +const mockTaskManager = taskManagerMock.createStart(); +const mockActionsConfig = actionsConfigMock.create(); + +beforeEach(() => { + jest.resetAllMocks(); + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 0, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValue(10); +}); + +describe('hasReachedTheQueuedActionsLimit()', () => { + test('returns true if the number of queued actions is greater than the config limit', async () => { + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 3, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValueOnce(2); + + expect(await hasReachedTheQueuedActionsLimit(mockTaskManager, mockActionsConfig, 1)).toEqual({ + hasReachedLimit: true, + numberOverLimit: 2, + }); + }); + + test('returns true if the number of queued actions is equal the config limit', async () => { + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 2, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValueOnce(3); + + expect(await hasReachedTheQueuedActionsLimit(mockTaskManager, mockActionsConfig, 1)).toEqual({ + hasReachedLimit: true, + numberOverLimit: 0, + }); + }); + + test('returns false if the number of queued actions is less than the config limit', async () => { + mockTaskManager.aggregate.mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 1, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: {}, + }); + mockActionsConfig.getMaxQueued.mockReturnValueOnce(3); + + expect(await hasReachedTheQueuedActionsLimit(mockTaskManager, mockActionsConfig, 1)).toEqual({ + hasReachedLimit: false, + numberOverLimit: 0, + }); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/has_reached_queued_actions_limit.ts b/x-pack/plugins/actions/server/lib/has_reached_queued_actions_limit.ts new file mode 100644 index 0000000000000..3c88b82712925 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/has_reached_queued_actions_limit.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; +import { ActionsConfigurationUtilities } from '../actions_config'; + +export async function hasReachedTheQueuedActionsLimit( + taskManager: TaskManagerStartContract, + configurationUtilities: ActionsConfigurationUtilities, + numberOfActions: number +) { + const limit = configurationUtilities.getMaxQueued(); + const { + hits: { total }, + } = await taskManager.aggregate({ + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { + 'task.scope': 'actions', + }, + }, + ], + }, + }, + }, + }, + aggs: {}, + }); + const tasks = typeof total === 'number' ? total : total?.value ?? 0; + const numberOfTasks = tasks + numberOfActions; + const hasReachedLimit = numberOfTasks >= limit; + return { + hasReachedLimit, + numberOverLimit: hasReachedLimit ? numberOfTasks - limit : 0, + }; +} diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index ad26114cf7d07..70a2cfd9f8e85 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -31,6 +31,7 @@ const createSetupMock = () => { getCaseConnectorClass: jest.fn(), getActionsHealth: jest.fn(), getActionsConfigurationUtilities: jest.fn(), + setEnabledConnectorTypes: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 66d75da5fd7cf..dd936600d7055 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -15,6 +15,7 @@ import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { eventLogMock } from '@kbn/event-log-plugin/server/mocks'; +import { serverlessPluginMock } from '@kbn/serverless/server/mocks'; import { ActionType, ActionsApiRequestHandlerContext, ExecutorType } from './types'; import { ActionsConfig } from './config'; import { @@ -348,6 +349,163 @@ describe('Actions Plugin', () => { expect(pluginSetup.isPreconfiguredConnector('anotherConnectorId')).toEqual(false); }); }); + + describe('setEnabledConnectorTypes (works only on serverless)', () => { + function setup(config: ActionsConfig) { + context = coreMock.createPluginInitializerContext(config); + plugin = new ActionsPlugin(context); + coreSetup = coreMock.createSetup(); + pluginsSetup = { + taskManager: taskManagerMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(), + licensing: licensingMock.createSetup(), + eventLog: eventLogMock.createSetup(), + usageCollection: usageCollectionPluginMock.createSetupContract(), + features: featuresPluginMock.createSetup(), + serverless: serverlessPluginMock.createSetupContract(), + }; + } + + it('should set connector type enabled', async () => { + setup(getConfig()); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup); + const coreStart = coreMock.createStart(); + const pluginsStart = { + licensing: licensingMock.createStart(), + taskManager: taskManagerMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + eventLog: eventLogMock.createStart(), + }; + const pluginStart = plugin.start(coreStart, pluginsStart); + + pluginSetup.registerType({ + id: '.server-log', + name: 'Server log', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + pluginSetup.registerType({ + id: '.slack', + name: 'Slack', + minimumLicenseRequired: 'gold', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + pluginSetup.setEnabledConnectorTypes(['.server-log']); + expect(pluginStart.isActionTypeEnabled('.server-log')).toBeTruthy(); + expect(pluginStart.isActionTypeEnabled('.slack')).toBeFalsy(); + }); + + it('should set all the connector types enabled when null or ["*"] passed', async () => { + setup(getConfig()); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup); + const coreStart = coreMock.createStart(); + const pluginsStart = { + licensing: licensingMock.createStart(), + taskManager: taskManagerMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + eventLog: eventLogMock.createStart(), + }; + const pluginStart = plugin.start(coreStart, pluginsStart); + + pluginSetup.registerType({ + id: '.server-log', + name: 'Server log', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + pluginSetup.registerType({ + id: '.index', + name: 'Index', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + pluginSetup.setEnabledConnectorTypes(['*']); + expect(pluginStart.isActionTypeEnabled('.server-log')).toBeTruthy(); + expect(pluginStart.isActionTypeEnabled('.index')).toBeTruthy(); + }); + + it('should set all the connector types disabled when [] passed', async () => { + setup(getConfig()); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup); + const coreStart = coreMock.createStart(); + const pluginsStart = { + licensing: licensingMock.createStart(), + taskManager: taskManagerMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + eventLog: eventLogMock.createStart(), + }; + const pluginStart = plugin.start(coreStart, pluginsStart); + + pluginSetup.registerType({ + id: '.server-log', + name: 'Server log', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + pluginSetup.registerType({ + id: '.index', + name: 'Index', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + pluginSetup.setEnabledConnectorTypes([]); + expect(pluginStart.isActionTypeEnabled('.server-log')).toBeFalsy(); + expect(pluginStart.isActionTypeEnabled('.index')).toBeFalsy(); + }); + + it('should throw if the enabledActionTypes is already set by the config', async () => { + setup({ ...getConfig(), enabledActionTypes: ['.email'] }); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup); + + expect(() => pluginSetup.setEnabledConnectorTypes(['.index'])).toThrow( + "Enabled connector types can be set only if they haven't already been set in the config" + ); + }); + }); }); describe('start()', () => { @@ -396,6 +554,41 @@ describe('Actions Plugin', () => { }; }); + it('should throw when there is an invalid connector type in enabledActionTypes', async () => { + const pluginSetup = await plugin.setup(coreSetup, { + ...pluginsSetup, + encryptedSavedObjects: { + ...pluginsSetup.encryptedSavedObjects, + canEncrypt: true, + }, + serverless: serverlessPluginMock.createSetupContract(), + }); + + pluginSetup.registerType({ + id: '.server-log', + name: 'Server log', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + + pluginSetup.setEnabledConnectorTypes(['.server-log', 'non-existing']); + + await expect(async () => + plugin.start(coreStart, { + ...pluginsStart, + serverless: serverlessPluginMock.createStartContract(), + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Action type \\"non-existing\\" is not registered."` + ); + }); + describe('getActionsClientWithRequest()', () => { it('should not throw error when ESO plugin has encryption key', async () => { await plugin.setup(coreSetup, { diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index b8b88b05049ca..60a27eb04e411 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -40,7 +40,8 @@ import { } from '@kbn/event-log-plugin/server'; import { MonitoringCollectionSetup } from '@kbn/monitoring-collection-plugin/server'; -import { ActionsConfig, getValidatedConfig } from './config'; +import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/server'; +import { ActionsConfig, AllowedHosts, EnabledConnectorTypes, getValidatedConfig } from './config'; import { resolveCustomHosts } from './lib/custom_host_settings'; import { ActionsClient } from './actions_client/actions_client'; import { ActionTypeRegistry } from './action_type_registry'; @@ -100,10 +101,8 @@ import { createSubActionConnectorFramework } from './sub_action_framework'; import { IServiceAbstract, SubActionConnectorType } from './sub_action_framework/types'; import { SubActionConnector } from './sub_action_framework/sub_action_connector'; import { CaseConnector } from './sub_action_framework/case'; -import { - type IUnsecuredActionsClient, - UnsecuredActionsClient, -} from './unsecured_actions_client/unsecured_actions_client'; +import type { IUnsecuredActionsClient } from './unsecured_actions_client/unsecured_actions_client'; +import { UnsecuredActionsClient } from './unsecured_actions_client/unsecured_actions_client'; import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function'; import { createSystemConnectors } from './create_system_actions'; @@ -130,6 +129,7 @@ export interface PluginSetupContract { getCaseConnectorClass: () => IServiceAbstract; getActionsHealth: () => { hasPermanentEncryptionKey: boolean }; getActionsConfigurationUtilities: () => ActionsConfigurationUtilities; + setEnabledConnectorTypes: (connectorTypes: EnabledConnectorTypes) => void; } export interface PluginStartContract { @@ -169,6 +169,7 @@ export interface ActionsPluginsSetup { features: FeaturesPluginSetup; spaces?: SpacesPluginSetup; monitoringCollection?: MonitoringCollectionSetup; + serverless?: ServerlessPluginSetup; } export interface ActionsPluginsStart { @@ -178,6 +179,7 @@ export interface ActionsPluginsStart { eventLog: IEventLogClientService; spaces?: SpacesPluginStart; security?: SecurityPluginStart; + serverless?: ServerlessPluginStart; } const includedHiddenTypes = [ @@ -299,7 +301,7 @@ export class ActionsPlugin implements Plugin( 'actions', - this.createRouteHandlerContext(core) + this.createRouteHandlerContext(core, actionsConfigUtils) ); if (usageCollection) { const eventLogIndex = this.eventLogService.getIndexPattern(); @@ -375,6 +377,20 @@ export class ActionsPlugin implements Plugin actionsConfigUtils, + setEnabledConnectorTypes: (connectorTypes) => { + if ( + !!plugins.serverless && + this.actionsConfig.enabledActionTypes.length === 1 && + this.actionsConfig.enabledActionTypes[0] === AllowedHosts.Any + ) { + this.actionsConfig.enabledActionTypes.pop(); + this.actionsConfig.enabledActionTypes.push(...connectorTypes); + } else { + throw new Error( + "Enabled connector types can be set only if they haven't already been set in the config" + ); + } + }, }; } @@ -388,8 +404,11 @@ export class ActionsPlugin implements Plugin { return this.actionTypeRegistry!.isActionTypeEnabled(id, options); @@ -623,7 +647,8 @@ export class ActionsPlugin implements Plugin + core: CoreSetup, + actionsConfigUtils: ActionsConfigurationUtilities ): IContextProvider => { const { actionTypeRegistry, @@ -669,12 +694,14 @@ export class ActionsPlugin implements Plugin { + if ( + !!plugins.serverless && + this.actionsConfig.enabledActionTypes.length > 0 && + this.actionsConfig.enabledActionTypes[0] !== AllowedHosts.Any + ) { + this.actionsConfig.enabledActionTypes.forEach((connectorType) => { + // Throws error if action type doesn't exist + this.actionTypeRegistry?.get(connectorType); + }); + } + }; + public stop() { if (this.licenseState) { this.licenseState.clean(); diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts index a2d87c8a5db4a..baa2cf34c1d41 100644 --- a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts @@ -9,6 +9,7 @@ import { ISavedObjectsRepository } from '@kbn/core/server'; import { BulkUnsecuredExecutionEnqueuer, ExecuteOptions, + ExecutionResponse, } from '../create_unsecured_execute_function'; import { asNotificationExecutionSource } from '../lib'; @@ -24,16 +25,19 @@ const ALLOWED_REQUESTER_IDS = [ export interface UnsecuredActionsClientOpts { internalSavedObjectsRepository: ISavedObjectsRepository; - executionEnqueuer: BulkUnsecuredExecutionEnqueuer; + executionEnqueuer: BulkUnsecuredExecutionEnqueuer; } export interface IUnsecuredActionsClient { - bulkEnqueueExecution: (requesterId: string, actionsToExecute: ExecuteOptions[]) => Promise; + bulkEnqueueExecution: ( + requesterId: string, + actionsToExecute: ExecuteOptions[] + ) => Promise; } export class UnsecuredActionsClient { private readonly internalSavedObjectsRepository: ISavedObjectsRepository; - private readonly executionEnqueuer: BulkUnsecuredExecutionEnqueuer; + private readonly executionEnqueuer: BulkUnsecuredExecutionEnqueuer; constructor(params: UnsecuredActionsClientOpts) { this.executionEnqueuer = params.executionEnqueuer; @@ -43,7 +47,7 @@ export class UnsecuredActionsClient { public async bulkEnqueueExecution( requesterId: string, actionsToExecute: ExecuteOptions[] - ): Promise { + ): Promise { // Check that requesterId is allowed if (!ALLOWED_REQUESTER_IDS.includes(requesterId)) { throw new Error( diff --git a/x-pack/plugins/actions/tsconfig.json b/x-pack/plugins/actions/tsconfig.json index 0f4d2faf03e1a..3beeddef429b2 100644 --- a/x-pack/plugins/actions/tsconfig.json +++ b/x-pack/plugins/actions/tsconfig.json @@ -43,6 +43,7 @@ "@kbn/core-saved-objects-api-server-mocks", "@kbn/core-elasticsearch-server-mocks", "@kbn/core-logging-server-mocks", + "@kbn/serverless" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx index dd2380fedfa32..c18764e45797d 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx @@ -16,7 +16,11 @@ import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { StorageContextProvider } from '@kbn/ml-local-storage'; import { UrlStateProvider } from '@kbn/ml-url-state'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { DatePickerContextProvider, mlTimefilterRefresh$ } from '@kbn/ml-date-picker'; +import { + DatePickerContextProvider, + type DatePickerDependencies, + mlTimefilterRefresh$, +} from '@kbn/ml-date-picker'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { type Observable } from 'rxjs'; @@ -46,16 +50,20 @@ export interface ChangePointDetectionAppStateProps { savedSearch: SavedSearch | null; /** App dependencies */ appDependencies: AiopsAppDependencies; + /** Optional flag to indicate whether kibana is running in serverless */ + isServerless?: boolean; } export const ChangePointDetectionAppState: FC = ({ dataView, savedSearch, appDependencies, + isServerless = false, }) => { - const datePickerDeps = { + const datePickerDeps: DatePickerDependencies = { ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, + isServerless, }; const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection'); diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx index a7c9bc17b6fe9..5ddf65d5b938d 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx @@ -12,7 +12,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { StorageContextProvider } from '@kbn/ml-local-storage'; import { UrlStateProvider } from '@kbn/ml-url-state'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { DatePickerContextProvider } from '@kbn/ml-date-picker'; +import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { DataSourceContext } from '../../hooks/use_data_source'; @@ -35,12 +35,15 @@ export interface LogCategorizationAppStateProps { savedSearch: SavedSearch | null; /** App dependencies */ appDependencies: AiopsAppDependencies; + /** Optional flag to indicate whether kibana is running in serverless */ + isServerless?: boolean; } export const LogCategorizationAppState: FC = ({ dataView, savedSearch, appDependencies, + isServerless = false, }) => { if (!dataView) return null; @@ -50,9 +53,10 @@ export const LogCategorizationAppState: FC = ({ return <>{warning}; } - const datePickerDeps = { + const datePickerDeps: DatePickerDependencies = { ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, + isServerless, }; return ( diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index 5413c264a3127..9539ff607e05e 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -71,7 +71,7 @@ export const LogCategorizationPage: FC = () => { const [globalState, setGlobalState] = useUrlState('_g'); const [selectedField, setSelectedField] = useState(); const [selectedCategory, setSelectedCategory] = useState(null); - const [selectedSavedSearch, setSelectedDataView] = useState(savedSearch); + const [selectedSavedSearch, setSelectedSavedSearch] = useState(savedSearch); const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); const [eventRate, setEventRate] = useState([]); @@ -91,7 +91,7 @@ export const LogCategorizationPage: FC = () => { useEffect(() => { if (savedSearch) { - setSelectedDataView(savedSearch); + setSelectedSavedSearch(savedSearch); } }, [savedSearch]); @@ -114,7 +114,7 @@ export const LogCategorizationPage: FC = () => { // When the user loads saved search and then clear or modify the query // we should remove the saved search and replace it with the index pattern id if (selectedSavedSearch !== null) { - setSelectedDataView(null); + setSelectedSavedSearch(null); } setUrlState({ diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index 212d0465b9cc0..0a41900feb9fb 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -13,7 +13,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { StorageContextProvider } from '@kbn/ml-local-storage'; import { UrlStateProvider } from '@kbn/ml-url-state'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { DatePickerContextProvider } from '@kbn/ml-date-picker'; +import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; @@ -40,6 +40,8 @@ export interface LogRateAnalysisAppStateProps { appDependencies: AiopsAppDependencies; /** Option to make main histogram sticky */ stickyHistogram?: boolean; + /** Optional flag to indicate whether kibana is running in serverless */ + isServerless?: boolean; } export const LogRateAnalysisAppState: FC = ({ @@ -47,6 +49,7 @@ export const LogRateAnalysisAppState: FC = ({ savedSearch, appDependencies, stickyHistogram, + isServerless = false, }) => { if (!dataView) return null; @@ -56,9 +59,10 @@ export const LogRateAnalysisAppState: FC = ({ return <>{warning}; } - const datePickerDeps = { + const datePickerDeps: DatePickerDependencies = { ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, + isServerless, }; return ( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx index ec258081d840d..b19e72e7c4b5a 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx @@ -57,6 +57,8 @@ export interface LogRateAnalysisContentWrapperProps { * @param d Log rate analysis results data */ onAnalysisCompleted?: (d: LogRateAnalysisResultsData) => void; + /** Optional flag to indicate whether kibana is running in serverless */ + isServerless?: boolean; } export const LogRateAnalysisContentWrapper: FC = ({ @@ -70,6 +72,7 @@ export const LogRateAnalysisContentWrapper: FC { if (!dataView) return null; diff --git a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts index 6b8cc0700f28f..aa364a416a046 100644 --- a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts +++ b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts @@ -114,6 +114,7 @@ export interface AiopsAppDependencies { presentationUtil?: PresentationUtilPluginStart; embeddable?: EmbeddableStart; cases?: CasesUiStart; + isServerless?: boolean; } /** diff --git a/x-pack/plugins/aiops/public/hooks/use_search.ts b/x-pack/plugins/aiops/public/hooks/use_search.ts index 9b48adeb27eb0..609beb6774bc9 100644 --- a/x-pack/plugins/aiops/public/hooks/use_search.ts +++ b/x-pack/plugins/aiops/public/hooks/use_search.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { useMemo } from 'react'; + import type { DataView } from '@kbn/data-views-plugin/public'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; @@ -24,12 +26,16 @@ export const useSearch = ( }, } = useAiopsAppContext(); - const searchData = getEsQueryFromSavedSearch({ - dataView, - uiSettings, - savedSearch, - filterManager, - }); + const searchData = useMemo( + () => + getEsQueryFromSavedSearch({ + dataView, + uiSettings, + savedSearch, + filterManager, + }), + [dataView, uiSettings, savedSearch, filterManager] + ); if (searchData === undefined || (aiopsListState && aiopsListState.searchString !== '')) { if (aiopsListState?.filters && readOnly === false) { diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts index aea967a056028..e58b795863e48 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts @@ -188,6 +188,7 @@ describe('mappingFromFieldMap', () => { dynamic: 'strict', properties: { '@timestamp': { + ignore_malformed: false, type: 'date', }, event: { diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts index 038fdaf48fbf5..1d5121883df69 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts @@ -43,6 +43,10 @@ export function mappingFromFieldMap( : rest; set(mappings.properties, field.name.split('.').join('.properties.'), mapped); + + if (name === '@timestamp') { + set(mappings.properties, `${name}.ignore_malformed`, false); + } }); return mappings; diff --git a/x-pack/plugins/alerting/common/default_rule_aggregation.test.ts b/x-pack/plugins/alerting/common/default_rule_aggregation.test.ts deleted file mode 100644 index e1c38587d2dd0..0000000000000 --- a/x-pack/plugins/alerting/common/default_rule_aggregation.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - getDefaultRuleAggregation, - formatDefaultAggregationResult, -} from './default_rule_aggregation'; - -describe('getDefaultRuleAggregation', () => { - it('should return aggregation with default maxTags', () => { - const result = getDefaultRuleAggregation(); - expect(result.tags).toEqual({ - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, - }); - }); - - it('should return aggregation with custom maxTags', () => { - const result = getDefaultRuleAggregation({ maxTags: 100 }); - expect(result.tags).toEqual({ - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 100 }, - }); - }); -}); - -describe('formatDefaultAggregationResult', () => { - it('should format aggregation result', () => { - const result = formatDefaultAggregationResult({ - status: { - buckets: [ - { key: 'active', doc_count: 8 }, - { key: 'error', doc_count: 6 }, - { key: 'ok', doc_count: 10 }, - { key: 'pending', doc_count: 4 }, - { key: 'unknown', doc_count: 2 }, - { key: 'warning', doc_count: 1 }, - ], - }, - outcome: { - buckets: [ - { key: 'succeeded', doc_count: 2 }, - { key: 'failed', doc_count: 4 }, - { key: 'warning', doc_count: 6 }, - ], - }, - enabled: { - buckets: [ - { key: 0, key_as_string: '0', doc_count: 2 }, - { key: 1, key_as_string: '1', doc_count: 28 }, - ], - }, - muted: { - buckets: [ - { key: 0, key_as_string: '0', doc_count: 27 }, - { key: 1, key_as_string: '1', doc_count: 3 }, - ], - }, - snoozed: { - count: { - doc_count: 5, - }, - }, - tags: { - buckets: [ - { - key: 'a', - doc_count: 10, - }, - { - key: 'b', - doc_count: 20, - }, - { - key: 'c', - doc_count: 30, - }, - ], - }, - }); - - expect(result).toEqual( - expect.objectContaining({ - ruleExecutionStatus: { - active: 8, - error: 6, - ok: 10, - pending: 4, - unknown: 2, - warning: 1, - }, - ruleLastRunOutcome: { - succeeded: 2, - failed: 4, - warning: 6, - }, - ruleEnabledStatus: { - enabled: 28, - disabled: 2, - }, - ruleMutedStatus: { - muted: 3, - unmuted: 27, - }, - ruleSnoozedStatus: { - snoozed: 5, - }, - ruleTags: ['a', 'b', 'c'], - }) - ); - }); -}); diff --git a/x-pack/plugins/alerting/common/default_rule_aggregation.ts b/x-pack/plugins/alerting/common/default_rule_aggregation.ts deleted file mode 100644 index 1b72657ce2dbe..0000000000000 --- a/x-pack/plugins/alerting/common/default_rule_aggregation.ts +++ /dev/null @@ -1,172 +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 { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { - RuleExecutionStatusValues, - RuleLastRunOutcomeValues, - RuleAggregationFormattedResult, -} from './rule'; - -export interface DefaultRuleAggregationResult { - status: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; - outcome: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; - muted: { - buckets: Array<{ - key: number; - key_as_string: string; - doc_count: number; - }>; - }; - enabled: { - buckets: Array<{ - key: number; - key_as_string: string; - doc_count: number; - }>; - }; - snoozed: { - count: { - doc_count: number; - }; - }; - tags: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; -} - -interface GetDefaultRuleAggregationParams { - maxTags?: number; -} - -export const getDefaultRuleAggregation = ( - params?: GetDefaultRuleAggregationParams -): Record => { - const { maxTags = 50 } = params || {}; - return { - status: { - terms: { field: 'alert.attributes.executionStatus.status' }, - }, - outcome: { - terms: { field: 'alert.attributes.lastRun.outcome' }, - }, - enabled: { - terms: { field: 'alert.attributes.enabled' }, - }, - muted: { - terms: { field: 'alert.attributes.muteAll' }, - }, - tags: { - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: maxTags }, - }, - snoozed: { - nested: { - path: 'alert.attributes.snoozeSchedule', - }, - aggs: { - count: { - filter: { - exists: { - field: 'alert.attributes.snoozeSchedule.duration', - }, - }, - }, - }, - }, - }; -}; - -export const formatDefaultAggregationResult = ( - aggregations: DefaultRuleAggregationResult -): RuleAggregationFormattedResult => { - if (!aggregations) { - // Return a placeholder with all zeroes - const placeholder: RuleAggregationFormattedResult = { - ruleExecutionStatus: {}, - ruleLastRunOutcome: {}, - ruleEnabledStatus: { - enabled: 0, - disabled: 0, - }, - ruleMutedStatus: { - muted: 0, - unmuted: 0, - }, - ruleSnoozedStatus: { snoozed: 0 }, - ruleTags: [], - }; - - for (const key of RuleExecutionStatusValues) { - placeholder.ruleExecutionStatus[key] = 0; - } - - return placeholder; - } - - const ruleExecutionStatus = aggregations.status.buckets.map(({ key, doc_count: docCount }) => ({ - [key]: docCount, - })); - - const ruleLastRunOutcome = aggregations.outcome.buckets.map(({ key, doc_count: docCount }) => ({ - [key]: docCount, - })); - - const enabledBuckets = aggregations.enabled.buckets; - const mutedBuckets = aggregations.muted.buckets; - - const result: RuleAggregationFormattedResult = { - ruleExecutionStatus: ruleExecutionStatus.reduce( - (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), - {} - ), - ruleLastRunOutcome: ruleLastRunOutcome.reduce( - (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), - {} - ), - ruleEnabledStatus: { - enabled: enabledBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, - disabled: enabledBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, - }, - ruleMutedStatus: { - muted: mutedBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, - unmuted: mutedBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, - }, - ruleSnoozedStatus: { - snoozed: aggregations.snoozed?.count?.doc_count ?? 0, - }, - ruleTags: [], - }; - - // Fill missing keys with zeroes - for (const key of RuleExecutionStatusValues) { - if (!result.ruleExecutionStatus.hasOwnProperty(key)) { - result.ruleExecutionStatus[key] = 0; - } - } - for (const key of RuleLastRunOutcomeValues) { - if (!result.ruleLastRunOutcome.hasOwnProperty(key)) { - result.ruleLastRunOutcome[key] = 0; - } - } - - const tagsBuckets = aggregations.tags?.buckets || []; - result.ruleTags = tagsBuckets.map((bucket) => bucket.key); - - return result; -}; diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 05b9e84ee2b7a..e2e9e477cc4cc 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -34,7 +34,6 @@ export * from './parse_duration'; export * from './execution_log_types'; export * from './rule_snooze_type'; export * from './rrule_type'; -export * from './default_rule_aggregation'; export * from './rule_tags_aggregation'; export * from './iso_weekdays'; export * from './saved_objects/rules/mappings'; diff --git a/x-pack/plugins/alerting/common/parse_duration.test.ts b/x-pack/plugins/alerting/common/parse_duration.test.ts index c661eab2691fe..279676a524550 100644 --- a/x-pack/plugins/alerting/common/parse_duration.test.ts +++ b/x-pack/plugins/alerting/common/parse_duration.test.ts @@ -10,6 +10,7 @@ import { formatDuration, getDurationNumberInItsUnit, getDurationUnitValue, + convertDurationToFrequency, } from './parse_duration'; test('parses seconds', () => { @@ -180,3 +181,26 @@ test('getDurationUnitValue hours', () => { const result = getDurationUnitValue('100h'); expect(result).toEqual('h'); }); + +test('convertDurationToFrequency converts duration', () => { + let result = convertDurationToFrequency('1m'); + expect(result).toEqual(1); + result = convertDurationToFrequency('5m'); + expect(result).toEqual(0.2); + result = convertDurationToFrequency('10s'); + expect(result).toEqual(6); + result = convertDurationToFrequency('1s'); + expect(result).toEqual(60); +}); + +test('convertDurationToFrequency throws when duration is invalid', () => { + expect(() => convertDurationToFrequency('0d')).toThrowErrorMatchingInlineSnapshot( + `"Invalid duration \\"0d\\". Durations must be of the form {number}x. Example: 5s, 5m, 5h or 5d\\""` + ); +}); + +test('convertDurationToFrequency throws when denomination is 0', () => { + expect(() => convertDurationToFrequency('1s', 0)).toThrowErrorMatchingInlineSnapshot( + `"Invalid denomination value: value cannot be 0"` + ); +}); diff --git a/x-pack/plugins/alerting/common/parse_duration.ts b/x-pack/plugins/alerting/common/parse_duration.ts index 3040b601fd64b..afbbf8609e6e3 100644 --- a/x-pack/plugins/alerting/common/parse_duration.ts +++ b/x-pack/plugins/alerting/common/parse_duration.ts @@ -10,6 +10,8 @@ const MINUTES_REGEX = /^[1-9][0-9]*m$/; const HOURS_REGEX = /^[1-9][0-9]*h$/; const DAYS_REGEX = /^[1-9][0-9]*d$/; +const MS_PER_MINUTE = 60 * 1000; + // parse an interval string '{digit*}{s|m|h|d}' into milliseconds export function parseDuration(duration: string): number { const parsed = parseInt(duration, 10); @@ -43,6 +45,19 @@ export function formatDuration(duration: string, fullUnit?: boolean): string { ); } +export function convertDurationToFrequency( + duration: string, + denomination: number = MS_PER_MINUTE +): number { + const durationInMs = parseDuration(duration); + if (denomination === 0) { + throw new Error(`Invalid denomination value: value cannot be 0`); + } + + const intervalInDenominationUnits = durationInMs / denomination; + return 1 / intervalInDenominationUnits; +} + export function getDurationNumberInItsUnit(duration: string): number { return parseInt(duration.replace(/[^0-9.]/g, ''), 10); } diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/index.ts new file mode 100644 index 0000000000000..0bbdbc0ea4069 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { + aggregateRulesRequestBodySchema, + aggregateRulesResponseBodySchema, +} from './schemas/latest'; +export type { AggregateRulesRequestBody, AggregateRulesResponseBody } from './types/latest'; + +export { + aggregateRulesRequestBodySchema as aggregateRulesRequestBodySchemaV1, + aggregateRulesResponseBodySchema as aggregateRulesResponseBodySchemaV1, +} from './schemas/v1'; +export type { + AggregateRulesRequestBody as AggregateRulesRequestBodyV1, + AggregateRulesResponseBody as AggregateRulesResponseBodyV1, + AggregateRulesResponse as AggregateRulesResponseV1, +} from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/v1.ts new file mode 100644 index 0000000000000..d49ccb090d53d --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/v1.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const aggregateRulesRequestBodySchema = schema.object({ + search: schema.maybe(schema.string()), + default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { + defaultValue: 'OR', + }), + search_fields: schema.maybe(schema.arrayOf(schema.string())), + has_reference: schema.maybe( + // use nullable as maybe is currently broken + // in config-schema + schema.nullable( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ) + ), + filter: schema.maybe(schema.string()), +}); + +export const aggregateRulesResponseBodySchema = schema.object({ + rule_execution_status: schema.recordOf(schema.string(), schema.number()), + rule_last_run_outcome: schema.recordOf(schema.string(), schema.number()), + rule_enabled_status: schema.object({ + enabled: schema.number(), + disabled: schema.number(), + }), + rule_muted_status: schema.object({ + muted: schema.number(), + unmuted: schema.number(), + }), + rule_snoozed_status: schema.object({ + snoozed: schema.number(), + }), + rule_tags: schema.arrayOf(schema.string()), +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/types/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/types/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/types/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/types/v1.ts new file mode 100644 index 0000000000000..2dc21a72b3783 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/types/v1.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TypeOf } from '@kbn/config-schema'; +import { aggregateRulesRequestBodySchemaV1, aggregateRulesResponseBodySchemaV1 } from '..'; + +export type AggregateRulesRequestBody = TypeOf; +export type AggregateRulesResponseBody = TypeOf; + +export interface AggregateRulesResponse { + body: AggregateRulesResponseBody; +} diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/index.ts new file mode 100644 index 0000000000000..79955cf7058c5 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + getScheduleFrequencyResponseSchema, + getScheduleFrequencyResponseBodySchema, +} from './schemas/latest'; + +export type { + GetScheduleFrequencyResponse, + GetScheduleFrequencyResponseBody, +} from './types/latest'; + +export { + getScheduleFrequencyResponseSchema as getScheduleFrequencyResponseSchemaV1, + getScheduleFrequencyResponseBodySchema as getScheduleFrequencyResponseBodySchemaV1, +} from './schemas/v1'; + +export type { + GetScheduleFrequencyResponse as GetScheduleFrequencyResponseV1, + GetScheduleFrequencyResponseBody as GetScheduleFrequencyResponseBodyV1, +} from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/schemas/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/schemas/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/schemas/v1.ts new file mode 100644 index 0000000000000..a5afa275a21c8 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/schemas/v1.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 { schema } from '@kbn/config-schema'; + +export const getScheduleFrequencyResponseBodySchema = schema.object({ + total_scheduled_per_minute: schema.number(), + remaining_schedules_per_minute: schema.number(), +}); + +export const getScheduleFrequencyResponseSchema = schema.object({ + body: getScheduleFrequencyResponseBodySchema, +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/types/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/types/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/types/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/types/v1.ts new file mode 100644 index 0000000000000..2d117b03c49b9 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/get_schedule_frequency/types/v1.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TypeOf } from '@kbn/config-schema'; +import { getScheduleFrequencyResponseSchemaV1, getScheduleFrequencyResponseBodySchemaV1 } from '..'; + +export type GetScheduleFrequencyResponseBody = TypeOf< + typeof getScheduleFrequencyResponseBodySchemaV1 +>; + +export type GetScheduleFrequencyResponse = TypeOf; diff --git a/x-pack/plugins/alerting/common/routes/rule/response/constants/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/constants/v1.ts index a241412310482..fbeb08ba6bc7f 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/constants/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/constants/v1.ts @@ -40,6 +40,7 @@ export const ruleExecutionStatusErrorReason = { export const ruleExecutionStatusWarningReason = { MAX_EXECUTABLE_ACTIONS: 'maxExecutableActions', MAX_ALERTS: 'maxAlerts', + MAX_QUEUED_ACTIONS: 'maxQueuedActions', } as const; export type RuleNotifyWhen = typeof ruleNotifyWhen[keyof typeof ruleNotifyWhen]; diff --git a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts index 3aecec56c3a41..2fb82c3558cb7 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts @@ -110,6 +110,7 @@ export const ruleExecutionStatusSchema = schema.object({ reason: schema.oneOf([ schema.literal(ruleExecutionStatusWarningReasonV1.MAX_EXECUTABLE_ACTIONS), schema.literal(ruleExecutionStatusWarningReasonV1.MAX_ALERTS), + schema.literal(ruleExecutionStatusWarningReasonV1.MAX_QUEUED_ACTIONS), ]), message: schema.string(), }) @@ -136,6 +137,7 @@ export const ruleLastRunSchema = schema.object({ schema.literal(ruleExecutionStatusErrorReasonV1.VALIDATE), schema.literal(ruleExecutionStatusWarningReasonV1.MAX_EXECUTABLE_ACTIONS), schema.literal(ruleExecutionStatusWarningReasonV1.MAX_ALERTS), + schema.literal(ruleExecutionStatusWarningReasonV1.MAX_QUEUED_ACTIONS), ]) ) ), diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 57ef90ed99620..5a8e1b275ef7e 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -10,7 +10,7 @@ import type { SavedObjectAttributes, SavedObjectsResolveResponse, } from '@kbn/core/server'; -import type { Filter, KueryNode } from '@kbn/es-query'; +import type { Filter } from '@kbn/es-query'; import { IsoWeekday } from './iso_weekdays'; import { RuleNotifyWhenType } from './rule_notify_when_type'; import { RuleSnooze } from './rule_snooze_type'; @@ -60,6 +60,7 @@ export enum RuleExecutionStatusErrorReasons { export enum RuleExecutionStatusWarningReasons { MAX_EXECUTABLE_ACTIONS = 'maxExecutableActions', MAX_ALERTS = 'maxAlerts', + MAX_QUEUED_ACTIONS = 'maxQueuedActions', } export type RuleAlertingOutcome = 'failure' | 'success' | 'unknown' | 'warning'; @@ -117,28 +118,6 @@ export interface RuleAction { alertsFilter?: AlertsFilter; } -export interface AggregateOptions { - search?: string; - defaultSearchOperator?: 'AND' | 'OR'; - searchFields?: string[]; - hasReference?: { - type: string; - id: string; - }; - filter?: string | KueryNode; - page?: number; - perPage?: number; -} - -export interface RuleAggregationFormattedResult { - ruleExecutionStatus: { [status: string]: number }; - ruleLastRunOutcome: { [status: string]: number }; - ruleEnabledStatus: { enabled: number; disabled: number }; - ruleMutedStatus: { muted: number; unmuted: number }; - ruleSnoozedStatus: { snoozed: number }; - ruleTags: string[]; -} - export interface RuleLastRun { outcome: RuleLastRunOutcomes; outcomeOrder?: number; diff --git a/x-pack/plugins/alerting/common/rule_tags_aggregation.ts b/x-pack/plugins/alerting/common/rule_tags_aggregation.ts index d7b8fc4089c27..04cf6676059d8 100644 --- a/x-pack/plugins/alerting/common/rule_tags_aggregation.ts +++ b/x-pack/plugins/alerting/common/rule_tags_aggregation.ts @@ -10,7 +10,7 @@ import type { AggregationsCompositeAggregation, AggregationsAggregateOrder, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { AggregateOptions } from './rule'; +import type { AggregateOptions } from '../server/application/rule/methods/aggregate/types'; export type RuleTagsAggregationOptions = Pick & { after?: AggregationsCompositeAggregation['after']; diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts index 78b2e41431c22..16eaec0889ed4 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts @@ -11,7 +11,6 @@ import { DEFAULT_FLAPPING_SETTINGS, RecoveredActionGroup, RuleAlertData, - RuleNotifyWhen, } from '../types'; import * as LegacyAlertsClientModule from './legacy_alerts_client'; import { LegacyAlertsClient } from './legacy_alerts_client'; @@ -114,7 +113,7 @@ describe('Alerts Client', () => { ruleRunMetricsStore, shouldLogAlerts: false, flappingSettings: DEFAULT_FLAPPING_SETTINGS, - notifyWhen: RuleNotifyWhen.CHANGE, + notifyOnActionGroupChange: true, maintenanceWindowIds: [], }; }); @@ -1363,10 +1362,6 @@ describe('Alerts Client', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/163192 - // FLAKY: https://github.com/elastic/kibana/issues/163193 - // FLAKY: https://github.com/elastic/kibana/issues/163194 - // FLAKY: https://github.com/elastic/kibana/issues/163195 describe('getSummarizedAlerts', () => { beforeEach(() => { clusterClient.search.mockReturnValue({ diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts index aa513588b83f8..4395df7217419 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts @@ -72,8 +72,8 @@ export const getParamsByTimeQuery: GetSummarizedAlertsParams = { ruleId: 'ruleId', spaceId: 'default', excludedAlertInstanceIds: [], - end: new Date(), - start: new Date(), + end: new Date('2023-09-06T00:01:00.000'), + start: new Date('2023-09-06T00:00:00.000'), }; export const getExpectedQueryByExecutionUuid = ({ @@ -258,7 +258,7 @@ export const getExpectedQueryByTimeRange = ({ { range: { 'kibana.alert.start': { - lt: end, + lt: start, }, }, }, diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts index f8c341e132e51..d723f0d0b64fc 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts @@ -6,7 +6,7 @@ */ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; -import { AlertInstanceContext, RecoveredActionGroup, RuleNotifyWhen } from '../types'; +import { AlertInstanceContext, RecoveredActionGroup } from '../types'; import { LegacyAlertsClient } from './legacy_alerts_client'; import { createAlertFactory, getPublicAlertFactory } from '../alert/create_alert_factory'; import { Alert } from '../alert/alert'; @@ -283,7 +283,7 @@ describe('Legacy Alerts Client', () => { ruleRunMetricsStore, shouldLogAlerts: true, flappingSettings: DEFAULT_FLAPPING_SETTINGS, - notifyWhen: RuleNotifyWhen.CHANGE, + notifyOnActionGroupChange: true, maintenanceWindowIds: ['window-id1', 'window-id2'], }); @@ -312,7 +312,7 @@ describe('Legacy Alerts Client', () => { lookBackWindow: 20, statusChangeThreshold: 4, }, - RuleNotifyWhen.CHANGE, + true, 'default', {}, { diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index 0375c1bf6867b..b7c17ee9579a8 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -136,7 +136,7 @@ export class LegacyAlertsClient< ruleRunMetricsStore, shouldLogAlerts, flappingSettings, - notifyWhen, + notifyOnActionGroupChange, maintenanceWindowIds, }: ProcessAndLogAlertsOpts) { const { @@ -163,7 +163,7 @@ export class LegacyAlertsClient< const alerts = getAlertsForNotification( flappingSettings, - notifyWhen, + notifyOnActionGroupChange, this.options.ruleType.defaultActionGroupId, processedAlertsNew, processedAlertsActive, diff --git a/x-pack/plugins/alerting/server/alerts_client/types.ts b/x-pack/plugins/alerting/server/alerts_client/types.ts index daaaab6add1c9..eccd381bc2a5c 100644 --- a/x-pack/plugins/alerting/server/alerts_client/types.ts +++ b/x-pack/plugins/alerting/server/alerts_client/types.ts @@ -16,7 +16,6 @@ import { SummarizedAlerts, RawAlertInstance, RuleAlertData, - RuleNotifyWhenType, WithoutReservedActionGroups, } from '../types'; import { AlertingEventLogger } from '../lib/alerting_event_logger/alerting_event_logger'; @@ -82,7 +81,7 @@ export interface ProcessAndLogAlertsOpts { shouldLogAlerts: boolean; ruleRunMetricsStore: RuleRunMetricsStore; flappingSettings: RulesSettingsFlappingProperties; - notifyWhen: RuleNotifyWhenType | null; + notifyOnActionGroupChange: boolean; maintenanceWindowIds: string[]; } diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index 90552e1d5b0ac..18a80a0bae31d 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -141,6 +141,7 @@ const getIndexTemplatePutBody = (opts?: GetIndexTemplatePutBodyOpts) => { rollover_alias: `.alerts-${context ? context : 'test'}.alerts-${namespace}`, }, }), + 'index.mapping.ignore_malformed': true, 'index.mapping.total_fields.limit': 2500, }, mappings: { @@ -808,6 +809,7 @@ describe('Alerts Service', () => { rollover_alias: `.alerts-empty.alerts-default`, }, }), + 'index.mapping.ignore_malformed': true, 'index.mapping.total_fields.limit': 2500, }, mappings: { diff --git a/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.test.ts b/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.test.ts index bf0ae8797eca5..85113b768860a 100644 --- a/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.test.ts @@ -48,6 +48,7 @@ const IndexTemplate = (namespace: string = 'default', useDataStream: boolean = f rollover_alias: `.alerts-test.alerts-${namespace}`, }, }), + 'index.mapping.ignore_malformed': true, 'index.mapping.total_fields.limit': 2500, }, }, diff --git a/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.ts b/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.ts index 30ee06a1ddda0..1466c2734ec19 100644 --- a/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.ts +++ b/x-pack/plugins/alerting/server/alerts_service/lib/create_or_update_index_template.ts @@ -68,6 +68,7 @@ export const getIndexTemplate = ({ : { 'index.lifecycle': indexLifecycle, }), + 'index.mapping.ignore_malformed': true, 'index.mapping.total_fields.limit': totalFieldsLimit, }, mappings: { diff --git a/x-pack/plugins/alerting/server/application/rule/constants.ts b/x-pack/plugins/alerting/server/application/rule/constants.ts index 7b0aa82a90ca9..bc75d91375ecb 100644 --- a/x-pack/plugins/alerting/server/application/rule/constants.ts +++ b/x-pack/plugins/alerting/server/application/rule/constants.ts @@ -40,4 +40,5 @@ export const ruleExecutionStatusErrorReason = { export const ruleExecutionStatusWarningReason = { MAX_EXECUTABLE_ACTIONS: 'maxExecutableActions', MAX_ALERTS: 'maxAlerts', + MAX_QUEUED_ACTIONS: 'maxQueuedActions', } as const; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts new file mode 100644 index 0000000000000..b8fbc0427b7f4 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts @@ -0,0 +1,439 @@ +/* + * Copyright 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 { RulesClient, ConstructorOptions } from '../../../../rules_client'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock'; +import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; +import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; +import { AlertingAuthorization } from '../../../../authorization/alerting_authorization'; +import { ActionsAuthorization } from '@kbn/actions-plugin/server'; +import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; +import { getBeforeSetup, setGlobalDate } from '../../../../rules_client/tests/lib'; + +import { RegistryRuleType } from '../../../../rule_type_registry'; +import { fromKueryExpression, nodeTypes } from '@kbn/es-query'; +import { RecoveredActionGroup } from '../../../../../common'; +import { DefaultRuleAggregationResult } from '../../../../routes/rule/apis/aggregate/types'; +import { defaultRuleAggregationFactory } from '.'; + +const taskManager = taskManagerMock.createStart(); +const ruleTypeRegistry = ruleTypeRegistryMock.create(); +const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); + +const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); +const authorization = alertingAuthorizationMock.create(); +const actionsAuthorization = actionsAuthorizationMock.create(); +const auditLogger = auditLoggerMock.create(); + +const kibanaVersion = 'v7.10.0'; +const rulesClientParams: jest.Mocked = { + taskManager, + ruleTypeRegistry, + unsecuredSavedObjectsClient, + minimumScheduleInterval: { value: '1m', enforce: false }, + authorization: authorization as unknown as AlertingAuthorization, + actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, + spaceId: 'default', + namespace: 'default', + getUserName: jest.fn(), + createAPIKey: jest.fn(), + logger: loggingSystemMock.create().get(), + encryptedSavedObjectsClient: encryptedSavedObjects, + getActionsClient: jest.fn(), + getEventLogClient: jest.fn(), + kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), + maxScheduledPerMinute: 1000, + internalSavedObjectsRepository, +}; + +beforeEach(() => { + getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); + (auditLogger.log as jest.Mock).mockClear(); +}); + +setGlobalDate(); + +describe('aggregate()', () => { + const listedTypes = new Set([ + { + actionGroups: [], + actionVariables: undefined, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + id: 'myType', + name: 'myType', + producer: 'myApp', + enabledInLicense: true, + hasAlertsMappings: false, + hasFieldsForAAD: false, + }, + ]); + beforeEach(() => { + authorization.getFindAuthorizationFilter.mockResolvedValue({ + ensureRuleTypeIsAuthorized() {}, + }); + unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ + total: 30, + per_page: 0, + page: 1, + saved_objects: [], + aggregations: { + status: { + buckets: [ + { key: 'active', doc_count: 8 }, + { key: 'error', doc_count: 6 }, + { key: 'ok', doc_count: 10 }, + { key: 'pending', doc_count: 4 }, + { key: 'unknown', doc_count: 2 }, + { key: 'warning', doc_count: 1 }, + ], + }, + outcome: { + buckets: [ + { key: 'succeeded', doc_count: 2 }, + { key: 'failed', doc_count: 4 }, + { key: 'warning', doc_count: 6 }, + ], + }, + enabled: { + buckets: [ + { key: 0, key_as_string: '0', doc_count: 2 }, + { key: 1, key_as_string: '1', doc_count: 28 }, + ], + }, + muted: { + buckets: [ + { key: 0, key_as_string: '0', doc_count: 27 }, + { key: 1, key_as_string: '1', doc_count: 3 }, + ], + }, + snoozed: { + doc_count: 0, + count: { + doc_count: 0, + }, + }, + tags: { + buckets: [ + { + key: 'a', + doc_count: 10, + }, + { + key: 'b', + doc_count: 20, + }, + { + key: 'c', + doc_count: 30, + }, + ], + }, + }, + }); + + ruleTypeRegistry.list.mockReturnValue(listedTypes); + authorization.filterByRuleTypeAuthorization.mockResolvedValue( + new Set([ + { + id: 'myType', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + producer: 'alerts', + authorizedConsumers: { + myApp: { read: true, all: true }, + }, + enabledInLicense: true, + hasAlertsMappings: false, + hasFieldsForAAD: false, + }, + ]) + ); + }); + + test('calls saved objects client with given params to perform aggregation', async () => { + const rulesClient = new RulesClient(rulesClientParams); + const result = await rulesClient.aggregate({ + options: {}, + aggs: defaultRuleAggregationFactory(), + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "enabled": Object { + "buckets": Array [ + Object { + "doc_count": 2, + "key": 0, + "key_as_string": "0", + }, + Object { + "doc_count": 28, + "key": 1, + "key_as_string": "1", + }, + ], + }, + "muted": Object { + "buckets": Array [ + Object { + "doc_count": 27, + "key": 0, + "key_as_string": "0", + }, + Object { + "doc_count": 3, + "key": 1, + "key_as_string": "1", + }, + ], + }, + "outcome": Object { + "buckets": Array [ + Object { + "doc_count": 2, + "key": "succeeded", + }, + Object { + "doc_count": 4, + "key": "failed", + }, + Object { + "doc_count": 6, + "key": "warning", + }, + ], + }, + "snoozed": Object { + "count": Object { + "doc_count": 0, + }, + "doc_count": 0, + }, + "status": Object { + "buckets": Array [ + Object { + "doc_count": 8, + "key": "active", + }, + Object { + "doc_count": 6, + "key": "error", + }, + Object { + "doc_count": 10, + "key": "ok", + }, + Object { + "doc_count": 4, + "key": "pending", + }, + Object { + "doc_count": 2, + "key": "unknown", + }, + Object { + "doc_count": 1, + "key": "warning", + }, + ], + }, + "tags": Object { + "buckets": Array [ + Object { + "doc_count": 10, + "key": "a", + }, + Object { + "doc_count": 20, + "key": "b", + }, + Object { + "doc_count": 30, + "key": "c", + }, + ], + }, + } + `); + expect(unsecuredSavedObjectsClient.find).toHaveBeenCalledTimes(1); + + expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toEqual([ + { + filter: undefined, + page: 1, + perPage: 0, + type: 'alert', + aggs: { + status: { + terms: { field: 'alert.attributes.executionStatus.status' }, + }, + outcome: { + terms: { field: 'alert.attributes.lastRun.outcome' }, + }, + enabled: { + terms: { field: 'alert.attributes.enabled' }, + }, + muted: { + terms: { field: 'alert.attributes.muteAll' }, + }, + snoozed: { + aggs: { + count: { + filter: { + exists: { + field: 'alert.attributes.snoozeSchedule.duration', + }, + }, + }, + }, + nested: { + path: 'alert.attributes.snoozeSchedule', + }, + }, + tags: { + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, + }, + }, + }, + ]); + }); + + test('supports filters when aggregating', async () => { + const authFilter = fromKueryExpression( + 'alert.attributes.alertTypeId:myType and alert.attributes.consumer:myApp' + ); + authorization.getFindAuthorizationFilter.mockResolvedValue({ + filter: authFilter, + ensureRuleTypeIsAuthorized() {}, + }); + + const rulesClient = new RulesClient(rulesClientParams); + await rulesClient.aggregate({ + options: { filter: 'foo: someTerm' }, + aggs: defaultRuleAggregationFactory(), + }); + + expect(unsecuredSavedObjectsClient.find).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toEqual([ + { + fields: undefined, + filter: nodeTypes.function.buildNode('and', [ + fromKueryExpression('foo: someTerm'), + authFilter, + ]), + page: 1, + perPage: 0, + type: 'alert', + aggs: { + status: { + terms: { field: 'alert.attributes.executionStatus.status' }, + }, + outcome: { + terms: { field: 'alert.attributes.lastRun.outcome' }, + }, + enabled: { + terms: { field: 'alert.attributes.enabled' }, + }, + muted: { + terms: { field: 'alert.attributes.muteAll' }, + }, + snoozed: { + aggs: { + count: { + filter: { + exists: { + field: 'alert.attributes.snoozeSchedule.duration', + }, + }, + }, + }, + nested: { + path: 'alert.attributes.snoozeSchedule', + }, + }, + tags: { + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, + }, + }, + }, + ]); + }); + + test('logs audit event when not authorized to aggregate rules', async () => { + const rulesClient = new RulesClient({ ...rulesClientParams, auditLogger }); + authorization.getFindAuthorizationFilter.mockRejectedValue(new Error('Unauthorized')); + + await expect( + rulesClient.aggregate({ aggs: defaultRuleAggregationFactory() }) + ).rejects.toThrow(); + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action: 'rule_aggregate', + outcome: 'failure', + }), + error: { + code: 'Error', + message: 'Unauthorized', + }, + }) + ); + }); + + describe('tags number limit', () => { + test('sets to default (50) if it is not provided', async () => { + const rulesClient = new RulesClient(rulesClientParams); + + await rulesClient.aggregate({ aggs: defaultRuleAggregationFactory() }); + + expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toMatchObject([ + { + aggs: { + tags: { + terms: { size: 50 }, + }, + }, + }, + ]); + }); + + test('sets to the provided value', async () => { + const rulesClient = new RulesClient(rulesClientParams); + + await rulesClient.aggregate({ + aggs: defaultRuleAggregationFactory({ maxTags: 1000 }), + }); + + expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toMatchObject([ + { + aggs: { + tags: { + terms: { size: 1000 }, + }, + }, + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.ts new file mode 100644 index 0000000000000..f992f5cead705 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.ts @@ -0,0 +1,62 @@ +/* + * Copyright 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 { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { findRulesSo } from '../../../../data/rule'; +import { AlertingAuthorizationEntity } from '../../../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events'; +import { buildKueryNodeFilter } from '../../../../rules_client/common'; +import { alertingAuthorizationFilterOpts } from '../../../../rules_client/common/constants'; +import { RulesClientContext } from '../../../../rules_client/types'; +import { aggregateOptionsSchema } from './schemas'; +import type { AggregateParams } from './types'; +import { validateRuleAggregationFields } from './validation'; + +export async function aggregateRules>( + context: RulesClientContext, + params: AggregateParams +): Promise { + const { options = {}, aggs } = params; + const { filter, page = 1, perPage = 0, ...restOptions } = options; + + let authorizationTuple; + try { + authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); + validateRuleAggregationFields(aggs); + aggregateOptionsSchema.validate(options); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.AGGREGATE, + error, + }) + ); + throw error; + } + + const { filter: authorizationFilter } = authorizationTuple; + const filterKueryNode = buildKueryNodeFilter(filter); + + const { aggregations } = await findRulesSo({ + savedObjectsClient: context.unsecuredSavedObjectsClient, + savedObjectsFindOptions: { + ...restOptions, + filter: + authorizationFilter && filterKueryNode + ? nodeBuilder.and([filterKueryNode, authorizationFilter as KueryNode]) + : authorizationFilter, + page, + perPage, + aggs, + }, + }); + + return aggregations!; +} diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/latest.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/latest.ts new file mode 100644 index 0000000000000..210b2ee194850 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/latest.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { defaultRuleAggregationFactory } from './v1'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/v1.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/v1.test.ts new file mode 100644 index 0000000000000..2accb33511681 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/v1.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { defaultRuleAggregationFactory } from './v1'; + +describe('getDefaultRuleAggregation', () => { + it('should return aggregation with default maxTags', () => { + const result = defaultRuleAggregationFactory(); + expect(result.tags).toEqual({ + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, + }); + }); + + it('should return aggregation with custom maxTags', () => { + const result = defaultRuleAggregationFactory({ maxTags: 100 }); + expect(result.tags).toEqual({ + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 100 }, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/v1.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/v1.ts new file mode 100644 index 0000000000000..371c12c1fada3 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/factories/default_rule_aggregation_factory/v1.ts @@ -0,0 +1,46 @@ +/* + * Copyright 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 { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/types'; +import { DefaultRuleAggregationParams } from '../../types'; + +export const defaultRuleAggregationFactory = ( + params?: DefaultRuleAggregationParams +): Record => { + const { maxTags = 50 } = params || {}; + return { + status: { + terms: { field: 'alert.attributes.executionStatus.status' }, + }, + outcome: { + terms: { field: 'alert.attributes.lastRun.outcome' }, + }, + enabled: { + terms: { field: 'alert.attributes.enabled' }, + }, + muted: { + terms: { field: 'alert.attributes.muteAll' }, + }, + tags: { + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: maxTags }, + }, + snoozed: { + nested: { + path: 'alert.attributes.snoozeSchedule', + }, + aggs: { + count: { + filter: { + exists: { + field: 'alert.attributes.snoozeSchedule.duration', + }, + }, + }, + }, + }, + }; +}; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/index.ts new file mode 100644 index 0000000000000..ccb5ca61daf9a --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { defaultRuleAggregationFactory } from './factories/default_rule_aggregation_factory/latest'; +export { defaultRuleAggregationFactory as defaultRuleAggregationFactoryV1 } from './factories/default_rule_aggregation_factory/v1'; +// export { aggregateOptionsSchema } from './schemas'; +// export type { AggregateOptions, AggregateParams, DefaultRuleAggregationParams } from './types'; + +export { aggregateRules } from './aggregate_rules'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/aggregate_options_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/aggregate_options_schema.ts new file mode 100644 index 0000000000000..3ac244b808752 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/aggregate_options_schema.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema } from '@kbn/config-schema'; + +export const aggregateOptionsSchema = schema.object({ + search: schema.maybe(schema.string()), + defaultSearchOperator: schema.maybe(schema.oneOf([schema.literal('AND'), schema.literal('OR')])), + searchFields: schema.maybe(schema.arrayOf(schema.string())), + hasReference: schema.maybe( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ), + // filter type is `string | KueryNode`, but `KueryNode` has no schema to import yet + filter: schema.maybe( + schema.oneOf([schema.string(), schema.recordOf(schema.string(), schema.any())]) + ), + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number()), +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/index.ts new file mode 100644 index 0000000000000..c29f688596ef4 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { aggregateOptionsSchema } from './aggregate_options_schema'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/types/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/types/index.ts new file mode 100644 index 0000000000000..3733a49003bba --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/types/index.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { TypeOf } from '@kbn/config-schema'; +import { KueryNode } from '@kbn/es-query'; +import { aggregateOptionsSchema } from '../schemas'; + +type AggregateOptionsSchemaTypes = TypeOf; +export type AggregateOptions = TypeOf & { + search?: AggregateOptionsSchemaTypes['search']; + defaultSearchOperator?: AggregateOptionsSchemaTypes['defaultSearchOperator']; + searchFields?: AggregateOptionsSchemaTypes['searchFields']; + hasReference?: AggregateOptionsSchemaTypes['hasReference']; + // Adding filter as in schema it's defined as any instead of KueryNode + filter?: string | KueryNode; + page?: AggregateOptionsSchemaTypes['page']; + perPage?: AggregateOptionsSchemaTypes['perPage']; +}; + +export interface AggregateParams { + options?: AggregateOptions; + aggs: Record; +} + +export interface DefaultRuleAggregationParams { + maxTags?: number; +} + +export interface RuleAggregationFormattedResult { + ruleExecutionStatus: Record; + ruleLastRunOutcome: Record; + ruleEnabledStatus: { + enabled: number; + disabled: number; + }; + ruleMutedStatus: { + muted: number; + unmuted: number; + }; + ruleSnoozedStatus: { + snoozed: number; + }; + ruleTags: string[]; +} diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/index.ts new file mode 100644 index 0000000000000..bce1dab4756fe --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { validateRuleAggregationFields } from './validate_rule_aggregation_fields'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_rule_aggregation_fields.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/validate_rule_aggregation_fields.test.ts similarity index 97% rename from x-pack/plugins/alerting/server/rules_client/lib/validate_rule_aggregation_fields.test.ts rename to x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/validate_rule_aggregation_fields.test.ts index 599938de4b1fc..8bb6e75b88f72 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_rule_aggregation_fields.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/validate_rule_aggregation_fields.test.ts @@ -5,8 +5,10 @@ * 2.0. */ -import { getRuleTagsAggregation, getDefaultRuleAggregation } from '../../../common'; import type { AggregationsAggregateOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { getRuleTagsAggregation } from '../../../../../../common'; +import { defaultRuleAggregationFactory } from '..'; + import { validateRuleAggregationFields } from './validate_rule_aggregation_fields'; describe('validateAggregationTerms', () => { @@ -95,7 +97,7 @@ describe('validateAggregationTerms', () => { }); it('should allow for default and tags aggregations', () => { - expect(() => validateRuleAggregationFields(getDefaultRuleAggregation())).not.toThrowError(); + expect(() => validateRuleAggregationFields(defaultRuleAggregationFactory())).not.toThrowError(); expect(() => validateRuleAggregationFields(getRuleTagsAggregation())).not.toThrowError(); }); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_rule_aggregation_fields.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/validate_rule_aggregation_fields.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/validate_rule_aggregation_fields.ts rename to x-pack/plugins/alerting/server/application/rule/methods/aggregate/validation/validate_rule_aggregation_fields.ts diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts index 5e672bcc83c68..af51009d71da1 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts @@ -10,7 +10,11 @@ import { omit } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock'; @@ -56,7 +60,12 @@ jest.mock('uuid', () => { return { v4: () => `${uuid++}` }; }); +jest.mock('../get_schedule_frequency', () => ({ + validateScheduleLimit: jest.fn(), +})); + const { isSnoozeActive } = jest.requireMock('../../../../lib/snooze/is_snooze_active'); +const { validateScheduleLimit } = jest.requireMock('../get_schedule_frequency'); const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -65,6 +74,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v8.2.0'; const createAPIKeyMock = jest.fn(); @@ -82,11 +92,13 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: createAPIKeyMock, logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: isAuthenticationTypeApiKeyMock, getAuthenticationAPIKey: getAuthenticationApiKeyMock, @@ -2495,6 +2507,66 @@ describe('bulkEdit()', () => { `Error updating rule with ID "${existingDecryptedRule.id}": the interval 10m is longer than the action frequencies` ); }); + + test('should only validate schedule limit if schedule is being modified', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + enabled: true, + tags: ['foo', 'test-1'], + alertTypeId: 'myType', + schedule: { interval: '1m' }, + consumer: 'myApp', + scheduledTaskId: 'task-123', + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + }, + params: {}, + throttle: null, + notifyWhen: null, + actions: [], + revision: 0, + }, + references: [], + version: '123', + }, + ], + }); + + await rulesClient.bulkEdit({ + filter: '', + operations: [ + { + field: 'tags', + operation: 'add', + value: ['test-1'], + }, + ], + }); + + expect(validateScheduleLimit).toHaveBeenCalledTimes(0); + + await rulesClient.bulkEdit({ + operations: [ + { + field: 'schedule', + operation: 'set', + value: { interval: '2m' }, + }, + { + field: 'tags', + operation: 'add', + value: ['test-1'], + }, + ], + }); + + expect(validateScheduleLimit).toHaveBeenCalledTimes(1); + }); }); describe('paramsModifier', () => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts index 050b138eddd6b..d76162696ead2 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts @@ -77,6 +77,11 @@ import { transformRuleDomainToRuleAttributes, transformRuleDomainToRule, } from '../../transforms'; +import { validateScheduleLimit } from '../get_schedule_frequency'; + +const isValidInterval = (interval: string | undefined): interval is string => { + return interval !== undefined; +}; export const bulkEditFieldsToExcludeFromRevisionUpdates = new Set(['snoozeSchedule', 'apiKey']); @@ -286,8 +291,16 @@ async function bulkEditRulesOcc( const errors: BulkOperationError[] = []; const apiKeysMap: ApiKeysMap = new Map(); const username = await context.getUserName(); + const prevInterval: string[] = []; for await (const response of rulesFinder.find()) { + const intervals = response.saved_objects + .filter((rule) => rule.attributes.enabled) + .map((rule) => rule.attributes.schedule?.interval) + .filter(isValidInterval); + + prevInterval.concat(intervals); + await pMap( response.saved_objects, async (rule: SavedObjectsFindResult) => @@ -308,9 +321,44 @@ async function bulkEditRulesOcc( } await rulesFinder.close(); + const updatedInterval = rules + .filter((rule) => rule.attributes.enabled) + .map((rule) => rule.attributes.schedule?.interval) + .filter(isValidInterval); + + try { + if (operations.some((operation) => operation.field === 'schedule')) { + await validateScheduleLimit({ + context, + prevInterval, + updatedInterval, + }); + } + } catch (error) { + return { + apiKeysToInvalidate: Array.from(apiKeysMap.values()) + .filter((value) => value.newApiKey) + .map((value) => value.newApiKey as string), + resultSavedObjects: [], + rules: [], + errors: rules.map((rule) => ({ + message: `Failed to bulk edit rule - ${error.message}`, + rule: { + id: rule.id, + name: rule.attributes.name || 'n/a', + }, + })), + skipped: [], + }; + } + const { result, apiKeysToInvalidate } = rules.length > 0 - ? await saveBulkUpdatedRules(context, rules, apiKeysMap) + ? await saveBulkUpdatedRules({ + context, + rules, + apiKeysMap, + }) : { result: { saved_objects: [] }, apiKeysToInvalidate: [], @@ -821,11 +869,15 @@ function updateAttributes( }; } -async function saveBulkUpdatedRules( - context: RulesClientContext, - rules: Array>, - apiKeysMap: ApiKeysMap -) { +async function saveBulkUpdatedRules({ + context, + rules, + apiKeysMap, +}: { + context: RulesClientContext; + rules: Array>; + apiKeysMap: ApiKeysMap; +}) { const apiKeysToInvalidate: string[] = []; let result; try { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts index 496cfe317586f..ef2d91ea484ff 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts @@ -8,7 +8,11 @@ import { schema } from '@kbn/config-schema'; import { CreateRuleParams } from './create_rule'; import { RulesClient, ConstructorOptions } from '../../../../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock'; @@ -43,6 +47,10 @@ jest.mock('uuid', () => { return { v4: () => `${uuid++}` }; }); +jest.mock('../get_schedule_frequency', () => ({ + validateScheduleLimit: jest.fn(), +})); + const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); @@ -50,6 +58,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v8.0.0'; const rulesClientParams: jest.Mocked = { @@ -63,11 +72,13 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.ts index ddd6691a635a3..616a16a8315ed 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.ts @@ -36,6 +36,7 @@ import { RuleAttributes } from '../../../../data/rule/types'; import type { CreateRuleData } from './types'; import { createRuleDataSchema } from './schemas'; import { createRuleSavedObject } from '../../../../rules_client/lib'; +import { validateScheduleLimit } from '../get_schedule_frequency'; export interface CreateRuleOptions { id?: string; @@ -60,6 +61,12 @@ export async function createRule( try { createRuleDataSchema.validate(data); + if (data.enabled) { + await validateScheduleLimit({ + context, + updatedInterval: data.schedule.interval, + }); + } } catch (error) { throw Boom.badRequest(`Error validating create data - ${error.message}`); } diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts new file mode 100644 index 0000000000000..cbd1476e111a1 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts @@ -0,0 +1,233 @@ +/* + * Copyright 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 { validateScheduleLimit } from './get_schedule_frequency'; +import { RulesClient, ConstructorOptions } from '../../../../rules_client'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock'; +import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; +import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; +import { AlertingAuthorization } from '../../../../authorization/alerting_authorization'; +import { ActionsAuthorization } from '@kbn/actions-plugin/server'; +import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; + +const taskManager = taskManagerMock.createStart(); +const ruleTypeRegistry = ruleTypeRegistryMock.create(); +const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); +const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); +const authorization = alertingAuthorizationMock.create(); +const actionsAuthorization = actionsAuthorizationMock.create(); +const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); + +const kibanaVersion = 'v8.0.0'; + +const rulesClientParams: jest.Mocked = { + taskManager, + ruleTypeRegistry, + unsecuredSavedObjectsClient, + authorization: authorization as unknown as AlertingAuthorization, + actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, + spaceId: 'default', + namespace: 'default', + getUserName: jest.fn(), + createAPIKey: jest.fn(), + logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, + encryptedSavedObjectsClient: encryptedSavedObjects, + getActionsClient: jest.fn(), + getEventLogClient: jest.fn(), + kibanaVersion, + auditLogger, + maxScheduledPerMinute: 100, + minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), +}; + +const getMockAggregationResult = ( + intervalAggs: Array<{ + interval: string; + count: number; + }> +) => { + return { + aggregations: { + schedule_intervals: { + buckets: intervalAggs.map(({ interval, count }) => ({ + key: interval, + doc_count: count, + })), + }, + }, + page: 1, + per_page: 20, + total: 1, + saved_objects: [], + }; +}; + +describe('getScheduleFrequency()', () => { + beforeEach(() => { + internalSavedObjectsRepository.find.mockResolvedValue( + getMockAggregationResult([ + { interval: '1m', count: 1 }, + { interval: '1m', count: 2 }, + { interval: '1m', count: 3 }, + { interval: '5m', count: 5 }, + { interval: '5m', count: 10 }, + { interval: '5m', count: 15 }, + ]) + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return the correct schedule frequency results', async () => { + const rulesClient = new RulesClient(rulesClientParams); + const result = await rulesClient.getScheduleFrequency(); + + // (1 * 6) + (1/5 * 30) = 12 + expect(result.totalScheduledPerMinute).toEqual(12); + + // 100 - 88 + expect(result.remainingSchedulesPerMinute).toEqual(88); + }); + + test('should handle empty bucket correctly', async () => { + internalSavedObjectsRepository.find.mockResolvedValue({ + page: 1, + per_page: 20, + total: 1, + saved_objects: [], + }); + + const rulesClient = new RulesClient(rulesClientParams); + const result = await rulesClient.getScheduleFrequency(); + + expect(result.totalScheduledPerMinute).toEqual(0); + expect(result.remainingSchedulesPerMinute).toEqual(100); + }); + + test('should handle malformed schedule interval correctly', async () => { + internalSavedObjectsRepository.find.mockResolvedValue( + getMockAggregationResult([ + { interval: '1m', count: 1 }, + { interval: '1m', count: 2 }, + { interval: '1m', count: 3 }, + { interval: '5m', count: 5 }, + { interval: '5m', count: 10 }, + { interval: '5m', count: 15 }, + { interval: 'invalid', count: 15 }, + ]) + ); + + const rulesClient = new RulesClient(rulesClientParams); + const result = await rulesClient.getScheduleFrequency(); + + expect(result.totalScheduledPerMinute).toEqual(12); + expect(result.remainingSchedulesPerMinute).toEqual(88); + }); + + test('should not go below 0 for remaining schedules', async () => { + internalSavedObjectsRepository.find.mockResolvedValue( + getMockAggregationResult([ + { interval: '1m', count: 1 }, + { interval: '1m', count: 2 }, + { interval: '1m', count: 3 }, + { interval: '5m', count: 5 }, + { interval: '5m', count: 10 }, + { interval: '5m', count: 15 }, + ]) + ); + + const rulesClient = new RulesClient({ + ...rulesClientParams, + maxScheduledPerMinute: 10, + }); + const result = await rulesClient.getScheduleFrequency(); + expect(result.totalScheduledPerMinute).toEqual(12); + expect(result.remainingSchedulesPerMinute).toEqual(0); + }); +}); + +describe('validateScheduleLimit', () => { + const context = { + ...rulesClientParams, + maxScheduledPerMinute: 5, + minimumScheduleIntervalInMs: 1000, + fieldsToExcludeFromPublicApi: [], + }; + + beforeEach(() => { + internalSavedObjectsRepository.find.mockResolvedValue( + getMockAggregationResult([{ interval: '1m', count: 2 }]) + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should not throw if the updated interval does not exceed limits', () => { + return expect( + validateScheduleLimit({ + context, + updatedInterval: ['1m', '1m'], + }) + ).resolves.toBe(undefined); + }); + + test('should throw if the updated interval exceeds limits', () => { + return expect( + validateScheduleLimit({ + context, + updatedInterval: ['1m', '1m', '1m', '2m'], + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Run limit reached: The rule has 3.5 runs per minute; there are only 3 runs per minute available."` + ); + }); + + test('should not throw if previous interval was modified to be under the limit', () => { + internalSavedObjectsRepository.find.mockResolvedValue( + getMockAggregationResult([{ interval: '1m', count: 6 }]) + ); + + return expect( + validateScheduleLimit({ + context, + prevInterval: ['1m', '1m'], + updatedInterval: ['2m', '2m'], + }) + ).resolves.toBe(undefined); + }); + + test('should throw if the previous interval was modified to exceed the limit', () => { + internalSavedObjectsRepository.find.mockResolvedValue( + getMockAggregationResult([{ interval: '1m', count: 5 }]) + ); + + return expect( + validateScheduleLimit({ + context, + prevInterval: ['1m'], + updatedInterval: ['30s'], + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Run limit reached: The rule has 2 runs per minute; there are only 1 runs per minute available."` + ); + }); +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.ts new file mode 100644 index 0000000000000..254cad93fd341 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { RulesClientContext } from '../../../../rules_client/types'; +import { RuleDomain } from '../../types'; +import { convertDurationToFrequency } from '../../../../../common/parse_duration'; +import { GetScheduleFrequencyResult } from './types'; +import { getSchemaFrequencyResultSchema } from './schema'; + +export interface SchedulesIntervalAggregationResult { + schedule_intervals: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; +} + +const convertIntervalToFrequency = (context: RulesClientContext, schedule: string) => { + let scheduleFrequency = 0; + + try { + // Normalize the interval (period) in terms of minutes + scheduleFrequency = convertDurationToFrequency(schedule); + } catch (e) { + context.logger.warn( + `Failed to parse rule schedule interval for schedule frequency calculation: ${e.message}` + ); + } + + return scheduleFrequency; +}; + +export const getScheduleFrequency = async ( + context: RulesClientContext +): Promise => { + const response = await context.internalSavedObjectsRepository.find< + RuleDomain, + SchedulesIntervalAggregationResult + >({ + type: 'alert', + filter: 'alert.attributes.enabled: true', + namespaces: ['*'], + aggs: { + schedule_intervals: { + terms: { + field: 'alert.attributes.schedule.interval', + }, + }, + }, + }); + + const buckets = response.aggregations?.schedule_intervals.buckets ?? []; + + const totalScheduledPerMinute = buckets.reduce((result, { key, doc_count: occurrence }) => { + const scheduleFrequency = convertIntervalToFrequency(context, key); + + // Sum up all of the frequencies, since this is an aggregation. + return result + scheduleFrequency * occurrence; + }, 0); + + const result = { + totalScheduledPerMinute, + remainingSchedulesPerMinute: Math.max( + context.maxScheduledPerMinute - totalScheduledPerMinute, + 0 + ), + }; + + try { + getSchemaFrequencyResultSchema.validate(result); + } catch (e) { + context.logger.warn(`Error validating rule schedules per minute: ${e}`); + } + + return result; +}; + +interface ValidateScheduleLimitParams { + context: RulesClientContext; + prevInterval?: string | string[]; + updatedInterval: string | string[]; +} + +export const validateScheduleLimit = async (params: ValidateScheduleLimitParams) => { + const { context, prevInterval = [], updatedInterval = [] } = params; + + const prevIntervalArray = Array.isArray(prevInterval) ? prevInterval : [prevInterval]; + const updatedIntervalArray = Array.isArray(updatedInterval) ? updatedInterval : [updatedInterval]; + + const prevSchedulePerMinute = prevIntervalArray.reduce((result, interval) => { + const scheduleFrequency = convertIntervalToFrequency(context, interval); + return result + scheduleFrequency; + }, 0); + + const updatedSchedulesPerMinute = updatedIntervalArray.reduce((result, interval) => { + const scheduleFrequency = convertIntervalToFrequency(context, interval); + return result + scheduleFrequency; + }, 0); + + const { remainingSchedulesPerMinute } = await getScheduleFrequency(context); + + // Compute the new remaining schedules per minute if we are editing rules. + // So we add back the edited schedules, since we assume those are being edited. + const computedRemainingSchedulesPerMinute = remainingSchedulesPerMinute + prevSchedulePerMinute; + + if (computedRemainingSchedulesPerMinute < updatedSchedulesPerMinute) { + throw new Error( + `Run limit reached: The rule has ${updatedSchedulesPerMinute} runs per minute; there are only ${computedRemainingSchedulesPerMinute} runs per minute available.` + ); + } +}; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/index.ts new file mode 100644 index 0000000000000..e39a1cd8a671c --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { GetScheduleFrequencyResult } from './types'; + +export { getScheduleFrequency, validateScheduleLimit } from './get_schedule_frequency'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/schema/get_schedule_frequency_result_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/schema/get_schedule_frequency_result_schema.ts new file mode 100644 index 0000000000000..b547c33323a80 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/schema/get_schedule_frequency_result_schema.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const getSchemaFrequencyResultSchema = schema.object({ + totalScheduledPerMinute: schema.number({ min: 0 }), + remainingSchedulesPerMinute: schema.number({ min: 0 }), +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/schema/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/schema/index.ts new file mode 100644 index 0000000000000..474bcdd524433 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/schema/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getSchemaFrequencyResultSchema } from './get_schedule_frequency_result_schema'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/types/get_schedule_frequency_result.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/types/get_schedule_frequency_result.ts new file mode 100644 index 0000000000000..4f53ea2e82810 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/types/get_schedule_frequency_result.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { getSchemaFrequencyResultSchema } from '../schema'; + +export type GetScheduleFrequencyResult = TypeOf; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/types/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/types/index.ts new file mode 100644 index 0000000000000..24e3465bbf3ae --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/types/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { GetScheduleFrequencyResult } from './get_schedule_frequency_result'; diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts index 07efe4793b562..ef8f1dc652bff 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts @@ -55,6 +55,7 @@ export const ruleExecutionStatusSchema = schema.object({ reason: schema.oneOf([ schema.literal(ruleExecutionStatusWarningReason.MAX_EXECUTABLE_ACTIONS), schema.literal(ruleExecutionStatusWarningReason.MAX_ALERTS), + schema.literal(ruleExecutionStatusWarningReason.MAX_QUEUED_ACTIONS), ]), message: schema.string(), }) @@ -81,6 +82,7 @@ export const ruleLastRunSchema = schema.object({ schema.literal(ruleExecutionStatusErrorReason.VALIDATE), schema.literal(ruleExecutionStatusWarningReason.MAX_EXECUTABLE_ACTIONS), schema.literal(ruleExecutionStatusWarningReason.MAX_ALERTS), + schema.literal(ruleExecutionStatusWarningReason.MAX_QUEUED_ACTIONS), ]) ) ), diff --git a/x-pack/plugins/alerting/server/config.test.ts b/x-pack/plugins/alerting/server/config.test.ts index 7df579771a91c..164b317cdf2ac 100644 --- a/x-pack/plugins/alerting/server/config.test.ts +++ b/x-pack/plugins/alerting/server/config.test.ts @@ -23,6 +23,7 @@ describe('config validation', () => { }, "maxEphemeralActionsPerAlert": 10, "rules": Object { + "maxScheduledPerMinute": 10000, "minimumScheduleInterval": Object { "enforce": false, "value": "1m", diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts index b1b9817ce1b9f..3ec1edb2f3c1c 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -38,6 +38,7 @@ const rulesSchema = schema.object({ }), enforce: schema.boolean({ defaultValue: false }), // if enforce is false, only warnings will be shown }), + maxScheduledPerMinute: schema.number({ defaultValue: 10000, max: 10000, min: 0 }), run: schema.object({ timeout: schema.maybe(schema.string({ validate: validateDurationSchema })), actions: schema.object({ @@ -70,7 +71,10 @@ export const configSchema = schema.object({ export type AlertingConfig = TypeOf; export type RulesConfig = TypeOf; -export type AlertingRulesConfig = Pick & { +export type AlertingRulesConfig = Pick< + AlertingConfig['rules'], + 'minimumScheduleInterval' | 'maxScheduledPerMinute' +> & { isUsingSecurity: boolean; }; export type ActionsConfig = RulesConfig['run']['actions']; diff --git a/x-pack/plugins/alerting/server/constants/translations.ts b/x-pack/plugins/alerting/server/constants/translations.ts index 15442cf8efc57..69fc9a39333b2 100644 --- a/x-pack/plugins/alerting/server/constants/translations.ts +++ b/x-pack/plugins/alerting/server/constants/translations.ts @@ -21,6 +21,10 @@ export const translations = { defaultMessage: 'Rule reported more than the maximum number of alerts in a single run. Alerts may be missed and recovery notifications may be delayed', }), + maxQueuedActions: i18n.translate('xpack.alerting.taskRunner.warning.maxQueuedActions', { + defaultMessage: + 'The maximum number of queued actions was reached; excess actions were not triggered.', + }), }, }, }; diff --git a/x-pack/plugins/alerting/server/data/rule/constants.ts b/x-pack/plugins/alerting/server/data/rule/constants.ts index 63d238a81574e..267864bdfd9e8 100644 --- a/x-pack/plugins/alerting/server/data/rule/constants.ts +++ b/x-pack/plugins/alerting/server/data/rule/constants.ts @@ -40,4 +40,5 @@ export const ruleExecutionStatusErrorReasonAttributes = { export const ruleExecutionStatusWarningReasonAttributes = { MAX_EXECUTABLE_ACTIONS: 'maxExecutableActions', MAX_ALERTS: 'maxAlerts', + MAX_QUEUED_ACTIONS: 'maxQueuedActions', } as const; diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts index 007cd4481bd7e..c8c2e5f1943ec 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts @@ -728,6 +728,7 @@ describe('AlertingEventLogger', () => { totalSearchDurationMs: 10333, hasReachedAlertLimit: false, triggeredActionsStatus: ActionsCompletion.COMPLETE, + hasReachedQueuedActionsLimit: false, }, }); @@ -826,6 +827,7 @@ describe('AlertingEventLogger', () => { totalSearchDurationMs: 10333, hasReachedAlertLimit: false, triggeredActionsStatus: ActionsCompletion.COMPLETE, + hasReachedQueuedActionsLimit: false, }, timings: { [TaskRunnerTimerSpan.StartTaskRun]: 10, diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts index 2cb820fabed39..04f732426cff5 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts @@ -9,7 +9,6 @@ import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../comm import { getAlertsForNotification } from '.'; import { Alert } from '../alert'; import { alertsWithAnyUUID } from '../test_utils'; -import { RuleNotifyWhen } from '../types'; describe('getAlertsForNotification', () => { test('should set pendingRecoveredCount to zero for all active alerts', () => { @@ -20,7 +19,7 @@ describe('getAlertsForNotification', () => { const { newAlerts, activeAlerts } = getAlertsForNotification( DEFAULT_FLAPPING_SETTINGS, - RuleNotifyWhen.CHANGE, + true, 'default', { '1': alert1, @@ -85,7 +84,7 @@ describe('getAlertsForNotification', () => { currentRecoveredAlerts, } = getAlertsForNotification( DEFAULT_FLAPPING_SETTINGS, - RuleNotifyWhen.CHANGE, + true, 'default', {}, {}, @@ -212,7 +211,7 @@ describe('getAlertsForNotification', () => { const { newAlerts, activeAlerts, recoveredAlerts, currentRecoveredAlerts } = getAlertsForNotification( DISABLE_FLAPPING_SETTINGS, - RuleNotifyWhen.CHANGE, + true, 'default', {}, {}, @@ -337,7 +336,7 @@ describe('getAlertsForNotification', () => { currentRecoveredAlerts, } = getAlertsForNotification( DEFAULT_FLAPPING_SETTINGS, - RuleNotifyWhen.ACTIVE, + false, 'default', {}, {}, diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts index e25752c4b7b37..4ff8408d67e11 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts @@ -8,12 +8,7 @@ import { keys } from 'lodash'; import { RulesSettingsFlappingProperties } from '../../common/rules_settings'; import { Alert } from '../alert'; -import { - AlertInstanceState, - AlertInstanceContext, - RuleNotifyWhenType, - RuleNotifyWhen, -} from '../types'; +import { AlertInstanceState, AlertInstanceContext } from '../types'; export function getAlertsForNotification< State extends AlertInstanceState, @@ -22,7 +17,7 @@ export function getAlertsForNotification< RecoveryActionGroupId extends string >( flappingSettings: RulesSettingsFlappingProperties, - notifyWhen: RuleNotifyWhenType | null, + notifyOnActionGroupChange: boolean, actionGroupId: string, newAlerts: Record> = {}, activeAlerts: Record> = {}, @@ -62,8 +57,9 @@ export function getAlertsForNotification< ); activeAlerts[id] = newAlert; - // rules with "on status change" should return notifications - if (notifyWhen === RuleNotifyWhen.CHANGE) { + // rule with "on status change" or rule with at least one + // action with "on status change" should return notifications + if (notifyOnActionGroupChange) { currentActiveAlerts[id] = newAlert; } diff --git a/x-pack/plugins/alerting/server/lib/last_run_status.test.ts b/x-pack/plugins/alerting/server/lib/last_run_status.test.ts index 33af749fe1e08..c4b1ac0acc3a7 100644 --- a/x-pack/plugins/alerting/server/lib/last_run_status.test.ts +++ b/x-pack/plugins/alerting/server/lib/last_run_status.test.ts @@ -13,6 +13,7 @@ import { RuleResultServiceResults, RuleResultService } from '../monitoring/rule_ const getMetrics = ({ hasReachedAlertLimit = false, triggeredActionsStatus = ActionsCompletion.COMPLETE, + hasReachedQueuedActionsLimit = false, }): RuleRunMetrics => { return { triggeredActionsStatus, @@ -25,6 +26,7 @@ const getMetrics = ({ numberOfTriggeredActions: 5, totalSearchDurationMs: 2, hasReachedAlertLimit, + hasReachedQueuedActionsLimit, }; }; @@ -126,6 +128,31 @@ describe('lastRunFromState', () => { }); }); + it('returns warning if rules actions completition is partial and queued action circuit breaker opens', () => { + const result = lastRunFromState( + { + metrics: getMetrics({ + triggeredActionsStatus: ActionsCompletion.PARTIAL, + hasReachedQueuedActionsLimit: true, + }), + }, + getRuleResultService({}) + ); + + expect(result.lastRun.outcome).toEqual('warning'); + expect(result.lastRun.outcomeMsg).toEqual([ + 'The maximum number of queued actions was reached; excess actions were not triggered.', + ]); + expect(result.lastRun.warning).toEqual('maxQueuedActions'); + + expect(result.lastRun.alertsCount).toEqual({ + active: 10, + new: 12, + recovered: 11, + ignored: 0, + }); + }); + it('overwrites rule execution warning if rule has reached alert limit; outcome messages are merged', () => { const ruleExecutionOutcomeMessage = 'Rule execution reported a warning'; const frameworkOutcomeMessage = @@ -184,6 +211,38 @@ describe('lastRunFromState', () => { }); }); + it('overwrites rule execution warning if rule has reached queued action limit; outcome messages are merged', () => { + const ruleExecutionOutcomeMessage = 'Rule execution reported a warning'; + const frameworkOutcomeMessage = + 'The maximum number of queued actions was reached; excess actions were not triggered.'; + const result = lastRunFromState( + { + metrics: getMetrics({ + triggeredActionsStatus: ActionsCompletion.PARTIAL, + hasReachedQueuedActionsLimit: true, + }), + }, + getRuleResultService({ + warnings: ['MOCK_WARNING'], + outcomeMessage: 'Rule execution reported a warning', + }) + ); + + expect(result.lastRun.outcome).toEqual('warning'); + expect(result.lastRun.outcomeMsg).toEqual([ + frameworkOutcomeMessage, + ruleExecutionOutcomeMessage, + ]); + expect(result.lastRun.warning).toEqual('maxQueuedActions'); + + expect(result.lastRun.alertsCount).toEqual({ + active: 10, + new: 12, + recovered: 11, + ignored: 0, + }); + }); + it('overwrites warning outcome to error if rule execution reports an error', () => { const result = lastRunFromState( { diff --git a/x-pack/plugins/alerting/server/lib/last_run_status.ts b/x-pack/plugins/alerting/server/lib/last_run_status.ts index 56da93f074c27..dedee9a658360 100644 --- a/x-pack/plugins/alerting/server/lib/last_run_status.ts +++ b/x-pack/plugins/alerting/server/lib/last_run_status.ts @@ -48,8 +48,13 @@ export const lastRunFromState = ( outcomeMsg.push(translations.taskRunner.warning.maxAlerts); } else if (metrics.triggeredActionsStatus === ActionsCompletion.PARTIAL) { outcome = RuleLastRunOutcomeValues[1]; - warning = RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS; - outcomeMsg.push(translations.taskRunner.warning.maxExecutableActions); + if (metrics.hasReachedQueuedActionsLimit) { + warning = RuleExecutionStatusWarningReasons.MAX_QUEUED_ACTIONS; + outcomeMsg.push(translations.taskRunner.warning.maxQueuedActions); + } else { + warning = RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS; + outcomeMsg.push(translations.taskRunner.warning.maxExecutableActions); + } } // Overwrite outcome to be error if last run reported any errors diff --git a/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts b/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts index 0210de56a6b0d..34c831db01a75 100644 --- a/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts +++ b/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts @@ -30,6 +30,7 @@ const executionMetrics = { numberOfRecoveredAlerts: 13, hasReachedAlertLimit: false, triggeredActionsStatus: ActionsCompletion.COMPLETE, + hasReachedQueuedActionsLimit: false, }; describe('RuleExecutionStatus', () => { @@ -48,6 +49,7 @@ describe('RuleExecutionStatus', () => { expect(received.numberOfNewAlerts).toEqual(expected.numberOfNewAlerts); expect(received.hasReachedAlertLimit).toEqual(expected.hasReachedAlertLimit); expect(received.triggeredActionsStatus).toEqual(expected.triggeredActionsStatus); + expect(received.hasReachedQueuedActionsLimit).toEqual(expected.hasReachedQueuedActionsLimit); } describe('executionStatusFromState()', () => { @@ -107,6 +109,30 @@ describe('RuleExecutionStatus', () => { }); }); + test('task state with max queued actions warning', () => { + const { status, metrics } = executionStatusFromState({ + alertInstances: { a: {} }, + metrics: { + ...executionMetrics, + triggeredActionsStatus: ActionsCompletion.PARTIAL, + hasReachedQueuedActionsLimit: true, + }, + }); + checkDateIsNearNow(status.lastExecutionDate); + expect(status.warning).toEqual({ + message: translations.taskRunner.warning.maxQueuedActions, + reason: RuleExecutionStatusWarningReasons.MAX_QUEUED_ACTIONS, + }); + expect(status.status).toBe('warning'); + expect(status.error).toBe(undefined); + + testExpectedMetrics(metrics!, { + ...executionMetrics, + triggeredActionsStatus: ActionsCompletion.PARTIAL, + hasReachedQueuedActionsLimit: true, + }); + }); + test('task state with max alerts warning', () => { const { status, metrics } = executionStatusFromState({ alertInstances: { a: {} }, diff --git a/x-pack/plugins/alerting/server/lib/rule_execution_status.ts b/x-pack/plugins/alerting/server/lib/rule_execution_status.ts index 43ab9e2153a94..2fea90c2410ff 100644 --- a/x-pack/plugins/alerting/server/lib/rule_execution_status.ts +++ b/x-pack/plugins/alerting/server/lib/rule_execution_status.ts @@ -47,10 +47,17 @@ export function executionStatusFromState( }; } else if (stateWithMetrics.metrics.triggeredActionsStatus === ActionsCompletion.PARTIAL) { status = RuleExecutionStatusValues[5]; - warning = { - reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, - message: translations.taskRunner.warning.maxExecutableActions, - }; + if (stateWithMetrics.metrics.hasReachedQueuedActionsLimit) { + warning = { + reason: RuleExecutionStatusWarningReasons.MAX_QUEUED_ACTIONS, + message: translations.taskRunner.warning.maxQueuedActions, + }; + } else { + warning = { + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + message: translations.taskRunner.warning.maxExecutableActions, + }; + } } return { diff --git a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts index 8f2410480cc6f..e2b7cc61550bd 100644 --- a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts +++ b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts @@ -25,6 +25,7 @@ describe('RuleRunMetricsStore', () => { expect(ruleRunMetricsStore.getNumberOfNewAlerts()).toBe(0); expect(ruleRunMetricsStore.getStatusByConnectorType('any')).toBe(undefined); expect(ruleRunMetricsStore.getHasReachedAlertLimit()).toBe(false); + expect(ruleRunMetricsStore.getHasReachedQueuedActionsLimit()).toBe(false); }); test('sets and returns numSearches', () => { @@ -95,6 +96,11 @@ describe('RuleRunMetricsStore', () => { expect(metricsStore.getEsSearchDurationMs()).toEqual(555); }); + test('sets and returns hasReachedQueuedActionsLimit', () => { + ruleRunMetricsStore.setHasReachedQueuedActionsLimit(true); + expect(ruleRunMetricsStore.getHasReachedQueuedActionsLimit()).toBe(true); + }); + test('gets metrics', () => { expect(ruleRunMetricsStore.getMetrics()).toEqual({ triggeredActionsStatus: 'partial', @@ -107,6 +113,7 @@ describe('RuleRunMetricsStore', () => { numberOfTriggeredActions: 5, totalSearchDurationMs: 2, hasReachedAlertLimit: true, + hasReachedQueuedActionsLimit: true, }); }); @@ -150,6 +157,19 @@ describe('RuleRunMetricsStore', () => { ).toBe(1); }); + // decrement + test('decrements numberOfTriggeredActions by 1', () => { + ruleRunMetricsStore.decrementNumberOfTriggeredActions(); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(5); + }); + + test('decrements numberOfTriggeredActionsByConnectorType by 1', () => { + ruleRunMetricsStore.decrementNumberOfTriggeredActionsByConnectorType(testConnectorId); + expect( + ruleRunMetricsStore.getStatusByConnectorType(testConnectorId).numberOfTriggeredActions + ).toBe(0); + }); + // Checker test('checks if it has reached the executable actions limit', () => { expect(ruleRunMetricsStore.hasReachedTheExecutableActionsLimit({ default: { max: 10 } })).toBe( diff --git a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts index 14879e1558ba6..80b72e0069bb6 100644 --- a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts +++ b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts @@ -27,6 +27,7 @@ interface State { numberOfGeneratedActions: number; }; }; + hasReachedQueuedActionsLimit: boolean; } export type RuleRunMetrics = Omit & { @@ -44,6 +45,7 @@ export class RuleRunMetricsStore { numberOfNewAlerts: 0, hasReachedAlertLimit: false, connectorTypes: {}, + hasReachedQueuedActionsLimit: false, }; // Getters @@ -90,6 +92,9 @@ export class RuleRunMetricsStore { public getHasReachedAlertLimit = () => { return this.state.hasReachedAlertLimit; }; + public getHasReachedQueuedActionsLimit = () => { + return this.state.hasReachedQueuedActionsLimit; + }; // Setters public setSearchMetrics = (searchMetrics: SearchMetrics[]) => { @@ -135,6 +140,9 @@ export class RuleRunMetricsStore { public setHasReachedAlertLimit = (hasReachedAlertLimit: boolean) => { this.state.hasReachedAlertLimit = hasReachedAlertLimit; }; + public setHasReachedQueuedActionsLimit = (hasReachedQueuedActionsLimit: boolean) => { + this.state.hasReachedQueuedActionsLimit = hasReachedQueuedActionsLimit; + }; // Checkers public hasReachedTheExecutableActionsLimit = (actionsConfigMap: ActionsConfigMap): boolean => @@ -182,4 +190,13 @@ export class RuleRunMetricsStore { const currentVal = this.state.connectorTypes[actionTypeId]?.numberOfGeneratedActions || 0; set(this.state, `connectorTypes["${actionTypeId}"].numberOfGeneratedActions`, currentVal + 1); }; + + // Decrementer + public decrementNumberOfTriggeredActions = () => { + this.state.numberOfTriggeredActions--; + }; + public decrementNumberOfTriggeredActionsByConnectorType = (actionTypeId: string) => { + const currentVal = this.state.connectorTypes[actionTypeId]?.numberOfTriggeredActions || 0; + set(this.state, `connectorTypes["${actionTypeId}"].numberOfTriggeredActions`, currentVal - 1); + }; } diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index b355ecbf370a5..3f59d1457d57c 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -37,6 +37,7 @@ jest.mock('./alerts_service/alerts_service', () => ({ import { SharePluginStart } from '@kbn/share-plugin/server'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { generateAlertingConfig } from './test_utils'; +import { serverlessPluginMock } from '@kbn/serverless/server/mocks'; const sampleRuleType: RuleType = { id: 'test', @@ -73,8 +74,9 @@ describe('Alerting Plugin', () => { data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), unifiedSearch: autocompletePluginMock.createSetupContract(), - // serverless setup is currently empty, and there is no mock - ...(useDataStreamForAlerts ? { serverless: {} } : {}), + ...(useDataStreamForAlerts + ? { serverless: serverlessPluginMock.createSetupContract() } + : {}), }; let plugin: AlertingPlugin; @@ -139,6 +141,7 @@ describe('Alerting Plugin', () => { await waitForSetupComplete(setupMocks); expect(setupContract.getConfig()).toEqual({ + maxScheduledPerMinute: 10000, isUsingSecurity: false, minimumScheduleInterval: { value: '1m', enforce: false }, }); @@ -241,7 +244,9 @@ describe('Alerting Plugin', () => { data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), unifiedSearch: autocompletePluginMock.createSetupContract(), - ...(useDataStreamForAlerts ? { serverless: {} } : {}), + ...(useDataStreamForAlerts + ? { serverless: serverlessPluginMock.createSetupContract() } + : {}), }); const startContract = plugin.start(coreMock.createStart(), { @@ -291,7 +296,9 @@ describe('Alerting Plugin', () => { data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), unifiedSearch: autocompletePluginMock.createSetupContract(), - ...(useDataStreamForAlerts ? { serverless: {} } : {}), + ...(useDataStreamForAlerts + ? { serverless: serverlessPluginMock.createSetupContract() } + : {}), }); const startContract = plugin.start(coreMock.createStart(), { @@ -352,7 +359,9 @@ describe('Alerting Plugin', () => { data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), unifiedSearch: autocompletePluginMock.createSetupContract(), - ...(useDataStreamForAlerts ? { serverless: {} } : {}), + ...(useDataStreamForAlerts + ? { serverless: serverlessPluginMock.createSetupContract() } + : {}), }); const startContract = plugin.start(coreMock.createStart(), { diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index fafd9a13925ab..e2e98347d88fe 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -415,7 +415,7 @@ export class AlertingPlugin { }, getConfig: () => { return { - ...pick(this.config.rules, 'minimumScheduleInterval'), + ...pick(this.config.rules, ['minimumScheduleInterval', 'maxScheduledPerMinute']), isUsingSecurity: this.licenseState ? !!this.licenseState.getIsSecurityEnabled() : false, }; }, @@ -481,6 +481,7 @@ export class AlertingPlugin { taskManager: plugins.taskManager, securityPluginSetup: security, securityPluginStart: plugins.security, + internalSavedObjectsRepository: core.savedObjects.createInternalRepository(['alert']), encryptedSavedObjectsClient, spaceIdToNamespace, getSpaceId(request: KibanaRequest) { @@ -492,6 +493,7 @@ export class AlertingPlugin { authorization: alertingAuthorizationClientFactory, eventLogger: this.eventLogger, minimumScheduleInterval: this.config.rules.minimumScheduleInterval, + maxScheduledPerMinute: this.config.rules.maxScheduledPerMinute, }); rulesSettingsClientFactory.initialize({ diff --git a/x-pack/plugins/alerting/server/raw_rule_schema.ts b/x-pack/plugins/alerting/server/raw_rule_schema.ts index 5843467a4cb46..65d4b48ec7d66 100644 --- a/x-pack/plugins/alerting/server/raw_rule_schema.ts +++ b/x-pack/plugins/alerting/server/raw_rule_schema.ts @@ -10,6 +10,7 @@ import { schema } from '@kbn/config-schema'; const executionStatusWarningReason = schema.oneOf([ schema.literal('maxExecutableActions'), schema.literal('maxAlerts'), + schema.literal('maxQueuedActions'), ]); const executionStatusErrorReason = schema.oneOf([ diff --git a/x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts b/x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts deleted file mode 100644 index c9bfbdca4aa0b..0000000000000 --- a/x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts +++ /dev/null @@ -1,351 +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 { aggregateRulesRoute } from './aggregate_rules'; -import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { rulesClientMock } from '../rules_client.mock'; -import { trackLegacyTerminology } from './lib/track_legacy_terminology'; -import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; - -const rulesClient = rulesClientMock.create(); -const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); -const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); - -jest.mock('../lib/license_api_access', () => ({ - verifyApiAccess: jest.fn(), -})); - -jest.mock('./lib/track_legacy_terminology', () => ({ - trackLegacyTerminology: jest.fn(), -})); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -const aggregateResult = { - status: { - buckets: [ - { - key: 'ok', - doc_count: 15, - }, - { - key: 'error', - doc_count: 2, - }, - { - key: 'active', - doc_count: 23, - }, - { - key: 'pending', - doc_count: 1, - }, - { - key: 'unknown', - doc_count: 0, - }, - { - key: 'warning', - doc_count: 10, - }, - ], - }, - outcome: { - buckets: [ - { - key: 'succeeded', - doc_count: 2, - }, - { - key: 'failed', - doc_count: 4, - }, - { - key: 'warning', - doc_count: 6, - }, - ], - }, - enabled: { - buckets: [ - { - key: 0, - key_as_string: '0', - doc_count: 2, - }, - { - key: 1, - key_as_string: '1', - doc_count: 28, - }, - ], - }, - muted: { - buckets: [ - { - key: 0, - key_as_string: '0', - doc_count: 27, - }, - { - key: 1, - key_as_string: '1', - doc_count: 3, - }, - ], - }, - snoozed: { - doc_count: 0, - count: { - doc_count: 0, - }, - }, - tags: { - buckets: [ - { - key: 'a', - doc_count: 10, - }, - { - key: 'b', - doc_count: 20, - }, - { - key: 'c', - doc_count: 30, - }, - ], - }, -}; - -describe('aggregateRulesRoute', () => { - it('aggregate rules with proper parameters', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - aggregateRulesRoute(router, licenseState); - - const [config, handler] = router.get.mock.calls[0]; - - expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rules/_aggregate"`); - - rulesClient.aggregate.mockResolvedValueOnce(aggregateResult); - - const [context, req, res] = mockHandlerArguments( - { rulesClient }, - { - query: { - default_search_operator: 'AND', - }, - }, - ['ok'] - ); - - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "rule_enabled_status": Object { - "disabled": 2, - "enabled": 28, - }, - "rule_execution_status": Object { - "active": 23, - "error": 2, - "ok": 15, - "pending": 1, - "unknown": 0, - "warning": 10, - }, - "rule_last_run_outcome": Object { - "failed": 4, - "succeeded": 2, - "warning": 6, - }, - "rule_muted_status": Object { - "muted": 3, - "unmuted": 27, - }, - "rule_snoozed_status": Object { - "snoozed": 0, - }, - "rule_tags": Array [ - "a", - "b", - "c", - ], - }, - } - `); - - expect(rulesClient.aggregate).toHaveBeenCalledTimes(1); - expect(rulesClient.aggregate.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "aggs": Object { - "enabled": Object { - "terms": Object { - "field": "alert.attributes.enabled", - }, - }, - "muted": Object { - "terms": Object { - "field": "alert.attributes.muteAll", - }, - }, - "outcome": Object { - "terms": Object { - "field": "alert.attributes.lastRun.outcome", - }, - }, - "snoozed": Object { - "aggs": Object { - "count": Object { - "filter": Object { - "exists": Object { - "field": "alert.attributes.snoozeSchedule.duration", - }, - }, - }, - }, - "nested": Object { - "path": "alert.attributes.snoozeSchedule", - }, - }, - "status": Object { - "terms": Object { - "field": "alert.attributes.executionStatus.status", - }, - }, - "tags": Object { - "terms": Object { - "field": "alert.attributes.tags", - "order": Object { - "_key": "asc", - }, - "size": 50, - }, - }, - }, - "options": Object { - "defaultSearchOperator": "AND", - }, - }, - ] - `); - - expect(res.ok).toHaveBeenCalledWith({ - body: { - rule_enabled_status: { - disabled: 2, - enabled: 28, - }, - rule_execution_status: { - ok: 15, - error: 2, - active: 23, - pending: 1, - unknown: 0, - warning: 10, - }, - rule_last_run_outcome: { - failed: 4, - succeeded: 2, - warning: 6, - }, - rule_muted_status: { - muted: 3, - unmuted: 27, - }, - rule_snoozed_status: { - snoozed: 0, - }, - rule_tags: ['a', 'b', 'c'], - }, - }); - }); - - it('ensures the license allows aggregating rules', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - aggregateRulesRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - rulesClient.aggregate.mockResolvedValueOnce(aggregateResult); - - const [context, req, res] = mockHandlerArguments( - { rulesClient }, - { - query: { - default_search_operator: 'OR', - }, - } - ); - - await handler(context, req, res); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('ensures the license check prevents aggregating rules', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - (verifyApiAccess as jest.Mock).mockImplementation(() => { - throw new Error('OMG'); - }); - - aggregateRulesRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const [context, req, res] = mockHandlerArguments( - {}, - { - query: {}, - }, - ['ok'] - ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('should track calls with deprecated param values', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - aggregateRulesRoute(router, licenseState, mockUsageCounter); - const [, handler] = router.get.mock.calls[0]; - - rulesClient.aggregate.mockResolvedValueOnce(aggregateResult); - - const [context, req, res] = mockHandlerArguments( - { rulesClient }, - { - params: {}, - query: { - search_fields: ['alertTypeId:1', 'message:foo'], - search: 'alertTypeId:2', - }, - }, - ['ok'] - ); - await handler(context, req, res); - expect(trackLegacyTerminology).toHaveBeenCalledTimes(1); - expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([ - 'alertTypeId:2', - ['alertTypeId:1', 'message:foo'], - ]); - }); -}); diff --git a/x-pack/plugins/alerting/server/routes/aggregate_rules.ts b/x-pack/plugins/alerting/server/routes/aggregate_rules.ts deleted file mode 100644 index ea3bb22bd0d17..0000000000000 --- a/x-pack/plugins/alerting/server/routes/aggregate_rules.ts +++ /dev/null @@ -1,133 +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 { IRouter } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { UsageCounter } from '@kbn/usage-collection-plugin/server'; -import { - AggregateOptions, - DefaultRuleAggregationResult, - formatDefaultAggregationResult, - getDefaultRuleAggregation, - RuleAggregationFormattedResult, -} from '../../common'; -import { ILicenseState } from '../lib'; -import { RewriteResponseCase, RewriteRequestCase, verifyAccessAndContext } from './lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; -import { trackLegacyTerminology } from './lib/track_legacy_terminology'; - -// config definition -const querySchema = schema.object({ - search: schema.maybe(schema.string()), - default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { - defaultValue: 'OR', - }), - search_fields: schema.maybe(schema.arrayOf(schema.string())), - has_reference: schema.maybe( - // use nullable as maybe is currently broken - // in config-schema - schema.nullable( - schema.object({ - type: schema.string(), - id: schema.string(), - }) - ) - ), - filter: schema.maybe(schema.string()), -}); - -const rewriteQueryReq: RewriteRequestCase = ({ - default_search_operator: defaultSearchOperator, - has_reference: hasReference, - search_fields: searchFields, - ...rest -}) => ({ - ...rest, - defaultSearchOperator, - ...(hasReference ? { hasReference } : {}), - ...(searchFields ? { searchFields } : {}), -}); -const rewriteBodyRes: RewriteResponseCase = ({ - ruleExecutionStatus, - ruleLastRunOutcome, - ruleEnabledStatus, - ruleMutedStatus, - ruleSnoozedStatus, - ruleTags, - ...rest -}) => ({ - ...rest, - rule_execution_status: ruleExecutionStatus, - rule_last_run_outcome: ruleLastRunOutcome, - rule_enabled_status: ruleEnabledStatus, - rule_muted_status: ruleMutedStatus, - rule_snoozed_status: ruleSnoozedStatus, - rule_tags: ruleTags, -}); - -export const aggregateRulesRoute = ( - router: IRouter, - licenseState: ILicenseState, - usageCounter?: UsageCounter -) => { - router.get( - { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, - validate: { - query: querySchema, - }, - }, - router.handleLegacyErrors( - verifyAccessAndContext(licenseState, async function (context, req, res) { - const rulesClient = (await context.alerting).getRulesClient(); - const options = rewriteQueryReq({ - ...req.query, - has_reference: req.query.has_reference || undefined, - }); - trackLegacyTerminology( - [req.query.search, req.query.search_fields].filter(Boolean) as string[], - usageCounter - ); - const aggregateResult = await rulesClient.aggregate({ - aggs: getDefaultRuleAggregation(), - options, - }); - return res.ok({ - body: rewriteBodyRes(formatDefaultAggregationResult(aggregateResult)), - }); - }) - ) - ); - router.post( - { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, - validate: { - body: querySchema, - }, - }, - router.handleLegacyErrors( - verifyAccessAndContext(licenseState, async function (context, req, res) { - const rulesClient = (await context.alerting).getRulesClient(); - const options = rewriteQueryReq({ - ...req.body, - has_reference: req.body.has_reference || undefined, - }); - trackLegacyTerminology( - [req.body.search, req.body.search_fields].filter(Boolean) as string[], - usageCounter - ); - const aggregateResult = await rulesClient.aggregate({ - aggs: getDefaultRuleAggregation(), - options, - }); - return res.ok({ - body: rewriteBodyRes(formatDefaultAggregationResult(aggregateResult)), - }); - }) - ) - ); -}; diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index 7b9acfc7f77df..1bffc87e7b60a 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -17,7 +17,7 @@ import { createRuleRoute } from './rule/apis/create'; import { getRuleRoute, getInternalRuleRoute } from './get_rule'; import { updateRuleRoute } from './update_rule'; import { deleteRuleRoute } from './delete_rule'; -import { aggregateRulesRoute } from './aggregate_rules'; +import { aggregateRulesRoute } from './rule/apis/aggregate/aggregate_rules_route'; import { disableRuleRoute } from './disable_rule'; import { enableRuleRoute } from './enable_rule'; import { findRulesRoute, findInternalRulesRoute } from './find_rules'; @@ -47,6 +47,7 @@ import { cloneRuleRoute } from './clone_rule'; import { getFlappingSettingsRoute } from './get_flapping_settings'; import { updateFlappingSettingsRoute } from './update_flapping_settings'; import { getRuleTagsRoute } from './get_rule_tags'; +import { getScheduleFrequencyRoute } from './rule/apis/get_schedule_frequency'; import { createMaintenanceWindowRoute } from './maintenance_window/create_maintenance_window'; import { getMaintenanceWindowRoute } from './maintenance_window/get_maintenance_window'; @@ -129,4 +130,5 @@ export function defineRoutes(opts: RouteOptions) { registerRulesValueSuggestionsRoute(router, licenseState, config$!); registerFieldsRoute(router, licenseState); bulkGetMaintenanceWindowRoute(router, licenseState); + getScheduleFrequencyRoute(router, licenseState); } diff --git a/x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts b/x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts deleted file mode 100644 index 9ba0bbd86da3f..0000000000000 --- a/x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts +++ /dev/null @@ -1,301 +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 { aggregateAlertRoute } from './aggregate'; -import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../../lib/license_state.mock'; -import { verifyApiAccess } from '../../lib/license_api_access'; -import { mockHandlerArguments } from '../_mock_handler_arguments'; -import { rulesClientMock } from '../../rules_client.mock'; -import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; -import { trackLegacyTerminology } from '../lib/track_legacy_terminology'; -import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; - -const rulesClient = rulesClientMock.create(); -const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); -const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); - -jest.mock('../../lib/track_legacy_route_usage', () => ({ - trackLegacyRouteUsage: jest.fn(), -})); - -jest.mock('../../lib/license_api_access', () => ({ - verifyApiAccess: jest.fn(), -})); - -jest.mock('../lib/track_legacy_terminology', () => ({ - trackLegacyTerminology: jest.fn(), -})); - -jest.mock('../../../common', () => ({ - ...jest.requireActual('../../../common'), - formatDefaultAggregationResult: jest.fn(), -})); - -const { formatDefaultAggregationResult } = jest.requireMock('../../../common'); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -describe('aggregateAlertRoute', () => { - it('aggregate alerts with proper parameters', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - aggregateAlertRoute(router, licenseState); - - const [config, handler] = router.get.mock.calls[0]; - - expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_aggregate"`); - - const aggregateResult = { - ruleExecutionStatus: { - ok: 15, - error: 2, - active: 23, - pending: 1, - unknown: 0, - }, - ruleLastRunOutcome: { - succeeded: 1, - failed: 2, - warning: 3, - }, - }; - formatDefaultAggregationResult.mockReturnValueOnce(aggregateResult); - - const [context, req, res] = mockHandlerArguments( - { rulesClient }, - { - query: { - default_search_operator: 'AND', - }, - }, - ['ok'] - ); - - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "alertExecutionStatus": Object { - "active": 23, - "error": 2, - "ok": 15, - "pending": 1, - "unknown": 0, - }, - "ruleLastRunOutcome": Object { - "failed": 2, - "succeeded": 1, - "warning": 3, - }, - }, - } - `); - - expect(rulesClient.aggregate).toHaveBeenCalledTimes(1); - expect(rulesClient.aggregate.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "aggs": Object { - "enabled": Object { - "terms": Object { - "field": "alert.attributes.enabled", - }, - }, - "muted": Object { - "terms": Object { - "field": "alert.attributes.muteAll", - }, - }, - "outcome": Object { - "terms": Object { - "field": "alert.attributes.lastRun.outcome", - }, - }, - "snoozed": Object { - "aggs": Object { - "count": Object { - "filter": Object { - "exists": Object { - "field": "alert.attributes.snoozeSchedule.duration", - }, - }, - }, - }, - "nested": Object { - "path": "alert.attributes.snoozeSchedule", - }, - }, - "status": Object { - "terms": Object { - "field": "alert.attributes.executionStatus.status", - }, - }, - "tags": Object { - "terms": Object { - "field": "alert.attributes.tags", - "order": Object { - "_key": "asc", - }, - "size": 50, - }, - }, - }, - "options": Object { - "defaultSearchOperator": "AND", - }, - }, - ] - `); - - expect(res.ok).toHaveBeenCalledWith({ - body: { - ruleLastRunOutcome: aggregateResult.ruleLastRunOutcome, - alertExecutionStatus: aggregateResult.ruleExecutionStatus, - }, - }); - }); - - it('ensures the license allows aggregating alerts', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - aggregateAlertRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - formatDefaultAggregationResult.mockReturnValueOnce({ - ruleExecutionStatus: { - ok: 15, - error: 2, - active: 23, - pending: 1, - unknown: 0, - }, - ruleLastRunOutcome: { - succeeded: 1, - failed: 2, - warning: 3, - }, - }); - - const [context, req, res] = mockHandlerArguments( - { rulesClient }, - { - query: { - default_search_operator: 'OR', - }, - } - ); - - await handler(context, req, res); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('ensures the license check prevents aggregating alerts', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - (verifyApiAccess as jest.Mock).mockImplementation(() => { - throw new Error('OMG'); - }); - - aggregateAlertRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const [context, req, res] = mockHandlerArguments( - {}, - { - query: {}, - }, - ['ok'] - ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('should track every call', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - aggregateAlertRoute(router, licenseState, mockUsageCounter); - - formatDefaultAggregationResult.mockReturnValueOnce({ - ruleExecutionStatus: { - ok: 15, - error: 2, - active: 23, - pending: 1, - unknown: 0, - }, - ruleLastRunOutcome: { - succeeded: 1, - failed: 2, - warning: 3, - }, - }); - - const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments( - { rulesClient }, - { - query: { - default_search_operator: 'AND', - }, - }, - ['ok'] - ); - await handler(context, req, res); - expect(trackLegacyRouteUsage).toHaveBeenCalledWith('aggregate', mockUsageCounter); - }); - - it('should track calls with deprecated param values', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - aggregateAlertRoute(router, licenseState, mockUsageCounter); - - formatDefaultAggregationResult.mockReturnValueOnce({ - ruleExecutionStatus: { - ok: 15, - error: 2, - active: 23, - pending: 1, - unknown: 0, - }, - ruleLastRunOutcome: { - succeeded: 1, - failed: 2, - warning: 3, - }, - }); - - const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments( - { rulesClient }, - { - params: {}, - query: { - search_fields: ['alertTypeId:1', 'message:foo'], - search: 'alertTypeId:2', - }, - }, - ['ok'] - ); - await handler(context, req, res); - expect(trackLegacyTerminology).toHaveBeenCalledTimes(1); - expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([ - 'alertTypeId:2', - ['alertTypeId:1', 'message:foo'], - ]); - }); -}); diff --git a/x-pack/plugins/alerting/server/routes/legacy/aggregate.ts b/x-pack/plugins/alerting/server/routes/legacy/aggregate.ts deleted file mode 100644 index 44559830a24e9..0000000000000 --- a/x-pack/plugins/alerting/server/routes/legacy/aggregate.ts +++ /dev/null @@ -1,98 +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 { schema } from '@kbn/config-schema'; -import { UsageCounter } from '@kbn/usage-collection-plugin/server'; -import type { AlertingRouter } from '../../types'; -import { ILicenseState } from '../../lib/license_state'; -import { verifyApiAccess } from '../../lib/license_api_access'; -import { - LEGACY_BASE_ALERT_API_PATH, - DefaultRuleAggregationResult, - getDefaultRuleAggregation, - formatDefaultAggregationResult, -} from '../../../common'; -import { renameKeys } from '../lib/rename_keys'; -import { FindOptions } from '../../rules_client'; -import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; -import { trackLegacyTerminology } from '../lib/track_legacy_terminology'; - -// config definition -const querySchema = schema.object({ - search: schema.maybe(schema.string()), - default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { - defaultValue: 'OR', - }), - search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), - has_reference: schema.maybe( - // use nullable as maybe is currently broken - // in config-schema - schema.nullable( - schema.object({ - type: schema.string(), - id: schema.string(), - }) - ) - ), - filter: schema.maybe(schema.string()), -}); - -export const aggregateAlertRoute = ( - router: AlertingRouter, - licenseState: ILicenseState, - usageCounter?: UsageCounter -) => { - router.get( - { - path: `${LEGACY_BASE_ALERT_API_PATH}/_aggregate`, - validate: { - query: querySchema, - }, - }, - router.handleLegacyErrors(async function (context, req, res) { - verifyApiAccess(licenseState); - if (!context.alerting) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); - } - const rulesClient = (await context.alerting).getRulesClient(); - - trackLegacyRouteUsage('aggregate', usageCounter); - trackLegacyTerminology( - [req.query.search, req.query.search_fields].filter(Boolean) as string[], - usageCounter - ); - - const query = req.query; - const renameMap = { - default_search_operator: 'defaultSearchOperator', - has_reference: 'hasReference', - search: 'search', - filter: 'filter', - }; - - const options = renameKeys>(renameMap, query); - - if (query.search_fields) { - options.searchFields = Array.isArray(query.search_fields) - ? query.search_fields - : [query.search_fields]; - } - - const aggregateResult = await rulesClient.aggregate({ - options, - aggs: getDefaultRuleAggregation(), - }); - const { ruleExecutionStatus, ...rest } = formatDefaultAggregationResult(aggregateResult); - return res.ok({ - body: { - ...rest, - alertExecutionStatus: ruleExecutionStatus, - }, - }); - }) - ); -}; diff --git a/x-pack/plugins/alerting/server/routes/legacy/index.ts b/x-pack/plugins/alerting/server/routes/legacy/index.ts index a89ccf0920b29..f915cff705318 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/index.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { aggregateAlertRoute } from './aggregate'; import { createAlertRoute } from './create'; import { deleteAlertRoute } from './delete'; import { findAlertRoute } from './find'; @@ -28,7 +27,6 @@ export function defineLegacyRoutes(opts: RouteOptions) { const { router, licenseState, encryptedSavedObjects, usageCounter } = opts; createAlertRoute(opts); - aggregateAlertRoute(router, licenseState, usageCounter); deleteAlertRoute(router, licenseState, usageCounter); findAlertRoute(router, licenseState, usageCounter); getAlertRoute(router, licenseState, usageCounter); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts new file mode 100644 index 0000000000000..51a7da620dfb3 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts @@ -0,0 +1,351 @@ +/* + * Copyright 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 { aggregateRulesRoute } from './aggregate_rules_route'; +import { httpServiceMock } from '@kbn/core/server/mocks'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { verifyApiAccess } from '../../../../lib/license_api_access'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; +import { trackLegacyTerminology } from '../../../lib/track_legacy_terminology'; +import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; + +const rulesClient = rulesClientMock.create(); +const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); +const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + +jest.mock('../../../../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +jest.mock('../../../lib/track_legacy_terminology', () => ({ + trackLegacyTerminology: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +const aggregateResult = { + status: { + buckets: [ + { + key: 'ok', + doc_count: 15, + }, + { + key: 'error', + doc_count: 2, + }, + { + key: 'active', + doc_count: 23, + }, + { + key: 'pending', + doc_count: 1, + }, + { + key: 'unknown', + doc_count: 0, + }, + { + key: 'warning', + doc_count: 10, + }, + ], + }, + outcome: { + buckets: [ + { + key: 'succeeded', + doc_count: 2, + }, + { + key: 'failed', + doc_count: 4, + }, + { + key: 'warning', + doc_count: 6, + }, + ], + }, + enabled: { + buckets: [ + { + key: 0, + key_as_string: '0', + doc_count: 2, + }, + { + key: 1, + key_as_string: '1', + doc_count: 28, + }, + ], + }, + muted: { + buckets: [ + { + key: 0, + key_as_string: '0', + doc_count: 27, + }, + { + key: 1, + key_as_string: '1', + doc_count: 3, + }, + ], + }, + snoozed: { + doc_count: 0, + count: { + doc_count: 0, + }, + }, + tags: { + buckets: [ + { + key: 'a', + doc_count: 10, + }, + { + key: 'b', + doc_count: 20, + }, + { + key: 'c', + doc_count: 30, + }, + ], + }, +}; + +describe('aggregateRulesRoute', () => { + it('aggregate rules with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + aggregateRulesRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rules/_aggregate"`); + + rulesClient.aggregate.mockResolvedValueOnce(aggregateResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: { + default_search_operator: 'AND', + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "rule_enabled_status": Object { + "disabled": 2, + "enabled": 28, + }, + "rule_execution_status": Object { + "active": 23, + "error": 2, + "ok": 15, + "pending": 1, + "unknown": 0, + "warning": 10, + }, + "rule_last_run_outcome": Object { + "failed": 4, + "succeeded": 2, + "warning": 6, + }, + "rule_muted_status": Object { + "muted": 3, + "unmuted": 27, + }, + "rule_snoozed_status": Object { + "snoozed": 0, + }, + "rule_tags": Array [ + "a", + "b", + "c", + ], + }, + } + `); + + expect(rulesClient.aggregate).toHaveBeenCalledTimes(1); + expect(rulesClient.aggregate.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "aggs": Object { + "enabled": Object { + "terms": Object { + "field": "alert.attributes.enabled", + }, + }, + "muted": Object { + "terms": Object { + "field": "alert.attributes.muteAll", + }, + }, + "outcome": Object { + "terms": Object { + "field": "alert.attributes.lastRun.outcome", + }, + }, + "snoozed": Object { + "aggs": Object { + "count": Object { + "filter": Object { + "exists": Object { + "field": "alert.attributes.snoozeSchedule.duration", + }, + }, + }, + }, + "nested": Object { + "path": "alert.attributes.snoozeSchedule", + }, + }, + "status": Object { + "terms": Object { + "field": "alert.attributes.executionStatus.status", + }, + }, + "tags": Object { + "terms": Object { + "field": "alert.attributes.tags", + "order": Object { + "_key": "asc", + }, + "size": 50, + }, + }, + }, + "options": Object { + "defaultSearchOperator": "AND", + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: { + rule_enabled_status: { + disabled: 2, + enabled: 28, + }, + rule_execution_status: { + ok: 15, + error: 2, + active: 23, + pending: 1, + unknown: 0, + warning: 10, + }, + rule_last_run_outcome: { + failed: 4, + succeeded: 2, + warning: 6, + }, + rule_muted_status: { + muted: 3, + unmuted: 27, + }, + rule_snoozed_status: { + snoozed: 0, + }, + rule_tags: ['a', 'b', 'c'], + }, + }); + }); + + it('ensures the license allows aggregating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + aggregateRulesRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + rulesClient.aggregate.mockResolvedValueOnce(aggregateResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: { + default_search_operator: 'OR', + }, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents aggregating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + aggregateRulesRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + {}, + { + body: {}, + }, + ['ok'] + ); + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('should track calls with deprecated param values', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + aggregateRulesRoute(router, licenseState, mockUsageCounter); + const [, handler] = router.post.mock.calls[0]; + + rulesClient.aggregate.mockResolvedValueOnce(aggregateResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: {}, + body: { + search_fields: ['alertTypeId:1', 'message:foo'], + search: 'alertTypeId:2', + }, + }, + ['ok'] + ); + await handler(context, req, res); + expect(trackLegacyTerminology).toHaveBeenCalledTimes(1); + expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([ + 'alertTypeId:2', + ['alertTypeId:1', 'message:foo'], + ]); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.ts new file mode 100644 index 0000000000000..a8f7a20baa4cb --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from '@kbn/core/server'; +import { UsageCounter } from '@kbn/usage-collection-plugin/server'; + +import { defaultRuleAggregationFactoryV1 } from '../../../../application/rule/methods/aggregate'; +import { ILicenseState } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types'; +import { trackLegacyTerminology } from '../../../lib/track_legacy_terminology'; +import { + aggregateRulesRequestBodySchemaV1, + AggregateRulesRequestBodyV1, + AggregateRulesResponseV1, +} from '../../../../../common/routes/rule/apis/aggregate'; +import { formatDefaultAggregationResult } from './transforms'; +import { transformAggregateQueryRequestV1, transformAggregateBodyResponseV1 } from './transforms'; +import { DefaultRuleAggregationResult } from './types'; + +export const aggregateRulesRoute = ( + router: IRouter, + licenseState: ILicenseState, + usageCounter?: UsageCounter +) => { + router.post( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, + validate: { + body: aggregateRulesRequestBodySchemaV1, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const rulesClient = (await context.alerting).getRulesClient(); + const body: AggregateRulesRequestBodyV1 = req.body; + const options = transformAggregateQueryRequestV1({ + ...body, + has_reference: body.has_reference || undefined, + }); + trackLegacyTerminology( + [body.search, body.search_fields].filter(Boolean) as string[], + usageCounter + ); + + const aggregateResult = await rulesClient.aggregate({ + aggs: defaultRuleAggregationFactoryV1(), + options, + }); + + const responsePayload: AggregateRulesResponseV1 = { + body: transformAggregateBodyResponseV1(formatDefaultAggregationResult(aggregateResult)), + }; + + return res.ok(responsePayload); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/format_default_aggregation_result/index.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/format_default_aggregation_result/index.test.ts new file mode 100644 index 0000000000000..0ea0ea3c22a7d --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/format_default_aggregation_result/index.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { formatDefaultAggregationResult } from '.'; + +describe('formatDefaultAggregationResult', () => { + it('should format aggregation result', () => { + const result = formatDefaultAggregationResult({ + status: { + buckets: [ + { key: 'active', doc_count: 8 }, + { key: 'error', doc_count: 6 }, + { key: 'ok', doc_count: 10 }, + { key: 'pending', doc_count: 4 }, + { key: 'unknown', doc_count: 2 }, + { key: 'warning', doc_count: 1 }, + ], + }, + outcome: { + buckets: [ + { key: 'succeeded', doc_count: 2 }, + { key: 'failed', doc_count: 4 }, + { key: 'warning', doc_count: 6 }, + ], + }, + enabled: { + buckets: [ + { key: 0, key_as_string: '0', doc_count: 2 }, + { key: 1, key_as_string: '1', doc_count: 28 }, + ], + }, + muted: { + buckets: [ + { key: 0, key_as_string: '0', doc_count: 27 }, + { key: 1, key_as_string: '1', doc_count: 3 }, + ], + }, + snoozed: { + count: { + doc_count: 5, + }, + }, + tags: { + buckets: [ + { + key: 'a', + doc_count: 10, + }, + { + key: 'b', + doc_count: 20, + }, + { + key: 'c', + doc_count: 30, + }, + ], + }, + }); + + expect(result).toEqual( + expect.objectContaining({ + ruleExecutionStatus: { + active: 8, + error: 6, + ok: 10, + pending: 4, + unknown: 2, + warning: 1, + }, + ruleLastRunOutcome: { + succeeded: 2, + failed: 4, + warning: 6, + }, + ruleEnabledStatus: { + enabled: 28, + disabled: 2, + }, + ruleMutedStatus: { + muted: 3, + unmuted: 27, + }, + ruleSnoozedStatus: { + snoozed: 5, + }, + ruleTags: ['a', 'b', 'c'], + }) + ); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/format_default_aggregation_result/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/format_default_aggregation_result/index.ts new file mode 100644 index 0000000000000..d751875e104d7 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/format_default_aggregation_result/index.ts @@ -0,0 +1,91 @@ +/* + * Copyright 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 { RuleAggregationFormattedResult } from '../../../../../../application/rule/methods/aggregate/types'; +import { RuleExecutionStatusValues, RuleLastRunOutcomeValues } from '../../../../../../../common'; +import { DefaultRuleAggregationResult } from '../../types'; + +export const formatDefaultAggregationResult = ( + aggregations: DefaultRuleAggregationResult +): RuleAggregationFormattedResult => { + if (!aggregations) { + // Return a placeholder with all zeroes + const placeholder: RuleAggregationFormattedResult = { + ruleExecutionStatus: {}, + ruleLastRunOutcome: {}, + ruleEnabledStatus: { + enabled: 0, + disabled: 0, + }, + ruleMutedStatus: { + muted: 0, + unmuted: 0, + }, + ruleSnoozedStatus: { snoozed: 0 }, + ruleTags: [], + }; + + for (const key of RuleExecutionStatusValues) { + placeholder.ruleExecutionStatus[key] = 0; + } + + return placeholder; + } + + const ruleExecutionStatus = aggregations.status.buckets.map(({ key, doc_count: docCount }) => ({ + [key]: docCount, + })); + + const ruleLastRunOutcome = aggregations.outcome.buckets.map(({ key, doc_count: docCount }) => ({ + [key]: docCount, + })); + + const enabledBuckets = aggregations.enabled.buckets; + const mutedBuckets = aggregations.muted.buckets; + + const result: RuleAggregationFormattedResult = { + ruleExecutionStatus: ruleExecutionStatus.reduce( + (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), + {} + ), + ruleLastRunOutcome: ruleLastRunOutcome.reduce( + (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), + {} + ), + ruleEnabledStatus: { + enabled: enabledBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, + disabled: enabledBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, + }, + ruleMutedStatus: { + muted: mutedBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, + unmuted: mutedBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, + }, + ruleSnoozedStatus: { + snoozed: aggregations.snoozed?.count?.doc_count ?? 0, + }, + ruleTags: [], + }; + + // Fill missing keys with zeroes + for (const key of RuleExecutionStatusValues) { + if (!result.ruleExecutionStatus.hasOwnProperty(key)) { + result.ruleExecutionStatus[key] = 0; + } + } + for (const key of RuleLastRunOutcomeValues) { + if (!result.ruleLastRunOutcome.hasOwnProperty(key)) { + result.ruleLastRunOutcome[key] = 0; + } + } + + const tagsBuckets = aggregations.tags?.buckets || []; + tagsBuckets.forEach((bucket) => { + result.ruleTags.push(bucket.key); + }); + + return result; +}; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/index.ts new file mode 100644 index 0000000000000..bd9e8015cf8f4 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { formatDefaultAggregationResult } from './format_default_aggregation_result'; +export { transformAggregateQueryRequest } from './transform_aggregate_query_request/latest'; +export { transformAggregateBodyResponse } from './transform_aggregate_body_response/latest'; + +export { transformAggregateQueryRequest as transformAggregateQueryRequestV1 } from './transform_aggregate_query_request/v1'; +export { transformAggregateBodyResponse as transformAggregateBodyResponseV1 } from './transform_aggregate_body_response/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_body_response/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_body_response/latest.ts new file mode 100644 index 0000000000000..1d31867d87b28 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_body_response/latest.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { transformAggregateBodyResponse } from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_body_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_body_response/v1.ts new file mode 100644 index 0000000000000..87bd99af3a665 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_body_response/v1.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RuleAggregationFormattedResult } from '../../../../../../application/rule/methods/aggregate/types'; +import { AggregateRulesResponseBodyV1 } from '../../../../../../../common/routes/rule/apis/aggregate'; + +export const transformAggregateBodyResponse = ({ + ruleExecutionStatus, + ruleEnabledStatus, + ruleLastRunOutcome, + ruleMutedStatus, + ruleSnoozedStatus, + ruleTags, +}: RuleAggregationFormattedResult): AggregateRulesResponseBodyV1 => ({ + rule_execution_status: ruleExecutionStatus, + rule_last_run_outcome: ruleLastRunOutcome, + rule_enabled_status: ruleEnabledStatus, + rule_muted_status: ruleMutedStatus, + rule_snoozed_status: ruleSnoozedStatus, + rule_tags: ruleTags, +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/latest.ts new file mode 100644 index 0000000000000..9ecc4c35b6049 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformAggregateQueryRequest } from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/v1.ts new file mode 100644 index 0000000000000..7b5879227ce97 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/v1.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RewriteRequestCase } from '@kbn/actions-plugin/common'; +import { AggregateOptions } from '../../../../../../application/rule/methods/aggregate/types'; + +export const transformAggregateQueryRequest: RewriteRequestCase = ({ + search, + default_search_operator: defaultSearchOperator, + search_fields: searchFields, + has_reference: hasReference, + filter, +}) => ({ + defaultSearchOperator, + ...(hasReference ? { hasReference } : {}), + ...(searchFields ? { searchFields } : {}), + ...(search ? { search } : {}), + ...(filter ? { filter } : {}), +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/types/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/types/index.ts new file mode 100644 index 0000000000000..a8eccab7f1e00 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/types/index.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface DefaultRuleAggregationResult { + status: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + outcome: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + muted: { + buckets: Array<{ + key: number; + key_as_string: string; + doc_count: number; + }>; + }; + enabled: { + buckets: Array<{ + key: number; + key_as_string: string; + doc_count: number; + }>; + }; + snoozed: { + count: { + doc_count: number; + }; + }; + tags: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; +} diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.test.ts new file mode 100644 index 0000000000000..9778bc12afc68 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright 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 { getScheduleFrequencyRoute } from './get_schedule_frequency_route'; +import { httpServiceMock } from '@kbn/core/server/mocks'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; + +const rulesClient = rulesClientMock.create(); + +jest.mock('../../../../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getScheduleFrequencyRoute', () => { + it('gets the schedule frequency limit and remaining allotment', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getScheduleFrequencyRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toBe('/internal/alerting/rules/_schedule_frequency'); + + expect(config).toMatchInlineSnapshot(` + Object { + "path": "/internal/alerting/rules/_schedule_frequency", + "validate": Object {}, + } + `); + + rulesClient.getScheduleFrequency.mockResolvedValueOnce({ + totalScheduledPerMinute: 9000, + remainingSchedulesPerMinute: 1000, + }); + + const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok']); + + await handler(context, req, res); + + expect(rulesClient.getScheduleFrequency).toHaveBeenCalledTimes(1); + expect(res.ok).toHaveBeenCalledWith({ + body: { + total_scheduled_per_minute: 9000, + remaining_schedules_per_minute: 1000, + }, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.ts b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.ts new file mode 100644 index 0000000000000..438c2f2b4aa54 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/get_schedule_frequency_route.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from '@kbn/core/server'; +import { ILicenseState } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types'; +import { GetScheduleFrequencyResponseV1 } from '../../../../../common/routes/rule/apis/get_schedule_frequency'; +import { transformGetScheduleFrequencyResultV1 } from './transforms'; + +export const getScheduleFrequencyRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_schedule_frequency`, + validate: {}, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async (context, req, res) => { + const rulesClient = (await context.alerting).getRulesClient(); + + const scheduleFrequencyResult = await rulesClient.getScheduleFrequency(); + + const response: GetScheduleFrequencyResponseV1 = { + body: transformGetScheduleFrequencyResultV1(scheduleFrequencyResult), + }; + + return res.ok(response); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/index.ts new file mode 100644 index 0000000000000..a27588e2cf9da --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getScheduleFrequencyRoute } from './get_schedule_frequency_route'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/index.ts new file mode 100644 index 0000000000000..06bdc9e64f637 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformGetScheduleFrequencyResult } from './transform_get_schedule_frequency_result/latest'; + +export { transformGetScheduleFrequencyResult as transformGetScheduleFrequencyResultV1 } from './transform_get_schedule_frequency_result/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/transform_get_schedule_frequency_result/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/transform_get_schedule_frequency_result/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/transform_get_schedule_frequency_result/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/transform_get_schedule_frequency_result/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/transform_get_schedule_frequency_result/v1.ts new file mode 100644 index 0000000000000..228de89cab7ca --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/get_schedule_frequency/transforms/transform_get_schedule_frequency_result/v1.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { GetScheduleFrequencyResponseBodyV1 } from '../../../../../../../common/routes/rule/apis/get_schedule_frequency'; +import type { GetScheduleFrequencyResult } from '../../../../../../application/rule/methods/get_schedule_frequency'; + +export const transformGetScheduleFrequencyResult = ( + result: GetScheduleFrequencyResult +): GetScheduleFrequencyResponseBodyV1 => { + return { + total_scheduled_per_minute: result.totalScheduledPerMinute, + remaining_schedules_per_minute: result.remainingSchedulesPerMinute, + }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 4b98ef3abaf9d..ebe6f6c555a67 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -52,6 +52,7 @@ const createRulesClientMock = () => { runSoon: jest.fn(), clone: jest.fn(), getAlertFromRaw: jest.fn(), + getScheduleFrequency: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts index 6f3e595a1ab46..0548c5f7fc783 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -24,6 +28,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v8.0.0'; const rulesClientParams: jest.Mocked = { @@ -36,10 +41,12 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleIntervalInMs: 1, fieldsToExcludeFromPublicApi: [], diff --git a/x-pack/plugins/alerting/server/rules_client/methods/aggregate.ts b/x-pack/plugins/alerting/server/rules_client/methods/aggregate.ts deleted file mode 100644 index 6dd26c1a7e197..0000000000000 --- a/x-pack/plugins/alerting/server/rules_client/methods/aggregate.ts +++ /dev/null @@ -1,64 +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 { KueryNode, nodeBuilder } from '@kbn/es-query'; -import type { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { AlertingAuthorizationEntity } from '../../authorization'; -import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; -import { buildKueryNodeFilter } from '../common'; -import { alertingAuthorizationFilterOpts } from '../common/constants'; -import { RulesClientContext } from '../types'; -import { RawRule, AggregateOptions } from '../../types'; -import { validateRuleAggregationFields } from '../lib/validate_rule_aggregation_fields'; - -export interface AggregateParams { - options?: AggregateOptions; - aggs: Record; -} - -export async function aggregate>( - context: RulesClientContext, - params: AggregateParams -): Promise { - const { options = {}, aggs } = params; - const { filter, page = 1, perPage = 0, ...restOptions } = options; - - let authorizationTuple; - try { - authorizationTuple = await context.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts - ); - validateRuleAggregationFields(aggs); - } catch (error) { - context.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.AGGREGATE, - error, - }) - ); - throw error; - } - - const { filter: authorizationFilter } = authorizationTuple; - const filterKueryNode = buildKueryNodeFilter(filter); - - const result = await context.unsecuredSavedObjectsClient.find({ - ...restOptions, - filter: - authorizationFilter && filterKueryNode - ? nodeBuilder.and([filterKueryNode, authorizationFilter as KueryNode]) - : authorizationFilter, - page, - perPage, - type: 'alert', - aggs, - }); - - // params. - return result.aggregations!; -} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts index 42b20e65585c1..0e3f0eb042ec5 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts @@ -4,9 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import pMap from 'p-map'; import { KueryNode, nodeBuilder } from '@kbn/es-query'; -import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; +import { SavedObjectsBulkUpdateObject, SavedObjectsFindResult } from '@kbn/core/server'; import { withSpan } from '@kbn/apm-utils'; import { Logger } from '@kbn/core/server'; import { TaskManagerStartContract, TaskStatus } from '@kbn/task-manager-plugin/server'; @@ -28,6 +29,7 @@ import { migrateLegacyActions, } from '../lib'; import { RulesClientContext, BulkOperationError, BulkOptions } from '../types'; +import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency'; const getShouldScheduleTask = async ( context: RulesClientContext, @@ -121,116 +123,136 @@ const bulkEnableRulesWithOCC = async ( ) ); + const rulesFinderRules: Array> = []; const rulesToEnable: Array> = []; const errors: BulkOperationError[] = []; const ruleNameToRuleIdMapping: Record = {}; const username = await context.getUserName(); + let scheduleValidationError = ''; await withSpan( { name: 'Get rules, collect them and their attributes', type: 'rules' }, async () => { for await (const response of rulesFinder.find()) { - await pMap(response.saved_objects, async (rule) => { - try { - if (rule.attributes.actions.length) { - try { - await context.actionsAuthorization.ensureAuthorized({ operation: 'execute' }); - } catch (error) { - throw Error(`Rule not authorized for bulk enable - ${error.message}`); - } - } - if (rule.attributes.name) { - ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; - } - - const migratedActions = await migrateLegacyActions(context, { - ruleId: rule.id, - actions: rule.attributes.actions, - references: rule.references, - attributes: rule.attributes, - }); + rulesFinderRules.push(...response.saved_objects); + } + await rulesFinder.close(); - const updatedAttributes = updateMeta(context, { - ...rule.attributes, - ...(!rule.attributes.apiKey && - (await createNewAPIKeySet(context, { - id: rule.attributes.alertTypeId, - ruleName: rule.attributes.name, - username, - shouldUpdateApiKey: true, - }))), - ...(migratedActions.hasLegacyActions - ? { - actions: migratedActions.resultedActions, - throttle: undefined, - notifyWhen: undefined, - } - : {}), - enabled: true, - updatedBy: username, - updatedAt: new Date().toISOString(), - executionStatus: { - status: 'pending', - lastDuration: 0, - lastExecutionDate: new Date().toISOString(), - error: null, - warning: null, - }, - }); + const updatedInterval = rulesFinderRules + .filter((rule) => !rule.attributes.enabled) + .map((rule) => rule.attributes.schedule?.interval); - const shouldScheduleTask = await getShouldScheduleTask( - context, - rule.attributes.scheduledTaskId - ); + try { + await validateScheduleLimit({ + context, + updatedInterval, + }); + } catch (error) { + scheduleValidationError = `Error validating enable rule data - ${error.message}`; + } - let scheduledTaskId; - if (shouldScheduleTask) { - const scheduledTask = await scheduleTask(context, { - id: rule.id, - consumer: rule.attributes.consumer, - ruleTypeId: rule.attributes.alertTypeId, - schedule: rule.attributes.schedule as IntervalSchedule, - throwOnConflict: false, - }); - scheduledTaskId = scheduledTask.id; + await pMap(rulesFinderRules, async (rule) => { + try { + if (scheduleValidationError) { + throw Error(scheduleValidationError); + } + if (rule.attributes.actions.length) { + try { + await context.actionsAuthorization.ensureAuthorized({ operation: 'execute' }); + } catch (error) { + throw Error(`Rule not authorized for bulk enable - ${error.message}`); } + } + if (rule.attributes.name) { + ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; + } - rulesToEnable.push({ - ...rule, - attributes: { - ...updatedAttributes, - ...(scheduledTaskId ? { scheduledTaskId } : undefined), - }, - ...(migratedActions.hasLegacyActions - ? { references: migratedActions.resultedReferences } - : {}), - }); + const migratedActions = await migrateLegacyActions(context, { + ruleId: rule.id, + actions: rule.attributes.actions, + references: rule.references, + attributes: rule.attributes, + }); + + const updatedAttributes = updateMeta(context, { + ...rule.attributes, + ...(!rule.attributes.apiKey && + (await createNewAPIKeySet(context, { + id: rule.attributes.alertTypeId, + ruleName: rule.attributes.name, + username, + shouldUpdateApiKey: true, + }))), + ...(migratedActions.hasLegacyActions + ? { + actions: migratedActions.resultedActions, + throttle: undefined, + notifyWhen: undefined, + } + : {}), + enabled: true, + updatedBy: username, + updatedAt: new Date().toISOString(), + executionStatus: { + status: 'pending', + lastDuration: 0, + lastExecutionDate: new Date().toISOString(), + error: null, + warning: null, + }, + }); - context.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.ENABLE, - outcome: 'unknown', - savedObject: { type: 'alert', id: rule.id }, - }) - ); - } catch (error) { - errors.push({ - message: error.message, - rule: { - id: rule.id, - name: rule.attributes?.name, - }, + const shouldScheduleTask = await getShouldScheduleTask( + context, + rule.attributes.scheduledTaskId + ); + + let scheduledTaskId; + if (shouldScheduleTask) { + const scheduledTask = await scheduleTask(context, { + id: rule.id, + consumer: rule.attributes.consumer, + ruleTypeId: rule.attributes.alertTypeId, + schedule: rule.attributes.schedule as IntervalSchedule, + throwOnConflict: false, }); - context.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.ENABLE, - error, - }) - ); + scheduledTaskId = scheduledTask.id; } - }); - } - await rulesFinder.close(); + + rulesToEnable.push({ + ...rule, + attributes: { + ...updatedAttributes, + ...(scheduledTaskId ? { scheduledTaskId } : undefined), + }, + ...(migratedActions.hasLegacyActions + ? { references: migratedActions.resultedReferences } + : {}), + }); + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + outcome: 'unknown', + savedObject: { type: 'alert', id: rule.id }, + }) + ); + } catch (error) { + errors.push({ + message: error.message, + rule: { + id: rule.id, + name: rule.attributes?.name, + }, + }); + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + error, + }) + ); + } + }); } ); diff --git a/x-pack/plugins/alerting/server/rules_client/methods/enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts index 948f254fe462a..97e677a0c28cc 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/enable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import Boom from '@hapi/boom'; import type { SavedObjectReference } from '@kbn/core/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { RawRule, IntervalSchedule } from '../../types'; @@ -13,6 +14,7 @@ import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { RulesClientContext } from '../types'; import { updateMeta, createNewAPIKeySet, scheduleTask, migrateLegacyActions } from '../lib'; +import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency'; export async function enable(context: RulesClientContext, { id }: { id: string }): Promise { return await retryIfConflicts( @@ -46,6 +48,15 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string } references = alert.references; } + try { + await validateScheduleLimit({ + context, + updatedInterval: attributes.schedule.interval, + }); + } catch (error) { + throw Boom.badRequest(`Error validating enable rule data - ${error.message}`); + } + try { await context.authorization.ensureAuthorized({ ruleTypeId: attributes.alertTypeId, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts index 68dc7fa0dd6a7..925f128f0b8b3 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -33,6 +33,7 @@ import { createNewAPIKeySet, migrateLegacyActions, } from '../lib'; +import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency'; type ShouldIncrementRevision = (params?: RuleTypeParams) => boolean; @@ -88,6 +89,21 @@ async function updateWithOCC( alertSavedObject = await context.unsecuredSavedObjectsClient.get('alert', id); } + const { + attributes: { enabled, schedule }, + } = alertSavedObject; + try { + if (enabled && schedule.interval !== data.schedule.interval) { + await validateScheduleLimit({ + context, + prevInterval: alertSavedObject.attributes.schedule?.interval, + updatedInterval: data.schedule.interval, + }); + } + } catch (error) { + throw Boom.badRequest(`Error validating update data - ${error.message}`); + } + try { await context.authorization.ensureAuthorized({ ruleTypeId: alertSavedObject.attributes.alertTypeId, diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d34a8d10ef172..f2274648d3981 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -33,7 +33,8 @@ import { GetRuleExecutionKPIParams, } from './methods/get_execution_kpi'; import { find, FindParams } from './methods/find'; -import { aggregate, AggregateParams } from './methods/aggregate'; +import { AggregateParams } from '../application/rule/methods/aggregate/types'; +import { aggregateRules } from '../application/rule/methods/aggregate'; import { deleteRule } from './methods/delete'; import { update, UpdateOptions } from './methods/update'; import { bulkDeleteRules } from './methods/bulk_delete'; @@ -57,6 +58,7 @@ import { runSoon } from './methods/run_soon'; import { listRuleTypes } from './methods/list_rule_types'; import { getAlertFromRaw, GetAlertFromRawParams } from './lib/get_alert_from_raw'; import { getTags, GetTagsParams } from './methods/get_tags'; +import { getScheduleFrequency } from '../application/rule/methods/get_schedule_frequency/get_schedule_frequency'; export type ConstructorOptions = Omit< RulesClientContext, @@ -107,7 +109,7 @@ export class RulesClient { } public aggregate = >(params: AggregateParams): Promise => - aggregate(this.context, params); + aggregateRules(this.context, params); public clone = (...args: CloneArguments) => clone(this.context, ...args); public create = (params: CreateRuleParams) => @@ -179,6 +181,8 @@ export class RulesClient { public getTags = (params: GetTagsParams) => getTags(this.context, params); + public getScheduleFrequency = () => getScheduleFrequency(this.context); + public getAlertFromRaw = (params: GetAlertFromRawParams) => getAlertFromRaw( this.context, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts deleted file mode 100644 index c45e74da45999..0000000000000 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ /dev/null @@ -1,431 +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 { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; -import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; -import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; -import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; -import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; -import { AlertingAuthorization } from '../../authorization/alerting_authorization'; -import { ActionsAuthorization } from '@kbn/actions-plugin/server'; -import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; -import { getBeforeSetup, setGlobalDate } from './lib'; -import { - RecoveredActionGroup, - getDefaultRuleAggregation, - DefaultRuleAggregationResult, -} from '../../../common'; -import { RegistryRuleType } from '../../rule_type_registry'; -import { fromKueryExpression, nodeTypes } from '@kbn/es-query'; - -const taskManager = taskManagerMock.createStart(); -const ruleTypeRegistry = ruleTypeRegistryMock.create(); -const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); - -const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertingAuthorizationMock.create(); -const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditLoggerMock.create(); - -const kibanaVersion = 'v7.10.0'; -const rulesClientParams: jest.Mocked = { - taskManager, - ruleTypeRegistry, - unsecuredSavedObjectsClient, - minimumScheduleInterval: { value: '1m', enforce: false }, - authorization: authorization as unknown as AlertingAuthorization, - actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, - spaceId: 'default', - namespace: 'default', - getUserName: jest.fn(), - createAPIKey: jest.fn(), - logger: loggingSystemMock.create().get(), - encryptedSavedObjectsClient: encryptedSavedObjects, - getActionsClient: jest.fn(), - getEventLogClient: jest.fn(), - kibanaVersion, - isAuthenticationTypeAPIKey: jest.fn(), - getAuthenticationAPIKey: jest.fn(), -}; - -beforeEach(() => { - getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); - (auditLogger.log as jest.Mock).mockClear(); -}); - -setGlobalDate(); - -describe('aggregate()', () => { - const listedTypes = new Set([ - { - actionGroups: [], - actionVariables: undefined, - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - isExportable: true, - recoveryActionGroup: RecoveredActionGroup, - id: 'myType', - name: 'myType', - producer: 'myApp', - enabledInLicense: true, - hasAlertsMappings: false, - hasFieldsForAAD: false, - }, - ]); - beforeEach(() => { - authorization.getFindAuthorizationFilter.mockResolvedValue({ - ensureRuleTypeIsAuthorized() {}, - }); - unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ - total: 30, - per_page: 0, - page: 1, - saved_objects: [], - aggregations: { - status: { - buckets: [ - { key: 'active', doc_count: 8 }, - { key: 'error', doc_count: 6 }, - { key: 'ok', doc_count: 10 }, - { key: 'pending', doc_count: 4 }, - { key: 'unknown', doc_count: 2 }, - { key: 'warning', doc_count: 1 }, - ], - }, - outcome: { - buckets: [ - { key: 'succeeded', doc_count: 2 }, - { key: 'failed', doc_count: 4 }, - { key: 'warning', doc_count: 6 }, - ], - }, - enabled: { - buckets: [ - { key: 0, key_as_string: '0', doc_count: 2 }, - { key: 1, key_as_string: '1', doc_count: 28 }, - ], - }, - muted: { - buckets: [ - { key: 0, key_as_string: '0', doc_count: 27 }, - { key: 1, key_as_string: '1', doc_count: 3 }, - ], - }, - snoozed: { - doc_count: 0, - count: { - doc_count: 0, - }, - }, - tags: { - buckets: [ - { - key: 'a', - doc_count: 10, - }, - { - key: 'b', - doc_count: 20, - }, - { - key: 'c', - doc_count: 30, - }, - ], - }, - }, - }); - - ruleTypeRegistry.list.mockReturnValue(listedTypes); - authorization.filterByRuleTypeAuthorization.mockResolvedValue( - new Set([ - { - id: 'myType', - name: 'Test', - actionGroups: [{ id: 'default', name: 'Default' }], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - isExportable: true, - recoveryActionGroup: RecoveredActionGroup, - producer: 'alerts', - authorizedConsumers: { - myApp: { read: true, all: true }, - }, - enabledInLicense: true, - hasAlertsMappings: false, - hasFieldsForAAD: false, - }, - ]) - ); - }); - - test('calls saved objects client with given params to perform aggregation', async () => { - const rulesClient = new RulesClient(rulesClientParams); - const result = await rulesClient.aggregate({ - options: {}, - aggs: getDefaultRuleAggregation(), - }); - - expect(result).toMatchInlineSnapshot(` - Object { - "enabled": Object { - "buckets": Array [ - Object { - "doc_count": 2, - "key": 0, - "key_as_string": "0", - }, - Object { - "doc_count": 28, - "key": 1, - "key_as_string": "1", - }, - ], - }, - "muted": Object { - "buckets": Array [ - Object { - "doc_count": 27, - "key": 0, - "key_as_string": "0", - }, - Object { - "doc_count": 3, - "key": 1, - "key_as_string": "1", - }, - ], - }, - "outcome": Object { - "buckets": Array [ - Object { - "doc_count": 2, - "key": "succeeded", - }, - Object { - "doc_count": 4, - "key": "failed", - }, - Object { - "doc_count": 6, - "key": "warning", - }, - ], - }, - "snoozed": Object { - "count": Object { - "doc_count": 0, - }, - "doc_count": 0, - }, - "status": Object { - "buckets": Array [ - Object { - "doc_count": 8, - "key": "active", - }, - Object { - "doc_count": 6, - "key": "error", - }, - Object { - "doc_count": 10, - "key": "ok", - }, - Object { - "doc_count": 4, - "key": "pending", - }, - Object { - "doc_count": 2, - "key": "unknown", - }, - Object { - "doc_count": 1, - "key": "warning", - }, - ], - }, - "tags": Object { - "buckets": Array [ - Object { - "doc_count": 10, - "key": "a", - }, - Object { - "doc_count": 20, - "key": "b", - }, - Object { - "doc_count": 30, - "key": "c", - }, - ], - }, - } - `); - expect(unsecuredSavedObjectsClient.find).toHaveBeenCalledTimes(1); - - expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toEqual([ - { - filter: undefined, - page: 1, - perPage: 0, - type: 'alert', - aggs: { - status: { - terms: { field: 'alert.attributes.executionStatus.status' }, - }, - outcome: { - terms: { field: 'alert.attributes.lastRun.outcome' }, - }, - enabled: { - terms: { field: 'alert.attributes.enabled' }, - }, - muted: { - terms: { field: 'alert.attributes.muteAll' }, - }, - snoozed: { - aggs: { - count: { - filter: { - exists: { - field: 'alert.attributes.snoozeSchedule.duration', - }, - }, - }, - }, - nested: { - path: 'alert.attributes.snoozeSchedule', - }, - }, - tags: { - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, - }, - }, - }, - ]); - }); - - test('supports filters when aggregating', async () => { - const authFilter = fromKueryExpression( - 'alert.attributes.alertTypeId:myType and alert.attributes.consumer:myApp' - ); - authorization.getFindAuthorizationFilter.mockResolvedValue({ - filter: authFilter, - ensureRuleTypeIsAuthorized() {}, - }); - - const rulesClient = new RulesClient(rulesClientParams); - await rulesClient.aggregate({ - options: { filter: 'foo: someTerm' }, - aggs: getDefaultRuleAggregation(), - }); - - expect(unsecuredSavedObjectsClient.find).toHaveBeenCalledTimes(1); - expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toEqual([ - { - fields: undefined, - filter: nodeTypes.function.buildNode('and', [ - fromKueryExpression('foo: someTerm'), - authFilter, - ]), - page: 1, - perPage: 0, - type: 'alert', - aggs: { - status: { - terms: { field: 'alert.attributes.executionStatus.status' }, - }, - outcome: { - terms: { field: 'alert.attributes.lastRun.outcome' }, - }, - enabled: { - terms: { field: 'alert.attributes.enabled' }, - }, - muted: { - terms: { field: 'alert.attributes.muteAll' }, - }, - snoozed: { - aggs: { - count: { - filter: { - exists: { - field: 'alert.attributes.snoozeSchedule.duration', - }, - }, - }, - }, - nested: { - path: 'alert.attributes.snoozeSchedule', - }, - }, - tags: { - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, - }, - }, - }, - ]); - }); - - test('logs audit event when not authorized to aggregate rules', async () => { - const rulesClient = new RulesClient({ ...rulesClientParams, auditLogger }); - authorization.getFindAuthorizationFilter.mockRejectedValue(new Error('Unauthorized')); - - await expect(rulesClient.aggregate({ aggs: getDefaultRuleAggregation() })).rejects.toThrow(); - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: expect.objectContaining({ - action: 'rule_aggregate', - outcome: 'failure', - }), - error: { - code: 'Error', - message: 'Unauthorized', - }, - }) - ); - }); - - describe('tags number limit', () => { - test('sets to default (50) if it is not provided', async () => { - const rulesClient = new RulesClient(rulesClientParams); - - await rulesClient.aggregate({ aggs: getDefaultRuleAggregation() }); - - expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toMatchObject([ - { - aggs: { - tags: { - terms: { size: 50 }, - }, - }, - }, - ]); - }); - - test('sets to the provided value', async () => { - const rulesClient = new RulesClient(rulesClientParams); - - await rulesClient.aggregate({ - aggs: getDefaultRuleAggregation({ maxTags: 1000 }), - }); - - expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toMatchObject([ - { - aggs: { - tags: { - terms: { size: 1000 }, - }, - }, - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts index dbe04ec420000..febf66a1d2f60 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -6,7 +6,7 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { savedObjectsClientMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -53,6 +53,7 @@ const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); const logger = loggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v8.2.0'; const createAPIKeyMock = jest.fn(); @@ -67,11 +68,13 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: createAPIKeyMock, logger, + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts index 18cc4492be8c9..c4d7e6eb4f755 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts @@ -6,7 +6,7 @@ */ import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { savedObjectsClientMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { SavedObject } from '@kbn/core-saved-objects-server'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; @@ -61,6 +61,7 @@ const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); const logger = loggerMock.create(); const eventLogger = eventLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v8.2.0'; const createAPIKeyMock = jest.fn(); @@ -75,12 +76,14 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: createAPIKeyMock, logger, + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, auditLogger, eventLogger, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts index 03f9c06109059..acba28bf20ac6 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -6,7 +6,7 @@ */ import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { savedObjectsClientMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -45,6 +45,10 @@ jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation bulkMarkApiKeysForInvalidation: jest.fn(), })); +jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({ + validateScheduleLimit: jest.fn(), +})); + const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); @@ -53,6 +57,7 @@ const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); const logger = loggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v8.2.0'; const createAPIKeyMock = jest.fn(); @@ -67,11 +72,13 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: createAPIKeyMock, logger, + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts index 786400557977d..efebe91a90272 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts @@ -8,7 +8,11 @@ import moment from 'moment'; import sinon from 'sinon'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -40,6 +44,7 @@ const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); const eventLogger = eventLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -50,10 +55,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts index a8f13dc25869d..ece860c63c30e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts @@ -8,7 +8,11 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -43,12 +47,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { taskManager, ruleTypeRegistry, unsecuredSavedObjectsClient, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, authorization: authorization as unknown as AlertingAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, @@ -57,6 +63,7 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index b135f9b0ee23d..23b23b607cd04 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -7,7 +7,11 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -44,6 +48,7 @@ const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); const eventLogger = eventLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -54,10 +59,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index d123469527ad5..e7e09c55a7920 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -7,7 +7,11 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -31,6 +35,10 @@ jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation bulkMarkApiKeysForInvalidation: jest.fn(), })); +jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({ + validateScheduleLimit: jest.fn(), +})); + const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); @@ -38,6 +46,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -48,10 +57,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 4b1a6fc2eba8c..227988aaa6da0 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -36,6 +40,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -46,10 +51,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index 20af52838652a..a5259a2ddd1e0 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -7,7 +7,11 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -33,6 +37,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -43,10 +48,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts index fdece7b756d42..d8069721c4a5c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts @@ -7,7 +7,11 @@ import { RulesClient, ConstructorOptions } from '../rules_client'; import { GetActionErrorLogByIdParams } from '../methods/get_action_error_log'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { fromKueryExpression } from '@kbn/es-query'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; @@ -31,6 +35,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -41,10 +46,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts index c35bcd6c56311..644cc6190f605 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -24,6 +28,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index dbfcc5f3fc017..68160306d3424 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -7,7 +7,11 @@ import { omit, mean } from 'lodash'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -30,6 +34,7 @@ const eventLogClient = eventLogClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -40,10 +45,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index 33fb19c40f7fd..0704a0e7afe16 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -7,7 +7,11 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -32,6 +36,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -42,10 +47,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts index 1f0c4f405f2c2..ca1ef9c275779 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts @@ -6,7 +6,11 @@ */ import { v4 } from 'uuid'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -27,12 +31,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { taskManager, ruleTypeRegistry, unsecuredSavedObjectsClient, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, authorization: authorization as unknown as AlertingAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, @@ -41,6 +47,7 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts index 9c8c78f2753f4..8aa5615269404 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -28,6 +32,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -38,10 +43,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 0c9c34f1cbabe..7466d7405fa24 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts index 2d369bad2ce69..2dedc7f92f38d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 37372752d53a3..4733fd81786e9 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -7,7 +7,11 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -33,6 +37,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -43,10 +48,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts index 080ed8cd44287..20bbd2fb72f1a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -25,6 +29,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -35,10 +40,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index 37f3b06f137a7..ecdea250916be 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts index f4f8d58f50e32..4b030bb7eb2b5 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 26350664b8445..76b62cdad887e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -9,7 +9,11 @@ import { v4 as uuidv4 } from 'uuid'; import { schema } from '@kbn/config-schema'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -50,6 +54,10 @@ jest.mock('uuid', () => { return { v4: () => `${uuid++}` }; }); +jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({ + validateScheduleLimit: jest.fn(), +})); + const bulkMarkApiKeysForInvalidationMock = bulkMarkApiKeysForInvalidation as jest.Mock; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -58,6 +66,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -71,11 +80,13 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts index c915ccf1fe5c4..cc5d4e32511bc 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts @@ -6,7 +6,11 @@ */ import { RulesClient, ConstructorOptions } from '../rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; @@ -30,6 +34,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditLoggerMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { @@ -40,10 +45,12 @@ const rulesClientParams: jest.Mocked = { actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, spaceId: 'default', namespace: 'default', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, getUserName: jest.fn(), createAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index afcaaaaf34499..c755651f5524d 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -6,7 +6,12 @@ */ import { KueryNode } from '@kbn/es-query'; -import { Logger, SavedObjectsClientContract, PluginInitializerContext } from '@kbn/core/server'; +import { + Logger, + SavedObjectsClientContract, + PluginInitializerContext, + ISavedObjectsRepository, +} from '@kbn/core/server'; import { ActionsClient, ActionsAuthorization } from '@kbn/actions-plugin/server'; import { GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, @@ -55,11 +60,13 @@ export interface RulesClientContext { readonly authorization: AlertingAuthorization; readonly ruleTypeRegistry: RuleTypeRegistry; readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; + readonly maxScheduledPerMinute: AlertingRulesConfig['maxScheduledPerMinute']; readonly minimumScheduleIntervalInMs: number; readonly createAPIKey: (name: string) => Promise; readonly getActionsClient: () => Promise; readonly actionsAuthorization: ActionsAuthorization; readonly getEventLogClient: () => Promise; + readonly internalSavedObjectsRepository: ISavedObjectsRepository; readonly encryptedSavedObjectsClient: EncryptedSavedObjectsClient; readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; readonly auditLogger?: AuditLogger; diff --git a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts index fcde2f6e4f444..6b4ce4c16fe8b 100644 --- a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts @@ -8,7 +8,11 @@ import { cloneDeep } from 'lodash'; import { RulesClient, ConstructorOptions } from './rules_client'; -import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + savedObjectsClientMock, + loggingSystemMock, + savedObjectsRepositoryMock, +} from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from './rule_type_registry.mock'; import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock'; @@ -21,6 +25,10 @@ import { RetryForConflictsAttempts } from './lib/retry_if_conflicts'; import { TaskStatus } from '@kbn/task-manager-plugin/server/task'; import { RecoveredActionGroup } from '../common'; +jest.mock('./application/rule/methods/get_schedule_frequency', () => ({ + validateScheduleLimit: jest.fn(), +})); + let rulesClient: RulesClient; const MockAlertId = 'alert-id'; @@ -34,6 +42,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const kibanaVersion = 'v7.10.0'; const logger = loggingSystemMock.create().get(); @@ -48,10 +57,12 @@ const rulesClientParams: jest.Mocked = { getUserName: jest.fn(), createAPIKey: jest.fn(), logger, + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index d1554a46991fd..6bb4f945d5430 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -12,6 +12,7 @@ import { savedObjectsClientMock, savedObjectsServiceMock, loggingSystemMock, + savedObjectsRepositoryMock, } from '@kbn/core/server/mocks'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { AuthenticatedUser } from '@kbn/security-plugin/common/model'; @@ -37,6 +38,7 @@ const securityPluginStart = securityMock.createStart(); const alertingAuthorization = alertingAuthorizationMock.create(); const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const rulesClientFactoryParams: jest.Mocked = { logger: loggingSystemMock.create().get(), @@ -44,7 +46,9 @@ const rulesClientFactoryParams: jest.Mocked = { ruleTypeRegistry: ruleTypeRegistryMock.create(), getSpaceId: jest.fn(), spaceIdToNamespace: jest.fn(), + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, + internalSavedObjectsRepository, encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(), actions: actionsMock.createStart(), eventLog: eventLogMock.createStart(), @@ -101,8 +105,10 @@ test('creates a rules client with proper constructor arguments when security is getActionsClient: expect.any(Function), getEventLogClient: expect.any(Function), createAPIKey: expect.any(Function), + internalSavedObjectsRepository: rulesClientFactoryParams.internalSavedObjectsRepository, encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient, kibanaVersion: '7.10.0', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: expect.any(Function), getAuthenticationAPIKey: expect.any(Function), @@ -139,10 +145,12 @@ test('creates a rules client with proper constructor arguments', async () => { namespace: 'default', getUserName: expect.any(Function), createAPIKey: expect.any(Function), + internalSavedObjectsRepository: rulesClientFactoryParams.internalSavedObjectsRepository, encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient, getActionsClient: expect.any(Function), getEventLogClient: expect.any(Function), kibanaVersion: '7.10.0', + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: expect.any(Function), getAuthenticationAPIKey: expect.any(Function), diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index d99dc12b13e28..e0b9de5dc53e2 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -10,6 +10,7 @@ import { Logger, SavedObjectsServiceStart, PluginInitializerContext, + ISavedObjectsRepository, } from '@kbn/core/server'; import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; import { @@ -34,12 +35,14 @@ export interface RulesClientFactoryOpts { getSpaceId: (request: KibanaRequest) => string; spaceIdToNamespace: SpaceIdToNamespaceFunction; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; + internalSavedObjectsRepository: ISavedObjectsRepository; actions: ActionsPluginStartContract; eventLog: IEventLogClientService; kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; authorization: AlertingAuthorizationClientFactory; eventLogger?: IEventLogger; minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; + maxScheduledPerMinute: AlertingRulesConfig['maxScheduledPerMinute']; } export class RulesClientFactory { @@ -52,12 +55,14 @@ export class RulesClientFactory { private getSpaceId!: (request: KibanaRequest) => string; private spaceIdToNamespace!: SpaceIdToNamespaceFunction; private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient; + private internalSavedObjectsRepository!: ISavedObjectsRepository; private actions!: ActionsPluginStartContract; private eventLog!: IEventLogClientService; private kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version']; private authorization!: AlertingAuthorizationClientFactory; private eventLogger?: IEventLogger; private minimumScheduleInterval!: AlertingRulesConfig['minimumScheduleInterval']; + private maxScheduledPerMinute!: AlertingRulesConfig['maxScheduledPerMinute']; public initialize(options: RulesClientFactoryOpts) { if (this.isInitialized) { @@ -72,12 +77,14 @@ export class RulesClientFactory { this.securityPluginStart = options.securityPluginStart; this.spaceIdToNamespace = options.spaceIdToNamespace; this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient; + this.internalSavedObjectsRepository = options.internalSavedObjectsRepository; this.actions = options.actions; this.eventLog = options.eventLog; this.kibanaVersion = options.kibanaVersion; this.authorization = options.authorization; this.eventLogger = options.eventLogger; this.minimumScheduleInterval = options.minimumScheduleInterval; + this.maxScheduledPerMinute = options.maxScheduledPerMinute; } public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient { @@ -95,6 +102,7 @@ export class RulesClientFactory { taskManager: this.taskManager, ruleTypeRegistry: this.ruleTypeRegistry, minimumScheduleInterval: this.minimumScheduleInterval, + maxScheduledPerMinute: this.maxScheduledPerMinute, unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, { excludedExtensions: [SECURITY_EXTENSION_ID], includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], @@ -102,6 +110,7 @@ export class RulesClientFactory { authorization: this.authorization.create(request), actionsAuthorization: actions.getActionsAuthorizationWithRequest(request), namespace: this.spaceIdToNamespace(spaceId), + internalSavedObjectsRepository: this.internalSavedObjectsRepository, encryptedSavedObjectsClient: this.encryptedSavedObjectsClient, auditLogger: securityPluginSetup?.audit.asScoped(request), async getUserName() { diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index ce86dd4756093..22d056f489104 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -28,12 +28,13 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; import { Alert } from '../alert'; -import { AlertInstanceState, AlertInstanceContext } from '../../common'; +import { AlertInstanceState, AlertInstanceContext, RuleNotifyWhen } from '../../common'; import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server'; import sinon from 'sinon'; import { mockAAD } from './fixtures'; import { schema } from '@kbn/config-schema'; import { alertsClientMock } from '../alerts_client/alerts_client.mock'; +import { ExecutionResponseType } from '@kbn/actions-plugin/server/create_execute_function'; jest.mock('./inject_action_params', () => ({ injectActionParams: jest.fn(), @@ -137,6 +138,11 @@ const defaultExecutionParams = { alertsClient, }; +const defaultExecutionResponse = { + errors: false, + items: [{ actionTypeId: 'test', id: '1', response: ExecutionResponseType.SUCCESS }], +}; + let ruleRunMetricsStore: RuleRunMetricsStore; let clock: sinon.SinonFakeTimers; type ActiveActionGroup = 'default' | 'other-group'; @@ -149,6 +155,7 @@ const generateAlert = ({ throttledActions = {}, lastScheduledActionsGroup = 'default', maintenanceWindowIds, + pendingRecoveredCount, }: { id: number; group?: ActiveActionGroup | 'recovered'; @@ -158,6 +165,7 @@ const generateAlert = ({ throttledActions?: ThrottledActions; lastScheduledActionsGroup?: string; maintenanceWindowIds?: string[]; + pendingRecoveredCount?: number; }) => { const alert = new Alert( String(id), @@ -170,6 +178,7 @@ const generateAlert = ({ group: lastScheduledActionsGroup, actions: throttledActions, }, + pendingRecoveredCount, }, } ); @@ -223,6 +232,7 @@ describe('Execution Handler', () => { renderActionParameterTemplatesDefault ); ruleRunMetricsStore = new RuleRunMetricsStore(); + actionsClient.bulkEnqueueExecution.mockResolvedValue(defaultExecutionResponse); }); beforeAll(() => { clock = sinon.useFakeTimers(); @@ -238,39 +248,40 @@ describe('Execution Handler', () => { expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, ], - ] - `); + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); expect(alertingEventLogger.logAction).toHaveBeenCalledTimes(1); expect(alertingEventLogger.logAction).toHaveBeenNthCalledWith(1, { @@ -334,6 +345,7 @@ describe('Execution Handler', () => { expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith([ { + actionTypeId: 'test2', consumer: 'rule-consumer', id: '2', params: { @@ -423,39 +435,40 @@ describe('Execution Handler', () => { expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", + "contextVal": "My context-val goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", - "contextVal": "My context-val goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, ], - ] - `); + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); }); test('state attribute gets parameterized', async () => { @@ -463,39 +476,40 @@ describe('Execution Handler', () => { await executionHandler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My state-val goes here", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My state-val goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, ], - ] - `); + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); }); test(`logs an error when action group isn't part of actionGroups available for the ruleType`, async () => { @@ -514,6 +528,21 @@ describe('Execution Handler', () => { }); test('Stops triggering actions when the number of total triggered actions is reached the number of max executable actions', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + actionTypeId: 'test2', + id: '1', + response: ExecutionResponseType.SUCCESS, + }, + { + actionTypeId: 'test2', + id: '2', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); const actions = [ { id: '1', @@ -573,6 +602,27 @@ describe('Execution Handler', () => { }); test('Skips triggering actions for a specific action type when it reaches the limit for that specific action type', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { actionTypeId: 'test', id: '1', response: ExecutionResponseType.SUCCESS }, + { + actionTypeId: 'test-action-type-id', + id: '2', + response: ExecutionResponseType.SUCCESS, + }, + { + actionTypeId: 'another-action-type-id', + id: '4', + response: ExecutionResponseType.SUCCESS, + }, + { + actionTypeId: 'another-action-type-id', + id: '5', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); const actions = [ ...defaultExecutionParams.rule.actions, { @@ -652,6 +702,77 @@ describe('Execution Handler', () => { expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); }); + test('Stops triggering actions when the number of total queued actions is reached the number of max queued actions', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: true, + items: [ + { + actionTypeId: 'test', + id: '1', + response: ExecutionResponseType.SUCCESS, + }, + { + actionTypeId: 'test', + id: '2', + response: ExecutionResponseType.SUCCESS, + }, + { + actionTypeId: 'test', + id: '3', + response: ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR, + }, + ], + }); + const actions = [ + { + id: '1', + group: 'default', + actionTypeId: 'test', + params: { + foo: true, + contextVal: 'My other {{context.value}} goes here', + stateVal: 'My other {{state.value}} goes here', + }, + }, + { + id: '2', + group: 'default', + actionTypeId: 'test', + params: { + foo: true, + contextVal: 'My other {{context.value}} goes here', + stateVal: 'My other {{state.value}} goes here', + }, + }, + { + id: '3', + group: 'default', + actionTypeId: 'test', + params: { + foo: true, + contextVal: '{{context.value}} goes here', + stateVal: '{{state.value}} goes here', + }, + }, + ]; + const executionHandler = new ExecutionHandler( + generateExecutionParams({ + ...defaultExecutionParams, + rule: { + ...defaultExecutionParams.rule, + actions, + }, + }) + ); + await executionHandler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); + + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(2); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(3); + expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL); + expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(1); + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); + }); + test('schedules alerts with recovered actions', async () => { const actions = [ { @@ -680,39 +801,40 @@ describe('Execution Handler', () => { expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, ], - ] - `); + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); }); test('does not schedule alerts with recovered actions that are muted', async () => { @@ -852,6 +974,16 @@ describe('Execution Handler', () => { }); test('triggers summary actions (per rule run)', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + actionTypeId: 'testActionTypeId', + id: '1', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); alertsClient.getSummarizedAlerts.mockResolvedValue({ new: { count: 1, @@ -895,36 +1027,37 @@ describe('Execution Handler', () => { }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ + Array [ + Array [ + Object { + "actionTypeId": "testActionTypeId", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "message": "New: 1 Ongoing: 0 Recovered: 0", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", "id": "1", - "params": Object { - "message": "New: 1 Ongoing: 0 Recovered: 0", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, ], - ] - `); + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); expect(alertingEventLogger.logAction).toBeCalledWith({ alertSummary: { new: 1, ongoing: 0, recovered: 0 }, id: '1', @@ -970,6 +1103,16 @@ describe('Execution Handler', () => { }); test('triggers summary actions (custom interval)', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + actionTypeId: 'testActionTypeId', + id: '1', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); alertsClient.getSummarizedAlerts.mockResolvedValue({ new: { count: 1, @@ -1022,36 +1165,37 @@ describe('Execution Handler', () => { }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ + Array [ + Array [ + Object { + "actionTypeId": "testActionTypeId", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "message": "New: 1 Ongoing: 0 Recovered: 0", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", "id": "1", - "params": Object { - "message": "New: 1 Ongoing: 0 Recovered: 0", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, ], - ] - `); + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); expect(alertingEventLogger.logAction).toBeCalledWith({ alertSummary: { new: 1, ongoing: 0, recovered: 0 }, id: '1', @@ -1206,6 +1350,17 @@ describe('Execution Handler', () => { }); test('schedules alerts with multiple recovered actions', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { actionTypeId: 'test', id: '1', response: ExecutionResponseType.SUCCESS }, + { + actionTypeId: 'test', + id: '2', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); const actions = [ { id: '1', @@ -1245,70 +1400,82 @@ describe('Execution Handler', () => { expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "2", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "2", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", }, ], - ] - `); + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); }); test('does not schedule actions for the summarized alerts that are filtered out (for each alert)', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + actionTypeId: 'testActionTypeId', + id: '1', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); alertsClient.getSummarizedAlerts.mockResolvedValue({ new: { count: 0, @@ -1372,6 +1539,16 @@ describe('Execution Handler', () => { }); test('does not schedule actions for the summarized alerts that are filtered out (summary of alerts onThrottleInterval)', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + actionTypeId: 'testActionTypeId', + id: '1', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); alertsClient.getSummarizedAlerts.mockResolvedValue({ new: { count: 0, @@ -1432,6 +1609,16 @@ describe('Execution Handler', () => { }); test('does not schedule actions for the for-each type alerts that are filtered out', async () => { + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + actionTypeId: 'testActionTypeId', + id: '1', + response: ExecutionResponseType.SUCCESS, + }, + ], + }); alertsClient.getSummarizedAlerts.mockResolvedValue({ new: { count: 1, @@ -1486,6 +1673,7 @@ describe('Execution Handler', () => { }); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith([ { + actionTypeId: 'testActionTypeId', apiKey: 'MTIzOmFiYw==', consumer: 'rule-consumer', executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28', @@ -1593,6 +1781,157 @@ describe('Execution Handler', () => { ); }); + test('does not schedule actions with notifyWhen not set to "on status change" for alerts that are flapping', async () => { + const executionHandler = new ExecutionHandler( + generateExecutionParams({ + ...defaultExecutionParams, + rule: { + ...defaultExecutionParams.rule, + actions: [ + { + ...defaultExecutionParams.rule.actions[0], + frequency: { + summary: false, + notifyWhen: RuleNotifyWhen.ACTIVE, + throttle: null, + }, + }, + ], + }, + }) + ); + + await executionHandler.run({ + ...generateAlert({ id: 1, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + ...generateAlert({ id: 2, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + ...generateAlert({ id: 3, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + }); + + expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); + }); + + test('does schedule actions with notifyWhen is set to "on status change" for alerts that are flapping', async () => { + const executionHandler = new ExecutionHandler( + generateExecutionParams({ + ...defaultExecutionParams, + rule: { + ...defaultExecutionParams.rule, + actions: [ + { + ...defaultExecutionParams.rule.actions[0], + frequency: { + summary: false, + notifyWhen: RuleNotifyWhen.CHANGE, + throttle: null, + }, + }, + ], + }, + }) + ); + + await executionHandler.run({ + ...generateAlert({ id: 1, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + ...generateAlert({ id: 2, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + ...generateAlert({ id: 3, pendingRecoveredCount: 1, lastScheduledActionsGroup: 'recovered' }), + }); + + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); + expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 3 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); + }); + describe('rule url', () => { const ruleWithUrl = { ...rule, diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 6214482ec2706..33c4c93abe111 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -10,7 +10,11 @@ import { Logger } from '@kbn/core/server'; import { getRuleDetailsRoute, triggersActionsRoute } from '@kbn/rule-data-utils'; import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server'; import { isEphemeralTaskRejectedDueToCapacityError } from '@kbn/task-manager-plugin/server'; -import { ExecuteOptions as EnqueueExecutionOptions } from '@kbn/actions-plugin/server/create_execute_function'; +import { + ExecuteOptions as EnqueueExecutionOptions, + ExecutionResponseItem, + ExecutionResponseType, +} from '@kbn/actions-plugin/server/create_execute_function'; import { ActionsCompletion } from '@kbn/alerting-state-types'; import { ActionsClient } from '@kbn/actions-plugin/server/actions_client'; import { chunk } from 'lodash'; @@ -32,6 +36,7 @@ import { RuleTypeState, SanitizedRule, RuleAlertData, + RuleNotifyWhen, } from '../../common'; import { generateActionHash, @@ -49,6 +54,18 @@ enum Reasons { ACTION_GROUP_NOT_CHANGED = 'actionGroupHasNotChanged', } +interface LogAction { + id: string; + typeId: string; + alertId?: string; + alertGroup?: string; + alertSummary?: { + new: number; + ongoing: number; + recovered: number; + }; +} + export interface RunResult { throttledSummaryActions: ThrottledActions; } @@ -176,8 +193,9 @@ export class ExecutionHandler< }, } = this; - const logActions = []; + const logActions: Record = {}; const bulkActions: EnqueueExecutionOptions[] = []; + let bulkActionsResponse: ExecutionResponseItem[] = []; this.ruleRunMetricsStore.incrementNumberOfGeneratedActions(executables.length); @@ -262,7 +280,7 @@ export class ExecutionHandler< throttledSummaryActions[action.uuid!] = { date: new Date().toISOString() }; } - logActions.push({ + logActions[action.id] = { id: action.id, typeId: action.actionTypeId, alertSummary: { @@ -270,7 +288,7 @@ export class ExecutionHandler< ongoing: summarizedAlerts.ongoing.count, recovered: summarizedAlerts.recovered.count, }, - }); + }; } else { const ruleUrl = this.buildRuleUrl(spaceId); const actionToRun = { @@ -307,12 +325,12 @@ export class ExecutionHandler< bulkActions, }); - logActions.push({ + logActions[action.id] = { id: action.id, typeId: action.actionTypeId, alertId: alert.getId(), alertGroup: action.group, - }); + }; if (!this.isRecoveredAlert(actionGroup)) { if (isActionOnInterval(action)) { @@ -331,12 +349,40 @@ export class ExecutionHandler< if (!!bulkActions.length) { for (const c of chunk(bulkActions, CHUNK_SIZE)) { - await this.actionsClient!.bulkEnqueueExecution(c); + const response = await this.actionsClient!.bulkEnqueueExecution(c); + if (response.errors) { + bulkActionsResponse = bulkActionsResponse.concat( + response.items.filter( + (i) => i.response === ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR + ) + ); + } } } - if (!!logActions.length) { - for (const action of logActions) { + if (!!bulkActionsResponse.length) { + for (const r of bulkActionsResponse) { + if (r.response === ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR) { + ruleRunMetricsStore.setHasReachedQueuedActionsLimit(true); + ruleRunMetricsStore.decrementNumberOfTriggeredActions(); + ruleRunMetricsStore.decrementNumberOfTriggeredActionsByConnectorType(r.actionTypeId); + ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({ + actionTypeId: r.actionTypeId, + status: ActionsCompletion.PARTIAL, + }); + + logger.debug( + `Rule "${this.rule.id}" skipped scheduling action "${r.id}" because the maximum number of queued actions has been reached.` + ); + + delete logActions[r.id]; + } + } + } + + const logActionsValues = Object.values(logActions); + if (!!logActionsValues.length) { + for (const action of logActionsValues) { alertingEventLogger.logAction(action); } } @@ -509,6 +555,7 @@ export class ExecutionHandler< typeId: this.ruleType.id, }, ], + actionTypeId: action.actionTypeId, }; } @@ -575,6 +622,16 @@ export class ExecutionHandler< ); continue; } + + // only actions with notifyWhen set to "on status change" should return + // notifications for flapping pending recovered alerts + if ( + alert.getPendingRecoveredCount() > 0 && + action.frequency?.notifyWhen !== RuleNotifyWhen.CHANGE + ) { + continue; + } + if (action.group === actionGroup && !this.isAlertMuted(alertId)) { if ( this.isRecoveredAlert(action.group) || diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 467d7460afc2b..64c798b868db1 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -395,13 +395,16 @@ export const generateEnqueueFunctionInput = ({ isBulk = false, isResolved, foo, + actionTypeId, }: { id: string; isBulk?: boolean; isResolved?: boolean; foo?: boolean; + actionTypeId?: string; }) => { const input = { + actionTypeId: actionTypeId || 'action', apiKey: 'MTIzOmFiYw==', executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28', id, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index fcd2464058350..c0d8a1434aa3d 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -237,6 +237,8 @@ describe('Task Runner', () => { logger.get.mockImplementation(() => logger); ruleType.executor.mockResolvedValue({ state: {} }); + + actionsClient.bulkEnqueueExecution.mockResolvedValue({ errors: false, items: [] }); }); test('successfully executes the task', async () => { @@ -299,7 +301,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ status: 'ok' }); @@ -381,7 +383,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 5, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -469,7 +471,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -723,7 +725,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":2,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":2,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":2,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":2,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); } @@ -1168,7 +1170,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -1295,7 +1297,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - `ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}` + `ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}` ); testAlertingEventLogCalls({ @@ -1490,7 +1492,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledTimes(1); expect(enqueueFunction).toHaveBeenCalledWith( - generateEnqueueFunctionInput({ isBulk, id: '1', foo: true }) + generateEnqueueFunctionInput({ isBulk, id: '1', foo: true, actionTypeId: 'slack' }) ); } ); @@ -1562,7 +1564,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledTimes(1); expect(enqueueFunction).toHaveBeenCalledWith( - generateEnqueueFunctionInput({ isBulk, id: '1', foo: true }) + generateEnqueueFunctionInput({ isBulk, id: '1', foo: true, actionTypeId: 'slack' }) ); expect(result.state.summaryActions).toEqual({ '111-111': { date: new Date(DATE_1970).toISOString() }, @@ -2440,7 +2442,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -2962,7 +2964,7 @@ describe('Task Runner', () => { status: 'warning', errorReason: `maxExecutableActions`, logAlert: 4, - logAction: 5, + logAction: 3, }); }); @@ -3146,6 +3148,7 @@ describe('Task Runner', () => { logAlert = 0, logAction = 0, hasReachedAlertLimit = false, + hasReachedQueuedActionsLimit = false, }: { status: string; ruleContext?: RuleContextOpts; @@ -3162,6 +3165,7 @@ describe('Task Runner', () => { errorReason?: string; errorMessage?: string; hasReachedAlertLimit?: boolean; + hasReachedQueuedActionsLimit?: boolean; }) { expect(alertingEventLogger.initialize).toHaveBeenCalledWith(ruleContext); if (status !== 'skip') { @@ -3215,6 +3219,7 @@ describe('Task Runner', () => { totalSearchDurationMs: 23423, hasReachedAlertLimit, triggeredActionsStatus: 'partial', + hasReachedQueuedActionsLimit, }, status: { lastExecutionDate: new Date('1970-01-01T00:00:00.000Z'), @@ -3250,6 +3255,7 @@ describe('Task Runner', () => { totalSearchDurationMs: 23423, hasReachedAlertLimit, triggeredActionsStatus: 'complete', + hasReachedQueuedActionsLimit, }, status: { lastExecutionDate: new Date('1970-01-01T00:00:00.000Z'), diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 6c871f63065a9..6d3be52cf2e62 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -6,7 +6,7 @@ */ import apm from 'elastic-apm-node'; -import { omit } from 'lodash'; +import { omit, some } from 'lodash'; import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from '@kbn/core/server'; @@ -50,6 +50,7 @@ import { MaintenanceWindow, RuleAlertData, SanitizedRule, + RuleNotifyWhen, } from '../../common'; import { NormalizedRuleType, UntypedNormalizedRuleType } from '../rule_type_registry'; import { getEsErrorMessage } from '../lib/errors'; @@ -546,7 +547,9 @@ export class TaskRunner< ruleRunMetricsStore, shouldLogAlerts: this.shouldLogAndScheduleActionsForAlerts(), flappingSettings, - notifyWhen, + notifyOnActionGroupChange: + notifyWhen === RuleNotifyWhen.CHANGE || + some(actions, (action) => action.frequency?.notifyWhen === RuleNotifyWhen.CHANGE), maintenanceWindowIds, }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts index 3ed2a63feacdc..cc1d162a1ecc7 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts @@ -14,7 +14,6 @@ import { AlertInstanceState, AlertInstanceContext, Rule, - RuleNotifyWhen, RuleAlertData, } from '../types'; import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; @@ -409,7 +408,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 5, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); expect( taskRunnerFactoryInitializerParams.internalSavedObjectsRepository.update @@ -772,7 +771,7 @@ describe('Task Runner', () => { lookBackWindow: 20, statusChangeThreshold: 4, }, - notifyWhen: RuleNotifyWhen.ACTIVE, + notifyOnActionGroupChange: false, maintenanceWindowIds: [], }); expect(alertsClientNotToUse.processAndLogAlerts).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 870cad51ed4b7..46783154a6a4a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -191,6 +191,8 @@ describe('Task Runner Cancel', () => { alertingEventLogger.getStartAndDuration.mockImplementation(() => ({ start: new Date() })); (AlertingEventLogger as jest.Mock).mockImplementation(() => alertingEventLogger); logger.get.mockImplementation(() => logger); + + actionsClient.bulkEnqueueExecution.mockResolvedValue({ errors: false, items: [] }); }); test('updates rule saved object execution status and writes to event log entry when task is cancelled mid-execution', async () => { @@ -470,7 +472,7 @@ describe('Task Runner Cancel', () => { ); expect(logger.debug).nthCalledWith( 8, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); } @@ -485,6 +487,7 @@ describe('Task Runner Cancel', () => { logAlert = 0, logAction = 0, hasReachedAlertLimit = false, + hasReachedQueuedActionsLimit = false, }: { status: string; ruleContext?: RuleContextOpts; @@ -497,6 +500,7 @@ describe('Task Runner Cancel', () => { logAlert?: number; logAction?: number; hasReachedAlertLimit?: boolean; + hasReachedQueuedActionsLimit?: boolean; }) { expect(alertingEventLogger.initialize).toHaveBeenCalledWith(ruleContext); expect(alertingEventLogger.start).toHaveBeenCalled(); @@ -515,6 +519,7 @@ describe('Task Runner Cancel', () => { totalSearchDurationMs: 23423, hasReachedAlertLimit, triggeredActionsStatus: 'complete', + hasReachedQueuedActionsLimit, }, status: { lastExecutionDate: new Date('1970-01-01T00:00:00.000Z'), diff --git a/x-pack/plugins/alerting/server/test_utils/index.ts b/x-pack/plugins/alerting/server/test_utils/index.ts index ec82b884fb427..9985f43348eb0 100644 --- a/x-pack/plugins/alerting/server/test_utils/index.ts +++ b/x-pack/plugins/alerting/server/test_utils/index.ts @@ -60,6 +60,7 @@ export function generateAlertingConfig(): AlertingConfig { maxEphemeralActionsPerAlert: 10, cancelAlertsOnRuleTimeout: true, rules: { + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, run: { actions: { diff --git a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap index 2bed81c9c05c3..d84aa51a0e365 100644 --- a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap @@ -1704,7 +1704,7 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the "total": { "type": "long", "_meta": { - "description": "Total number of shards for span and trasnaction indices" + "description": "Total number of shards for span and transaction indices" } } } @@ -1718,7 +1718,7 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the "count": { "type": "long", "_meta": { - "description": "Total number of transaction and span documents overall" + "description": "Total number of metric documents overall" } } } @@ -1728,7 +1728,7 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the "size_in_bytes": { "type": "long", "_meta": { - "description": "Size of the index in byte units overall." + "description": "Size of the metric indicess in byte units overall." } } } @@ -1957,6 +1957,22 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the } } }, + "top_traces": { + "properties": { + "max": { + "type": "long", + "_meta": { + "description": "Max number of documents in top 100 traces withing the last day" + } + }, + "median": { + "type": "long", + "_meta": { + "description": "Median number of documents in top 100 traces within the last day" + } + } + } + }, "tasks": { "properties": { "aggregated_transactions": { @@ -2168,6 +2184,20 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the } } } + }, + "top_traces": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long", + "_meta": { + "description": "Execution time in milliseconds for the \\"top_traces\\" task" + } + } + } + } + } } } } diff --git a/x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts b/x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts index 284147303ced0..8b78798d2bc55 100644 --- a/x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts +++ b/x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts @@ -23,7 +23,7 @@ describe('getCriticalPath', () => { errorDocs: [], exceedsMax: false, spanLinksCountById: {}, - traceItemCount: events.length, + traceDocsTotal: events.length, maxTraceItems: 5000, }, entryTransaction, diff --git a/x-pack/plugins/apm/common/utils/to_kuery_filter_format.test.ts b/x-pack/plugins/apm/common/utils/to_kuery_filter_format.test.ts new file mode 100644 index 0000000000000..0a1e01d8404f7 --- /dev/null +++ b/x-pack/plugins/apm/common/utils/to_kuery_filter_format.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { toKueryFilterFormat } from './to_kuery_filter_format'; + +describe('toKueryFilterFormat', () => { + it('returns a single value', () => { + expect(toKueryFilterFormat('key', ['foo'])).toEqual(`key : "foo"`); + }); + + it('returns multiple values default separator', () => { + expect(toKueryFilterFormat('key', ['foo', 'bar', 'baz'])).toEqual( + `key : "foo" OR key : "bar" OR key : "baz"` + ); + }); + + it('returns multiple values custom separator', () => { + expect(toKueryFilterFormat('key', ['foo', 'bar', 'baz'], 'AND')).toEqual( + `key : "foo" AND key : "bar" AND key : "baz"` + ); + }); + + it('return empty string when no hostname', () => { + expect(toKueryFilterFormat('key', [])).toEqual(''); + }); +}); diff --git a/x-pack/plugins/apm/common/utils/to_kuery_filter_format.ts b/x-pack/plugins/apm/common/utils/to_kuery_filter_format.ts new file mode 100644 index 0000000000000..8e3169f4d07e2 --- /dev/null +++ b/x-pack/plugins/apm/common/utils/to_kuery_filter_format.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function toKueryFilterFormat( + key: string, + values: string[], + separator: 'OR' | 'AND' = 'OR' +) { + return values.map((value) => `${key} : "${value}"`).join(` ${separator} `); +} diff --git a/x-pack/plugins/apm/dev_docs/telemetry.md b/x-pack/plugins/apm/dev_docs/telemetry.md index 7bfb065965261..e11ea3bcdec33 100644 --- a/x-pack/plugins/apm/dev_docs/telemetry.md +++ b/x-pack/plugins/apm/dev_docs/telemetry.md @@ -69,3 +69,27 @@ mappings snapshot used in the jest tests. Behavioral telemetry is recorded with the ui_metrics and application_usage methods from the Usage Collection plugin. Please fill this in with more details. + +## Event based telemetry + +Event-based telemetry (EBT) allows sending raw or minimally prepared data to the telemetry endpoints. + +EBT is part of the core analytics service in Kibana and the `TelemetryService` provides an easy way to track custom events that are specific to `APM`. + +#### Collect a new event type + +1. You need to define the event type in the [telemetry_events.ts](https://github.com/elastic/kibana/blob/4283802c195231f710be0d9870615fbc31382a31/x-pack/plugins/apm/public/services/telemetry/telemetry_events.ts#L36) +2. Define the tracking method in the [telemetry_client.ts](https://github.com/elastic/kibana/blob/4283802c195231f710be0d9870615fbc31382a31/x-pack/plugins/apm/public/services/telemetry/telemetry_client.ts#L18) +3. Use the tracking method with the telemetry client (`telemetry.reportSearchQuerySumbitted({property: test})`) + +In addition to the custom properties, analytics module automatically sends context properties. The list of the properties can be found [here](https://docs.elastic.dev/telemetry/collection/event-based-telemetry-context#browser-context) + +#### How to check the events + +In development, the events are sent to staging telemetry every hour and these events are stored in the `ebt-kibana-browser` dataview. + +For instance, you can use a query like the following as an example to filter the apm event Search Query Submitted. + +``` +context.applicationId : "apm" and event_type : "Search Query Submitted" +``` diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md index 533ad5f474fd8..362ae0027d6d4 100644 --- a/x-pack/plugins/apm/dev_docs/testing.md +++ b/x-pack/plugins/apm/dev_docs/testing.md @@ -31,7 +31,7 @@ open target/coverage/jest/index.html The API tests are located in [`x-pack/test/apm_api_integration/`](/x-pack/test/apm_api_integration/). -### Start server and run test in a single process +#### Start server and run test (single process) ``` node x-pack/plugins/apm/scripts/test/api [--trial/--basic] [--help] @@ -40,7 +40,7 @@ node x-pack/plugins/apm/scripts/test/api [--trial/--basic] [--help] The above command will start an ES instance on http://localhost:9220, a Kibana instance on http://localhost:5620 and run the api tests. Once the tests finish, the instances will be terminated. -### Start server and run test in separate processes +#### Start server and run test (separate processes) ```sh @@ -61,7 +61,7 @@ node x-pack/plugins/apm/scripts/test/api --runner --basic --updateSnapshots (The test server needs to be running) -**API Test tips** +#### API Test tips - For data generation in API tests have a look at the [kbn-apm-synthtrace](../../../../packages/kbn-apm-synthtrace/README.md) package - For debugging access Elasticsearch on http://localhost:9220 and Kibana on http://localhost:5620 (`elastic` / `changeme`) @@ -85,19 +85,19 @@ Tests run on buildkite PR pipeline are parallelized (4 parallel jobs) and are or [Test tips and best practices](../ftr_e2e/README.md) -### Start test server +#### Start test server ``` node x-pack/plugins/apm/scripts/test/e2e --server ``` -### Run tests +#### Run tests ``` node x-pack/plugins/apm/scripts/test/e2e --runner --open ``` -### Rum tests multiple times to check for flakiness +### Run tests multiple times to check for flakiness ``` node x-pack/plugins/apm/scripts/test/e2e --runner --times [--spec ] @@ -111,17 +111,11 @@ Accessibility tests are added on the e2e with `checkA11y()`, they will run toget ## Functional tests (Security and Correlations tests) -TODO: We could try moving this tests to the new e2e tests located at `x-pack/plugins/apm/ftr_e2e`. - -**Start server** - -``` +```sh +# Start server node scripts/functional_tests_server --config x-pack/test/functional/apps/apm/config.ts -``` - -**Run tests** -``` +# Run tests node scripts/functional_test_runner --config x-pack/test/functional/apps/apm/config.ts --grep='APM specs' ``` @@ -129,6 +123,22 @@ APM tests are located in `x-pack/test/functional/apps/apm`. For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) diff --git a/x-pack/plugins/apm/scripts/test/README.md b/x-pack/plugins/apm/scripts/test/README.md +## Serverless API tests + +#### Start server and run tests (single process) +``` +node scripts/functional_tests.js --config x-pack/test_serverless/api_integration/test_suites/observability/config.ts +``` + +#### Start server and run tests (separate processes) +```sh +# Start server +node scripts/functional_tests_server.js --config x-pack/test_serverless/api_integration/test_suites/observability/config.ts + +# Run tests +node scripts/functional_test_runner --config=x-pack/test_serverless/api_integration/test_suites/observability/config.ts +``` + ## Storybook ### Start diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/service_map.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/service_map.cy.ts index 2471617e7b1f4..2caeba304f3b4 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/service_map.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/service_map.cy.ts @@ -9,7 +9,7 @@ import { synthtrace } from '../../../synthtrace'; import { opbeans } from '../../fixtures/synthtrace/opbeans'; const start = '2021-10-10T00:00:00.000Z'; -const end = '2021-10-10T00:15:00.000Z'; +const end = '2021-10-10T00:01:00.000Z'; const serviceMapHref = url.format({ pathname: '/app/apm/service-map', @@ -29,7 +29,7 @@ const detailedServiceMap = url.format({ }, }); -describe('Service map', () => { +describe('service map', () => { before(() => { synthtrace.index( opbeans({ @@ -47,22 +47,51 @@ describe('Service map', () => { cy.loginAsViewerUser(); }); - describe('When navigating to service map', () => { - it('opens service map', () => { + describe('when navigating to service map', () => { + beforeEach(() => { + cy.intercept('GET', '/internal/apm/service-map?*').as('serviceMap'); + }); + + it.skip('shows nodes in service map', () => { cy.visitKibana(serviceMapHref); - cy.contains('h1', 'Services'); + cy.wait('@serviceMap'); + cy.getByTestSubj('apmServiceGroupsTourDismissButton').click(); + + prepareCanvasForScreenshot(); + + cy.withHidden('[data-test-subj="headerGlobalNav"]', () => + cy.getByTestSubj('serviceMap').matchImage({ + imagesPath: '{spec_path}/snapshots', + title: 'global_service_map', + matchAgainstPath: 'cypress/e2e/service_map/snapshots/service_map.png', + maxDiffThreshold: 0.02, // maximum threshold above which the test should fail + }) + ); }); - it('opens detailed service map', () => { + it.skip('shows nodes in detailed service map', () => { cy.visitKibana(detailedServiceMap); + cy.wait('@serviceMap'); cy.contains('h1', 'opbeans-java'); + + prepareCanvasForScreenshot(); + + cy.withHidden('[data-test-subj="headerGlobalNav"]', () => + cy.getByTestSubj('serviceMap').matchImage({ + imagesPath: '{spec_path}/snapshots', + title: 'detailed_service_map', + matchAgainstPath: + 'cypress/e2e/service_map/snapshots/detailed_service_map.png', + maxDiffThreshold: 0.02, // maximum threshold above which the test should fail + }) + ); }); - describe('When there is no data', () => { + describe('when there is no data', () => { it('shows empty state', () => { cy.visitKibana(serviceMapHref); // we need to dismiss the service-group call out first - cy.contains('Dismiss').click(); + cy.getByTestSubj('apmServiceGroupsTourDismissButton').click(); cy.getByTestSubj('apmUnifiedSearchBar').type('_id : foo{enter}'); cy.contains('No services available'); // search bar is still visible @@ -71,3 +100,15 @@ describe('Service map', () => { }); }); }); + +function prepareCanvasForScreenshot() { + cy.get('html, body').invoke( + 'attr', + 'style', + 'height: auto; scroll-behavior: auto;' + ); + + cy.wait(300); + cy.getByTestSubj('centerServiceMap').click(); + cy.scrollTo('top'); +} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/detailed_service_map.png b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/detailed_service_map.png new file mode 100644 index 0000000000000..a8d8ccdbd7936 Binary files /dev/null and b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/detailed_service_map.png differ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/service_map.png b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/service_map.png new file mode 100644 index 0000000000000..91fba348325b4 Binary files /dev/null and b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/service_map/snapshots/service_map.png differ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/transaction_details/large_trace_in_waterfall/generate_large_trace.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/transaction_details/large_trace_in_waterfall/generate_large_trace.ts new file mode 100644 index 0000000000000..80ba2485a44f9 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/transaction_details/large_trace_in_waterfall/generate_large_trace.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable @typescript-eslint/no-shadow */ +import { apm, timerange, DistributedTrace } from '@kbn/apm-synthtrace-client'; +import { synthtrace } from '../../../../synthtrace'; + +const RATE_PER_MINUTE = 1; + +export function generateLargeTrace({ + start, + end, + rootTransactionName, + repeaterFactor, + environment, +}: { + start: number; + end: number; + rootTransactionName: string; + repeaterFactor: number; + environment: string; +}) { + const range = timerange(start, end); + + const synthRum = apm + .service({ name: 'synth-rum', environment, agentName: 'rum-js' }) + .instance('my-instance'); + + const synthNode = apm + .service({ name: 'synth-node', environment, agentName: 'nodejs' }) + .instance('my-instance'); + + const synthGo = apm + .service({ name: 'synth-go', environment, agentName: 'go' }) + .instance('my-instance'); + + const synthDotnet = apm + .service({ name: 'synth-dotnet', environment, agentName: 'dotnet' }) + .instance('my-instance'); + + const synthJava = apm + .service({ name: 'synth-java', environment, agentName: 'java' }) + .instance('my-instance'); + + const traces = range.ratePerMinute(RATE_PER_MINUTE).generator((timestamp) => { + return new DistributedTrace({ + serviceInstance: synthRum, + transactionName: rootTransactionName, + timestamp, + children: (_) => { + _.service({ + repeat: 5 * repeaterFactor, + serviceInstance: synthNode, + transactionName: 'GET /nodejs/products', + latency: 100, + + children: (_) => { + _.service({ + serviceInstance: synthGo, + transactionName: 'GET /go', + children: (_) => { + _.service({ + repeat: 5 * repeaterFactor, + serviceInstance: synthJava, + transactionName: 'GET /java', + children: (_) => { + _.external({ + name: 'GET telemetry.elastic.co', + url: 'https://telemetry.elastic.co/ping', + duration: 50, + }); + }, + }); + }, + }); + _.db({ + name: 'GET apm-*/_search', + type: 'elasticsearch', + duration: 400, + }); + _.db({ name: 'GET', type: 'redis', duration: 500 }); + _.db({ + name: 'SELECT * FROM users', + type: 'sqlite', + duration: 600, + }); + }, + }); + + _.service({ + serviceInstance: synthNode, + transactionName: 'GET /nodejs/users', + latency: 100, + repeat: 5 * repeaterFactor, + children: (_) => { + _.service({ + serviceInstance: synthGo, + transactionName: 'GET /go/security', + latency: 50, + children: (_) => { + _.service({ + repeat: 5 * repeaterFactor, + serviceInstance: synthDotnet, + transactionName: 'GET /dotnet/cases/4', + latency: 50, + children: (_) => + _.db({ + name: 'GET apm-*/_search', + type: 'elasticsearch', + duration: 600, + statement: JSON.stringify( + { + query: { + query_string: { + query: '(new york city) OR (big apple)', + default_field: 'content', + }, + }, + }, + null, + 2 + ), + }), + }); + }, + }); + }, + }); + }, + }).getTransaction(); + }); + + return synthtrace.index(traces); +} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/transaction_details/large_trace_in_waterfall/large_traces_in_waterfall.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/transaction_details/large_trace_in_waterfall/large_traces_in_waterfall.cy.ts new file mode 100644 index 0000000000000..5f40687274fb5 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/transaction_details/large_trace_in_waterfall/large_traces_in_waterfall.cy.ts @@ -0,0 +1,80 @@ +/* + * Copyright 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 { synthtrace } from '../../../../synthtrace'; +import { generateLargeTrace } from './generate_large_trace'; + +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:01:00.000Z'; +const rootTransactionName = `Large trace`; + +const timeRange = { rangeFrom: start, rangeTo: end }; + +describe('Large Trace in waterfall', () => { + before(() => { + synthtrace.clean(); + + generateLargeTrace({ + start: new Date(start).getTime(), + end: new Date(end).getTime(), + rootTransactionName, + repeaterFactor: 10, + environment: 'large_trace', + }); + }); + + after(() => { + synthtrace.clean(); + }); + + describe('when navigating to a trace sample with default maxTraceItems', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana( + `/app/apm/services/synth-rum/transactions/view?${new URLSearchParams({ + ...timeRange, + transactionName: rootTransactionName, + })}` + ); + }); + + it('renders waterfall items', () => { + cy.getByTestSubj('waterfallItem').should('have.length.greaterThan', 200); + }); + + it('shows warning about trace size', () => { + cy.getByTestSubj('apmWaterfallSizeWarning').should( + 'have.text', + 'The number of items in this trace is 15551 which is higher than the current limit of 5000. Please increase the limit via `xpack.apm.ui.maxTraceItems` to see the full trace' + ); + }); + }); + + describe('when navigating to a trace sample with maxTraceItems=20000', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + + cy.intercept('GET', '/internal/apm/traces/**', (req) => { + req.query.maxTraceItems = 20000; + }).as('getTraces'); + + cy.visitKibana( + `/app/apm/services/synth-rum/transactions/view?${new URLSearchParams({ + ...timeRange, + transactionName: rootTransactionName, + })}` + ); + }); + + it('renders waterfall items', () => { + cy.getByTestSubj('waterfallItem').should('have.length.greaterThan', 400); + }); + + it('does not show the warning about trace size', () => { + cy.getByTestSubj('apmWaterfallSizeWarning').should('not.exist'); + }); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index c191a9add4e96..da8b1d418f3a6 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -8,6 +8,7 @@ import 'cypress-real-events/support'; import { Interception } from 'cypress/types/net-stubbing'; import 'cypress-axe'; import moment from 'moment'; +import '@frsource/cypress-plugin-visual-regression-diff'; import { AXE_CONFIG, AXE_OPTIONS } from '@kbn/axe-config'; import { ApmUsername } from '../../../server/test_helpers/create_apm_users/authentication'; @@ -152,6 +153,12 @@ Cypress.Commands.add('dismissServiceGroupsTour', () => { ); }); +Cypress.Commands.add('withHidden', (selector, callback) => { + cy.get(selector).invoke('attr', 'style', 'display: none'); + callback(); + cy.get(selector).invoke('attr', 'style', ''); +}); + // A11y configuration const axeConfig = { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts index fe9ac641c1ce0..46bdb95d6ff82 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts @@ -28,5 +28,6 @@ declare namespace Cypress { updateAdvancedSettings(settings: Record): void; getByTestSubj(selector: string): Chainable>; dismissServiceGroupsTour(): void; + withHidden(selector: string, callback: () => void): void; } } diff --git a/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts b/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts index 5194a7aa549b2..9fe1c8608fad2 100644 --- a/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts +++ b/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts @@ -10,6 +10,8 @@ import { LogLevel, } from '@kbn/apm-synthtrace'; import { createEsClientForTesting } from '@kbn/test'; +// eslint-disable-next-line @kbn/imports/no_unresolvable_imports +import { initPlugin } from '@frsource/cypress-plugin-visual-regression-diff/plugins'; import del from 'del'; import { some } from 'lodash'; import { Readable } from 'stream'; @@ -35,6 +37,8 @@ export function setupNodeEvents( synthtraceEsClient.pipeline(synthtraceEsClient.getDefaultPipeline(false)); + initPlugin(on, config); + on('task', { // send logs to node process log(message) { diff --git a/x-pack/plugins/apm/kibana.jsonc b/x-pack/plugins/apm/kibana.jsonc index ed7f8fca0a241..5cdc440e0ff24 100644 --- a/x-pack/plugins/apm/kibana.jsonc +++ b/x-pack/plugins/apm/kibana.jsonc @@ -15,10 +15,10 @@ "controls", "embeddable", "features", - "infra", "logsShared", "inspector", "licensing", + "metricsDataAccess", "observability", "observabilityShared", "exploratoryView", @@ -41,6 +41,7 @@ "discover", "fleet", "fieldFormats", + "infra", "home", "ml", "security", @@ -50,6 +51,7 @@ "customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App "licenseManagement", "profiling", + "profilingDataAccess" ], "requiredBundles": [ "advancedSettings", @@ -58,7 +60,6 @@ "kibanaUtils", "ml", "observability", - "esUiShared", "maps", "observabilityAIAssistant" ] diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 6f92ded082a01..52020aed93453 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -15,7 +15,7 @@ import { } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { ConfigSchema } from '..'; -import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; +import { ApmPluginSetupDeps, ApmPluginStartDeps, ApmServices } from '../plugin'; import { createCallApmApi } from '../services/rest/create_call_apm_api'; import { setHelpExtension } from '../set_help_extension'; import { setReadonlyBadge } from '../update_badge'; @@ -24,7 +24,6 @@ import { ApmAppRoot } from '../components/routing/app_root'; /** * This module is rendered asynchronously in the Kibana platform. */ - export const renderApp = ({ coreStart, pluginsSetup, @@ -32,6 +31,7 @@ export const renderApp = ({ config, pluginsStart, observabilityRuleTypeRegistry, + apmServices, }: { coreStart: CoreStart; pluginsSetup: ApmPluginSetupDeps; @@ -39,6 +39,7 @@ export const renderApp = ({ config: ConfigSchema; pluginsStart: ApmPluginStartDeps; observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; + apmServices: ApmServices; }) => { const { element, theme$ } = appMountParameters; const apmPluginContextValue = { @@ -57,6 +58,7 @@ export const renderApp = ({ lens: pluginsStart.lens, uiActions: pluginsStart.uiActions, observabilityAIAssistant: pluginsStart.observabilityAIAssistant, + share: pluginsSetup.share, }; // render APM feedback link in global help menu @@ -80,6 +82,7 @@ export const renderApp = ({ , element diff --git a/x-pack/plugins/apm/public/assistant_functions/get_apm_services_list.ts b/x-pack/plugins/apm/public/assistant_functions/get_apm_services_list.ts index 83f1c53d5ca70..9047f768449e5 100644 --- a/x-pack/plugins/apm/public/assistant_functions/get_apm_services_list.ts +++ b/x-pack/plugins/apm/public/assistant_functions/get_apm_services_list.ts @@ -66,10 +66,10 @@ export function registerGetApmServicesListFunction({ } as const, }, async ({ arguments: args }, signal) => { - return callApmApi('GET /internal/apm/assistant/get_services_list', { + return callApmApi('POST /internal/apm/assistant/get_services_list', { signal, params: { - query: args, + body: args, }, }); } diff --git a/x-pack/plugins/apm/public/assistant_functions/get_apm_timeseries.tsx b/x-pack/plugins/apm/public/assistant_functions/get_apm_timeseries.tsx index 9bdfd4b4789d8..56269d884ad84 100644 --- a/x-pack/plugins/apm/public/assistant_functions/get_apm_timeseries.tsx +++ b/x-pack/plugins/apm/public/assistant_functions/get_apm_timeseries.tsx @@ -140,9 +140,8 @@ export function registerGetApmTimeseriesFunction({ description: 'The name of the service', }, 'service.environment': { - ...NON_EMPTY_STRING, description: - 'The environment that the service is running in.', + 'The environment that the service is running in. If undefined, all environments will be included. Only use this if you have confirmed the environment that the service is running in.', }, filter: { type: 'string', @@ -160,12 +159,7 @@ export function registerGetApmTimeseriesFunction({ 'The offset. Right: 15m. 8h. 1d. Wrong: -15m. -8h. -1d.', }, }, - required: [ - 'service.name', - 'service.environment', - 'timeseries', - 'title', - ], + required: ['service.name', 'timeseries', 'title'], }, }, }, diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx index b42a3eb3b528f..e8fa1b130b2d7 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx @@ -106,7 +106,9 @@ export function ErrorCountRuleType(props: Props) { start, end, groupBy: params.groupBy, - searchConfiguration: JSON.stringify(params.searchConfiguration), + searchConfiguration: params.searchConfiguration?.query?.query + ? JSON.stringify(params.searchConfiguration) + : undefined, }, }, } diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx index 900f90253a6e1..cd8abf26c74d4 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_duration_rule_type/index.tsx @@ -136,7 +136,9 @@ export function TransactionDurationRuleType(props: Props) { start, end, groupBy: params.groupBy, - searchConfiguration: JSON.stringify(params.searchConfiguration), + searchConfiguration: params.searchConfiguration?.query?.query + ? JSON.stringify(params.searchConfiguration) + : undefined, }, }, } diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx index cf1132e58b9e0..dd37092a260ef 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx @@ -108,7 +108,9 @@ export function TransactionErrorRateRuleType(props: Props) { start, end, groupBy: params.groupBy, - searchConfiguration: JSON.stringify(params.searchConfiguration), + searchConfiguration: params.searchConfiguration?.query?.query + ? JSON.stringify(params.searchConfiguration) + : undefined, }, }, } diff --git a/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx b/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx index 3a972b44f9a41..c883376cac4e1 100644 --- a/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx +++ b/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx @@ -51,6 +51,7 @@ export function HelpPopoverButton({ return ( `${value}`, - trend: [], + trend: currentPeriod?.mostCrashes.timeseries, trendShape: MetricTrendShape.Area, }, { diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx index 707b759631d89..3e8d42d5a2a3c 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import React, { useCallback } from 'react'; import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params'; import { useFetcher, @@ -104,17 +105,16 @@ export function MobileStats({ const metrics: MetricDatum[] = [ { - color: euiTheme.eui.euiColorDisabled, + color: euiTheme.eui.euiColorLightestShade, title: i18n.translate('xpack.apm.mobile.metrics.crash.rate', { - defaultMessage: 'Crash Rate (Crash per minute)', - }), - subtitle: i18n.translate('xpack.apm.mobile.coming.soon', { - defaultMessage: 'Coming Soon', + defaultMessage: 'Crash rate', }), icon: getIcon('bug'), - value: 'N/A', - valueFormatter: (value: number) => valueFormatter(value), - trend: [], + value: data?.currentPeriod?.crashRate?.value ?? NOT_AVAILABLE_LABEL, + valueFormatter: (value: number) => + valueFormatter(Number((value * 100).toPrecision(2)), '%'), + trend: data?.currentPeriod?.crashRate?.timeseries, + extra: getComparisonValueFormatter(data?.previousPeriod.crashRate?.value), trendShape: MetricTrendShape.Area, }, { @@ -137,7 +137,7 @@ export function MobileStats({ defaultMessage: 'Sessions', }), icon: getIcon('timeslider'), - value: data?.currentPeriod?.sessions?.value ?? NaN, + value: data?.currentPeriod?.sessions?.value ?? NOT_AVAILABLE_LABEL, valueFormatter: (value: number) => valueFormatter(value), trend: data?.currentPeriod?.sessions?.timeseries, extra: getComparisonValueFormatter(data?.previousPeriod.sessions?.value), @@ -149,7 +149,7 @@ export function MobileStats({ defaultMessage: 'HTTP requests', }), icon: getIcon('kubernetesPod'), - value: data?.currentPeriod?.requests?.value ?? NaN, + value: data?.currentPeriod?.requests?.value ?? NOT_AVAILABLE_LABEL, extra: getComparisonValueFormatter(data?.previousPeriod.requests?.value), valueFormatter: (value: number) => valueFormatter(value), trend: data?.currentPeriod?.requests?.timeseries, diff --git a/x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx b/x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx index 1c232f14e7187..ccbc70f4a8345 100644 --- a/x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx +++ b/x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx @@ -53,6 +53,7 @@ function ConfigurationValueColumn({ {value && ( {value && ( + {hostNames.map((hostName) => ( +
  • {`- ${hostName}`}
  • + ))} + + ); + } + + return ( + + + + {i18n.translate('xpack.apm.profiling.flamegraph.filteredLabel', { + defaultMessage: 'Displaying items from specific host names', + })} + + + + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/profiling_overview/index.tsx b/x-pack/plugins/apm/public/components/app/profiling_overview/index.tsx new file mode 100644 index 0000000000000..8cafb3e2beb17 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/profiling_overview/index.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentProps, +} from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { ProfilingFlamegraph } from './profiling_flamegraph'; +import { ProfilingTopNFunctions } from './profiling_top_functions'; +import { usePreferredDataSourceAndBucketSize } from '../../../hooks/use_preferred_data_source_and_bucket_size'; +import { ApmDocumentType } from '../../../../common/document_type'; + +export function ProfilingOverview() { + const { + path: { serviceName }, + query: { rangeFrom, rangeTo, environment, kuery }, + } = useApmParams('/services/{serviceName}/profiling'); + const { isProfilingAvailable } = useProfilingPlugin(); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const preferred = usePreferredDataSourceAndBucketSize({ + start, + end, + kuery, + type: ApmDocumentType.TransactionMetric, + numBuckets: 20, + }); + + const tabs = useMemo((): EuiTabbedContentProps['tabs'] => { + return [ + { + id: 'flamegraph', + name: i18n.translate('xpack.apm.profiling.tabs.flamegraph', { + defaultMessage: 'Flamegraph', + }), + content: ( + <> + + + + ), + }, + { + id: 'topNFunctions', + name: i18n.translate('xpack.apm.profiling.tabs.topNFunctions', { + defaultMessage: 'Top 10 Functions', + }), + content: ( + <> + + + + ), + }, + ]; + }, [end, environment, preferred?.source, serviceName, start]); + + if (!isProfilingAvailable) { + return null; + } + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx new file mode 100644 index 0000000000000..2df8eb2e28c8b --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EmbeddableFlamegraph } from '@kbn/observability-shared-plugin/public'; +import React from 'react'; +import { HOST_NAME } from '../../../../common/es_fields/apm'; +import { toKueryFilterFormat } from '../../../../common/utils/to_kuery_filter_format'; +import { isPending, useFetcher } from '../../../hooks/use_fetcher'; +import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; +import { HostnamesFilterWarning } from './host_names_filter_warning'; +import { ApmDataSourceWithSummary } from '../../../../common/data_source'; +import { ApmDocumentType } from '../../../../common/document_type'; + +interface Props { + serviceName: string; + start: string; + end: string; + environment: string; + dataSource?: ApmDataSourceWithSummary< + ApmDocumentType.TransactionMetric | ApmDocumentType.TransactionEvent + >; +} + +export function ProfilingFlamegraph({ + start, + end, + serviceName, + environment, + dataSource, +}: Props) { + const { profilingLocators } = useProfilingPlugin(); + + const { data, status } = useFetcher( + (callApmApi) => { + if (dataSource) { + return callApmApi( + 'GET /internal/apm/services/{serviceName}/profiling/flamegraph', + { + params: { + path: { serviceName }, + query: { + start, + end, + environment, + documentType: dataSource.documentType, + rollupInterval: dataSource.rollupInterval, + }, + }, + } + ); + } + }, + [dataSource, serviceName, start, end, environment] + ); + + const hostNamesKueryFormat = toKueryFilterFormat( + HOST_NAME, + data?.hostNames || [] + ); + + return ( + <> + + + + + +
    + + {i18n.translate('xpack.apm.profiling.flamegraph.link', { + defaultMessage: 'Go to Universal Profiling Flamegraph', + })} + +
    +
    +
    + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_top_functions.tsx b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_top_functions.tsx new file mode 100644 index 0000000000000..0c2a225040728 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_top_functions.tsx @@ -0,0 +1,103 @@ +/* + * Copyright 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 { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EmbeddableFunctions } from '@kbn/observability-shared-plugin/public'; +import React from 'react'; +import { HOST_NAME } from '../../../../common/es_fields/apm'; +import { toKueryFilterFormat } from '../../../../common/utils/to_kuery_filter_format'; +import { isPending, useFetcher } from '../../../hooks/use_fetcher'; +import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; +import { HostnamesFilterWarning } from './host_names_filter_warning'; +import { ApmDataSourceWithSummary } from '../../../../common/data_source'; +import { ApmDocumentType } from '../../../../common/document_type'; + +interface Props { + serviceName: string; + start: string; + end: string; + environment: string; + startIndex: number; + endIndex: number; + dataSource?: ApmDataSourceWithSummary< + ApmDocumentType.TransactionMetric | ApmDocumentType.TransactionEvent + >; +} + +export function ProfilingTopNFunctions({ + serviceName, + start, + end, + environment, + startIndex, + endIndex, + dataSource, +}: Props) { + const { profilingLocators } = useProfilingPlugin(); + + const { data, status } = useFetcher( + (callApmApi) => { + if (dataSource) { + return callApmApi( + 'GET /internal/apm/services/{serviceName}/profiling/functions', + { + params: { + path: { serviceName }, + query: { + start, + end, + environment, + startIndex, + endIndex, + documentType: dataSource.documentType, + rollupInterval: dataSource.rollupInterval, + }, + }, + } + ); + } + }, + [dataSource, serviceName, start, end, environment, startIndex, endIndex] + ); + + const hostNamesKueryFormat = toKueryFilterFormat( + HOST_NAME, + data?.hostNames || [] + ); + + return ( + <> + + + + + +
    + + {i18n.translate('xpack.apm.profiling.topnFunctions.link', { + defaultMessage: 'Go to Universal Profiling Functions', + })} + +
    +
    +
    + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/service_map/controls.tsx b/x-pack/plugins/apm/public/components/app/service_map/controls.tsx index f73f798cbe942..a2695e071a32e 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/controls.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/controls.tsx @@ -212,6 +212,7 @@ export function Controls() { + - + - + - + diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx index 99ec4a3d5ce5f..f4dc33f73176c 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx @@ -12,7 +12,7 @@ import { AppMountParameters, Capabilities, CoreStart } from '@kbn/core/public'; import { useHistory, useLocation } from 'react-router-dom'; import { Start as InspectorPublicPluginStart, RequestAdapter } from '@kbn/inspector-plugin/public'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; -import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { datasourceSelector, hasFieldsSelector } from '../../state_management'; import { GraphSavePolicy, GraphWorkspaceSavedObject, Workspace } from '../../types'; import { AsObservable, Settings, SettingsWorkspaceProps } from '../settings'; @@ -162,12 +162,10 @@ export const WorkspaceTopNavMenu = (props: WorkspaceTopNavMenuProps) => { props.coreStart.overlays.openFlyout( toMountPoint( - wrapWithTheme( - - - , - props.coreStart.theme.theme$ - ) + + + , + { theme: props.coreStart.theme, i18n: props.coreStart.i18n } ), { size: 'm', diff --git a/x-pack/plugins/graph/tsconfig.json b/x-pack/plugins/graph/tsconfig.json index f4dc6a3faaf73..1e8059c99c5d7 100644 --- a/x-pack/plugins/graph/tsconfig.json +++ b/x-pack/plugins/graph/tsconfig.json @@ -46,6 +46,7 @@ "@kbn/content-management-table-list-view-table", "@kbn/content-management-table-list-view", "@kbn/core-ui-settings-browser", + "@kbn/react-kibana-mount", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/grokdebugger/server/index.js b/x-pack/plugins/grokdebugger/server/index.js index 360d42321afaa..418959762f9ab 100644 --- a/x-pack/plugins/grokdebugger/server/index.js +++ b/x-pack/plugins/grokdebugger/server/index.js @@ -5,8 +5,10 @@ * 2.0. */ -import { Plugin } from './plugin'; +import { Plugin, config } from './plugin'; -export function plugin(initializerContext) { - return new Plugin(initializerContext); +export function plugin() { + return new Plugin(); } + +export { config }; diff --git a/x-pack/plugins/grokdebugger/server/plugin.js b/x-pack/plugins/grokdebugger/server/plugin.js index 58644e2faa2a4..f591608d5bed2 100644 --- a/x-pack/plugins/grokdebugger/server/plugin.js +++ b/x-pack/plugins/grokdebugger/server/plugin.js @@ -5,13 +5,15 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; +import { offeringBasedSchema, schema } from '@kbn/config-schema'; import { KibanaFramework } from './lib/kibana_framework'; import { registerGrokdebuggerRoutes } from './routes/api/grokdebugger'; export const config = { schema: schema.object({ - enabled: schema.boolean({ defaultValue: true }), + enabled: offeringBasedSchema({ + serverless: schema.boolean({ defaultValue: true }), + }), }), }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap index 7d7a293d7ae87..72c481a2ee613 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap @@ -134,18 +134,18 @@ Array [ class="euiFlexItem emotion-euiFlexItem-grow-1" >
    Lifecycle policy
    Current action
    rollover
    Failed step
    check-rollover-ready
    @@ -185,42 +185,42 @@ Array [ class="euiFlexItem emotion-euiFlexItem-grow-1" >
    Current phase
    hot
    Current action time
    2018-12-07 13:02:55
    Phase definition
    Lifecycle policy
    Current action
    complete
    Failed step
    -
    @@ -312,30 +312,30 @@ Array [ class="euiFlexItem emotion-euiFlexItem-grow-1" >
    Current phase
    new
    Current action time
    2018-12-07 13:02:55
    diff --git a/x-pack/plugins/index_management/README.md b/x-pack/plugins/index_management/README.md index b50309ac36099..8ac2837a683b6 100644 --- a/x-pack/plugins/index_management/README.md +++ b/x-pack/plugins/index_management/README.md @@ -49,11 +49,34 @@ POST %25%7B%5B%40metadata%5D%5Bbeat%5D%7D-%25%7B%5B%40metadata%5D%5Bversion%5D%7 } ``` +Create a data stream configured with data stream lifecyle. + +``` +PUT _index_template/my-index-template +{ + "index_patterns": ["my-data-stream*"], + "data_stream": { }, + "priority": 500, + "template": { + "lifecycle": { + "data_retention": "7d" + } + }, + "_meta": { + "description": "Template with data stream lifecycle" + } +} +``` + +``` +PUT _data_stream/my-data-stream +``` + ## Index templates tab ### Quick steps for testing -**Legacy index templates** are only shown in the UI on stateful *and* if a user has existing legacy index templates. You can test this functionality by creating one in Console: +**Legacy index templates** are only shown in the UI on stateful _and_ if a user has existing legacy index templates. You can test this functionality by creating one in Console: ``` PUT _template/template_1 @@ -67,6 +90,7 @@ On serverless, Elasticsearch does not support legacy index templates and therefo To test **Cloud-managed templates**: 1. Add `cluster.metadata.managed_index_templates` setting via Dev Tools: + ``` PUT /_cluster/settings { @@ -77,6 +101,7 @@ PUT /_cluster/settings ``` 2. Create a template with the format: `.cloud-` via Dev Tools. + ``` PUT _template/.cloud-example { @@ -101,4 +126,4 @@ In 7.x, the UI supports types defined as part of the mappings for legacy index t } } } -``` \ No newline at end of file +``` diff --git a/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.helpers.ts new file mode 100644 index 0000000000000..e652ece8a90ec --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.helpers.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from 'react-dom/test-utils'; + +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; +import { HttpSetup } from '@kbn/core/public'; +import { EnrichPolicyCreate } from '../../../public/application/sections/enrich_policy_create'; +import { indexManagementStore } from '../../../public/application/store'; +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: AsyncTestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/enrich_policies/create`], + componentRoutePath: `/:section(enrich_policies)/create`, + }, + doMountAsync: true, +}; + +export interface CreateEnrichPoliciesTestBed extends TestBed { + actions: { + clickNextButton: () => Promise; + clickBackButton: () => Promise; + clickRequestTab: () => Promise; + clickCreatePolicy: () => Promise; + completeConfigurationStep: ({ indices }: { indices?: string }) => Promise; + completeFieldsSelectionStep: () => Promise; + isOnConfigurationStep: () => boolean; + isOnFieldSelectionStep: () => boolean; + isOnCreateStep: () => boolean; + }; +} + +export const setup = async ( + httpSetup: HttpSetup, + overridingDependencies: any = {} +): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(EnrichPolicyCreate, httpSetup, overridingDependencies), + testBedConfig + ); + const testBed = await initTestBed(); + + /** + * User Actions + */ + const isOnConfigurationStep = () => testBed.exists('configurationForm'); + const isOnFieldSelectionStep = () => testBed.exists('fieldSelectionForm'); + const isOnCreateStep = () => testBed.exists('creationStep'); + const clickNextButton = async () => { + await act(async () => { + testBed.find('nextButton').simulate('click'); + }); + + testBed.component.update(); + }; + const clickBackButton = async () => { + await act(async () => { + testBed.find('backButton').simulate('click'); + }); + + testBed.component.update(); + }; + const clickCreatePolicy = async (executeAfter?: boolean) => { + await act(async () => { + testBed.find(executeAfter ? 'createAndExecuteButton' : 'createButton').simulate('click'); + }); + + testBed.component.update(); + }; + + const clickRequestTab = async () => { + await act(async () => { + testBed.find('requestTab').simulate('click'); + }); + + testBed.component.update(); + }; + + const completeConfigurationStep = async ({ indices }: { indices?: string }) => { + const { form } = testBed; + + form.setInputValue('policyNameField.input', 'test_policy'); + form.setSelectValue('policyTypeField', 'match'); + form.setSelectValue('policySourceIndicesField', indices ?? 'test-1'); + + await clickNextButton(); + }; + + const completeFieldsSelectionStep = async () => { + const { form } = testBed; + + form.setSelectValue('matchField', 'name'); + form.setSelectValue('enrichFields', 'email'); + + await clickNextButton(); + }; + + return { + ...testBed, + actions: { + clickNextButton, + clickBackButton, + clickRequestTab, + clickCreatePolicy, + completeConfigurationStep, + completeFieldsSelectionStep, + isOnConfigurationStep, + isOnFieldSelectionStep, + isOnCreateStep, + }, + }; +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.test.tsx new file mode 100644 index 0000000000000..4d419299e350e --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.test.tsx @@ -0,0 +1,213 @@ +/* + * Copyright 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 { act } from 'react-dom/test-utils'; + +import { setupEnvironment } from '../helpers'; +import { getMatchingIndices, getFieldsFromIndices } from '../helpers/fixtures'; +import { CreateEnrichPoliciesTestBed, setup } from './create_enrich_policy.helpers'; +import { getESPolicyCreationApiCall } from '../../../common/lib'; + +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + // Mocking CodeEditor, which uses React Monaco under the hood + CodeEditor: (props: any) => ( + { + props.onChange(e.jsonContent); + }} + /> + ), + }; +}); + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + // Mock EuiComboBox as a simple input instead so that its easier to test + EuiComboBox: (props: any) => ( + { + props.onChange(e.target.value.split(', ')); + }} + /> + ), + }; +}); + +describe('Create enrich policy', () => { + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: CreateEnrichPoliciesTestBed; + + beforeEach(async () => { + httpRequestsMockHelpers.setGetMatchingIndices(getMatchingIndices()); + + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + }); + + test('Has header and docs link', async () => { + const { exists, component } = testBed; + component.update(); + + expect(exists('createEnrichPolicyHeaderContent')).toBe(true); + expect(exists('createEnrichPolicyDocumentationLink')).toBe(true); + }); + + describe('Configuration step', () => { + it('Fields have helpers', async () => { + const { exists } = testBed; + + expect(exists('typePopoverIcon')).toBe(true); + expect(exists('uploadFileLink')).toBe(true); + expect(exists('matchAllQueryLink')).toBe(true); + }); + + it('shows validation errors if form isnt filled', async () => { + await testBed.actions.clickNextButton(); + + expect(testBed.form.getErrorsMessages()).toHaveLength(3); + }); + + it('Allows to submit the form when fields are filled', async () => { + const { actions } = testBed; + + await testBed.actions.completeConfigurationStep({}); + + expect(actions.isOnFieldSelectionStep()).toBe(true); + }); + }); + + describe('Fields selection step', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setGetFieldsFromIndices(getFieldsFromIndices()); + + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + + await testBed.actions.completeConfigurationStep({}); + }); + + it('shows validation errors if form isnt filled', async () => { + await testBed.actions.clickNextButton(); + + expect(testBed.form.getErrorsMessages()).toHaveLength(2); + }); + + it('Allows to submit the form when fields are filled', async () => { + const { form, actions } = testBed; + + form.setSelectValue('matchField', 'name'); + form.setSelectValue('enrichFields', 'email'); + + await testBed.actions.clickNextButton(); + + expect(actions.isOnCreateStep()).toBe(true); + }); + + it('When no common fields are returned it shows an error callout', async () => { + httpRequestsMockHelpers.setGetFieldsFromIndices({ + commonFields: [], + indices: [], + }); + + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + + await testBed.actions.completeConfigurationStep({ indices: 'test-1, test-2' }); + + expect(testBed.exists('noCommonFieldsError')).toBe(true); + }); + }); + + describe('Creation step', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setGetFieldsFromIndices(getFieldsFromIndices()); + + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + + await testBed.actions.completeConfigurationStep({}); + await testBed.actions.completeFieldsSelectionStep(); + }); + + it('Shows CTAs for creating the policy', async () => { + const { exists } = testBed; + + expect(exists('createButton')).toBe(true); + expect(exists('createAndExecuteButton')).toBe(true); + }); + + it('Shows policy summary and request', async () => { + const { find } = testBed; + + expect(find('enrichPolicySummaryList').text()).toContain('test_policy'); + + await testBed.actions.clickRequestTab(); + + expect(find('requestBody').text()).toContain(getESPolicyCreationApiCall('test_policy')); + }); + + it('Shows error message when creating the policy fails', async () => { + const { exists, actions } = testBed; + const error = { + statusCode: 400, + error: 'Bad Request', + message: 'something went wrong...', + }; + + httpRequestsMockHelpers.setCreateEnrichPolicy(undefined, error); + + await actions.clickCreatePolicy(); + + expect(exists('errorWhenCreatingCallout')).toBe(true); + }); + }); + + it('Can navigate back and forth with next/back buttons', async () => { + httpRequestsMockHelpers.setGetFieldsFromIndices(getFieldsFromIndices()); + + await act(async () => { + testBed = await setup(httpSetup); + }); + + const { component, actions } = testBed; + component.update(); + + // Navigate to create step + await actions.completeConfigurationStep({}); + await actions.completeFieldsSelectionStep(); + + // Clicking back button should take us to fields selection step + await actions.clickBackButton(); + expect(actions.isOnFieldSelectionStep()).toBe(true); + + // Clicking back button should take us to configuration step + await actions.clickBackButton(); + expect(actions.isOnConfigurationStep()).toBe(true); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/fixtures.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/fixtures.ts index 9f2423b1056f8..a70ad0ea552ec 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/fixtures.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/fixtures.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { EnrichPolicyType } from '@elastic/elasticsearch/lib/api/types'; + export const indexSettings = { settings: { index: { number_of_shards: '1' } }, defaults: { index: { flush_after_merge: '512mb' } }, @@ -45,3 +47,31 @@ export const indexStats = { }, }, }; + +export const createTestEnrichPolicy = (name: string, type: EnrichPolicyType) => ({ + name, + type, + sourceIndices: ['users'], + matchField: 'email', + enrichFields: ['first_name', 'last_name', 'city'], + query: { + match_all: {}, + }, +}); + +export const getMatchingIndices = () => ({ + indices: ['test-1', 'test-2', 'test-3', 'test-4', 'test-5'], +}); + +export const getFieldsFromIndices = () => ({ + commonFields: [], + indices: [ + { + index: 'test-1', + fields: [ + { name: 'first_name', type: 'keyword', normalizedType: 'keyword' }, + { name: 'age', type: 'long', normalizedType: 'number' }, + ], + }, + ], +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts index 9811255af25b0..b935a19160e44 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts @@ -19,7 +19,8 @@ export interface ResponseError { // Register helpers to mock HTTP Requests const registerHttpRequestMockHelpers = ( - httpSetup: ReturnType + httpSetup: ReturnType, + shouldDelayResponse: () => boolean ) => { const mockResponses = new Map>>( ['GET', 'PUT', 'DELETE', 'POST'].map( @@ -28,7 +29,14 @@ const registerHttpRequestMockHelpers = ( ); const mockMethodImplementation = (method: HttpMethod, path: string) => { - return mockResponses.get(method)?.get(path) ?? Promise.resolve({}); + const responsePromise = mockResponses.get(method)?.get(path) ?? Promise.resolve({}); + if (shouldDelayResponse()) { + return new Promise((resolve) => { + setTimeout(() => resolve(responsePromise), 1000); + }); + } + + return responsePromise; }; httpSetup.get.mockImplementation((path) => @@ -94,8 +102,11 @@ const registerHttpRequestMockHelpers = ( const setCreateTemplateResponse = (response?: HttpResponse, error?: ResponseError) => mockResponse('POST', `${API_BASE_PATH}/index_templates`, response, error); - const setLoadIndexSettingsResponse = (response?: HttpResponse, error?: ResponseError) => - mockResponse('GET', `${API_BASE_PATH}/settings/:name`, response, error); + const setLoadIndexSettingsResponse = ( + indexName: string, + response?: HttpResponse, + error?: ResponseError + ) => mockResponse('GET', `${API_BASE_PATH}/settings/${indexName}`, response, error); const setLoadIndexMappingResponse = ( indexName: string, @@ -127,12 +138,56 @@ const registerHttpRequestMockHelpers = ( const setLoadTelemetryResponse = (response?: HttpResponse, error?: ResponseError) => mockResponse('GET', '/api/ui_counters/_report', response, error); + const setLoadEnrichPoliciesResponse = (response?: HttpResponse, error?: ResponseError) => + mockResponse('GET', `${INTERNAL_API_BASE_PATH}/enrich_policies`, response, error); + + const setGetMatchingIndices = (response?: HttpResponse, error?: ResponseError) => + mockResponse( + 'POST', + `${INTERNAL_API_BASE_PATH}/enrich_policies/get_matching_indices`, + response, + error + ); + + const setGetFieldsFromIndices = (response?: HttpResponse, error?: ResponseError) => + mockResponse( + 'POST', + `${INTERNAL_API_BASE_PATH}/enrich_policies/get_fields_from_indices`, + response, + error + ); + + const setCreateEnrichPolicy = (response?: HttpResponse, error?: ResponseError) => + mockResponse('POST', `${INTERNAL_API_BASE_PATH}/enrich_policies`, response, error); + + const setDeleteEnrichPolicyResponse = ( + policyName: string, + response?: HttpResponse, + error?: ResponseError + ) => + mockResponse( + 'DELETE', + `${INTERNAL_API_BASE_PATH}/enrich_policies/${policyName}`, + response, + error + ); + + const setExecuteEnrichPolicyResponse = ( + policyName: string, + response?: HttpResponse, + error?: ResponseError + ) => + mockResponse('PUT', `${INTERNAL_API_BASE_PATH}/enrich_policies/${policyName}`, response, error); + const setLoadIndexDetailsResponse = ( indexName: string, response?: HttpResponse, error?: ResponseError ) => mockResponse('GET', `${INTERNAL_API_BASE_PATH}/indices/${indexName}`, response, error); + const setCreateIndexResponse = (response?: HttpResponse, error?: ResponseError) => + mockResponse('PUT', `${INTERNAL_API_BASE_PATH}/indices/create`, response, error); + return { setLoadTemplatesResponse, setLoadIndicesResponse, @@ -151,16 +206,30 @@ const registerHttpRequestMockHelpers = ( setLoadComponentTemplatesResponse, setLoadNodesPluginsResponse, setLoadTelemetryResponse, + setLoadEnrichPoliciesResponse, + setDeleteEnrichPolicyResponse, + setExecuteEnrichPolicyResponse, setLoadIndexDetailsResponse, + setCreateIndexResponse, + setGetMatchingIndices, + setGetFieldsFromIndices, + setCreateEnrichPolicy, }; }; export const init = () => { + let isResponseDelayed = false; + const getDelayResponse = () => isResponseDelayed; + const setDelayResponse = (shouldDelayResponse: boolean) => { + isResponseDelayed = shouldDelayResponse; + }; + const httpSetup = httpServiceMock.createSetupContract(); - const httpRequestsMockHelpers = registerHttpRequestMockHelpers(httpSetup); + const httpRequestsMockHelpers = registerHttpRequestMockHelpers(httpSetup, getDelayResponse); return { httpSetup, httpRequestsMockHelpers, + setDelayResponse, }; }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index 0e485de1e7775..20ae08038a2a2 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { merge } from 'lodash'; +import { LocationDescriptorObject } from 'history'; import SemVer from 'semver/classes/semver'; import { HttpSetup } from '@kbn/core/public'; @@ -15,11 +16,17 @@ import { docLinksServiceMock, uiSettingsServiceMock, themeServiceMock, + scopedHistoryMock, executionContextServiceMock, + applicationServiceMock, + fatalErrorsServiceMock, + httpServiceMock, } from '@kbn/core/public/mocks'; + import { GlobalFlyout } from '@kbn/es-ui-shared-plugin/public'; +import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; - +import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; import { settingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; import { MAJOR_VERSION } from '../../../common'; import { AppContextProvider } from '../../../public/application/app_context'; @@ -43,24 +50,39 @@ const { GlobalFlyoutProvider } = GlobalFlyout; export const services = { extensionsService: new ExtensionsService(), uiMetricService: new UiMetricService('index_management'), + notificationService: notificationServiceMock.createSetupContract(), }; services.uiMetricService.setup({ reportUiCounter() {} } as any); setExtensionsService(services.extensionsService); setUiMetricService(services.uiMetricService); +const history = scopedHistoryMock.create(); +history.createHref.mockImplementation((location: LocationDescriptorObject) => { + return `${location.pathname}?${location.search}`; +}); + const appDependencies = { services, + history, core: { - getUrlForApp: () => {}, + getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp, executionContext: executionContextServiceMock.createStartContract(), + http: httpServiceMock.createSetupContract(), + application: applicationServiceMock.createStartContract(), + fatalErrors: fatalErrorsServiceMock.createSetupContract(), + }, + plugins: { + usageCollection: usageCollectionPluginMock.createSetupContract(), + isFleetEnabled: false, + share: sharePluginMock.createStartContract(), }, - plugins: {}, // Default stateful configuration config: { enableLegacyTemplates: true, enableIndexActions: true, enableIndexStats: true, + enableIndexDetailsPage: false, }, } as any; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 7591ca90f1596..29a13faaa4fc0 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -64,4 +64,32 @@ export type TestSubjects = | 'updateEditIndexSettingsButton' | 'updateIndexSettingsErrorCallout' | 'viewButton' - | 'detailPanelTabSelected'; + | 'enrichPoliciesTable' + | 'deletePolicyModal' + | 'executePolicyModal' + | 'policyDetailsFlyout' + | 'policyTypeValue' + | 'policyIndicesValue' + | 'policyMatchFieldValue' + | 'policyEnrichFieldsValue' + | 'queryEditor' + | 'createIndexButton' + | 'createIndexNameFieldText' + | 'createIndexCancelButton' + | 'createEnrichPolicyHeaderContent' + | 'createEnrichPolicyDocumentationLink' + | 'policyNameField.input' + | 'policyTypeField' + | 'policySourceIndicesField' + | 'typePopoverIcon' + | 'uploadFileLink' + | 'matchAllQueryLink' + | 'matchField' + | 'enrichFields' + | 'noCommonFieldsError' + | 'createButton' + | 'createAndExecuteButton' + | 'enrichPolicySummaryList' + | 'requestBody' + | 'errorWhenCreatingCallout' + | 'createIndexSaveButton'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index e38359a6d9f37..d9e6694d8ba8d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -8,7 +8,6 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; -import { EuiDescriptionListDescription } from '@elastic/eui'; import { registerTestBed, TestBed, @@ -43,8 +42,9 @@ export interface DataStreamsTabTestBed extends TestBed { findDetailPanelTitle: () => string; findEmptyPromptIndexTemplateLink: () => ReactWrapper; findDetailPanelIlmPolicyLink: () => ReactWrapper; - findDetailPanelIlmPolicyName: () => ReactWrapper; + findDetailPanelIlmPolicyDetail: () => ReactWrapper; findDetailPanelIndexTemplateLink: () => ReactWrapper; + findDetailPanelDataRetentionDetail: () => ReactWrapper; } export const setup = async ( @@ -211,10 +211,14 @@ export const setup = async ( return find('indexTemplateLink'); }; - const findDetailPanelIlmPolicyName = () => { - const descriptionList = testBed.component.find(EuiDescriptionListDescription); - // ilm policy is the last in the details list - return descriptionList.last(); + const findDetailPanelIlmPolicyDetail = () => { + const { find } = testBed; + return find('ilmPolicyDetail'); + }; + + const findDetailPanelDataRetentionDetail = () => { + const { find } = testBed; + return find('dataRetentionDetail'); }; return { @@ -240,8 +244,9 @@ export const setup = async ( findDetailPanelTitle, findEmptyPromptIndexTemplateLink, findDetailPanelIlmPolicyLink, - findDetailPanelIlmPolicyName, + findDetailPanelIlmPolicyDetail, findDetailPanelIndexTemplateLink, + findDetailPanelDataRetentionDetail, }; }; @@ -264,6 +269,9 @@ export const createDataStreamPayload = (dataStream: Partial): DataSt delete_index: true, }, hidden: false, + lifecycle: { + data_retention: '7d', + }, ...dataStream, }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 208bec0a1d6e6..9e35f288499c5 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -8,6 +8,10 @@ import { act } from 'react-dom/test-utils'; import { createMemoryHistory } from 'history'; +import { + breadcrumbService, + IndexManagementBreadcrumb, +} from '../../../public/application/services/breadcrumbs'; import { API_BASE_PATH } from '../../../common/constants'; import * as fixtures from '../../../test/fixtures'; import { setupEnvironment } from '../helpers'; @@ -40,6 +44,7 @@ const urlServiceMock = { describe('Data Streams tab', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); let testBed: DataStreamsTabTestBed; + jest.spyOn(breadcrumbService, 'setBreadcrumbs'); describe('when there are no data streams', () => { beforeEach(async () => { @@ -120,6 +125,12 @@ describe('Data Streams tab', () => { testBed.component.update(); expect(testBed.find('dataStreamTable').text()).toContain('No data streams found'); }); + + test('updates the breadcrumbs to data streams', () => { + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.dataStreams + ); + }); }); describe('when there are data streams', () => { @@ -170,8 +181,8 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', '1', 'Delete'], - ['', 'dataStream2', 'green', '1', 'Delete'], + ['', 'dataStream1', 'green', '1', '7d', 'Delete'], + ['', 'dataStream2', 'green', '1', '7d', 'Delete'], ]); }); @@ -209,8 +220,8 @@ describe('Data Streams tab', () => { // The table renders with the stats columns though. const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], - ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', '7d', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', '7d', 'Delete'], ]); }); @@ -229,8 +240,8 @@ describe('Data Streams tab', () => { // the human-readable string values. const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], - ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', '7d', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', '7d', 'Delete'], ]); }); @@ -335,6 +346,12 @@ describe('Data Streams tab', () => { expect(find('summaryTab').exists()).toBeTruthy(); expect(find('title').text().trim()).toBe('indexTemplate'); }); + + test('shows data retention detail when configured', async () => { + const { actions, findDetailPanelDataRetentionDetail } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelDataRetentionDetail().exists()).toBeTruthy(); + }); }); }); @@ -423,10 +440,10 @@ describe('Data Streams tab', () => { }); testBed.component.update(); - const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyDetail } = testBed; await actions.clickNameAt(0); expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); - expect(findDetailPanelIlmPolicyName().contains('None')).toBeTruthy(); + expect(findDetailPanelIlmPolicyDetail().exists()).toBeFalsy(); }); test('without an ILM url locator and with an ILM policy', async () => { @@ -453,10 +470,10 @@ describe('Data Streams tab', () => { }); testBed.component.update(); - const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyDetail } = testBed; await actions.clickNameAt(0); expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); - expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy(); + expect(findDetailPanelIlmPolicyDetail().contains('my_ilm_policy')).toBeTruthy(); }); }); @@ -489,8 +506,8 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', 'Delete'], - ['', 'non-managed-data-stream', 'green', '1', 'Delete'], + ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', '7d', 'Delete'], + ['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'], ]); }); @@ -499,14 +516,16 @@ describe('Data Streams tab', () => { let { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', 'Delete'], - ['', 'non-managed-data-stream', 'green', '1', 'Delete'], + ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', '7d', 'Delete'], + ['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'], ]); actions.toggleViewFilterAt(0); ({ tableCellsValues } = table.getMetaData('dataStreamTable')); - expect(tableCellsValues).toEqual([['', 'non-managed-data-stream', 'green', '1', 'Delete']]); + expect(tableCellsValues).toEqual([ + ['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'], + ]); }); }); @@ -537,7 +556,7 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', `hidden-data-stream${nonBreakingSpace}Hidden`, 'green', '1', 'Delete'], + ['', `hidden-data-stream${nonBreakingSpace}Hidden`, 'green', '1', '7d', 'Delete'], ]); }); }); @@ -570,8 +589,8 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStreamNoDelete', 'green', '1', ''], - ['', 'dataStreamWithDelete', 'green', '1', 'Delete'], + ['', 'dataStreamNoDelete', 'green', '1', '7d', ''], + ['', 'dataStreamWithDelete', 'green', '1', '7d', 'Delete'], ]); }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.helpers.ts new file mode 100644 index 0000000000000..2c50a6ae5a85b --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.helpers.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from 'react-dom/test-utils'; + +import { + registerTestBed, + TestBed, + AsyncTestBedConfig, + findTestSubject, +} from '@kbn/test-jest-helpers'; +import { HttpSetup } from '@kbn/core/public'; +import { IndexManagementHome } from '../../../public/application/sections/home'; +import { indexManagementStore } from '../../../public/application/store'; +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: AsyncTestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/enrich_policies`], + componentRoutePath: `/:section(enrich_policies)`, + }, + doMountAsync: true, +}; + +export interface EnrichPoliciesTestBed extends TestBed { + actions: { + goToEnrichPoliciesTab: () => void; + clickReloadPoliciesButton: () => void; + clickDeletePolicyAt: (index: number) => Promise; + clickConfirmDeletePolicyButton: () => Promise; + clickExecutePolicyAt: (index: number) => Promise; + clickConfirmExecutePolicyButton: () => Promise; + clickEnrichPolicyAt: (index: number) => Promise; + }; +} + +export const setup = async ( + httpSetup: HttpSetup, + overridingDependencies: any = {} +): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(IndexManagementHome, httpSetup, overridingDependencies), + testBedConfig + ); + const testBed = await initTestBed(); + + /** + * User Actions + */ + const goToEnrichPoliciesTab = () => testBed.find('enrich_policiesTab').simulate('click'); + + const clickReloadPoliciesButton = () => testBed.find('reloadPoliciesButton').simulate('click'); + + const clickDeletePolicyAt = async (index: number) => { + const { rows } = testBed.table.getMetaData('enrichPoliciesTable'); + + const deletePolicyButton = findTestSubject(rows[index].reactWrapper, 'deletePolicyButton'); + + await act(async () => { + deletePolicyButton.simulate('click'); + }); + + testBed.component.update(); + }; + + const clickConfirmDeletePolicyButton = async () => { + const modal = testBed.find('deletePolicyModal'); + const confirmButton = findTestSubject(modal, 'confirmModalConfirmButton'); + + await act(async () => { + confirmButton.simulate('click'); + }); + + testBed.component.update(); + }; + + const clickExecutePolicyAt = async (index: number) => { + const { rows } = testBed.table.getMetaData('enrichPoliciesTable'); + + const executePolicyButton = findTestSubject(rows[index].reactWrapper, 'executePolicyButton'); + + await act(async () => { + executePolicyButton.simulate('click'); + }); + + testBed.component.update(); + }; + + const clickConfirmExecutePolicyButton = async () => { + const modal = testBed.find('executePolicyModal'); + const confirmButton = findTestSubject(modal, 'confirmModalConfirmButton'); + + await act(async () => { + confirmButton.simulate('click'); + }); + + testBed.component.update(); + }; + + const clickEnrichPolicyAt = async (index: number) => { + const { component, table, router } = testBed; + + const { rows } = table.getMetaData('enrichPoliciesTable'); + + const policyLink = findTestSubject(rows[index].reactWrapper, 'enrichPolicyDetailsLink'); + + await act(async () => { + const { href } = policyLink.props(); + router.navigateTo(href!); + }); + + component.update(); + }; + + return { + ...testBed, + actions: { + goToEnrichPoliciesTab, + clickReloadPoliciesButton, + clickDeletePolicyAt, + clickConfirmDeletePolicyButton, + clickExecutePolicyAt, + clickConfirmExecutePolicyButton, + clickEnrichPolicyAt, + }, + }; +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx new file mode 100644 index 0000000000000..609c98a4e5a22 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx @@ -0,0 +1,263 @@ +/* + * Copyright 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 { act } from 'react-dom/test-utils'; +import { notificationServiceMock } from '@kbn/core/public/mocks'; + +import { setupEnvironment } from '../helpers'; +import { createTestEnrichPolicy } from '../helpers/fixtures'; +import { EnrichPoliciesTestBed, setup } from './enrich_policies.helpers'; +import { notificationService } from '../../../public/application/services/notification'; + +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + // Mocking CodeEditor, which uses React Monaco under the hood + CodeEditor: (props: any) => ( + { + props.onChange(e.jsonContent); + }} + /> + ), + }; +}); + +describe('Enrich policies tab', () => { + const { httpSetup, httpRequestsMockHelpers, setDelayResponse } = setupEnvironment(); + let testBed: EnrichPoliciesTestBed; + + describe('empty states', () => { + beforeEach(async () => { + setDelayResponse(false); + }); + + test('displays a loading prompt', async () => { + setDelayResponse(true); + + testBed = await setup(httpSetup); + + await act(async () => { + testBed.actions.goToEnrichPoliciesTab(); + }); + + const { exists, component } = testBed; + component.update(); + + expect(exists('sectionLoading')).toBe(true); + }); + + test('displays a error prompt', async () => { + const error = { + statusCode: 400, + error: 'Bad Request', + message: 'something went wrong...', + }; + + httpRequestsMockHelpers.setLoadEnrichPoliciesResponse(undefined, error); + + testBed = await setup(httpSetup); + + await act(async () => { + testBed.actions.goToEnrichPoliciesTab(); + }); + + const { exists, component } = testBed; + component.update(); + + expect(exists('sectionError')).toBe(true); + }); + }); + + describe('policies list', () => { + let testPolicy: ReturnType; + beforeEach(async () => { + testPolicy = createTestEnrichPolicy('policy-match', 'match'); + + httpRequestsMockHelpers.setLoadEnrichPoliciesResponse([ + testPolicy, + createTestEnrichPolicy('policy-range', 'range'), + ]); + + testBed = await setup(httpSetup); + await act(async () => { + testBed.actions.goToEnrichPoliciesTab(); + }); + + testBed.component.update(); + }); + + it('shows enrich policies in table', async () => { + const { table } = testBed; + expect(table.getMetaData('enrichPoliciesTable').rows.length).toBe(2); + }); + + it('can reload the table data through a call to action', async () => { + const { actions } = testBed; + + // Reset mock to clear calls from setup + httpSetup.get.mockClear(); + + await act(async () => { + actions.clickReloadPoliciesButton(); + }); + + // Should have made a call to load the policies after the reload + // button is clicked. + expect(httpSetup.get.mock.calls).toHaveLength(1); + }); + + describe('details flyout', () => { + it('can open the details flyout', async () => { + const { actions, exists } = testBed; + + await actions.clickEnrichPolicyAt(0); + + expect(exists('policyDetailsFlyout')).toBe(true); + }); + + it('contains all the necessary policy fields', async () => { + const { actions, find } = testBed; + + await actions.clickEnrichPolicyAt(0); + + expect(find('policyTypeValue').text()).toBe(testPolicy.type); + expect(find('policyIndicesValue').text()).toBe(testPolicy.sourceIndices.join(', ')); + expect(find('policyMatchFieldValue').text()).toBe(testPolicy.matchField); + expect(find('policyEnrichFieldsValue').text()).toBe(testPolicy.enrichFields.join(', ')); + + const codeEditorValue = find('queryEditor') + .at(0) + .getDOMNode() + .getAttribute('data-currentvalue'); + expect(JSON.parse(codeEditorValue || '')).toEqual(testPolicy.query); + }); + }); + + describe('policy actions', () => { + const notificationsServiceMock = notificationServiceMock.createStartContract(); + + beforeEach(async () => { + notificationService.setup(notificationsServiceMock); + + httpRequestsMockHelpers.setLoadEnrichPoliciesResponse([ + createTestEnrichPolicy('policy-match', 'match'), + ]); + + testBed = await setup(httpSetup, { + services: { + notificationService, + }, + }); + + await act(async () => { + testBed.actions.goToEnrichPoliciesTab(); + }); + + testBed.component.update(); + }); + + describe('deletion', () => { + it('can delete a policy', async () => { + const { actions, exists } = testBed; + + httpRequestsMockHelpers.setDeleteEnrichPolicyResponse('policy-match', { + acknowledged: true, + }); + + await actions.clickDeletePolicyAt(0); + + expect(exists('deletePolicyModal')).toBe(true); + + await actions.clickConfirmDeletePolicyButton(); + + expect(notificationsServiceMock.toasts.add).toHaveBeenLastCalledWith( + expect.objectContaining({ + title: 'Deleted policy-match', + }) + ); + expect(httpSetup.delete.mock.calls.length).toBe(1); + }); + + test('displays an error toast if it fails', async () => { + const { actions, exists } = testBed; + + const error = { + statusCode: 400, + error: 'Bad Request', + message: 'something went wrong...', + }; + + httpRequestsMockHelpers.setDeleteEnrichPolicyResponse('policy-match', undefined, error); + + await actions.clickDeletePolicyAt(0); + + expect(exists('deletePolicyModal')).toBe(true); + + await actions.clickConfirmDeletePolicyButton(); + + expect(notificationsServiceMock.toasts.add).toHaveBeenLastCalledWith( + expect.objectContaining({ + title: `Error deleting enrich policy: 'something went wrong...'`, + }) + ); + }); + }); + + describe('execution', () => { + it('can execute a policy', async () => { + const { actions, exists } = testBed; + + httpRequestsMockHelpers.setExecuteEnrichPolicyResponse('policy-match', { + acknowledged: true, + }); + + await actions.clickExecutePolicyAt(0); + + expect(exists('executePolicyModal')).toBe(true); + + await actions.clickConfirmExecutePolicyButton(); + + expect(notificationsServiceMock.toasts.add).toHaveBeenLastCalledWith( + expect.objectContaining({ + title: 'Executed policy-match', + }) + ); + expect(httpSetup.put.mock.calls.length).toBe(1); + }); + + test('displays an error toast if it fails', async () => { + const { actions, exists } = testBed; + + const error = { + statusCode: 400, + error: 'Bad Request', + message: 'something went wrong...', + }; + + httpRequestsMockHelpers.setExecuteEnrichPolicyResponse('policy-match', undefined, error); + + await actions.clickExecutePolicyAt(0); + + expect(exists('executePolicyModal')).toBe(true); + + await actions.clickConfirmExecutePolicyButton(); + + expect(notificationsServiceMock.toasts.add).toHaveBeenLastCalledWith( + expect.objectContaining({ + title: `Error executing enrich policy: 'something went wrong...'`, + }) + ); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts index 16761dc23679c..2085368fc7760 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts @@ -56,9 +56,15 @@ describe('', () => { const indexManagementContainer = find('indexManagementHeaderContent'); const tabListContainer = indexManagementContainer.find('div.euiTabs'); const allTabs = tabListContainer.children(); - const allTabsLabels = ['Indices', 'Data Streams', 'Index Templates', 'Component Templates']; - - expect(allTabs.length).toBe(4); + const allTabsLabels = [ + 'Indices', + 'Data Streams', + 'Index Templates', + 'Component Templates', + 'Enrich Policies', + ]; + + expect(allTabs.length).toBe(5); for (let i = 0; i < allTabs.length; i++) { expect(tabListContainer.childAt(i).text()).toEqual(allTabsLabels[i]); } diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts index 3d1360d620ff5..be09ed36af8d3 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts @@ -8,6 +8,10 @@ import { act } from 'react-dom/test-utils'; import * as fixtures from '../../../test/fixtures'; +import { + breadcrumbService, + IndexManagementBreadcrumb, +} from '../../../public/application/services/breadcrumbs'; import { API_BASE_PATH } from '../../../common/constants'; import { setupEnvironment, getRandomString } from '../helpers'; @@ -26,8 +30,22 @@ const removeWhiteSpaceOnArrayValues = (array: any[]) => describe('Index Templates tab', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); let testBed: IndexTemplatesTabTestBed; + jest.spyOn(breadcrumbService, 'setBreadcrumbs'); describe('when there are no index templates of either kind', () => { + test('updates the breadcrumbs to component templates', async () => { + httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] }); + + await act(async () => { + testBed = await setup(httpSetup); + }); + const { component } = testBed; + component.update(); + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.templates + ); + }); + test('should display an empty prompt', async () => { httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index 8aaa356ca293e..f3ce7cbe71a95 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -40,6 +40,9 @@ export interface IndicesTestBed extends TestBed { clickManageContextMenuButton: () => void; clickContextMenuOption: (optionDataTestSubject: string) => void; clickModalConfirm: () => void; + clickCreateIndexButton: () => void; + clickCreateIndexCancelButton: () => void; + clickCreateIndexSaveButton: () => void; }; findDataStreamDetailPanel: () => ReactWrapper; findDataStreamDetailPanelTitle: () => string; @@ -135,6 +138,35 @@ export const setup = async ( return find('dataStreamDetailPanelTitle').text(); }; + const clickCreateIndexButton = async () => { + const { find, component } = testBed; + + await act(async () => { + find('createIndexButton').simulate('click'); + }); + component.update(); + }; + + const clickCreateIndexCancelButton = async () => { + const { find, exists, component } = testBed; + + expect(exists('createIndexCancelButton')).toBe(true); + await act(async () => { + find('createIndexCancelButton').simulate('click'); + }); + component.update(); + }; + + const clickCreateIndexSaveButton = async () => { + const { find, exists, component } = testBed; + + expect(exists('createIndexSaveButton')).toBe(true); + await act(async () => { + find('createIndexSaveButton').simulate('click'); + }); + component.update(); + }; + return { ...testBed, actions: { @@ -146,6 +178,9 @@ export const setup = async ( clickManageContextMenuButton, clickContextMenuOption, clickModalConfirm, + clickCreateIndexButton, + clickCreateIndexCancelButton, + clickCreateIndexSaveButton, }, findDataStreamDetailPanel, findDataStreamDetailPanelTitle, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index 80c03726c3d40..68655f822ffbe 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { API_BASE_PATH } from '../../../common'; +import { API_BASE_PATH, INTERNAL_API_BASE_PATH } from '../../../common'; import { setupEnvironment, nextTick } from '../helpers'; import { IndicesTestBed, setup } from './indices_tab.helpers'; import { createDataStreamPayload, createNonDataStreamIndex } from './data_streams_tab.helpers'; @@ -45,12 +45,17 @@ jest.mock('../../../public/application/lib/ace', () => { */ import { stubWebWorker } from '@kbn/test-jest-helpers'; import { createMemoryHistory } from 'history'; +import { + breadcrumbService, + IndexManagementBreadcrumb, +} from '../../../public/application/services/breadcrumbs'; stubWebWorker(); describe('', () => { let testBed: IndicesTestBed; let httpSetup: ReturnType['httpSetup']; let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; + jest.spyOn(breadcrumbService, 'setBreadcrumbs'); beforeEach(() => { const mockEnvironment = setupEnvironment(); @@ -71,6 +76,12 @@ describe('', () => { component.update(); }); + test('updates the breadcrumbs to indices', () => { + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.indices + ); + }); + test('toggles the include hidden button through URL hash correctly', () => { const { actions } = testBed; expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); @@ -521,4 +532,72 @@ describe('', () => { }); }); }); + + describe('Create Index', () => { + const indexNameA = 'test-index-a'; + const indexNameB = 'test-index-b'; + const indexMockA = createNonDataStreamIndex(indexNameA); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([ + { + ...indexMockA, + }, + ]); + + testBed = await setup(httpSetup, { + history: createMemoryHistory(), + }); + + await act(async () => { + const { component } = testBed; + + await nextTick(); + component.update(); + }); + }); + + test('shows the create index button', async () => { + const { exists } = testBed; + + expect(exists('createIndexButton')).toBe(true); + }); + + test('can open & close the create index modal', async () => { + const { exists, actions } = testBed; + + await actions.clickCreateIndexButton(); + + expect(exists('createIndexNameFieldText')).toBe(true); + + await await actions.clickCreateIndexCancelButton(); + + expect(exists('createIndexNameFieldText')).toBe(false); + }); + + test('creating an index', async () => { + const { component, exists, find, actions } = testBed; + + expect(httpSetup.get).toHaveBeenCalledTimes(1); + expect(httpSetup.get).toHaveBeenNthCalledWith(1, '/api/index_management/indices'); + + await actions.clickCreateIndexButton(); + + expect(exists('createIndexNameFieldText')).toBe(true); + await act(async () => { + find('createIndexNameFieldText').simulate('change', { target: { value: indexNameB } }); + }); + component.update(); + + await actions.clickCreateIndexSaveButton(); + + // Saves the index with expected name + expect(httpSetup.put).toHaveBeenCalledWith(`${INTERNAL_API_BASE_PATH}/indices/create`, { + body: '{"indexName":"test-index-b"}', + }); + // It refresh indices after saving + expect(httpSetup.get).toHaveBeenCalledTimes(2); + expect(httpSetup.get).toHaveBeenNthCalledWith(2, '/api/index_management/indices'); + }); + }); }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts index cdf5e9f5ab841..24cf015e3918d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts @@ -44,6 +44,17 @@ export interface IndexDetailsPageTestBed extends TestBed { isErrorDisplayed: () => boolean; clickErrorReloadButton: () => Promise; }; + settings: { + getCodeBlockContent: () => string; + getDocsLinkHref: () => string; + isErrorDisplayed: () => boolean; + clickErrorReloadButton: () => Promise; + clickEditModeSwitch: () => Promise; + getCodeEditorContent: () => string; + updateCodeEditorContent: (value: string) => Promise; + saveSettings: () => Promise; + resetChanges: () => Promise; + }; clickBackToIndicesButton: () => Promise; discoverLinkExists: () => boolean; contextMenu: { @@ -65,6 +76,11 @@ export interface IndexDetailsPageTestBed extends TestBed { indexStatsTabExists: () => boolean; isWarningDisplayed: () => boolean; }; + overview: { + indexStatsContentExists: () => boolean; + indexDetailsContentExists: () => boolean; + addDocCodeBlockExists: () => boolean; + }; }; } @@ -105,6 +121,18 @@ export const setup = async ( return find('indexDetailsContent').text(); }; + const overview = { + indexStatsContentExists: () => { + return exists('overviewTabIndexStats'); + }, + indexDetailsContentExists: () => { + return exists('overviewTabIndexDetails'); + }, + addDocCodeBlockExists: () => { + return exists('codeBlockControlsPanel'); + }, + }; + const mappings = { getCodeBlockContent: () => { return find('indexDetailsMappingsCodeBlock').text(); @@ -123,6 +151,53 @@ export const setup = async ( }, }; + const settings = { + getCodeBlockContent: () => { + return find('indexDetailsSettingsCodeBlock').text(); + }, + getDocsLinkHref: () => { + return find('indexDetailsSettingsDocsLink').prop('href'); + }, + isErrorDisplayed: () => { + return exists('indexDetailsSettingsError'); + }, + clickErrorReloadButton: async () => { + await act(async () => { + find('indexDetailsSettingsReloadButton').simulate('click'); + }); + component.update(); + }, + clickEditModeSwitch: async () => { + await act(async () => { + find('indexDetailsSettingsEditModeSwitch').simulate('click'); + }); + component.update(); + }, + getCodeEditorContent: () => { + return find('indexDetailsSettingsEditor').prop('data-currentvalue'); + }, + updateCodeEditorContent: async (value: string) => { + // the code editor is mocked as an input so need to set data-currentvalue attribute to change the value + find('indexDetailsSettingsEditor').getDOMNode().setAttribute('data-currentvalue', value); + await act(async () => { + find('indexDetailsSettingsEditor').simulate('change'); + }); + component.update(); + }, + saveSettings: async () => { + await act(async () => { + find('indexDetailsSettingsSave').simulate('click'); + }); + component.update(); + }, + resetChanges: async () => { + await act(async () => { + find('indexDetailsSettingsResetChanges').simulate('click'); + }); + component.update(); + }, + }; + const clickBackToIndicesButton = async () => { await act(async () => { find('indexDetailsBackToIndicesButton').simulate('click'); @@ -199,6 +274,8 @@ export const setup = async ( clickIndexDetailsTab, getActiveTabContent, mappings, + settings, + overview, clickBackToIndicesButton, discoverLinkExists, contextMenu, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.ts deleted file mode 100644 index ddd1d44ac9440..0000000000000 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.ts +++ /dev/null @@ -1,389 +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 { setupEnvironment } from '../helpers'; -import { IndexDetailsPageTestBed, setup } from './index_details_page.helpers'; -import { act } from 'react-dom/test-utils'; -import { IndexDetailsSection } from '../../../public/application/sections/home/index_list/details_page'; -import { testIndexMappings, testIndexMock, testIndexName, testIndexStats } from './mocks'; -import { API_BASE_PATH, INTERNAL_API_BASE_PATH } from '../../../common'; - -describe('', () => { - let testBed: IndexDetailsPageTestBed; - let httpSetup: ReturnType['httpSetup']; - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - - beforeEach(async () => { - const mockEnvironment = setupEnvironment(); - ({ httpSetup, httpRequestsMockHelpers } = mockEnvironment); - // testIndexName is configured in initialEntries of the memory router - httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testIndexMock); - httpRequestsMockHelpers.setLoadIndexStatsResponse(testIndexName, testIndexStats); - httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, testIndexMappings); - - await act(async () => { - testBed = await setup(httpSetup, { - url: { - locators: { - get: () => ({ navigate: jest.fn() }), - }, - }, - }); - }); - testBed.component.update(); - }); - - describe('error section', () => { - beforeEach(async () => { - httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, undefined, { - statusCode: 400, - message: `Data for index ${testIndexName} was not found`, - }); - await act(async () => { - testBed = await setup(httpSetup); - }); - - testBed.component.update(); - }); - it('displays an error callout when failed to load index details', async () => { - expect(testBed.actions.errorSection.isDisplayed()).toBe(true); - }); - - it('resends a request when reload button is clicked', async () => { - // already sent 2 requests while setting up the component - const numberOfRequests = 2; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - await testBed.actions.errorSection.clickReloadButton(); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - }); - - describe('Stats tab', () => { - it('loads index stats from the API', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); - expect(httpSetup.get).toHaveBeenLastCalledWith(`${API_BASE_PATH}/stats/${testIndexName}`, { - asSystemRequest: undefined, - body: undefined, - query: undefined, - version: undefined, - }); - }); - - it('renders index stats', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); - const tabContent = testBed.actions.stats.getCodeBlockContent(); - expect(tabContent).toEqual(JSON.stringify(testIndexStats, null, 2)); - }); - - it('sets the docs link href from the documenation service', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); - const docsLinkHref = testBed.actions.stats.getDocsLinkHref(); - // the url from the mocked docs mock - expect(docsLinkHref).toEqual( - 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/indices-stats.html' - ); - }); - - it('renders a warning message if an index is not open', async () => { - const testIndexMockWithClosedStatus = { - ...testIndexMock, - status: 'closed', - }; - - httpRequestsMockHelpers.setLoadIndexDetailsResponse( - testIndexName, - testIndexMockWithClosedStatus - ); - - await act(async () => { - testBed = await setup(httpSetup); - }); - testBed.component.update(); - - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); - expect(testBed.actions.stats.isWarningDisplayed()).toBe(true); - }); - - it('hides index stats tab if enableIndexStats===false', async () => { - await act(async () => { - testBed = await setup(httpSetup, { - config: { enableIndexStats: false }, - }); - }); - testBed.component.update(); - - expect(testBed.actions.stats.indexStatsTabExists()).toBe(false); - }); - - describe('Error handling', () => { - beforeEach(async () => { - httpRequestsMockHelpers.setLoadIndexStatsResponse(testIndexName, undefined, { - statusCode: 500, - message: 'Error', - }); - await act(async () => { - testBed = await setup(httpSetup); - }); - - testBed.component.update(); - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); - }); - - it('there is an error prompt', async () => { - expect(testBed.actions.stats.isErrorDisplayed()).toBe(true); - }); - - it('resends a request when reload button is clicked', async () => { - // already sent 3 requests while setting up the component - const numberOfRequests = 3; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - await testBed.actions.stats.clickErrorReloadButton(); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - }); - }); - - it('loads index details from the API', async () => { - expect(httpSetup.get).toHaveBeenLastCalledWith( - `${INTERNAL_API_BASE_PATH}/indices/${testIndexName}`, - { asSystemRequest: undefined, body: undefined, query: undefined, version: undefined } - ); - }); - - it('displays index name in the header', () => { - const header = testBed.actions.getHeader(); - // testIndexName is configured in initialEntries of the memory router - expect(header).toEqual(testIndexName); - }); - - it('defaults to overview tab', () => { - const tabContent = testBed.actions.getActiveTabContent(); - expect(tabContent).toEqual('Overview'); - }); - - it('documents tab', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Documents); - const tabContent = testBed.actions.getActiveTabContent(); - expect(tabContent).toEqual('Documents'); - }); - - describe('mappings tab', () => { - it('loads mappings from the API', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); - expect(httpSetup.get).toHaveBeenLastCalledWith(`${API_BASE_PATH}/mapping/${testIndexName}`, { - asSystemRequest: undefined, - body: undefined, - query: undefined, - version: undefined, - }); - }); - - it('displays the mappings in the code block', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); - - const tabContent = testBed.actions.mappings.getCodeBlockContent(); - expect(tabContent).toEqual(JSON.stringify(testIndexMappings, null, 2)); - }); - - it('sets the docs link href from the documenation service', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); - const docsLinkHref = testBed.actions.mappings.getDocsLinkHref(); - // the url from the mocked docs mock - expect(docsLinkHref).toEqual( - 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/mapping.html' - ); - }); - - describe('error loading mappings', () => { - beforeEach(async () => { - httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, undefined, { - statusCode: 400, - message: `Was not able to load mappings`, - }); - await act(async () => { - testBed = await setup(httpSetup); - }); - - testBed.component.update(); - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); - }); - - it('there is an error prompt', async () => { - expect(testBed.actions.mappings.isErrorDisplayed()).toBe(true); - }); - - it('resends a request when reload button is clicked', async () => { - // already sent 3 requests while setting up the component - const numberOfRequests = 3; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - await testBed.actions.mappings.clickErrorReloadButton(); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - }); - }); - - it('settings tab', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); - const tabContent = testBed.actions.getActiveTabContent(); - expect(tabContent).toEqual('Settings'); - }); - - it('pipelines tab', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Pipelines); - const tabContent = testBed.actions.getActiveTabContent(); - expect(tabContent).toEqual('Pipelines'); - }); - - it('navigates back to indices', async () => { - jest.spyOn(testBed.routerMock.history, 'push'); - await testBed.actions.clickBackToIndicesButton(); - expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1); - expect(testBed.routerMock.history.push).toHaveBeenCalledWith('/indices'); - }); - - it('renders a link to discover', () => { - // we only need to test that the link is rendered since the link component has its own tests for navigation - expect(testBed.actions.discoverLinkExists()).toBe(true); - }); - - describe('context menu', () => { - it('opens an index context menu when "manage index" button is clicked', async () => { - expect(testBed.actions.contextMenu.isOpened()).toBe(false); - await testBed.actions.contextMenu.clickManageIndexButton(); - expect(testBed.actions.contextMenu.isOpened()).toBe(true); - }); - - it('closes an index', async () => { - // already sent 1 request while setting up the component - const numberOfRequests = 1; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('closeIndexMenuButton'); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/close`, { - body: JSON.stringify({ indices: [testIndexName] }), - }); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - - it('opens an index', async () => { - httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, { - ...testIndexMock, - status: 'close', - }); - - await act(async () => { - testBed = await setup(httpSetup); - }); - testBed.component.update(); - - // already sent 2 requests while setting up the component - const numberOfRequests = 2; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('openIndexMenuButton'); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/open`, { - body: JSON.stringify({ indices: [testIndexName] }), - }); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - - it('forcemerges an index', async () => { - // already sent 1 request while setting up the component - const numberOfRequests = 1; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('forcemergeIndexMenuButton'); - await testBed.actions.contextMenu.confirmForcemerge('2'); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/forcemerge`, { - body: JSON.stringify({ indices: [testIndexName], maxNumSegments: '2' }), - }); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - - it('refreshes an index', async () => { - // already sent 1 request while setting up the component - const numberOfRequests = 1; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('refreshIndexMenuButton'); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/refresh`, { - body: JSON.stringify({ indices: [testIndexName] }), - }); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - - it(`clears an index's cache`, async () => { - // already sent 1 request while setting up the component - const numberOfRequests = 1; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('clearCacheIndexMenuButton'); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/clear_cache`, { - body: JSON.stringify({ indices: [testIndexName] }), - }); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - - it(`flushes an index`, async () => { - // already sent 1 request while setting up the component - const numberOfRequests = 1; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('flushIndexMenuButton'); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/flush`, { - body: JSON.stringify({ indices: [testIndexName] }), - }); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - - it(`deletes an index`, async () => { - jest.spyOn(testBed.routerMock.history, 'push'); - // already sent 1 request while setting up the component - const numberOfRequests = 1; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('deleteIndexMenuButton'); - await testBed.actions.contextMenu.confirmDelete(); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/delete`, { - body: JSON.stringify({ indices: [testIndexName] }), - }); - - expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1); - expect(testBed.routerMock.history.push).toHaveBeenCalledWith('/indices'); - }); - - it(`unfreezes a frozen index`, async () => { - httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, { - ...testIndexMock, - isFrozen: true, - }); - - await act(async () => { - testBed = await setup(httpSetup); - }); - testBed.component.update(); - - // already sent 1 request while setting up the component - const numberOfRequests = 2; - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - - await testBed.actions.contextMenu.clickManageIndexButton(); - await testBed.actions.contextMenu.clickIndexAction('unfreezeIndexMenuButton'); - expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/unfreeze`, { - body: JSON.stringify({ indices: [testIndexName] }), - }); - expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); - }); - }); -}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx new file mode 100644 index 0000000000000..31680f9f860b9 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -0,0 +1,563 @@ +/* + * Copyright 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 { setupEnvironment } from '../helpers'; +import { IndexDetailsPageTestBed, setup } from './index_details_page.helpers'; +import { act } from 'react-dom/test-utils'; +import { + breadcrumbService, + IndexManagementBreadcrumb, +} from '../../../public/application/services/breadcrumbs'; +import { IndexDetailsSection } from '../../../public/application/sections/home/index_list/details_page'; +import { + testIndexEditableSettings, + testIndexMappings, + testIndexMock, + testIndexName, + testIndexSettings, + testIndexStats, +} from './mocks'; +import { API_BASE_PATH, INTERNAL_API_BASE_PATH } from '../../../common'; +import React from 'react'; + +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + // Mocking CodeEditor, which uses React Monaco under the hood + CodeEditor: (props: any) => ( + ) => { + props.onChange(e.currentTarget.getAttribute('data-currentvalue')); + }} + /> + ), + }; +}); + +describe('', () => { + let testBed: IndexDetailsPageTestBed; + let httpSetup: ReturnType['httpSetup']; + let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; + jest.spyOn(breadcrumbService, 'setBreadcrumbs'); + + beforeEach(async () => { + const mockEnvironment = setupEnvironment(); + ({ httpSetup, httpRequestsMockHelpers } = mockEnvironment); + // testIndexName is configured in initialEntries of the memory router + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testIndexMock); + httpRequestsMockHelpers.setLoadIndexStatsResponse(testIndexName, testIndexStats); + httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, testIndexMappings); + httpRequestsMockHelpers.setLoadIndexSettingsResponse(testIndexName, testIndexSettings); + + await act(async () => { + testBed = await setup(httpSetup, { + url: { + locators: { + get: () => ({ navigate: jest.fn() }), + }, + }, + }); + }); + testBed.component.update(); + }); + + describe('error section', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, undefined, { + statusCode: 400, + message: `Data for index ${testIndexName} was not found`, + }); + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + }); + it('displays an error callout when failed to load index details', async () => { + expect(testBed.actions.errorSection.isDisplayed()).toBe(true); + }); + + it('resends a request when reload button is clicked', async () => { + // already sent 2 requests while setting up the component + const numberOfRequests = 2; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + await testBed.actions.errorSection.clickReloadButton(); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + }); + + describe('Stats tab', () => { + it('updates the breadcrumbs to index details stats', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.indexDetailsStats + ); + }); + + it('loads index stats from the API', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); + expect(httpSetup.get).toHaveBeenLastCalledWith(`${API_BASE_PATH}/stats/${testIndexName}`, { + asSystemRequest: undefined, + body: undefined, + query: undefined, + version: undefined, + }); + }); + + it('renders index stats', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); + const tabContent = testBed.actions.stats.getCodeBlockContent(); + expect(tabContent).toEqual(JSON.stringify(testIndexStats, null, 2)); + }); + + it('sets the docs link href from the documenation service', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); + const docsLinkHref = testBed.actions.stats.getDocsLinkHref(); + // the url from the mocked docs mock + expect(docsLinkHref).toEqual( + 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/indices-stats.html' + ); + }); + + it('renders a warning message if an index is not open', async () => { + const testIndexMockWithClosedStatus = { + ...testIndexMock, + status: 'closed', + }; + + httpRequestsMockHelpers.setLoadIndexDetailsResponse( + testIndexName, + testIndexMockWithClosedStatus + ); + + await act(async () => { + testBed = await setup(httpSetup); + }); + testBed.component.update(); + + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); + expect(testBed.actions.stats.isWarningDisplayed()).toBe(true); + }); + + it('hides index stats tab if enableIndexStats===false', async () => { + await act(async () => { + testBed = await setup(httpSetup, { + config: { enableIndexStats: false }, + }); + }); + testBed.component.update(); + + expect(testBed.actions.stats.indexStatsTabExists()).toBe(false); + }); + + describe('Error handling', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndexStatsResponse(testIndexName, undefined, { + statusCode: 500, + message: 'Error', + }); + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Stats); + }); + + it('there is an error prompt', async () => { + expect(testBed.actions.stats.isErrorDisplayed()).toBe(true); + }); + + it('resends a request when reload button is clicked', async () => { + // already sent 3 requests while setting up the component + const numberOfRequests = 3; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + await testBed.actions.stats.clickErrorReloadButton(); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + }); + }); + + it('loads index details from the API', async () => { + expect(httpSetup.get).toHaveBeenLastCalledWith( + `${INTERNAL_API_BASE_PATH}/indices/${testIndexName}`, + { asSystemRequest: undefined, body: undefined, query: undefined, version: undefined } + ); + }); + + it('displays index name in the header', () => { + const header = testBed.actions.getHeader(); + // testIndexName is configured in initialEntries of the memory router + expect(header).toEqual(testIndexName); + }); + + describe('Overview tab', () => { + it('updates the breadcrumbs to index details overview', async () => { + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.indexDetailsOverview + ); + }); + + it('renders index details', () => { + expect(testBed.actions.overview.indexDetailsContentExists()).toBe(true); + expect(testBed.actions.overview.indexStatsContentExists()).toBe(true); + expect(testBed.actions.overview.addDocCodeBlockExists()).toBe(true); + }); + + it('hides index stats from detail panels if enableIndexStats===false', async () => { + await act(async () => { + testBed = await setup(httpSetup, { + config: { enableIndexStats: false }, + }); + }); + testBed.component.update(); + + expect(testBed.actions.overview.indexDetailsContentExists()).toBe(true); + expect(testBed.actions.overview.indexStatsContentExists()).toBe(false); + }); + }); + + it('documents tab', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Documents); + const tabContent = testBed.actions.getActiveTabContent(); + expect(tabContent).toEqual('Documents'); + }); + + describe('Mappings tab', () => { + it('updates the breadcrumbs to index details mappings', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.indexDetailsMappings + ); + }); + it('loads mappings from the API', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + expect(httpSetup.get).toHaveBeenLastCalledWith(`${API_BASE_PATH}/mapping/${testIndexName}`, { + asSystemRequest: undefined, + body: undefined, + query: undefined, + version: undefined, + }); + }); + + it('displays the mappings in the code block', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + + const tabContent = testBed.actions.mappings.getCodeBlockContent(); + expect(tabContent).toEqual(JSON.stringify(testIndexMappings, null, 2)); + }); + + it('sets the docs link href from the documentation service', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + const docsLinkHref = testBed.actions.mappings.getDocsLinkHref(); + // the url from the mocked docs mock + expect(docsLinkHref).toEqual( + 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/mapping.html' + ); + }); + + describe('error loading mappings', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, undefined, { + statusCode: 400, + message: `Was not able to load mappings`, + }); + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + }); + + it('there is an error prompt', async () => { + expect(testBed.actions.mappings.isErrorDisplayed()).toBe(true); + }); + + it('resends a request when reload button is clicked', async () => { + // already sent 3 requests while setting up the component + const numberOfRequests = 3; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + await testBed.actions.mappings.clickErrorReloadButton(); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + }); + }); + + describe('Settings tab', () => { + it('updates the breadcrumbs to index details settings', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.indexDetailsSettings + ); + }); + + it('loads settings from the API', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); + expect(httpSetup.get).toHaveBeenLastCalledWith(`${API_BASE_PATH}/settings/${testIndexName}`, { + asSystemRequest: undefined, + body: undefined, + query: undefined, + version: undefined, + }); + }); + + it('displays the settings in the code block', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); + + const tabContent = testBed.actions.settings.getCodeBlockContent(); + expect(tabContent).toEqual(JSON.stringify(testIndexSettings, null, 2)); + }); + + it('sets the docs link href from the documentation service', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); + const docsLinkHref = testBed.actions.settings.getDocsLinkHref(); + // the url from the mocked docs mock + expect(docsLinkHref).toEqual( + 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/index-modules.html#index-modules-settings' + ); + }); + + describe('error loading settings', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndexSettingsResponse(testIndexName, undefined, { + statusCode: 400, + message: `Was not able to load settings`, + }); + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); + }); + + it('there is an error prompt', async () => { + expect(testBed.actions.settings.isErrorDisplayed()).toBe(true); + }); + + it('resends a request when reload button is clicked', async () => { + // already sent 3 requests while setting up the component + const numberOfRequests = 3; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + await testBed.actions.settings.clickErrorReloadButton(); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + }); + + describe('edit settings', () => { + beforeEach(async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); + await testBed.actions.settings.clickEditModeSwitch(); + }); + + it('displays the editable settings (flattened and filtered)', () => { + const editorContent = testBed.actions.settings.getCodeEditorContent(); + expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettings, null, 2)); + }); + + it('updates the settings', async () => { + const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' }; + await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings)); + await testBed.actions.settings.saveSettings(); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/settings/${testIndexName}`, + { + asSystemRequest: undefined, + body: JSON.stringify({ 'index.priority': '2' }), + query: undefined, + version: undefined, + } + ); + }); + + it('reloads the settings after an update', async () => { + const numberOfRequests = 2; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' }; + await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings)); + await testBed.actions.settings.saveSettings(); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + expect(httpSetup.get).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/settings/${testIndexName}`, + { + asSystemRequest: undefined, + body: undefined, + query: undefined, + version: undefined, + } + ); + }); + + it('resets the changes in the editor', async () => { + const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' }; + await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings)); + await testBed.actions.settings.resetChanges(); + const editorContent = testBed.actions.settings.getCodeEditorContent(); + expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettings, null, 2)); + }); + }); + }); + + it('pipelines tab', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Pipelines); + const tabContent = testBed.actions.getActiveTabContent(); + expect(tabContent).toEqual('Pipelines'); + }); + + it('navigates back to indices', async () => { + jest.spyOn(testBed.routerMock.history, 'push'); + await testBed.actions.clickBackToIndicesButton(); + expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1); + expect(testBed.routerMock.history.push).toHaveBeenCalledWith('/indices'); + }); + + it('renders a link to discover', () => { + // we only need to test that the link is rendered since the link component has its own tests for navigation + expect(testBed.actions.discoverLinkExists()).toBe(true); + }); + + describe('context menu', () => { + it('opens an index context menu when "manage index" button is clicked', async () => { + expect(testBed.actions.contextMenu.isOpened()).toBe(false); + await testBed.actions.contextMenu.clickManageIndexButton(); + expect(testBed.actions.contextMenu.isOpened()).toBe(true); + }); + + it('closes an index', async () => { + // already sent 1 request while setting up the component + const numberOfRequests = 1; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('closeIndexMenuButton'); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/close`, { + body: JSON.stringify({ indices: [testIndexName] }), + }); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + + it('opens an index', async () => { + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, { + ...testIndexMock, + status: 'close', + }); + + await act(async () => { + testBed = await setup(httpSetup); + }); + testBed.component.update(); + + // already sent 2 requests while setting up the component + const numberOfRequests = 2; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('openIndexMenuButton'); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/open`, { + body: JSON.stringify({ indices: [testIndexName] }), + }); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + + it('forcemerges an index', async () => { + // already sent 1 request while setting up the component + const numberOfRequests = 1; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('forcemergeIndexMenuButton'); + await testBed.actions.contextMenu.confirmForcemerge('2'); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/forcemerge`, { + body: JSON.stringify({ indices: [testIndexName], maxNumSegments: '2' }), + }); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + + it('refreshes an index', async () => { + // already sent 1 request while setting up the component + const numberOfRequests = 1; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('refreshIndexMenuButton'); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/refresh`, { + body: JSON.stringify({ indices: [testIndexName] }), + }); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + + it(`clears an index's cache`, async () => { + // already sent 1 request while setting up the component + const numberOfRequests = 1; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('clearCacheIndexMenuButton'); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/clear_cache`, { + body: JSON.stringify({ indices: [testIndexName] }), + }); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + + it(`flushes an index`, async () => { + // already sent 1 request while setting up the component + const numberOfRequests = 1; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('flushIndexMenuButton'); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/flush`, { + body: JSON.stringify({ indices: [testIndexName] }), + }); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + + it(`deletes an index`, async () => { + jest.spyOn(testBed.routerMock.history, 'push'); + // already sent 1 request while setting up the component + const numberOfRequests = 1; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('deleteIndexMenuButton'); + await testBed.actions.contextMenu.confirmDelete(); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/delete`, { + body: JSON.stringify({ indices: [testIndexName] }), + }); + + expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1); + expect(testBed.routerMock.history.push).toHaveBeenCalledWith('/indices'); + }); + + it(`unfreezes a frozen index`, async () => { + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, { + ...testIndexMock, + isFrozen: true, + }); + + await act(async () => { + testBed = await setup(httpSetup); + }); + testBed.component.update(); + + // already sent 1 request while setting up the component + const numberOfRequests = 2; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + + await testBed.actions.contextMenu.clickManageIndexButton(); + await testBed.actions.contextMenu.clickIndexAction('unfreezeIndexMenuButton'); + expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/unfreeze`, { + body: JSON.stringify({ indices: [testIndexName] }), + }); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts index 7666c1ac0dee6..67ff8d57a1eb8 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts @@ -42,6 +42,37 @@ export const testIndexMappings = { }, }; +// Mocking partial index settings response +export const testIndexSettings = { + settings: { + index: { + routing: { + allocation: { + include: { + _tier_preference: 'data_content', + }, + }, + }, + number_of_shards: '1', + }, + }, + defaults: { + index: { + flush_after_merge: '512mb', + max_script_fields: '32', + query: { + default_field: ['*'], + }, + priority: '1', + }, + }, +}; +export const testIndexEditableSettings = { + 'index.priority': '1', + 'index.query.default_field': ['*'], + 'index.routing.allocation.include._tier_preference': 'data_content', +}; + // Mocking partial index stats response export const testIndexStats = { _shards: { diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index 5c27c16946325..78593b40b5eaa 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { DataStream, DataStreamFromEs, Health } from '../types'; +import { DataStream, EnhancedDataStreamFromEs, Health } from '../types'; -export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataStream { +export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs): DataStream { const { name, timestamp_field: timeStampField, @@ -22,6 +22,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS _meta, privileges, hidden, + lifecycle, } = dataStreamFromEs; return { @@ -44,9 +45,12 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS _meta, privileges, hidden, + lifecycle, }; } -export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] { +export function deserializeDataStreamList( + dataStreamsFromEs: EnhancedDataStreamFromEs[] +): DataStream[] { return dataStreamsFromEs.map((dataStream) => deserializeDataStream(dataStream)); } diff --git a/x-pack/plugins/index_management/common/lib/enrich_policies.ts b/x-pack/plugins/index_management/common/lib/enrich_policies.ts new file mode 100644 index 0000000000000..9372578089c84 --- /dev/null +++ b/x-pack/plugins/index_management/common/lib/enrich_policies.ts @@ -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 type { EnrichSummary, EnrichPolicyType } from '@elastic/elasticsearch/lib/api/types'; +import type { SerializedEnrichPolicy } from '../types'; + +export const getPolicyType = (policy: EnrichSummary): EnrichPolicyType => { + if (policy.config.match) { + return 'match'; + } + + if (policy.config.geo_match) { + return 'geo_match'; + } + + if (policy.config.range) { + return 'range'; + } + + throw new Error('Unknown policy type'); +}; + +export const getESPolicyCreationApiCall = (policyName: string) => { + return `PUT _enrich/policy/${policyName}`; +}; + +export const serializeAsESPolicy = (policy: SerializedEnrichPolicy) => { + const policyType = policy.type as EnrichPolicyType; + + return { + [policyType]: { + indices: policy.sourceIndices, + match_field: policy.matchField, + enrich_fields: policy.enrichFields, + query: policy.query, + }, + }; +}; diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts index fbea0a7536bff..095742e3c3675 100644 --- a/x-pack/plugins/index_management/common/lib/index.ts +++ b/x-pack/plugins/index_management/common/lib/index.ts @@ -23,3 +23,5 @@ export { deserializeComponentTemplateList, serializeComponentTemplate, } from './component_template_serialization'; + +export { getPolicyType, serializeAsESPolicy, getESPolicyCreationApiCall } from './enrich_policies'; diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index 7ac08f6684466..88e4bc237e819 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -5,20 +5,20 @@ * 2.0. */ +import { + ByteSize, + IndicesDataLifecycleWithRollover, + IndicesDataStream, + IndicesDataStreamsStatsDataStreamsStatsItem, + Metadata, +} from '@elastic/elasticsearch/lib/api/types'; + interface TimestampFieldFromEs { name: string; } type TimestampField = TimestampFieldFromEs; -interface MetaFromEs { - managed_by: string; - package: any; - managed: boolean; -} - -type Meta = MetaFromEs; - interface PrivilegesFromEs { delete_index: boolean; } @@ -27,20 +27,13 @@ type Privileges = PrivilegesFromEs; export type HealthFromEs = 'GREEN' | 'YELLOW' | 'RED'; -export interface DataStreamFromEs { - name: string; - timestamp_field: TimestampFieldFromEs; - indices: DataStreamIndexFromEs[]; - generation: number; - _meta?: MetaFromEs; - status: HealthFromEs; - template: string; - ilm_policy?: string; - store_size?: string; - store_size_bytes?: number; - maximum_timestamp?: number; - privileges: PrivilegesFromEs; - hidden: boolean; +export interface EnhancedDataStreamFromEs extends IndicesDataStream { + store_size?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size']; + store_size_bytes?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size_bytes']; + maximum_timestamp?: IndicesDataStreamsStatsDataStreamsStatsItem['maximum_timestamp']; + privileges: { + delete_index: boolean; + }; } export interface DataStreamIndexFromEs { @@ -58,12 +51,13 @@ export interface DataStream { health: Health; indexTemplateName: string; ilmPolicyName?: string; - storageSize?: string; + storageSize?: ByteSize; storageSizeBytes?: number; maxTimeStamp?: number; - _meta?: Meta; + _meta?: Metadata; privileges: Privileges; hidden: boolean; + lifecycle?: IndicesDataLifecycleWithRollover; } export interface DataStreamIndex { diff --git a/x-pack/plugins/index_management/common/types/enrich_policies.ts b/x-pack/plugins/index_management/common/types/enrich_policies.ts index 4688cb41135f1..52bab8e6c0f18 100644 --- a/x-pack/plugins/index_management/common/types/enrich_policies.ts +++ b/x-pack/plugins/index_management/common/types/enrich_policies.ts @@ -13,4 +13,21 @@ export interface SerializedEnrichPolicy { sourceIndices: string[]; matchField: string; enrichFields: string[]; + query?: Record; +} + +export interface FieldItem { + name: string; + type: string; + normalizedType: string; +} + +export interface IndexWithFields { + index: string; + fields: FieldItem[]; +} + +export interface FieldFromIndicesRequest { + commonFields: FieldItem[]; + indices: IndexWithFields[]; } diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index ce5d96a842366..f3f2315cdd15b 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -13,7 +13,7 @@ export * from './mappings'; export * from './templates'; -export type { DataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; +export type { EnhancedDataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; export * from './component_templates'; diff --git a/x-pack/plugins/index_management/common/types/indices.ts b/x-pack/plugins/index_management/common/types/indices.ts index 608ee392a3f9e..ab6080f30fdb2 100644 --- a/x-pack/plugins/index_management/common/types/indices.ts +++ b/x-pack/plugins/index_management/common/types/indices.ts @@ -83,3 +83,8 @@ export interface Index { primary_size?: string; documents_deleted?: number; } + +export interface IndexSettingsResponse { + settings: IndexSettings; + defaults: IndexSettings; +} diff --git a/x-pack/plugins/index_management/kibana.jsonc b/x-pack/plugins/index_management/kibana.jsonc index 47171db66450d..250fde111d29b 100644 --- a/x-pack/plugins/index_management/kibana.jsonc +++ b/x-pack/plugins/index_management/kibana.jsonc @@ -19,7 +19,8 @@ "optionalPlugins": [ "security", "usageCollection", - "fleet" + "fleet", + "cloud" ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/index_management/public/application/app.tsx b/x-pack/plugins/index_management/public/application/app.tsx index 66a6c45cc4a35..163af333e7247 100644 --- a/x-pack/plugins/index_management/public/application/app.tsx +++ b/x-pack/plugins/index_management/public/application/app.tsx @@ -17,6 +17,7 @@ import { IndexManagementHome, homeSections, Section } from './sections/home'; import { TemplateCreate } from './sections/template_create'; import { TemplateClone } from './sections/template_clone'; import { TemplateEdit } from './sections/template_edit'; +import { EnrichPolicyCreate } from './sections/enrich_policy_create'; import { useAppContext } from './app_context'; import { ComponentTemplateCreate, @@ -51,6 +52,7 @@ export const AppWithoutRouter = () => ( component={ComponentTemplateClone} /> + diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index fcedba9a1b941..f23baeb342c00 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -18,10 +18,12 @@ import { DocLinksStart, IUiSettingsClient, ExecutionContextStart, + HttpSetup, } from '@kbn/core/public'; -import { SharePluginStart } from '@kbn/share-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SettingsStart } from '@kbn/core-ui-settings-browser'; +import type { CloudSetup } from '@kbn/cloud-plugin/public'; import { ExtensionsService } from '../services'; import { UiMetricService, NotificationService, HttpService } from './services'; @@ -33,10 +35,13 @@ export interface AppDependencies { getUrlForApp: ApplicationStart['getUrlForApp']; executionContext: ExecutionContextStart; application: ApplicationStart; + http: HttpSetup; }; plugins: { usageCollection: UsageCollectionSetup; isFleetEnabled: boolean; + share: SharePluginStart; + cloud?: CloudSetup; }; services: { uiMetricService: UiMetricService; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx index 8309db0699fc6..5f7dac7431d93 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx @@ -10,6 +10,7 @@ import { act } from 'react-dom/test-utils'; import '@kbn/es-ui-shared-plugin/public/components/code_editor/jest_mock'; import '../../../../../../test/global_mocks'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; import { setupEnvironment } from './helpers'; import { API_BASE_PATH } from './helpers/constants'; import { setup, ComponentTemplateCreateTestBed } from './helpers/component_template_create.helpers'; @@ -53,6 +54,7 @@ describe('', () => { let testBed: ComponentTemplateCreateTestBed; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); + jest.spyOn(breadcrumbService, 'setBreadcrumbs'); describe('On component mount', () => { beforeEach(async () => { @@ -63,6 +65,12 @@ describe('', () => { testBed.component.update(); }); + test('updates the breadcrumbs to component templates', () => { + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.componentTemplateCreate + ); + }); + test('should set the correct page header', async () => { const { exists, find } = testBed; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx index cc93a08ed8e89..68c6cfc290c77 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import '../../../../../../test/global_mocks'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; import { setupEnvironment } from './helpers'; import { API_BASE_PATH } from './helpers/constants'; import { setup, ComponentTemplateEditTestBed } from './helpers/component_template_edit.helpers'; @@ -69,6 +70,7 @@ describe('', () => { let testBed: ComponentTemplateEditTestBed; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); + jest.spyOn(breadcrumbService, 'setBreadcrumbs'); const COMPONENT_TEMPLATE_NAME = 'comp-1'; const COMPONENT_TEMPLATE_TO_EDIT = { @@ -95,6 +97,12 @@ describe('', () => { testBed.component.update(); }); + test('updates the breadcrumbs to component templates', () => { + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.componentTemplateEdit + ); + }); + test('should set the correct page title', () => { const { exists, find } = testBed; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts index ae40fbc9009a7..d0325dfb4a177 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts @@ -7,6 +7,7 @@ import { act } from 'react-dom/test-utils'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; import { ComponentTemplateListItem } from '../../shared_imports'; import { setupEnvironment, pageHelpers } from './helpers'; @@ -18,6 +19,7 @@ const { setup } = pageHelpers.componentTemplateList; describe('', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); let testBed: ComponentTemplateListTestBed; + jest.spyOn(breadcrumbService, 'setBreadcrumbs'); beforeEach(async () => { await act(async () => { @@ -27,6 +29,12 @@ describe('', () => { testBed.component.update(); }); + test('updates the breadcrumbs to component templates', () => { + expect(breadcrumbService.setBreadcrumbs).toHaveBeenLastCalledWith( + IndexManagementBreadcrumb.componentTemplates + ); + }); + describe('With component templates', () => { const componentTemplate1: ComponentTemplateListItem = { name: 'test_component_template_1', diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx index 25165fe782c99..526ed9d653d6a 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx @@ -13,6 +13,7 @@ import { executionContextServiceMock } from '@kbn/core-execution-context-browser import { notificationServiceMock, applicationServiceMock, coreMock } from '@kbn/core/public/mocks'; import { GlobalFlyout } from '@kbn/es-ui-shared-plugin/public'; +import { breadcrumbService } from '../../../../../services/breadcrumbs'; import { AppContextProvider } from '../../../../../app_context'; import { MappingsEditorProvider } from '../../../../mappings_editor'; import { ComponentTemplatesProvider } from '../../../component_templates_context'; @@ -34,12 +35,14 @@ export const componentTemplatesDependencies = (httpSetup: HttpSetup, coreStart?: trackMetric: () => {}, docLinks: docLinksServiceMock.createStartContract(), toasts: notificationServiceMock.createSetupContract().toasts, - setBreadcrumbs: () => {}, getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp, executionContext: executionContextServiceMock.createInternalStartContract(), }); -export const setupEnvironment = initHttpRequests; +export const setupEnvironment = () => { + breadcrumbService.setup(() => undefined); + return initHttpRequests(); +}; export const WithAppDependencies = (Comp: any, httpSetup: HttpSetup, coreStart?: CoreStart) => (props: any) => diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx index bced972e1bf9c..fc309751bfe8d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx @@ -13,6 +13,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { ScopedHistory } from '@kbn/core/public'; import { EuiLink, EuiText, EuiSpacer } from '@elastic/eui'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../services/breadcrumbs'; import { APP_WRAPPER_CLASS, PageLoading, @@ -48,6 +49,10 @@ export const ComponentTemplateList: React.FunctionComponent = ({ const { api, trackMetric, documentation } = useComponentTemplatesContext(); const redirectTo = useRedirectPath(history); + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.componentTemplates); + }, []); + const { data, isLoading, error, resendRequest } = api.useLoadComponentTemplates(); const [componentTemplatesToDelete, setComponentTemplatesToDelete] = useState([]); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx index 548577c486aca..cac84c44f51c6 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx @@ -10,6 +10,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPageSection, EuiSpacer, EuiPageHeader } from '@elastic/eui'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; import { ComponentTemplateDeserialized } from '../../shared_imports'; import { useComponentTemplatesContext } from '../../component_templates_context'; import { ComponentTemplateForm } from '../component_template_form'; @@ -28,7 +29,7 @@ export const ComponentTemplateCreate: React.FunctionComponent(false); const [saveError, setSaveError] = useState(null); - const { api, breadcrumbs } = useComponentTemplatesContext(); + const { api } = useComponentTemplatesContext(); const onSave = async (componentTemplate: ComponentTemplateDeserialized) => { const { name } = componentTemplate; @@ -54,19 +55,31 @@ export const ComponentTemplateCreate: React.FunctionComponent { - breadcrumbs.setCreateBreadcrumbs(); - }, [breadcrumbs]); + if (isCloning) { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.componentTemplateClone); + } else { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.componentTemplateCreate); + } + }, [isCloning]); return ( - + {isCloning ? ( + + ) : ( + + )} } bottomBorder diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx index 8fa4694ee033a..fbbb6167202f0 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx @@ -12,6 +12,7 @@ import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { EuiPageSection, EuiPageHeader, EuiSpacer } from '@elastic/eui'; import { History } from 'history'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; import { useComponentTemplatesContext } from '../../component_templates_context'; import { ComponentTemplateDeserialized, @@ -61,7 +62,7 @@ export const ComponentTemplateEdit: React.FunctionComponent { - const { api, breadcrumbs, overlays } = useComponentTemplatesContext(); + const { api, overlays } = useComponentTemplatesContext(); const { activeStep: defaultActiveStep, updateStep } = useStepFromQueryString(history); const redirectTo = useRedirectPath(history); @@ -75,8 +76,8 @@ export const ComponentTemplateEdit: React.FunctionComponent dataStreamResponse?.data_streams ?? [], [dataStreamResponse]); useEffect(() => { - breadcrumbs.setEditBreadcrumbs(); - }, [breadcrumbs]); + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.componentTemplateEdit); + }, []); const onSave = async (updatedComponentTemplate: ComponentTemplateDeserialized) => { setIsSaving(true); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx index 3f9380bf8e72d..ade11c3a1b600 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx @@ -15,8 +15,7 @@ import { CoreStart, ExecutionContextStart, } from '@kbn/core/public'; -import { ManagementAppMountParams } from '@kbn/management-plugin/public'; -import { getApi, getUseRequest, getSendRequest, getDocumentation, getBreadcrumbs } from './lib'; +import { getApi, getUseRequest, getSendRequest, getDocumentation } from './lib'; const ComponentTemplatesContext = createContext(undefined); @@ -26,7 +25,6 @@ interface Props { trackMetric: (type: UiCounterMetricType, eventName: string) => void; docLinks: DocLinksStart; toasts: NotificationsSetup['toasts']; - setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; getUrlForApp: CoreStart['application']['getUrlForApp']; executionContext: ExecutionContextStart; overlays: CoreStart['overlays']; @@ -37,7 +35,6 @@ interface Context { apiBasePath: string; api: ReturnType; documentation: ReturnType; - breadcrumbs: ReturnType; trackMetric: (type: UiCounterMetricType, eventName: string) => void; toasts: NotificationsSetup['toasts']; overlays: CoreStart['overlays']; @@ -59,7 +56,6 @@ export const ComponentTemplatesProvider = ({ trackMetric, docLinks, toasts, - setBreadcrumbs, getUrlForApp, executionContext, } = value; @@ -69,7 +65,6 @@ export const ComponentTemplatesProvider = ({ const api = getApi(useRequest, sendRequest, apiBasePath, trackMetric); const documentation = getDocumentation(docLinks); - const breadcrumbs = getBreadcrumbs(setBreadcrumbs); return ( { - const baseBreadcrumbs = [ - { - text: i18n.translate('xpack.idxMgmt.componentTemplate.breadcrumb.homeLabel', { - defaultMessage: 'Index Management', - }), - href: '/', - }, - { - text: i18n.translate('xpack.idxMgmt.componentTemplate.breadcrumb.componentTemplatesLabel', { - defaultMessage: 'Component templates', - }), - href: '/component_templates', - }, - ]; - - const setCreateBreadcrumbs = () => { - const createBreadcrumbs = [ - ...baseBreadcrumbs, - { - text: i18n.translate( - 'xpack.idxMgmt.componentTemplate.breadcrumb.createComponentTemplateLabel', - { - defaultMessage: 'Create component template', - } - ), - }, - ]; - - return setBreadcrumbs(createBreadcrumbs); - }; - - const setEditBreadcrumbs = () => { - const editBreadcrumbs = [ - ...baseBreadcrumbs, - { - text: i18n.translate( - 'xpack.idxMgmt.componentTemplate.breadcrumb.editComponentTemplateLabel', - { - defaultMessage: 'Edit component template', - } - ), - }, - ]; - - return setBreadcrumbs(editBreadcrumbs); - }; - - return { - setCreateBreadcrumbs, - setEditBreadcrumbs, - }; -}; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/index.ts index 0d105a017d470..ed7dfd31806b8 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/lib/index.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/index.ts @@ -10,5 +10,3 @@ export * from './api'; export * from './request'; export * from './documentation'; - -export * from './breadcrumbs'; diff --git a/x-pack/plugins/index_management/public/application/lib/__snapshots__/flatten_object.test.js.snap b/x-pack/plugins/index_management/public/application/lib/__snapshots__/flatten_object.test.ts.snap similarity index 100% rename from x-pack/plugins/index_management/public/application/lib/__snapshots__/flatten_object.test.js.snap rename to x-pack/plugins/index_management/public/application/lib/__snapshots__/flatten_object.test.ts.snap diff --git a/x-pack/plugins/index_management/public/application/lib/edit_settings.js b/x-pack/plugins/index_management/public/application/lib/edit_settings.ts similarity index 100% rename from x-pack/plugins/index_management/public/application/lib/edit_settings.js rename to x-pack/plugins/index_management/public/application/lib/edit_settings.ts diff --git a/x-pack/plugins/index_management/public/application/lib/flatten_object.js b/x-pack/plugins/index_management/public/application/lib/flatten_object.js deleted file mode 100644 index ddc6f2851f65f..0000000000000 --- a/x-pack/plugins/index_management/public/application/lib/flatten_object.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import _ from 'lodash'; - -export const flattenObject = (nestedObj, flattenArrays) => { - const stack = []; // track key stack - const flatObj = {}; - const dot = '.'; - (function flattenObj(obj) { - _.keys(obj).forEach(function (key) { - stack.push(key); - if (!flattenArrays && Array.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; - else if (_.isObject(obj[key])) flattenObj(obj[key]); - else flatObj[stack.join(dot)] = obj[key]; - stack.pop(); - }); - })(nestedObj); - return flatObj; -}; diff --git a/x-pack/plugins/index_management/public/application/lib/flatten_object.test.js b/x-pack/plugins/index_management/public/application/lib/flatten_object.test.ts similarity index 100% rename from x-pack/plugins/index_management/public/application/lib/flatten_object.test.js rename to x-pack/plugins/index_management/public/application/lib/flatten_object.test.ts diff --git a/x-pack/plugins/index_management/public/application/lib/flatten_object.ts b/x-pack/plugins/index_management/public/application/lib/flatten_object.ts new file mode 100644 index 0000000000000..5dae932938a3a --- /dev/null +++ b/x-pack/plugins/index_management/public/application/lib/flatten_object.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; + +export const flattenObject = (nestedObj: any) => { + const stack = [] as any[]; // track key stack + const flatObj = {} as any; + const dot = '.'; + (function flattenObj(obj) { + _.keys(obj).forEach(function (key) { + stack.push(key); + if (Array.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; + else if (_.isObject(obj[key])) flattenObj(obj[key]); + else flatObj[stack.join(dot)] = obj[key]; + stack.pop(); + }); + })(nestedObj); + return flatObj; +}; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index df723e21ae287..10caf3bcc5a57 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -11,6 +11,7 @@ import { CoreSetup, CoreStart } from '@kbn/core/public'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { CloudSetup } from '@kbn/cloud-plugin/public'; import { UIM_APP_NAME } from '../../common/constants'; import { PLUGIN } from '../../common/constants/plugin'; import { ExtensionsService } from '../services'; @@ -57,6 +58,7 @@ export async function mountManagementSection({ enableLegacyTemplates = true, enableIndexDetailsPage = false, enableIndexStats = true, + cloud, }: { coreSetup: CoreSetup; usageCollection: UsageCollectionSetup; @@ -68,6 +70,7 @@ export async function mountManagementSection({ enableLegacyTemplates?: boolean; enableIndexDetailsPage?: boolean; enableIndexStats?: boolean; + cloud?: CloudSetup; }) { const { element, setBreadcrumbs, history, theme$ } = params; const [core, startDependencies] = await coreSetup.getStartServices(); @@ -79,6 +82,7 @@ export async function mountManagementSection({ uiSettings, executionContext, settings, + http, } = core; const { url } = startDependencies.share; @@ -98,10 +102,13 @@ export async function mountManagementSection({ getUrlForApp: application.getUrlForApp, executionContext, application, + http, }, plugins: { usageCollection, isFleetEnabled, + share: startDependencies.share, + cloud, }, services: { httpService, diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/create_policy_context.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/create_policy_context.tsx new file mode 100644 index 0000000000000..a84d503758c6a --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/create_policy_context.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext, useState } from 'react'; +import type { SerializedEnrichPolicy } from '../../../../common'; + +export type DraftPolicy = Partial; + +export interface CompletionState { + configurationStep: boolean; + fieldsSelectionStep: boolean; +} + +export interface Context { + draft: DraftPolicy; + updateDraft: React.Dispatch>; + completionState: CompletionState; + updateCompletionState: React.Dispatch>; +} + +export const CreatePolicyContext = createContext({} as any); + +export const CreatePolicyContextProvider = ({ children }: { children: React.ReactNode }) => { + const [draft, updateDraft] = useState({}); + const [completionState, updateCompletionState] = useState({ + configurationStep: false, + fieldsSelectionStep: false, + }); + + const contextValue = { + draft, + updateDraft, + completionState, + updateCompletionState, + }; + + return ( + {children} + ); +}; + +export const useCreatePolicyContext = () => { + const ctx = useContext(CreatePolicyContext); + if (!ctx) throw new Error('Cannot use outside of create policy context'); + + return ctx; +}; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/create_policy_wizard.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/create_policy_wizard.tsx new file mode 100644 index 0000000000000..fc5aa6f667a6f --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/create_policy_wizard.tsx @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useMemo, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiSteps, EuiStepStatus, EuiCallOut, EuiSpacer } from '@elastic/eui'; + +import { useAppContext } from '../../app_context'; +import { ConfigurationStep, FieldSelectionStep, CreateStep } from './steps'; +import { useCreatePolicyContext } from './create_policy_context'; +import { createEnrichPolicy } from '../../services/api'; +import type { Error } from '../../../shared_imports'; +import type { SerializedEnrichPolicy } from '../../../../common'; + +const CONFIGURATION = 1; +const FIELD_SELECTION = 2; +const CREATE = 3; + +export const CreatePolicyWizard = () => { + const { + history, + services: { notificationService }, + } = useAppContext(); + const { draft, completionState } = useCreatePolicyContext(); + const [currentStep, setCurrentStep] = useState(CONFIGURATION); + const [isLoading, setIsLoading] = useState(false); + const [createError, setCreateError] = useState(null); + + const getStepStatus = useCallback( + (forStep: number): EuiStepStatus => { + if (currentStep === forStep) { + return 'current'; + } + + return 'incomplete'; + }, + [currentStep] + ); + + const onSubmit = useCallback( + async (executePolicyAfterCreation?: boolean) => { + setIsLoading(true); + const { error, data } = await createEnrichPolicy( + draft as SerializedEnrichPolicy, + executePolicyAfterCreation + ); + setIsLoading(false); + + if (data) { + const toastMessage = executePolicyAfterCreation + ? i18n.translate( + 'xpack.idxMgmt.enrichPoliciesCreate.createAndExecuteNotificationMessage', + { + defaultMessage: 'Created and executed policy: {policyName}', + values: { policyName: draft.name }, + } + ) + : i18n.translate('xpack.idxMgmt.enrichPoliciesCreate.createNotificationMessage', { + defaultMessage: 'Created policy: {policyName}', + values: { policyName: draft.name }, + }); + + notificationService.showSuccessToast(toastMessage); + } + + if (error) { + setCreateError(error); + return; + } + + history.push('/enrich_policies'); + }, + [draft, history, setIsLoading, setCreateError, notificationService] + ); + + const changeCurrentStepTo = useCallback( + (step: number) => { + setCurrentStep(step); + setCreateError(null); + }, + [setCurrentStep, setCreateError] + ); + + const stepDefinitions = useMemo( + () => [ + { + step: CONFIGURATION, + title: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStepLabel', { + defaultMessage: 'Configuration', + }), + status: completionState.configurationStep ? 'complete' : getStepStatus(CONFIGURATION), + children: currentStep === CONFIGURATION && ( + changeCurrentStepTo(FIELD_SELECTION)} /> + ), + }, + { + step: FIELD_SELECTION, + title: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStepLabel', { + defaultMessage: 'Field selection', + }), + status: completionState.fieldsSelectionStep ? 'complete' : getStepStatus(FIELD_SELECTION), + children: currentStep === FIELD_SELECTION && ( + changeCurrentStepTo(CREATE)} + onBack={() => changeCurrentStepTo(CONFIGURATION)} + /> + ), + }, + { + step: CREATE, + title: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStepLabel', { + defaultMessage: 'Create', + }), + status: (currentStep === CREATE ? 'current' : 'incomplete') as EuiStepStatus, + children: currentStep === CREATE && ( + changeCurrentStepTo(FIELD_SELECTION)} + /> + ), + }, + ], + [currentStep, changeCurrentStepTo, completionState, getStepStatus, isLoading, onSubmit] + ); + + return ( + <> + {createError && ( + <> + +

    {createError?.message || createError?.error}

    +
    + + + )} + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/enrich_policy_create.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/enrich_policy_create.tsx new file mode 100644 index 0000000000000..321dee68ae8aa --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/enrich_policy_create.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 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, { useEffect } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButtonEmpty, EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { documentationService } from '../../services/documentation'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../services/breadcrumbs'; + +import { CreatePolicyWizard } from './create_policy_wizard'; +import { CreatePolicyContextProvider } from './create_policy_context'; + +export const EnrichPolicyCreate: React.FunctionComponent = () => { + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.enrichPoliciesCreate); + }, []); + + return ( + + + + + } + description={ + + } + bottomBorder + rightSideItems={[ + + + , + ]} + /> + + + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/index.ts b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/index.ts new file mode 100644 index 0000000000000..475396c04dff9 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { EnrichPolicyCreate } from './enrich_policy_create'; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/configuration.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/configuration.tsx new file mode 100644 index 0000000000000..b7e0f0fcd2129 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/configuration.tsx @@ -0,0 +1,352 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { omit, isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiIcon, + EuiCode, + EuiButton, + EuiText, + EuiLink, + EuiSpacer, +} from '@elastic/eui'; +import { + useForm, + Form, + fieldValidators, + FormSchema, + FIELD_TYPES, + UseField, + TextField, + SelectField, + JsonEditorField, +} from '../../../../shared_imports'; + +import { useAppContext } from '../../../app_context'; +import { IndicesSelector } from './fields/indices_selector'; +import { documentationService } from '../../../services/documentation'; +import { useCreatePolicyContext, DraftPolicy } from '../create_policy_context'; + +interface Props { + onNext: () => void; +} + +export const configurationFormSchema: FormSchema = { + name: { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.policyNameField', { + defaultMessage: 'Policy name', + }), + validations: [ + { + validator: fieldValidators.emptyField( + i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.configurationStep.policyNameRequiredError', + { + defaultMessage: 'A policy name value is required.', + } + ) + ), + }, + ], + }, + + type: { + type: FIELD_TYPES.SELECT, + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.policyTypeLabel', { + defaultMessage: 'Policy type', + }), + validations: [ + { + validator: fieldValidators.emptyField( + i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.typeRequiredError', { + defaultMessage: 'A policy type value is required.', + }) + ), + }, + ], + }, + + sourceIndices: { + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesLabel', { + defaultMessage: 'Source indices', + }), + validations: [ + { + validator: fieldValidators.emptyField( + i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesRequiredError', + { + defaultMessage: 'At least one source index is required.', + } + ) + ), + }, + ], + }, + + query: { + type: FIELD_TYPES.JSON, + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryLabel', { + defaultMessage: 'Query (optional)', + }), + serializer: (jsonString: string) => { + let parsedJSON: any; + try { + parsedJSON = JSON.parse(jsonString); + } catch { + parsedJSON = {}; + } + + return parsedJSON; + }, + deserializer: (json: any) => + json && typeof json === 'object' ? JSON.stringify(json, null, 2) : '{\n\n}', + validations: [ + { + validator: fieldValidators.isJsonField( + i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryInvalidError', { + defaultMessage: 'The query is not valid JSON.', + }), + { allowEmptyString: true } + ), + }, + ], + }, +}; + +export const ConfigurationStep = ({ onNext }: Props) => { + const { + core: { + application: { getUrlForApp }, + }, + } = useAppContext(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const { draft, updateDraft, updateCompletionState } = useCreatePolicyContext(); + + const { form } = useForm({ + defaultValue: draft, + schema: configurationFormSchema, + id: 'configurationForm', + }); + + const onSubmit = async () => { + const { isValid, data } = await form.submit(); + + if (!isValid) { + return; + } + + // Update form completion state + updateCompletionState((prevCompletionState) => ({ + ...prevCompletionState, + configurationStep: true, + })); + + // Update draft state with the data of the form + updateDraft((prevDraft: DraftPolicy) => ({ + ...prevDraft, + ...(isEmpty(data.query) ? omit(data, 'query') : data), + })); + + // And then navigate to the next step + onNext(); + }; + + return ( + + + + setIsPopoverOpen((isOpen) => !isOpen)} + > + + + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + > + +

    + +

    +
      +
    • + Match }} + /> +
    • +
    • + Geo match }} + /> +
    • +
    • + Range }} + /> +
    • +
    +
    + + } + componentProps={{ + fullWidth: false, + euiFieldProps: { + 'data-test-subj': 'policyTypeField', + options: [ + { + value: 'match', + text: i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.configurationStep.matchOption', + { defaultMessage: 'Match' } + ), + }, + { + value: 'geo_match', + text: i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.configurationStep.geoMatchOption', + { defaultMessage: 'Geo match' } + ), + }, + { + value: 'range', + text: i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeOption', + { defaultMessage: 'Range' } + ), + }, + ], + }, + }} + /> + + + + + + + } + componentProps={{ + euiFieldProps: { + 'data-test-subj': 'policySourceIndicesField', + }, + fullWidth: false, + }} + /> + + + + + ), + }} + /> + ), + codeEditorProps: { + height: '300px', + allowFullScreen: true, + 'aria-label': i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.configurationStep.ariaLabelQuery', + { + defaultMessage: 'Query field data editor', + } + ), + options: { + lineNumbers: 'off', + tabSize: 2, + automaticLayout: true, + }, + }, + }} + /> + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/create.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/create.tsx new file mode 100644 index 0000000000000..5d50e0cc3edbe --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/create.tsx @@ -0,0 +1,266 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiButton, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiTabbedContent, + EuiSpacer, + EuiCodeBlock, +} from '@elastic/eui'; + +import type { SerializedEnrichPolicy } from '../../../../../common'; +import { useCreatePolicyContext } from '../create_policy_context'; +import { serializeAsESPolicy, getESPolicyCreationApiCall } from '../../../../../common/lib'; + +// Beyond a certain point, highlighting the syntax will bog down performance to unacceptable +// levels. This way we prevent that happening for very large requests. +const getLanguageForQuery = (query: string) => (query.length < 60000 ? 'json' : undefined); + +const SummaryTab = ({ policy }: { policy: SerializedEnrichPolicy }) => { + const queryAsString = policy.query ? JSON.stringify(policy.query, null, 2) : ''; + const language = getLanguageForQuery(queryAsString); + + return ( + <> + + + + {/* Policy name */} + {policy.name && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.nameLabel', { + defaultMessage: 'Name', + })} + + + {policy.name} + + + )} + + {/* Policy type */} + {policy.type && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.typeLabel', { + defaultMessage: 'Type', + })} + + + {policy.type} + + + )} + + {/* Policy source indices */} + {policy.sourceIndices && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.sourceIndicesLabel', { + defaultMessage: 'Source indices', + })} + + + +
      + {policy.sourceIndices.map((index: string) => ( +
    • + {index} +
    • + ))} +
    +
    +
    + + )} + + {/* Policy match field */} + {policy.matchField && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.matchFieldLabel', { + defaultMessage: 'Match field', + })} + + + {policy.matchField} + + + )} + + {/* Policy enrich fields */} + {policy.enrichFields && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.enrichFieldsLabel', { + defaultMessage: 'Enrich fields', + })} + + + +
      + {policy.enrichFields.map((field: string) => ( +
    • + {field} +
    • + ))} +
    +
    +
    + + )} + + {/* Policy query */} + {policy.query && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.queryLabel', { + defaultMessage: 'Query', + })} + + + + {queryAsString} + + + + )} +
    + + ); +}; + +const RequestTab = ({ policy }: { policy: SerializedEnrichPolicy }) => { + const request = JSON.stringify(serializeAsESPolicy(policy), null, 2); + const language = getLanguageForQuery(request); + + return ( + <> + + + +

    + +

    +
    + + + + + {getESPolicyCreationApiCall(policy.name)} + {`\n`} + {request} + + + ); +}; + +const CREATE_AND_EXECUTE_POLICY = true; +interface Props { + onSubmit: (executePolicyAfterCreation?: boolean) => void; + onBack: () => void; + isLoading: boolean; +} + +export const CreateStep = ({ onBack, onSubmit, isLoading }: Props) => { + const { draft } = useCreatePolicyContext(); + + const summaryTabs = [ + { + id: 'summary', + name: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.summaryTabLabel', { + defaultMessage: 'Summary', + }), + 'data-test-subj': 'summaryTab', + content: , + }, + { + id: 'request', + name: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.createStep.requestTabLabel', { + defaultMessage: 'Request', + }), + 'data-test-subj': 'requestTab', + content: , + }, + ]; + + return ( + <> + + + + + + + + + + + + + + + + onSubmit(CREATE_AND_EXECUTE_POLICY)} + isDisabled={isLoading} + data-test-subj="createAndExecuteButton" + > + + + + + + onSubmit()} + isDisabled={isLoading} + data-test-subj="createButton" + > + + + + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/field_selection.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/field_selection.tsx new file mode 100644 index 0000000000000..788ab0a695139 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/field_selection.tsx @@ -0,0 +1,288 @@ +/* + * Copyright 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, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiIconTip, + EuiSpacer, + EuiComboBoxOptionOption, + EuiCallOut, +} from '@elastic/eui'; +import { FieldIcon as KbnFieldIcon } from '@kbn/react-field'; +import { + useForm, + Form, + fieldValidators, + FormSchema, + UseField, + FIELD_TYPES, + ComboBoxField, +} from '../../../../shared_imports'; + +import type { IndexWithFields, FieldItem } from '../../../../../common'; +import { getFieldsFromIndices } from '../../../services/api'; +import { useCreatePolicyContext, DraftPolicy } from '../create_policy_context'; + +interface Props { + onNext: () => void; + onBack: () => void; +} + +export const fieldSelectionFormSchema: FormSchema = { + matchField: { + // Since this ComboBoxField is not a multi-select, we need to serialize/deserialize the value + // into a string to be able to save it in the policy. + defaultValue: '', + type: FIELD_TYPES.COMBO_BOX, + serializer: (v: string[]) => { + return v.join(', '); + }, + deserializer: (v: string) => { + return v.length === 0 ? [] : v.split(', '); + }, + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStep.matchFieldField', { + defaultMessage: 'Match field', + }), + validations: [ + { + validator: fieldValidators.emptyField( + i18n.translate('xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStep.matchFieldRequired', { + defaultMessage: 'A match field is required.', + }) + ), + }, + ], + }, + + enrichFields: { + defaultValue: [], + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStep.enrichFieldsField', { + defaultMessage: 'Enrich fields', + }), + validations: [ + { + validator: fieldValidators.emptyField( + i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStep.enrichFieldsRequired', + { + defaultMessage: 'At least one enrich field is required.', + } + ) + ), + }, + ], + }, +}; + +const buildFieldOption = (field: FieldItem) => ({ + label: field.name, + prepend: , +}); + +export const FieldSelectionStep = ({ onBack, onNext }: Props) => { + const [isLoading, setIsLoading] = useState(false); + const [fieldOptions, setFieldOptions] = useState([]); + const [matchFieldOptions, setMatchFieldOptions] = useState([]); + const { draft, updateDraft, updateCompletionState } = useCreatePolicyContext(); + + useEffect(() => { + const fetchFields = async () => { + setIsLoading(true); + const { data } = await getFieldsFromIndices(draft.sourceIndices as string[]); + setIsLoading(false); + + if (data?.commonFields?.length) { + setMatchFieldOptions(data.commonFields.map(buildFieldOption)); + // If there is only one index, we can use the fields of that index as match field options + } else if (data?.indices?.length === 1) { + setMatchFieldOptions(data.indices[0].fields.map(buildFieldOption)); + } + + if (data?.indices?.length) { + setFieldOptions( + data.indices.map((index: IndexWithFields) => ({ + label: index.index, + options: index.fields.map(buildFieldOption), + })) + ); + } + }; + + fetchFields(); + }, [draft.sourceIndices]); + + const { form } = useForm({ + defaultValue: draft, + schema: fieldSelectionFormSchema, + id: 'fieldSelectionForm', + }); + + const onSubmit = async () => { + const { isValid, data } = await form.submit(); + + if (!isValid) { + return; + } + + // Update form completion state + updateCompletionState((prevCompletionState) => ({ + ...prevCompletionState, + fieldsSelectionStep: true, + })); + + // Update draft state with the data of the form + updateDraft((prevDraft: DraftPolicy) => ({ + ...prevDraft, + ...data, + })); + + // And then navigate to the next step + onNext(); + }; + + const hasSelectedMultipleIndices = (draft.sourceIndices?.length ?? 0) > 1; + + return ( +
    + {!isLoading && hasSelectedMultipleIndices && matchFieldOptions.length === 0 && ( + <> + + + + + + + )} + + } + componentProps={{ + fullWidth: false, + helpText: hasSelectedMultipleIndices + ? i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStep.matchFieldHelpText', + { + defaultMessage: + 'The match field must be the same across all selected source indices.', + } + ) + : undefined, + euiFieldProps: { + 'data-test-subj': 'matchField', + placeholder: i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStep.matchFieldPlaceholder', + { + defaultMessage: 'Select a field', + } + ), + noSuggestions: false, + singleSelection: { asPlainText: true }, + options: matchFieldOptions, + }, + }} + /> + + + } + componentProps={{ + fullWidth: false, + euiFieldProps: { + 'data-test-subj': 'enrichFields', + placeholder: i18n.translate( + 'xpack.idxMgmt.enrichPolicyCreate.fieldSelectionStep.enrichFieldsPlaceholder', + { + defaultMessage: 'Select fields to enrich', + } + ), + noSuggestions: false, + options: fieldOptions, + }, + }} + /> + + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/fields/indices_selector.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/fields/indices_selector.tsx new file mode 100644 index 0000000000000..f246527b9dfdc --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/fields/indices_selector.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useCallback, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { uniq, isEmpty } from 'lodash'; +import { EuiFormRow, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import type { EuiComboBoxProps } from '@elastic/eui'; +import { getMatchingIndices } from '../../../../services/api'; +import type { FieldHook } from '../../../../../shared_imports'; +import { getFieldValidityAndErrorMessage } from '../../../../../shared_imports'; + +interface IOption { + label: string; + options: Array<{ value: string; label: string; key?: string }>; +} + +interface Props { + field: FieldHook; + euiFieldProps: EuiComboBoxProps; + [key: string]: any; +} + +const getIndexOptions = async (patternString: string) => { + const options: IOption[] = []; + + if (!patternString) { + return options; + } + + const { data } = await getMatchingIndices(patternString); + const matchingIndices = data.indices; + + if (matchingIndices.length) { + const matchingOptions = uniq([...matchingIndices]); + + options.push({ + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.indicesSelector.optionsLabel', { + defaultMessage: 'Based on your indices', + }), + options: matchingOptions + .map((match) => { + return { + label: match, + value: match, + }; + }) + .sort((a, b) => String(a.label).localeCompare(b.label)), + }); + } else { + options.push({ + label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.indicesSelector.noMatchingOption', { + defaultMessage: 'No indices match your search criteria.', + }), + options: [], + }); + } + + return options; +}; + +export const IndicesSelector = ({ field, euiFieldProps, ...rest }: Props) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const [indexOptions, setIndexOptions] = useState([]); + const [isIndiciesLoading, setIsIndiciesLoading] = useState(false); + + const onSearchChange = useCallback( + async (search: string) => { + const indexPattern = isEmpty(search) ? '*' : search; + + setIsIndiciesLoading(true); + setIndexOptions(await getIndexOptions(indexPattern)); + setIsIndiciesLoading(false); + }, + [setIsIndiciesLoading, setIndexOptions] + ); + + // Fetch indices on mount so that the ComboBox has some initial options + useEffect(() => { + if (isEmpty(field.value)) { + onSearchChange('*'); + } + }, [field.value, onSearchChange]); + + return ( + + { + return { + label: anIndex, + value: anIndex, + }; + })} + onChange={async (selected: EuiComboBoxOptionOption[]) => { + field.setValue(selected.map((aSelected) => aSelected.value) as string[]); + }} + onSearchChange={onSearchChange} + onBlur={() => { + if (!field.value) { + field.setValue([]); + } + }} + data-test-subj="comboBox" + {...euiFieldProps} + /> + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/index.ts b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/index.ts new file mode 100644 index 0000000000000..3fd6dc30977dc --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/steps/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ConfigurationStep } from './configuration'; +export { FieldSelectionStep } from './field_selection'; +export { CreateStep } from './create'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index d3d8b0bc64baf..28fc4a5f15218 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, @@ -41,24 +41,16 @@ interface DetailsListProps { name: string; toolTip: string; content: any; + dataTestSubj: string; }>; } const DetailsList: React.FunctionComponent = ({ details }) => { - const groups: any[] = []; - let items: any[]; + const descriptionListItems = details.map((detail, index) => { + const { name, toolTip, content, dataTestSubj } = detail; - details.forEach((detail, index) => { - const { name, toolTip, content } = detail; - - if (index % 2 === 0) { - items = []; - - groups.push({items}); - } - - items.push( - + return ( + {name} @@ -69,12 +61,27 @@ const DetailsList: React.FunctionComponent = ({ details }) => - {content} - + + {content} + + ); }); - return {groups}; + const midpoint = Math.ceil(descriptionListItems.length / 2); + const descriptionListColumnOne = descriptionListItems.slice(0, midpoint); + const descriptionListColumnTwo = descriptionListItems.slice(-midpoint); + + return ( + + + {descriptionListColumnOne} + + + {descriptionListColumnTwo} + + + ); }; interface Props { @@ -123,23 +130,64 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ ilmPolicyName, storageSize, maxTimeStamp, + lifecycle, } = dataStream; - const details = [ + + const getManagementDetails = () => { + const managementDetails = []; + + if (lifecycle?.data_retention) { + managementDetails.push({ + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionTitle', { + defaultMessage: 'Data retention', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionToolTip', { + defaultMessage: 'The amount of time to retain the data in the data stream.', + }), + content: lifecycle.data_retention, + dataTestSubj: 'dataRetentionDetail', + }); + } + + if (ilmPolicyName) { + managementDetails.push({ + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', { + defaultMessage: 'Index lifecycle policy', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { + defaultMessage: `The index lifecycle policy that manages the data in the data stream.`, + }), + content: ilmPolicyLink ? ( + + {ilmPolicyName} + + ) : ( + ilmPolicyName + ), + dataTestSubj: 'ilmPolicyDetail', + }); + } + + return managementDetails; + }; + + const defaultDetails = [ { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthTitle', { defaultMessage: 'Health', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthToolTip', { - defaultMessage: `The health of the data stream's current backing indices`, + defaultMessage: `The health of the data stream's current backing indices.`, }), content: , + dataTestSubj: 'healthDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampTitle', { defaultMessage: 'Last updated', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampToolTip', { - defaultMessage: 'The most recent document to be added to the data stream', + defaultMessage: 'The most recent document to be added to the data stream.', }), content: maxTimeStamp ? ( humanizeTimeStamp(maxTimeStamp) @@ -150,22 +198,24 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ })} ), + dataTestSubj: 'lastUpdatedDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeTitle', { defaultMessage: 'Storage size', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeToolTip', { - defaultMessage: `Total size of all shards in the data stream’s backing indices`, + defaultMessage: `The total size of all shards in the data stream’s backing indices.`, }), content: storageSize, + dataTestSubj: 'storageSizeDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesTitle', { defaultMessage: 'Indices', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', { - defaultMessage: `The data stream's current backing indices`, + defaultMessage: `The data stream's current backing indices.`, }), content: ( = ({ {indices.length} ), + dataTestSubj: 'indicesDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle', { defaultMessage: 'Timestamp field', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldToolTip', { - defaultMessage: 'Timestamp field shared by all documents in the data stream', + defaultMessage: 'The timestamp field shared by all documents in the data stream.', }), content: timeStampField.name, + dataTestSubj: 'timestampDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationTitle', { defaultMessage: 'Generation', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationToolTip', { - defaultMessage: 'Cumulative count of backing indices created for the data stream', + defaultMessage: 'The number of backing indices generated for the data stream.', }), content: generation, + dataTestSubj: 'generationDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle', { @@ -202,7 +255,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateToolTip', { defaultMessage: - 'The index template that configured the data stream and configures its backing indices', + 'The index template that configured the data stream and configures its backing indices.', }), content: ( = ({ {indexTemplateName} ), - }, - { - name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', { - defaultMessage: 'Index lifecycle policy', - }), - toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { - defaultMessage: `The index lifecycle policy that manages the data stream's data`, - }), - content: - ilmPolicyName && ilmPolicyLink ? ( - - {ilmPolicyName} - - ) : ( - ilmPolicyName || ( - - {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', { - defaultMessage: `None`, - })} - - ) - ), + dataTestSubj: 'indexTemplateDetail', }, ]; + const managementDetails = getManagementDetails(); + const details = [...defaultDetails, ...managementDetails]; + content = ; } diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 0d17936ec7553..140fb2a4282ee 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -34,6 +34,7 @@ import { } from '../../../../shared_imports'; import { useAppContext } from '../../../app_context'; import { useLoadDataStreams } from '../../../services/api'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../services/breadcrumbs'; import { documentationService } from '../../../services/documentation'; import { Section } from '../home'; import { DataStreamTable } from './data_stream_table'; @@ -66,6 +67,10 @@ export const DataStreamList: React.FunctionComponent { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.dataStreams); + }, []); + const [isIncludeStatsChecked, setIsIncludeStatsChecked] = useState(false); const { error, diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 8e070ae7fe125..d67922f3cdcbc 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -8,7 +8,14 @@ import React, { useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui'; +import { + EuiInMemoryTable, + EuiBasicTableColumn, + EuiButton, + EuiLink, + EuiIcon, + EuiToolTip, +} from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; import { DataStream } from '../../../../../../common/types'; @@ -71,7 +78,6 @@ export const DataStreamTable: React.FunctionComponent = ({ render: (health: DataStream['health']) => { return ; }, - width: '100px', }); if (includeStats) { @@ -80,7 +86,6 @@ export const DataStreamTable: React.FunctionComponent = ({ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnTitle', { defaultMessage: 'Last updated', }), - width: '300px', truncateText: true, sortable: true, render: (maxTimeStamp: DataStream['maxTimeStamp']) => @@ -120,6 +125,28 @@ export const DataStreamTable: React.FunctionComponent = ({ ), }); + columns.push({ + field: 'lifecycle', + name: ( + + + {i18n.translate('xpack.idxMgmt.dataStreamList.table.dataRetentionColumnTitle', { + defaultMessage: 'Data retention', + })}{' '} + + + + ), + truncateText: true, + sortable: true, + render: (lifecycle: DataStream['lifecycle']) => lifecycle?.data_retention, + }); + columns.push({ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionColumnTitle', { defaultMessage: 'Actions', diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/delete_policy_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/delete_policy_modal.tsx new file mode 100644 index 0000000000000..b60b01f60bc93 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/delete_policy_modal.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState, useRef } from 'react'; +import { EuiConfirmModal } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { deleteEnrichPolicy } from '../../../../services/api'; +import { useAppContext } from '../../../../app_context'; + +export const DeletePolicyModal = ({ + policyToDelete, + callback, +}: { + policyToDelete: string; + callback: (data?: { hasDeletedPolicy: boolean }) => void; +}) => { + const mounted = useRef(false); + const { + services: { notificationService }, + } = useAppContext(); + const [isDeleting, setIsDeleting] = useState(false); + + // Since the async action of this component needs to set state after unmounting, + // we need to track the mounted state of this component to avoid a memory leak. + useEffect(() => { + mounted.current = true; + return () => { + mounted.current = false; + }; + }, []); + + const handleDeletePolicy = () => { + setIsDeleting(true); + + deleteEnrichPolicy(policyToDelete) + .then(({ data, error }) => { + if (data) { + const successMessage = i18n.translate( + 'xpack.idxMgmt.enrichPolicies.deleteModal.successDeleteNotificationMessage', + { defaultMessage: 'Deleted {policyToDelete}', values: { policyToDelete } } + ); + notificationService.showSuccessToast(successMessage); + + return callback({ hasDeletedPolicy: true }); + } + + if (error) { + const errorMessage = i18n.translate( + 'xpack.idxMgmt.enrichPolicies.deleteModal.errorDeleteNotificationMessage', + { + defaultMessage: "Error deleting enrich policy: '{error}'", + values: { error: error.message }, + } + ); + notificationService.showDangerToast(errorMessage); + } + + callback(); + }) + .finally(() => { + if (mounted.current) { + setIsDeleting(false); + } + }); + }; + + const handleOnCancel = () => { + callback(); + }; + + return ( + +

    + {policyToDelete}, + }} + /> +

    +
    + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/execute_policy_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/execute_policy_modal.tsx new file mode 100644 index 0000000000000..d95d602d1fb25 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/execute_policy_modal.tsx @@ -0,0 +1,103 @@ +/* + * Copyright 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, { useEffect, useState, useRef } from 'react'; +import { EuiConfirmModal } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { executeEnrichPolicy } from '../../../../services/api'; +import { useAppContext } from '../../../../app_context'; + +export const ExecutePolicyModal = ({ + policyToExecute, + callback, +}: { + policyToExecute: string; + callback: (data?: { hasExecutedPolicy: boolean }) => void; +}) => { + const mounted = useRef(false); + const { + services: { notificationService }, + } = useAppContext(); + const [isExecuting, setIsExecuting] = useState(false); + + // Since the async action of this component needs to set state after unmounting, + // we need to track the mounted state of this component to avoid a memory leak. + useEffect(() => { + mounted.current = true; + return () => { + mounted.current = false; + }; + }, []); + + const handleExecutePolicy = () => { + setIsExecuting(true); + + executeEnrichPolicy(policyToExecute) + .then(({ data, error }) => { + if (data) { + const successMessage = i18n.translate( + 'xpack.idxMgmt.enrichPolicies.executeModal.successExecuteNotificationMessage', + { defaultMessage: 'Executed {policyToExecute}', values: { policyToExecute } } + ); + notificationService.showSuccessToast(successMessage); + + return callback({ hasExecutedPolicy: true }); + } + + if (error) { + const errorMessage = i18n.translate( + 'xpack.idxMgmt.enrichPolicies.executeModal.errorExecuteNotificationMessage', + { + defaultMessage: "Error executing enrich policy: '{error}'", + values: { error: error.message }, + } + ); + notificationService.showDangerToast(errorMessage); + } + + callback(); + }) + .finally(() => { + if (mounted.current) { + setIsExecuting(false); + } + }); + }; + + const handleOnCancel = () => { + callback(); + }; + + return ( + +

    + {policyToExecute}, + }} + /> +

    +
    + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/index.ts b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/index.ts new file mode 100644 index 0000000000000..73ee9dacfedd3 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/confirm_modals/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { DeletePolicyModal } from './delete_policy_modal'; +export { ExecutePolicyModal } from './execute_policy_modal'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/details_flyout/index.ts b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/details_flyout/index.ts new file mode 100644 index 0000000000000..a8f86e489f18a --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/details_flyout/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { PolicyDetailsFlyout } from './policy_details_flyout'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/details_flyout/policy_details_flyout.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/details_flyout/policy_details_flyout.tsx new file mode 100644 index 0000000000000..55bcb7aaa6cd3 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/details_flyout/policy_details_flyout.tsx @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, +} from '@elastic/eui'; +import { CodeEditor } from '@kbn/kibana-react-plugin/public'; +import type { SerializedEnrichPolicy } from '../../../../../../common'; + +export interface Props { + policy: SerializedEnrichPolicy; + onClose: () => void; +} + +export const PolicyDetailsFlyout: FunctionComponent = ({ policy, onClose }) => { + return ( + + + + + +

    {policy.name}

    +
    +
    +
    +
    + + + + {/* Policy type */} + {policy.type && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicies.detailsFlyout.typeTitle', { + defaultMessage: 'Type', + })} + + + {policy.type} + + + )} + + {/* Policy source indices */} + {policy.sourceIndices && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicies.detailsFlyout.sourceIndicesTitle', { + defaultMessage: 'Source indices', + })} + + + {policy.sourceIndices.join(', ')} + + + )} + + {/* Policy match field */} + {policy.matchField && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicies.detailsFlyout.matchFieldTitle', { + defaultMessage: 'Match field', + })} + + + {policy.matchField} + + + )} + + {/* Policy enrich fields */} + {policy.enrichFields && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicies.detailsFlyout.enrichFieldsTitle', { + defaultMessage: 'Enrich fields', + })} + + + {policy.enrichFields.join(', ')} + + + )} + + {/* Policy query */} + {policy.query && ( + <> + + {i18n.translate('xpack.idxMgmt.enrichPolicies.detailsFlyout.queryTitle', { + defaultMessage: 'Query', + })} + + + + + + )} + + + + + + + + {i18n.translate('xpack.idxMgmt.enrichPolicies.detailsFlyout.closeButton', { + defaultMessage: 'Close', + })} + + + + +
    + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/empty_state.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/empty_state.tsx new file mode 100644 index 0000000000000..8581b99d6822d --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/empty_state.tsx @@ -0,0 +1,54 @@ +/* + * Copyright 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 { FormattedMessage } from '@kbn/i18n-react'; +import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +import { useAppContext } from '../../../../app_context'; + +export const EmptyState = () => { + const { history } = useAppContext(); + + return ( + + +

    + } + body={ +

    + +

    + } + actions={ + + + + } + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/error_state.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/error_state.tsx new file mode 100644 index 0000000000000..fa2863811f65f --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/error_state.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 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 { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton, EuiPageTemplate, EuiSpacer, EuiText } from '@elastic/eui'; +import { useLoadEnrichPolicies } from '../../../../services/api'; + +export const ErrorState = ({ + error, + resendRequest, +}: { + error: { + error: string; + message?: string; + }; + resendRequest: ReturnType['resendRequest']; +}) => { + return ( + + +

    + } + body={ + <> + +

    {error?.message || error?.error}

    +
    + + + + + + } + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/index.ts b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/index.ts new file mode 100644 index 0000000000000..ca1837401bdf2 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ErrorState } from './error_state'; +export { LoadingState } from './loading_state'; +export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/loading_state.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/loading_state.tsx new file mode 100644 index 0000000000000..fc1f98659eae3 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/empty_states/loading_state.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +export const LoadingState = () => { + return ( + } + body={ + + + + } + data-test-subj="sectionLoading" + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/enrich_policies_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/enrich_policies_list.tsx new file mode 100644 index 0000000000000..da37f4b5fe5bf --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/enrich_policies_list.tsx @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useEffect } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiSpacer, EuiText, EuiLink } from '@elastic/eui'; +import { RouteComponentProps } from 'react-router-dom'; +import { Location } from 'history'; +import { parse } from 'query-string'; + +import { APP_WRAPPER_CLASS, useExecutionContext } from '../../../../shared_imports'; +import type { SerializedEnrichPolicy } from '../../../../../common'; +import { useAppContext } from '../../../app_context'; +import { useRedirectPath } from '../../../hooks/redirect_path'; + +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../services/breadcrumbs'; +import { documentationService } from '../../../services/documentation'; +import { useLoadEnrichPolicies } from '../../../services/api'; +import { PoliciesTable } from './policies_table'; +import { DeletePolicyModal, ExecutePolicyModal } from './confirm_modals'; +import { LoadingState, ErrorState, EmptyState } from './empty_states'; +import { PolicyDetailsFlyout } from './details_flyout'; + +const getEnrichPolicyNameFromLocation = (location: Location) => { + const { policy } = parse(location.search.substring(1)); + return policy; +}; + +export const EnrichPoliciesList: React.FunctionComponent = ({ + history, + location, +}) => { + const { + core: { executionContext }, + } = useAppContext(); + const redirectTo = useRedirectPath(history); + + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.enrichPolicies); + }, []); + + useExecutionContext(executionContext, { + type: 'application', + page: 'indexManagementEnrichPoliciesTab', + }); + + // Policy details flyout + const enrichPolicyNameFromLocation = getEnrichPolicyNameFromLocation(location); + const [showFlyoutFor, setShowFlyoutFor] = useState(); + + // Policy table actions + const [policyToDelete, setPolicyToDelete] = useState(); + const [policyToExecute, setPolicyToExecute] = useState(); + + const { + error, + isLoading, + data: policies, + resendRequest: reloadPolicies, + } = useLoadEnrichPolicies(); + + useEffect(() => { + if (enrichPolicyNameFromLocation && policies?.length) { + const policy = policies.find((p) => p.name === enrichPolicyNameFromLocation); + setShowFlyoutFor(policy); + } + }, [enrichPolicyNameFromLocation, policies]); + + if (isLoading) { + return ; + } + + if (error) { + return ; + } + + if (policies?.length === 0) { + return ; + } + + return ( +
    + + + + + ), + }} + /> + + + + + + {policyToDelete && ( + { + if (deleteResponse?.hasDeletedPolicy) { + reloadPolicies(); + } + setPolicyToDelete(undefined); + }} + /> + )} + + {policyToExecute && ( + { + if (executeResponse?.hasExecutedPolicy) { + reloadPolicies(); + } + setPolicyToExecute(undefined); + }} + /> + )} + + {showFlyoutFor && ( + { + setShowFlyoutFor(undefined); + redirectTo('/enrich_policies'); + }} + /> + )} +
    + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/index.ts b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/index.ts new file mode 100644 index 0000000000000..bac651ef9b59b --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { EnrichPoliciesList } from './enrich_policies_list'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/index.ts b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/index.ts new file mode 100644 index 0000000000000..bdd9eb08278e1 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { PoliciesTable } from './policies_table'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/policies_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/policies_table.tsx new file mode 100644 index 0000000000000..87002e6042270 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/policies_table.tsx @@ -0,0 +1,175 @@ +/* + * Copyright 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, { FunctionComponent } from 'react'; +import { + EuiInMemoryTable, + EuiBasicTableColumn, + EuiSearchBarProps, + EuiButton, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; +import { useAppContext } from '../../../../app_context'; +import type { SerializedEnrichPolicy } from '../../../../../../common/types'; + +export interface Props { + policies: SerializedEnrichPolicy[]; + onReloadClick: () => void; + onDeletePolicyClick: (policyName: string) => void; + onExecutePolicyClick: (policyName: string) => void; +} + +const pagination = { + initialPageSize: 50, + pageSizeOptions: [25, 50, 100], +}; + +export const PoliciesTable: FunctionComponent = ({ + policies, + onReloadClick, + onDeletePolicyClick, + onExecutePolicyClick, +}) => { + const { history } = useAppContext(); + + const renderToolsRight = () => { + return [ + + + , + + + , + ]; + }; + + const search: EuiSearchBarProps = { + toolsRight: renderToolsRight(), + box: { + incremental: true, + }, + }; + + const columns: Array> = [ + { + field: 'name', + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.nameField', { + defaultMessage: 'Name', + }), + sortable: true, + truncateText: true, + render: (name: string) => ( + + {name} + + ), + }, + { + field: 'type', + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.typeField', { + defaultMessage: 'Type', + }), + sortable: true, + }, + { + field: 'sourceIndices', + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.sourceIndicesField', { + defaultMessage: 'Source indices', + }), + truncateText: true, + render: (indices: string[]) => {indices.join(', ')}, + }, + { + field: 'matchField', + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.matchFieldField', { + defaultMessage: 'Match field', + }), + truncateText: true, + }, + { + field: 'enrichFields', + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.enrichFieldsField', { + defaultMessage: 'Enrich fields', + }), + truncateText: true, + render: (fields: string[]) => {fields.join(', ')}, + }, + { + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.actionsField', { + defaultMessage: 'Actions', + }), + actions: [ + { + isPrimary: true, + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeAction', { + defaultMessage: 'Execute', + }), + description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeDescription', { + defaultMessage: 'Execute this enrich policy', + }), + type: 'icon', + icon: 'play', + 'data-test-subj': 'executePolicyButton', + onClick: ({ name }) => onExecutePolicyClick(name), + }, + { + isPrimary: true, + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteAction', { + defaultMessage: 'Delete', + }), + description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteDescription', { + defaultMessage: 'Delete this enrich policy', + }), + type: 'icon', + icon: 'trash', + color: 'danger', + 'data-test-subj': 'deletePolicyButton', + onClick: ({ name }) => onDeletePolicyClick(name), + }, + ], + }, + ]; + + return ( + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 27e68920c5612..15717a6acb7e3 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -5,16 +5,16 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { Routes, Route } from '@kbn/shared-ux-router'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButtonEmpty, EuiPageHeader, EuiSpacer } from '@elastic/eui'; -import { breadcrumbService } from '../../services/breadcrumbs'; import { documentationService } from '../../services/documentation'; import { useAppContext } from '../../app_context'; import { ComponentTemplateList } from '../../components/component_templates'; import { IndexList } from './index_list'; +import { EnrichPoliciesList } from './enrich_policies_list'; import { IndexDetailsPage } from './index_list/details_page'; import { DataStreamList } from './data_stream_list'; import { TemplateList } from './template_list'; @@ -24,6 +24,7 @@ export enum Section { DataStreams = 'data_streams', IndexTemplates = 'templates', ComponentTemplates = 'component_templates', + EnrichPolicies = 'enrich_policies', } export const homeSections = [ @@ -31,6 +32,7 @@ export const homeSections = [ Section.DataStreams, Section.IndexTemplates, Section.ComponentTemplates, + Section.EnrichPolicies, ]; interface MatchParams { @@ -78,16 +80,21 @@ export const IndexManagementHome: React.FunctionComponent ), }, + { + id: Section.EnrichPolicies, + name: ( + + ), + }, ]; const onSectionChange = (newSection: Section) => { history.push(`/${newSection}`); }; - useEffect(() => { - breadcrumbService.setBreadcrumbs('home'); - }, []); - const indexManagementTabs = ( <> + ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx new file mode 100644 index 0000000000000..746d684f48b75 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton } from '@elastic/eui'; + +import { CreateIndexModal } from './create_index_modal'; + +export const CreateIndexButton = ({ loadIndices }: { loadIndices: () => void }) => { + const [createIndexModalOpen, setCreateIndexModalOpen] = useState(false); + + return ( + <> + setCreateIndexModalOpen(true)} + key="createIndexButton" + data-test-subj="createIndexButton" + data-telemetry-id="idxMgmt-indexList-createIndexButton" + > + + + {createIndexModalOpen && ( + setCreateIndexModalOpen(false)} + loadIndices={loadIndices} + /> + )} + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_modal.tsx new file mode 100644 index 0000000000000..c54aaf0b12374 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_modal.tsx @@ -0,0 +1,164 @@ +/* + * Copyright 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, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { createIndex } from '../../../../services'; +import { notificationService } from '../../../../services/notification'; + +import { isValidIndexName } from './utils'; + +const INVALID_INDEX_NAME_ERROR = i18n.translate( + 'xpack.idxMgmt.createIndex.modal.invalidName.error', + { defaultMessage: 'Index name is not valid' } +); + +export interface CreateIndexModalProps { + closeModal: () => void; + loadIndices: () => void; +} + +export const CreateIndexModal = ({ closeModal, loadIndices }: CreateIndexModalProps) => { + const [indexName, setIndexName] = useState(''); + const [indexNameError, setIndexNameError] = useState(); + const [isSaving, setIsSaving] = useState(false); + const [createError, setCreateError] = useState(); + + const putCreateIndex = useCallback(async () => { + setIsSaving(true); + try { + const { error } = await createIndex(indexName); + setIsSaving(false); + if (!error) { + loadIndices(); + closeModal(); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage', { + defaultMessage: 'Successfully created index: {indexName}', + values: { indexName }, + }) + ); + return; + } + setCreateError(error.message); + } catch (e) { + setIsSaving(false); + setCreateError(e.message); + } + }, [closeModal, indexName, loadIndices]); + + const onSave = () => { + if (isValidIndexName(indexName)) { + putCreateIndex(); + } + }; + + const onNameChange = (name: string) => { + setIndexName(name); + if (!isValidIndexName(name)) { + setIndexNameError(INVALID_INDEX_NAME_ERROR); + } else if (indexNameError) { + setIndexNameError(undefined); + } + }; + + return ( + + + + + + + + {createError && ( + <> + + + + + + + + )} + + + onNameChange(e.target.value)} + data-test-subj="createIndexNameFieldText" + /> + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/utils.test.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/utils.test.ts new file mode 100644 index 0000000000000..f967e7294a35b --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/utils.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright 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 { isValidIndexName } from './utils'; + +describe('create index utilities', () => { + describe('isValidIndexName', () => { + it('returns undefined for a valid name', () => { + expect(isValidIndexName('my-index')).toBe(true); + }); + it('returns error for empty name', () => { + expect(isValidIndexName('')).toBe(false); + }); + it('returns error name is not lower case', () => { + expect(isValidIndexName('MyIndexName')).toBe(false); + }); + it('returns error for .', () => { + expect(isValidIndexName('.')).toBe(false); + }); + it('returns error for ..', () => { + expect(isValidIndexName('..')).toBe(false); + }); + it('returns error if name starts with -, _,., or +', () => { + expect(isValidIndexName('-index')).toBe(false); + expect(isValidIndexName('_index')).toBe(false); + expect(isValidIndexName('+index')).toBe(false); + expect(isValidIndexName('.index')).toBe(false); + + expect(isValidIndexName('index-name')).toBe(true); + expect(isValidIndexName('index_name')).toBe(true); + expect(isValidIndexName('index+name')).toBe(true); + expect(isValidIndexName('index.name')).toBe(true); + }); + it('returns error if name contains spaces', () => { + expect(isValidIndexName('index name')).toBe(false); + }); + it('returns error if name contains special characters', () => { + expect(isValidIndexName('index/name')).toBe(false); + expect(isValidIndexName('index\\name')).toBe(false); + expect(isValidIndexName('index*name')).toBe(false); + expect(isValidIndexName('index?name')).toBe(false); + expect(isValidIndexName('index"name')).toBe(false); + expect(isValidIndexName('indexname')).toBe(false); + expect(isValidIndexName('index|name')).toBe(false); + expect(isValidIndexName('index,name')).toBe(false); + expect(isValidIndexName('index#name')).toBe(false); + expect(isValidIndexName('index:name')).toBe(false); + }); + it('returns error exceeds 255 bytes', () => { + const indexName = `this-is-a-long-index-name-with-unicode-characters🔥☕️🔥🔥-${'0'.repeat( + 200 + )}`; + + expect(isValidIndexName(indexName)).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/utils.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/utils.ts new file mode 100644 index 0000000000000..cc2661c24faa0 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/utils.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html for the current rules + +export function isValidIndexName(name: string) { + const byteLength = encodeURI(name).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1; + const reg = new RegExp('[\\\\/:*?"<>|\\s,#]+'); + const indexPatternInvalid = + byteLength > 255 || // name can't be greater than 255 bytes + name !== name.toLowerCase() || // name should be lowercase + name.match(/^[-_+.]/) !== null || // name can't start with these chars + name.match(reg) !== null || // name can't contain these chars + name.length === 0; // name can't be empty + + return !indexPatternInvalid; +} diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx index 80f79d9ab3c76..33378fc138c04 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx @@ -21,6 +21,7 @@ import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; import { Index } from '../../../../../../common'; import { INDEX_OPEN } from '../../../../../../common/constants'; +import { Error } from '../../../../../shared_imports'; import { loadIndex } from '../../../../services'; import { useAppContext } from '../../../../app_context'; import { DiscoverLink } from '../../../../lib/discover_link'; @@ -29,6 +30,8 @@ import { DetailsPageError } from './details_page_error'; import { ManageIndexButton } from './manage_index_button'; import { DetailsPageStats } from './details_page_stats'; import { DetailsPageMappings } from './details_page_mappings'; +import { DetailsPageOverview } from './details_page_overview'; +import { DetailsPageSettings } from './details_page_settings'; export enum IndexDetailsSection { Overview = 'overview', @@ -86,7 +89,7 @@ export const DetailsPage: React.FunctionComponent< }) => { const { config } = useAppContext(); const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(); + const [error, setError] = useState(null); const [index, setIndex] = useState(); const fetchIndexDetails = useCallback(async () => { @@ -187,7 +190,7 @@ export const DetailsPage: React.FunctionComponent<
    Overview
    } + render={() => } />
    Settings
    } + path={`/${Section.Indices}/:indexName/${IndexDetailsSection.Settings}`} + render={(props: RouteComponentProps<{ indexName: string }>) => ( + + )} /> > = ({ match: { @@ -32,6 +33,10 @@ export const DetailsPageMappings: FunctionComponent { const { isLoading, data, error, resendRequest } = useLoadIndexMappings(indexName); + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsMappings); + }, []); + if (isLoading) { return ( diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx new file mode 100644 index 0000000000000..712d6d9f19b7e --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useMemo, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiSpacer, + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiStat, + EuiTitle, + EuiText, + EuiTextColor, +} from '@elastic/eui'; +import { + CodeBox, + LanguageDefinition, + LanguageDefinitionSnippetArguments, + getLanguageDefinitionCodeSnippet, + getConsoleRequest, +} from '@kbn/search-api-panels'; +import type { Index } from '../../../../../../../common'; +import { useAppContext } from '../../../../../app_context'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../../services/breadcrumbs'; +import { languageDefinitions, curlDefinition } from './languages'; + +interface Props { + indexDetails: Index; +} + +export const DetailsPageOverview: React.FunctionComponent = ({ indexDetails }) => { + const { + name, + status, + documents, + documents_deleted: documentsDeleted, + primary, + replica, + aliases, + } = indexDetails; + const { config, core, plugins } = useAppContext(); + + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsOverview); + }, []); + + const [selectedLanguage, setSelectedLanguage] = useState(curlDefinition); + + const elasticsearchURL = useMemo(() => { + return plugins.cloud?.elasticsearchUrl ?? 'https://your_deployment_url'; + }, [plugins.cloud]); + + const codeSnippetArguments: LanguageDefinitionSnippetArguments = { + url: elasticsearchURL, + apiKey: 'your_api_key', + indexName: name, + }; + + return ( + <> + + {config.enableIndexStats && ( + + + + + + + + + + + + + + + + )} + + + + + {primary && ( + + + + )} + + {replica && ( + + + + )} + + + + + + + + + + + + + + +

    + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.addMoreDataTitle', { + defaultMessage: 'Add more data to this index', + })} +

    +
    + + + + + +

    + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.addMoreDataDescription', { + defaultMessage: + 'Keep adding more documents to your already created index using the API', + })} +

    +
    +
    +
    + + + + +
    + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/index.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/index.ts new file mode 100644 index 0000000000000..e3f5b65bdcadf --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { DetailsPageOverview } from './details_page_overview'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts new file mode 100644 index 0000000000000..838efe28580c0 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts @@ -0,0 +1,179 @@ +/* + * Copyright 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 { Languages, LanguageDefinition } from '@kbn/search-api-panels'; +import { i18n } from '@kbn/i18n'; + +const INDEX_NAME_PLACEHOLDER = 'index_name'; + +export const curlDefinition: LanguageDefinition = { + id: Languages.CURL, + name: i18n.translate('xpack.idxMgmt.indexDetails.languages.cURL', { + defaultMessage: 'cURL', + }), + iconType: 'curl.svg', + languageStyling: 'shell', + ingestDataIndex: ({ apiKey, indexName, url }) => `curl -X POST ${url}/_bulk?pretty \\ + -H "Authorization: ApiKey ${apiKey}" \\ + -H "Content-Type: application/json" \\ + -d' +{ "index" : { "_index" : "${indexName ?? INDEX_NAME_PLACEHOLDER}" } } +{"name": "foo", "title": "bar" } +`, +}; + +export const javascriptDefinition: LanguageDefinition = { + id: Languages.JAVASCRIPT, + name: i18n.translate('xpack.idxMgmt.indexDetails.languages.javascript', { + defaultMessage: 'JavaScript', + }), + iconType: 'javascript.svg', + ingestDataIndex: ({ + apiKey, + url, + indexName, + }) => `const { Client } = require('@elastic/elasticsearch'); +const client = new Client({ + node: '${url}', + auth: { + apiKey: '${apiKey}' + } +}); +const dataset = [ + {'name': 'foo', 'title': 'bar'}, +]; + +// Index with the bulk helper +const result = await client.helpers.bulk({ + datasource: dataset, + onDocument (doc) { + return { index: { _index: '${indexName ?? 'index_name'}' }}; + } +}); +console.log(result); +`, +}; + +export const goDefinition: LanguageDefinition = { + id: Languages.GO, + name: i18n.translate('xpack.idxMgmt.indexDetails.languages.go', { + defaultMessage: 'Go', + }), + iconType: 'go.svg', + ingestDataIndex: ({ apiKey, url, indexName }) => `import ( + "context" + "fmt" + "log" + "strings" +​ + "github.com/elastic/elasticsearch-serverless-go" +) +​ +func main() { + cfg := elasticsearch.Config{ + Address: "${url}", + APIKey: "${apiKey}", + } + es, err := elasticsearch.NewClient(cfg) + if err != nil { + log.Fatalf("Error creating the client: %s", err) + } + res, err := es.Bulk(). + Index("${indexName}"). + Raw(strings.NewReader(\` +{ "index": { "_id": "1"}} +{"name": "foo", "title": "bar"}\n\`)). + Do(context.Background()) + ​ + fmt.Println(res, err) +}`, +}; + +export const pythonDefinition: LanguageDefinition = { + id: Languages.PYTHON, + name: i18n.translate('xpack.idxMgmt.indexDetails.languages.python', { + defaultMessage: 'Python', + }), + iconType: 'python.svg', + ingestDataIndex: ({ apiKey, url, indexName }) => `from elasticsearch import Elasticsearch + +client = Elasticsearch( + "${url}", + api_key="${apiKey}" +) + +documents = [ + {"index": {"_index": "${indexName ?? INDEX_NAME_PLACEHOLDER}"}}, + {"name": "foo", "title": "bar"}, +] + +client.bulk(operations=documents) +`, +}; + +export const phpDefinition: LanguageDefinition = { + id: Languages.PHP, + name: i18n.translate('xpack.idxMgmt.indexDetails.languages.php', { + defaultMessage: 'PHP', + }), + iconType: 'php.svg', + ingestDataIndex: ({ apiKey, url, indexName }) => `$client = ClientBuilder::create() + ->setHosts(['${url}']) + ->setApiKey('${apiKey}') + ->build(); + +$params = [ +'body' => [ +[ +'index' => [ +'_index' => '${indexName ?? INDEX_NAME_PLACEHOLDER}', +'_id' => '1', +], +], +[ +'name' => 'foo', +'title' => 'bar', +], +], +]; + +$response = $client->bulk($params); +echo $response->getStatusCode(); +echo (string) $response->getBody(); +`, +}; + +export const rubyDefinition: LanguageDefinition = { + id: Languages.RUBY, + name: i18n.translate('xpack.idxMgmt.indexDetails.languages.ruby', { + defaultMessage: 'Ruby', + }), + iconType: 'ruby.svg', + ingestDataIndex: ({ apiKey, url, indexName }) => `client = ElasticsearchServerless::Client.new( + api_key: '${apiKey}', + url: '${url}' +) + +documents = [ + { index: { _index: '${ + indexName ?? INDEX_NAME_PLACEHOLDER + }', data: {name: "foo", "title": "bar"} } }, +] +client.bulk(body: documents) +`, +}; + +const languageDefinitionRecords: Partial> = { + [Languages.CURL]: curlDefinition, + [Languages.PYTHON]: pythonDefinition, + [Languages.JAVASCRIPT]: javascriptDefinition, + [Languages.PHP]: phpDefinition, + [Languages.GO]: goDefinition, + [Languages.RUBY]: rubyDefinition, +}; + +export const languageDefinitions: LanguageDefinition[] = Object.values(languageDefinitionRecords); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings.tsx new file mode 100644 index 0000000000000..ef7e59f43e6a3 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 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, { FunctionComponent, useEffect } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import { EuiButton, EuiPageTemplate, EuiSpacer, EuiText } from '@elastic/eui'; +import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useLoadIndexSettings } from '../../../../services'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; +import { DetailsPageSettingsContent } from './details_page_settings_content'; + +export const DetailsPageSettings: FunctionComponent< + RouteComponentProps<{ indexName: string }> & { isIndexOpen: boolean } +> = ({ + match: { + params: { indexName }, + }, + isIndexOpen, +}) => { + const { isLoading, data, error, resendRequest } = useLoadIndexSettings(indexName); + + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsSettings); + }, []); + + if (isLoading) { + return ( + + + + ); + } + if (error || !data) { + return ( + + + + } + body={ + <> + + + + + + + + + } + /> + ); + } + return ( + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings_content.tsx new file mode 100644 index 0000000000000..f418548e5c843 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings_content.tsx @@ -0,0 +1,280 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, FunctionComponent, useCallback } from 'react'; +import { + EuiButton, + EuiCallOut, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, + EuiText, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import _ from 'lodash'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { CodeEditor } from '@kbn/kibana-react-plugin/public'; +import { IndexSettingsResponse } from '../../../../../../common'; +import { Error } from '../../../../../shared_imports'; +import { documentationService, updateIndexSettings } from '../../../../services'; +import { notificationService } from '../../../../services/notification'; +import { flattenObject } from '../../../../lib/flatten_object'; +import { readOnlySettings, settingsToDisplay } from '../../../../lib/edit_settings'; + +const getEditableSettings = ( + data: Props['data'], + isIndexOpen: boolean +): { originalSettings: Record; settingsString: string } => { + const { defaults, settings } = data; + // settings user has actually set + const flattenedSettings = flattenObject(settings); + // settings with their defaults + const flattenedDefaults = flattenObject(defaults); + const filteredDefaults = _.pick(flattenedDefaults, settingsToDisplay); + const newSettings = { ...filteredDefaults, ...flattenedSettings }; + // store these to be used as autocomplete values later + readOnlySettings.forEach((e) => delete newSettings[e]); + // can't change codec on open index + if (isIndexOpen) { + delete newSettings['index.codec']; + } + const settingsString = JSON.stringify(newSettings, null, 2); + return { originalSettings: newSettings, settingsString }; +}; + +interface Props { + isIndexOpen: boolean; + data: IndexSettingsResponse; + indexName: string; + reloadIndexSettings: () => void; +} + +export const DetailsPageSettingsContent: FunctionComponent = ({ + isIndexOpen, + data, + indexName, + reloadIndexSettings, +}) => { + const [isEditMode, setIsEditMode] = useState(false); + const onEditModeChange = (event: EuiSwitchEvent) => { + setUpdateError(null); + setIsEditMode(event.target.checked); + }; + + const { originalSettings, settingsString } = getEditableSettings(data, isIndexOpen); + const [editableSettings, setEditableSettings] = useState(settingsString); + const [isLoading, setIsLoading] = useState(false); + const [updateError, setUpdateError] = useState(null); + + const resetChanges = useCallback(() => { + setUpdateError(null); + setEditableSettings(settingsString); + }, [settingsString]); + + const updateSettings = useCallback(async () => { + setUpdateError(null); + setIsLoading(true); + try { + const editedSettings = JSON.parse(editableSettings); + // don't set if the values have not changed + Object.keys(originalSettings).forEach((key) => { + if (_.isEqual(originalSettings[key], editedSettings[key])) { + delete editedSettings[key]; + } + }); + + if (Object.keys(editedSettings).length !== 0) { + const { error } = await updateIndexSettings(indexName, editedSettings); + if (error) { + setIsLoading(false); + setUpdateError(error); + } else { + setIsLoading(false); + setIsEditMode(false); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.indexDetails.settings.updateSuccessMessage', { + defaultMessage: 'Successfully updated settings for index {indexName}', + values: { indexName }, + }) + ); + reloadIndexSettings(); + } + } else { + setIsLoading(false); + setIsEditMode(false); + notificationService.showWarningToast( + i18n.translate('xpack.idxMgmt.indexDetails.settings.noChangeWarning', { + defaultMessage: 'No settings changed', + }) + ); + } + } catch (e) { + setIsLoading(false); + setUpdateError({ + error: i18n.translate('xpack.idxMgmt.indexDetails.settings.updateError', { + defaultMessage: 'Unable to update settings', + }), + }); + } + }, [originalSettings, editableSettings, indexName, reloadIndexSettings]); + return ( + // using "rowReverse" to keep the card on the left side to be on top of the code block on smaller screens + + + + + + + + + + + + + + + + + + } + checked={isEditMode} + onChange={onEditModeChange} + /> + + + + + + + + + + + + + + + {updateError && ( + <> + + +

    + {updateError.error} + {updateError.message && : {updateError.message}} +

    +
    + + )} + + + + +
    +
    + + + + {isEditMode ? ( + + ) : ( + + {JSON.stringify(data, null, 2)} + + )} + + +
    + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_stats.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_stats.tsx index 3e4319a03a9da..4c18804b0aee5 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_stats.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_stats.tsx @@ -25,8 +25,9 @@ import { import { css } from '@emotion/react'; import { IndicesStatsResponse } from '@elastic/elasticsearch/lib/api/types'; -import { SectionLoading } from '../../../../../shared_imports'; +import { SectionLoading, Error } from '../../../../../shared_imports'; import { loadIndexStatistics, documentationService } from '../../../../services'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; interface Props { isIndexOpen: boolean; @@ -41,9 +42,13 @@ export const DetailsPageStats: FunctionComponent< isIndexOpen, }) => { const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(); + const [error, setError] = useState(); const [indexStats, setIndexStats] = useState(); + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsStats); + }, []); + const fetchIndexStats = useCallback(async () => { setIsLoading(true); try { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.tsx index 14bbd94adc2fd..48f5436eafedf 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.tsx @@ -5,11 +5,12 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { ScopedHistory } from '@kbn/core/public'; import { APP_WRAPPER_CLASS, useExecutionContext } from '../../../../shared_imports'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../services/breadcrumbs'; import { useAppContext } from '../../../app_context'; import { DetailPanel } from './detail_panel'; import { IndexTable } from './index_table'; @@ -25,6 +26,10 @@ export const IndexList: React.FunctionComponent = ({ histor page: 'indexManagementIndicesTab', }); + useEffect(() => { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indices); + }, []); + const openDetailPanel = useCallback( (indexName: string) => { return history.push(encodeURI(`/indices/${indexName}`)); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 5284de7c537cb..94440da220fca 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -50,6 +50,7 @@ import { AppContextConsumer } from '../../../../app_context'; import { renderBadges } from '../../../../lib/render_badges'; import { NoMatch, DataHealth } from '../../../../components'; import { IndexActionsContextMenu } from '../index_actions_context_menu'; +import { CreateIndexButton } from '../create_index/create_index_button'; const getHeaders = ({ showIndexStats }) => { const headers = {}; @@ -623,6 +624,9 @@ export class IndexTable extends Component { )} + + + {this.renderFilterError()} diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx index 111ff83a14edd..53292944cf158 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx @@ -34,6 +34,7 @@ import { } from '../../../../shared_imports'; import { LegacyIndexTemplatesDeprecation } from '../../../components'; import { useLoadIndexTemplates } from '../../../services/api'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../../services/breadcrumbs'; import { documentationService } from '../../../services/documentation'; import { useAppContext, useServices } from '../../../app_context'; import { @@ -80,6 +81,10 @@ export const TemplateList: React.FunctionComponent { + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.templates); + }, []); + const [filters, setFilters] = useState>({ managed: { name: i18n.translate('xpack.idxMgmt.indexTemplatesList.viewManagedTemplateLabel', { diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx index 25b84a8bc6fde..2e98110e2f3c5 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx @@ -11,14 +11,13 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPageSection } from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; -import { PageLoading, PageError, Error } from '../../../shared_imports'; +import { PageLoading, PageError, Error, attemptToURIDecode } from '../../../shared_imports'; import { TemplateDeserialized } from '../../../../common'; import { TemplateForm } from '../../components'; -import { breadcrumbService } from '../../services/breadcrumbs'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../services/breadcrumbs'; import { getTemplateDetailsLink } from '../../services/routing'; import { saveTemplate, useLoadIndexTemplate } from '../../services/api'; import { getIsLegacyFromQueryParams } from '../../lib/index_templates'; -import { attemptToURIDecode } from '../../../shared_imports'; import { useAppContext } from '../../app_context'; interface MatchParams { @@ -70,7 +69,7 @@ export const TemplateClone: React.FunctionComponent { - breadcrumbService.setBreadcrumbs('templateClone'); + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.templateClone); }, []); if (isLoading) { diff --git a/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx b/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx index 6961c223e6993..02d8f59835112 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx @@ -6,16 +6,15 @@ */ import React, { useEffect, useState } from 'react'; -import { RouteComponentProps } from 'react-router-dom'; +import { RouteComponentProps, useLocation } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPageSection } from '@elastic/eui'; -import { useLocation } from 'react-router-dom'; import { parse } from 'query-string'; import { ScopedHistory } from '@kbn/core/public'; import { TemplateDeserialized } from '../../../../common'; import { TemplateForm } from '../../components'; -import { breadcrumbService } from '../../services/breadcrumbs'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../services/breadcrumbs'; import { saveTemplate } from '../../services/api'; import { getTemplateDetailsLink } from '../../services/routing'; import { useAppContext } from '../../app_context'; @@ -53,7 +52,7 @@ export const TemplateCreate: React.FunctionComponent = ({ h }; useEffect(() => { - breadcrumbService.setBreadcrumbs('templateCreate'); + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.templateCreate); }, []); return ( diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx index c37a7ce44b672..9c68d602e4e02 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx @@ -14,7 +14,7 @@ import { ScopedHistory } from '@kbn/core/public'; import { TemplateDeserialized } from '../../../../common'; import { PageError, PageLoading, attemptToURIDecode, Error } from '../../../shared_imports'; -import { breadcrumbService } from '../../services/breadcrumbs'; +import { breadcrumbService, IndexManagementBreadcrumb } from '../../services/breadcrumbs'; import { useLoadIndexTemplate, updateTemplate } from '../../services/api'; import { getTemplateDetailsLink } from '../../services/routing'; import { TemplateForm } from '../../components'; @@ -46,7 +46,7 @@ export const TemplateEdit: React.FunctionComponent { - breadcrumbService.setBreadcrumbs('templateEdit'); + breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.templateEdit); }, []); const onSave = async (updatedTemplate: TemplateDeserialized) => { diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 3daee29ec62e8..fb6ed8fd77720 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -9,6 +9,7 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { IndicesStatsResponse } from '@elastic/elasticsearch/lib/api/types'; import { API_BASE_PATH, + INTERNAL_API_BASE_PATH, UIM_UPDATE_SETTINGS, UIM_INDEX_CLEAR_CACHE, UIM_INDEX_CLEAR_CACHE_MANY, @@ -32,13 +33,19 @@ import { UIM_TEMPLATE_UPDATE, UIM_TEMPLATE_CLONE, UIM_TEMPLATE_SIMULATE, - INTERNAL_API_BASE_PATH, } from '../../../common/constants'; -import { TemplateDeserialized, TemplateListItem, DataStream, Index } from '../../../common'; +import { + TemplateDeserialized, + TemplateListItem, + DataStream, + Index, + IndexSettingsResponse, +} from '../../../common'; import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants'; import { useRequest, sendRequest } from './use_request'; import { httpService } from './http'; import { UiMetricService } from './ui_metric'; +import type { SerializedEnrichPolicy, FieldFromIndicesRequest } from '../../../common'; interface ReloadIndicesOptions { asSystemRequest?: boolean; @@ -314,6 +321,67 @@ export function useLoadNodesPlugins() { }); } +export const useLoadEnrichPolicies = () => { + return useRequest({ + path: `${INTERNAL_API_BASE_PATH}/enrich_policies`, + method: 'get', + }); +}; + +export async function deleteEnrichPolicy(policyName: string) { + const result = sendRequest({ + path: `${INTERNAL_API_BASE_PATH}/enrich_policies/${policyName}`, + method: 'delete', + }); + + return result; +} + +export async function executeEnrichPolicy(policyName: string) { + const result = sendRequest({ + path: `${INTERNAL_API_BASE_PATH}/enrich_policies/${policyName}`, + method: 'put', + }); + + return result; +} + +export async function createEnrichPolicy( + policy: SerializedEnrichPolicy, + executePolicyAfterCreation?: boolean +) { + const result = sendRequest({ + path: `${INTERNAL_API_BASE_PATH}/enrich_policies`, + method: 'post', + body: JSON.stringify({ policy }), + query: { + executePolicyAfterCreation, + }, + }); + + return result; +} + +export async function getMatchingIndices(pattern: string) { + const result = sendRequest({ + path: `${INTERNAL_API_BASE_PATH}/enrich_policies/get_matching_indices`, + method: 'post', + body: JSON.stringify({ pattern }), + }); + + return result; +} + +export async function getFieldsFromIndices(indices: string[]) { + const result = sendRequest({ + path: `${INTERNAL_API_BASE_PATH}/enrich_policies/get_fields_from_indices`, + method: 'post', + body: JSON.stringify({ indices }), + }); + + return result; +} + export function loadIndex(indexName: string) { return sendRequest({ path: `${INTERNAL_API_BASE_PATH}/indices/${encodeURIComponent(indexName)}`, @@ -334,3 +402,20 @@ export function loadIndexStatistics(indexName: string) { method: 'get', }); } + +export function useLoadIndexSettings(indexName: string) { + return useRequest({ + path: `${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`, + method: 'get', + }); +} + +export function createIndex(indexName: string) { + return sendRequest({ + path: `${INTERNAL_API_BASE_PATH}/indices/create`, + method: 'put', + body: JSON.stringify({ + indexName, + }), + }); +} diff --git a/x-pack/plugins/index_management/public/application/services/breadcrumbs.ts b/x-pack/plugins/index_management/public/application/services/breadcrumbs.ts index 75ad96d276b90..eb42288ffecbc 100644 --- a/x-pack/plugins/index_management/public/application/services/breadcrumbs.ts +++ b/x-pack/plugins/index_management/public/application/services/breadcrumbs.ts @@ -7,17 +7,53 @@ import { i18n } from '@kbn/i18n'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; +import { EuiBreadcrumb } from '@elastic/eui'; type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; +export enum IndexManagementBreadcrumb { + home = 'home', + /** + * Indices tab + */ + indices = 'indices', + /** + * Index details page + */ + indexDetailsOverview = 'indexDetailsOverview', + indexDetailsMappings = 'indexDetailsMappings', + indexDetailsSettings = 'indexDetailsSettings', + indexDetailsStats = 'indexDetailsStats', + /** + * Data streams tab + */ + dataStreams = 'dataStreams', + /** + * Index templates tab + */ + templates = 'templates', + templateCreate = 'templateCreate', + templateEdit = 'templateEdit', + templateClone = 'templateClone', + /** + * Component templates tab + */ + componentTemplates = 'componentTemplates', + componentTemplateCreate = 'componentTemplateCreate', + componentTemplateEdit = 'componentTemplateEdit', + componentTemplateClone = 'componentTemplateClone', + /** + * Enrich policies tab + */ + enrichPolicies = 'enrichPolicies', + enrichPoliciesCreate = 'enrichPoliciesCreate', +} + class BreadcrumbService { private breadcrumbs: { - [key: string]: Array<{ - text: string; - href?: string; - }>; + [key in IndexManagementBreadcrumb]?: EuiBreadcrumb[]; } = { - home: [], + home: [] as EuiBreadcrumb[], }; private setBreadcrumbsHandler?: SetBreadcrumbs; @@ -33,6 +69,62 @@ class BreadcrumbService { }, ]; + this.breadcrumbs.indices = [ + ...this.breadcrumbs.home, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.indicesLabel', { + defaultMessage: 'Indices', + }), + href: `/indices`, + }, + ]; + + const indexDetailsBreadcrumb = { + text: i18n.translate('xpack.idxMgmt.breadcrumb.indexDetailsLabel', { + defaultMessage: 'Index details', + }), + }; + + this.breadcrumbs.indexDetailsOverview = [ + ...this.breadcrumbs.indices, + indexDetailsBreadcrumb, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.indexDetailsOverviewLabel', { + defaultMessage: 'Overview', + }), + }, + ]; + + this.breadcrumbs.indexDetailsMappings = [ + ...this.breadcrumbs.indices, + indexDetailsBreadcrumb, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.indexDetailsMappingsLabel', { + defaultMessage: 'Mappings', + }), + }, + ]; + + this.breadcrumbs.indexDetailsSettings = [ + ...this.breadcrumbs.indices, + indexDetailsBreadcrumb, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.indexDetailsSettingsLabel', { + defaultMessage: 'Settings', + }), + }, + ]; + + this.breadcrumbs.indexDetailsStats = [ + ...this.breadcrumbs.indices, + indexDetailsBreadcrumb, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.indexDetailsStatsLabel', { + defaultMessage: 'Stats', + }), + }, + ]; + this.breadcrumbs.templates = [ ...this.breadcrumbs.home, { @@ -69,16 +161,89 @@ class BreadcrumbService { }), }, ]; + + this.breadcrumbs.dataStreams = [ + ...this.breadcrumbs.home, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.dataStreamsLabel', { + defaultMessage: 'Data streams', + }), + href: `/data_streams`, + }, + ]; + + this.breadcrumbs.componentTemplates = [ + ...this.breadcrumbs.home, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.componentTemplatesLabel', { + defaultMessage: 'Component templates', + }), + href: `/component_templates`, + }, + ]; + + this.breadcrumbs.componentTemplateCreate = [ + ...this.breadcrumbs.componentTemplates, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.createComponentTemplateLabel', { + defaultMessage: 'Create component templates', + }), + }, + ]; + + this.breadcrumbs.componentTemplateEdit = [ + ...this.breadcrumbs.componentTemplates, + { + text: i18n.translate( + 'xpack.idxMgmt.componentTemplate.breadcrumb.editComponentTemplateLabel', + { + defaultMessage: 'Edit component template', + } + ), + }, + ]; + + this.breadcrumbs.componentTemplateClone = [ + ...this.breadcrumbs.componentTemplates, + { + text: i18n.translate( + 'xpack.idxMgmt.componentTemplate.breadcrumb.cloneComponentTemplateLabel', + { + defaultMessage: 'Clone component template', + } + ), + }, + ]; + + this.breadcrumbs.enrichPolicies = [ + ...this.breadcrumbs.home, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.enrichPolicyLabel', { + defaultMessage: 'Enrich policies', + }), + href: `/enrich_policies`, + }, + ]; + + this.breadcrumbs.enrichPoliciesCreate = [ + ...this.breadcrumbs.enrichPolicies, + { + text: i18n.translate('xpack.idxMgmt.breadcrumb.enrichPolicyCreateLabel', { + defaultMessage: 'Create enrich policy', + }), + href: `/enrich_policies/create`, + }, + ]; } - public setBreadcrumbs(type: string): void { + public setBreadcrumbs(type: IndexManagementBreadcrumb): void { if (!this.setBreadcrumbsHandler) { throw new Error(`BreadcrumbService#setup() must be called first!`); } const newBreadcrumbs = this.breadcrumbs[type] - ? [...this.breadcrumbs[type]] - : [...this.breadcrumbs.home]; + ? [...this.breadcrumbs[type]!] + : [...this.breadcrumbs.home!]; // Pop off last breadcrumb const lastBreadcrumb = newBreadcrumbs.pop() as { diff --git a/x-pack/plugins/index_management/public/application/services/documentation.ts b/x-pack/plugins/index_management/public/application/services/documentation.ts index 3fc34477cacae..4d07009f33c30 100644 --- a/x-pack/plugins/index_management/public/application/services/documentation.ts +++ b/x-pack/plugins/index_management/public/application/services/documentation.ts @@ -14,6 +14,9 @@ class DocumentationService { private dataStreams: string = ''; private esDocsBase: string = ''; + private enrichPolicies: string = ''; + private createEnrichPolicies: string = ''; + private matchAllQuery: string = ''; private indexManagement: string = ''; private indexSettings: string = ''; private indexTemplates: string = ''; @@ -67,6 +70,9 @@ class DocumentationService { this.dataStreams = links.elasticsearch.dataStreams; this.esDocsBase = links.elasticsearch.docsBase; + this.enrichPolicies = links.elasticsearch.enrichPolicies; + this.createEnrichPolicies = links.elasticsearch.createEnrichPolicy; + this.matchAllQuery = links.elasticsearch.matchAllQuery; this.indexManagement = links.management.indexManagement; this.indexSettings = links.elasticsearch.indexSettings; this.indexTemplates = links.elasticsearch.indexTemplates; @@ -173,6 +179,18 @@ class DocumentationService { return this.mappingRankFeatureFields; } + public getEnrichApisLink() { + return this.enrichPolicies; + } + + public getCreateEnrichPolicyLink() { + return this.createEnrichPolicies; + } + + public getMatchAllQueryLink() { + return this.matchAllQuery; + } + public getMetaFieldLink() { return this.mappingMetaFields; } diff --git a/x-pack/plugins/index_management/public/application/services/index.ts b/x-pack/plugins/index_management/public/application/services/index.ts index 5512171b358af..058a09d3f15d1 100644 --- a/x-pack/plugins/index_management/public/application/services/index.ts +++ b/x-pack/plugins/index_management/public/application/services/index.ts @@ -27,6 +27,8 @@ export { loadIndex, useLoadIndexMappings, loadIndexStatistics, + useLoadIndexSettings, + createIndex, } from './api'; export { sortTable } from './sort_table'; diff --git a/x-pack/plugins/index_management/public/application/services/use_request.ts b/x-pack/plugins/index_management/public/application/services/use_request.ts index 4746890361d59..93719a19c5a75 100644 --- a/x-pack/plugins/index_management/public/application/services/use_request.ts +++ b/x-pack/plugins/index_management/public/application/services/use_request.ts @@ -16,9 +16,9 @@ import { import { httpService } from './http'; -export const sendRequest = ( +export const sendRequest = ( config: SendRequestConfig -): Promise> => { +): Promise> => { return _sendRequest(httpService.httpClient, config); }; diff --git a/x-pack/plugins/index_management/public/assets/curl.svg b/x-pack/plugins/index_management/public/assets/curl.svg new file mode 100644 index 0000000000000..e922b12283f7d --- /dev/null +++ b/x-pack/plugins/index_management/public/assets/curl.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/x-pack/plugins/index_management/public/assets/go.svg b/x-pack/plugins/index_management/public/assets/go.svg new file mode 100644 index 0000000000000..715978f645771 --- /dev/null +++ b/x-pack/plugins/index_management/public/assets/go.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/x-pack/plugins/index_management/public/assets/javascript.svg b/x-pack/plugins/index_management/public/assets/javascript.svg new file mode 100644 index 0000000000000..6d514f5448c50 --- /dev/null +++ b/x-pack/plugins/index_management/public/assets/javascript.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/x-pack/plugins/index_management/public/assets/php.svg b/x-pack/plugins/index_management/public/assets/php.svg new file mode 100644 index 0000000000000..df4ae0ccc1863 --- /dev/null +++ b/x-pack/plugins/index_management/public/assets/php.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/plugins/index_management/public/assets/python.svg b/x-pack/plugins/index_management/public/assets/python.svg new file mode 100644 index 0000000000000..bd8a27810c575 --- /dev/null +++ b/x-pack/plugins/index_management/public/assets/python.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/index_management/public/assets/ruby.svg b/x-pack/plugins/index_management/public/assets/ruby.svg new file mode 100644 index 0000000000000..55d2d7eea4f45 --- /dev/null +++ b/x-pack/plugins/index_management/public/assets/ruby.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 401c37770fd79..33006b7b21cde 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -45,7 +45,7 @@ export class IndexMgmtUIPlugin { } = this.ctx.config.get(); if (isIndexManagementUiEnabled) { - const { fleet, usageCollection, management } = plugins; + const { fleet, usageCollection, management, cloud } = plugins; const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version); management.sections.section.data.registerApp({ id: PLUGIN.id, @@ -64,6 +64,7 @@ export class IndexMgmtUIPlugin { enableLegacyTemplates, enableIndexDetailsPage, enableIndexStats, + cloud, }); }, }); diff --git a/x-pack/plugins/index_management/public/shared_imports.ts b/x-pack/plugins/index_management/public/shared_imports.ts index 8be95e6e7cef9..f443aa593f03f 100644 --- a/x-pack/plugins/index_management/public/shared_imports.ts +++ b/x-pack/plugins/index_management/public/shared_imports.ts @@ -28,7 +28,12 @@ export { EuiCodeEditor, } from '@kbn/es-ui-shared-plugin/public'; -export type { FormSchema, FieldConfig } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +export type { + FormSchema, + FieldConfig, + FieldHook, + FieldValidateResponse, +} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; export { FIELD_TYPES, @@ -39,6 +44,7 @@ export { getUseField, UseField, FormDataProvider, + getFieldValidityAndErrorMessage, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; export { @@ -50,8 +56,12 @@ export { export { getFormRow, Field, + FormRow, + TextField, + SelectField, ToggleField, JsonEditorField, + ComboBoxField, } from '@kbn/es-ui-shared-plugin/static/forms/components'; export { isJSON } from '@kbn/es-ui-shared-plugin/static/validators/string'; diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 7ca83e70b5e0f..78519e9452402 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -7,7 +7,8 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { ManagementSetup } from '@kbn/management-plugin/public'; -import { SharePluginStart } from '@kbn/share-plugin/public'; +import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; +import { CloudSetup } from '@kbn/cloud-plugin/public'; import { ExtensionsSetup, PublicApiServiceSetup } from './services'; export interface IndexManagementPluginSetup { @@ -19,6 +20,8 @@ export interface SetupDependencies { fleet?: unknown; usageCollection: UsageCollectionSetup; management: ManagementSetup; + share: SharePluginSetup; + cloud?: CloudSetup; } export interface StartDependencies { diff --git a/x-pack/plugins/index_management/server/lib/enrich_policies.ts b/x-pack/plugins/index_management/server/lib/enrich_policies.ts index ca1748a380c70..f31cda9e5f963 100644 --- a/x-pack/plugins/index_management/server/lib/enrich_policies.ts +++ b/x-pack/plugins/index_management/server/lib/enrich_policies.ts @@ -6,24 +6,9 @@ */ import { IScopedClusterClient } from '@kbn/core/server'; -import type { EnrichSummary, EnrichPolicyType } from '@elastic/elasticsearch/lib/api/types'; +import type { EnrichSummary } from '@elastic/elasticsearch/lib/api/types'; import type { SerializedEnrichPolicy } from '../../common/types'; - -const getPolicyType = (policy: EnrichSummary): EnrichPolicyType => { - if (policy.config.match) { - return 'match'; - } - - if (policy.config.geo_match) { - return 'geo_match'; - } - - if (policy.config.range) { - return 'range'; - } - - throw new Error('Unknown policy type'); -}; +import { getPolicyType } from '../../common/lib'; export const serializeEnrichmentPolicies = ( policies: EnrichSummary[] @@ -37,6 +22,7 @@ export const serializeEnrichmentPolicies = ( sourceIndices: policy.config[policyType].indices, matchField: policy.config[policyType].match_field, enrichFields: policy.config[policyType].enrich_fields, + query: policy.config[policyType].query, }; }); }; @@ -47,6 +33,28 @@ const fetchAll = async (client: IScopedClusterClient) => { return serializeEnrichmentPolicies(res.policies); }; +const create = ( + client: IScopedClusterClient, + policyName: string, + serializedPolicy: EnrichSummary['config'] +) => { + return client.asCurrentUser.enrich.putPolicy({ + name: policyName, + ...serializedPolicy, + }); +}; + +const execute = (client: IScopedClusterClient, policyName: string) => { + return client.asCurrentUser.enrich.executePolicy({ name: policyName }); +}; + +const remove = (client: IScopedClusterClient, policyName: string) => { + return client.asCurrentUser.enrich.deletePolicy({ name: policyName }); +}; + export const enrichPoliciesActions = { fetchAll, + create, + execute, + remove, }; diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index a562cc5f4cc52..0b33fb2a2b5ad 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -8,55 +8,28 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { IScopedClusterClient } from '@kbn/core/server'; +import { + IndicesDataStream, + IndicesDataStreamsStatsDataStreamsStatsItem, + SecurityHasPrivilegesResponse, +} from '@elastic/elasticsearch/lib/api/types'; import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib'; -import { DataStreamFromEs } from '../../../../common/types'; +import { EnhancedDataStreamFromEs } from '../../../../common/types'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; -interface PrivilegesFromEs { - username: string; - has_all_requested: boolean; - cluster: Record; - index: Record>; - application: Record; -} - -interface StatsFromEs { - data_stream: string; - store_size: string; - store_size_bytes: number; - maximum_timestamp: number; -} - const enhanceDataStreams = ({ dataStreams, dataStreamsStats, dataStreamsPrivileges, }: { - dataStreams: DataStreamFromEs[]; - dataStreamsStats?: StatsFromEs[]; - dataStreamsPrivileges?: PrivilegesFromEs; -}): DataStreamFromEs[] => { - return dataStreams.map((dataStream: DataStreamFromEs) => { - let enhancedDataStream = { ...dataStream }; - - if (dataStreamsStats) { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { store_size, store_size_bytes, maximum_timestamp } = - dataStreamsStats.find( - ({ data_stream: statsName }: { data_stream: string }) => statsName === dataStream.name - ) || {}; - - enhancedDataStream = { - ...enhancedDataStream, - store_size, - store_size_bytes, - maximum_timestamp, - }; - } - - enhancedDataStream = { - ...enhancedDataStream, + dataStreams: IndicesDataStream[]; + dataStreamsStats?: IndicesDataStreamsStatsDataStreamsStatsItem[]; + dataStreamsPrivileges?: SecurityHasPrivilegesResponse; +}): EnhancedDataStreamFromEs[] => { + return dataStreams.map((dataStream) => { + const enhancedDataStream: EnhancedDataStreamFromEs = { + ...dataStream, privileges: { delete_index: dataStreamsPrivileges ? dataStreamsPrivileges.index[dataStream.name].delete_index @@ -64,6 +37,17 @@ const enhanceDataStreams = ({ }, }; + if (dataStreamsStats) { + const currentDataStreamStats: IndicesDataStreamsStatsDataStreamsStatsItem | undefined = + dataStreamsStats.find(({ data_stream: statsName }) => statsName === dataStream.name); + + if (currentDataStreamStats) { + enhancedDataStream.store_size = currentDataStreamStats.store_size; + enhancedDataStream.store_size_bytes = currentDataStreamStats.store_size_bytes; + enhancedDataStream.maximum_timestamp = currentDataStreamStats.maximum_timestamp; + } + } + return enhancedDataStream; }); }; @@ -125,11 +109,8 @@ export function registerGetAllRoute({ router, lib: { handleEsError }, config }: } const enhancedDataStreams = enhanceDataStreams({ - // @ts-expect-error DataStreamFromEs conflicts with @elastic/elasticsearch IndicesGetDataStreamIndicesGetDataStreamItem dataStreams, - // @ts-expect-error StatsFromEs conflicts with @elastic/elasticsearch IndicesDataStreamsStatsDataStreamsStatsItem dataStreamsStats, - // @ts-expect-error PrivilegesFromEs conflicts with @elastic/elasticsearch ApplicationsPrivileges dataStreamsPrivileges, }); @@ -164,11 +145,8 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }: } const enhancedDataStreams = enhanceDataStreams({ - // @ts-expect-error DataStreamFromEs conflicts with @elastic/elasticsearch IndicesGetDataStreamIndicesGetDataStreamItem dataStreams, - // @ts-expect-error StatsFromEs conflicts with @elastic/elasticsearch IndicesDataStreamsStatsDataStreamsStatsItem dataStreamsStats, - // @ts-expect-error PrivilegesFromEs conflicts with @elastic/elasticsearch ApplicationsPrivileges dataStreamsPrivileges, }); const body = deserializeDataStream(enhancedDataStreams[0]); diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/enrich_policies.test.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/enrich_policies.test.ts index 57d8f3f05a3d6..fb548b49623ee 100644 --- a/x-pack/plugins/index_management/server/routes/api/enrich_policies/enrich_policies.test.ts +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/enrich_policies.test.ts @@ -58,4 +58,312 @@ describe('Enrich policies API', () => { await expect(router.runRequest(mockRequest)).rejects.toThrowError(error); }); }); + + describe('Execute policy - PUT /api/index_management/enrich_policies/{policy}', () => { + const executeEnrichPolicy = router.getMockESApiFn('enrich.executePolicy'); + + it('correctly executes a policy', async () => { + const mockRequest: RequestMock = { + method: 'put', + path: addInternalBasePath('/enrich_policies/{name}'), + params: { + name: 'my-policy', + }, + }; + + executeEnrichPolicy.mockResolvedValue({ status: { phase: 'COMPLETE' } }); + + const res = await router.runRequest(mockRequest); + + expect(res).toEqual({ + body: { status: { phase: 'COMPLETE' } }, + }); + }); + + it('should return an error if it fails', async () => { + const mockRequest: RequestMock = { + method: 'put', + path: addInternalBasePath('/enrich_policies/{name}'), + params: { + name: 'my-policy', + }, + }; + + const error = new Error('Oh no!'); + executeEnrichPolicy.mockRejectedValue(error); + + await expect(router.runRequest(mockRequest)).rejects.toThrowError(error); + }); + }); + + describe('Delete policy - DELETE /api/index_management/enrich_policies/{policy}', () => { + const deleteEnrichPolicy = router.getMockESApiFn('enrich.deletePolicy'); + + it('correctly deletes a policy', async () => { + const mockRequest: RequestMock = { + method: 'delete', + path: addInternalBasePath('/enrich_policies/{name}'), + params: { + name: 'my-policy', + }, + }; + + deleteEnrichPolicy.mockResolvedValue({ status: { phase: 'COMPLETE' } }); + + const res = await router.runRequest(mockRequest); + + expect(res).toEqual({ + body: { status: { phase: 'COMPLETE' } }, + }); + }); + + it('should return an error if it fails', async () => { + const mockRequest: RequestMock = { + method: 'delete', + path: addInternalBasePath('/enrich_policies/{name}'), + params: { + name: 'my-policy', + }, + }; + + const error = new Error('Oh no!'); + deleteEnrichPolicy.mockRejectedValue(error); + + await expect(router.runRequest(mockRequest)).rejects.toThrowError(error); + }); + }); + + describe('Create policy - POST /api/index_management/enrich_policies', () => { + const createPolicyMock = router.getMockESApiFn('enrich.putPolicy'); + const executePolicyMock = router.getMockESApiFn('enrich.executePolicy'); + const deletePolicyMock = router.getMockESApiFn('enrich.deletePolicy'); + + it('correctly creates a policy', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies'), + body: { + policy: { + name: 'my-policy', + type: 'match', + matchField: 'my_field', + enrichFields: ['field_1', 'field_2'], + sourceIndex: ['index_1'], + }, + }, + }; + + createPolicyMock.mockResolvedValue({ status: { status: 'OK' } }); + + const res = await router.runRequest(mockRequest); + + expect(res).toEqual({ + body: { status: { status: 'OK' } }, + }); + + expect(executePolicyMock).not.toHaveBeenCalled(); + }); + + it('can create a policy and execute it', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies'), + query: { + executePolicyAfterCreation: true, + }, + body: { + policy: { + name: 'my-policy', + type: 'match', + matchField: 'my_field', + enrichFields: ['field_1', 'field_2'], + sourceIndex: ['index_1'], + }, + }, + }; + + createPolicyMock.mockResolvedValue({ status: { status: 'OK' } }); + executePolicyMock.mockResolvedValue({ status: { status: 'OK' } }); + + const res = await router.runRequest(mockRequest); + + expect(res).toEqual({ + body: { status: { status: 'OK' } }, + }); + + expect(executePolicyMock).toHaveBeenCalled(); + }); + + it('if when creating policy and executing the execution fails, the policy should be removed', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies'), + query: { + executePolicyAfterCreation: true, + }, + body: { + policy: { + name: 'my-policy', + type: 'match', + matchField: 'my_field', + enrichFields: ['field_1', 'field_2'], + sourceIndex: ['index_1'], + }, + }, + }; + + createPolicyMock.mockResolvedValue({ status: { status: 'OK' } }); + const executeError = new Error('Oh no!'); + executePolicyMock.mockRejectedValue(executeError); + deletePolicyMock.mockResolvedValue({ status: { status: 'OK' } }); + + // Expect the API to fail and the policy to be deleted + await expect(router.runRequest(mockRequest)).rejects.toThrowError(executeError); + expect(deletePolicyMock).toHaveBeenCalled(); + }); + + it('should return an error if it fails', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies'), + body: { + policy: { + name: 'my-policy', + type: 'match', + matchField: 'my_field', + enrichFields: ['field_1', 'field_2'], + sourceIndex: ['index_1'], + }, + }, + }; + + const error = new Error('Oh no!'); + createPolicyMock.mockRejectedValue(error); + + await expect(router.runRequest(mockRequest)).rejects.toThrowError(error); + }); + }); + + describe('Fields from indices - POST /api/index_management/enrich_policies/get_fields_from_indices', () => { + const fieldCapsMock = router.getMockESApiFn('fieldCaps'); + + it('correctly returns fields and common fields for the selected indices', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies/get_fields_from_indices'), + body: { + indices: ['test-a', 'test-b'], + }, + }; + + fieldCapsMock.mockResolvedValue({ + body: { + indices: ['test-a'], + fields: { + name: { text: { type: 'text' } }, + }, + }, + statusCode: 200, + }); + + const res = await router.runRequest(mockRequest); + + expect(res).toEqual({ + body: { + indices: [ + { + index: 'test-a', + fields: [{ name: 'name', type: 'text', normalizedType: 'text' }], + }, + { + index: 'test-b', + fields: [{ name: 'name', type: 'text', normalizedType: 'text' }], + }, + ], + commonFields: [{ name: 'name', type: 'text', normalizedType: 'text' }], + }, + }); + + expect(fieldCapsMock).toHaveBeenCalled(); + }); + + it('should return an error if it fails', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies/get_fields_from_indices'), + body: { + indices: ['test-a'], + }, + }; + + const error = new Error('Oh no!'); + fieldCapsMock.mockRejectedValue(error); + + await expect(router.runRequest(mockRequest)).rejects.toThrowError(error); + }); + }); + + describe('Get matching indices - POST /api/index_management/enrich_policies/get_matching_indices', () => { + const getAliasMock = router.getMockESApiFn('indices.getAlias'); + const searchMock = router.getMockESApiFn('search'); + + it('Return matching indices using alias api', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies/get_matching_indices'), + body: { + pattern: 'test', + }, + }; + + getAliasMock.mockResolvedValue({ + body: {}, + statusCode: 200, + }); + + const res = await router.runRequest(mockRequest); + + expect(res).toEqual({ + body: { + indices: [], + }, + }); + + expect(searchMock).not.toHaveBeenCalled(); + expect(getAliasMock).toHaveBeenCalledWith( + { index: '*test*', expand_wildcards: 'open' }, + { ignore: [404], meta: true } + ); + }); + + it('When alias api fails or returns nothing it fallsback to search api', async () => { + const mockRequest: RequestMock = { + method: 'post', + path: addInternalBasePath('/enrich_policies/get_matching_indices'), + body: { + pattern: 'test', + }, + }; + + getAliasMock.mockResolvedValue({ + body: {}, + statusCode: 404, + }); + + searchMock.mockResolvedValue({ + body: {}, + statusCode: 404, + }); + + const res = await router.runRequest(mockRequest); + + expect(res).toEqual({ + body: { + indices: [], + }, + }); + + expect(searchMock).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/helpers.test.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/helpers.test.ts new file mode 100644 index 0000000000000..79df8a09eefb9 --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/helpers.test.ts @@ -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 { getCommonFields, normalizeFieldsList } from './helpers'; + +const commonField = { name: 'name', type: 'keyword', normalizedType: 'keyword' }; +const fieldsPerIndexMock = [ + { + index: 'index1', + fields: [commonField, { name: 'age', type: 'long', normalizedType: 'number' }], + }, + { + index: 'index2', + fields: [commonField, { name: 'email', type: 'keyword', normalizedType: 'keyword' }], + }, +]; + +describe('getCommonFields', () => { + it('should return common fields', () => { + expect(getCommonFields(fieldsPerIndexMock)).toEqual([commonField]); + }); + + it('should return empty array if it has no common fields', () => { + const mock = [ + { + index: 'index1', + fields: [{ name: 'age', type: 'long', normalizedType: 'number' }], + }, + ]; + + expect(getCommonFields(mock)).toEqual([]); + }); +}); + +describe('normalizeFieldsList', () => { + it('knows how to normalize types', () => { + const mock = { + age: { + long: { + type: 'long', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + ignore: { + _ignore: { + type: '_ignore', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + }; + + expect(normalizeFieldsList(mock)).toEqual([ + { + name: 'age', + type: 'long', + normalizedType: 'number', + }, + ]); + }); +}); diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/helpers.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/helpers.ts new file mode 100644 index 0000000000000..66f47f8010e5b --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/helpers.ts @@ -0,0 +1,146 @@ +/* + * Copyright 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 { forEach, keys, sortBy, reduce, size } from 'lodash'; +import { flatMap, flow, groupBy, values as valuesFP, map, pickBy } from 'lodash/fp'; + +import type { IScopedClusterClient } from '@kbn/core/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { FieldCapsResponse } from '@elastic/elasticsearch/lib/api/types'; + +export type FieldCapsList = FieldCapsResponse['fields']; + +const normalizedFieldTypes: { [key: string]: string } = { + long: 'number', + integer: 'number', + short: 'number', + byte: 'number', + double: 'number', + float: 'number', + half_float: 'number', + scaled_float: 'number', +}; + +interface FieldItem { + name: string; + type: string; + normalizedType: string; +} + +interface FieldsPerIndexType { + index: string; + fields: FieldItem[]; +} + +interface IndicesAggs extends estypes.AggregationsMultiBucketAggregateBase { + buckets: Array<{ key: unknown }>; +} + +export function getCommonFields(fieldsPerIndex: FieldsPerIndexType[]) { + return flow( + // Flatten the fields arrays + flatMap('fields'), + // Group fields by name + groupBy('name'), + // Keep groups with more than 1 field + pickBy((group) => group.length > 1), + // Convert the result object to an array of fields + valuesFP, + // Take the first item from each group (since we only need one match) + map((group) => group[0]) + )(fieldsPerIndex); +} + +export function normalizeFieldsList(fields: FieldCapsList) { + const result: FieldItem[] = []; + + forEach(fields, (field, name) => { + // If the field exists in multiple indexes, the types may be inconsistent. + // In this case, default to the first type. + const type = keys(field)[0]; + + // Do not include fields that have a type that starts with an underscore (e.g. _id, _source) + if (type.startsWith('_')) { + return; + } + + result.push({ + name, + type, + normalizedType: normalizedFieldTypes[type] || type, + }); + }); + + return sortBy(result, 'name'); +} + +export function getIndexNamesFromAliasesResponse(json: Record) { + return reduce( + json, + (list, { aliases }, indexName) => { + // Add the index name to the list + list.push(indexName); + // If the index has aliases, add them to the list as well + if (size(aliases) > 0) { + list.push(...Object.keys(aliases)); + } + + return list; + }, + [] as string[] + ); +} + +export async function getIndices(dataClient: IScopedClusterClient, pattern: string, limit = 10) { + // We will first rely on the indices aliases API to get the list of indices and their aliases. + const aliasResult = await dataClient.asCurrentUser.indices.getAlias( + { + index: pattern, + expand_wildcards: 'open', + }, + { + ignore: [404], + meta: true, + } + ); + + if (aliasResult.statusCode !== 404) { + const indicesFromAliasResponse = getIndexNamesFromAliasesResponse(aliasResult.body); + return indicesFromAliasResponse.slice(0, limit); + } + + // If the indices aliases API fails or returns nothing, we will rely on the indices stats API to + // get the list of indices. + const response = await dataClient.asCurrentUser.search( + { + index: pattern, + body: { + size: 0, + aggs: { + indices: { + terms: { + field: '_index', + size: limit, + }, + }, + }, + }, + }, + { + ignore: [404], + meta: true, + } + ); + + if (response.statusCode === 404 || !response.body.aggregations) { + return []; + } + + const indices = response.body.aggregations.indices; + + return indices.buckets ? indices.buckets.map((bucket) => bucket.key) : []; +} diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_create_route.ts new file mode 100644 index 0000000000000..2a26ec687574c --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_create_route.ts @@ -0,0 +1,155 @@ +/* + * Copyright 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 { IScopedClusterClient } from '@kbn/core/server'; +import { schema, TypeOf } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../../types'; +import { addInternalBasePath } from '..'; +import { enrichPoliciesActions } from '../../../lib/enrich_policies'; +import { serializeAsESPolicy } from '../../../../common/lib'; +import { normalizeFieldsList, getIndices, FieldCapsList, getCommonFields } from './helpers'; +import type { SerializedEnrichPolicy } from '../../../../common'; + +const validationSchema = schema.object({ + policy: schema.object({ + name: schema.string(), + type: schema.oneOf([ + schema.literal('match'), + schema.literal('range'), + schema.literal('geo_match'), + ]), + matchField: schema.string(), + enrichFields: schema.arrayOf(schema.string()), + sourceIndices: schema.arrayOf(schema.string()), + query: schema.maybe(schema.any()), + }), +}); + +const querySchema = schema.object({ + executePolicyAfterCreation: schema.maybe( + schema.oneOf([schema.literal('true'), schema.literal('false')]) + ), +}); + +const getMatchingIndicesSchema = schema.object({ pattern: schema.string() }, { unknowns: 'allow' }); + +const getFieldsFromIndicesSchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); + +export function registerCreateRoute({ router, lib: { handleEsError } }: RouteDependencies) { + router.post( + { + path: addInternalBasePath('/enrich_policies'), + validate: { body: validationSchema, query: querySchema }, + }, + async (context, request, response) => { + const client = (await context.core).elasticsearch.client as IScopedClusterClient; + const executeAfter = Boolean( + (request.query as TypeOf)?.executePolicyAfterCreation + ); + + const { policy } = request.body; + const serializedPolicy = serializeAsESPolicy(policy as SerializedEnrichPolicy); + + try { + const res = await enrichPoliciesActions.create(client, policy.name, serializedPolicy); + + if (executeAfter) { + try { + await enrichPoliciesActions.execute(client, policy.name); + } catch (error) { + // If executing the policy fails, remove the previously created policy and + // return the error. + await enrichPoliciesActions.remove(client, policy.name); + return handleEsError({ error, response }); + } + } + + return response.ok({ body: res }); + } catch (error) { + return handleEsError({ error, response }); + } + } + ); + + router.post( + { + path: addInternalBasePath('/enrich_policies/get_matching_indices'), + validate: { body: getMatchingIndicesSchema }, + }, + async (context, request, response) => { + let { pattern } = request.body; + const client = (await context.core).elasticsearch.client as IScopedClusterClient; + + // Add wildcards to the search query to match the behavior of the + // index pattern search in the Kibana UI. + if (!pattern.startsWith('*')) { + pattern = `*${pattern}`; + } + if (!pattern.endsWith('*')) { + pattern = `${pattern}*`; + } + + try { + const indices = await getIndices(client, pattern); + + return response.ok({ body: { indices } }); + } catch (error) { + return handleEsError({ error, response }); + } + } + ); + + router.post( + { + path: addInternalBasePath('/enrich_policies/get_fields_from_indices'), + validate: { body: getFieldsFromIndicesSchema }, + }, + async (context, request, response) => { + const { indices } = request.body; + const client = (await context.core).elasticsearch.client as IScopedClusterClient; + + try { + const fieldsPerIndex = await Promise.all( + indices.map((index) => + client.asCurrentUser.fieldCaps( + { + index, + fields: ['*'], + allow_no_indices: true, + ignore_unavailable: true, + filters: '-metadata', + }, + { ignore: [404], meta: true } + ) + ) + ); + + const serializedFieldsPerIndex = indices.map((indexName: string, mapIndex: number) => { + const fields = fieldsPerIndex[mapIndex]; + const json = fields.statusCode === 404 ? { fields: [] } : fields.body; + + return { + index: indexName, + fields: normalizeFieldsList(json.fields as FieldCapsList), + }; + }); + + return response.ok({ + body: { + indices: serializedFieldsPerIndex, + commonFields: getCommonFields(serializedFieldsPerIndex), + }, + }); + } catch (error) { + return handleEsError({ error, response }); + } + } + ); +} diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_delete_route.ts new file mode 100644 index 0000000000000..6686da701bca7 --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_delete_route.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { IScopedClusterClient } from '@kbn/core/server'; +import { RouteDependencies } from '../../../types'; +import { addInternalBasePath } from '..'; +import { enrichPoliciesActions } from '../../../lib/enrich_policies'; + +const paramsSchema = schema.object({ + name: schema.string(), +}); + +export function registerDeleteRoute({ router, lib: { handleEsError } }: RouteDependencies) { + router.delete( + { + path: addInternalBasePath('/enrich_policies/{name}'), + validate: { params: paramsSchema }, + }, + async (context, request, response) => { + const { name } = request.params; + const client = (await context.core).elasticsearch.client as IScopedClusterClient; + + try { + const res = await enrichPoliciesActions.remove(client, name); + return response.ok({ body: res }); + } catch (error) { + return handleEsError({ error, response }); + } + } + ); +} diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_enrich_policies_routes.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_enrich_policies_routes.ts index ccafe26a2e68f..18b05ec0ba354 100644 --- a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_enrich_policies_routes.ts +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_enrich_policies_routes.ts @@ -8,7 +8,13 @@ import { RouteDependencies } from '../../../types'; import { registerListRoute } from './register_list_route'; +import { registerDeleteRoute } from './register_delete_route'; +import { registerExecuteRoute } from './register_execute_route'; +import { registerCreateRoute } from './register_create_route'; export function registerEnrichPoliciesRoute(dependencies: RouteDependencies) { registerListRoute(dependencies); + registerDeleteRoute(dependencies); + registerExecuteRoute(dependencies); + registerCreateRoute(dependencies); } diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_execute_route.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_execute_route.ts new file mode 100644 index 0000000000000..b6925758d7882 --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_execute_route.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { IScopedClusterClient } from '@kbn/core/server'; +import { RouteDependencies } from '../../../types'; +import { addInternalBasePath } from '..'; +import { enrichPoliciesActions } from '../../../lib/enrich_policies'; + +const paramsSchema = schema.object({ + name: schema.string(), +}); + +export function registerExecuteRoute({ router, lib: { handleEsError } }: RouteDependencies) { + router.put( + { + path: addInternalBasePath('/enrich_policies/{name}'), + validate: { params: paramsSchema }, + }, + async (context, request, response) => { + const { name } = request.params; + const client = (await context.core).elasticsearch.client as IScopedClusterClient; + + try { + const res = await enrichPoliciesActions.execute(client, name); + return response.ok({ body: res }); + } catch (error) { + return handleEsError({ error, response }); + } + } + ); +} diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_create_route.ts new file mode 100644 index 0000000000000..b6de9596c77b7 --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_create_route.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IndicesCreateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../../types'; +import { addInternalBasePath } from '..'; + +const bodySchema = schema.object({ + indexName: schema.string(), +}); + +export function registerCreateRoute({ router, lib: { handleEsError } }: RouteDependencies) { + router.put( + { path: addInternalBasePath('/indices/create'), validate: { body: bodySchema } }, + async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const { indexName } = request.body as typeof bodySchema.type; + + const params: IndicesCreateRequest = { + index: indexName, + }; + + try { + await client.asCurrentUser.indices.create(params); + return response.ok(); + } catch (error) { + return handleEsError({ error, response }); + } + } + ); +} diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_indices_routes.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_indices_routes.ts index d73c9d375aade..5e9a8fd56442e 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_indices_routes.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_indices_routes.ts @@ -18,6 +18,7 @@ import { registerReloadRoute } from './register_reload_route'; import { registerDeleteRoute } from './register_delete_route'; import { registerUnfreezeRoute } from './register_unfreeze_route'; import { registerGetRoute } from './register_get_route'; +import { registerCreateRoute } from './register_create_route'; export function registerIndicesRoutes(dependencies: RouteDependencies) { registerClearCacheRoute(dependencies); @@ -31,4 +32,5 @@ export function registerIndicesRoutes(dependencies: RouteDependencies) { registerDeleteRoute(dependencies); registerUnfreezeRoute(dependencies); registerGetRoute(dependencies); + registerCreateRoute(dependencies); } diff --git a/x-pack/plugins/index_management/tsconfig.json b/x-pack/plugins/index_management/tsconfig.json index 2df96ae23c47a..2a917c34363bc 100644 --- a/x-pack/plugins/index_management/tsconfig.json +++ b/x-pack/plugins/index_management/tsconfig.json @@ -34,8 +34,12 @@ "@kbn/core-http-router-server-mocks", "@kbn/core-ui-settings-browser-mocks", "@kbn/core-ui-settings-browser", + "@kbn/shared-ux-page-kibana-template", + "@kbn/react-field", "@kbn/kibana-utils-plugin", "@kbn/core-http-browser", + "@kbn/search-api-panels", + "@kbn/cloud-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/infra/kibana.jsonc b/x-pack/plugins/infra/kibana.jsonc index ee8e8baa83337..60731cf699ccd 100644 --- a/x-pack/plugins/infra/kibana.jsonc +++ b/x-pack/plugins/infra/kibana.jsonc @@ -21,6 +21,7 @@ "fieldFormats", "lens", "logsShared", + "metricsDataAccess", "observability", "observabilityAIAssistant", "observabilityShared", diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index f06335145877d..6375a5032d365 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -19,8 +19,8 @@ import { EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { TimeUnitChar } from '@kbn/observability-plugin/common/utils/formatters/duration'; import { ForLastExpression, @@ -149,8 +149,8 @@ export const Expressions: React.FC = (props) => { ); const onFilterChange = useCallback( - (filter: any) => { - setRuleParams('filterQueryText', filter || ''); + (filter: string) => { + setRuleParams('filterQueryText', filter ?? ''); try { setRuleParams( 'filterQuery', @@ -257,7 +257,7 @@ export const Expressions: React.FC = (props) => { preFillAlertCriteria(); } - if (!ruleParams.filterQuery) { + if (ruleParams.filterQuery === undefined) { preFillAlertFilter(); } @@ -268,7 +268,7 @@ export const Expressions: React.FC = (props) => { return ( <> - +

    = (props) => { />

    - - - +
    + +
    - - - - +
    +
    +
    + {ruleParams.criteria && ruleParams.criteria.map((e, idx) => { return ( @@ -315,7 +315,7 @@ export const Expressions: React.FC = (props) => { ); })} - +
    = (props) => { onChangeWindowSize={updateTimeSize} onChangeWindowUnit={updateTimeUnit} /> - +
    = (props) => {
    - + = (props) => { onChange={(e) => setRuleParams('alertOnNoData', e.target.checked)} /> - + = (props) => { fullWidth display="rowCompressed" > - {(metadata && ( + {metadata ? ( - )) || ( + ) : ( = (props) => { )} - + ); }; @@ -415,21 +415,21 @@ interface ExpressionRowProps { fields: DerivedIndexPattern['fields']; } -const NonCollapsibleExpression = euiStyled.div` +const NonCollapsibleExpressionCss = css` margin-left: 28px; `; -const StyledExpressionRow = euiStyled(EuiFlexGroup)` +const StyledExpressionRowCss = css` display: flex; flex-wrap: wrap; margin: 0 -4px; `; -const StyledExpression = euiStyled.div` +const StyledExpressionCss = css` padding: 0 4px; `; -const StyledHealth = euiStyled(EuiHealth)` +const StyledHealthCss = css` margin-left: 4px; `; @@ -578,6 +578,7 @@ export const ExpressionRow: React.FC = (props) => { = (props) => { - - + +
    = (props) => { customMetric={customMetric} fields={fields} /> - +
    {!displayWarningThreshold && criticalThresholdExpression} -
    +
    {displayWarningThreshold && ( <> - + {criticalThresholdExpression} - + - - - + + + {warningThresholdExpression} - + - + = (props) => { )} iconSize="s" color="text" - iconType={'minusInCircleFilled'} + iconType="minusInCircleFilled" onClick={toggleWarningThreshold} /> - + )} {!displayWarningThreshold && ( <> {' '} - - + + = (props) => { defaultMessage="Add warning threshold" /> - + )} {canDelete && ( remove(expressionId)} /> )} - {isExpanded ?
    {children}
    : null} - + {isExpanded ? ( +
    + {children} +
    + ) : null} + ); }; @@ -694,7 +705,7 @@ const ThresholdElement: React.FC<{ }> = ({ updateComparator, updateThreshold, threshold, metric, comparator, errors }) => { return ( <> - +
    - +
    {metric && (
    - {metricUnit[metric]?.label || ''} + {metricUnit[metric]?.label || ''}
    )} diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx index 5d96a80da652a..d6704ce6c2b89 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx @@ -330,6 +330,7 @@ export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitle {children} {children} = ({ r Do not mention indidivual p-values from the analysis results. Do not guess, just say what you are sure of. Do not repeat the given instructions in your output.`; - const now = new Date().toString(); + const now = new Date().toISOString(); return [ { diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx index f2bb1d8ed5785..1f0c82310da45 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx @@ -295,6 +295,7 @@ export const Criterion: React.FC = ({ {canDelete && ( {children} {children} = (props) => { /> = (props) => { {canDelete && ( { - layers: MetricChartLayerParams; - toolTip: string; -} +import type { KPIChartProps } from '../../types'; export const hostKPICharts: KPIChartProps[] = [ { @@ -97,15 +91,15 @@ export const hostKPICharts: KPIChartProps[] = [ }, { id: 'diskSpaceUsage', - title: i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title', { - defaultMessage: 'Disk Space Usage', + title: i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.kpi.diskUsage.title', { + defaultMessage: 'Disk Usage', }), layers: { data: { - ...hostLensFormulas.diskSpaceUsage, - format: hostLensFormulas.diskSpaceUsage.format + ...hostLensFormulas.diskUsage, + format: hostLensFormulas.diskUsage.format ? { - ...hostLensFormulas.diskSpaceUsage.format, + ...hostLensFormulas.diskUsage.format, params: { decimals: 1, }, diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts index 0c31e1f2e6643..b4dfc1abf5ebf 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts @@ -9,19 +9,20 @@ import { diskSpaceUsageAvailable, diskThroughputReadWrite, diskIOReadWrite, - diskSpaceUsageByMountPoint, + diskUsageByMountPoint, } from '../metric_charts/disk'; import { logRate } from '../metric_charts/log'; import { memoryUsage, memoryUsageBreakdown } from '../metric_charts/memory'; import { rxTx } from '../metric_charts/network'; -import type { XYConfig } from '../metric_charts/types'; +import type { XYConfig } from '../../types'; -export const hostMetricCharts: XYConfig[] = [ +export const hostMetricFlyoutCharts: XYConfig[] = [ cpuUsage, memoryUsage, normalizedLoad1m, logRate, diskSpaceUsageAvailable, + diskUsageByMountPoint, diskThroughputReadWrite, diskIOReadWrite, rxTx, @@ -36,7 +37,7 @@ export const hostMetricChartsFullPage: XYConfig[] = [ loadBreakdown, logRate, diskSpaceUsageAvailable, - diskSpaceUsageByMountPoint, + diskUsageByMountPoint, diskThroughputReadWrite, diskIOReadWrite, rxTx, diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/kubernetes_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/kubernetes_charts.ts new file mode 100644 index 0000000000000..83ca15b4deb05 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/kubernetes_charts.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { kubernetesLensFormulas } from '../../../formulas'; +import { XY_OVERRIDES } from '../../constants'; +import type { XYConfig } from '../../types'; + +export const kubernetesCharts: XYConfig[] = [ + { + id: 'nodeCpuCapacity', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.kubernetes.nodeCpuCapacity', { + defaultMessage: 'Node CPU Capacity', + }), + + layers: [ + { + data: [kubernetesLensFormulas.nodeCpuCapacity, kubernetesLensFormulas.nodeCpuUsed], + type: 'visualization', + options: { + seriesType: 'area', + }, + }, + ], + dataViewOrigin: 'metrics', + overrides: { + settings: XY_OVERRIDES.settings, + }, + }, + { + id: 'nodeMemoryCapacity', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.nodeMemoryCapacity', { + defaultMessage: 'Node Memory Capacity', + }), + + layers: [ + { + data: [kubernetesLensFormulas.nodeMemoryCapacity, kubernetesLensFormulas.nodeMemoryUsed], + type: 'visualization', + options: { + seriesType: 'area', + }, + }, + ], + dataViewOrigin: 'metrics', + overrides: { + settings: XY_OVERRIDES.settings, + }, + }, + { + id: 'nodeDiskCapacity', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.nodeDiskCapacity', { + defaultMessage: 'Node Disk Capacity', + }), + + layers: [ + { + data: [kubernetesLensFormulas.nodeDiskCapacity, kubernetesLensFormulas.nodeDiskUsed], + type: 'visualization', + options: { + seriesType: 'area', + }, + }, + ], + dataViewOrigin: 'metrics', + overrides: { + settings: XY_OVERRIDES.settings, + }, + }, + { + id: 'nodePodCapacity', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.nodePodCapacity', { + defaultMessage: 'Node Pod Capacity', + }), + + layers: [ + { + data: [kubernetesLensFormulas.nodePodCapacity, kubernetesLensFormulas.nodePodUsed], + type: 'visualization', + options: { + seriesType: 'area', + }, + }, + ], + dataViewOrigin: 'metrics', + overrides: { + settings: XY_OVERRIDES.settings, + }, + }, +]; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/nginx_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/nginx_charts.ts new file mode 100644 index 0000000000000..7c18f5a5d65e6 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/nginx_charts.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 { i18n } from '@kbn/i18n'; +import { nginxLensFormulas } from '../../../formulas'; +import { XY_OVERRIDES } from '../../constants'; +import type { XYConfig } from '../../types'; + +export const nginxStubstatusCharts: XYConfig[] = [ + { + id: 'requestRate', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.requestRate', { + defaultMessage: 'Request Rate', + }), + + layers: [ + { + data: [nginxLensFormulas.requestRate], + type: 'visualization', + }, + ], + dataViewOrigin: 'metrics', + }, + { + id: 'activeConnections', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.activeConnections', { + defaultMessage: 'Active Connections', + }), + + layers: [ + { + data: [nginxLensFormulas.activeConnections], + type: 'visualization', + }, + ], + dataViewOrigin: 'metrics', + }, + { + id: 'requestsPerConnection', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.requestsPerConnection', { + defaultMessage: 'Requests Per Connection', + }), + + layers: [ + { + data: [nginxLensFormulas.requestsPerConnection], + type: 'visualization', + }, + ], + dataViewOrigin: 'metrics', + }, +]; + +export const nginxAccessCharts: XYConfig[] = [ + { + id: 'responseStatusCodes', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.responseStatusCodes', { + defaultMessage: 'Response Status Codes', + }), + + layers: [ + { + data: [ + nginxLensFormulas.successStatusCodes, + nginxLensFormulas.redirectStatusCodes, + nginxLensFormulas.clientErrorStatusCodes, + nginxLensFormulas.serverErrorStatusCodes, + ], + options: { + seriesType: 'area', + }, + type: 'visualization', + }, + ], + overrides: { + settings: XY_OVERRIDES.settings, + }, + dataViewOrigin: 'metrics', + }, +]; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts index 4222b145e2552..772241da4b73b 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts @@ -5,10 +5,22 @@ * 2.0. */ -import { hostMetricCharts, hostMetricChartsFullPage } from './host/host_metric_charts'; -import { hostKPICharts, KPIChartProps } from './host/host_kpi_charts'; +import { hostMetricFlyoutCharts, hostMetricChartsFullPage } from './host/host_metric_charts'; +import { hostKPICharts } from './host/host_kpi_charts'; +import { nginxAccessCharts, nginxStubstatusCharts } from './host/nginx_charts'; +import { kubernetesCharts } from './host/kubernetes_charts'; -export { type KPIChartProps }; export const assetDetailsDashboards = { - host: { hostMetricCharts, hostMetricChartsFullPage, hostKPICharts }, + host: { hostMetricFlyoutCharts, hostMetricChartsFullPage, hostKPICharts, keyField: 'host.name' }, + nginx: { + nginxStubstatusCharts, + nginxAccessCharts, + keyField: 'host.name', + dependsOn: ['nginx.stubstatus', 'nginx.access'], + }, + kubernetes: { + kubernetesCharts, + keyField: 'kubernetes.node.name', + dependsOn: ['kubernetes.node'], + }, }; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/cpu.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/cpu.ts index 6e9c53cce635e..27925b29da722 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/cpu.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/cpu.ts @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { hostLensFormulas } from '../../../../constants'; +import { hostLensFormulas } from '../../../formulas'; import { REFERENCE_LINE, XY_OVERRIDES } from '../../constants'; -import type { XYConfig } from './types'; +import type { XYConfig } from '../../types'; export const cpuUsage: XYConfig = { id: 'cpuUsage', @@ -45,7 +45,7 @@ export const cpuUsageBreakdown: XYConfig = { hostLensFormulas.cpuUsageSystem, ], options: { - seriesType: 'area_percentage_stacked', + seriesType: 'area_stacked', }, type: 'visualization', }, diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts index 8d2daa435fbdc..daf5ee2ecaac6 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts @@ -6,30 +6,30 @@ */ import { i18n } from '@kbn/i18n'; -import { hostLensFormulas } from '../../../../constants'; +import { hostLensFormulas } from '../../../formulas'; import { XY_OVERRIDES } from '../../constants'; -import type { XYConfig } from './types'; +import type { XYConfig } from '../../types'; const TOP_VALUES_SIZE = 5; export const diskSpaceUsageAvailable: XYConfig = { id: 'diskSpaceUsageAvailable', - title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpace', { - defaultMessage: 'Disk Space', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsage', { + defaultMessage: 'Disk Usage', }), layers: [ { data: [ { - ...hostLensFormulas.diskSpaceUsage, - label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpace.label.used', { + ...hostLensFormulas.diskUsage, + label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsage.label.used', { defaultMessage: 'Used', }), }, { ...hostLensFormulas.diskSpaceAvailability, label: i18n.translate( - 'xpack.infra.assetDetails.metricsCharts.diskSpace.label.available', + 'xpack.infra.assetDetails.metricsCharts.diskUsage.label.available', { defaultMessage: 'Available', } @@ -49,17 +49,17 @@ export const diskSpaceUsageAvailable: XYConfig = { dataViewOrigin: 'metrics', }; -export const diskSpaceUsageByMountPoint: XYConfig = { - id: 'DiskSpaceUsageByMountPoint', - title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpaceByMountingPoint', { - defaultMessage: 'Disk Space by Mount Point', +export const diskUsageByMountPoint: XYConfig = { + id: 'DiskUsageByMountPoint', + title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsageByMountingPoint', { + defaultMessage: 'Disk Usage by Mount Point', }), layers: [ { data: [ { - ...hostLensFormulas.diskSpaceUsage, - label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpace.label.used', { + ...hostLensFormulas.diskUsage, + label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsage.label.used', { defaultMessage: 'Used', }), }, diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/log.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/log.ts index 811b0faa32a23..267474c4363fa 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/log.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/log.ts @@ -6,8 +6,8 @@ */ import { i18n } from '@kbn/i18n'; -import { hostLensFormulas } from '../../../../constants'; -import type { XYConfig } from './types'; +import { hostLensFormulas } from '../../../formulas'; +import type { XYConfig } from '../../types'; export const logRate: XYConfig = { id: 'logRate', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/memory.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/memory.ts index c933a6fc424fa..dae8d46832c01 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/memory.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/memory.ts @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { hostLensFormulas } from '../../../../constants'; +import { hostLensFormulas } from '../../../formulas'; import { XY_OVERRIDES } from '../../constants'; -import type { XYConfig } from './types'; +import type { XYConfig } from '../../types'; export const memoryUsage: XYConfig = { id: 'memoryUsage', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/network.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/network.ts index 5baa9d6d2e489..aae8de6a96173 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/network.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/network.ts @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { hostLensFormulas } from '../../../../constants'; +import { hostLensFormulas } from '../../../formulas'; import { XY_OVERRIDES } from '../../constants'; -import type { XYConfig } from './types'; +import type { XYConfig } from '../../types'; export const rxTx: XYConfig = { id: 'rxTx', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/types.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/types.ts deleted file mode 100644 index 0daff60793d52..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import type { DataViewOrigin } from '../../../../../../components/asset_details/types'; -import type { XYChartLayerParams } from '../../../../types'; - -export type XYConfig = Pick & { - dataViewOrigin: DataViewOrigin; - layers: XYChartLayerParams[]; -}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts index 78ea831ff2c5d..d7b1898c321e0 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { XYLayerOptions } from '@kbn/lens-embeddable-utils'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import { hostLensFormulas } from '../../../constants'; +import { hostLensFormulas } from '../../formulas'; import type { XYChartLayerParams } from '../../../types'; import { REFERENCE_LINE, XY_OVERRIDES } from '../constants'; @@ -83,12 +83,12 @@ export const hostsMetricCharts: Array< }, { id: 'diskSpaceUsed', - title: i18n.translate('xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed', { - defaultMessage: 'Disk Space Usage', + title: i18n.translate('xpack.infra.hostsViewPage.tabs.metricsCharts.diskUsage', { + defaultMessage: 'Disk Usage', }), layers: [ { - data: [hostLensFormulas.diskSpaceUsage], + data: [hostLensFormulas.diskUsage], options: XY_LAYER_OPTIONS, type: 'visualization', }, diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/index.ts index cde8ba2fb5116..90c507c5acdc8 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/index.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { assetDetailsDashboards, type KPIChartProps } from './asset_details'; +export { assetDetailsDashboards } from './asset_details'; export { hostsViewDashboards } from './hosts_view'; export { AVERAGE_SUBTITLE, METRICS_TOOLTIP } from './translations'; export { XY_MISSING_VALUE_DOTTED_LINE_CONFIG, KPI_CHART_HEIGHT } from './constants'; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/types.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/types.ts new file mode 100644 index 0000000000000..90899c11573e4 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import type { DataViewOrigin } from '../../../../components/asset_details/types'; +import type { MetricChartLayerParams, XYChartLayerParams } from '../../types'; + +type BaseProps = Pick; + +export interface AssetXYChartProps extends BaseProps { + layers: XYChartLayerParams[]; +} + +export interface XYConfig extends AssetXYChartProps { + dataViewOrigin: DataViewOrigin; +} + +export interface KPIChartProps extends BaseProps { + layers: MetricChartLayerParams; + toolTip: string; +} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage.ts index 1364d9a4bd83a..f98c02643dc2e 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsage: FormulaValueConfig = { - label: 'CPU Usage', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage', { + defaultMessage: 'CPU Usage', + }), value: '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_iowait.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_iowait.ts index 3e811a7ad517f..b43f77252dcc1 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_iowait.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_iowait.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsageIowait: FormulaValueConfig = { - label: 'iowait', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage.iowaitLabel', { + defaultMessage: 'iowait', + }), value: 'average(system.cpu.iowait.pct) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_irq.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_irq.ts index 68cdbe9b65ea5..f5fb7f2319769 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_irq.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_irq.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsageIrq: FormulaValueConfig = { - label: 'irq', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage.irqLabel', { + defaultMessage: 'irq', + }), value: 'average(system.cpu.irq.pct) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_nice.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_nice.ts index 6ab8e01fec0c7..94093af85a90d 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_nice.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_nice.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsageNice: FormulaValueConfig = { - label: 'nice', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage.niceLabel', { + defaultMessage: 'nice', + }), value: 'average(system.cpu.nice.norm.pct) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_softirq.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_softirq.ts index bb26685b9d8bc..adc9428a24947 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_softirq.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_softirq.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsageSoftirq: FormulaValueConfig = { - label: 'softirq', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage.softirqLabel', { + defaultMessage: 'softirq', + }), value: 'average(system.cpu.softirq.pct) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_steal.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_steal.ts index ec0fa2652427c..4ca7ffb7c3352 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_steal.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_steal.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsageSteal: FormulaValueConfig = { - label: 'steal', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage.stealLabel', { + defaultMessage: 'steal', + }), value: 'average(system.cpu.steal.pct) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_system.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_system.ts index 303ff89aa256e..591734973647b 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_system.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_system.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsageSystem: FormulaValueConfig = { - label: 'system', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage.systemLabel', { + defaultMessage: 'system', + }), value: 'average(system.cpu.system.pct) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_user.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_user.ts index d24c9b82a199a..8fe8d2baf1f3b 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_user.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu_usage_user.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const cpuUsageUser: FormulaValueConfig = { - label: 'user', + label: i18n.translate('xpack.infra.assetDetails.formulas.cpuUsage.userLabel', { + defaultMessage: 'user', + }), value: 'average(system.cpu.user.pct) / max(system.cpu.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_iops.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_iops.ts index 9b3f22164aacc..74f3b6b27b2e2 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_iops.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_iops.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const diskIORead: FormulaValueConfig = { - label: 'Disk Read IOPS', + label: i18n.translate('xpack.infra.assetDetails.formulas.diskIORead', { + defaultMessage: 'Disk Read IOPS', + }), value: "counter_rate(max(system.diskio.read.count), kql='system.diskio.read.count: *')", format: { id: 'number', @@ -16,4 +19,5 @@ export const diskIORead: FormulaValueConfig = { decimals: 0, }, }, + timeScale: 's', }; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_throughput.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_throughput.ts index 5043fb7f94fe1..eb92ba34406e3 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_throughput.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_read_throughput.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const diskReadThroughput: FormulaValueConfig = { - label: 'Disk Read Throughput', + label: i18n.translate('xpack.infra.assetDetails.formulas.diskReadThroughput', { + defaultMessage: 'Disk Read Throughput', + }), value: "counter_rate(max(system.diskio.read.bytes), kql='system.diskio.read.bytes: *')", format: { id: 'bytes', @@ -16,4 +19,5 @@ export const diskReadThroughput: FormulaValueConfig = { decimals: 1, }, }, + timeScale: 's', }; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_availability.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_availability.ts index 11d8346c06d28..02fc69d72b106 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_availability.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_availability.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const diskSpaceAvailability: FormulaValueConfig = { - label: 'Disk Space Availability', + label: i18n.translate('xpack.infra.assetDetails.formulas.diskSpaceAvailability', { + defaultMessage: 'Disk Space Availability', + }), value: '1 - average(system.filesystem.used.pct)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_available.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_available.ts index 2d55c085deb0b..f8d34c24ffee0 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_available.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_available.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const diskSpaceAvailable: FormulaValueConfig = { - label: 'Disk Space Available', + label: i18n.translate('xpack.infra.assetDetails.formulas.diskSpaceAvailable', { + defaultMessage: 'Disk Space Available', + }), value: 'average(system.filesystem.free)', format: { id: 'bytes', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts deleted file mode 100644 index 24143b58c81e6..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; - -export const diskSpaceUsage: FormulaValueConfig = { - label: 'Disk Space Usage', - value: 'average(system.filesystem.used.pct)', - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, -}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_usage.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_usage.ts new file mode 100644 index 0000000000000..bce105387021f --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_usage.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const diskUsage: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.diskUsage', { + defaultMessage: 'Disk Usage', + }), + value: 'average(system.filesystem.used.pct)', + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_iops.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_iops.ts index 2831957ccb230..6936f6eee3d6c 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_iops.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_iops.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const diskIOWrite: FormulaValueConfig = { - label: 'Disk Write IOPS', + label: i18n.translate('xpack.infra.assetDetails.formulas.diskIOWrite', { + defaultMessage: 'Disk Write IOPS', + }), value: "counter_rate(max(system.diskio.write.count), kql='system.diskio.write.count: *')", format: { id: 'number', @@ -16,4 +19,5 @@ export const diskIOWrite: FormulaValueConfig = { decimals: 0, }, }, + timeScale: 's', }; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_throughput.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_throughput.ts index 9f0f0937bff37..6c1a536c7a3a1 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_throughput.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_write_throughput.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const diskWriteThroughput: FormulaValueConfig = { - label: 'Disk Write Throughput', + label: i18n.translate('xpack.infra.assetDetails.formulas.diskWriteThroughput', { + defaultMessage: 'Disk Write Throughput', + }), value: "counter_rate(max(system.diskio.write.bytes), kql='system.diskio.write.bytes: *')", format: { id: 'bytes', @@ -16,4 +19,5 @@ export const diskWriteThroughput: FormulaValueConfig = { decimals: 1, }, }, + timeScale: 's', }; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts index f34a9d1913e49..1fb01045d6eed 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const hostCount: FormulaValueConfig = { - label: 'Hosts', + label: i18n.translate('xpack.infra.assetDetails.formulas.hostCount.hostsLabel', { + defaultMessage: 'Hosts', + }), value: 'unique_count(host.name)', format: { id: 'number', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts index 1741741951e1a..25b21adb7195a 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts @@ -5,31 +5,62 @@ * 2.0. */ -export { cpuUsage } from './cpu_usage'; -export { cpuUsageIowait } from './cpu_usage_iowait'; -export { cpuUsageIrq } from './cpu_usage_irq'; -export { cpuUsageNice } from './cpu_usage_nice'; -export { cpuUsageSoftirq } from './cpu_usage_softirq'; -export { cpuUsageSteal } from './cpu_usage_steal'; -export { cpuUsageUser } from './cpu_usage_user'; -export { cpuUsageSystem } from './cpu_usage_system'; -export { diskIORead } from './disk_read_iops'; -export { diskIOWrite } from './disk_write_iops'; -export { diskReadThroughput } from './disk_read_throughput'; -export { diskWriteThroughput } from './disk_write_throughput'; -export { diskSpaceAvailability } from './disk_space_availability'; -export { diskSpaceAvailable } from './disk_space_available'; -export { diskSpaceUsage } from './disk_space_usage'; -export { hostCount } from './host_count'; -export { logRate } from './log_rate'; -export { normalizedLoad1m } from './normalized_load_1m'; -export { load1m } from './load_1m'; -export { load5m } from './load_5m'; -export { load15m } from './load_15m'; -export { memoryUsage } from './memory_usage'; -export { memoryFree } from './memory_free'; -export { memoryUsed } from './memory_used'; -export { memoryFreeExcludingCache } from './memory_free_excluding_cache'; -export { memoryCache } from './memory_cache'; -export { rx } from './rx'; -export { tx } from './tx'; +import { cpuUsage } from './cpu_usage'; +import { cpuUsageIowait } from './cpu_usage_iowait'; +import { cpuUsageIrq } from './cpu_usage_irq'; +import { cpuUsageNice } from './cpu_usage_nice'; +import { cpuUsageSoftirq } from './cpu_usage_softirq'; +import { cpuUsageSteal } from './cpu_usage_steal'; +import { cpuUsageUser } from './cpu_usage_user'; +import { cpuUsageSystem } from './cpu_usage_system'; +import { diskIORead } from './disk_read_iops'; +import { diskIOWrite } from './disk_write_iops'; +import { diskReadThroughput } from './disk_read_throughput'; +import { diskWriteThroughput } from './disk_write_throughput'; +import { diskSpaceAvailability } from './disk_space_availability'; +import { diskSpaceAvailable } from './disk_space_available'; +import { diskUsage } from './disk_usage'; +import { hostCount } from './host_count'; +import { logRate } from './log_rate'; +import { normalizedLoad1m } from './normalized_load_1m'; +import { load1m } from './load_1m'; +import { load5m } from './load_5m'; +import { load15m } from './load_15m'; +import { memoryUsage } from './memory_usage'; +import { memoryFree } from './memory_free'; +import { memoryUsed } from './memory_used'; +import { memoryFreeExcludingCache } from './memory_free_excluding_cache'; +import { memoryCache } from './memory_cache'; +import { rx } from './rx'; +import { tx } from './tx'; + +export const hostLensFormulas = { + cpuUsage, + cpuUsageIowait, + cpuUsageIrq, + cpuUsageNice, + cpuUsageSoftirq, + cpuUsageSteal, + cpuUsageUser, + cpuUsageSystem, + diskIORead, + diskIOWrite, + diskReadThroughput, + diskWriteThroughput, + diskSpaceAvailability, + diskSpaceAvailable, + diskUsage, + hostCount, + logRate, + normalizedLoad1m, + load1m, + load5m, + load15m, + memoryUsage, + memoryFree, + memoryUsed, + memoryFreeExcludingCache, + memoryCache, + rx, + tx, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_15m.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_15m.ts index f34ee8aa31bdf..84b865d4081c6 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_15m.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_15m.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const load15m: FormulaValueConfig = { - label: 'Load (15m)', + label: i18n.translate('xpack.infra.assetDetails.formulas.load15m', { + defaultMessage: 'Load (15m)', + }), value: 'average(system.load.15)', format: { id: 'number', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_1m.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_1m.ts index ac11637fdab56..8656176be2f04 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_1m.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_1m.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const load1m: FormulaValueConfig = { - label: 'Load (1m)', + label: i18n.translate('xpack.infra.assetDetails.formulas.load1m', { + defaultMessage: 'Load (1m)', + }), value: 'average(system.load.1)', format: { id: 'number', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_5m.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_5m.ts index 1fb8c15fc5888..b76ae333f093d 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_5m.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load_5m.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const load5m: FormulaValueConfig = { - label: 'Load (5m)', + label: i18n.translate('xpack.infra.assetDetails.formulas.load5m', { + defaultMessage: 'Load (5m)', + }), value: 'average(system.load.5)', format: { id: 'number', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/log_rate.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/log_rate.ts index 3365efca35ebb..2db54217e3231 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/log_rate.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/log_rate.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const logRate: FormulaValueConfig = { - label: 'Log Rate', + label: i18n.translate('xpack.infra.assetDetails.formulas.logRate', { + defaultMessage: 'Log Rate', + }), value: 'differences(cumulative_sum(count()))', format: { id: 'number', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_cache.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_cache.ts index 6688f51fb49c5..10e54d99dc62c 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_cache.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_cache.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const memoryCache: FormulaValueConfig = { - label: 'cache', + label: i18n.translate('xpack.infra.assetDetails.formulas.metric.label.cache', { + defaultMessage: 'cache', + }), value: 'average(system.memory.used.bytes) - average(system.memory.actual.used.bytes)', format: { id: 'bytes', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free.ts index 5221faa86b3be..8b2032a63a5e6 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const memoryFree: FormulaValueConfig = { - label: 'Memory Free', + label: i18n.translate('xpack.infra.assetDetails.formulas.memoryFree', { + defaultMessage: 'Memory Free', + }), value: 'max(system.memory.total) - average(system.memory.actual.used.bytes)', format: { id: 'bytes', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free_excluding_cache.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free_excluding_cache.ts index 96890b22a7472..71f03b974bf32 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free_excluding_cache.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_free_excluding_cache.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const memoryFreeExcludingCache: FormulaValueConfig = { - label: 'free', + label: i18n.translate('xpack.infra.assetDetails.formulas.metric.label.free', { + defaultMessage: 'free', + }), value: 'average(system.memory.free)', format: { id: 'bytes', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_usage.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_usage.ts index d7074968ce8b0..59e09889e65a6 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_usage.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_usage.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const memoryUsage: FormulaValueConfig = { - label: 'Memory Usage', + label: i18n.translate('xpack.infra.assetDetails.formulas.memoryUsage', { + defaultMessage: 'Memory Usage', + }), value: 'average(system.memory.actual.used.pct)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_used.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_used.ts index 8df1f24353d6a..2c02a22f393be 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_used.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_used.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const memoryUsed: FormulaValueConfig = { - label: 'used', + label: i18n.translate('xpack.infra.assetDetails.formulas.metric.label.used', { + defaultMessage: 'used', + }), value: 'average(system.memory.actual.used.bytes)', format: { id: 'bytes', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/normalized_load_1m.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/normalized_load_1m.ts index 3071804f3b5b4..d0e70374829e4 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/normalized_load_1m.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/normalized_load_1m.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const normalizedLoad1m: FormulaValueConfig = { - label: 'Normalized Load', + label: i18n.translate('xpack.infra.assetDetails.formulas.normalizedLoad1m', { + defaultMessage: 'Normalized Load', + }), value: 'average(system.load.1) / max(system.load.cores)', format: { id: 'percent', diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts index 92162fad6010f..87536f1a573e2 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const rx: FormulaValueConfig = { - label: 'Network Inbound (RX)', + label: i18n.translate('xpack.infra.assetDetails.formulas.rx', { + defaultMessage: 'Network Inbound (RX)', + }), value: "average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)", format: { @@ -17,4 +20,5 @@ export const rx: FormulaValueConfig = { decimals: 1, }, }, + timeScale: 's', }; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts index 2b196103619a7..2aefe2c4cd115 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; export const tx: FormulaValueConfig = { - label: 'Network Outbound (TX)', + label: i18n.translate('xpack.infra.assetDetails.formulas.tx', { + defaultMessage: 'Network Outbound (TX)', + }), value: "average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)", format: { @@ -17,4 +20,5 @@ export const tx: FormulaValueConfig = { decimals: 1, }, }, + timeScale: 's', }; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/index.ts new file mode 100644 index 0000000000000..18e993a0d0a99 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { hostLensFormulas } from './host'; +export { nginxLensFormulas } from './nginx'; +export { kubernetesLensFormulas } from './kubernetes'; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/index.ts new file mode 100644 index 0000000000000..f7593588f254d --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { nodeCpuCapacity } from './node_cpu_capacity'; +import { nodeCpuUsed } from './node_cpu_used'; +import { nodeDiskCapacity } from './node_disk_capacity'; +import { nodeDiskUsed } from './node_disk_used'; +import { nodeMemoryCapacity } from './node_memory_capacity'; +import { nodeMemoryUsed } from './node_memory_used'; +import { nodePodCapacity } from './node_pod_capacity'; +import { nodePodUsed } from './node_pod_used'; + +export const kubernetesLensFormulas = { + nodeCpuCapacity, + nodeCpuUsed, + nodeDiskCapacity, + nodeDiskUsed, + nodeMemoryCapacity, + nodeMemoryUsed, + nodePodCapacity, + nodePodUsed, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_cpu_capacity.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_cpu_capacity.ts new file mode 100644 index 0000000000000..1be71eb6c1a56 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_cpu_capacity.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodeCpuCapacity: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.capacity', { + defaultMessage: 'Capacity', + }), + value: 'max(kubernetes.node.cpu.allocatable.cores) * 1000000000', + format: { + id: 'number', + params: { + decimals: 1, + compact: true, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_cpu_used.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_cpu_used.ts new file mode 100644 index 0000000000000..ece5d4437961f --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_cpu_used.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodeCpuUsed: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.used', { + defaultMessage: 'Used', + }), + value: 'average(kubernetes.node.cpu.usage.nanocores)', + format: { + id: 'number', + params: { + decimals: 1, + compact: true, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_disk_capacity.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_disk_capacity.ts new file mode 100644 index 0000000000000..ef30107970d04 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_disk_capacity.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodeDiskCapacity: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.capacity', { + defaultMessage: 'Capacity', + }), + value: 'max(kubernetes.node.fs.capacity.bytes)', + format: { + id: 'bytes', + params: { + decimals: 1, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_disk_used.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_disk_used.ts new file mode 100644 index 0000000000000..8a38f22cfe141 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_disk_used.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodeDiskUsed: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.used', { + defaultMessage: 'Used', + }), + value: 'average(kubernetes.node.fs.used.bytes)', + format: { + id: 'bytes', + params: { + decimals: 1, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_memory_capacity.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_memory_capacity.ts new file mode 100644 index 0000000000000..379580c7fba30 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_memory_capacity.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodeMemoryCapacity: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.capacity', { + defaultMessage: 'Capacity', + }), + value: 'max(kubernetes.node.memory.allocatable.bytes)', + format: { + id: 'bytes', + params: { + decimals: 1, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_memory_used.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_memory_used.ts new file mode 100644 index 0000000000000..34d5bffebd92a --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_memory_used.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodeMemoryUsed: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.used', { + defaultMessage: 'Used', + }), + value: 'average(kubernetes.node.memory.usage.bytes)', + format: { + id: 'bytes', + params: { + decimals: 1, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_pod_capacity.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_pod_capacity.ts new file mode 100644 index 0000000000000..d3990328c0552 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_pod_capacity.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodePodCapacity: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.capacity', { + defaultMessage: 'Capacity', + }), + value: + "last_value(kubernetes.node.pod.allocatable.total, kql='kubernetes.node.pod.allocatable.total: *')", + format: { + id: 'number', + params: { + decimals: 0, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_pod_used.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_pod_used.ts new file mode 100644 index 0000000000000..6c3e3e952d635 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/kubernetes/node_pod_used.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const nodePodUsed: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.kubernetes.used', { + defaultMessage: 'Used', + }), + value: 'unique_count(kubernetes.pod.uid)', + format: { + id: 'number', + params: { + decimals: 0, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/active_connections.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/active_connections.ts new file mode 100644 index 0000000000000..90161ac7ebc5f --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/active_connections.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const activeConnections: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.nginx.activeConnections', { + defaultMessage: 'Active Connections', + }), + value: 'average(nginx.stubstatus.active)', + format: { + id: 'number', + params: { + decimals: 0, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/client_error_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/client_error_status_codes.ts new file mode 100644 index 0000000000000..2588f71f1444b --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/client_error_status_codes.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; +import { defaultPalette, Color } from '../../../../../../common/color_palette'; + +export const clientErrorStatusCodes: FormulaValueConfig = { + label: '400-499', + value: `count(kql='http.response.status_code >= 400 and http.response.status_code <= 499')`, + format: { + id: 'number', + params: { + decimals: 0, + }, + }, + color: defaultPalette[Color.color5], +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/index.ts new file mode 100644 index 0000000000000..87a1d5db40fd5 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { requestRate } from './request_rate'; +import { activeConnections } from './active_connections'; +import { requestsPerConnection } from './requests_per_connection'; +import { successStatusCodes } from './success_status_codes'; +import { redirectStatusCodes } from './redirect_status_codes'; +import { clientErrorStatusCodes } from './client_error_status_codes'; +import { serverErrorStatusCodes } from './server_error_status_codes'; + +export const nginxLensFormulas = { + activeConnections, + requestRate, + requestsPerConnection, + successStatusCodes, + redirectStatusCodes, + clientErrorStatusCodes, + serverErrorStatusCodes, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/redirect_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/redirect_status_codes.ts new file mode 100644 index 0000000000000..637546667dbab --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/redirect_status_codes.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; +import { defaultPalette, Color } from '../../../../../../common/color_palette'; + +export const redirectStatusCodes: FormulaValueConfig = { + label: '300-399', + value: `count(kql='http.response.status_code >= 300 and http.response.status_code <= 399')`, + format: { + id: 'number', + params: { + decimals: 0, + }, + }, + color: defaultPalette[Color.color0], +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/request_rate.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/request_rate.ts new file mode 100644 index 0000000000000..1e86f28a4bfce --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/request_rate.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const requestRate: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.nginx.requestRate', { + defaultMessage: 'Request Rate', + }), + value: 'differences(max(nginx.stubstatus.requests))', + format: { + id: 'number', + params: { + decimals: 0, + }, + }, + timeScale: 's', +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/requests_per_connection.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/requests_per_connection.ts new file mode 100644 index 0000000000000..a74f46e2014da --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/requests_per_connection.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; + +export const requestsPerConnection: FormulaValueConfig = { + label: i18n.translate('xpack.infra.assetDetails.formulas.nginx.requestsPerConnection', { + defaultMessage: 'Requests Per Connection', + }), + value: 'max(nginx.stubstatus.requests) / max(nginx.stubstatus.handled)', + format: { + id: 'number', + params: { + decimals: 0, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/server_error_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/server_error_status_codes.ts new file mode 100644 index 0000000000000..f5fba7775190b --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/server_error_status_codes.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; +import { defaultPalette, Color } from '../../../../../../common/color_palette'; + +export const serverErrorStatusCodes: FormulaValueConfig = { + label: '500-599', + value: `count(kql='http.response.status_code >= 500 and http.response.status_code <= 599')`, + format: { + id: 'number', + params: { + decimals: 0, + }, + }, + color: defaultPalette[Color.color1], +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/success_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/success_status_codes.ts new file mode 100644 index 0000000000000..865ce8dd907b4 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/success_status_codes.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils'; +import { defaultPalette, Color } from '../../../../../../common/color_palette'; + +export const successStatusCodes: FormulaValueConfig = { + label: '200-299', + value: `count(kql='http.response.status_code >= 200 and http.response.status_code <= 299')`, + format: { + id: 'number', + params: { + decimals: 0, + }, + }, + color: defaultPalette[Color.color2], +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/types.ts b/x-pack/plugins/infra/public/common/visualizations/types.ts index 3a3a3164e23f8..301908cec9527 100644 --- a/x-pack/plugins/infra/public/common/visualizations/types.ts +++ b/x-pack/plugins/infra/public/common/visualizations/types.ts @@ -10,11 +10,6 @@ import type { XYLayerConfig, XYReferenceLinesLayerConfig, } from '@kbn/lens-embeddable-utils'; -import { hostLensFormulas } from './constants'; - -export type HostsLensFormulas = keyof typeof hostLensFormulas; -export type HostsLensMetricChartFormulas = Exclude; -export type HostsLensLineChartFormulas = Exclude; export type XYChartLayerParams = | (XYLayerConfig & { type: 'visualization' }) diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts index 05e17c16eb929..b6e35dbf51773 100644 --- a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts +++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts @@ -6,42 +6,42 @@ */ import { i18n } from '@kbn/i18n'; -import { type AssetDetailsProps, FlyoutTabIds, type Tab } from '../../../types'; +import { type AssetDetailsProps, ContentTabIds, type Tab } from '../../../types'; const links: AssetDetailsProps['links'] = ['alertRule', 'nodeDetails', 'apmServices']; const tabs: Tab[] = [ { - id: FlyoutTabIds.OVERVIEW, + id: ContentTabIds.OVERVIEW, name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', { defaultMessage: 'Overview', }), }, { - id: FlyoutTabIds.LOGS, + id: ContentTabIds.LOGS, name: i18n.translate('xpack.infra.nodeDetails.tabs.logs', { defaultMessage: 'Logs', }), }, { - id: FlyoutTabIds.METADATA, + id: ContentTabIds.METADATA, name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.metadata', { defaultMessage: 'Metadata', }), }, { - id: FlyoutTabIds.PROCESSES, + id: ContentTabIds.PROCESSES, name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', { defaultMessage: 'Processes', }), }, { - id: FlyoutTabIds.ANOMALIES, + id: ContentTabIds.ANOMALIES, name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', { defaultMessage: 'Anomalies', }), }, { - id: FlyoutTabIds.LINK_TO_APM, + id: ContentTabIds.LINK_TO_APM, name: i18n.translate('xpack.infra.infra.nodeDetails.apmTabLabel', { defaultMessage: 'APM', }), diff --git a/x-pack/plugins/infra/public/components/asset_details/components/metadata_error_callout.tsx b/x-pack/plugins/infra/public/components/asset_details/components/metadata_error_callout.tsx new file mode 100644 index 0000000000000..7ada9160a6bd8 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/components/metadata_error_callout.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useMetadataStateProviderContext } from '../hooks/use_metadata_state'; + +export const MetadataErrorCallout = () => { + const { refresh } = useMetadataStateProviderContext(); + return ( + + + {i18n.translate('xpack.infra.metadataEmbeddable.errorAction', { + defaultMessage: 'refetch the metadata', + })} + + ), + }} + /> + + ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/components/metadata_explanation.tsx b/x-pack/plugins/infra/public/components/asset_details/components/metadata_explanation.tsx index bdd77916db02f..857dc493041c1 100644 --- a/x-pack/plugins/infra/public/components/asset_details/components/metadata_explanation.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/components/metadata_explanation.tsx @@ -21,7 +21,7 @@ const MetadataExplanationTooltipContent = React.memo(() => { }; return ( - + { iconSize="s" iconColor="subdued" icon="iInCircle" - panelPaddingSize="m" data-test-subj="infraAssetDetailsMetadataPopoverButton" > diff --git a/x-pack/plugins/infra/public/components/asset_details/components/processes_explanation.tsx b/x-pack/plugins/infra/public/components/asset_details/components/processes_explanation.tsx index 5f1af3f9f8a26..5d3f181a85ac1 100644 --- a/x-pack/plugins/infra/public/components/asset_details/components/processes_explanation.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/components/processes_explanation.tsx @@ -6,106 +6,32 @@ */ import React from 'react'; -import { EuiText, EuiLink } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDateRangeProviderContext } from '../hooks/use_date_range'; -import { Popover } from '../tabs/common/popover'; - -const DOCUMENTATION_LINK = - 'https://www.elastic.co/guide/en/observability/current/view-infrastructure-metrics.html'; -const SYSTEM_INTEGRATION_DOCS_LINK = 'https://docs.elastic.co/en/integrations/system'; - -const ProcessesExplanationTooltipContent = React.memo(() => { - const onClick = (e: React.MouseEvent) => { - e.stopPropagation(); - }; - - return ( - -

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

    -

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

    -
    - ); -}); export const ProcessesExplanationMessage = () => { const { getDateRangeInTimestamp } = useDateRangeProviderContext(); const dateFromRange = new Date(getDateRangeInTimestamp().to); return ( - - - - - ), - time: ( - - ), - }} - /> - - - - - - - - + + , + time: ( + + ), + }} + /> + ); }; diff --git a/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx b/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx new file mode 100644 index 0000000000000..da639ff88bd13 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { type ReactNode } from 'react'; + +import { EuiFlexItem, EuiTitle, EuiFlexGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { HostMetricsExplanationContent } from '../../lens'; +import { Popover } from '../tabs/common/popover'; +import { AlertsTooltipContent } from './alerts_tooltip_content'; + +const SectionTitle = ({ + title, + 'data-test-subj': dataTestSubject, +}: { + title: string; + 'data-test-subj'?: string; +}) => { + return ( + + {title} + + ); +}; + +const TitleWithTooltip = ({ + title, + 'data-test-subj': dataTestSubject, + tooltipTestSubj, + children, +}: { + title: string; + children: ReactNode; + 'data-test-subj'?: string; + tooltipTestSubj?: string; +}) => { + return ( + + + + + + + {children} + + + + ); +}; + +export const MetricsSectionTitle = () => { + return ( + + + + ); +}; + +export const NginxMetricsSectionTitle = () => ( + +); + +export const KubernetesMetricsSectionTitle = () => ( + +); + +export const MetadataSectionTitle = () => ( + +); + +export const AlertsSectionTitle = () => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/components/top_processes_tooltip.tsx b/x-pack/plugins/infra/public/components/asset_details/components/top_processes_tooltip.tsx new file mode 100644 index 0000000000000..15ca71c79ef52 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/components/top_processes_tooltip.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiText, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { Popover } from '../tabs/common/popover'; + +const DOCUMENTATION_LINK = + 'https://www.elastic.co/guide/en/observability/current/view-infrastructure-metrics.html'; +const SYSTEM_INTEGRATION_DOCS_LINK = 'https://docs.elastic.co/en/integrations/system'; + +export const TopProcessesTooltip = React.memo(() => { + return ( + + +

    + +

    +

    + + + + ), + }} + /> +

    +

    + + + + ), + }} + /> +

    +
    +
    + ); +}); diff --git a/x-pack/plugins/infra/public/components/asset_details/constants.ts b/x-pack/plugins/infra/public/components/asset_details/constants.ts index 726f47450d0cc..a689efe20d5e3 100644 --- a/x-pack/plugins/infra/public/components/asset_details/constants.ts +++ b/x-pack/plugins/infra/public/components/asset_details/constants.ts @@ -5,6 +5,17 @@ * 2.0. */ +import { INTEGRATION_NAME } from './types'; + export const ASSET_DETAILS_FLYOUT_COMPONENT_NAME = 'infraAssetDetailsFlyout'; +export const ASSET_DETAILS_PAGE_COMPONENT_NAME = 'infraAssetDetailsPage'; + export const METRIC_CHART_HEIGHT = 300; export const APM_HOST_FILTER_FIELD = 'host.hostname'; + +export const ASSET_DETAILS_URL_STATE_KEY = 'assetDetails'; + +export const INTEGRATIONS = { + [INTEGRATION_NAME.nginx]: ['nginx.stubstatus', 'nginx.access'], + [INTEGRATION_NAME.kubernetes]: ['kubernetes.node'], +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/content/content.tsx b/x-pack/plugins/infra/public/components/asset_details/content/content.tsx index c9d622cf01493..0cf51f1fff3d7 100644 --- a/x-pack/plugins/infra/public/components/asset_details/content/content.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/content/content.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { DatePicker } from '../date_picker/date_picker'; import { useTabSwitcherContext } from '../hooks/use_tab_switcher'; import { Anomalies, Metadata, Processes, Osquery, Logs, Overview } from '../tabs'; -import { FlyoutTabIds } from '../types'; +import { ContentTabIds } from '../types'; export const Content = () => { return ( @@ -18,31 +18,31 @@ export const Content = () => { - + - + - + - + - + - + @@ -50,11 +50,11 @@ export const Content = () => { ); }; -const DatePickerWrapper = ({ visibleFor }: { visibleFor: FlyoutTabIds[] }) => { +const DatePickerWrapper = ({ visibleFor }: { visibleFor: ContentTabIds[] }) => { const { activeTabId } = useTabSwitcherContext(); return ( - diff --git a/x-pack/plugins/ml/public/application/routing/routes/memory_usage.tsx b/x-pack/plugins/ml/public/application/routing/routes/memory_usage.tsx index 9f78410615586..dce72c080e94f 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/memory_usage.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/memory_usage.tsx @@ -38,7 +38,12 @@ export const nodesListRouteFactory = ( const PageWrapper: FC = () => { const { context } = useRouteResolver( 'full', - ['canGetJobs', 'canGetDataFrameAnalytics', 'canGetTrainedModels'], + // only enabled in non-serverless mode + // if a serverless project ever contains all three features + // this check will have to be changed to an + // explicit isServerless check which will probably + // require a change in useRouteResolver + ['isADEnabled', 'isDFAEnabled', 'isNLPEnabled'], basicResolvers() ); diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx index bc93f13f01a70..cb12cd22ab6c2 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx @@ -199,7 +199,7 @@ export const dataComparisonIndexOrSearchRouteFactory = ( {...props} nextStepPath={createPath(ML_PAGES.DATA_COMPARISON)} deps={deps} - mode={MODE.DATAVISUALIZER} + mode={MODE.NEW_JOB} /> ), breadcrumbs: getDataVisBreadcrumbs(navigateToPath, basePath), diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js index e2b183a7855a2..5f2be59156762 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.js +++ b/x-pack/plugins/ml/public/application/services/job_service.js @@ -8,7 +8,6 @@ import { cloneDeep, each, find, get, isNumber } from 'lodash'; import moment from 'moment'; -import { i18n } from '@kbn/i18n'; import { validateTimeRange, TIME_FORMAT } from '@kbn/ml-date-utils'; import { parseInterval } from '../../../common/util/parse_interval'; @@ -47,50 +46,6 @@ class JobService { this.jobDescriptions = {}; this.detectorsByJob = {}; this.customUrlsByJob = {}; - this.jobStats = { - activeNodes: { - label: i18n.translate('xpack.ml.jobService.activeMLNodesLabel', { - defaultMessage: 'Active ML nodes', - }), - value: 0, - show: true, - }, - total: { - label: i18n.translate('xpack.ml.jobService.totalJobsLabel', { - defaultMessage: 'Total jobs', - }), - value: 0, - show: true, - }, - open: { - label: i18n.translate('xpack.ml.jobService.openJobsLabel', { - defaultMessage: 'Open jobs', - }), - value: 0, - show: true, - }, - closed: { - label: i18n.translate('xpack.ml.jobService.closedJobsLabel', { - defaultMessage: 'Closed jobs', - }), - value: 0, - show: true, - }, - failed: { - label: i18n.translate('xpack.ml.jobService.failedJobsLabel', { - defaultMessage: 'Failed jobs', - }), - value: 0, - show: false, - }, - activeDatafeeds: { - label: i18n.translate('xpack.ml.jobService.activeDatafeedsLabel', { - defaultMessage: 'Active datafeeds', - }), - value: 0, - show: true, - }, - }; } loadJobs() { diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index 25ccede9f25e8..5a7edda953998 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -53,6 +53,11 @@ import { savedObjectsApiProvider } from './saved_objects'; import { trainedModelsApiProvider } from './trained_models'; import { notificationsProvider } from './notifications'; +export interface MlHasPrivilegesResponse { + hasPrivileges?: estypes.SecurityHasPrivilegesResponse; + upgradeInProgress: boolean; +} + export interface MlInfoResponse { defaults: MlServerDefaults; limits: MlServerLimits; @@ -408,7 +413,7 @@ export function mlApiServicesProvider(httpService: HttpService) { hasPrivileges(obj: any) { const body = JSON.stringify(obj); - return httpService.http({ + return httpService.http({ path: `${ML_INTERNAL_BASE_PATH}/_has_privileges`, method: 'POST', body, @@ -778,6 +783,23 @@ export function mlApiServicesProvider(httpService: HttpService) { }); }, + reindexWithPipeline(pipelineName: string, sourceIndex: string, destinationIndex: string) { + return httpService.http({ + path: `${ML_INTERNAL_BASE_PATH}/reindex_with_pipeline`, + method: 'POST', + body: JSON.stringify({ + source: { + index: sourceIndex, + }, + dest: { + index: destinationIndex, + pipeline: pipelineName, + }, + }), + version: '1', + }); + }, + annotations: annotationsApiProvider(httpService), dataFrameAnalytics: dataFrameAnalyticsApiProvider(httpService), filters: filtersApiProvider(httpService), diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts index dd7408287cbf4..4e1771c25d0ef 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts @@ -27,7 +27,7 @@ describe('AnomalyChartsEmbeddableFactory', () => { const [coreStart, pluginsStart] = await getStartServices(); // act - const factory = new AnomalyChartsEmbeddableFactory(getStartServices); + const factory = new AnomalyChartsEmbeddableFactory(getStartServices, false); await factory.create({ jobIds: ['test-job'], diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts index e502d03bcd964..a6056e84a87be 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts @@ -35,7 +35,8 @@ export class AnomalyChartsEmbeddableFactory ]; constructor( - private getStartServices: StartServicesAccessor + private getStartServices: StartServicesAccessor, + private isServerless: boolean ) {} public async isEditable() { @@ -61,7 +62,7 @@ export class AnomalyChartsEmbeddableFactory const { resolveEmbeddableAnomalyChartsUserInput } = await import( './anomaly_charts_setup_flyout' ); - return await resolveEmbeddableAnomalyChartsUserInput(coreStart); + return await resolveEmbeddableAnomalyChartsUserInput(coreStart, this.isServerless); } catch (e) { return Promise.reject(); } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx index 92aea068c5b15..13849c8e6064c 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx @@ -19,6 +19,7 @@ import { mlApiServicesProvider } from '../../application/services/ml_api_service export async function resolveEmbeddableAnomalyChartsUserInput( coreStart: CoreStart, + isServerless: boolean, input?: AnomalyChartsEmbeddableInput ): Promise> { const { http, overlays, theme, i18n } = coreStart; @@ -27,7 +28,7 @@ export async function resolveEmbeddableAnomalyChartsUserInput( return new Promise(async (resolve, reject) => { try { - const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds); + const { jobIds } = await resolveJobSelection(coreStart, isServerless, input?.jobIds); const title = input?.title ?? getDefaultExplorerChartsPanelTitle(jobIds); const { jobs } = await getJobs({ jobId: jobIds.join(',') }); const influencers = extractInfluencers(jobs); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx index cb759f6783b46..2b75202095e3f 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx @@ -27,7 +27,7 @@ describe('AnomalySwimlaneEmbeddableFactory', () => { const [coreStart, pluginsStart] = await getStartServices(); // act - const factory = new AnomalySwimlaneEmbeddableFactory(getStartServices); + const factory = new AnomalySwimlaneEmbeddableFactory(getStartServices, false); await factory.create({ jobIds: ['test-job'], diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts index 3e7f958ea778e..421d12193e56f 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts @@ -35,7 +35,8 @@ export class AnomalySwimlaneEmbeddableFactory ]; constructor( - private getStartServices: StartServicesAccessor + private getStartServices: StartServicesAccessor, + private isServerless: boolean ) {} public async isEditable() { @@ -59,7 +60,7 @@ export class AnomalySwimlaneEmbeddableFactory try { const { resolveAnomalySwimlaneUserInput } = await import('./anomaly_swimlane_setup_flyout'); - return await resolveAnomalySwimlaneUserInput(coreStart); + return await resolveAnomalySwimlaneUserInput(coreStart, this.isServerless); } catch (e) { return Promise.reject(); } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx index dc2ca931cc805..2c0e6c5e2d963 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx @@ -19,6 +19,7 @@ import { mlApiServicesProvider } from '../../application/services/ml_api_service export async function resolveAnomalySwimlaneUserInput( coreStart: CoreStart, + isServerless: boolean, input?: AnomalySwimlaneEmbeddableInput ): Promise> { const { http, overlays, theme, i18n } = coreStart; @@ -27,7 +28,7 @@ export async function resolveAnomalySwimlaneUserInput( return new Promise(async (resolve, reject) => { try { - const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds); + const { jobIds } = await resolveJobSelection(coreStart, isServerless, input?.jobIds); const title = input?.title ?? getDefaultSwimlanePanelTitle(jobIds); const { jobs } = await getJobs({ jobId: jobIds.join(',') }); const influencers = extractInfluencers(jobs); diff --git a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx index 00c4a02d4e929..3cd49afd8c361 100644 --- a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx +++ b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx @@ -26,6 +26,7 @@ import { JobSelectorFlyout } from './components/job_selector_flyout'; */ export async function resolveJobSelection( coreStart: CoreStart, + isServerless: boolean, selectedJobIds?: JobId[] ): Promise<{ jobIds: string[]; groups: Array<{ groupId: string; jobIds: string[] }> }> { const { @@ -69,7 +70,9 @@ export async function resolveJobSelection( const flyoutSession = coreStart.overlays.openFlyout( toMountPoint( - + { const { @@ -53,7 +54,7 @@ export function createFlyout( data, lens, dashboardService, - mlServices: getMlGlobalServices(http), + mlServices: getMlGlobalServices(http, isServerless), }} > { return createFlyout( LensLayerSelectionFlyout, @@ -29,6 +30,7 @@ export async function showLensVisToADJobFlyout( share, data, dashboardService, + isServerless, lens ); } diff --git a/x-pack/plugins/ml/public/embeddables/job_creation/map/show_flyout.tsx b/x-pack/plugins/ml/public/embeddables/job_creation/map/show_flyout.tsx index 5380513f1dc97..da2d8bb2e0d4c 100644 --- a/x-pack/plugins/ml/public/embeddables/job_creation/map/show_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/job_creation/map/show_flyout.tsx @@ -19,7 +19,16 @@ export async function showMapVisToADJobFlyout( coreStart: CoreStart, share: SharePluginStart, data: DataPublicPluginStart, - dashboardService: DashboardStart + dashboardService: DashboardStart, + isServerless: boolean ): Promise { - return createFlyout(GeoJobFlyout, embeddable, coreStart, share, data, dashboardService); + return createFlyout( + GeoJobFlyout, + embeddable, + coreStart, + share, + data, + dashboardService, + isServerless + ); } diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index c66d4c6ec8d33..3a32f8b25ae89 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -51,9 +51,9 @@ import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/ import { registerManagementSection } from './application/management'; import { MlLocatorDefinition, type MlLocator } from './locator'; import { setDependencyCache } from './application/util/dependency_cache'; -import { registerFeature } from './register_feature'; +import { registerHomeFeature } from './register_home_feature'; import { isFullLicense, isMlEnabled } from '../common/license'; -import { PLUGIN_ICON_SOLUTION, PLUGIN_ID } from '../common/constants/app'; +import { ML_APP_ROUTE, PLUGIN_ICON_SOLUTION, PLUGIN_ID } from '../common/constants/app'; import type { MlCapabilities } from './shared'; export interface MlStartDependencies { @@ -103,8 +103,11 @@ export class MlPlugin implements Plugin { private appUpdater$ = new BehaviorSubject(() => ({})); private locator: undefined | MlLocator; + private isServerless: boolean = false; - constructor(private initializerContext: PluginInitializerContext) {} + constructor(private initializerContext: PluginInitializerContext) { + this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless'; + } setup(core: MlCoreSetup, pluginsSetup: MlSetupDependencies) { core.application.register({ @@ -114,12 +117,11 @@ export class MlPlugin implements Plugin { }), order: 5000, euiIconType: PLUGIN_ICON_SOLUTION, - appRoute: '/app/ml', + appRoute: ML_APP_ROUTE, category: DEFAULT_APP_CATEGORIES.kibana, updater$: this.appUpdater$, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); - const kibanaVersion = this.initializerContext.env.packageInfo.version; const { renderApp } = await import('./application/app'); return renderApp( coreStart, @@ -137,7 +139,7 @@ export class MlPlugin implements Plugin { embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, maps: pluginsStart.maps, uiActions: pluginsStart.uiActions, - kibanaVersion, + kibanaVersion: this.initializerContext.env.packageInfo.version, triggersActionsUi: pluginsStart.triggersActionsUi, dataVisualizer: pluginsStart.dataVisualizer, usageCollection: pluginsSetup.usageCollection, @@ -149,7 +151,8 @@ export class MlPlugin implements Plugin { contentManagement: pluginsStart.contentManagement, presentationUtil: pluginsStart.presentationUtil, }, - params + params, + this.isServerless ); }, }); @@ -159,9 +162,14 @@ export class MlPlugin implements Plugin { } if (pluginsSetup.management) { - registerManagementSection(pluginsSetup.management, core, { - usageCollection: pluginsSetup.usageCollection, - }).enable(); + registerManagementSection( + pluginsSetup.management, + core, + { + usageCollection: pluginsSetup.usageCollection, + }, + this.isServerless + ).enable(); } const licensing = pluginsSetup.licensing.license$.pipe(take(1)); @@ -170,53 +178,58 @@ export class MlPlugin implements Plugin { const fullLicense = isFullLicense(license); const [coreStart, pluginStart] = await core.getStartServices(); const { capabilities } = coreStart.application; + const mlCapabilities = capabilities.ml as MlCapabilities; + // register various ML plugin features which require a full license + // note including registerHomeFeature in register_helper would cause the page bundle size to increase significantly if (mlEnabled) { // add ML to home page if (pluginsSetup.home) { - registerFeature(pluginsSetup.home); + registerHomeFeature(pluginsSetup.home); } - } else { - // if ml is disabled in elasticsearch, disable ML in kibana - this.appUpdater$.next(() => ({ - status: AppStatus.inaccessible, - })); - } - - // register various ML plugin features which require a full license - // note including registerFeature in register_helper would cause the page bundle size to increase significantly - const { - registerEmbeddables, - registerMlUiActions, - registerSearchLinks, - registerMlAlerts, - registerMapExtension, - registerCasesAttachments, - } = await import('./register_helper'); - - if (pluginsSetup.maps) { - // Pass capabilites.ml.canGetJobs as minimum permission to show anomalies card in maps layers - const canGetJobs = capabilities.ml?.canGetJobs === true; - const canCreateJobs = capabilities.ml?.canCreateJob === true; - await registerMapExtension(pluginsSetup.maps, core, { canGetJobs, canCreateJobs }); - } - if (mlEnabled) { - registerSearchLinks(this.appUpdater$, fullLicense, capabilities.ml as MlCapabilities); + const { + registerEmbeddables, + registerMlUiActions, + registerSearchLinks, + registerMlAlerts, + registerMapExtension, + registerCasesAttachments, + } = await import('./register_helper'); + registerSearchLinks(this.appUpdater$, fullLicense, mlCapabilities, this.isServerless); if (fullLicense) { - registerEmbeddables(pluginsSetup.embeddable, core); - registerMlUiActions(pluginsSetup.uiActions, core); + registerMlUiActions(pluginsSetup.uiActions, core, this.isServerless); - if (pluginsSetup.cases) { - registerCasesAttachments(pluginsSetup.cases, coreStart, pluginStart); - } + if (mlCapabilities.isADEnabled) { + registerEmbeddables(pluginsSetup.embeddable, core, this.isServerless); + + if (pluginsSetup.cases) { + registerCasesAttachments(pluginsSetup.cases, coreStart, pluginStart); + } + + if ( + pluginsSetup.triggersActionsUi && + mlCapabilities.canUseMlAlerts && + mlCapabilities.canGetJobs + ) { + registerMlAlerts(pluginsSetup.triggersActionsUi, pluginsSetup.alerting); + } - const canUseMlAlerts = capabilities.ml?.canUseMlAlerts; - if (pluginsSetup.triggersActionsUi && canUseMlAlerts) { - registerMlAlerts(pluginsSetup.triggersActionsUi, pluginsSetup.alerting); + if (pluginsSetup.maps) { + // Pass canGetJobs as minimum permission to show anomalies card in maps layers + await registerMapExtension(pluginsSetup.maps, core, { + canGetJobs: mlCapabilities.canGetJobs, + canCreateJobs: mlCapabilities.canCreateJob, + }); + } } } + } else { + // if ml is disabled in elasticsearch, disable ML in kibana + this.appUpdater$.next(() => ({ + status: AppStatus.inaccessible, + })); } }); diff --git a/x-pack/plugins/ml/public/register_feature.ts b/x-pack/plugins/ml/public/register_feature.ts deleted file mode 100644 index bc0f6f751351e..0000000000000 --- a/x-pack/plugins/ml/public/register_feature.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { PLUGIN_ID } from '../common/constants/app'; - -export const registerFeature = (home: HomePublicPluginSetup) => { - // register ML so it appears on the Kibana home page - home.featureCatalogue.register({ - id: PLUGIN_ID, - title: i18n.translate('xpack.ml.machineLearningTitle', { - defaultMessage: 'Machine Learning', - }), - subtitle: i18n.translate('xpack.ml.machineLearningSubtitle', { - defaultMessage: 'Model, predict, and detect.', - }), - description: i18n.translate('xpack.ml.machineLearningDescription', { - defaultMessage: - 'Automatically model the normal behavior of your time series data to detect anomalies.', - }), - icon: 'machineLearningApp', - path: '/app/ml', - showOnHomePage: false, - category: 'data', - solutionId: 'kibana', - order: 500, - }); -}; diff --git a/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts b/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts index 35c37c56ecdff..6b7f10103b440 100644 --- a/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts +++ b/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts @@ -15,7 +15,8 @@ import type { MlCapabilities } from '../../shared'; export function registerSearchLinks( appUpdater: BehaviorSubject, isFullLicense: boolean, - mlCapabilities: MlCapabilities + mlCapabilities: MlCapabilities, + isServerless: boolean ) { appUpdater.next(() => ({ keywords: [ @@ -23,6 +24,6 @@ export function registerSearchLinks( defaultMessage: 'ML', }), ], - deepLinks: getDeepLinks(isFullLicense, mlCapabilities), + deepLinks: getDeepLinks(isFullLicense, mlCapabilities, isServerless), })); } diff --git a/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts b/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts index dfe2b4bbaf200..f9bdd2b50e4a4 100644 --- a/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts +++ b/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts @@ -12,276 +12,275 @@ import { type AppDeepLink, AppNavLinkStatus } from '@kbn/core/public'; import { ML_PAGES } from '../../../common/constants/locator'; import type { MlCapabilities } from '../../shared'; -function getNavStatus( +function createDeepLinks( mlCapabilities: MlCapabilities, - statusIfServerless: boolean -): AppNavLinkStatus | undefined { - if (mlCapabilities.isADEnabled && mlCapabilities.isDFAEnabled && mlCapabilities.isNLPEnabled) { - // if all features are enabled we can assume that we are not running in serverless mode. - // returning default will not add the link to the nav menu, but the link will be registered for searching - return AppNavLinkStatus.default; - } - - return statusIfServerless ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden; -} + isFullLicense: boolean, + isServerless: boolean +) { + function getNavStatus( + visible: boolean, + showInServerless: boolean = true + ): AppNavLinkStatus | undefined { + if (isServerless) { + // in serverless the status needs to be "visible" rather than "default" + // for the links to appear in the nav menu. + return showInServerless && visible ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden; + } -function getOverviewLinkDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - return { - id: 'overview', - title: i18n.translate('xpack.ml.deepLink.overview', { - defaultMessage: 'Overview', - }), - path: `/${ML_PAGES.OVERVIEW}`, - navLinkStatus: getNavStatus(mlCapabilities, false), - }; -} + return visible ? AppNavLinkStatus.default : AppNavLinkStatus.hidden; + } -function getAnomalyDetectionDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - const navLinkStatus = getNavStatus(mlCapabilities, mlCapabilities.isADEnabled); return { - id: 'anomalyDetection', - title: i18n.translate('xpack.ml.deepLink.anomalyDetection', { - defaultMessage: 'Anomaly Detection', - }), - path: `/${ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE}`, - navLinkStatus, - deepLinks: [ - { - id: 'anomalyExplorer', - title: i18n.translate('xpack.ml.deepLink.anomalyExplorer', { - defaultMessage: 'Anomaly explorer', - }), - path: `/${ML_PAGES.ANOMALY_EXPLORER}`, - navLinkStatus, - }, - { - id: 'singleMetricViewer', - title: i18n.translate('xpack.ml.deepLink.singleMetricViewer', { - defaultMessage: 'Single metric viewer', + getOverviewLinkDeepLink: (): AppDeepLink => { + const navLinkStatus = getNavStatus(mlCapabilities.isADEnabled || mlCapabilities.isDFAEnabled); + return { + id: 'overview', + title: i18n.translate('xpack.ml.deepLink.overview', { + defaultMessage: 'Overview', }), - path: `/${ML_PAGES.SINGLE_METRIC_VIEWER}`, + path: `/${ML_PAGES.OVERVIEW}`, navLinkStatus, - }, - ], - }; -} + }; + }, -function getDataFrameAnalyticsDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - const navLinkStatus = getNavStatus(mlCapabilities, mlCapabilities.isDFAEnabled); - return { - id: 'dataFrameAnalytics', - title: i18n.translate('xpack.ml.deepLink.dataFrameAnalytics', { - defaultMessage: 'Data Frame Analytics', - }), - path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`, - navLinkStatus, - deepLinks: [ - { - id: 'resultExplorer', - title: i18n.translate('xpack.ml.deepLink.resultExplorer', { - defaultMessage: 'Results explorer', + getAnomalyDetectionDeepLink: (): AppDeepLink => { + const navLinkStatus = getNavStatus(mlCapabilities.isADEnabled); + return { + id: 'anomalyDetection', + title: i18n.translate('xpack.ml.deepLink.anomalyDetection', { + defaultMessage: 'Anomaly Detection', }), - path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION}`, + path: `/${ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE}`, navLinkStatus, - }, - { - id: 'analyticsMap', - title: i18n.translate('xpack.ml.deepLink.analyticsMap', { - defaultMessage: 'Analytics map', + deepLinks: [ + { + id: 'anomalyExplorer', + title: i18n.translate('xpack.ml.deepLink.anomalyExplorer', { + defaultMessage: 'Anomaly explorer', + }), + path: `/${ML_PAGES.ANOMALY_EXPLORER}`, + navLinkStatus, + }, + { + id: 'singleMetricViewer', + title: i18n.translate('xpack.ml.deepLink.singleMetricViewer', { + defaultMessage: 'Single metric viewer', + }), + path: `/${ML_PAGES.SINGLE_METRIC_VIEWER}`, + navLinkStatus, + }, + ], + }; + }, + + getDataFrameAnalyticsDeepLink: (): AppDeepLink => { + const navLinkStatus = getNavStatus(mlCapabilities.isDFAEnabled); + return { + id: 'dataFrameAnalytics', + title: i18n.translate('xpack.ml.deepLink.dataFrameAnalytics', { + defaultMessage: 'Data Frame Analytics', }), - path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_MAP}`, + path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`, navLinkStatus, - }, - ], - }; -} + deepLinks: [ + { + id: 'resultExplorer', + title: i18n.translate('xpack.ml.deepLink.resultExplorer', { + defaultMessage: 'Results explorer', + }), + path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION}`, + navLinkStatus, + }, + { + id: 'analyticsMap', + title: i18n.translate('xpack.ml.deepLink.analyticsMap', { + defaultMessage: 'Analytics map', + }), + path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_MAP}`, + navLinkStatus, + }, + ], + }; + }, -function getAiopsDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - const navLinkStatus = getNavStatus(mlCapabilities, mlCapabilities.canUseAiops); - return { - id: 'aiOps', - title: i18n.translate('xpack.ml.deepLink.aiOps', { - defaultMessage: 'AIOps', - }), - // Default to the index select page for log rate analysis since we don't have an AIops overview page - path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`, - navLinkStatus, - deepLinks: [ - { - id: 'logRateAnalysis', - title: i18n.translate('xpack.ml.deepLink.logRateAnalysis', { - defaultMessage: 'Log Rate Analysis', + getModelManagementDeepLink: (): AppDeepLink => { + const navLinkStatus = getNavStatus( + mlCapabilities.isDFAEnabled || mlCapabilities.isNLPEnabled + ); + return { + id: 'modelManagement', + title: i18n.translate('xpack.ml.deepLink.modelManagement', { + defaultMessage: 'Model Management', }), - path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`, + path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`, navLinkStatus, - }, - { - id: 'logPatternAnalysis', - title: i18n.translate('xpack.ml.deepLink.logPatternAnalysis', { - defaultMessage: 'Log Pattern Analysis', + deepLinks: [ + { + id: 'nodesOverview', + title: i18n.translate('xpack.ml.deepLink.trainedModels', { + defaultMessage: 'Trained Models', + }), + path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`, + navLinkStatus, + }, + { + id: 'nodes', + title: i18n.translate('xpack.ml.deepLink.nodes', { + defaultMessage: 'Nodes', + }), + path: `/${ML_PAGES.NODES}`, + navLinkStatus: getNavStatus( + mlCapabilities.isDFAEnabled || mlCapabilities.isNLPEnabled, + false + ), + }, + ], + }; + }, + + getMemoryUsageDeepLink: (): AppDeepLink => { + return { + id: 'memoryUsage', + title: i18n.translate('xpack.ml.deepLink.memoryUsage', { + defaultMessage: 'Memory Usage', }), - path: `/${ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT}`, - navLinkStatus, - }, - { - id: 'changePointDetections', - title: i18n.translate('xpack.ml.deepLink.changePointDetection', { - defaultMessage: 'Change Point Detection', + path: `/${ML_PAGES.MEMORY_USAGE}`, + navLinkStatus: getNavStatus(isFullLicense, false), + }; + }, + + getSettingsDeepLink: (): AppDeepLink => { + const navLinkStatus = getNavStatus(mlCapabilities.isADEnabled); + return { + id: 'settings', + title: i18n.translate('xpack.ml.deepLink.settings', { + defaultMessage: 'Settings', }), - path: `/${ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT}`, + path: `/${ML_PAGES.SETTINGS}`, navLinkStatus, - }, - ], - }; -} + deepLinks: [ + { + id: 'calendarSettings', + title: i18n.translate('xpack.ml.deepLink.calendarSettings', { + defaultMessage: 'Calendars', + }), + path: `/${ML_PAGES.CALENDARS_MANAGE}`, + navLinkStatus, + }, + { + id: 'filterListsSettings', + title: i18n.translate('xpack.ml.deepLink.filterListsSettings', { + defaultMessage: 'Filter Lists', + }), + path: `/${ML_PAGES.SETTINGS}`, // Link to settings page as read only users cannot view filter lists. + navLinkStatus, + }, + ], + }; + }, -function getModelManagementDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - const navLinkStatus = getNavStatus(mlCapabilities, mlCapabilities.isNLPEnabled); - return { - id: 'modelManagement', - title: i18n.translate('xpack.ml.deepLink.modelManagement', { - defaultMessage: 'Model Management', - }), - path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`, - navLinkStatus, - deepLinks: [ - { - id: 'nodesOverview', - title: i18n.translate('xpack.ml.deepLink.trainedModels', { - defaultMessage: 'Trained Models', + getAiopsDeepLink: (): AppDeepLink => { + const navLinkStatus = getNavStatus(mlCapabilities.canUseAiops); + return { + id: 'aiOps', + title: i18n.translate('xpack.ml.deepLink.aiOps', { + defaultMessage: 'AIOps', }), - path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`, + // Default to the index select page for log rate analysis since we don't have an AIops overview page + path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`, navLinkStatus, - }, - { - id: 'nodes', - title: i18n.translate('xpack.ml.deepLink.nodes', { - defaultMessage: 'Nodes', - }), - path: `/${ML_PAGES.NODES}`, - navLinkStatus: getNavStatus(mlCapabilities, false), - }, - ], - }; -} + deepLinks: [ + { + id: 'logRateAnalysis', + title: i18n.translate('xpack.ml.deepLink.logRateAnalysis', { + defaultMessage: 'Log Rate Analysis', + }), + path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`, + navLinkStatus, + }, + { + id: 'logPatternAnalysis', + title: i18n.translate('xpack.ml.deepLink.logPatternAnalysis', { + defaultMessage: 'Log Pattern Analysis', + }), + path: `/${ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT}`, + navLinkStatus, + }, + { + id: 'changePointDetections', + title: i18n.translate('xpack.ml.deepLink.changePointDetection', { + defaultMessage: 'Change Point Detection', + }), + path: `/${ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT}`, + navLinkStatus, + }, + ], + }; + }, -function getMemoryUsageDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - return { - id: 'memoryUsage', - title: i18n.translate('xpack.ml.deepLink.memoryUsage', { - defaultMessage: 'Memory Usage', - }), - path: `/${ML_PAGES.MEMORY_USAGE}`, - navLinkStatus: getNavStatus(mlCapabilities, false), - }; -} - -function getDataVisualizerDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - return { - id: 'dataVisualizer', - title: i18n.translate('xpack.ml.deepLink.dataVisualizer', { - defaultMessage: 'Data Visualizer', - }), - path: `/${ML_PAGES.DATA_VISUALIZER}`, - navLinkStatus: getNavStatus(mlCapabilities, false), - }; -} - -function getFileUploadDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - return { - id: 'fileUpload', - title: i18n.translate('xpack.ml.deepLink.fileUpload', { - defaultMessage: 'File Upload', - }), - keywords: ['CSV', 'JSON'], - path: `/${ML_PAGES.DATA_VISUALIZER_FILE}`, - navLinkStatus: getNavStatus(mlCapabilities, false), - }; -} - -function getIndexDataVisualizerDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - return { - id: 'indexDataVisualizer', - title: i18n.translate('xpack.ml.deepLink.indexDataVisualizer', { - defaultMessage: 'Index Data Visualizer', - }), - path: `/${ML_PAGES.DATA_VISUALIZER_INDEX_SELECT}`, - navLinkStatus: getNavStatus(mlCapabilities, false), - }; -} + getNotificationsDeepLink: (): AppDeepLink => { + return { + id: 'notifications', + title: i18n.translate('xpack.ml.deepLink.notifications', { + defaultMessage: 'Notifications', + }), + path: `/${ML_PAGES.NOTIFICATIONS}`, + navLinkStatus: getNavStatus(isFullLicense), + }; + }, -function getDataComparisonDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - return { - id: 'dataComparison', - title: i18n.translate('xpack.ml.deepLink.dataComparison', { - defaultMessage: 'Data Comparison', - }), - path: `/${ML_PAGES.DATA_COMPARISON_INDEX_SELECT}`, - navLinkStatus: getNavStatus(mlCapabilities, false), - }; -} + getDataVisualizerDeepLink: (): AppDeepLink => { + return { + id: 'dataVisualizer', + title: i18n.translate('xpack.ml.deepLink.dataVisualizer', { + defaultMessage: 'Data Visualizer', + }), + path: `/${ML_PAGES.DATA_VISUALIZER}`, + navLinkStatus: getNavStatus(true), + }; + }, -function getSettingsDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - const navLinkStatus = getNavStatus(mlCapabilities, mlCapabilities.isADEnabled); - return { - id: 'settings', - title: i18n.translate('xpack.ml.deepLink.settings', { - defaultMessage: 'Settings', - }), - path: `/${ML_PAGES.SETTINGS}`, - navLinkStatus, - deepLinks: [ - { - id: 'calendarSettings', - title: i18n.translate('xpack.ml.deepLink.calendarSettings', { - defaultMessage: 'Calendars', + getFileUploadDeepLink: (): AppDeepLink => { + return { + id: 'fileUpload', + title: i18n.translate('xpack.ml.deepLink.fileUpload', { + defaultMessage: 'File Upload', }), - path: `/${ML_PAGES.CALENDARS_MANAGE}`, - navLinkStatus, - }, - { - id: 'filterListsSettings', - title: i18n.translate('xpack.ml.deepLink.filterListsSettings', { - defaultMessage: 'Filter Lists', + keywords: ['CSV', 'JSON'], + path: `/${ML_PAGES.DATA_VISUALIZER_FILE}`, + navLinkStatus: getNavStatus(true), + }; + }, + + getIndexDataVisualizerDeepLink: (): AppDeepLink => { + return { + id: 'indexDataVisualizer', + title: i18n.translate('xpack.ml.deepLink.indexDataVisualizer', { + defaultMessage: 'Index Data Visualizer', }), - path: `/${ML_PAGES.SETTINGS}`, // Link to settings page as read only users cannot view filter lists. - navLinkStatus, - }, - ], - }; -} + path: `/${ML_PAGES.DATA_VISUALIZER_INDEX_SELECT}`, + navLinkStatus: getNavStatus(true), + }; + }, -function getNotificationsDeepLink(mlCapabilities: MlCapabilities): AppDeepLink { - return { - id: 'notifications', - title: i18n.translate('xpack.ml.deepLink.notifications', { - defaultMessage: 'Notifications', - }), - path: `/${ML_PAGES.NOTIFICATIONS}`, - navLinkStatus: getNavStatus(mlCapabilities, true), + getDataComparisonDeepLink: (): AppDeepLink => { + return { + id: 'dataComparison', + title: i18n.translate('xpack.ml.deepLink.dataComparison', { + defaultMessage: 'Data Comparison', + }), + path: `/${ML_PAGES.DATA_COMPARISON_INDEX_SELECT}`, + navLinkStatus: getNavStatus(true), + }; + }, }; } -export function getDeepLinks(isFullLicense: boolean, mlCapabilities: MlCapabilities) { - const deepLinks: Array> = [ - getDataVisualizerDeepLink(mlCapabilities), - getFileUploadDeepLink(mlCapabilities), - getIndexDataVisualizerDeepLink(mlCapabilities), - getDataComparisonDeepLink(mlCapabilities), - ]; - - if (isFullLicense === true) { - deepLinks.push( - getOverviewLinkDeepLink(mlCapabilities), - getAnomalyDetectionDeepLink(mlCapabilities), - getDataFrameAnalyticsDeepLink(mlCapabilities), - getModelManagementDeepLink(mlCapabilities), - getMemoryUsageDeepLink(mlCapabilities), - getSettingsDeepLink(mlCapabilities), - getAiopsDeepLink(mlCapabilities), - getNotificationsDeepLink(mlCapabilities) - ); - } - - return deepLinks; +export function getDeepLinks( + isFullLicense: boolean, + mlCapabilities: MlCapabilities, + isServerless: boolean +) { + const links = createDeepLinks(mlCapabilities, isFullLicense, isServerless); + return Object.values(links).map((link) => link()); } diff --git a/x-pack/plugins/ml/public/register_home_feature.ts b/x-pack/plugins/ml/public/register_home_feature.ts new file mode 100644 index 0000000000000..baae66f72eea5 --- /dev/null +++ b/x-pack/plugins/ml/public/register_home_feature.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; +import { ML_APP_ROUTE, PLUGIN_ID } from '../common/constants/app'; + +export const registerHomeFeature = (home: HomePublicPluginSetup) => { + // register ML so it appears on the Kibana home page + home.featureCatalogue.register({ + id: PLUGIN_ID, + title: i18n.translate('xpack.ml.machineLearningTitle', { + defaultMessage: 'Machine Learning', + }), + subtitle: i18n.translate('xpack.ml.machineLearningSubtitle', { + defaultMessage: 'Model, predict, and detect.', + }), + description: i18n.translate('xpack.ml.machineLearningDescription', { + defaultMessage: + 'Automatically model the normal behavior of your time series data to detect anomalies.', + }), + icon: 'machineLearningApp', + path: ML_APP_ROUTE, + showOnHomePage: false, + category: 'data', + solutionId: 'kibana', + order: 500, + }); +}; diff --git a/x-pack/plugins/ml/public/ui_actions/edit_anomaly_charts_panel_action.tsx b/x-pack/plugins/ml/public/ui_actions/edit_anomaly_charts_panel_action.tsx index d79c897958554..e4f765f87c598 100644 --- a/x-pack/plugins/ml/public/ui_actions/edit_anomaly_charts_panel_action.tsx +++ b/x-pack/plugins/ml/public/ui_actions/edit_anomaly_charts_panel_action.tsx @@ -17,7 +17,8 @@ import { export const EDIT_ANOMALY_CHARTS_PANEL_ACTION = 'editAnomalyChartsPanelAction'; export function createEditAnomalyChartsPanelAction( - getStartServices: MlCoreSetup['getStartServices'] + getStartServices: MlCoreSetup['getStartServices'], + isServerless: boolean ): UiActionsActionDefinition { return { id: 'edit-anomaly-charts', @@ -43,6 +44,7 @@ export function createEditAnomalyChartsPanelAction( const result = await resolveEmbeddableAnomalyChartsUserInput( coreStart, + isServerless, embeddable.getInput() ); embeddable.updateInput(result); diff --git a/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx b/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx index 4352dc2df89bf..5070862023598 100644 --- a/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx +++ b/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx @@ -14,7 +14,8 @@ import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, EditSwimlanePanelContext } from '../e export const EDIT_SWIMLANE_PANEL_ACTION = 'editSwimlanePanelAction'; export function createEditSwimlanePanelAction( - getStartServices: MlCoreSetup['getStartServices'] + getStartServices: MlCoreSetup['getStartServices'], + isServerless: boolean ): UiActionsActionDefinition { return { id: 'edit-anomaly-swimlane', @@ -38,7 +39,11 @@ export function createEditSwimlanePanelAction( '../embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout' ); - const result = await resolveAnomalySwimlaneUserInput(coreStart, embeddable.getInput()); + const result = await resolveAnomalySwimlaneUserInput( + coreStart, + isServerless, + embeddable.getInput() + ); embeddable.updateInput(result); } catch (e) { return Promise.reject(); diff --git a/x-pack/plugins/ml/public/ui_actions/index.ts b/x-pack/plugins/ml/public/ui_actions/index.ts index 4067547e08956..f08f5bcd886bc 100644 --- a/x-pack/plugins/ml/public/ui_actions/index.ts +++ b/x-pack/plugins/ml/public/ui_actions/index.ts @@ -34,17 +34,24 @@ export { SWIM_LANE_SELECTION_TRIGGER }; */ export function registerMlUiActions( uiActions: UiActionsSetup, - core: CoreSetup + core: CoreSetup, + isServerless: boolean ) { // Initialize actions - const editSwimlanePanelAction = createEditSwimlanePanelAction(core.getStartServices); + const editSwimlanePanelAction = createEditSwimlanePanelAction( + core.getStartServices, + isServerless + ); const openInExplorerAction = createOpenInExplorerAction(core.getStartServices); const applyInfluencerFiltersAction = createApplyInfluencerFiltersAction(core.getStartServices); const applyEntityFieldFilterAction = createApplyEntityFieldFiltersAction(core.getStartServices); const applyTimeRangeSelectionAction = createApplyTimeRangeSelectionAction(core.getStartServices); const clearSelectionAction = createClearSelectionAction(core.getStartServices); - const editExplorerPanelAction = createEditAnomalyChartsPanelAction(core.getStartServices); - const visToAdJobAction = createVisToADJobAction(core.getStartServices); + const editExplorerPanelAction = createEditAnomalyChartsPanelAction( + core.getStartServices, + isServerless + ); + const visToAdJobAction = createVisToADJobAction(core.getStartServices, isServerless); // Register actions uiActions.registerAction(editSwimlanePanelAction); diff --git a/x-pack/plugins/ml/public/ui_actions/open_vis_in_ml_action.tsx b/x-pack/plugins/ml/public/ui_actions/open_vis_in_ml_action.tsx index fb0aa38e44d90..3e06b6175d61e 100644 --- a/x-pack/plugins/ml/public/ui_actions/open_vis_in_ml_action.tsx +++ b/x-pack/plugins/ml/public/ui_actions/open_vis_in_ml_action.tsx @@ -15,7 +15,8 @@ import { isLensEmbeddable, isMapEmbeddable } from '../application/jobs/new_job/j export const CREATE_LENS_VIS_TO_ML_AD_JOB_ACTION = 'createMLADJobAction'; export function createVisToADJobAction( - getStartServices: MlCoreSetup['getStartServices'] + getStartServices: MlCoreSetup['getStartServices'], + isServerless: boolean ): UiActionsActionDefinition<{ embeddable: Embeddable | MapEmbeddable }> { return { id: 'create-ml-ad-job-action', @@ -39,11 +40,26 @@ export function createVisToADJobAction( if (lens === undefined) { return; } - await showLensVisToADJobFlyout(embeddable, coreStart, share, data, lens, dashboard); + await showLensVisToADJobFlyout( + embeddable, + coreStart, + share, + data, + lens, + dashboard, + isServerless + ); } else if (isMapEmbeddable(embeddable)) { const [{ showMapVisToADJobFlyout }, [coreStart, { share, data, dashboard }]] = await Promise.all([import('../embeddables/job_creation/map'), getStartServices()]); - await showMapVisToADJobFlyout(embeddable, coreStart, share, data, dashboard); + await showMapVisToADJobFlyout( + embeddable, + coreStart, + share, + data, + dashboard, + isServerless + ); } } catch (e) { return Promise.reject(); diff --git a/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json b/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json index 90dbdbd9c121f..6ec23bc13c559 100644 --- a/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json +++ b/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json @@ -123,6 +123,7 @@ "MlInfo", "MlEsSearch", "MlIndexExists", + "MlReindexWithPipeline", "MlSpecificIndexExists", "JobAuditMessages", diff --git a/x-pack/plugins/ml/server/config_schema.ts b/x-pack/plugins/ml/server/config_schema.ts new file mode 100644 index 0000000000000..16db0505cc4e0 --- /dev/null +++ b/x-pack/plugins/ml/server/config_schema.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema, type TypeOf } from '@kbn/config-schema'; + +const enabledSchema = schema.maybe( + schema.object({ + enabled: schema.boolean(), + }) +); + +export const configSchema = schema.object({ + ad: enabledSchema, + dfa: enabledSchema, + nlp: enabledSchema, +}); + +export type ConfigSchema = TypeOf; diff --git a/x-pack/plugins/ml/server/index.ts b/x-pack/plugins/ml/server/index.ts index 9f46005dd5068..53c5b81ec9591 100644 --- a/x-pack/plugins/ml/server/index.ts +++ b/x-pack/plugins/ml/server/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PluginInitializerContext } from '@kbn/core/server'; +import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; import { MlServerPlugin } from './plugin'; export type { MlPluginSetup, MlPluginStart } from './plugin'; export type { @@ -26,5 +26,10 @@ export { InsufficientMLCapabilities, MLPrivilegesUninitialized, } from './shared'; +import { configSchema, type ConfigSchema } from './config_schema'; -export const plugin = (ctx: PluginInitializerContext) => new MlServerPlugin(ctx); +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; + +export const plugin = (ctx: PluginInitializerContext) => new MlServerPlugin(ctx); diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index 31794a901416f..18be37a187c44 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -8,12 +8,12 @@ import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import rison from '@kbn/rison'; -import { Duration } from 'moment/moment'; +import type { Duration } from 'moment/moment'; import { memoize } from 'lodash'; import { FIELD_FORMAT_IDS, - IFieldFormat, - SerializedFieldFormat, + type IFieldFormat, + type SerializedFieldFormat, } from '@kbn/field-formats-plugin/common'; import { isDefined } from '@kbn/ml-is-defined'; import { @@ -24,12 +24,12 @@ import { ML_ANOMALY_RESULT_TYPE, } from '@kbn/ml-anomaly-utils'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; -import { MlClient } from '../ml_client'; -import { +import type { MlClient } from '../ml_client'; +import type { MlAnomalyDetectionAlertParams, MlAnomalyDetectionAlertPreviewRequest, } from '../../routes/schemas/alerting_schema'; -import { +import type { AlertExecutionResult, InfluencerAnomalyAlertDoc, PreviewResponse, @@ -37,11 +37,11 @@ import { RecordAnomalyAlertDoc, TopHitsResultsKeys, } from '../../../common/types/alerts'; -import { AnomalyDetectionAlertContext } from './register_anomaly_detection_alert_type'; +import type { AnomalyDetectionAlertContext } from './register_anomaly_detection_alert_type'; import { resolveMaxTimeInterval } from '../../../common/util/job_utils'; import { getTopNBuckets, resolveLookbackInterval } from '../../../common/util/alerts'; import type { DatafeedsService } from '../../models/job_service/datafeeds'; -import { FieldFormatsRegistryProvider } from '../../../common/types/kibana'; +import type { FieldFormatsRegistryProvider } from '../../../common/types/kibana'; import type { AwaitReturnType } from '../../../common/types/common'; import { getTypicalAndActualValues } from '../../models/results_service/results_service'; import type { GetDataViewsService } from '../data_views_utils'; diff --git a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts index a1ec1931485ac..0644cdc764f20 100644 --- a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts @@ -6,17 +6,17 @@ */ import { groupBy, keyBy, memoize, partition } from 'lodash'; -import { KibanaRequest, Logger, SavedObjectsClientContract } from '@kbn/core/server'; +import type { KibanaRequest, Logger, SavedObjectsClientContract } from '@kbn/core/server'; import { i18n } from '@kbn/i18n'; -import { MlJob } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { MlJob } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isDefined } from '@kbn/ml-is-defined'; -import { MlClient } from '../ml_client'; -import { JobSelection } from '../../routes/schemas/alerting_schema'; -import { datafeedsProvider, DatafeedsService } from '../../models/job_service/datafeeds'; +import type { MlClient } from '../ml_client'; +import type { JobSelection } from '../../routes/schemas/alerting_schema'; +import { datafeedsProvider, type DatafeedsService } from '../../models/job_service/datafeeds'; import { ALL_JOBS_SELECTION, HEALTH_CHECK_NAMES } from '../../../common/constants/alerts'; -import { DatafeedStats } from '../../../common/types/anomaly_detection_jobs'; -import { GetGuards } from '../../shared_services/shared_services'; -import { +import type { DatafeedStats } from '../../../common/types/anomaly_detection_jobs'; +import type { GetGuards } from '../../shared_services/shared_services'; +import type { AnomalyDetectionJobsHealthAlertContext, DelayedDataResponse, JobsErrorsResponse, @@ -28,12 +28,12 @@ import { getResultJobsHealthRuleConfig, resolveLookbackInterval, } from '../../../common/util/alerts'; -import { AnnotationService } from '../../models/annotation_service/annotation'; +import type { AnnotationService } from '../../models/annotation_service/annotation'; import { annotationServiceProvider } from '../../models/annotation_service'; import { parseInterval } from '../../../common/util/parse_interval'; import { jobAuditMessagesProvider, - JobAuditMessagesService, + type JobAuditMessagesService, } from '../../models/job_audit_messages/job_audit_messages'; import type { FieldFormatsRegistryProvider } from '../../../common/types/kibana'; diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index d68e40f44b4a6..2a1f19b48802e 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -6,8 +6,8 @@ */ import { i18n } from '@kbn/i18n'; -import { KibanaRequest } from '@kbn/core/server'; -import { +import type { KibanaRequest } from '@kbn/core/server'; +import type { ActionGroup, AlertInstanceContext, AlertInstanceState, @@ -17,11 +17,14 @@ import { ML_ALERT_TYPES } from '../../../common/constants/alerts'; import { PLUGIN_ID } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; import { - MlAnomalyDetectionAlertParams, + type MlAnomalyDetectionAlertParams, mlAnomalyDetectionAlertParams, } from '../../routes/schemas/alerting_schema'; -import { RegisterAlertParams } from './register_ml_alerts'; -import { InfluencerAnomalyAlertDoc, RecordAnomalyAlertDoc } from '../../../common/types/alerts'; +import type { RegisterAlertParams } from './register_ml_alerts'; +import { + InfluencerAnomalyAlertDoc, + type RecordAnomalyAlertDoc, +} from '../../../common/types/alerts'; /** * Base Anomaly detection alerting rule context. diff --git a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts index 81a2a1462d720..e7c9c527439cd 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts @@ -6,13 +6,13 @@ */ import { i18n } from '@kbn/i18n'; -import { KibanaRequest } from '@kbn/core/server'; -import { +import type { KibanaRequest } from '@kbn/core/server'; +import type { MlDatafeedState, MlJobState, MlJobStats, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { +import type { ActionGroup, AlertInstanceContext, AlertInstanceState, @@ -24,9 +24,9 @@ import { PLUGIN_ID } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; import { anomalyDetectionJobsHealthRuleParams, - AnomalyDetectionJobsHealthRuleParams, + type AnomalyDetectionJobsHealthRuleParams, } from '../../routes/schemas/alerting_schema'; -import { RegisterAlertParams } from './register_ml_alerts'; +import type { RegisterAlertParams } from './register_ml_alerts'; import type { JobMessage } from '../../../common/types/audit_message'; type ModelSizeStats = MlJobStats['model_size_stats']; diff --git a/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts b/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts index ab1136b5d1edc..582676b61928b 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { Logger } from '@kbn/core/server'; -import { AlertingPlugin } from '@kbn/alerting-plugin/server'; +import type { Logger } from '@kbn/core/server'; +import type { AlertingPlugin } from '@kbn/alerting-plugin/server'; import { registerAnomalyDetectionAlertType } from './register_anomaly_detection_alert_type'; -import { SharedServices } from '../../shared_services'; +import type { SharedServices } from '../../shared_services'; import { registerJobsMonitoringRuleType } from './register_jobs_monitoring_rule_type'; -import { MlServicesProviders } from '../../shared_services/shared_services'; +import type { MlServicesProviders } from '../../shared_services/shared_services'; +import type { MlFeatures } from '../../types'; export interface RegisterAlertParams { alerting: AlertingPlugin['setup']; @@ -19,7 +20,9 @@ export interface RegisterAlertParams { mlServicesProviders: MlServicesProviders; } -export function registerMlAlerts(params: RegisterAlertParams) { - registerAnomalyDetectionAlertType(params); - registerJobsMonitoringRuleType(params); +export function registerMlAlerts(alertParams: RegisterAlertParams, enabledFeatures: MlFeatures) { + if (enabledFeatures.ad === true) { + registerAnomalyDetectionAlertType(alertParams); + registerJobsMonitoringRuleType(alertParams); + } } diff --git a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts index 2be5bd877552a..dadb15b8a46b8 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts @@ -59,7 +59,7 @@ function getSwitcher( basicLicenseMlCapabilities.forEach((c) => (mlCaps[c] = originalCapabilities[c])); } - return { ml: applyEnabledFeatures(mlCaps, enabledFeatures) }; + return { ml: mlCaps }; } catch (e) { logger.debug(`Error updating capabilities for ML based on licensing: ${e}`); return {}; @@ -67,22 +67,26 @@ function getSwitcher( }; } -function applyEnabledFeatures(mlCaps: MlCapabilities, enabledFeatures: MlFeatures) { - mlCaps.isADEnabled = enabledFeatures.ad; - mlCaps.isDFAEnabled = enabledFeatures.dfa; - mlCaps.isNLPEnabled = enabledFeatures.nlp; +function applyEnabledFeatures(mlCaps: MlCapabilities, { ad, dfa, nlp }: MlFeatures) { + mlCaps.isADEnabled = ad; + mlCaps.isDFAEnabled = dfa; + mlCaps.isNLPEnabled = nlp; + mlCaps.canViewMlNodes = mlCaps.canViewMlNodes && ad && dfa && nlp; - mlCaps.canViewMlNodes = - mlCaps.canViewMlNodes && mlCaps.isADEnabled && mlCaps.isDFAEnabled && mlCaps.isNLPEnabled; - - if (enabledFeatures.ad === false) { - featureCapabilities.ad.forEach((c) => (mlCaps[c] = false)); + if (ad === false) { + for (const c of featureCapabilities.ad) { + mlCaps[c] = false; + } } - if (enabledFeatures.dfa === false) { - featureCapabilities.dfa.forEach((c) => (mlCaps[c] = false)); + if (dfa === false) { + for (const c of featureCapabilities.dfa) { + mlCaps[c] = false; + } } - if (enabledFeatures.nlp === false) { - featureCapabilities.nlp.forEach((c) => (mlCaps[c] = false)); + if (nlp === false && dfa === false) { + for (const c of featureCapabilities.nlp) { + mlCaps[c] = false; + } } return mlCaps; diff --git a/x-pack/plugins/ml/server/lib/register_cases.ts b/x-pack/plugins/ml/server/lib/register_cases.ts new file mode 100644 index 0000000000000..a9644638e11dc --- /dev/null +++ b/x-pack/plugins/ml/server/lib/register_cases.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CasesSetup } from '@kbn/cases-plugin/server'; +import { + CASE_ATTACHMENT_TYPE_ID_ANOMALY_EXPLORER_CHARTS, + CASE_ATTACHMENT_TYPE_ID_ANOMALY_SWIMLANE, +} from '../../common/constants/cases'; +import type { MlFeatures } from '../types'; + +export function registerCasesPersistableState(cases: CasesSetup, enabledFeatures: MlFeatures) { + if (enabledFeatures.ad === true) { + cases.attachmentFramework.registerPersistableState({ + id: CASE_ATTACHMENT_TYPE_ID_ANOMALY_SWIMLANE, + }); + + cases.attachmentFramework.registerPersistableState({ + id: CASE_ATTACHMENT_TYPE_ID_ANOMALY_EXPLORER_CHARTS, + }); + } +} diff --git a/x-pack/plugins/ml/server/lib/register_sample_data_set_links.ts b/x-pack/plugins/ml/server/lib/register_sample_data_set_links.ts new file mode 100644 index 0000000000000..b4aa199a55a0a --- /dev/null +++ b/x-pack/plugins/ml/server/lib/register_sample_data_set_links.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { HomeServerPluginSetup } from '@kbn/home-plugin/server'; +import type { MlFeatures } from '../types'; + +export function registerSampleDataSetLinks( + home: HomeServerPluginSetup, + enabledFeatures: MlFeatures +) { + if (enabledFeatures.ad === true) { + const sampleDataLinkLabel = i18n.translate('xpack.ml.sampleDataLinkLabel', { + defaultMessage: 'ML jobs', + }); + const { addAppLinksToSampleDataset } = home.sampleData; + const getCreateJobPath = (jobId: string, dataViewId: string) => + `/app/ml/modules/check_view_or_create?id=${jobId}&index=${dataViewId}`; + + addAppLinksToSampleDataset('ecommerce', [ + { + sampleObject: { + type: 'index-pattern', + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + }, + getPath: (objectId) => getCreateJobPath('sample_data_ecommerce', objectId), + label: sampleDataLinkLabel, + icon: 'machineLearningApp', + }, + ]); + + addAppLinksToSampleDataset('logs', [ + { + sampleObject: { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + }, + getPath: (objectId) => getCreateJobPath('sample_data_weblogs', objectId), + label: sampleDataLinkLabel, + icon: 'machineLearningApp', + }, + ]); + } +} diff --git a/x-pack/plugins/ml/server/lib/sample_data_sets/index.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/index.ts deleted file mode 100644 index 1cdc7c76ba0d3..0000000000000 --- a/x-pack/plugins/ml/server/lib/sample_data_sets/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { initSampleDataSets } from './sample_data_sets'; diff --git a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts deleted file mode 100644 index 393490793004a..0000000000000 --- a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts +++ /dev/null @@ -1,45 +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 { i18n } from '@kbn/i18n'; -import type { HomeServerPluginSetup } from '@kbn/home-plugin/server'; -import { MlLicense } from '../../../common/license'; - -export function initSampleDataSets(mlLicense: MlLicense, home: HomeServerPluginSetup) { - if (mlLicense.isMlEnabled() && mlLicense.isFullLicense()) { - const sampleDataLinkLabel = i18n.translate('xpack.ml.sampleDataLinkLabel', { - defaultMessage: 'ML jobs', - }); - const { addAppLinksToSampleDataset } = home.sampleData; - const getCreateJobPath = (jobId: string, dataViewId: string) => - `/app/ml/modules/check_view_or_create?id=${jobId}&index=${dataViewId}`; - - addAppLinksToSampleDataset('ecommerce', [ - { - sampleObject: { - type: 'index-pattern', - id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', - }, - getPath: (objectId) => getCreateJobPath('sample_data_ecommerce', objectId), - label: sampleDataLinkLabel, - icon: 'machineLearningApp', - }, - ]); - - addAppLinksToSampleDataset('logs', [ - { - sampleObject: { - type: 'index-pattern', - id: '90943e30-9a47-11e8-b64d-95841ca0b247', - }, - getPath: (objectId) => getCreateJobPath('sample_data_weblogs', objectId), - label: sampleDataLinkLabel, - icon: 'machineLearningApp', - }, - ]); - } -} diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts index 05c2764297d2d..4f1796782a6ae 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts @@ -7,7 +7,7 @@ import Boom from '@hapi/boom'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { IScopedClusterClient } from '@kbn/core/server'; +import type { IScopedClusterClient } from '@kbn/core/server'; import { getAnalysisType, INDEX_CREATED_BY, @@ -23,20 +23,21 @@ import { flatten } from 'lodash'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { modelsProvider } from '../model_management'; import { - ExtendAnalyticsMapArgs, - GetAnalyticsMapArgs, - InitialElementsReturnType, + type ExtendAnalyticsMapArgs, + type GetAnalyticsMapArgs, + type InitialElementsReturnType, + type NextLinkReturnType, + type GetAnalyticsJobIdArg, + type GetAnalyticsModelIdArg, isCompleteInitialReturnType, isAnalyticsMapEdgeElement, isAnalyticsMapNodeElement, isIndexPatternLinkReturnType, isJobDataLinkReturnType, isTransformLinkReturnType, - NextLinkReturnType, - GetAnalyticsJobIdArg, - GetAnalyticsModelIdArg, } from './types'; import type { MlClient } from '../../lib/ml_client'; +import type { MlFeatures } from '../../types'; import { DEFAULT_TRAINED_MODELS_PAGE_SIZE } from '../../routes/trained_models'; export class AnalyticsManager { @@ -44,12 +45,20 @@ export class AnalyticsManager { private _jobs: estypes.MlDataframeAnalyticsSummary[] = []; private _transforms?: TransformGetTransformTransformSummary[]; - constructor(private _mlClient: MlClient, private _client: IScopedClusterClient) {} + constructor( + private readonly _mlClient: MlClient, + private readonly _client: IScopedClusterClient, + private readonly _enabledFeatures: MlFeatures + ) {} private async initData() { const [models, jobs] = await Promise.all([ - this._mlClient.getTrainedModels({ size: DEFAULT_TRAINED_MODELS_PAGE_SIZE }), - this._mlClient.getDataFrameAnalytics({ size: 1000 }), + this._enabledFeatures.nlp || this._enabledFeatures.dfa + ? this._mlClient.getTrainedModels({ size: DEFAULT_TRAINED_MODELS_PAGE_SIZE }) + : { trained_model_configs: [] }, + this._enabledFeatures.dfa + ? this._mlClient.getDataFrameAnalytics({ size: 1000 }) + : { data_frame_analytics: [] }, ]); this._trainedModels = models.trained_model_configs; this._jobs = jobs.data_frame_analytics; diff --git a/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts b/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts index 3f85487a4cbf3..31857da674d52 100644 --- a/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts +++ b/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts @@ -133,10 +133,16 @@ describe('Model service', () => { }), } as unknown as jest.Mocked; + const mlFeatures = { + ad: true, + dfa: true, + nlp: true, + }; + let service: MemoryUsageService; beforeEach(() => { - service = new MemoryUsageService(mlClient); + service = new MemoryUsageService(mlClient, mlFeatures); }); afterEach(() => {}); diff --git a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts index 541b396b0a6e5..cd665c387302f 100644 --- a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts +++ b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts @@ -22,6 +22,7 @@ import type { NodeDeploymentStatsResponse, NodesOverviewResponse, } from '../../../common/types/trained_models'; +import type { MlFeatures } from '../../types'; // @ts-expect-error numeral missing value const AD_EXTRA_MEMORY = numeral('10MB').value(); @@ -33,7 +34,7 @@ const NODE_FIELDS = ['attributes', 'name', 'roles'] as const; export type RequiredNodeFields = Pick; export class MemoryUsageService { - constructor(private readonly mlClient: MlClient) {} + constructor(private readonly mlClient: MlClient, private readonly mlFeatures: MlFeatures) {} public async getMemorySizes(itemType?: MlSavedObjectType, node?: string, showClosedJobs = false) { let memories: MemoryUsageInfo[] = []; @@ -60,11 +61,19 @@ export class MemoryUsageService { } private async getADJobsSizes() { + if (this.mlFeatures.ad === false) { + return []; + } + const jobs = await this.mlClient.getJobStats(); return jobs.jobs.map(this.getADJobMemorySize); } private async getTrainedModelsSizes() { + if (this.mlFeatures.nlp === false) { + return []; + } + const [models, stats] = await Promise.all([ this.mlClient.getTrainedModels(), this.mlClient.getTrainedModelsStats(), @@ -83,6 +92,10 @@ export class MemoryUsageService { } private async getDFAJobsSizes() { + if (this.mlFeatures.dfa === false) { + return []; + } + const [jobs, jobsStats] = await Promise.all([ this.mlClient.getDataFrameAnalytics(), this.mlClient.getDataFrameAnalyticsStats(), diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index b3f829faf170b..342350fac998a 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -18,21 +18,20 @@ import type { SavedObjectsServiceStart, UiSettingsServiceStart, } from '@kbn/core/server'; -import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; +import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; import type { HomeServerPluginSetup } from '@kbn/home-plugin/server'; +import type { CasesSetup } from '@kbn/cases-plugin/server'; +import type { MlFeatures, PluginsSetup, PluginsStart, RouteInitialization } from './types'; +import type { MlCapabilities } from '../common/types/capabilities'; +import type { ConfigSchema } from './config_schema'; import { jsonSchemaRoutes } from './routes/json_schema'; import { notificationsRoutes } from './routes/notifications'; -import type { MlFeatures, PluginsSetup, PluginsStart, RouteInitialization } from './types'; import { PLUGIN_ID } from '../common/constants/app'; -import type { MlCapabilities } from '../common/types/capabilities'; - import { initMlServerLog } from './lib/log'; -import { initSampleDataSets } from './lib/sample_data_sets'; - import { annotationRoutes } from './routes/annotations'; import { calendars } from './routes/calendars'; import { dataFeedRoutes } from './routes/datafeeds'; @@ -68,18 +67,10 @@ import { ML_ALERT_TYPES } from '../common/constants/alerts'; import { alertingRoutes } from './routes/alerting'; import { registerCollector } from './usage'; import { SavedObjectsSyncService } from './saved_objects/sync_task'; -import { - CASE_ATTACHMENT_TYPE_ID_ANOMALY_SWIMLANE, - CASE_ATTACHMENT_TYPE_ID_ANOMALY_EXPLORER_CHARTS, -} from '../common/constants/cases'; +import { registerCasesPersistableState } from './lib/register_cases'; +import { registerSampleDataSetLinks } from './lib/register_sample_data_set_links'; -type SetFeaturesEnabled = (features: MlFeatures) => void; - -interface MlSetup { - setFeaturesEnabled: SetFeaturesEnabled; -} - -export type MlPluginSetup = SharedServices & MlSetup; +export type MlPluginSetup = SharedServices; export type MlPluginStart = void; export class MlServerPlugin @@ -95,6 +86,7 @@ export class MlServerPlugin private spacesPlugin: SpacesPluginSetup | undefined; private security: SecurityPluginSetup | undefined; private home: HomeServerPluginSetup | null = null; + private cases: CasesSetup | null | undefined = null; private dataViews: DataViewsPluginStart | null = null; private isMlReady: Promise; private setMlReady: () => void = () => {}; @@ -105,17 +97,19 @@ export class MlServerPlugin nlp: true, }; - constructor(ctx: PluginInitializerContext) { + constructor(ctx: PluginInitializerContext) { this.log = ctx.logger.get(); this.mlLicense = new MlLicense(); this.isMlReady = new Promise((resolve) => (this.setMlReady = resolve)); this.savedObjectsSyncService = new SavedObjectsSyncService(this.log); + this.initEnabledFeatures(ctx.config.get()); } public setup(coreSetup: CoreSetup, plugins: PluginsSetup): MlPluginSetup { this.spacesPlugin = plugins.spaces; this.security = plugins.security; this.home = plugins.home; + this.cases = plugins.cases; const { admin, user, apmUser } = getPluginPrivileges(); plugins.features.registerKibanaFeature({ @@ -157,8 +151,6 @@ export class MlServerPlugin }, }); - registerKibanaSettings(coreSetup); - // initialize capabilities switcher to add license filter to ml capabilities setupCapabilitiesSwitcher( coreSetup, @@ -224,25 +216,39 @@ export class MlServerPlugin coreSetup.getStartServices ), mlLicense: this.mlLicense, - enabledFeatures: this.enabledFeatures, + getEnabledFeatures: () => Object.assign({}, this.enabledFeatures), }; - annotationRoutes(routeInit, plugins.security); - calendars(routeInit); - dataFeedRoutes(routeInit); - dataFrameAnalyticsRoutes(routeInit); - dataRecognizer(routeInit); + // Register Anomaly Detection routes + if (this.enabledFeatures.ad) { + annotationRoutes(routeInit, plugins.security); + calendars(routeInit); + dataFeedRoutes(routeInit); + dataRecognizer(routeInit); + filtersRoutes(routeInit); + jobAuditMessagesRoutes(routeInit); + jobRoutes(routeInit); + jobServiceRoutes(routeInit); + resultsServiceRoutes(routeInit); + jobValidationRoutes(routeInit); + } + + // Register Data Frame Analytics routes + if (this.enabledFeatures.dfa) { + dataFrameAnalyticsRoutes(routeInit); + } + + // Register Trained Model Management routes + if (this.enabledFeatures.dfa || this.enabledFeatures.nlp) { + modelManagementRoutes(routeInit); + trainedModelsRoutes(routeInit); + } + + // Register Miscellaneous routes dataVisualizerRoutes(routeInit); fieldsService(routeInit); - filtersRoutes(routeInit); indicesRoutes(routeInit); - jobAuditMessagesRoutes(routeInit); - jobRoutes(routeInit); - jobServiceRoutes(routeInit); managementRoutes(routeInit); - modelManagementRoutes(routeInit); - resultsServiceRoutes(routeInit); - jobValidationRoutes(routeInit); savedObjectsRoutes(routeInit, { getSpaces, resolveMlCapabilities, @@ -252,7 +258,6 @@ export class MlServerPlugin cloud: plugins.cloud, resolveMlCapabilities, }); - trainedModelsRoutes(routeInit); notificationsRoutes(routeInit); jsonSchemaRoutes(routeInit); alertingRoutes(routeInit, sharedServicesProviders); @@ -260,14 +265,19 @@ export class MlServerPlugin initMlServerLog({ log: this.log }); if (plugins.alerting) { - registerMlAlerts({ - alerting: plugins.alerting, - logger: this.log, - mlSharedServices: sharedServicesProviders, - mlServicesProviders: internalServicesProviders, - }); + registerMlAlerts( + { + alerting: plugins.alerting, + logger: this.log, + mlSharedServices: sharedServicesProviders, + mlServicesProviders: internalServicesProviders, + }, + this.enabledFeatures + ); } + registerKibanaSettings(coreSetup); + if (plugins.usageCollection) { const getIndexForType = (type: string) => coreSetup @@ -276,29 +286,7 @@ export class MlServerPlugin registerCollector(plugins.usageCollection, getIndexForType); } - if (plugins.cases) { - plugins.cases.attachmentFramework.registerPersistableState({ - id: CASE_ATTACHMENT_TYPE_ID_ANOMALY_SWIMLANE, - }); - - plugins.cases.attachmentFramework.registerPersistableState({ - id: CASE_ATTACHMENT_TYPE_ID_ANOMALY_EXPLORER_CHARTS, - }); - } - - const setFeaturesEnabled = (features: MlFeatures) => { - if (features.ad !== undefined) { - this.enabledFeatures.ad = features.ad; - } - if (features.dfa !== undefined) { - this.enabledFeatures.dfa = features.dfa; - } - if (features.nlp !== undefined) { - this.enabledFeatures.nlp = features.nlp; - } - }; - - return { ...sharedServicesProviders, setFeaturesEnabled }; + return { ...sharedServicesProviders }; } public start(coreStart: CoreStart, plugins: PluginsStart): MlPluginStart { @@ -319,10 +307,14 @@ export class MlServerPlugin return; } - if (this.home) { - initSampleDataSets(mlLicense, this.home); + if (mlLicense.isMlEnabled() && mlLicense.isFullLicense()) { + if (this.cases) { + registerCasesPersistableState(this.cases, this.enabledFeatures); + } + if (this.home) { + registerSampleDataSetLinks(this.home, this.enabledFeatures); + } } - // check whether the job saved objects exist // and create them if needed. const { initializeJobs } = jobSavedObjectsInitializationFactory( @@ -340,4 +332,16 @@ export class MlServerPlugin public stop() { this.mlLicense.unsubscribe(); } + + private initEnabledFeatures(config: ConfigSchema) { + if (config.ad?.enabled !== undefined) { + this.enabledFeatures.ad = config.ad.enabled; + } + if (config.dfa?.enabled !== undefined) { + this.enabledFeatures.dfa = config.dfa.enabled; + } + if (config.nlp?.enabled !== undefined) { + this.enabledFeatures.nlp = config.nlp.enabled; + } + } } diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 56e4e4ee4f625..fb5eca48d8fa1 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -15,7 +15,7 @@ import { import { ML_INTERNAL_BASE_PATH } from '../../common/constants/app'; import { wrapError } from '../client/error_wrapper'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; -import type { RouteInitialization } from '../types'; +import type { MlFeatures, RouteInitialization } from '../types'; import { dataAnalyticsJobConfigSchema, dataAnalyticsJobUpdateSchema, @@ -51,9 +51,10 @@ function deleteDestDataViewById(dataViewsService: DataViewsService, dataViewId: function getExtendedMap( mlClient: MlClient, client: IScopedClusterClient, - idOptions: ExtendAnalyticsMapArgs + idOptions: ExtendAnalyticsMapArgs, + enabledFeatures: MlFeatures ) { - const analytics = new AnalyticsManager(mlClient, client); + const analytics = new AnalyticsManager(mlClient, client, enabledFeatures); return analytics.extendAnalyticsMapForAnalyticsJob(idOptions); } @@ -63,17 +64,13 @@ function getExtendedModelsMap( idOptions: { analyticsId?: string; modelId?: string; - } + }, + enabledFeatures: MlFeatures ) { - const analytics = new AnalyticsManager(mlClient, client); + const analytics = new AnalyticsManager(mlClient, client, enabledFeatures); return analytics.extendModelsMap(idOptions); } -export function getAnalyticsManager(mlClient: MlClient, client: IScopedClusterClient) { - const analytics = new AnalyticsManager(mlClient, client); - return analytics; -} - // replace the recursive field and agg references with a // map of ids to allow it to be stringified for transportation // over the network. @@ -95,7 +92,12 @@ function convertForStringify(aggs: Aggregation[], fields: Field[]): void { /** * Routes for the data frame analytics */ -export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: RouteInitialization) { +export function dataFrameAnalyticsRoutes({ + router, + mlLicense, + routeGuard, + getEnabledFeatures, +}: RouteInitialization) { async function userCanDeleteIndex( client: IScopedClusterClient, destinationIndex: string @@ -795,16 +797,26 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout let results; if (treatAsRoot === 'true' || treatAsRoot === true) { - // @ts-expect-error never used as analyticsId - results = await getExtendedMap(mlClient, client, { - analyticsId: type !== JOB_MAP_NODE_TYPES.INDEX ? analyticsId : undefined, - index: type === JOB_MAP_NODE_TYPES.INDEX ? analyticsId : undefined, - }); + results = await getExtendedMap( + mlClient, + client, + // @ts-expect-error never used as analyticsId + { + analyticsId: type !== JOB_MAP_NODE_TYPES.INDEX ? analyticsId : undefined, + index: type === JOB_MAP_NODE_TYPES.INDEX ? analyticsId : undefined, + }, + getEnabledFeatures() + ); } else { - results = await getExtendedModelsMap(mlClient, client, { - analyticsId: type !== JOB_MAP_NODE_TYPES.TRAINED_MODEL ? analyticsId : undefined, - modelId: type === JOB_MAP_NODE_TYPES.TRAINED_MODEL ? analyticsId : undefined, - }); + results = await getExtendedModelsMap( + mlClient, + client, + { + analyticsId: type !== JOB_MAP_NODE_TYPES.TRAINED_MODEL ? analyticsId : undefined, + modelId: type === JOB_MAP_NODE_TYPES.TRAINED_MODEL ? analyticsId : undefined, + }, + getEnabledFeatures() + ); } return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/management.ts b/x-pack/plugins/ml/server/routes/management.ts index 9c68c8fcef261..7a74bac8d5128 100644 --- a/x-pack/plugins/ml/server/routes/management.ts +++ b/x-pack/plugins/ml/server/routes/management.ts @@ -19,11 +19,12 @@ import type { AnalyticsManagementItems, TrainedModelsManagementItems, } from '../../common/types/management'; +import { filterForEnabledFeatureModels } from './trained_models'; /** * Routes for management service */ -export function managementRoutes({ router, routeGuard }: RouteInitialization) { +export function managementRoutes({ router, routeGuard, getEnabledFeatures }: RouteInitialization) { /** * @apiGroup Management * @@ -126,12 +127,14 @@ export function managementRoutes({ router, routeGuard }: RouteInitialization) { trainedModelsSpaces(), ]); + const filteredModels = filterForEnabledFeatureModels(models, getEnabledFeatures()); + const modelStatsMapped = modelsStats.reduce((acc, cur) => { acc[cur.model_id] = cur; return acc; }, {} as Record); - const modelsWithSpaces: TrainedModelsManagementItems[] = models.map((m) => { + const modelsWithSpaces: TrainedModelsManagementItems[] = filteredModels.map((m) => { const id = m.model_id; return { id, diff --git a/x-pack/plugins/ml/server/routes/model_management.ts b/x-pack/plugins/ml/server/routes/model_management.ts index 853ee6582a7f7..31709dd4c32d0 100644 --- a/x-pack/plugins/ml/server/routes/model_management.ts +++ b/x-pack/plugins/ml/server/routes/model_management.ts @@ -20,7 +20,11 @@ import { wrapError } from '../client/error_wrapper'; import { MemoryUsageService } from '../models/model_management'; import { itemTypeLiterals } from './schemas/saved_objects'; -export function modelManagementRoutes({ router, routeGuard }: RouteInitialization) { +export function modelManagementRoutes({ + router, + routeGuard, + getEnabledFeatures, +}: RouteInitialization) { /** * @apiGroup ModelManagement * @@ -48,7 +52,7 @@ export function modelManagementRoutes({ router, routeGuard }: RouteInitializatio }, routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, response }) => { try { - const memoryUsageService = new MemoryUsageService(mlClient); + const memoryUsageService = new MemoryUsageService(mlClient, getEnabledFeatures()); const result = await memoryUsageService.getNodesOverview(); return response.ok({ body: result, @@ -95,7 +99,7 @@ export function modelManagementRoutes({ router, routeGuard }: RouteInitializatio routeGuard.fullLicenseAPIGuard(async ({ mlClient, response, request }) => { try { - const memoryUsageService = new MemoryUsageService(mlClient); + const memoryUsageService = new MemoryUsageService(mlClient, getEnabledFeatures()); return response.ok({ body: await memoryUsageService.getMemorySizes( request.query.type, diff --git a/x-pack/plugins/ml/server/routes/notifications.ts b/x-pack/plugins/ml/server/routes/notifications.ts index f9c64543dd042..c248399ff001a 100644 --- a/x-pack/plugins/ml/server/routes/notifications.ts +++ b/x-pack/plugins/ml/server/routes/notifications.ts @@ -14,7 +14,11 @@ import { import { wrapError } from '../client/error_wrapper'; import type { RouteInitialization } from '../types'; -export function notificationsRoutes({ router, routeGuard, enabledFeatures }: RouteInitialization) { +export function notificationsRoutes({ + router, + routeGuard, + getEnabledFeatures, +}: RouteInitialization) { /** * @apiGroup Notifications * @@ -49,7 +53,7 @@ export function notificationsRoutes({ router, routeGuard, enabledFeatures }: Rou const notificationsService = new NotificationsService( client, mlSavedObjectService, - enabledFeatures + getEnabledFeatures() ); const results = await notificationsService.searchMessages(request.query); @@ -98,7 +102,7 @@ export function notificationsRoutes({ router, routeGuard, enabledFeatures }: Rou const notificationsService = new NotificationsService( client, mlSavedObjectService, - enabledFeatures + getEnabledFeatures() ); const results = await notificationsService.countMessages(request.query); diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts index 812640fbcd368..41e59996cb954 100644 --- a/x-pack/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -6,6 +6,7 @@ */ import { schema } from '@kbn/config-schema'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ML_INTERNAL_BASE_PATH } from '../../common/constants/app'; import { wrapError } from '../client/error_wrapper'; @@ -73,7 +74,6 @@ export function systemRoutes( // return that security is disabled and don't call the privilegeCheck endpoint return response.ok({ body: { - securityDisabled: true, upgradeInProgress, }, }); @@ -81,7 +81,7 @@ export function systemRoutes( const body = await asCurrentUser.security.hasPrivileges({ body: request.body }); return response.ok({ body: { - ...body, + hasPrivileges: body, upgradeInProgress, }, }); @@ -277,6 +277,47 @@ export function systemRoutes( return acc; }, {} as Record); + return response.ok({ + body: result, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + }) + ); + + /** + * @apiGroup SystemRoutes + * + * @api {post} /internal/ml/reindex_with_pipeline ES reindex wrapper to reindex with pipeline + * @apiName MlReindexWithPipeline + */ + router.versioned + .post({ + path: `${ML_INTERNAL_BASE_PATH}/reindex_with_pipeline`, + access: 'internal', + options: { + tags: ['access:ml:canCreateTrainedModels'], + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: schema.any(), + }, + }, + }, + routeGuard.fullLicenseAPIGuard(async ({ client, request, response }) => { + const reindexRequest = { + body: request.body, + // Create a task and return task id instead of blocking until complete + wait_for_completion: false, + } as estypes.ReindexRequest; + try { + const result = await client.asCurrentUser.reindex(reindexRequest); + return response.ok({ body: result, }); diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index 065b5878da9b2..ab5d3a87e8f46 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -5,11 +5,12 @@ * 2.0. */ +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { schema } from '@kbn/config-schema'; -import { ErrorType } from '@kbn/ml-error-utils'; -import { type MlGetTrainedModelsRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { ErrorType } from '@kbn/ml-error-utils'; +import type { MlGetTrainedModelsRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ML_INTERNAL_BASE_PATH } from '../../common/constants/app'; -import { RouteInitialization } from '../types'; +import type { MlFeatures, RouteInitialization } from '../types'; import { wrapError } from '../client/error_wrapper'; import { deleteTrainedModelQuerySchema, @@ -25,14 +26,34 @@ import { updateDeploymentParamsSchema, createIngestPipelineSchema, } from './schemas/inference_schema'; -import { TrainedModelConfigResponse } from '../../common/types/trained_models'; +import type { TrainedModelConfigResponse } from '../../common/types/trained_models'; import { mlLog } from '../lib/log'; import { forceQuerySchema } from './schemas/anomaly_detectors_schema'; import { modelsProvider } from '../models/model_management'; export const DEFAULT_TRAINED_MODELS_PAGE_SIZE = 10000; -export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) { +export function filterForEnabledFeatureModels( + models: TrainedModelConfigResponse[] | estypes.MlTrainedModelConfig[], + enabledFeatures: MlFeatures +) { + let filteredModels = models; + if (enabledFeatures.nlp === false) { + filteredModels = filteredModels.filter((m) => m.model_type === 'tree_ensemble'); + } + + if (enabledFeatures.dfa === false) { + filteredModels = filteredModels.filter((m) => m.model_type !== 'tree_ensemble'); + } + + return filteredModels; +} + +export function trainedModelsRoutes({ + router, + routeGuard, + getEnabledFeatures, +}: RouteInitialization) { /** * @apiGroup TrainedModels * @@ -62,14 +83,14 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) try { const { modelId } = request.params; const { with_pipelines: withPipelines, ...query } = request.query; - const body = await mlClient.getTrainedModels({ + const resp = await mlClient.getTrainedModels({ ...query, ...(modelId ? { model_id: modelId } : {}), size: DEFAULT_TRAINED_MODELS_PAGE_SIZE, } as MlGetTrainedModelsRequest); // model_type is missing // @ts-ignore - const result = body.trained_model_configs as TrainedModelConfigResponse[]; + const result = resp.trained_model_configs as TrainedModelConfigResponse[]; try { if (withPipelines) { // Also need to retrieve the list of deployment IDs from stats @@ -123,8 +144,10 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) mlLog.debug(e); } + const body = filterForEnabledFeatureModels(result, getEnabledFeatures()); + return response.ok({ - body: result, + body, }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/ml/server/types.ts b/x-pack/plugins/ml/server/types.ts index 4bca1336ae0b9..302df71df5f38 100644 --- a/x-pack/plugins/ml/server/types.ts +++ b/x-pack/plugins/ml/server/types.ts @@ -80,7 +80,7 @@ export interface RouteInitialization { router: IRouter; mlLicense: MlLicense; routeGuard: RouteGuard; - enabledFeatures: MlFeatures; + getEnabledFeatures: () => MlFeatures; } export type MlFeatures = Record<'ad' | 'dfa' | 'nlp', boolean>; diff --git a/x-pack/plugins/monitoring/dev_docs/reference/indices.md b/x-pack/plugins/monitoring/dev_docs/reference/indices.md index 41038ba43b845..1993d31d157f2 100644 --- a/x-pack/plugins/monitoring/dev_docs/reference/indices.md +++ b/x-pack/plugins/monitoring/dev_docs/reference/indices.md @@ -24,8 +24,8 @@ And finally if using the standalone metricbeat modules with `xpack.enabled: fals The index templates for `.monitoring-*` are shipped with and managed by Elasticsearch itself and can be found in that code repository. For example: -- [monitoring-es.json](https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/resources/monitoring-es.json) - for internal collection or standalone metricbeat prior to 8.0 -- [monitoring-es-mb.json](https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/resources/monitoring-es-mb.json) - for standalone metricbeat after 8.0 +- [monitoring-es.json](https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/core/template-resources/src/main/resources/monitoring-es.json) - for internal collection or standalone metricbeat prior to 8.0 +- [monitoring-es-mb.json](https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/core/template-resources/src/main/resources/monitoring-es-mb.json) - for standalone metricbeat after 8.0 To verify changes to these templates, either make them in place on a running cluster or run elasticsearch from source. diff --git a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap index d995e36569959..429c292e423cc 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap @@ -26,7 +26,7 @@ Array [ data-type="row" >
    403 Forbidden
    @@ -36,7 +36,7 @@ Array [ no access for you
    500 Internal Server Error
    diff --git a/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.ts b/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.ts index e63e28f9f3e7a..2f423afbb0501 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/beats/_beats_stats.ts @@ -9,6 +9,18 @@ import { upperFirst } from 'lodash'; import type { BeatsElasticsearchResponse, BucketCount } from './types'; export const getDiffCalculation = (max: number | null, min: number | null) => { + /* + Note: This function calculates the delta between the first and last document in the time range + in order to display in the UI. If both the first and last document have the same counter value + reported, which may happen when events are not happening continuously, the UI will display 0. + Another case where the UI might display confusing information is in case the Beat was not + monitored from the start, in which case the first ingested document will have a non 0 counter + value. As an example, if Filebeat has ingested 30 000 lines when the monitoring starts, the + first document will have the counter set to 30 000, if another 20 000 documents are collected + the delta will only show 20 000 rather than the counter value of 50 000. The fact that the + counter "started" (from the perspective of monitoring) at 30 000 is lost. + */ + // no need to test max >= 0, but min <= 0 which is normal for a derivative after restart // because we are aggregating/collapsing on ephemeral_ids if (max !== null && min !== null && max >= 0 && min >= 0 && max >= min) { diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts b/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts index 4e355e01cfc32..2f0a505ba1f55 100644 --- a/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts +++ b/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts @@ -5,18 +5,35 @@ * 2.0. */ +import { loggerMock } from '@kbn/logging-mocks'; import { unsecuredActionsClientMock } from '@kbn/actions-plugin/server/unsecured_actions_client/unsecured_actions_client.mock'; import { ConnectorsEmailService } from './connectors_email_service'; import type { PlainTextEmail, HTMLEmail } from './types'; +import { ExecutionResponseType } from '@kbn/actions-plugin/server/create_execute_function'; const REQUESTER_ID = 'requesterId'; const CONNECTOR_ID = 'connectorId'; describe('sendPlainTextEmail()', () => { + const logger = loggerMock.create(); + beforeEach(() => { + loggerMock.clear(logger); + }); + describe('calls the provided ActionsClient#bulkEnqueueExecution() with the appropriate params', () => { it(`omits the 'relatedSavedObjects' field if no context is provided`, () => { const actionsClient = unsecuredActionsClientMock.create(); - const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient); + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + id: CONNECTOR_ID, + response: ExecutionResponseType.SUCCESS, + actionTypeId: 'test', + }, + ], + }); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient, logger); const payload: PlainTextEmail = { to: ['user1@email.com'], subject: 'This is a notification email', @@ -40,7 +57,17 @@ describe('sendPlainTextEmail()', () => { it(`populates the 'relatedSavedObjects' field if context is provided`, () => { const actionsClient = unsecuredActionsClientMock.create(); - const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient); + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + id: CONNECTOR_ID, + response: ExecutionResponseType.SUCCESS, + actionTypeId: 'test', + }, + ], + }); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient, logger); const payload: PlainTextEmail = { to: ['user1@email.com', 'user2@email.com', 'user3@email.com'], subject: 'This is a notification email', @@ -107,14 +134,53 @@ describe('sendPlainTextEmail()', () => { }, ]); }); + + it(`logs an error when the maximum number of queued actions has been reached`, async () => { + const actionsClient = unsecuredActionsClientMock.create(); + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: true, + items: [ + { + id: CONNECTOR_ID, + response: ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR, + actionTypeId: 'test', + }, + ], + }); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient, logger); + const payload: PlainTextEmail = { + to: ['user1@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + }; + + await email.sendPlainTextEmail(payload); + + expect(logger.warn).toHaveBeenCalled(); + }); }); }); describe('sendHTMLEmail()', () => { + const logger = loggerMock.create(); + beforeEach(() => { + loggerMock.clear(logger); + }); + describe('calls the provided ActionsClient#bulkEnqueueExecution() with the appropriate params', () => { it(`omits the 'relatedSavedObjects' field if no context is provided`, () => { const actionsClient = unsecuredActionsClientMock.create(); - const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient); + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + id: CONNECTOR_ID, + response: ExecutionResponseType.SUCCESS, + actionTypeId: 'test', + }, + ], + }); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient, logger); const payload: HTMLEmail = { to: ['user1@email.com'], subject: 'This is a notification email', @@ -140,7 +206,17 @@ describe('sendHTMLEmail()', () => { it(`populates the 'relatedSavedObjects' field if context is provided`, () => { const actionsClient = unsecuredActionsClientMock.create(); - const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient); + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: false, + items: [ + { + id: CONNECTOR_ID, + response: ExecutionResponseType.SUCCESS, + actionTypeId: 'test', + }, + ], + }); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient, logger); const payload: HTMLEmail = { to: ['user1@email.com', 'user2@email.com', 'user3@email.com'], subject: 'This is a notification email', @@ -211,5 +287,29 @@ describe('sendHTMLEmail()', () => { }, ]); }); + it(`logs an error when the maximum number of queued actions has been reached`, async () => { + const actionsClient = unsecuredActionsClientMock.create(); + actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ + errors: true, + items: [ + { + id: CONNECTOR_ID, + response: ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR, + actionTypeId: 'test', + }, + ], + }); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient, logger); + const payload: HTMLEmail = { + to: ['user1@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + messageHTML: 'With some contents inside.', + }; + + await email.sendHTMLEmail(payload); + + expect(logger.warn).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service.ts b/x-pack/plugins/notifications/server/services/connectors_email_service.ts index 91958ce12d133..94acd532cdac5 100755 --- a/x-pack/plugins/notifications/server/services/connectors_email_service.ts +++ b/x-pack/plugins/notifications/server/services/connectors_email_service.ts @@ -6,13 +6,19 @@ */ import type { IUnsecuredActionsClient } from '@kbn/actions-plugin/server'; +import { + ExecutionResponseItem, + ExecutionResponseType, +} from '@kbn/actions-plugin/server/create_execute_function'; +import type { Logger } from '@kbn/core/server'; import type { EmailService, PlainTextEmail, HTMLEmail } from './types'; export class ConnectorsEmailService implements EmailService { constructor( private requesterId: string, private connectorId: string, - private actionsClient: IUnsecuredActionsClient + private actionsClient: IUnsecuredActionsClient, + private logger: Logger ) {} async sendPlainTextEmail(params: PlainTextEmail): Promise { @@ -25,7 +31,11 @@ export class ConnectorsEmailService implements EmailService { }, relatedSavedObjects: params.context?.relatedObjects, })); - return await this.actionsClient.bulkEnqueueExecution(this.requesterId, actions); + + const response = await this.actionsClient.bulkEnqueueExecution(this.requesterId, actions); + if (response.errors) { + this.logEnqueueExecutionResponse(response.items); + } } async sendHTMLEmail(params: HTMLEmail): Promise { @@ -40,6 +50,19 @@ export class ConnectorsEmailService implements EmailService { relatedSavedObjects: params.context?.relatedObjects, })); - return await this.actionsClient.bulkEnqueueExecution(this.requesterId, actions); + const response = await this.actionsClient.bulkEnqueueExecution(this.requesterId, actions); + if (response.errors) { + this.logEnqueueExecutionResponse(response.items); + } + } + + private logEnqueueExecutionResponse(items: ExecutionResponseItem[]) { + for (const r of items) { + if (r.response === ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR) { + this.logger.warn( + `Skipped scheduling action "${r.id}" because the maximum number of queued actions has been reached.` + ); + } + } } } diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts index 7db7502640054..7c7cfacef5c35 100644 --- a/x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts +++ b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts @@ -235,7 +235,8 @@ describe('ConnectorsEmailServiceProvider', () => { expect(connectorsEmailServiceMock).toHaveBeenCalledWith( PLUGIN_ID, validConnectorConfig.connectors.default.email, - actionsStart.getUnsecuredActionsClient() + actionsStart.getUnsecuredActionsClient(), + logger ); }); }); diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts index b3364f31d3689..5c631005c969e 100755 --- a/x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts +++ b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts @@ -71,7 +71,12 @@ export class EmailServiceProvider try { const unsecuredActionsClient = actions.getUnsecuredActionsClient(); email = new LicensedEmailService( - new ConnectorsEmailService(PLUGIN_ID, emailConnector, unsecuredActionsClient), + new ConnectorsEmailService( + PLUGIN_ID, + emailConnector, + unsecuredActionsClient, + this.logger + ), licensing.license$, MINIMUM_LICENSE, this.logger diff --git a/x-pack/plugins/observability/common/constants.ts b/x-pack/plugins/observability/common/constants.ts index 3ba31bb1ee1eb..97f4341168fd9 100644 --- a/x-pack/plugins/observability/common/constants.ts +++ b/x-pack/plugins/observability/common/constants.ts @@ -10,7 +10,7 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; export const SLO_BURN_RATE_RULE_TYPE_ID = 'slo.rules.burnRate'; -export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.threshold'; +export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold'; export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; export const ALERT_STATUS_ALL = 'all'; diff --git a/x-pack/plugins/observability/common/threshold_rule/color_palette.ts b/x-pack/plugins/observability/common/custom_threshold_rule/color_palette.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/color_palette.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/color_palette.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/constants.ts b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/constants.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/constants.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.test.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.test.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/bytes.test.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.test.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/bytes.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/datetime.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/datetime.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/datetime.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/datetime.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/high_precision.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/high_precision.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/high_precision.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/high_precision.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/index.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/index.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/number.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/number.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/number.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/number.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/percent.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/percent.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/percent.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/percent.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/snapshot_metric_formats.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/snapshot_metric_formats.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/snapshot_metric_formats.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/snapshot_metric_formats.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/types.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/types.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/types.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.test.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.test.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.test.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.test.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts similarity index 89% rename from x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts index 1ba0879fcf465..114f30fd85307 100644 --- a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts @@ -10,7 +10,7 @@ import { createFormatter } from './formatters'; export const metricValueFormatter = (value: number | null, metric: string = '') => { const noDataValue = i18n.translate( - 'xpack.observability.threshold.rule.alerting.noDataFormattedValue', + 'xpack.observability.customThreshold.rule.alerting.noDataFormattedValue', { defaultMessage: '[NO DATA]', } diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts new file mode 100644 index 0000000000000..66913385123c4 --- /dev/null +++ b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { xor } from 'lodash'; +import { METRIC_EXPLORER_AGGREGATIONS } from './constants'; + +export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; + +type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; + +const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< + Record +>((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); + +export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); + +export type MetricExplorerCustomMetricAggregations = Exclude< + MetricsExplorerAggregation, + 'custom' | 'rate' | 'p95' | 'p99' +>; +const metricsExplorerCustomMetricAggregationKeys = xor( + METRIC_EXPLORER_AGGREGATIONS, + OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS +).reduce>( + (acc, agg) => ({ ...acc, [agg]: null }), + {} as Record +); +export const metricsExplorerCustomMetricAggregationRT = rt.keyof( + metricsExplorerCustomMetricAggregationKeys +); + +export const metricsExplorerMetricRequiredFieldsRT = rt.type({ + aggregation: metricsExplorerAggregationRT, +}); + +export const metricsExplorerCustomMetricRT = rt.intersection([ + rt.type({ + name: rt.string, + aggregation: metricsExplorerCustomMetricAggregationRT, + }), + rt.partial({ + field: rt.string, + filter: rt.string, + }), +]); + +export type MetricsExplorerCustomMetric = rt.TypeOf; + +export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ + field: rt.union([rt.string, rt.undefined]), + custom_metrics: rt.array(metricsExplorerCustomMetricRT), + equation: rt.string, +}); + +export const metricsExplorerMetricRT = rt.intersection([ + metricsExplorerMetricRequiredFieldsRT, + metricsExplorerMetricOptionalFieldsRT, +]); + +export const timeRangeRT = rt.type({ + from: rt.number, + to: rt.number, + interval: rt.string, +}); + +export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ + timerange: timeRangeRT, + indexPattern: rt.string, + metrics: rt.array(metricsExplorerMetricRT), +}); + +const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); +export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); + +export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ + groupBy: rt.union([groupByRT, rt.array(groupByRT)]), + afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), + limit: rt.union([rt.number, rt.null, rt.undefined]), + filterQuery: rt.union([rt.string, rt.null, rt.undefined]), + forceInterval: rt.boolean, + dropLastBucket: rt.boolean, +}); + +export const metricsExplorerRequestBodyRT = rt.intersection([ + metricsExplorerRequestBodyRequiredFieldsRT, + metricsExplorerRequestBodyOptionalFieldsRT, +]); + +export const metricsExplorerPageInfoRT = rt.type({ + total: rt.number, + afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), +}); + +export const metricsExplorerColumnTypeRT = rt.keyof({ + date: null, + number: null, + string: null, +}); + +export const metricsExplorerColumnRT = rt.type({ + name: rt.string, + type: metricsExplorerColumnTypeRT, +}); + +export const metricsExplorerRowRT = rt.intersection([ + rt.type({ + timestamp: rt.number, + }), + rt.record( + rt.string, + rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) + ), +]); + +export const metricsExplorerSeriesRT = rt.intersection([ + rt.type({ + id: rt.string, + columns: rt.array(metricsExplorerColumnRT), + rows: rt.array(metricsExplorerRowRT), + }), + rt.partial({ + keys: rt.array(rt.string), + }), +]); + +export const metricsExplorerResponseRT = rt.type({ + series: rt.array(metricsExplorerSeriesRT), + pageInfo: metricsExplorerPageInfoRT, +}); + +export type AfterKey = rt.TypeOf; + +export type MetricsExplorerAggregation = rt.TypeOf; + +export type MetricsExplorerColumnType = rt.TypeOf; + +export type MetricsExplorerMetric = rt.TypeOf; + +export type MetricsExplorerPageInfo = rt.TypeOf; + +export type MetricsExplorerColumn = rt.TypeOf; + +export type MetricsExplorerRow = rt.TypeOf; + +export type MetricsExplorerSeries = rt.TypeOf; + +export type MetricsExplorerRequestBody = rt.TypeOf; + +export type MetricsExplorerResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts new file mode 100644 index 0000000000000..66e058dfad8cf --- /dev/null +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -0,0 +1,291 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; +import { values } from 'lodash'; +import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { Color } from './color_palette'; +import { metricsExplorerMetricRT } from './metrics_explorer'; +import { TimeUnitChar } from '../utils/formatters/duration'; +import { SNAPSHOT_CUSTOM_AGGREGATIONS } from './constants'; + +type DeepPartialArray = Array>; + +type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; +export type DeepPartial = T extends any[] + ? DeepPartialArray + : T extends object + ? DeepPartialObject + : T; + +export const ThresholdFormatterTypeRT = rt.keyof({ + abbreviatedNumber: null, + bits: null, + bytes: null, + number: null, + percent: null, + highPrecision: null, +}); +export type ThresholdFormatterType = rt.TypeOf; + +const pointRT = rt.type({ + timestamp: rt.number, + value: rt.number, +}); + +export type Point = rt.TypeOf; + +const serieRT = rt.type({ + id: rt.string, + points: rt.array(pointRT), +}); + +const seriesRT = rt.array(serieRT); + +export type Series = rt.TypeOf; + +export const getLogAlertsChartPreviewDataSuccessResponsePayloadRT = rt.type({ + data: rt.type({ + series: seriesRT, + }), +}); + +export type GetLogAlertsChartPreviewDataSuccessResponsePayload = rt.TypeOf< + typeof getLogAlertsChartPreviewDataSuccessResponsePayloadRT +>; + +/** + * Properties specific to the Metrics Source Configuration. + */ +export const SourceConfigurationTimestampColumnRuntimeType = rt.type({ + timestampColumn: rt.type({ + id: rt.string, + }), +}); +export const SourceConfigurationMessageColumnRuntimeType = rt.type({ + messageColumn: rt.type({ + id: rt.string, + }), +}); + +export const SourceConfigurationFieldColumnRuntimeType = rt.type({ + fieldColumn: rt.type({ + id: rt.string, + field: rt.string, + }), +}); + +export const SourceConfigurationColumnRuntimeType = rt.union([ + SourceConfigurationTimestampColumnRuntimeType, + SourceConfigurationMessageColumnRuntimeType, + SourceConfigurationFieldColumnRuntimeType, +]); + +// Kibana data views +export const logDataViewReferenceRT = rt.type({ + type: rt.literal('data_view'), + dataViewId: rt.string, +}); + +// Index name +export const logIndexNameReferenceRT = rt.type({ + type: rt.literal('index_name'), + indexName: rt.string, +}); + +export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); + +/** + * Source status + */ +const SourceStatusFieldRuntimeType = rt.type({ + name: rt.string, + type: rt.string, + searchable: rt.boolean, + aggregatable: rt.boolean, + displayable: rt.boolean, +}); +export const SourceStatusRuntimeType = rt.type({ + logIndicesExist: rt.boolean, + metricIndicesExist: rt.boolean, + remoteClustersExist: rt.boolean, + indexFields: rt.array(SourceStatusFieldRuntimeType), +}); +export const metricsSourceStatusRT = rt.strict({ + metricIndicesExist: SourceStatusRuntimeType.props.metricIndicesExist, + remoteClustersExist: SourceStatusRuntimeType.props.metricIndicesExist, + indexFields: SourceStatusRuntimeType.props.indexFields, +}); + +export type MetricsSourceStatus = rt.TypeOf; + +export enum Comparator { + GT = '>', + LT = '<', + GT_OR_EQ = '>=', + LT_OR_EQ = '<=', + BETWEEN = 'between', + OUTSIDE_RANGE = 'outside', +} + +export enum Aggregators { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', + P95 = 'p95', + P99 = 'p99', + CUSTOM = 'custom', +} + +const metricsExplorerOptionsMetricRT = rt.intersection([ + metricsExplorerMetricRT, + rt.partial({ + rate: rt.boolean, + color: rt.keyof(Object.fromEntries(values(Color).map((c) => [c, null])) as Record), + label: rt.string, + }), +]); + +export type MetricsExplorerOptionsMetric = rt.TypeOf; + +export enum MetricsExplorerChartType { + line = 'line', + area = 'area', + bar = 'bar', +} + +export enum InfraRuleType { + MetricThreshold = 'metrics.alert.threshold', + InventoryThreshold = 'metrics.alert.inventory.threshold', + Anomaly = 'metrics.alert.anomaly', +} + +export enum AlertStates { + OK, + ALERT, + WARNING, + NO_DATA, + ERROR, +} + +const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); +const metricAnomalyMetricRT = rt.union([ + rt.literal('memory_usage'), + rt.literal('network_in'), + rt.literal('network_out'), +]); +const metricAnomalyInfluencerFilterRT = rt.type({ + fieldName: rt.string, + fieldValue: rt.string, +}); + +export interface MetricAnomalyParams { + nodeType: rt.TypeOf; + metric: rt.TypeOf; + alertInterval?: string; + sourceId?: string; + spaceId?: string; + threshold: Exclude; + influencerFilter: rt.TypeOf | undefined; +} + +// Types for the executor + +export interface ThresholdParams { + criteria: MetricExpressionParams[]; + filterQuery?: string; + sourceId?: string; + alertOnNoData?: boolean; + alertOnGroupDisappear?: boolean; + searchConfiguration: SerializedSearchSourceFields; + groupBy?: string[]; +} + +interface BaseMetricExpressionParams { + timeSize: number; + timeUnit: TimeUnitChar; + sourceId?: string; + threshold: number[]; + comparator: Comparator; + warningComparator?: Comparator; + warningThreshold?: number[]; +} + +export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Exclude; + metric: string; +} + +export interface CountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Aggregators.COUNT; +} + +export type CustomMetricAggTypes = Exclude< + Aggregators, + Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 +>; + +export interface CustomThresholdExpressionMetric { + name: string; + aggType: CustomMetricAggTypes; + field?: string; + filter?: string; +} + +export interface CustomMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Aggregators.CUSTOM; + metrics: CustomThresholdExpressionMetric[]; + equation?: string; + label?: string; +} + +export type MetricExpressionParams = + | NonCountMetricExpressionParams + | CountMetricExpressionParams + | CustomMetricExpressionParams; + +export const QUERY_INVALID: unique symbol = Symbol('QUERY_INVALID'); + +export type FilterQuery = string | typeof QUERY_INVALID; + +export interface AlertExecutionDetails { + alertId: string; + executionId: string; +} + +export enum InfraFormatterType { + number = 'number', + abbreviatedNumber = 'abbreviatedNumber', + bytes = 'bytes', + bits = 'bits', + percent = 'percent', +} + +export type SnapshotCustomAggregation = typeof SNAPSHOT_CUSTOM_AGGREGATIONS[number]; +const snapshotCustomAggregationKeys = SNAPSHOT_CUSTOM_AGGREGATIONS.reduce< + Record +>((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); + +export const SnapshotCustomAggregationRT = rt.keyof(snapshotCustomAggregationKeys); + +export const SnapshotCustomMetricInputRT = rt.intersection([ + rt.type({ + type: rt.literal('custom'), + field: rt.string, + aggregation: SnapshotCustomAggregationRT, + id: rt.string, + }), + rt.partial({ + label: rt.string, + }), +]); +export type SnapshotCustomMetricInput = rt.TypeOf; diff --git a/x-pack/plugins/observability/common/locators/paths.ts b/x-pack/plugins/observability/common/locators/paths.ts index 234e53f37c3ad..a69062321dd29 100644 --- a/x-pack/plugins/observability/common/locators/paths.ts +++ b/x-pack/plugins/observability/common/locators/paths.ts @@ -13,6 +13,7 @@ export const ALERTS_PATH = '/alerts' as const; export const ALERT_DETAIL_PATH = '/alerts/:alertId' as const; export const EXPLORATORY_VIEW_PATH = '/exploratory-view' as const; // has been moved to its own app. Keeping around for redirecting purposes. export const RULES_PATH = '/alerts/rules' as const; +export const RULES_LOGS_PATH = '/alerts/rules/logs' as const; export const RULE_DETAIL_PATH = '/alerts/rules/:ruleId' as const; export const SLOS_PATH = '/slos' as const; export const SLOS_WELCOME_PATH = '/slos/welcome' as const; @@ -42,3 +43,9 @@ export const paths = { : `${OBSERVABILITY_BASE_PATH}${SLOS_PATH}/${encodeURI(sloId)}`, }, }; + +export const relativePaths = { + observability: { + ruleDetails: (ruleId: string) => `${RULES_PATH}/${encodeURI(ruleId)}`, + }, +}; diff --git a/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts b/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts deleted file mode 100644 index d735e398e6661..0000000000000 --- a/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as rt from 'io-ts'; -import { xor } from 'lodash'; - -export const METRIC_EXPLORER_AGGREGATIONS = [ - 'avg', - 'max', - 'min', - 'cardinality', - 'rate', - 'count', - 'sum', - 'p95', - 'p99', - 'custom', -] as const; - -export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; - -type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; - -const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< - Record ->((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); - -export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); - -export type MetricExplorerCustomMetricAggregations = Exclude< - MetricsExplorerAggregation, - 'custom' | 'rate' | 'p95' | 'p99' ->; -const metricsExplorerCustomMetricAggregationKeys = xor( - METRIC_EXPLORER_AGGREGATIONS, - OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS -).reduce>( - (acc, agg) => ({ ...acc, [agg]: null }), - {} as Record -); -export const metricsExplorerCustomMetricAggregationRT = rt.keyof( - metricsExplorerCustomMetricAggregationKeys -); - -export const metricsExplorerMetricRequiredFieldsRT = rt.type({ - aggregation: metricsExplorerAggregationRT, -}); - -export const metricsExplorerCustomMetricRT = rt.intersection([ - rt.type({ - name: rt.string, - aggregation: metricsExplorerCustomMetricAggregationRT, - }), - rt.partial({ - field: rt.string, - filter: rt.string, - }), -]); - -export type MetricsExplorerCustomMetric = rt.TypeOf; - -export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ - field: rt.union([rt.string, rt.undefined]), - custom_metrics: rt.array(metricsExplorerCustomMetricRT), - equation: rt.string, -}); - -export const metricsExplorerMetricRT = rt.intersection([ - metricsExplorerMetricRequiredFieldsRT, - metricsExplorerMetricOptionalFieldsRT, -]); - -export const timeRangeRT = rt.type({ - from: rt.number, - to: rt.number, - interval: rt.string, -}); - -export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ - timerange: timeRangeRT, - indexPattern: rt.string, - metrics: rt.array(metricsExplorerMetricRT), -}); - -const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); -export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); - -export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ - groupBy: rt.union([groupByRT, rt.array(groupByRT)]), - afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), - limit: rt.union([rt.number, rt.null, rt.undefined]), - filterQuery: rt.union([rt.string, rt.null, rt.undefined]), - forceInterval: rt.boolean, - dropLastBucket: rt.boolean, -}); - -export const metricsExplorerRequestBodyRT = rt.intersection([ - metricsExplorerRequestBodyRequiredFieldsRT, - metricsExplorerRequestBodyOptionalFieldsRT, -]); - -export const metricsExplorerPageInfoRT = rt.type({ - total: rt.number, - afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), -}); - -export const metricsExplorerColumnTypeRT = rt.keyof({ - date: null, - number: null, - string: null, -}); - -export const metricsExplorerColumnRT = rt.type({ - name: rt.string, - type: metricsExplorerColumnTypeRT, -}); - -export const metricsExplorerRowRT = rt.intersection([ - rt.type({ - timestamp: rt.number, - }), - rt.record( - rt.string, - rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) - ), -]); - -export const metricsExplorerSeriesRT = rt.intersection([ - rt.type({ - id: rt.string, - columns: rt.array(metricsExplorerColumnRT), - rows: rt.array(metricsExplorerRowRT), - }), - rt.partial({ - keys: rt.array(rt.string), - }), -]); - -export const metricsExplorerResponseRT = rt.type({ - series: rt.array(metricsExplorerSeriesRT), - pageInfo: metricsExplorerPageInfoRT, -}); - -export type AfterKey = rt.TypeOf; - -export type MetricsExplorerAggregation = rt.TypeOf; - -export type MetricsExplorerColumnType = rt.TypeOf; - -export type MetricsExplorerMetric = rt.TypeOf; - -export type MetricsExplorerPageInfo = rt.TypeOf; - -export type MetricsExplorerColumn = rt.TypeOf; - -export type MetricsExplorerRow = rt.TypeOf; - -export type MetricsExplorerSeries = rt.TypeOf; - -export type MetricsExplorerRequestBody = rt.TypeOf; - -export type MetricsExplorerResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability/common/threshold_rule/types.ts b/x-pack/plugins/observability/common/threshold_rule/types.ts deleted file mode 100644 index 6511a2cb280a5..0000000000000 --- a/x-pack/plugins/observability/common/threshold_rule/types.ts +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as rt from 'io-ts'; -import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; -import { values } from 'lodash'; -import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; -import { Color } from './color_palette'; -import { metricsExplorerMetricRT } from './metrics_explorer'; -import { TimeUnitChar } from '../utils/formatters/duration'; -import { SNAPSHOT_CUSTOM_AGGREGATIONS } from './constants'; - -type DeepPartialArray = Array>; - -type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; -export type DeepPartial = T extends any[] - ? DeepPartialArray - : T extends object - ? DeepPartialObject - : T; - -export const ThresholdFormatterTypeRT = rt.keyof({ - abbreviatedNumber: null, - bits: null, - bytes: null, - number: null, - percent: null, - highPrecision: null, -}); -export type ThresholdFormatterType = rt.TypeOf; - -const pointRT = rt.type({ - timestamp: rt.number, - value: rt.number, -}); - -export type Point = rt.TypeOf; - -const serieRT = rt.type({ - id: rt.string, - points: rt.array(pointRT), -}); - -const seriesRT = rt.array(serieRT); - -export type Series = rt.TypeOf; - -export const getLogAlertsChartPreviewDataSuccessResponsePayloadRT = rt.type({ - data: rt.type({ - series: seriesRT, - }), -}); - -export type GetLogAlertsChartPreviewDataSuccessResponsePayload = rt.TypeOf< - typeof getLogAlertsChartPreviewDataSuccessResponsePayloadRT ->; - -/** - * Properties specific to the Metrics Source Configuration. - */ -export const SourceConfigurationTimestampColumnRuntimeType = rt.type({ - timestampColumn: rt.type({ - id: rt.string, - }), -}); -export const SourceConfigurationMessageColumnRuntimeType = rt.type({ - messageColumn: rt.type({ - id: rt.string, - }), -}); - -export const SourceConfigurationFieldColumnRuntimeType = rt.type({ - fieldColumn: rt.type({ - id: rt.string, - field: rt.string, - }), -}); - -export const SourceConfigurationColumnRuntimeType = rt.union([ - SourceConfigurationTimestampColumnRuntimeType, - SourceConfigurationMessageColumnRuntimeType, - SourceConfigurationFieldColumnRuntimeType, -]); - -// Kibana data views -export const logDataViewReferenceRT = rt.type({ - type: rt.literal('data_view'), - dataViewId: rt.string, -}); - -// Index name -export const logIndexNameReferenceRT = rt.type({ - type: rt.literal('index_name'), - indexName: rt.string, -}); - -export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); - -/** - * Source status - */ -const SourceStatusFieldRuntimeType = rt.type({ - name: rt.string, - type: rt.string, - searchable: rt.boolean, - aggregatable: rt.boolean, - displayable: rt.boolean, -}); -export const SourceStatusRuntimeType = rt.type({ - logIndicesExist: rt.boolean, - metricIndicesExist: rt.boolean, - remoteClustersExist: rt.boolean, - indexFields: rt.array(SourceStatusFieldRuntimeType), -}); -export const metricsSourceStatusRT = rt.strict({ - metricIndicesExist: SourceStatusRuntimeType.props.metricIndicesExist, - remoteClustersExist: SourceStatusRuntimeType.props.metricIndicesExist, - indexFields: SourceStatusRuntimeType.props.indexFields, -}); - -export type MetricsSourceStatus = rt.TypeOf; - -export enum Comparator { - GT = '>', - LT = '<', - GT_OR_EQ = '>=', - LT_OR_EQ = '<=', - BETWEEN = 'between', - OUTSIDE_RANGE = 'outside', -} - -export enum Aggregators { - COUNT = 'count', - AVERAGE = 'avg', - SUM = 'sum', - MIN = 'min', - MAX = 'max', - RATE = 'rate', - CARDINALITY = 'cardinality', - P95 = 'p95', - P99 = 'p99', - CUSTOM = 'custom', -} - -const metricsExplorerOptionsMetricRT = rt.intersection([ - metricsExplorerMetricRT, - rt.partial({ - rate: rt.boolean, - color: rt.keyof(Object.fromEntries(values(Color).map((c) => [c, null])) as Record), - label: rt.string, - }), -]); - -export type MetricsExplorerOptionsMetric = rt.TypeOf; - -export enum MetricsExplorerChartType { - line = 'line', - area = 'area', - bar = 'bar', -} - -export enum InfraRuleType { - MetricThreshold = 'metrics.alert.threshold', - InventoryThreshold = 'metrics.alert.inventory.threshold', - Anomaly = 'metrics.alert.anomaly', -} - -export enum AlertStates { - OK, - ALERT, - WARNING, - NO_DATA, - ERROR, -} - -const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); -const metricAnomalyMetricRT = rt.union([ - rt.literal('memory_usage'), - rt.literal('network_in'), - rt.literal('network_out'), -]); -const metricAnomalyInfluencerFilterRT = rt.type({ - fieldName: rt.string, - fieldValue: rt.string, -}); - -export interface MetricAnomalyParams { - nodeType: rt.TypeOf; - metric: rt.TypeOf; - alertInterval?: string; - sourceId?: string; - spaceId?: string; - threshold: Exclude; - influencerFilter: rt.TypeOf | undefined; -} - -// Types for the executor - -export interface ThresholdParams { - criteria: MetricExpressionParams[]; - filterQuery?: string; - sourceId?: string; - alertOnNoData?: boolean; - alertOnGroupDisappear?: boolean; - searchConfiguration: SerializedSearchSourceFields; - groupBy?: string[]; -} - -interface BaseMetricExpressionParams { - timeSize: number; - timeUnit: TimeUnitChar; - sourceId?: string; - threshold: number[]; - comparator: Comparator; - warningComparator?: Comparator; - warningThreshold?: number[]; -} - -export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Exclude; - metric: string; -} - -export interface CountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Aggregators.COUNT; -} - -export type CustomMetricAggTypes = Exclude< - Aggregators, - Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 ->; - -export interface MetricExpressionCustomMetric { - name: string; - aggType: CustomMetricAggTypes; - field?: string; - filter?: string; -} - -export interface CustomMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Aggregators.CUSTOM; - customMetrics: MetricExpressionCustomMetric[]; - equation?: string; - label?: string; -} - -export type MetricExpressionParams = - | NonCountMetricExpressionParams - | CountMetricExpressionParams - | CustomMetricExpressionParams; - -export const QUERY_INVALID: unique symbol = Symbol('QUERY_INVALID'); - -export type FilterQuery = string | typeof QUERY_INVALID; - -export interface AlertExecutionDetails { - alertId: string; - executionId: string; -} - -export enum InfraFormatterType { - number = 'number', - abbreviatedNumber = 'abbreviatedNumber', - bytes = 'bytes', - bits = 'bits', - percent = 'percent', -} - -export type SnapshotCustomAggregation = typeof SNAPSHOT_CUSTOM_AGGREGATIONS[number]; -const snapshotCustomAggregationKeys = SNAPSHOT_CUSTOM_AGGREGATIONS.reduce< - Record ->((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); - -export const SnapshotCustomAggregationRT = rt.keyof(snapshotCustomAggregationKeys); - -export const SnapshotCustomMetricInputRT = rt.intersection([ - rt.type({ - type: rt.literal('custom'), - field: rt.string, - aggregation: SnapshotCustomAggregationRT, - id: rt.string, - }), - rt.partial({ - label: rt.string, - }), -]); -export type SnapshotCustomMetricInput = rt.TypeOf; diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.json b/x-pack/plugins/observability/docs/openapi/slo/bundled.json index 374d35c9dd212..559f5713e2c35 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/bundled.json +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.json @@ -677,6 +677,74 @@ } } } + }, + "/s/{spaceId}/api/observability/slos/_delete_instances": { + "post": { + "summary": "Batch delete rollup and summary data for the matching list of sloId and instanceId", + "operationId": "deleteSloInstancesOp", + "description": "You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/delete_slo_instances_request" + } + } + } + }, + "responses": { + "204": { + "description": "Successful request" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + } } }, "components": { @@ -1718,10 +1786,10 @@ "title": "Historical summary request", "type": "object", "required": [ - "sloIds" + "list" ], "properties": { - "sloIds": { + "list": { "description": "The list of SLO identifiers to get the historical summary for", "type": "array", "items": { @@ -1756,6 +1824,39 @@ } } } + }, + "delete_slo_instances_request": { + "title": "Delete SLO instances request", + "description": "The delete SLO instances request takes a list of SLO id and instance id, then delete the rollup and summary data. This API can be used to remove the staled data of an instance SLO that no longer get updated.\n", + "type": "object", + "required": [ + "list" + ], + "properties": { + "list": { + "description": "An array of slo id and instance id", + "type": "array", + "items": { + "type": "object", + "required": [ + "sloId", + "instanceId" + ], + "properties": { + "sloId": { + "description": "The SLO unique identifier", + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + }, + "instanceId": { + "description": "The SLO instance identifier", + "type": "string", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + } + } + } + } + } } } } diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml index a6cdf5c376485..efeeb090f0156 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml @@ -408,6 +408,46 @@ paths: application/json: schema: $ref: '#/components/schemas/403_response' + /s/{spaceId}/api/observability/slos/_delete_instances: + post: + summary: Batch delete rollup and summary data for the matching list of sloId and instanceId + operationId: deleteSloInstancesOp + description: | + You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. + tags: + - slo + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/delete_slo_instances_request' + responses: + '204': + description: Successful request + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + '401': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' + servers: + - url: https://localhost:5601 components: securitySchemes: basicAuth: @@ -1190,9 +1230,9 @@ components: title: Historical summary request type: object required: - - sloIds + - list properties: - sloIds: + list: description: The list of SLO identifiers to get the historical summary for type: array items: @@ -1216,3 +1256,28 @@ components: example: 0.9836 errorBudget: $ref: '#/components/schemas/error_budget' + delete_slo_instances_request: + title: Delete SLO instances request + description: | + The delete SLO instances request takes a list of SLO id and instance id, then delete the rollup and summary data. This API can be used to remove the staled data of an instance SLO that no longer get updated. + type: object + required: + - list + properties: + list: + description: An array of slo id and instance id + type: array + items: + type: object + required: + - sloId + - instanceId + properties: + sloId: + description: The SLO unique identifier + type: string + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + instanceId: + description: The SLO instance identifier + type: string + example: 8853df00-ae2e-11ed-90af-09bb6422b258 diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/delete_slo_instances_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/delete_slo_instances_request.yaml new file mode 100644 index 0000000000000..819050a915df5 --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/delete_slo_instances_request.yaml @@ -0,0 +1,26 @@ +title: Delete SLO instances request +description: > + The delete SLO instances request takes a list of SLO id and instance id, then delete the rollup and summary data. + This API can be used to remove the staled data of an instance SLO that no longer get updated. +type: object +required: + - list +properties: + list: + description: An array of slo id and instance id + type: array + items: + type: object + required: + - sloId + - instanceId + properties: + sloId: + description: The SLO unique identifier + type: string + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + instanceId: + description: The SLO instance identifier + type: string + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + \ No newline at end of file diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/historical_summary_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/historical_summary_request.yaml index 737a5b83f03f9..a2be13fc9842d 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/historical_summary_request.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/historical_summary_request.yaml @@ -1,9 +1,9 @@ title: Historical summary request type: object required: - - sloIds + - list properties: - sloIds: + list: description: The list of SLO identifiers to get the historical summary for type: array items: diff --git a/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml b/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml index 44f16ed4585e0..ee722573efa91 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml @@ -31,6 +31,8 @@ paths: $ref: "paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml" "/s/{spaceId}/internal/observability/slos/_historical_summary": $ref: "paths/s@{spaceid}@api@slos@_historical_summary.yaml" + "/s/{spaceId}/api/observability/slos/_delete_instances": + $ref: "paths/s@{spaceid}@api@slos@_delete_instances.yaml" components: securitySchemes: basicAuth: diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@_delete_instances.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@_delete_instances.yaml new file mode 100644 index 0000000000000..e9775576695a2 --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos@_delete_instances.yaml @@ -0,0 +1,40 @@ +post: + summary: Batch delete rollup and summary data for the matching list of sloId and instanceId + operationId: deleteSloInstancesOp + description: > + You must have `all` privileges for the **SLOs** feature in the + **Observability** section of the Kibana feature privileges. + tags: + - slo + parameters: + - $ref: ../components/headers/kbn_xsrf.yaml + - $ref: ../components/parameters/space_id.yaml + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/delete_slo_instances_request.yaml' + responses: + '204': + description: Successful request + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' + '401': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' + servers: + - url: https://localhost:5601 diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index 2ced468eb1f98..5064e06b156e0 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -8,9 +8,11 @@ "browser": true, "configPath": ["xpack", "observability"], "requiredPlugins": [ + "aiops", "alerting", "cases", "charts", + "contentManagement", "data", "dataViews", "dataViewEditor", diff --git a/x-pack/plugins/observability/public/components/burn_rate_rule_editor/long_window_duration.tsx b/x-pack/plugins/observability/public/components/burn_rate_rule_editor/long_window_duration.tsx index af982e928bd6b..6721fbe7dcdf9 100644 --- a/x-pack/plugins/observability/public/components/burn_rate_rule_editor/long_window_duration.tsx +++ b/x-pack/plugins/observability/public/components/burn_rate_rule_editor/long_window_duration.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip } from '@elastic/eui'; +import { EuiFieldNumber, EuiFormRow, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { ChangeEvent, useState } from 'react'; -import { toMinutes } from '../../utils/slo/duration'; import { Duration } from '../../typings'; +import { toMinutes } from '../../utils/slo/duration'; interface Props { shortWindowDuration: Duration; @@ -36,22 +36,18 @@ export function LongWindowDuration({ return ( - - - - - + ); } diff --git a/x-pack/plugins/observability/public/components/threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap rename to x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap diff --git a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx similarity index 88% rename from x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx index e540572e67bc5..2e035cdcf8fb8 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx @@ -39,6 +39,12 @@ jest.mock('../../../utils/kibana_react', () => ({ services: { ...mockCoreMock.createStart(), charts: mockedChartStartContract, + aiops: { + EmbeddableChangePointChart: jest.fn(), + }, + data: { + search: jest.fn(), + }, }, }), })); @@ -47,6 +53,7 @@ describe('AlertDetailsAppSection', () => { const queryClient = new QueryClient(); const mockedSetAlertSummaryFields = jest.fn(); const ruleLink = 'ruleLink'; + const renderComponent = () => { return render( @@ -69,7 +76,7 @@ describe('AlertDetailsAppSection', () => { it('should render rule and alert data', async () => { const result = renderComponent(); - expect((await result.findByTestId('thresholdRuleAppSection')).children.length).toBe(3); + expect((await result.findByTestId('thresholdAlertOverviewSection')).children.length).toBe(3); expect(result.getByTestId('thresholdRule-2000-2500')).toBeTruthy(); }); @@ -92,9 +99,9 @@ describe('AlertDetailsAppSection', () => { it('should render annotations', async () => { const mockedExpressionChart = jest.fn(() =>
    ); (ExpressionChart as jest.Mock).mockImplementation(mockedExpressionChart); - renderComponent(); + const alertDetailsAppSectionComponent = renderComponent(); - expect(mockedExpressionChart).toHaveBeenCalledTimes(3); + expect(alertDetailsAppSectionComponent.getAllByTestId('ExpressionChart').length).toBe(3); expect(mockedExpressionChart.mock.calls[0]).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx new file mode 100644 index 0000000000000..ecb31f4a49b4b --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx @@ -0,0 +1,254 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { DataViewBase, Query } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useEffect, useMemo, useState } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES } from '@kbn/rule-data-utils'; +import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common'; +import { + AlertAnnotation, + getPaddedAlertTimeRange, + AlertActiveTimeRangeAnnotation, +} from '@kbn/observability-alert-details'; +import { DataView } from '@kbn/data-views-plugin/common'; +import type { TimeRange } from '@kbn/es-query'; +import { useKibana } from '../../../utils/kibana_react'; +import { metricValueFormatter } from '../../../../common/custom_threshold_rule/metric_value_formatter'; +import { AlertSummaryField, TopAlert } from '../../..'; +import { generateUniqueKey } from '../lib/generate_unique_key'; + +import { ExpressionChart } from './expression_chart'; +import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart'; +import { Threshold } from './custom_threshold'; +import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options'; +import { AlertParams, MetricExpression, MetricThresholdRuleTypeParams } from '../types'; + +// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 +export type MetricThresholdRule = Rule; +export type MetricThresholdAlert = TopAlert; + +const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm'; +const ALERT_START_ANNOTATION_ID = 'alert_start_annotation'; +const ALERT_TIME_RANGE_ANNOTATION_ID = 'alert_time_range_annotation'; +const OVERVIEW_TAB_ID = 'overview'; +const RELATED_EVENTS_TAB_ID = 'relatedEvents'; + +interface AppSectionProps { + alert: MetricThresholdAlert; + rule: MetricThresholdRule; + ruleLink: string; + setAlertSummaryFields: React.Dispatch>; +} + +// eslint-disable-next-line import/no-default-export +export default function AlertDetailsAppSection({ + alert, + rule, + ruleLink, + setAlertSummaryFields, +}: AppSectionProps) { + const { uiSettings, charts, aiops, data } = useKibana().services; + const { EmbeddableChangePointChart } = aiops; + const { euiTheme } = useEuiTheme(); + const [dataView, setDataView] = useState(); + const [, setDataViewError] = useState(); + const ruleParams = rule.params as RuleTypeParams & AlertParams; + const chartProps = { + theme: charts.theme.useChartsTheme(), + baseTheme: charts.theme.useChartsBaseTheme(), + }; + const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]); + const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; + const annotations = [ + , + , + ]; + useEffect(() => { + setAlertSummaryFields([ + { + label: i18n.translate( + 'xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.rule', + { + defaultMessage: 'Rule', + } + ), + value: ( + + {rule.name} + + ), + }, + ]); + }, [alert, rule, ruleLink, setAlertSummaryFields]); + + const derivedIndexPattern = useMemo( + () => ({ + fields: dataView?.fields || [], + title: dataView?.getIndexPattern() || 'unknown-index', + }), + [dataView] + ); + + useEffect(() => { + const initDataView = async () => { + const ruleSearchConfiguration = ruleParams.searchConfiguration; + try { + const createdSearchSource = await data.search.searchSource.create(ruleSearchConfiguration); + setDataView(createdSearchSource.getField('index')); + } catch (error) { + setDataViewError(error); + } + }; + + initDataView(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data.search.searchSource]); + + const relatedEventsTimeRange = (criterion: MetricExpression): TimeRange => { + return { + from: moment(alert.start) + .subtract((criterion.timeSize ?? 5) * 2, criterion.timeUnit ?? 'minutes') + .toISOString(), + to: moment(alert.lastUpdated).toISOString(), + mode: 'absolute', + }; + }; + + const overviewTab = !!ruleParams.criteria ? ( + <> + + + {ruleParams.criteria.map((criterion, index) => ( + + + +

    + {criterion.aggType.toUpperCase()}{' '} + {'metric' in criterion ? criterion.metric : undefined} +

    +
    + + + + + + + + metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined) + } + title={i18n.translate( + 'xpack.observability.customThreshold.rule.alertDetailsAppSection.thresholdTitle', + { + defaultMessage: 'Threshold breached', + } + )} + comparator={criterion.comparator} + /> + + + + + +
    +
    + ))} +
    + + ) : null; + + const relatedEventsTab = !!ruleParams.criteria ? ( + <> + + + {ruleParams.criteria.map((criterion, criterionIndex) => + criterion.metrics?.map( + (metric, metricIndex) => + dataView && + dataView.id && ( + + ) + ) + )} + + + ) : null; + + const tabs: EuiTabbedContentTab[] = [ + { + id: OVERVIEW_TAB_ID, + name: i18n.translate('xpack.observability.threshold.alertDetails.tab.overviewLabel', { + defaultMessage: 'Overview', + }), + 'data-test-subj': 'overviewTab', + content: overviewTab, + }, + { + id: RELATED_EVENTS_TAB_ID, + name: i18n.translate('xpack.observability.threshold.alertDetails.tab.relatedEventsLabel', { + defaultMessage: 'Related Events', + }), + 'data-test-subj': 'relatedEventsTab', + content: relatedEventsTab, + }, + ]; + + return ; +} diff --git a/x-pack/plugins/observability/public/components/threshold/components/alert_flyout.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx similarity index 94% rename from x-pack/plugins/observability/public/components/threshold/components/alert_flyout.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx index e0d24d58eb0db..824d5b41d14ca 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/alert_flyout.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useContext, useMemo } from 'react'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; -import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { TriggerActionsContext } from './triggers_actions_context'; import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/closable_popover_title.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/closable_popover_title.test.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/closable_popover_title.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/closable_popover_title.test.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/components/closable_popover_title.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/closable_popover_title.tsx similarity index 95% rename from x-pack/plugins/observability/public/components/threshold/components/closable_popover_title.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/closable_popover_title.tsx index 7fe5d73783011..1caa7c82940aa 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/closable_popover_title.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/closable_popover_title.tsx @@ -21,6 +21,7 @@ export function ClosablePopoverTitle({ children, onClose }: ClosablePopoverTitle {children} @@ -132,7 +132,7 @@ export function ErrorState() { diff --git a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.test.tsx similarity index 96% rename from x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.test.tsx index c07e053374644..a8e64ac343a0f 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Color } from '../../../../../common/threshold_rule/color_palette'; -import { Comparator } from '../../../../../common/threshold_rule/types'; +import { Color } from '../../../../../common/custom_threshold_rule/color_palette'; +import { Comparator } from '../../../../../common/custom_threshold_rule/types'; import { shallow } from 'enzyme'; import React from 'react'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.tsx similarity index 95% rename from x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.tsx index 09bd7e21fdc10..e911eba6ad6c8 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.tsx @@ -7,8 +7,8 @@ import { AnnotationDomainType, LineAnnotation, RectAnnotation } from '@elastic/charts'; import { first, last } from 'lodash'; import React from 'react'; -import { Color, colorTransformer } from '../../../../../common/threshold_rule/color_palette'; -import { Comparator } from '../../../../../common/threshold_rule/types'; +import { Color, colorTransformer } from '../../../../../common/custom_threshold_rule/color_palette'; +import { Comparator } from '../../../../../common/custom_threshold_rule/types'; interface ThresholdAnnotationsProps { threshold: number[]; diff --git a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.stories.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx index 7cea506155818..f58894e7918d9 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.stories.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -13,7 +13,7 @@ import { Aggregators, Comparator, MetricExpressionParams, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; import { TimeUnitChar } from '../../../../../common'; import { CustomEquationEditor, CustomEquationEditorProps } from './custom_equation_editor'; @@ -115,7 +115,7 @@ CustomEquationEditorWithEquationErrors.args = { expression: { ...BASE_ARGS.expression, equation: 'Math.round(A / B)', - customMetrics: [ + metrics: [ { name: 'A', aggType: Aggregators.AVERAGE, field: 'system.cpu.user.pct' }, { name: 'B', aggType: Aggregators.MAX, field: 'system.cpu.cores' }, ], diff --git a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx similarity index 86% rename from x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx index 8dbb44eda71dd..d41f5d5b0b85b 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx @@ -21,12 +21,12 @@ import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/threshold_rule/metrics_explorer'; +import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/custom_threshold_rule/metrics_explorer'; import { Aggregators, CustomMetricAggTypes, - MetricExpressionCustomMetric, -} from '../../../../../common/threshold_rule/types'; + CustomThresholdExpressionMetric, +} from '../../../../../common/custom_threshold_rule/types'; import { MetricExpression } from '../../types'; import { CustomMetrics, AggregationTypes, NormalizedFields } from './types'; @@ -58,7 +58,7 @@ export function CustomEquationEditor({ dataView, }: CustomEquationEditorProps) { const [customMetrics, setCustomMetrics] = useState( - expression?.customMetrics ?? [NEW_METRIC] + expression?.metrics ?? [NEW_METRIC] ); const [customEqPopoverOpen, setCustomEqPopoverOpen] = useState(false); const [equation, setEquation] = useState(expression?.equation || undefined); @@ -69,7 +69,7 @@ export function CustomEquationEditor({ const currentVars = previous?.map((m) => m.name) ?? []; const name = first(xor(VAR_NAMES, currentVars))!; const nextMetrics = [...(previous || []), { ...NEW_METRIC, name }]; - debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation }); + debouncedOnChange({ ...expression, metrics: nextMetrics, equation }); return nextMetrics; }); }, [debouncedOnChange, equation, expression]); @@ -79,7 +79,7 @@ export function CustomEquationEditor({ setCustomMetrics((previous) => { const nextMetrics = previous?.filter((row) => row.name !== name) ?? [NEW_METRIC]; const finalMetrics = (nextMetrics.length && nextMetrics) || [NEW_METRIC]; - debouncedOnChange({ ...expression, customMetrics: finalMetrics, equation }); + debouncedOnChange({ ...expression, metrics: finalMetrics, equation }); return finalMetrics; }); }, @@ -87,10 +87,10 @@ export function CustomEquationEditor({ ); const handleChange = useCallback( - (metric: MetricExpressionCustomMetric) => { + (metric: CustomThresholdExpressionMetric) => { setCustomMetrics((previous) => { const nextMetrics = previous?.map((m) => (m.name === metric.name ? metric : m)); - debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation }); + debouncedOnChange({ ...expression, metrics: nextMetrics, equation }); return nextMetrics; }); }, @@ -100,7 +100,7 @@ export function CustomEquationEditor({ const handleEquationChange = useCallback( (e: React.ChangeEvent) => { setEquation(e.target.value); - debouncedOnChange({ ...expression, customMetrics, equation: e.target.value }); + debouncedOnChange({ ...expression, metrics: customMetrics, equation: e.target.value }); }, [debouncedOnChange, expression, customMetrics] ); @@ -148,7 +148,7 @@ export function CustomEquationEditor({ isDisabled={disableAdd} > @@ -160,7 +160,7 @@ export function CustomEquationEditor({ setCustomEqPopoverOpen(false)}>   @@ -115,7 +118,7 @@ export function MetricRowWithAgg({ setAggTypePopoverOpen(false)}> @@ -154,7 +157,7 @@ export function MetricRowWithAgg({ @@ -195,7 +198,7 @@ export function MetricRowWithAgg({ ) : ( void; + onDelete: (name: string) => void; + disableDelete: boolean; + disableAdd: boolean; + onChange: (metric: CustomThresholdExpressionMetric) => void; + aggregationTypes: AggregationTypes; + errors: IErrorObject; +} diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.stories.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.stories.tsx new file mode 100644 index 0000000000000..8e1e6b590e601 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.stories.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ComponentMeta } from '@storybook/react'; +import { LIGHT_THEME } from '@elastic/charts'; +import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; + +import { Comparator } from '../../../../common/custom_threshold_rule/types'; +import { Props, Threshold as Component } from './custom_threshold'; + +export default { + component: Component, + title: 'app/Alerts/Threshold', + decorators: [ + (Story) => ( +
    + {Story()} +
    + ), + ], +} as ComponentMeta; + +const defaultProps: Props = { + chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, + comparator: Comparator.GT, + id: 'componentId', + threshold: 90, + title: 'Threshold breached', + value: 93, + valueFormatter: (d) => `${d}%`, +}; + +export const Default = { + args: { + ...defaultProps, + }, +}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.test.tsx new file mode 100644 index 0000000000000..a1d61e0ce28cd --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LIGHT_THEME } from '@elastic/charts'; +import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; + +import { render } from '@testing-library/react'; +import { Props, Threshold } from './custom_threshold'; +import React from 'react'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; + +describe('Threshold', () => { + const renderComponent = (props: Partial = {}) => { + const defaultProps: Props = { + chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, + comparator: Comparator.GT, + id: 'componentId', + threshold: 90, + title: 'Threshold breached', + value: 93, + valueFormatter: (d) => `${d}%`, + }; + + return render( +
    + +
    + ); + }; + + it('shows component', () => { + const component = renderComponent(); + expect(component.queryByTestId('thresholdRule-90-93')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx new file mode 100644 index 0000000000000..0dd80826899b0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Chart, Metric, Settings } from '@elastic/charts'; +import { EuiIcon, EuiPanel, useEuiBackgroundColor } from '@elastic/eui'; +import type { PartialTheme, Theme } from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; + +export interface ChartProps { + theme: PartialTheme; + baseTheme: Theme; +} + +export interface Props { + chartProps: ChartProps; + comparator: Comparator | string; + id: string; + threshold: number; + title: string; + value: number; + valueFormatter: (d: number) => string; +} + +export function Threshold({ + chartProps: { theme, baseTheme }, + comparator, + id, + threshold, + title, + value, + valueFormatter, +}: Props) { + const color = useEuiBackgroundColor('danger'); + + return ( + + + + + {i18n.translate( + 'xpack.observability.customThreshold.rule.thresholdExtraTitle', + { + values: { comparator, threshold: valueFormatter(threshold) }, + defaultMessage: `Alert when {comparator} {threshold}`, + } + )} +
    + ), + color, + value, + valueFormatter, + icon: ({ width, height, color: iconColor }) => ( + + ), + }, + ], + ]} + /> + + + ); +} diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx similarity index 96% rename from x-pack/plugins/observability/public/components/threshold/components/expression_chart.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx index 83c05b5694030..e9d6d2c726665 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx @@ -14,7 +14,7 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; import { MetricExpression } from '../types'; import { ExpressionChart } from './expression_chart'; -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; const mockStartServices = mockCoreMock.createStart(); diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx similarity index 94% rename from x-pack/plugins/observability/public/components/threshold/components/expression_chart.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx index 4172175a3b573..8dcb31dd96be5 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx @@ -27,12 +27,12 @@ import { useKibana } from '../../../utils/kibana_react'; import { MetricsExplorerAggregation, MetricsExplorerRow, -} from '../../../../common/threshold_rule/metrics_explorer'; -import { Color } from '../../../../common/threshold_rule/color_palette'; +} from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { Color } from '../../../../common/custom_threshold_rule/color_palette'; import { MetricsExplorerChartType, MetricsExplorerOptionsMetric, -} from '../../../../common/threshold_rule/types'; +} from '../../../../common/custom_threshold_rule/types'; import { MetricExpression, TimeRange } from '../types'; import { createFormatterForMetric } from '../helpers/create_formatter_for_metric'; import { useMetricsExplorerChartData } from '../hooks/use_metrics_explorer_chart_data'; @@ -213,7 +213,7 @@ export function ExpressionChart({ {series.id !== 'ALL' ? ( @@ -221,7 +221,7 @@ export function ExpressionChart({ ) : ( diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_row.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/components/expression_row.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx index 7a7536849c187..ed0c4c52f772c 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_row.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { Comparator } from '../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx similarity index 82% rename from x-pack/plugins/observability/public/components/threshold/components/expression_row.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx index ac8a4a05092d4..e508977658c0c 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx @@ -24,7 +24,7 @@ import { } from '@kbn/triggers-actions-ui-plugin/public'; import { DataViewBase } from '@kbn/es-query'; import { debounce } from 'lodash'; -import { Comparator } from '../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { AGGREGATION_TYPES, DerivedIndexPattern, MetricExpression } from '../types'; import { CustomEquationEditor } from './custom_equation'; import { CUSTOM_EQUATION, LABEL_HELP_MESSAGE, LABEL_LABEL } from '../i18n_strings'; @@ -33,7 +33,7 @@ import { decimalToPct, pctToDecimal } from '../helpers/corrected_percent_convert const customComparators = { ...builtInComparators, [Comparator.OUTSIDE_RANGE]: { - text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel', { + text: i18n.translate('xpack.observability.customThreshold.rule.alertFlyout.outsideRangeLabel', { defaultMessage: 'Is not between', }), value: Comparator.OUTSIDE_RANGE, @@ -173,8 +173,9 @@ export const ExpressionRow: React.FC = (props) => { {canDelete && ( ({ id: 1, - title: i18n.translate('xpack.observability.threshold.rule.infrastructureDropdownTitle', { - defaultMessage: 'Infrastructure rules', - }), + title: i18n.translate( + 'xpack.observability.customThreshold.rule.infrastructureDropdownTitle', + { + defaultMessage: 'Infrastructure rules', + } + ), items: [ { 'data-test-subj': 'inventory-alerts-create-rule', - name: i18n.translate('xpack.observability.threshold.rule.createInventoryRuleButton', { - defaultMessage: 'Create inventory rule', - }), + name: i18n.translate( + 'xpack.observability.customThreshold.rule.createInventoryRuleButton', + { + defaultMessage: 'Create inventory rule', + } + ), onClick: () => { closePopover(); setVisibleFlyoutType('inventory'); @@ -66,15 +72,18 @@ export function MetricsAlertDropdown() { const metricsAlertsPanel = useMemo( () => ({ id: 2, - title: i18n.translate('xpack.observability.threshold.rule.metricsDropdownTitle', { + title: i18n.translate('xpack.observability.customThreshold.rule.metricsDropdownTitle', { defaultMessage: 'Metrics rules', }), items: [ { 'data-test-subj': 'metrics-threshold-alerts-create-rule', - name: i18n.translate('xpack.observability.threshold.rule.createThresholdRuleButton', { - defaultMessage: 'Create threshold rule', - }), + name: i18n.translate( + 'xpack.observability.customThreshold.rule.createThresholdRuleButton', + { + defaultMessage: 'Create threshold rule', + } + ), onClick: () => { closePopover(); setVisibleFlyoutType('threshold'); @@ -89,7 +98,7 @@ export function MetricsAlertDropdown() { const manageAlertsMenuItem = useMemo( () => ({ - name: i18n.translate('xpack.observability.threshold.rule.manageRules', { + name: i18n.translate('xpack.observability.customThreshold.rule.manageRules', { defaultMessage: 'Manage rules', }), icon: 'tableOfContents', @@ -105,7 +114,7 @@ export function MetricsAlertDropdown() { { 'data-test-subj': 'inventory-alerts-menu-option', name: i18n.translate( - 'xpack.observability.threshold.rule.infrastructureDropdownMenu', + 'xpack.observability.customThreshold.rule.infrastructureDropdownMenu', { defaultMessage: 'Infrastructure', } @@ -114,7 +123,7 @@ export function MetricsAlertDropdown() { }, { 'data-test-subj': 'metrics-threshold-alerts-menu-option', - name: i18n.translate('xpack.observability.threshold.rule.metricsDropdownMenu', { + name: i18n.translate('xpack.observability.customThreshold.rule.metricsDropdownMenu', { defaultMessage: 'Metrics', }), panel: 2, @@ -130,7 +139,7 @@ export function MetricsAlertDropdown() { [ { id: 0, - title: i18n.translate('xpack.observability.threshold.rule.alertDropdownTitle', { + title: i18n.translate('xpack.observability.customThreshold.rule.alertDropdownTitle', { defaultMessage: 'Alerts and rules', }), items: firstPanelMenuItems, @@ -152,7 +161,7 @@ export function MetricsAlertDropdown() { data-test-subj="thresholdRuleStructure-alerts-and-rules" > diff --git a/x-pack/plugins/observability/public/components/threshold/components/series_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx similarity index 93% rename from x-pack/plugins/observability/public/components/threshold/components/series_chart.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx index c7716c474b22c..c0e35c85580bc 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/series_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx @@ -14,12 +14,12 @@ import { AreaSeriesStyle, BarSeriesStyle, } from '@elastic/charts'; -import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; -import { Color, colorTransformer } from '../../../../common/threshold_rule/color_palette'; +import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { Color, colorTransformer } from '../../../../common/custom_threshold_rule/color_palette'; import { MetricsExplorerChartType, MetricsExplorerOptionsMetric, -} from '../../../../common/threshold_rule/types'; +} from '../../../../common/custom_threshold_rule/types'; import { getMetricId } from '../helpers/get_metric_id'; import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_time_zone_setting'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/triggers_actions_context.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/triggers_actions_context.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/triggers_actions_context.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/triggers_actions_context.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/components/validation.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/validation.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx new file mode 100644 index 0000000000000..d40a1f2852bcc --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx @@ -0,0 +1,255 @@ +/* + * Copyright 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 { Query, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { buildEsQuery, fromKueryExpression } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; +import { isEmpty } from 'lodash'; +import { + Aggregators, + Comparator, + CustomMetricExpressionParams, + MetricExpressionParams, +} from '../../../../common/custom_threshold_rule/types'; + +export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; + +const isCustomMetricExpressionParams = ( + subject: MetricExpressionParams +): subject is CustomMetricExpressionParams => { + return subject.aggType === Aggregators.CUSTOM; +}; + +export function validateMetricThreshold({ + criteria, + searchConfiguration, +}: { + criteria: MetricExpressionParams[]; + searchConfiguration: SerializedSearchSourceFields; +}): ValidationResult { + const validationResult = { errors: {} }; + const errors: { + [id: string]: { + aggField: string[]; + timeSizeUnit: string[]; + timeWindowSize: string[]; + critical: { + threshold0: string[]; + threshold1: string[]; + }; + warning: { + threshold0: string[]; + threshold1: string[]; + }; + metric: string[]; + metricsError?: string; + metrics: Record; + equation?: string; + }; + } & { filterQuery?: string[]; searchConfiguration?: string[] } = {}; + validationResult.errors = errors; + + if (!searchConfiguration || !searchConfiguration.index) { + errors.searchConfiguration = [ + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration', + { + defaultMessage: 'Data view is required.', + } + ), + ]; + } + + if (searchConfiguration && searchConfiguration.query) { + try { + buildEsQuery( + undefined, + [{ query: (searchConfiguration.query as Query).query, language: 'kuery' }], + [] + ); + } catch (e) { + errors.filterQuery = [ + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery', + { + defaultMessage: 'Filter query is invalid.', + } + ), + ]; + } + } + + if (!criteria || !criteria.length) { + return validationResult; + } + + criteria.forEach((c, idx) => { + // Create an id for each criteria, so we can map errors to specific criteria. + const id = idx.toString(); + + errors[id] = errors[id] || { + aggField: [], + timeSizeUnit: [], + timeWindowSize: [], + critical: { + threshold0: [], + threshold1: [], + }, + warning: { + threshold0: [], + threshold1: [], + }, + metric: [], + metrics: {}, + }; + if (!c.aggType) { + errors[id].aggField.push( + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.aggregationRequired', + { + defaultMessage: 'Aggregation is required.', + } + ) + ); + } + + if (!c.threshold || !c.threshold.length) { + errors[id].critical.threshold0.push( + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdRequired', + { + defaultMessage: 'Threshold is required.', + } + ) + ); + } + + if (c.warningThreshold && !c.warningThreshold.length) { + errors[id].warning.threshold0.push( + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdRequired', + { + defaultMessage: 'Threshold is required.', + } + ) + ); + } + + for (const props of [ + { comparator: c.comparator, threshold: c.threshold, type: 'critical' }, + { comparator: c.warningComparator, threshold: c.warningThreshold, type: 'warning' }, + ]) { + // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i]. + // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element. + const { comparator, threshold, type } = props as { + comparator?: Comparator; + threshold?: number[]; + type: 'critical' | 'warning'; + }; + if (threshold && threshold.length && ![...threshold].every(isNumber)) { + [...threshold].forEach((v, i) => { + if (!isNumber(v)) { + const key = i === 0 ? 'threshold0' : 'threshold1'; + errors[id][type][key].push( + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdTypeRequired', + { + defaultMessage: 'Thresholds must contain a valid number.', + } + ) + ); + } + }); + } + + if (comparator === Comparator.BETWEEN && (!threshold || threshold.length < 2)) { + errors[id][type].threshold1.push( + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdRequired', + { + defaultMessage: 'Threshold is required.', + } + ) + ); + } + } + + if (!c.timeSize) { + errors[id].timeWindowSize.push( + i18n.translate('xpack.observability.customThreshold.rule.alertFlyout.error.timeRequred', { + defaultMessage: 'Time size is Required.', + }) + ); + } + + if (c.aggType !== 'count' && c.aggType !== 'custom' && !c.metric) { + errors[id].metric.push( + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metricRequired', + { + defaultMessage: 'Metric is required.', + } + ) + ); + } + + if (isCustomMetricExpressionParams(c)) { + if (!c.metrics || (c.metrics && c.metrics.length < 1)) { + errors[id].metricsError = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metricsError', + { + defaultMessage: 'You must define at least 1 custom metric', + } + ); + } else { + c.metrics.forEach((metric) => { + const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; + if (!metric.aggType) { + customMetricErrors.aggType = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired', + { + defaultMessage: 'Aggregation is required', + } + ); + } + if (metric.aggType !== 'count' && !metric.field) { + customMetricErrors.field = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired', + { + defaultMessage: 'Field is required', + } + ); + } + if (metric.aggType === 'count' && metric.filter) { + try { + fromKueryExpression(metric.filter); + } catch (e) { + customMetricErrors.filter = e.message; + } + } + if (!isEmpty(customMetricErrors)) { + errors[id].metrics[metric.name] = customMetricErrors; + } + }); + } + + if (c.equation && c.equation.match(EQUATION_REGEX)) { + errors[id].equation = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters', + { + defaultMessage: + 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + } + ); + } + } + }); + + return validationResult; +} +const isNumber = (value: unknown): value is number => typeof value === 'number'; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx new file mode 100644 index 0000000000000..accd48ca969a0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx @@ -0,0 +1,224 @@ +/* + * Copyright 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 { act } from 'react-dom/test-utils'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { queryClient } from '@kbn/osquery-plugin/public/query_client'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; + +import { Comparator } from '../../../common/custom_threshold_rule/types'; +import { MetricsExplorerMetric } from '../../../common/custom_threshold_rule/metrics_explorer'; +import { useKibana } from '../../utils/kibana_react'; +import { kibanaStartMock } from '../../utils/kibana_react.mock'; +import Expressions from './custom_threshold_rule_expression'; + +jest.mock('../../utils/kibana_react'); + +const useKibanaMock = useKibana as jest.Mock; + +const mockKibana = () => { + useKibanaMock.mockReturnValue({ + ...kibanaStartMock.startContract(), + }); +}; + +const dataViewMock = dataViewPluginMocks.createStartContract(); + +describe('Expression', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockKibana(); + }); + + async function setup(currentOptions: { + metrics?: MetricsExplorerMetric[]; + filterQuery?: string; + groupBy?: string; + }) { + const ruleParams = { + criteria: [], + groupBy: undefined, + sourceId: 'default', + searchConfiguration: { + index: 'mockedIndex', + query: { + query: '', + language: 'kuery', + }, + }, + }; + const wrapper = mountWithIntl( + + Reflect.set(ruleParams, key, value)} + setRuleProperty={() => {}} + metadata={{ + currentOptions, + adHocDataViewList: [], + }} + dataViews={dataViewMock} + onChangeMetaData={jest.fn()} + /> + + ); + + const update = async () => + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + await update(); + + return { wrapper, update, ruleParams }; + } + + it('should prefill the alert using the context metadata', async () => { + const currentOptions = { + groupBy: 'host.hostname', + filterQuery: 'foo', + metrics: [ + { aggregation: 'avg', field: 'system.load.1' }, + { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, + ] as MetricsExplorerMetric[], + }; + const { ruleParams } = await setup(currentOptions); + expect(ruleParams.groupBy).toBe('host.hostname'); + expect(ruleParams.searchConfiguration.query.query).toBe('foo'); + expect(ruleParams.criteria).toEqual([ + { + metric: 'system.load.1', + comparator: Comparator.GT, + threshold: [], + timeSize: 1, + timeUnit: 'm', + aggType: 'avg', + }, + { + metric: 'system.cpu.user.pct', + comparator: Comparator.GT, + threshold: [], + timeSize: 1, + timeUnit: 'm', + aggType: 'cardinality', + }, + ]); + }); + + it('should show an error message when searchSource throws an error', async () => { + const currentOptions = { + groupBy: 'host.hostname', + metrics: [ + { aggregation: 'avg', field: 'system.load.1' }, + { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, + ] as MetricsExplorerMetric[], + }; + const errorMessage = 'Error in searchSource create'; + const kibanaMock = kibanaStartMock.startContract(); + useKibanaMock.mockReturnValue({ + ...kibanaMock, + services: { + ...kibanaMock.services, + data: { + dataViews: { + create: jest.fn(), + }, + query: { + timefilter: { + timefilter: jest.fn(), + }, + }, + search: { + searchSource: { + create: jest.fn(() => { + throw new Error(errorMessage); + }), + }, + }, + }, + }, + }); + const { wrapper } = await setup(currentOptions); + expect(wrapper.find(`[data-test-subj="thresholdRuleExpressionError"]`).first().text()).toBe( + errorMessage + ); + }); + + it('should show no timestamp error when selected data view does not have a timeField', async () => { + const currentOptions = { + groupBy: 'host.hostname', + metrics: [ + { aggregation: 'avg', field: 'system.load.1' }, + { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, + ] as MetricsExplorerMetric[], + }; + const mockedIndex = { + id: 'c34a7c79-a88b-4b4a-ad19-72f6d24104e4', + title: 'metrics-fake_hosts', + fieldFormatMap: {}, + typeMeta: {}, + // We should not provide timeFieldName here to show thresholdRuleDataViewErrorNoTimestamp error + // timeFieldName: '@timestamp', + }; + const mockedDataView = { + getIndexPattern: () => 'mockedIndexPattern', + getName: () => 'mockedName', + ...mockedIndex, + }; + const mockedSearchSource = { + id: 'data_source', + shouldOverwriteDataViewType: false, + requestStartHandlers: [], + inheritOptions: {}, + history: [], + fields: { + index: mockedIndex, + }, + getField: jest.fn(() => mockedDataView), + dependencies: { + aggs: { + types: {}, + }, + }, + }; + const kibanaMock = kibanaStartMock.startContract(); + useKibanaMock.mockReturnValue({ + ...kibanaMock, + services: { + ...kibanaMock.services, + data: { + dataViews: { + create: jest.fn(), + }, + query: { + timefilter: { + timefilter: jest.fn(), + }, + }, + search: { + searchSource: { + create: jest.fn(() => mockedSearchSource), + }, + }, + }, + }, + }); + const { wrapper } = await setup(currentOptions); + expect( + wrapper.find(`[data-test-subj="thresholdRuleDataViewErrorNoTimestamp"]`).first().text() + ).toBe( + 'The selected data view does not have a timestamp field, please select another data view.' + ); + }); +}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx new file mode 100644 index 0000000000000..761cff54bc961 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx @@ -0,0 +1,617 @@ +/* + * Copyright 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, useEffect, useMemo, useState } from 'react'; +import { debounce } from 'lodash'; +import { + EuiButtonEmpty, + EuiCallOut, + EuiCheckbox, + EuiEmptyPrompt, + EuiFormErrorText, + EuiFormRow, + EuiIcon, + EuiLink, + EuiLoadingSpinner, + EuiSpacer, + EuiText, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { ISearchSource, Query } from '@kbn/data-plugin/common'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { DataViewBase } from '@kbn/es-query'; +import { DataViewSelectPopover } from '@kbn/stack-alerts-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + ForLastExpression, + IErrorObject, + RuleTypeParams, + RuleTypeParamsExpressionProps, +} from '@kbn/triggers-actions-ui-plugin/public'; + +import { useKibana } from '../../utils/kibana_react'; +import { Aggregators, Comparator } from '../../../common/custom_threshold_rule/types'; +import { TimeUnitChar } from '../../../common/utils/formatters/duration'; +import { AlertContextMeta, AlertParams, MetricExpression } from './types'; +import { ExpressionChart } from './components/expression_chart'; +import { ExpressionRow } from './components/expression_row'; +import { MetricsExplorerGroupBy } from './components/group_by'; +import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; + +const FILTER_TYPING_DEBOUNCE_MS = 500; + +type Props = Omit< + RuleTypeParamsExpressionProps, + 'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' +>; + +export const defaultExpression = { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [], + timeSize: 1, + timeUnit: 'm', +} as MetricExpression; + +// eslint-disable-next-line import/no-default-export +export default function Expressions(props: Props) { + const { setRuleParams, ruleParams, errors, metadata, onChangeMetaData } = props; + const { + data, + dataViews, + dataViewEditor, + docLinks, + unifiedSearch: { + ui: { SearchBar }, + }, + } = useKibana().services; + + const [timeSize, setTimeSize] = useState(1); + const [timeUnit, setTimeUnit] = useState('m'); + const [dataView, setDataView] = useState(); + const [dataViewTimeFieldError, setDataViewTimeFieldError] = useState(); + const [searchSource, setSearchSource] = useState(); + const [paramsError, setParamsError] = useState(); + const derivedIndexPattern = useMemo( + () => ({ + fields: dataView?.fields || [], + title: dataView?.getIndexPattern() || 'unknown-index', + }), + [dataView] + ); + + useEffect(() => { + const initSearchSource = async () => { + let initialSearchConfiguration = ruleParams.searchConfiguration; + + if (!ruleParams.searchConfiguration || !ruleParams.searchConfiguration.index) { + const newSearchSource = data.search.searchSource.createEmpty(); + newSearchSource.setField('query', data.query.queryString.getDefaultQuery()); + const defaultDataView = await data.dataViews.getDefaultDataView(); + if (defaultDataView) { + newSearchSource.setField('index', defaultDataView); + setDataView(defaultDataView); + } + initialSearchConfiguration = newSearchSource.getSerializedFields(); + } + + try { + const createdSearchSource = await data.search.searchSource.create( + initialSearchConfiguration + ); + setRuleParams('searchConfiguration', { + ...initialSearchConfiguration, + ...(ruleParams.searchConfiguration?.query && { + query: ruleParams.searchConfiguration.query, + }), + }); + setSearchSource(createdSearchSource); + setDataView(createdSearchSource.getField('index')); + + if (createdSearchSource.getField('index')) { + const timeFieldName = createdSearchSource.getField('index')?.timeFieldName; + if (!timeFieldName) { + setDataViewTimeFieldError( + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp', + { + defaultMessage: + 'The selected data view does not have a timestamp field, please select another data view.', + } + ) + ); + } else { + setDataViewTimeFieldError(undefined); + } + } else { + setDataViewTimeFieldError(undefined); + } + } catch (error) { + setParamsError(error); + } + }; + + initSearchSource(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data.search.searchSource, data.dataViews, dataView]); + + useEffect(() => { + if (ruleParams.criteria && ruleParams.criteria.length) { + setTimeSize(ruleParams.criteria[0].timeSize); + setTimeUnit(ruleParams.criteria[0].timeUnit); + } else { + preFillAlertCriteria(); + } + + if (!ruleParams.filterQuery) { + preFillAlertFilter(); + } + + if (!ruleParams.groupBy) { + preFillAlertGroupBy(); + } + + if (typeof ruleParams.alertOnNoData === 'undefined') { + setRuleParams('alertOnNoData', true); + } + if (typeof ruleParams.alertOnGroupDisappear === 'undefined') { + setRuleParams('alertOnGroupDisappear', true); + } + }, [metadata]); // eslint-disable-line react-hooks/exhaustive-deps + + const options = useMemo(() => { + if (metadata?.currentOptions?.metrics) { + return metadata.currentOptions as MetricsExplorerOptions; + } else { + return { + metrics: [], + aggregation: 'avg', + }; + } + }, [metadata]); + + const onSelectDataView = useCallback( + (newDataView: DataView) => { + const ruleCriteria = (ruleParams.criteria ? ruleParams.criteria.slice() : []).map( + (criterion) => { + criterion.metrics?.forEach((metric) => { + metric.field = undefined; + }); + return criterion; + } + ); + setRuleParams('criteria', ruleCriteria); + searchSource?.setParent(undefined).setField('index', newDataView); + setRuleParams('searchConfiguration', searchSource?.getSerializedFields()); + setDataView(newDataView); + }, + [ruleParams.criteria, searchSource, setRuleParams] + ); + + const updateParams = useCallback( + (id, e: MetricExpression) => { + const ruleCriteria = ruleParams.criteria ? ruleParams.criteria.slice() : []; + ruleCriteria[id] = e; + setRuleParams('criteria', ruleCriteria); + }, + [setRuleParams, ruleParams.criteria] + ); + + const addExpression = useCallback(() => { + const ruleCriteria = ruleParams.criteria?.slice() || []; + ruleCriteria.push({ + ...defaultExpression, + timeSize: timeSize ?? defaultExpression.timeSize, + timeUnit: timeUnit ?? defaultExpression.timeUnit, + }); + setRuleParams('criteria', ruleCriteria); + }, [setRuleParams, ruleParams.criteria, timeSize, timeUnit]); + + const removeExpression = useCallback( + (id: number) => { + const ruleCriteria = ruleParams.criteria?.slice() || []; + if (ruleCriteria.length > 1) { + ruleCriteria.splice(id, 1); + setRuleParams('criteria', ruleCriteria); + } + }, + [setRuleParams, ruleParams.criteria] + ); + + const onFilterChange = useCallback( + ({ query }: { query?: Query }) => { + setRuleParams('searchConfiguration', { ...ruleParams.searchConfiguration, query }); + }, + [setRuleParams, ruleParams.searchConfiguration] + ); + + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ + onFilterChange, + ]); + + const onGroupByChange = useCallback( + (group: string | null | string[]) => { + setRuleParams('groupBy', group && group.length ? group : ''); + }, + [setRuleParams] + ); + + const emptyError = useMemo(() => { + return { + aggField: [], + timeSizeUnit: [], + timeWindowSize: [], + }; + }, []); + + const updateTimeSize = useCallback( + (ts: number | undefined) => { + const ruleCriteria = + ruleParams.criteria?.map((c) => ({ + ...c, + timeSize: ts, + })) || []; + setTimeSize(ts || undefined); + setRuleParams('criteria', ruleCriteria); + }, + [ruleParams.criteria, setRuleParams] + ); + + const updateTimeUnit = useCallback( + (tu: string) => { + const ruleCriteria = (ruleParams.criteria?.map((c) => ({ + ...c, + timeUnit: tu, + })) || []) as AlertParams['criteria']; + setTimeUnit(tu as TimeUnitChar); + setRuleParams('criteria', ruleCriteria); + }, + [ruleParams.criteria, setRuleParams] + ); + + const preFillAlertCriteria = useCallback(() => { + const md = metadata; + if (md?.currentOptions?.metrics?.length) { + setRuleParams( + 'criteria', + md.currentOptions.metrics.map((metric) => ({ + metric: metric.field, + comparator: Comparator.GT, + threshold: [], + timeSize, + timeUnit, + aggType: metric.aggregation, + })) as AlertParams['criteria'] + ); + } else { + setRuleParams('criteria', [defaultExpression]); + } + }, [metadata, setRuleParams, timeSize, timeUnit]); + + const preFillAlertFilter = useCallback(() => { + const md = metadata; + if (md && md.currentOptions?.filterQuery) { + setRuleParams('searchConfiguration', { + ...ruleParams.searchConfiguration, + query: { + query: md.currentOptions.filterQuery, + language: 'kuery', + }, + }); + } else if (md && md.currentOptions?.groupBy && md.series) { + const { groupBy } = md.currentOptions; + const query = Array.isArray(groupBy) + ? groupBy.map((field, index) => `${field}: "${md.series?.keys?.[index]}"`).join(' and ') + : `${groupBy}: "${md.series.id}"`; + setRuleParams('searchConfiguration', { + ...ruleParams.searchConfiguration, + query: { + query, + language: 'kuery', + }, + }); + } + }, [metadata, setRuleParams, ruleParams.searchConfiguration]); + + const preFillAlertGroupBy = useCallback(() => { + const md = metadata; + if (md && md.currentOptions?.groupBy && !md.series) { + setRuleParams('groupBy', md.currentOptions.groupBy); + } + }, [metadata, setRuleParams]); + + const hasGroupBy = useMemo( + () => ruleParams.groupBy && ruleParams.groupBy.length > 0, + [ruleParams.groupBy] + ); + + const disableNoData = useMemo( + () => ruleParams.criteria?.every((c) => c.aggType === Aggregators.COUNT), + [ruleParams.criteria] + ); + + // Test to see if any of the group fields in groupBy are already filtered down to a single + // group by the filterQuery. If this is the case, then a groupBy is unnecessary, as it would only + // ever produce one group instance + const groupByFilterTestPatterns = useMemo(() => { + if (!ruleParams.groupBy) return null; + const groups = !Array.isArray(ruleParams.groupBy) ? [ruleParams.groupBy] : ruleParams.groupBy; + return groups.map((group: string) => ({ + groupName: group, + pattern: new RegExp(`{"match(_phrase)?":{"${group}":"(.*?)"}}`), + })); + }, [ruleParams.groupBy]); + + const redundantFilterGroupBy = useMemo(() => { + const { filterQuery } = ruleParams; + if (typeof filterQuery !== 'string' || !groupByFilterTestPatterns) return []; + return groupByFilterTestPatterns + .map(({ groupName, pattern }) => { + if (pattern.test(filterQuery)) { + return groupName; + } + }) + .filter((g) => typeof g === 'string') as string[]; + }, [ruleParams, groupByFilterTestPatterns]); + + if (paramsError) { + return ( + <> + +

    {paramsError.message}

    +
    + + + ); + } + + if (!searchSource) { + return ( + <> + } /> + + + ); + } + + const placeHolder = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.searchBar.placeholder', + { + defaultMessage: 'Search for observability data… (e.g. host.name:host-1)', + } + ); + + return ( + <> + +
    + +
    +
    + + { + onChangeMetaData({ ...metadata, adHocDataViewList }); + }} + /> + {dataViewTimeFieldError && ( + + {dataViewTimeFieldError} + + )} + + +
    + +
    +
    + + + {errors.filterQuery && ( + + {errors.filterQuery} + + )} + + +
    + +
    +
    + {ruleParams.criteria && + ruleParams.criteria.map((e, idx) => { + return ( +
    + {/* index has semantic meaning, we show the condition title starting from the 2nd one */} + {idx >= 1 && ( + +
    + +
    +
    + )} + 1) || false} + fields={derivedIndexPattern.fields as any} + remove={removeExpression} + addExpression={addExpression} + key={idx} // idx's don't usually make good key's but here the index has semantic meaning + expressionId={idx} + setRuleParams={updateParams} + errors={(errors[idx] as IErrorObject) || emptyError} + expression={e || {}} + dataView={derivedIndexPattern} + > + {/* Preview */} + + +
    + ); + })} + + + + +
    + + + +
    + + + + + {redundantFilterGroupBy.length > 0 && ( + <> + + + {redundantFilterGroupBy.join(', ')}, + groupCount: redundantFilterGroupBy.length, + filteringAndGroupingLink: ( + + {i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink', + { defaultMessage: 'the docs' } + )} + + ), + }} + /> + + + )} + + + {i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.alertOnGroupDisappear', + { + defaultMessage: 'Alert me if a group stops reporting data', + } + )}{' '} + + + + + } + disabled={disableNoData || !hasGroupBy} + checked={Boolean(hasGroupBy && ruleParams.alertOnGroupDisappear)} + onChange={(e) => setRuleParams('alertOnGroupDisappear', e.target.checked)} + /> + + + ); +} + +const docCountNoDataDisabledHelpText = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.docCountNoDataDisabledHelpText', + { + defaultMessage: '[This setting is not applicable to the Document Count aggregator.]', + } +); diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/calculate_domain.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts similarity index 92% rename from x-pack/plugins/observability/public/components/threshold/helpers/calculate_domain.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts index 17a3b3eee7726..16d0d54d825c1 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/calculate_domain.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts @@ -6,8 +6,8 @@ */ import { min, max, sum, isNumber } from 'lodash'; -import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; -import { MetricsExplorerOptionsMetric } from '../../../../common/threshold_rule/types'; +import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { MetricsExplorerOptionsMetric } from '../../../../common/custom_threshold_rule/types'; import { getMetricId } from './get_metric_id'; const getMin = (values: Array) => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.test.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.test.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metric.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts similarity index 76% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metric.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts index 7cb96af683573..1423413e98388 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metric.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts @@ -6,9 +6,9 @@ */ import numeral from '@elastic/numeral'; -import { InfraFormatterType } from '../../../../common/threshold_rule/types'; -import { createFormatter } from '../../../../common/threshold_rule/formatters'; -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { InfraFormatterType } from '../../../../common/custom_threshold_rule/types'; +import { createFormatter } from '../../../../common/custom_threshold_rule/formatters'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { metricToFormat } from './metric_to_format'; export const createFormatterForMetric = (metric?: MetricsExplorerMetric) => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metrics.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts similarity index 94% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metrics.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts index 07ed09095a6aa..0da0e765d724a 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metrics.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { createFormatterForMetric } from './create_formatter_for_metric'; describe('createFormatterForMetric()', () => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts similarity index 88% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts index f1fe34c377f13..70c4959aafc19 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { createMetricLabel } from './create_metric_label'; describe('createMetricLabel()', () => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/get_metric_id.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts similarity index 93% rename from x-pack/plugins/observability/public/components/threshold/helpers/get_metric_id.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts index 969ade79e4dda..a60af79ac6e2d 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/get_metric_id.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricsExplorerOptionsMetric } from '../../../../common/threshold_rule/types'; +import { MetricsExplorerOptionsMetric } from '../../../../common/custom_threshold_rule/types'; export const getMetricId = (metric: MetricsExplorerOptionsMetric, index: string | number) => { return `metric_${index}`; diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/kuery.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/kuery.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/kuery.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/kuery.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/metric_to_format.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts similarity index 80% rename from x-pack/plugins/observability/public/components/threshold/helpers/metric_to_format.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts index 0ed004793b296..2a7d28b72b7c7 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/metric_to_format.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts @@ -6,8 +6,8 @@ */ import { last } from 'lodash'; -import { InfraFormatterType } from '../../../../common/threshold_rule/types'; -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { InfraFormatterType } from '../../../../common/custom_threshold_rule/types'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; export const metricToFormat = (metric?: MetricsExplorerMetric) => { if (metric && metric.field) { diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/notifications.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/notifications.ts new file mode 100644 index 0000000000000..bc840a206ac64 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/notifications.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +export const useSourceNotifier = () => { + const { notifications } = useKibana(); + + const updateFailure = (message?: string) => { + notifications.toasts.danger({ + toastLifeTimeMs: 3000, + title: i18n.translate( + 'xpack.observability.customThreshold.rule.sourceConfiguration.updateFailureTitle', + { + defaultMessage: 'Configuration update failed', + } + ), + body: [ + i18n.translate( + 'xpack.observability.customThreshold.rule.sourceConfiguration.updateFailureBody', + { + defaultMessage: + "We couldn't apply the changes to the Metrics configuration. Try again later.", + } + ), + message, + ] + .filter(Boolean) + .join(' '), + }); + }; + + const updateSuccess = () => { + notifications.toasts.success({ + toastLifeTimeMs: 3000, + title: i18n.translate( + 'xpack.observability.customThreshold.rule.sourceConfiguration.updateSuccessTitle', + { + defaultMessage: 'Metrics settings successfully updated', + } + ), + }); + }; + + return { + updateFailure, + updateSuccess, + }; +}; diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/runtime_types.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/runtime_types.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/runtime_types.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/runtime_types.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/source_errors.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/source_errors.ts similarity index 89% rename from x-pack/plugins/observability/public/components/threshold/helpers/source_errors.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/source_errors.ts index 45cbbad7d3e3a..3ab64f1aac0bb 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/source_errors.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/source_errors.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; const missingHttpMessage = i18n.translate( - 'xpack.observability.threshold.rule.sourceConfiguration.missingHttp', + 'xpack.observability.customThreshold.rule.sourceConfiguration.missingHttp', { defaultMessage: 'Failed to load source: No HTTP client available.', } diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/use_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/use_alert_prefill.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_time_zone_setting.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_time_zone_setting.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_time_zone_setting.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_time_zone_setting.ts diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_timefilter_time.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_timefilter_time.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_timefilter_time.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_timefilter_time.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metric_threshold_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts similarity index 91% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metric_threshold_alert_prefill.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts index 86262d0f272c2..6314ddb20a115 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metric_threshold_alert_prefill.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts @@ -7,7 +7,7 @@ import { isEqual } from 'lodash'; import { useState } from 'react'; -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; export interface MetricThresholdPrefillOptions { groupBy: string | string[] | undefined; diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts similarity index 87% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_chart_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts index 75c1759e04287..ef810bc8857d9 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts @@ -8,8 +8,8 @@ import DateMath from '@kbn/datemath'; import { DataViewBase } from '@kbn/es-query'; import { useMemo } from 'react'; -import { MetricExplorerCustomMetricAggregations } from '../../../../common/threshold_rule/metrics_explorer'; -import { MetricExpressionCustomMetric } from '../../../../common/threshold_rule/types'; +import { MetricExplorerCustomMetricAggregations } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { CustomThresholdExpressionMetric } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression, TimeRange } from '../types'; import { useMetricsExplorerData } from './use_metrics_explorer_data'; @@ -43,8 +43,7 @@ export const useMetricsExplorerChartData = ( ? { aggregation: 'custom', custom_metrics: - expression?.customMetrics?.map(mapMetricThresholdMetricToMetricsExplorerMetric) ?? - [], + expression?.metrics?.map(mapMetricThresholdMetricToMetricsExplorerMetric) ?? [], equation: expression.equation, } : { field: expression.metric, aggregation: expression.aggType }, @@ -57,7 +56,7 @@ export const useMetricsExplorerChartData = ( expression.equation, expression.metric, // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify(expression.customMetrics), + JSON.stringify(expression.metrics), filterQuery, groupBy, ] @@ -78,7 +77,9 @@ export const useMetricsExplorerChartData = ( return useMetricsExplorerData(options, derivedIndexPattern, timestamps); }; -const mapMetricThresholdMetricToMetricsExplorerMetric = (metric: MetricExpressionCustomMetric) => { +const mapMetricThresholdMetricToMetricsExplorerMetric = ( + metric: CustomThresholdExpressionMetric +) => { if (metric.aggType === 'count') { return { name: metric.name, diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts index 2e01d72572fb8..59857990b906e 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts @@ -12,7 +12,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { MetricsExplorerResponse, metricsExplorerResponseRT, -} from '../../../../common/threshold_rule/metrics_explorer'; +} from '../../../../common/custom_threshold_rule/metrics_explorer'; import { MetricsExplorerOptions, diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts index a23bed69d9f43..7d734a961b04e 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts @@ -12,8 +12,8 @@ import createContainer from 'constate'; import type { TimeRange } from '@kbn/es-query'; import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react'; -import { metricsExplorerMetricRT } from '../../../../common/threshold_rule/metrics_explorer'; -import { Color } from '../../../../common/threshold_rule/color_palette'; +import { metricsExplorerMetricRT } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { Color } from '../../../../common/custom_threshold_rule/color_palette'; import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime } from './use_kibana_timefilter_time'; diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_tracked_promise.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_tracked_promise.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_tracked_promise.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_tracked_promise.ts diff --git a/x-pack/plugins/observability/public/components/custom_threshold/i18n_strings.ts b/x-pack/plugins/observability/public/components/custom_threshold/i18n_strings.ts new file mode 100644 index 0000000000000..cca654da08a09 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/i18n_strings.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const EQUATION_HELP_MESSAGE = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.equationHelpMessage', + { + defaultMessage: + 'Supports basic math equations, valid charaters are: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + } +); + +export const LABEL_LABEL = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.labelLabel', + { defaultMessage: 'Label (optional)' } +); + +export const LABEL_HELP_MESSAGE = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.labelHelpMessage', + { + defaultMessage: 'Custom label will show on the alert chart and in reason', + } +); + +export const CUSTOM_EQUATION = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.customEquation', + { + defaultMessage: 'Custom equation', + } +); + +export const DELETE_LABEL = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.deleteRowButton', + { defaultMessage: 'Delete' } +); + +export const AGGREGATION_LABEL = (name: string) => + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel', + { + defaultMessage: 'Aggregation {name}', + values: { name }, + } + ); diff --git a/x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts similarity index 93% rename from x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts index bafdf5f235467..f6b20963ccb00 100644 --- a/x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression } from '../types'; import { generateUniqueKey } from './generate_unique_key'; diff --git a/x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.ts rename to x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts diff --git a/x-pack/plugins/observability/public/components/threshold/lib/transform_metrics_explorer_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts similarity index 91% rename from x-pack/plugins/observability/public/components/threshold/lib/transform_metrics_explorer_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts index 5fc221afca80f..2a30136924420 100644 --- a/x-pack/plugins/observability/public/components/threshold/lib/transform_metrics_explorer_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts @@ -6,7 +6,7 @@ */ import { first } from 'lodash'; -import { MetricsExplorerResponse } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerResponse } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { MetricThresholdAlertParams, ExpressionChartSeries } from '../types'; export const transformMetricsExplorerData = ( diff --git a/x-pack/plugins/observability/public/components/threshold/mocks/metric_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts similarity index 90% rename from x-pack/plugins/observability/public/components/threshold/mocks/metric_threshold_rule.ts rename to x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts index 98f20b455267e..810ff3c1f5983 100644 --- a/x-pack/plugins/observability/public/components/threshold/mocks/metric_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts @@ -6,7 +6,7 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { MetricThresholdAlert, MetricThresholdRule } from '../components/alert_details_app_section'; @@ -84,7 +84,13 @@ export const buildMetricThresholdRule = ( metric: 'system.memory.used.pct', }, ], - filterQuery: 'host.hostname: Users-System.local and service.type: system', + searchConfiguration: { + query: { + query: 'host.hostname: Users-System.local and service.type: system', + language: 'kuery', + }, + index: 'mockedIndex', + }, groupBy: ['host.hostname'], }, monitoring: { @@ -153,18 +159,18 @@ export const buildMetricThresholdAlert = ( alertOnGroupDisappear: true, }, 'kibana.alert.evaluation.values': [2500, 5], - 'kibana.alert.rule.category': 'Metric threshold', + 'kibana.alert.rule.category': 'Custom threshold (BETA)', 'kibana.alert.rule.consumer': 'alerts', 'kibana.alert.rule.execution.uuid': '62dd07ef-ead9-4b1f-a415-7c83d03925f7', 'kibana.alert.rule.name': 'One condition', - 'kibana.alert.rule.producer': 'infrastructure', - 'kibana.alert.rule.rule_type_id': 'metrics.alert.threshold', + 'kibana.alert.rule.producer': 'observability', + 'kibana.alert.rule.rule_type_id': 'observability.rules.custom_threshold', 'kibana.alert.rule.uuid': '3a1ed8c0-c1a8-11ed-9249-ed6d75986bdc', 'kibana.space_ids': ['default'], 'kibana.alert.rule.tags': [], '@timestamp': '2023-03-28T14:40:00.000Z', 'kibana.alert.reason': 'system.cpu.user.pct reported no data in the last 1m for ', - 'kibana.alert.action_group': 'metrics.threshold.nodata', + 'kibana.alert.action_group': 'custom_threshold.nodata', tags: [], 'kibana.alert.duration.us': 248391946000, 'kibana.alert.time_range': { diff --git a/x-pack/plugins/observability/public/components/threshold/rule_data_formatters.ts b/x-pack/plugins/observability/public/components/custom_threshold/rule_data_formatters.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/rule_data_formatters.ts rename to x-pack/plugins/observability/public/components/custom_threshold/rule_data_formatters.ts diff --git a/x-pack/plugins/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/types.ts new file mode 100644 index 0000000000000..d0b173b4d7c34 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/types.ts @@ -0,0 +1,180 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { CasesUiStart } from '@kbn/cases-plugin/public'; +import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import { DataPublicPluginStart, SerializedSearchSourceFields } from '@kbn/data-plugin/public'; +import { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; +import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { LensPublicStart } from '@kbn/lens-plugin/public'; +import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; +import { SharePluginStart } from '@kbn/share-plugin/public'; +import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import { + RuleTypeParams, + TriggersAndActionsUIPublicPluginStart, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { TimeUnitChar } from '../../../common/utils/formatters'; +import { MetricsExplorerSeries } from '../../../common/custom_threshold_rule/metrics_explorer'; +import { + Comparator, + CustomMetricExpressionParams, + MetricExpressionParams, + MetricsSourceStatus, + NonCountMetricExpressionParams, + SnapshotCustomMetricInput, +} from '../../../common/custom_threshold_rule/types'; +import { ObservabilityPublicStart } from '../../plugin'; +import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; + +export interface AlertContextMeta { + adHocDataViewList: DataView[]; + currentOptions?: Partial; + series?: MetricsExplorerSeries; +} + +export type MetricExpression = Omit< + MetricExpressionParams, + 'metric' | 'timeSize' | 'timeUnit' | 'metrics' | 'equation' +> & { + metric?: NonCountMetricExpressionParams['metric']; + metrics?: CustomMetricExpressionParams['metrics']; + label?: CustomMetricExpressionParams['label']; + equation?: CustomMetricExpressionParams['equation']; + timeSize?: MetricExpressionParams['timeSize']; + timeUnit?: MetricExpressionParams['timeUnit']; +}; + +export enum AGGREGATION_TYPES { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', + P95 = 'p95', + P99 = 'p99', + CUSTOM = 'custom', +} + +export interface MetricThresholdAlertParams { + criteria?: MetricExpression[]; + groupBy?: string | string[]; + filterQuery?: string; + sourceId?: string; +} + +export interface ExpressionChartRow { + timestamp: number; + value: number; +} + +export type ExpressionChartSeries = ExpressionChartRow[][]; + +export interface TimeRange { + from?: string; + to?: string; +} + +export interface AlertParams { + criteria: MetricExpression[]; + groupBy?: string | string[]; + sourceId: string; + filterQuery?: string; + alertOnNoData?: boolean; + alertOnGroupDisappear?: boolean; + searchConfiguration: SerializedSearchSourceFields; + shouldDropPartialBuckets?: boolean; +} + +export interface InfraClientStartDeps { + cases: CasesUiStart; + charts: ChartsPluginStart; + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + discover: DiscoverStart; + embeddable?: EmbeddableStart; + lens: LensPublicStart; + // TODO:: check if needed => https://github.com/elastic/kibana/issues/159340 + // ml: MlPluginStart; + observability: ObservabilityPublicStart; + observabilityShared: ObservabilitySharedPluginStart; + osquery?: unknown; // OsqueryPluginStart; + share: SharePluginStart; + spaces: SpacesPluginStart; + storage: IStorageWrapper; + triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + uiActions: UiActionsStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + usageCollection: UsageCollectionStart; + // TODO:: check if needed => https://github.com/elastic/kibana/issues/159340 + // telemetry: ITelemetryClient; +} + +export type RendererResult = React.ReactElement | null; + +export type RendererFunction = (args: RenderArgs) => Result; +export interface DerivedIndexPattern { + fields: MetricsSourceStatus['indexFields']; + title: string; +} + +export const SnapshotMetricTypeKeys = { + count: null, + cpu: null, + diskLatency: null, + load: null, + memory: null, + memoryTotal: null, + tx: null, + rx: null, + logRate: null, + diskIOReadBytes: null, + diskIOWriteBytes: null, + s3TotalRequests: null, + s3NumberOfObjects: null, + s3BucketSize: null, + s3DownloadBytes: null, + s3UploadBytes: null, + rdsConnections: null, + rdsQueriesExecuted: null, + rdsActiveTransactions: null, + rdsLatency: null, + sqsMessagesVisible: null, + sqsMessagesDelayed: null, + sqsMessagesSent: null, + sqsMessagesEmpty: null, + sqsOldestMessage: null, + custom: null, +}; +export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys); + +export type SnapshotMetricType = rt.TypeOf; +export interface InventoryMetricConditions { + metric: SnapshotMetricType; + timeSize: number; + timeUnit: TimeUnitChar; + sourceId?: string; + threshold: number[]; + comparator: Comparator; + customMetric?: SnapshotCustomMetricInput; + warningThreshold?: number[]; + warningComparator?: Comparator; +} + +export interface MetricThresholdRuleTypeParams extends RuleTypeParams { + criteria: MetricExpressionParams[]; + searchConfiguration: SerializedSearchSourceFields; + groupBy?: string | string[]; +} diff --git a/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx b/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx index 86a9300e98aa0..2325434a08234 100644 --- a/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx +++ b/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx @@ -14,7 +14,7 @@ import { } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { QuerySuggestion } from '@kbn/unified-search-plugin/public'; -import { InfraClientStartDeps, RendererFunction } from '../threshold/types'; +import { InfraClientStartDeps, RendererFunction } from '../custom_threshold/types'; interface WithKueryAutocompletionLifecycleProps { kibana: KibanaReactContextValue; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.stories.tsx b/x-pack/plugins/observability/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.stories.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.stories.tsx rename to x-pack/plugins/observability/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.stories.tsx diff --git a/x-pack/plugins/observability/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.tsx b/x-pack/plugins/observability/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.tsx new file mode 100644 index 0000000000000..0822758859d60 --- /dev/null +++ b/x-pack/plugins/observability/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiConfirmModal } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React from 'react'; + +export interface SloDeleteConfirmationModalProps { + slo: SLOWithSummaryResponse; + onCancel: () => void; + onConfirm: () => void; +} + +export function SloDeleteConfirmationModal({ + slo, + onCancel, + onConfirm, +}: SloDeleteConfirmationModalProps) { + const { name, groupBy } = slo; + return ( + + {groupBy !== ALL_VALUE + ? i18n.translate( + 'xpack.observability.slo.deleteConfirmationModal.partitionByDisclaimerText', + { + defaultMessage: + 'This SLO has been generated with a partition key on "{partitionKey}". Deleting this SLO definition will result in all instances being deleted.', + values: { partitionKey: groupBy }, + } + ) + : i18n.translate('xpack.observability.slo.deleteConfirmationModal.descriptionText', { + defaultMessage: "You can't recover this SLO after deleting it.", + })} + + ); +} diff --git a/x-pack/plugins/observability/public/components/slo/error_rate_chart/error_rate_chart.tsx b/x-pack/plugins/observability/public/components/slo/error_rate_chart/error_rate_chart.tsx new file mode 100644 index 0000000000000..0c804afb8bea3 --- /dev/null +++ b/x-pack/plugins/observability/public/components/slo/error_rate_chart/error_rate_chart.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { SLOResponse } from '@kbn/slo-schema'; +import moment from 'moment'; +import React from 'react'; +import { useKibana } from '../../../utils/kibana_react'; +import { useLensDefinition } from './use_lens_definition'; + +interface Props { + slo: SLOResponse; + fromRange: Date; +} + +export function ErrorRateChart({ slo, fromRange }: Props) { + const { + lens: { EmbeddableComponent }, + } = useKibana().services; + const lensDef = useLensDefinition(slo); + + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/components/slo/error_rate_chart/index.ts b/x-pack/plugins/observability/public/components/slo/error_rate_chart/index.ts new file mode 100644 index 0000000000000..d5f3e681e1ae6 --- /dev/null +++ b/x-pack/plugins/observability/public/components/slo/error_rate_chart/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './error_rate_chart'; diff --git a/x-pack/plugins/observability/public/components/slo/error_rate_chart/use_lens_definition.ts b/x-pack/plugins/observability/public/components/slo/error_rate_chart/use_lens_definition.ts new file mode 100644 index 0000000000000..05f431750400f --- /dev/null +++ b/x-pack/plugins/observability/public/components/slo/error_rate_chart/use_lens_definition.ts @@ -0,0 +1,566 @@ +/* + * Copyright 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 { useEuiTheme } from '@elastic/eui'; +import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { ALL_VALUE, SLOResponse } from '@kbn/slo-schema'; + +export function useLensDefinition(slo: SLOResponse): TypedLensByValueInput['attributes'] { + const { euiTheme } = useEuiTheme(); + + return { + title: 'SLO Error Rate', + description: '', + visualizationType: 'lnsXY', + type: 'lens', + references: [], + state: { + visualization: { + legend: { + isVisible: false, + position: 'right', + showSingleSeries: false, + }, + valueLabels: 'hide', + fittingFunction: 'None', + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: true, + }, + tickLabelsVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + labelsOrientation: { + x: 0, + yLeft: 0, + yRight: 0, + }, + gridlinesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + preferredSeriesType: 'area', + layers: [ + { + layerId: '8730e8af-7dac-430e-9cef-3b9989ff0866', + accessors: ['9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14'], + position: 'top', + seriesType: 'area', + showGridlines: false, + layerType: 'data', + xAccessor: '627ded04-eae0-4437-83a1-bbb6138d2c3b', + yConfig: [ + { + forAccessor: '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14', + color: euiTheme.colors.danger, + }, + ], + }, + { + layerId: '34298f84-681e-4fa3-8107-d6facb32ed92', + layerType: 'referenceLine', + accessors: [ + '0a42b72b-cd5a-4d59-81ec-847d97c268e6', + '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762', + 'c531a6b1-70dd-4918-bdd0-a21535a7af05', + '61f9e663-10eb-41f7-b584-1f0f95418489', + ], + yConfig: [ + { + forAccessor: '0a42b72b-cd5a-4d59-81ec-847d97c268e6', + axisMode: 'left', + textVisibility: true, + color: euiTheme.colors.danger, + iconPosition: 'right', + }, + { + forAccessor: '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762', + axisMode: 'left', + textVisibility: true, + color: euiTheme.colors.danger, + iconPosition: 'right', + }, + { + forAccessor: 'c531a6b1-70dd-4918-bdd0-a21535a7af05', + axisMode: 'left', + textVisibility: true, + color: euiTheme.colors.danger, + iconPosition: 'right', + }, + { + forAccessor: '61f9e663-10eb-41f7-b584-1f0f95418489', + axisMode: 'left', + textVisibility: true, + color: euiTheme.colors.danger, + iconPosition: 'right', + }, + ], + }, + ], + }, + query: { + query: `slo.id : "${slo.id}" and slo.instanceId : "${slo.instanceId ?? ALL_VALUE}"`, + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + '8730e8af-7dac-430e-9cef-3b9989ff0866': { + columns: { + '627ded04-eae0-4437-83a1-bbb6138d2c3b': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { + // @ts-ignore + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + }, + ...(slo.budgetingMethod === 'occurrences' && { + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0': { + label: 'Part of Error rate', + dataType: 'number', + operationType: 'sum', + sourceField: 'slo.numerator', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + emptyAsNull: false, + }, + customLabel: true, + }, + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1': { + label: 'Part of Error rate', + dataType: 'number', + operationType: 'sum', + sourceField: 'slo.denominator', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + emptyAsNull: false, + }, + customLabel: true, + }, + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2': { + label: 'Part of Error rate', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + tinymathAst: { + type: 'function', + name: 'subtract', + args: [ + 1, + { + type: 'function', + name: 'divide', + args: [ + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1', + ], + location: { + min: 3, + max: 47, + }, + text: '(sum(slo.numerator) / sum(slo.denominator))', + }, + ], + location: { + min: 0, + max: 47, + }, + text: '1 - (sum(slo.numerator) / sum(slo.denominator))', + }, + }, + references: [ + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1', + ], + customLabel: true, + }, + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14': { + label: 'Error rate', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + formula: '1 - (sum(slo.numerator) / sum(slo.denominator))', + isFormulaBroken: false, + format: { + id: 'percent', + params: { + decimals: 2, + }, + }, + }, + references: ['9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2'], + customLabel: true, + }, + }), + ...(slo.budgetingMethod === 'timeslices' && { + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0': { + label: 'Part of Error rate', + dataType: 'number', + operationType: 'sum', + sourceField: 'slo.isGoodSlice', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + emptyAsNull: false, + }, + customLabel: true, + }, + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1': { + label: 'Part of Error rate', + dataType: 'number', + operationType: 'count', + sourceField: 'slo.isGoodSlice', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + emptyAsNull: false, + }, + customLabel: true, + }, + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2': { + label: 'Part of Error rate', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + tinymathAst: { + type: 'function', + name: 'subtract', + args: [ + 1, + { + type: 'function', + name: 'divide', + args: [ + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1', + ], + location: { + min: 3, + max: 47, + }, + text: '(sum(slo.isGoodSlice) / count(slo.isGoodSlice))', + }, + ], + location: { + min: 0, + max: 47, + }, + text: '1 - (sum(slo.isGoodSlice) / count(slo.isGoodSlice))', + }, + }, + references: [ + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1', + ], + customLabel: true, + }, + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14': { + label: 'Error rate', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + formula: '1 - (sum(slo.isGoodSlice) / count(slo.isGoodSlice))', + isFormulaBroken: false, + format: { + id: 'percent', + params: { + decimals: 2, + }, + }, + }, + references: ['9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2'], + customLabel: true, + }, + }), + }, + columnOrder: [ + '627ded04-eae0-4437-83a1-bbb6138d2c3b', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1', + '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2', + ], + incompleteColumns: {}, + sampling: 1, + }, + '34298f84-681e-4fa3-8107-d6facb32ed92': { + linkToLayers: [], + columns: { + '0a42b72b-cd5a-4d59-81ec-847d97c268e6X0': { + label: 'Part of 14.4x', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + tinymathAst: { + type: 'function', + name: 'multiply', + args: [ + { + type: 'function', + name: 'subtract', + args: [1, slo.objective.target], + location: { + min: 1, + max: 9, + }, + text: `1 - ${slo.objective.target}`, + }, + 14.4, + ], + location: { + min: 0, + max: 17, + }, + text: `(1 - ${slo.objective.target}) * 14.4`, + }, + }, + references: [], + customLabel: true, + }, + '0a42b72b-cd5a-4d59-81ec-847d97c268e6': { + label: '14.4x', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + formula: `(1 - ${slo.objective.target}) * 14.4`, + isFormulaBroken: false, + }, + references: ['0a42b72b-cd5a-4d59-81ec-847d97c268e6X0'], + customLabel: true, + }, + '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762X0': { + label: 'Part of 6x', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + tinymathAst: { + type: 'function', + name: 'multiply', + args: [ + { + type: 'function', + name: 'subtract', + args: [1, slo.objective.target], + location: { + min: 1, + max: 9, + }, + text: `1 - ${slo.objective.target}`, + }, + 6, + ], + location: { + min: 0, + max: 14, + }, + text: `(1 - ${slo.objective.target}) * 6`, + }, + }, + references: [], + customLabel: true, + }, + '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762': { + label: '6x', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + formula: `(1 - ${slo.objective.target}) * 6`, + isFormulaBroken: false, + }, + references: ['76d3bcc9-7d45-4b08-b2b1-8d3866ca0762X0'], + customLabel: true, + }, + 'c531a6b1-70dd-4918-bdd0-a21535a7af05X0': { + label: 'Part of 3x', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + tinymathAst: { + type: 'function', + name: 'multiply', + args: [ + { + type: 'function', + name: 'subtract', + args: [1, slo.objective.target], + location: { + min: 1, + max: 9, + }, + text: `1 - ${slo.objective.target}`, + }, + 3, + ], + location: { + min: 0, + max: 14, + }, + text: `(1 - ${slo.objective.target}) * 3`, + }, + }, + references: [], + customLabel: true, + }, + 'c531a6b1-70dd-4918-bdd0-a21535a7af05': { + label: '3x', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + formula: `(1 - ${slo.objective.target}) * 3`, + isFormulaBroken: false, + }, + references: ['c531a6b1-70dd-4918-bdd0-a21535a7af05X0'], + customLabel: true, + }, + '61f9e663-10eb-41f7-b584-1f0f95418489X0': { + label: 'Part of 1x', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + tinymathAst: { + type: 'function', + name: 'multiply', + args: [ + { + type: 'function', + name: 'subtract', + args: [1, slo.objective.target], + location: { + min: 1, + max: 9, + }, + text: `1 - ${slo.objective.target}`, + }, + 1, + ], + location: { + min: 0, + max: 14, + }, + text: `(1 - ${slo.objective.target}) * 1`, + }, + }, + references: [], + customLabel: true, + }, + '61f9e663-10eb-41f7-b584-1f0f95418489': { + label: '1x', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + // @ts-ignore + formula: `(1 - ${slo.objective.target}) * 1`, + isFormulaBroken: false, + }, + references: ['61f9e663-10eb-41f7-b584-1f0f95418489X0'], + customLabel: true, + }, + }, + columnOrder: [ + '0a42b72b-cd5a-4d59-81ec-847d97c268e6', + '0a42b72b-cd5a-4d59-81ec-847d97c268e6X0', + '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762X0', + '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762', + 'c531a6b1-70dd-4918-bdd0-a21535a7af05X0', + 'c531a6b1-70dd-4918-bdd0-a21535a7af05', + '61f9e663-10eb-41f7-b584-1f0f95418489X0', + '61f9e663-10eb-41f7-b584-1f0f95418489', + ], + sampling: 1, + ignoreGlobalFilters: false, + incompleteColumns: {}, + }, + }, + }, + indexpattern: { + layers: {}, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [ + { + type: 'index-pattern', + id: '32ca1ad4-81c0-4daf-b9d1-07118044bdc5', + name: 'indexpattern-datasource-layer-8730e8af-7dac-430e-9cef-3b9989ff0866', + }, + { + type: 'index-pattern', + id: '32ca1ad4-81c0-4daf-b9d1-07118044bdc5', + name: 'indexpattern-datasource-layer-34298f84-681e-4fa3-8107-d6facb32ed92', + }, + ], + adHocDataViews: { + '32ca1ad4-81c0-4daf-b9d1-07118044bdc5': { + id: '32ca1ad4-81c0-4daf-b9d1-07118044bdc5', + title: '.slo-observability.sli-v2.*', + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: 'SLO Rollup Data', + }, + }, + }, + }; +} diff --git a/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx index 1a665924b5227..455d6d9d24ed3 100644 --- a/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx +++ b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_group_by_badge.tsx @@ -25,10 +25,10 @@ export function SloGroupByBadge({ slo }: Props) { ; -export type MetricThresholdAlert = TopAlert; - -const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm'; -const ALERT_START_ANNOTATION_ID = 'alert_start_annotation'; -const ALERT_TIME_RANGE_ANNOTATION_ID = 'alert_time_range_annotation'; - -interface AppSectionProps { - alert: MetricThresholdAlert; - rule: MetricThresholdRule; - ruleLink: string; - setAlertSummaryFields: React.Dispatch>; -} - -// eslint-disable-next-line import/no-default-export -export default function AlertDetailsAppSection({ - alert, - rule, - ruleLink, - setAlertSummaryFields, -}: AppSectionProps) { - const { uiSettings, charts } = useKibana().services; - const { euiTheme } = useEuiTheme(); - - // TODO Use rule data view - const derivedIndexPattern = useMemo( - () => ({ - fields: [], - title: 'unknown-index', - }), - [] - ); - const chartProps = { - theme: charts.theme.useChartsTheme(), - baseTheme: charts.theme.useChartsBaseTheme(), - }; - const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]); - const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; - const annotations = [ - , - , - ]; - useEffect(() => { - setAlertSummaryFields([ - { - label: i18n.translate( - 'xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule', - { - defaultMessage: 'Rule', - } - ), - value: ( - - {rule.name} - - ), - }, - ]); - }, [alert, rule, ruleLink, setAlertSummaryFields]); - - return !!rule.params.criteria ? ( - - {rule.params.criteria.map((criterion, index) => ( - - - -

    - {criterion.aggType.toUpperCase()}{' '} - {'metric' in criterion ? criterion.metric : undefined} -

    -
    - - - - - - - - metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined) - } - title={i18n.translate( - 'xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle', - { - defaultMessage: 'Threshold breached', - } - )} - comparator={criterion.comparator} - /> - - - - - -
    -
    - ))} -
    - ) : null; -} diff --git a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/types.ts b/x-pack/plugins/observability/public/components/threshold/components/custom_equation/types.ts deleted file mode 100644 index abc63849ad1d1..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { AggregationType, IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; -import { MetricExpressionCustomMetric } from '../../../../../common/threshold_rule/types'; -import { MetricExpression } from '../../types'; - -export type CustomMetrics = MetricExpression['customMetrics']; - -export interface AggregationTypes { - [x: string]: AggregationType; -} - -export interface NormalizedField { - name: string; - normalizedType: string; -} - -export type NormalizedFields = NormalizedField[]; - -export interface MetricRowBaseProps { - name: string; - onAdd: () => void; - onDelete: (name: string) => void; - disableDelete: boolean; - disableAdd: boolean; - onChange: (metric: MetricExpressionCustomMetric) => void; - aggregationTypes: AggregationTypes; - errors: IErrorObject; -} diff --git a/x-pack/plugins/observability/public/components/threshold/components/threshold.stories.tsx b/x-pack/plugins/observability/public/components/threshold/components/threshold.stories.tsx deleted file mode 100644 index df4242d5ebe36..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/components/threshold.stories.tsx +++ /dev/null @@ -1,47 +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 { ComponentMeta } from '@storybook/react'; -import { LIGHT_THEME } from '@elastic/charts'; -import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; - -import { Comparator } from '../../../../common/threshold_rule/types'; -import { Props, Threshold as Component } from './threshold'; - -export default { - component: Component, - title: 'app/Alerts/Threshold', - decorators: [ - (Story) => ( -
    - {Story()} -
    - ), - ], -} as ComponentMeta; - -const defaultProps: Props = { - chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, - comparator: Comparator.GT, - id: 'componentId', - threshold: 90, - title: 'Threshold breached', - value: 93, - valueFormatter: (d) => `${d}%`, -}; - -export const Default = { - args: { - ...defaultProps, - }, -}; diff --git a/x-pack/plugins/observability/public/components/threshold/components/threshold.test.tsx b/x-pack/plugins/observability/public/components/threshold/components/threshold.test.tsx deleted file mode 100644 index 8aa2e98cf955c..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/components/threshold.test.tsx +++ /dev/null @@ -1,44 +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 { LIGHT_THEME } from '@elastic/charts'; -import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; - -import { render } from '@testing-library/react'; -import { Props, Threshold } from './threshold'; -import React from 'react'; -import { Comparator } from '../../../../common/threshold_rule/types'; - -describe('Threshold', () => { - const renderComponent = (props: Partial = {}) => { - const defaultProps: Props = { - chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, - comparator: Comparator.GT, - id: 'componentId', - threshold: 90, - title: 'Threshold breached', - value: 93, - valueFormatter: (d) => `${d}%`, - }; - - return render( -
    - -
    - ); - }; - - it('shows component', () => { - const component = renderComponent(); - expect(component.queryByTestId('thresholdRule-90-93')).toBeTruthy(); - }); -}); diff --git a/x-pack/plugins/observability/public/components/threshold/components/threshold.tsx b/x-pack/plugins/observability/public/components/threshold/components/threshold.tsx deleted file mode 100644 index 28ae78cdccf27..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/components/threshold.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { Chart, Metric, Settings } from '@elastic/charts'; -import { EuiIcon, EuiPanel, useEuiBackgroundColor } from '@elastic/eui'; -import type { PartialTheme, Theme } from '@elastic/charts'; -import { i18n } from '@kbn/i18n'; -import { Comparator } from '../../../../common/threshold_rule/types'; - -export interface ChartProps { - theme: PartialTheme; - baseTheme: Theme; -} - -export interface Props { - chartProps: ChartProps; - comparator: Comparator | string; - id: string; - threshold: number; - title: string; - value: number; - valueFormatter: (d: number) => string; -} - -export function Threshold({ - chartProps: { theme, baseTheme }, - comparator, - id, - threshold, - title, - value, - valueFormatter, -}: Props) { - const color = useEuiBackgroundColor('danger'); - - return ( - - - - - {i18n.translate('xpack.observability.threshold.rule.thresholdExtraTitle', { - values: { comparator, threshold: valueFormatter(threshold) }, - defaultMessage: `Alert when {comparator} {threshold}`, - })} - - ), - color, - value, - valueFormatter, - icon: ({ width, height, color: iconColor }) => ( - - ), - }, - ], - ]} - /> - - - ); -} diff --git a/x-pack/plugins/observability/public/components/threshold/components/validation.tsx b/x-pack/plugins/observability/public/components/threshold/components/validation.tsx deleted file mode 100644 index 2d72c1e090869..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/components/validation.tsx +++ /dev/null @@ -1,235 +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 { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; -import { buildEsQuery, fromKueryExpression } from '@kbn/es-query'; -import { i18n } from '@kbn/i18n'; -import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; -import { isEmpty } from 'lodash'; -import { - Aggregators, - Comparator, - CustomMetricExpressionParams, - MetricExpressionParams, -} from '../../../../common/threshold_rule/types'; - -export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; - -const isCustomMetricExpressionParams = ( - subject: MetricExpressionParams -): subject is CustomMetricExpressionParams => { - return subject.aggType === Aggregators.CUSTOM; -}; - -export function validateMetricThreshold({ - criteria, - searchConfiguration, - filterQuery, -}: { - criteria: MetricExpressionParams[]; - searchConfiguration: SerializedSearchSourceFields; - filterQuery?: string; -}): ValidationResult { - const validationResult = { errors: {} }; - const errors: { - [id: string]: { - aggField: string[]; - timeSizeUnit: string[]; - timeWindowSize: string[]; - critical: { - threshold0: string[]; - threshold1: string[]; - }; - warning: { - threshold0: string[]; - threshold1: string[]; - }; - metric: string[]; - customMetricsError?: string; - customMetrics: Record; - equation?: string; - }; - } & { filterQuery?: string[]; searchConfiguration?: string[] } = {}; - validationResult.errors = errors; - - if (!searchConfiguration || !searchConfiguration.index) { - errors.searchConfiguration = [ - i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.invalidSearchConfiguration', - { - defaultMessage: 'Data view is required.', - } - ), - ]; - } - - if (filterQuery) { - try { - buildEsQuery(undefined, [{ query: filterQuery, language: 'kuery' }], []); - } catch (e) { - errors.filterQuery = [ - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery', { - defaultMessage: 'Filter query is invalid.', - }), - ]; - } - } - - if (!criteria || !criteria.length) { - return validationResult; - } - - criteria.forEach((c, idx) => { - // Create an id for each criteria, so we can map errors to specific criteria. - const id = idx.toString(); - - errors[id] = errors[id] || { - aggField: [], - timeSizeUnit: [], - timeWindowSize: [], - critical: { - threshold0: [], - threshold1: [], - }, - warning: { - threshold0: [], - threshold1: [], - }, - metric: [], - customMetrics: {}, - }; - if (!c.aggType) { - errors[id].aggField.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired', { - defaultMessage: 'Aggregation is required.', - }) - ); - } - - if (!c.threshold || !c.threshold.length) { - errors[id].critical.threshold0.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { - defaultMessage: 'Threshold is required.', - }) - ); - } - - if (c.warningThreshold && !c.warningThreshold.length) { - errors[id].warning.threshold0.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { - defaultMessage: 'Threshold is required.', - }) - ); - } - - for (const props of [ - { comparator: c.comparator, threshold: c.threshold, type: 'critical' }, - { comparator: c.warningComparator, threshold: c.warningThreshold, type: 'warning' }, - ]) { - // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i]. - // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element. - const { comparator, threshold, type } = props as { - comparator?: Comparator; - threshold?: number[]; - type: 'critical' | 'warning'; - }; - if (threshold && threshold.length && ![...threshold].every(isNumber)) { - [...threshold].forEach((v, i) => { - if (!isNumber(v)) { - const key = i === 0 ? 'threshold0' : 'threshold1'; - errors[id][type][key].push( - i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired', - { - defaultMessage: 'Thresholds must contain a valid number.', - } - ) - ); - } - }); - } - - if (comparator === Comparator.BETWEEN && (!threshold || threshold.length < 2)) { - errors[id][type].threshold1.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { - defaultMessage: 'Threshold is required.', - }) - ); - } - } - - if (!c.timeSize) { - errors[id].timeWindowSize.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.timeRequred', { - defaultMessage: 'Time size is Required.', - }) - ); - } - - if (c.aggType !== 'count' && c.aggType !== 'custom' && !c.metric) { - errors[id].metric.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.metricRequired', { - defaultMessage: 'Metric is required.', - }) - ); - } - - if (isCustomMetricExpressionParams(c)) { - if (!c.customMetrics || (c.customMetrics && c.customMetrics.length < 1)) { - errors[id].customMetricsError = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.customMetricsError', - { - defaultMessage: 'You must define at least 1 custom metric', - } - ); - } else { - c.customMetrics.forEach((metric) => { - const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; - if (!metric.aggType) { - customMetricErrors.aggType = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.customMetrics.aggTypeRequired', - { - defaultMessage: 'Aggregation is required', - } - ); - } - if (metric.aggType !== 'count' && !metric.field) { - customMetricErrors.field = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.customMetrics.fieldRequired', - { - defaultMessage: 'Field is required', - } - ); - } - if (metric.aggType === 'count' && metric.filter) { - try { - fromKueryExpression(metric.filter); - } catch (e) { - customMetricErrors.filter = e.message; - } - } - if (!isEmpty(customMetricErrors)) { - errors[id].customMetrics[metric.name] = customMetricErrors; - } - }); - } - - if (c.equation && c.equation.match(EQUATION_REGEX)) { - errors[id].equation = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters', - { - defaultMessage: - 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', - } - ); - } - } - }); - - return validationResult; -} -const isNumber = (value: unknown): value is number => typeof value === 'number'; diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/notifications.ts b/x-pack/plugins/observability/public/components/threshold/helpers/notifications.ts deleted file mode 100644 index 914333439f426..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/helpers/notifications.ts +++ /dev/null @@ -1,51 +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 { i18n } from '@kbn/i18n'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const useSourceNotifier = () => { - const { notifications } = useKibana(); - - const updateFailure = (message?: string) => { - notifications.toasts.danger({ - toastLifeTimeMs: 3000, - title: i18n.translate( - 'xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle', - { - defaultMessage: 'Configuration update failed', - } - ), - body: [ - i18n.translate('xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody', { - defaultMessage: - "We couldn't apply the changes to the Metrics configuration. Try again later.", - }), - message, - ] - .filter(Boolean) - .join(' '), - }); - }; - - const updateSuccess = () => { - notifications.toasts.success({ - toastLifeTimeMs: 3000, - title: i18n.translate( - 'xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle', - { - defaultMessage: 'Metrics settings successfully updated', - } - ), - }); - }; - - return { - updateFailure, - updateSuccess, - }; -}; diff --git a/x-pack/plugins/observability/public/components/threshold/i18n_strings.ts b/x-pack/plugins/observability/public/components/threshold/i18n_strings.ts deleted file mode 100644 index 0c480fbb9fb28..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/i18n_strings.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const EQUATION_HELP_MESSAGE = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage', - { - defaultMessage: - 'Supports basic math equations, valid charaters are: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', - } -); - -export const LABEL_LABEL = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel', - { defaultMessage: 'Label (optional)' } -); - -export const LABEL_HELP_MESSAGE = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage', - { - defaultMessage: 'Custom label will show on the alert chart and in reason', - } -); - -export const CUSTOM_EQUATION = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquation', - { - defaultMessage: 'Custom equation', - } -); - -export const DELETE_LABEL = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton', - { defaultMessage: 'Delete' } -); - -export const AGGREGATION_LABEL = (name: string) => - i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel', - { - defaultMessage: 'Aggregation {name}', - values: { name }, - } - ); diff --git a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.test.tsx b/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.test.tsx deleted file mode 100644 index 9a4091ee1ca29..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.test.tsx +++ /dev/null @@ -1,221 +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 { act } from 'react-dom/test-utils'; -import { QueryClientProvider } from '@tanstack/react-query'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import { queryClient } from '@kbn/osquery-plugin/public/query_client'; -import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; - -import { Comparator } from '../../../common/threshold_rule/types'; -import { MetricsExplorerMetric } from '../../../common/threshold_rule/metrics_explorer'; -import { useKibana } from '../../utils/kibana_react'; -import { kibanaStartMock } from '../../utils/kibana_react.mock'; -import Expressions from './threshold_rule_expression'; - -jest.mock('../../utils/kibana_react'); - -const useKibanaMock = useKibana as jest.Mock; - -const mockKibana = () => { - useKibanaMock.mockReturnValue({ - ...kibanaStartMock.startContract(), - }); -}; - -const dataViewMock = dataViewPluginMocks.createStartContract(); - -describe('Expression', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockKibana(); - }); - - async function setup(currentOptions: { - metrics?: MetricsExplorerMetric[]; - filterQuery?: string; - groupBy?: string; - }) { - const ruleParams = { - criteria: [], - groupBy: undefined, - filterQuery: '', - sourceId: 'default', - searchConfiguration: {}, - }; - const wrapper = mountWithIntl( - - Reflect.set(ruleParams, key, value)} - setRuleProperty={() => {}} - metadata={{ - currentOptions, - adHocDataViewList: [], - }} - dataViews={dataViewMock} - onChangeMetaData={jest.fn()} - /> - - ); - - const update = async () => - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - await update(); - - return { wrapper, update, ruleParams }; - } - - it('should prefill the alert using the context metadata', async () => { - const currentOptions = { - groupBy: 'host.hostname', - filterQuery: 'foo', - metrics: [ - { aggregation: 'avg', field: 'system.load.1' }, - { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, - ] as MetricsExplorerMetric[], - }; - const { ruleParams } = await setup(currentOptions); - expect(ruleParams.groupBy).toBe('host.hostname'); - expect(ruleParams.filterQuery).toBe('foo'); - expect(ruleParams.criteria).toEqual([ - { - metric: 'system.load.1', - comparator: Comparator.GT, - threshold: [], - timeSize: 1, - timeUnit: 'm', - aggType: 'avg', - }, - { - metric: 'system.cpu.user.pct', - comparator: Comparator.GT, - threshold: [], - timeSize: 1, - timeUnit: 'm', - aggType: 'cardinality', - }, - ]); - }); - - it('should show an error message when searchSource throws an error', async () => { - const currentOptions = { - groupBy: 'host.hostname', - filterQuery: 'foo', - metrics: [ - { aggregation: 'avg', field: 'system.load.1' }, - { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, - ] as MetricsExplorerMetric[], - }; - const errorMessage = 'Error in searchSource create'; - const kibanaMock = kibanaStartMock.startContract(); - useKibanaMock.mockReturnValue({ - ...kibanaMock, - services: { - ...kibanaMock.services, - data: { - dataViews: { - create: jest.fn(), - }, - query: { - timefilter: { - timefilter: jest.fn(), - }, - }, - search: { - searchSource: { - create: jest.fn(() => { - throw new Error(errorMessage); - }), - }, - }, - }, - }, - }); - const { wrapper } = await setup(currentOptions); - expect(wrapper.find(`[data-test-subj="thresholdRuleExpressionError"]`).first().text()).toBe( - errorMessage - ); - }); - - it('should show no timestamp error when selected data view does not have a timeField', async () => { - const currentOptions = { - groupBy: 'host.hostname', - filterQuery: 'foo', - metrics: [ - { aggregation: 'avg', field: 'system.load.1' }, - { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, - ] as MetricsExplorerMetric[], - }; - const mockedIndex = { - id: 'c34a7c79-a88b-4b4a-ad19-72f6d24104e4', - title: 'metrics-fake_hosts', - fieldFormatMap: {}, - typeMeta: {}, - // We should not provide timeFieldName here to show thresholdRuleDataViewErrorNoTimestamp error - // timeFieldName: '@timestamp', - }; - const mockedDataView = { - getIndexPattern: () => 'mockedIndexPattern', - getName: () => 'mockedName', - ...mockedIndex, - }; - const mockedSearchSource = { - id: 'data_source', - shouldOverwriteDataViewType: false, - requestStartHandlers: [], - inheritOptions: {}, - history: [], - fields: { - index: mockedIndex, - }, - getField: jest.fn(() => mockedDataView), - dependencies: { - aggs: { - types: {}, - }, - }, - }; - const kibanaMock = kibanaStartMock.startContract(); - useKibanaMock.mockReturnValue({ - ...kibanaMock, - services: { - ...kibanaMock.services, - data: { - dataViews: { - create: jest.fn(), - }, - query: { - timefilter: { - timefilter: jest.fn(), - }, - }, - search: { - searchSource: { - create: jest.fn(() => mockedSearchSource), - }, - }, - }, - }, - }); - const { wrapper } = await setup(currentOptions); - expect( - wrapper.find(`[data-test-subj="thresholdRuleDataViewErrorNoTimestamp"]`).first().text() - ).toBe( - 'The selected data view does not have a timestamp field, please select another data view.' - ); - }); -}); diff --git a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx b/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx deleted file mode 100644 index 66382ddf1c673..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx +++ /dev/null @@ -1,576 +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, { useCallback, useEffect, useMemo, useState } from 'react'; -import { debounce } from 'lodash'; -import { - EuiButtonEmpty, - EuiCallOut, - EuiCheckbox, - EuiEmptyPrompt, - EuiFormErrorText, - EuiFormRow, - EuiIcon, - EuiLink, - EuiLoadingSpinner, - EuiSpacer, - EuiText, - EuiTitle, - EuiToolTip, -} from '@elastic/eui'; -import { ISearchSource } from '@kbn/data-plugin/common'; -import { DataView } from '@kbn/data-views-plugin/common'; -import { DataViewBase } from '@kbn/es-query'; -import { DataViewSelectPopover } from '@kbn/stack-alerts-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - ForLastExpression, - IErrorObject, - RuleTypeParams, - RuleTypeParamsExpressionProps, -} from '@kbn/triggers-actions-ui-plugin/public'; - -import { useKibana } from '../../utils/kibana_react'; -import { Aggregators, Comparator } from '../../../common/threshold_rule/types'; -import { TimeUnitChar } from '../../../common/utils/formatters/duration'; -import { AlertContextMeta, AlertParams, MetricExpression } from './types'; -import { ExpressionChart } from './components/expression_chart'; -import { ExpressionRow } from './components/expression_row'; -import { RuleFlyoutKueryBar } from '../rule_kql_filter/kuery_bar'; -import { MetricsExplorerGroupBy } from './components/group_by'; -import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; - -const FILTER_TYPING_DEBOUNCE_MS = 500; - -type Props = Omit< - RuleTypeParamsExpressionProps, - 'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' ->; - -export const defaultExpression = { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [], - timeSize: 1, - timeUnit: 'm', -} as MetricExpression; - -// eslint-disable-next-line import/no-default-export -export default function Expressions(props: Props) { - const { setRuleParams, ruleParams, errors, metadata, onChangeMetaData } = props; - const { data, dataViews, dataViewEditor, docLinks } = useKibana().services; - - const [timeSize, setTimeSize] = useState(1); - const [timeUnit, setTimeUnit] = useState('m'); - const [dataView, setDataView] = useState(); - const [dataViewTimeFieldError, setDataViewTimeFieldError] = useState(); - const [searchSource, setSearchSource] = useState(); - const [paramsError, setParamsError] = useState(); - const derivedIndexPattern = useMemo( - () => ({ - fields: dataView?.fields || [], - title: dataView?.getIndexPattern() || 'unknown-index', - }), - [dataView] - ); - - useEffect(() => { - const initSearchSource = async () => { - let initialSearchConfiguration = ruleParams.searchConfiguration; - - if (!ruleParams.searchConfiguration) { - const newSearchSource = data.search.searchSource.createEmpty(); - newSearchSource.setField('query', data.query.queryString.getDefaultQuery()); - const defaultDataView = await data.dataViews.getDefaultDataView(); - if (defaultDataView) { - newSearchSource.setField('index', defaultDataView); - setDataView(defaultDataView); - } - initialSearchConfiguration = newSearchSource.getSerializedFields(); - } - - try { - const createdSearchSource = await data.search.searchSource.create( - initialSearchConfiguration - ); - setRuleParams('searchConfiguration', initialSearchConfiguration); - setSearchSource(createdSearchSource); - setDataView(createdSearchSource.getField('index')); - - if (createdSearchSource.getField('index')) { - const timeFieldName = createdSearchSource.getField('index')?.timeFieldName; - if (!timeFieldName) { - setDataViewTimeFieldError( - i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.dataViewError.noTimestamp', - { - defaultMessage: - 'The selected data view does not have a timestamp field, please select another data view.', - } - ) - ); - } else { - setDataViewTimeFieldError(undefined); - } - } else { - setDataViewTimeFieldError(undefined); - } - } catch (error) { - setParamsError(error); - } - }; - - initSearchSource(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data.search.searchSource, data.dataViews, dataView]); - - const options = useMemo(() => { - if (metadata?.currentOptions?.metrics) { - return metadata.currentOptions as MetricsExplorerOptions; - } else { - return { - metrics: [], - aggregation: 'avg', - }; - } - }, [metadata]); - - const onSelectDataView = useCallback( - (newDataView: DataView) => { - const ruleCriteria = (ruleParams.criteria ? ruleParams.criteria.slice() : []).map( - (criterion) => { - criterion.customMetrics?.forEach((metric) => { - metric.field = undefined; - }); - return criterion; - } - ); - setRuleParams('criteria', ruleCriteria); - searchSource?.setParent(undefined).setField('index', newDataView); - setRuleParams('searchConfiguration', searchSource?.getSerializedFields()); - setDataView(newDataView); - }, - [ruleParams.criteria, searchSource, setRuleParams] - ); - - const updateParams = useCallback( - (id, e: MetricExpression) => { - const ruleCriteria = ruleParams.criteria ? ruleParams.criteria.slice() : []; - ruleCriteria[id] = e; - setRuleParams('criteria', ruleCriteria); - }, - [setRuleParams, ruleParams.criteria] - ); - - const addExpression = useCallback(() => { - const ruleCriteria = ruleParams.criteria?.slice() || []; - ruleCriteria.push({ - ...defaultExpression, - timeSize: timeSize ?? defaultExpression.timeSize, - timeUnit: timeUnit ?? defaultExpression.timeUnit, - }); - setRuleParams('criteria', ruleCriteria); - }, [setRuleParams, ruleParams.criteria, timeSize, timeUnit]); - - const removeExpression = useCallback( - (id: number) => { - const ruleCriteria = ruleParams.criteria?.slice() || []; - if (ruleCriteria.length > 1) { - ruleCriteria.splice(id, 1); - setRuleParams('criteria', ruleCriteria); - } - }, - [setRuleParams, ruleParams.criteria] - ); - - const onFilterChange = useCallback( - (filter: any) => { - setRuleParams('filterQuery', filter); - }, - [setRuleParams] - ); - - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ - onFilterChange, - ]); - - const onGroupByChange = useCallback( - (group: string | null | string[]) => { - setRuleParams('groupBy', group && group.length ? group : ''); - }, - [setRuleParams] - ); - - const emptyError = useMemo(() => { - return { - aggField: [], - timeSizeUnit: [], - timeWindowSize: [], - }; - }, []); - - const updateTimeSize = useCallback( - (ts: number | undefined) => { - const ruleCriteria = - ruleParams.criteria?.map((c) => ({ - ...c, - timeSize: ts, - })) || []; - setTimeSize(ts || undefined); - setRuleParams('criteria', ruleCriteria); - }, - [ruleParams.criteria, setRuleParams] - ); - - const updateTimeUnit = useCallback( - (tu: string) => { - const ruleCriteria = (ruleParams.criteria?.map((c) => ({ - ...c, - timeUnit: tu, - })) || []) as AlertParams['criteria']; - setTimeUnit(tu as TimeUnitChar); - setRuleParams('criteria', ruleCriteria); - }, - [ruleParams.criteria, setRuleParams] - ); - - const preFillAlertCriteria = useCallback(() => { - const md = metadata; - if (md?.currentOptions?.metrics?.length) { - setRuleParams( - 'criteria', - md.currentOptions.metrics.map((metric) => ({ - metric: metric.field, - comparator: Comparator.GT, - threshold: [], - timeSize, - timeUnit, - aggType: metric.aggregation, - })) as AlertParams['criteria'] - ); - } else { - setRuleParams('criteria', [defaultExpression]); - } - }, [metadata, setRuleParams, timeSize, timeUnit]); - - const preFillAlertFilter = useCallback(() => { - const md = metadata; - if (md && md.currentOptions?.filterQuery) { - setRuleParams('filterQuery', md.currentOptions.filterQuery); - } else if (md && md.currentOptions?.groupBy && md.series) { - const { groupBy } = md.currentOptions; - const filter = Array.isArray(groupBy) - ? groupBy.map((field, index) => `${field}: "${md.series?.keys?.[index]}"`).join(' and ') - : `${groupBy}: "${md.series.id}"`; - setRuleParams('filterQuery', filter); - } - }, [metadata, setRuleParams]); - - const preFillAlertGroupBy = useCallback(() => { - const md = metadata; - if (md && md.currentOptions?.groupBy && !md.series) { - setRuleParams('groupBy', md.currentOptions.groupBy); - } - }, [metadata, setRuleParams]); - - useEffect(() => { - if (ruleParams.criteria && ruleParams.criteria.length) { - setTimeSize(ruleParams.criteria[0].timeSize); - setTimeUnit(ruleParams.criteria[0].timeUnit); - } else { - preFillAlertCriteria(); - } - - if (!ruleParams.filterQuery) { - preFillAlertFilter(); - } - - if (!ruleParams.groupBy) { - preFillAlertGroupBy(); - } - - if (typeof ruleParams.alertOnNoData === 'undefined') { - setRuleParams('alertOnNoData', true); - } - if (typeof ruleParams.alertOnGroupDisappear === 'undefined') { - setRuleParams('alertOnGroupDisappear', true); - } - }, [metadata]); // eslint-disable-line react-hooks/exhaustive-deps - - const hasGroupBy = useMemo( - () => ruleParams.groupBy && ruleParams.groupBy.length > 0, - [ruleParams.groupBy] - ); - - const disableNoData = useMemo( - () => ruleParams.criteria?.every((c) => c.aggType === Aggregators.COUNT), - [ruleParams.criteria] - ); - - // Test to see if any of the group fields in groupBy are already filtered down to a single - // group by the filterQuery. If this is the case, then a groupBy is unnecessary, as it would only - // ever produce one group instance - const groupByFilterTestPatterns = useMemo(() => { - if (!ruleParams.groupBy) return null; - const groups = !Array.isArray(ruleParams.groupBy) ? [ruleParams.groupBy] : ruleParams.groupBy; - return groups.map((group: string) => ({ - groupName: group, - pattern: new RegExp(`{"match(_phrase)?":{"${group}":"(.*?)"}}`), - })); - }, [ruleParams.groupBy]); - - const redundantFilterGroupBy = useMemo(() => { - const { filterQuery } = ruleParams; - if (typeof filterQuery !== 'string' || !groupByFilterTestPatterns) return []; - return groupByFilterTestPatterns - .map(({ groupName, pattern }) => { - if (pattern.test(filterQuery)) { - return groupName; - } - }) - .filter((g) => typeof g === 'string') as string[]; - }, [ruleParams, groupByFilterTestPatterns]); - - if (paramsError) { - return ( - <> - -

    {paramsError.message}

    -
    - - - ); - } - - if (!searchSource) { - return ( - <> - } /> - - - ); - } - - const placeHolder = i18n.translate( - 'xpack.observability.threshold.rule.homePage.toolbar.kqlSearchFieldPlaceholder', - { - defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)', - } - ); - - return ( - <> - -
    - -
    -
    - - { - onChangeMetaData({ ...metadata, adHocDataViewList }); - }} - /> - {dataViewTimeFieldError && ( - - {dataViewTimeFieldError} - - )} - - -
    - -
    -
    - - - - -
    - -
    -
    - {ruleParams.criteria && - ruleParams.criteria.map((e, idx) => { - return ( -
    - {/* index has semantic meaning, we show the condition title starting from the 2nd one */} - {idx >= 1 && ( - -
    - -
    -
    - )} - 1) || false} - fields={derivedIndexPattern.fields as any} - remove={removeExpression} - addExpression={addExpression} - key={idx} // idx's don't usually make good key's but here the index has semantic meaning - expressionId={idx} - setRuleParams={updateParams} - errors={(errors[idx] as IErrorObject) || emptyError} - expression={e || {}} - dataView={derivedIndexPattern} - > - {/* Preview */} - - -
    - ); - })} - - - - -
    - - - -
    - - - - - {redundantFilterGroupBy.length > 0 && ( - <> - - - {redundantFilterGroupBy.join(', ')}, - groupCount: redundantFilterGroupBy.length, - filteringAndGroupingLink: ( - - {i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink', - { defaultMessage: 'the docs' } - )} - - ), - }} - /> - - - )} - - - {i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear', - { - defaultMessage: 'Alert me if a group stops reporting data', - } - )}{' '} - - - - - } - disabled={disableNoData || !hasGroupBy} - checked={Boolean(hasGroupBy && ruleParams.alertOnGroupDisappear)} - onChange={(e) => setRuleParams('alertOnGroupDisappear', e.target.checked)} - /> - - - ); -} - -const docCountNoDataDisabledHelpText = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText', - { - defaultMessage: '[This setting is not applicable to the Document Count aggregator.]', - } -); diff --git a/x-pack/plugins/observability/public/components/threshold/types.ts b/x-pack/plugins/observability/public/components/threshold/types.ts deleted file mode 100644 index 59741f40a1356..0000000000000 --- a/x-pack/plugins/observability/public/components/threshold/types.ts +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as rt from 'io-ts'; -import { CasesUiStart } from '@kbn/cases-plugin/public'; -import { ChartsPluginStart } from '@kbn/charts-plugin/public'; -import { DataPublicPluginStart, SerializedSearchSourceFields } from '@kbn/data-plugin/public'; -import { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; -import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; -import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import { LensPublicStart } from '@kbn/lens-plugin/public'; -import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; -import { SharePluginStart } from '@kbn/share-plugin/public'; -import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import { - RuleTypeParams, - TriggersAndActionsUIPublicPluginStart, -} from '@kbn/triggers-actions-ui-plugin/public'; -import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { TimeUnitChar } from '../../../common/utils/formatters'; -import { MetricsExplorerSeries } from '../../../common/threshold_rule/metrics_explorer'; -import { - Comparator, - CustomMetricExpressionParams, - MetricExpressionParams, - MetricsSourceStatus, - NonCountMetricExpressionParams, - SnapshotCustomMetricInput, -} from '../../../common/threshold_rule/types'; -import { ObservabilityPublicStart } from '../../plugin'; -import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; - -export interface AlertContextMeta { - adHocDataViewList: DataView[]; - currentOptions?: Partial; - series?: MetricsExplorerSeries; -} - -export type MetricExpression = Omit< - MetricExpressionParams, - 'metric' | 'timeSize' | 'timeUnit' | 'metrics' | 'equation' | 'customMetrics' -> & { - metric?: NonCountMetricExpressionParams['metric']; - customMetrics?: CustomMetricExpressionParams['customMetrics']; - label?: CustomMetricExpressionParams['label']; - equation?: CustomMetricExpressionParams['equation']; - timeSize?: MetricExpressionParams['timeSize']; - timeUnit?: MetricExpressionParams['timeUnit']; -}; - -export enum AGGREGATION_TYPES { - COUNT = 'count', - AVERAGE = 'avg', - SUM = 'sum', - MIN = 'min', - MAX = 'max', - RATE = 'rate', - CARDINALITY = 'cardinality', - P95 = 'p95', - P99 = 'p99', - CUSTOM = 'custom', -} - -export interface MetricThresholdAlertParams { - criteria?: MetricExpression[]; - groupBy?: string | string[]; - filterQuery?: string; - sourceId?: string; -} - -export interface ExpressionChartRow { - timestamp: number; - value: number; -} - -export type ExpressionChartSeries = ExpressionChartRow[][]; - -export interface TimeRange { - from?: string; - to?: string; -} - -export interface AlertParams { - criteria: MetricExpression[]; - groupBy?: string | string[]; - sourceId: string; - filterQuery?: string; - alertOnNoData?: boolean; - alertOnGroupDisappear?: boolean; - searchConfiguration: SerializedSearchSourceFields; - shouldDropPartialBuckets?: boolean; -} - -export interface InfraClientStartDeps { - cases: CasesUiStart; - charts: ChartsPluginStart; - data: DataPublicPluginStart; - dataViews: DataViewsPublicPluginStart; - discover: DiscoverStart; - embeddable?: EmbeddableStart; - lens: LensPublicStart; - // TODO:: check if needed => https://github.com/elastic/kibana/issues/159340 - // ml: MlPluginStart; - observability: ObservabilityPublicStart; - observabilityShared: ObservabilitySharedPluginStart; - osquery?: unknown; // OsqueryPluginStart; - share: SharePluginStart; - spaces: SpacesPluginStart; - storage: IStorageWrapper; - triggersActionsUi: TriggersAndActionsUIPublicPluginStart; - uiActions: UiActionsStart; - unifiedSearch: UnifiedSearchPublicPluginStart; - usageCollection: UsageCollectionStart; - // TODO:: check if needed => https://github.com/elastic/kibana/issues/159340 - // telemetry: ITelemetryClient; -} - -export type RendererResult = React.ReactElement | null; - -export type RendererFunction = (args: RenderArgs) => Result; -export interface DerivedIndexPattern { - fields: MetricsSourceStatus['indexFields']; - title: string; -} - -export const SnapshotMetricTypeKeys = { - count: null, - cpu: null, - diskLatency: null, - load: null, - memory: null, - memoryTotal: null, - tx: null, - rx: null, - logRate: null, - diskIOReadBytes: null, - diskIOWriteBytes: null, - s3TotalRequests: null, - s3NumberOfObjects: null, - s3BucketSize: null, - s3DownloadBytes: null, - s3UploadBytes: null, - rdsConnections: null, - rdsQueriesExecuted: null, - rdsActiveTransactions: null, - rdsLatency: null, - sqsMessagesVisible: null, - sqsMessagesDelayed: null, - sqsMessagesSent: null, - sqsMessagesEmpty: null, - sqsOldestMessage: null, - custom: null, -}; -export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys); - -export type SnapshotMetricType = rt.TypeOf; -export interface InventoryMetricConditions { - metric: SnapshotMetricType; - timeSize: number; - timeUnit: TimeUnitChar; - sourceId?: string; - threshold: number[]; - comparator: Comparator; - customMetric?: SnapshotCustomMetricInput; - warningThreshold?: number[]; - warningComparator?: Comparator; -} - -export interface MetricThresholdRuleTypeParams extends RuleTypeParams { - criteria: MetricExpressionParams[]; -} diff --git a/x-pack/plugins/observability/public/context/constants.ts b/x-pack/plugins/observability/public/context/constants.ts index 962622b128ded..59e4822f42877 100644 --- a/x-pack/plugins/observability/public/context/constants.ts +++ b/x-pack/plugins/observability/public/context/constants.ts @@ -12,3 +12,4 @@ export const UPTIME_APP = 'uptime'; export const APM_APP = 'apm'; export const INFRA_LOGS_APP = 'infra_logs'; export const INFRA_METRICS_APP = 'infra_metrics'; +export const UNIVERSAL_PROFILING_APP = 'universal_profiling'; diff --git a/x-pack/plugins/observability/public/context/has_data_context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context/has_data_context.test.tsx index 0f349f37cc068..3acc868a30c02 100644 --- a/x-pack/plugins/observability/public/context/has_data_context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context/has_data_context.test.tsx @@ -61,7 +61,7 @@ describe('HasDataContextProvider', () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toMatchObject({ hasDataMap: {}, - hasAnyData: undefined, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -78,6 +78,7 @@ describe('HasDataContextProvider', () => { infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: false, isAllRequestsComplete: true, @@ -114,8 +115,10 @@ describe('HasDataContextProvider', () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { + universal_profiling: { hasData: false, status: 'success' }, + }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -139,6 +142,7 @@ describe('HasDataContextProvider', () => { status: 'success', }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: false, isAllRequestsComplete: true, @@ -176,8 +180,8 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true apm returns true and all other apps return false', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { universal_profiling: { hasData: false, status: 'success' } }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -203,6 +207,7 @@ describe('HasDataContextProvider', () => { status: 'success', }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: true, isAllRequestsComplete: true, @@ -240,8 +245,8 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true and all apps return true', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { universal_profiling: { hasData: false, status: 'success' } }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -271,6 +276,7 @@ describe('HasDataContextProvider', () => { status: 'success', }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: true, isAllRequestsComplete: true, @@ -295,8 +301,8 @@ describe('HasDataContextProvider', () => { wrapper, }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { universal_profiling: { hasData: false, status: 'success' } }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -314,6 +320,7 @@ describe('HasDataContextProvider', () => { infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: true, isAllRequestsComplete: true, @@ -340,8 +347,8 @@ describe('HasDataContextProvider', () => { wrapper, }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { universal_profiling: { hasData: false, status: 'success' } }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -363,6 +370,7 @@ describe('HasDataContextProvider', () => { infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: false, isAllRequestsComplete: true, @@ -406,8 +414,8 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true, apm is undefined and all other apps return true', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { universal_profiling: { hasData: false, status: 'success' } }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -434,6 +442,7 @@ describe('HasDataContextProvider', () => { status: 'success', }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: true, isAllRequestsComplete: true, @@ -484,8 +493,8 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns false and all apps return undefined', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { universal_profiling: { hasData: false, status: 'success' } }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -503,6 +512,7 @@ describe('HasDataContextProvider', () => { infra_metrics: { hasData: undefined, status: 'failure' }, ux: { hasData: undefined, status: 'failure' }, alert: { hasData: false, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: false, isAllRequestsComplete: true, @@ -530,8 +540,8 @@ describe('HasDataContextProvider', () => { it('returns if alerts are available', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasDataMap: {}, - hasAnyData: undefined, + hasDataMap: { universal_profiling: { hasData: false, status: 'success' } }, + hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), @@ -548,10 +558,8 @@ describe('HasDataContextProvider', () => { infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, - alert: { - hasData: true, - status: 'success', - }, + alert: { hasData: true, status: 'success' }, + universal_profiling: { hasData: false, status: 'success' }, }, hasAnyData: true, isAllRequestsComplete: true, diff --git a/x-pack/plugins/observability/public/context/has_data_context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context/has_data_context.tsx index d6e70b74f446a..40c54e7d0b2ec 100644 --- a/x-pack/plugins/observability/public/context/has_data_context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context/has_data_context.tsx @@ -16,6 +16,7 @@ import { APM_APP, INFRA_LOGS_APP, INFRA_METRICS_APP, + UNIVERSAL_PROFILING_APP, UPTIME_APP, UX_APP, } from '../constants'; @@ -54,6 +55,7 @@ const apps: DataContextApps[] = [ INFRA_METRICS_APP, UX_APP, ALERT_APP, + UNIVERSAL_PROFILING_APP, ]; export function HasDataContextProvider({ children }: { children: React.ReactNode }) { @@ -123,6 +125,10 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode indices: resultInfraMetrics?.indices, }); break; + case UNIVERSAL_PROFILING_APP: + // Profiling only shows the empty section for now + updateState({ hasData: false }); + break; } } catch (e) { setHasDataMap((prevState) => ({ diff --git a/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts index 7add468442677..6857609500f92 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts @@ -42,10 +42,14 @@ export function useDeleteSlo() { }); const [queryKey, previousData] = queriesData?.at(0) ?? []; + // taking into account partitioned slo + const matchingSloCount = + previousData?.results?.filter((result) => result.id === slo.id)?.length ?? 0; + const optimisticUpdate = { page: previousData?.page ?? 1, perPage: previousData?.perPage ?? 25, - total: previousData?.total ? previousData.total - 1 : 0, + total: previousData?.total ? previousData.total - matchingSloCount : 0, results: previousData?.results?.filter((result) => result.id !== slo.id) ?? [], }; diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx index a4ca36772217b..bef504ed330ea 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx @@ -11,7 +11,7 @@ export interface AlertSummaryField { label: ReactNode | string; value: ReactNode | string | number; } -export interface AlertSummaryProps { +interface AlertSummaryProps { alertSummaryFields?: AlertSummaryField[]; } diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx index 14afd4994b2be..1ba58e56aa77e 100644 --- a/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx @@ -216,6 +216,7 @@ export function AlertActions({ })} > { @@ -23,16 +27,17 @@ export function LandingPage() { const hasLogsData = logs?.hasData; if (hasLogsData) { - navigateToApp(DISCOVER_APP_ID, { - deepLinkId: 'log-explorer', - }); + const allDataSetsLocator = + url.locators.get(ALL_DATASETS_LOCATOR_ID); + + allDataSetsLocator?.navigate({}); } else if (hasApmData) { navigateToUrl(basePath.prepend('/app/apm/services')); } else { navigateToUrl(basePath.prepend('/app/observabilityOnboarding')); } } - }, [basePath, hasDataMap, isAllRequestsComplete, navigateToApp, navigateToUrl]); + }, [basePath, hasDataMap, isAllRequestsComplete, navigateToApp, navigateToUrl, url.locators]); return <>; } diff --git a/x-pack/plugins/observability/public/pages/overview/components/date_picker/date_picker.tsx b/x-pack/plugins/observability/public/pages/overview/components/date_picker/date_picker.tsx index 1de12b64e6dfb..e3bb21a460067 100644 --- a/x-pack/plugins/observability/public/pages/overview/components/date_picker/date_picker.tsx +++ b/x-pack/plugins/observability/public/pages/overview/components/date_picker/date_picker.tsx @@ -26,7 +26,7 @@ export interface TimePickerTimeDefaults { to: string; } -export interface DatePickerProps { +interface DatePickerProps { rangeFrom?: string; rangeTo?: string; refreshPaused?: boolean; diff --git a/x-pack/plugins/observability/public/pages/overview/components/sections/empty/empty_sections.tsx b/x-pack/plugins/observability/public/pages/overview/components/sections/empty/empty_sections.tsx index 9dc940008a96e..886f9ae4d42ed 100644 --- a/x-pack/plugins/observability/public/pages/overview/components/sections/empty/empty_sections.tsx +++ b/x-pack/plugins/observability/public/pages/overview/components/sections/empty/empty_sections.tsx @@ -147,5 +147,23 @@ const getEmptySections = ({ http }: { http: HttpSetup }): Section[] => { }), href: http.basePath.prepend(paths.observability.rules), }, + { + id: 'universal_profiling', + title: i18n.translate('xpack.observability.emptySection.apps.universalProfiling.title', { + defaultMessage: 'Universal Profiling', + }), + icon: 'logoObservability', + description: i18n.translate( + 'xpack.observability.emptySection.apps.universalProfiling.description', + { + defaultMessage: + 'Understand what lines of code are consuming compute resources across your entire infrastructure, with minimal overhead and zero instrumentation', + } + ), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.universalProfiling.link', { + defaultMessage: 'Install Profiling Host Agent', + }), + href: http.basePath.prepend('/app/profiling/add-data-instructions'), + }, ]; }; diff --git a/x-pack/plugins/observability/public/pages/overview/components/sections/ux/core_web_vitals/web_core_vitals_title.tsx b/x-pack/plugins/observability/public/pages/overview/components/sections/ux/core_web_vitals/web_core_vitals_title.tsx index 9c2904638c281..a14ce837bad07 100644 --- a/x-pack/plugins/observability/public/pages/overview/components/sections/ux/core_web_vitals/web_core_vitals_title.tsx +++ b/x-pack/plugins/observability/public/pages/overview/components/sections/ux/core_web_vitals/web_core_vitals_title.tsx @@ -62,6 +62,7 @@ export function WebCoreVitalsTitle({ isOpen={isPopoverOpen} button={ setIsPopoverOpen(true)} color={'text'} @@ -110,6 +111,7 @@ export function WebCoreVitalsTitle({ isOpen={isBrowserPopoverOpen} button={ setIsBrowserPopoverOpen(true)} color={'text'} diff --git a/x-pack/plugins/observability/public/pages/rules/global_logs_tab.tsx b/x-pack/plugins/observability/public/pages/rules/global_logs_tab.tsx new file mode 100644 index 0000000000000..83b79b11c6cab --- /dev/null +++ b/x-pack/plugins/observability/public/pages/rules/global_logs_tab.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { relativePaths } from '../../../common/locators/paths'; +import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; +import { useKibana } from '../../utils/kibana_react'; + +export function GlobalLogsTab() { + const { + triggersActionsUi: { getGlobalRuleEventLogList: GlobalRuleEventLogList }, + } = useKibana().services; + const filteredRuleTypes = useGetFilteredRuleTypes(); + + return ( + + ); +} + +// eslint-disable-next-line import/no-default-export +export { GlobalLogsTab as default }; diff --git a/x-pack/plugins/observability/public/pages/rules/rules.tsx b/x-pack/plugins/observability/public/pages/rules/rules.tsx index 2de5ebd13bd45..af3eba0b67636 100644 --- a/x-pack/plugins/observability/public/pages/rules/rules.tsx +++ b/x-pack/plugins/observability/public/pages/rules/rules.tsx @@ -5,33 +5,39 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { lazy, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { RuleStatus, useLoadRuleTypes } from '@kbn/triggers-actions-ui-plugin/public'; +import { useLoadRuleTypes } from '@kbn/triggers-actions-ui-plugin/public'; import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common'; -import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import { RULES_LOGS_PATH, RULES_PATH } from '../../../common/locators/paths'; import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; import { HeaderMenu } from '../overview/components/header_menu/header_menu'; +import { RulesTab } from './rules_tab'; -export function RulesPage() { +const GlobalLogsTab = lazy(() => import('./global_logs_tab')); + +const RULES_TAB_NAME = 'rules'; + +interface RulesPageProps { + activeTab?: string; +} +export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) { const { http, docLinks, - triggersActionsUi: { - getAddRuleFlyout: AddRuleFlyout, - getRulesList: RuleList, - getRulesSettingsLink: RulesSettingsLink, - }, + triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout, getRulesSettingsLink: RulesSettingsLink }, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); const history = useHistory(); + const [addRuleFlyoutVisibility, setAddRuleFlyoutVisibility] = useState(false); + const [stateRefresh, setRefresh] = useState(new Date()); useBreadcrumbs([ { @@ -57,63 +63,29 @@ export function RulesPage() { (ruleType) => ruleType.authorizedConsumers[ALERTS_FEATURE_ID]?.all ); - const urlStateStorage = createKbnUrlStateStorage({ - history, - useHash: false, - useHashQuery: false, - }); - - const { lastResponse, params, search, status, type } = urlStateStorage.get<{ - lastResponse: string[]; - params: Record; - search: string; - status: RuleStatus[]; - type: string[]; - }>('_a') || { lastResponse: [], params: {}, search: '', status: [], type: [] }; - - const [stateLastResponse, setLastResponse] = useState(lastResponse); - const [stateParams, setParams] = useState>(params); - const [stateSearch, setSearch] = useState(search); - const [stateStatus, setStatus] = useState(status); - const [stateType, setType] = useState(type); - - const [stateRefresh, setRefresh] = useState(new Date()); - - const [addRuleFlyoutVisibility, setAddRuleFlyoutVisibility] = useState(false); - - const handleStatusFilterChange = (newStatus: RuleStatus[]) => { - setStatus(newStatus); - urlStateStorage.set('_a', { lastResponse, params, search, status: newStatus, type }); - }; - - const handleLastRunOutcomeFilterChange = (newLastResponse: string[]) => { - setRefresh(new Date()); - setLastResponse(newLastResponse); - urlStateStorage.set('_a', { lastResponse: newLastResponse, params, search, status, type }); - }; - - const handleTypeFilterChange = (newType: string[]) => { - setType(newType); - urlStateStorage.set('_a', { lastResponse, params, search, status, type: newType }); - }; - - const handleSearchFilterChange = (newSearch: string) => { - setSearch(newSearch); - urlStateStorage.set('_a', { lastResponse, params, search: newSearch, status, type }); - }; - - const handleRuleParamFilterChange = (newParams: Record) => { - setParams(newParams); - urlStateStorage.set('_a', { lastResponse, params: newParams, search, status, type }); - }; + const tabs = [ + { + name: 'rules', + label: ( + + ), + onClick: () => history.push(RULES_PATH), + isSelected: activeTab === RULES_TAB_NAME, + }, + { + name: 'logs', + label: ( + + ), + onClick: () => history.push(RULES_LOGS_PATH), + ['data-test-subj']: 'ruleLogsTab', + isSelected: activeTab !== RULES_TAB_NAME, + }, + ]; - return ( - , - , - - - , - ], + ] + : []), + , + + + , + ]; + + return ( + - + {activeTab === RULES_TAB_NAME ? ( + + ) : ( + + )} diff --git a/x-pack/plugins/observability/public/pages/rules/rules_tab.tsx b/x-pack/plugins/observability/public/pages/rules/rules_tab.tsx new file mode 100644 index 0000000000000..317091e81fb29 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/rules/rules_tab.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, { useState } from 'react'; + +import { useHistory } from 'react-router-dom'; +import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; +import { RuleStatus } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '../../utils/kibana_react'; +import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; + +interface RulesTabProps { + setRefresh: React.Dispatch>; + stateRefresh: Date; +} + +export function RulesTab({ setRefresh, stateRefresh }: RulesTabProps) { + const { + triggersActionsUi: { getRulesList: RuleList }, + } = useKibana().services; + const history = useHistory(); + + const urlStateStorage = createKbnUrlStateStorage({ + history, + useHash: false, + useHashQuery: false, + }); + + const filteredRuleTypes = useGetFilteredRuleTypes(); + + const { lastResponse, params, search, status, type } = urlStateStorage.get<{ + lastResponse: string[]; + params: Record; + search: string; + status: RuleStatus[]; + type: string[]; + }>('_a') || { lastResponse: [], params: {}, search: '', status: [], type: [] }; + + const [stateLastResponse, setLastResponse] = useState(lastResponse); + const [stateParams, setParams] = useState>(params); + const [stateSearch, setSearch] = useState(search); + const [stateStatus, setStatus] = useState(status); + const [stateType, setType] = useState(type); + + const handleStatusFilterChange = (newStatus: RuleStatus[]) => { + setStatus(newStatus); + urlStateStorage.set('_a', { lastResponse, params, search, status: newStatus, type }); + }; + + const handleLastRunOutcomeFilterChange = (newLastResponse: string[]) => { + setRefresh(new Date()); + setLastResponse(newLastResponse); + urlStateStorage.set('_a', { lastResponse: newLastResponse, params, search, status, type }); + }; + + const handleTypeFilterChange = (newType: string[]) => { + setType(newType); + urlStateStorage.set('_a', { lastResponse, params, search, status, type: newType }); + }; + + const handleSearchFilterChange = (newSearch: string) => { + setSearch(newSearch); + urlStateStorage.set('_a', { lastResponse, params, search: newSearch, status, type }); + }; + + const handleRuleParamFilterChange = (newParams: Record) => { + setParams(newParams); + urlStateStorage.set('_a', { lastResponse, params: newParams, search, status, type }); + }; + + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.test.tsx new file mode 100644 index 0000000000000..5185fa73758d1 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.test.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { screen } from '@testing-library/react'; +import React from 'react'; + +import { buildSlo } from '../../../data/slo/slo'; +import { render } from '../../../utils/test_helper'; +import { BurnRate } from './burn_rate'; + +describe('BurnRate', () => { + it("displays '--' when burn rate is 'undefined'", async () => { + const slo = buildSlo(); + render(); + + expect(screen.queryByTestId('sloDetailsBurnRateStat')).toHaveTextContent('--'); + }); + + it("displays the burn rate value when not 'undefined'", async () => { + const slo = buildSlo(); + render(); + + expect(screen.queryByTestId('sloDetailsBurnRateStat')).toHaveTextContent('3.4x'); + }); + + it("displays the burn rate value when '0'", async () => { + const slo = buildSlo(); + render(); + + expect(screen.queryByTestId('sloDetailsBurnRateStat')).toHaveTextContent('0x'); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.tsx new file mode 100644 index 0000000000000..46c35436359cf --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText, EuiTextColor } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import { SLOResponse } from '@kbn/slo-schema'; +import moment from 'moment'; +import React from 'react'; +import { toDuration, toMinutes } from '../../../utils/slo/duration'; + +export interface BurnRateParams { + slo: SLOResponse; + threshold: number; + burnRate?: number; + isLoading?: boolean; +} + +type Status = 'NO_DATA' | 'BREACHED' | 'OK'; + +function getTitleFromStatus(status: Status): string { + if (status === 'NO_DATA') + return i18n.translate('xpack.observability.slo.burnRate.noDataStatusTitle', { + defaultMessage: 'No value', + }); + if (status === 'BREACHED') + return i18n.translate('xpack.observability.slo.burnRate.breachedStatustTitle', { + defaultMessage: 'Critical value breached', + }); + + return i18n.translate('xpack.observability.slo.burnRate.okStatusTitle', { + defaultMessage: 'Acceptable value', + }); +} + +function getSubtitleFromStatus( + status: Status, + burnRate: number | undefined = 1, + slo: SLOResponse +): string { + if (status === 'NO_DATA') + return i18n.translate('xpack.observability.slo.burnRate.noDataStatusSubtitle', { + defaultMessage: 'Waiting for more data.', + }); + if (status === 'BREACHED') + return i18n.translate('xpack.observability.slo.burnRate.breachedStatustSubtitle', { + defaultMessage: 'At current rate, the error budget will be exhausted in {hour} hours.', + values: { + hour: numeral( + moment + .duration(toMinutes(toDuration(slo.timeWindow.duration)) / burnRate, 'minutes') + .asHours() + ).format('0'), + }, + }); + + return i18n.translate('xpack.observability.slo.burnRate.okStatusSubtitle', { + defaultMessage: 'There is no risk of error budget exhaustion.', + }); +} + +export function BurnRate({ threshold, burnRate, slo, isLoading }: BurnRateParams) { + const status: Status = + burnRate === undefined ? 'NO_DATA' : burnRate > threshold ? 'BREACHED' : 'OK'; + const color = status === 'NO_DATA' ? 'subdued' : status === 'BREACHED' ? 'danger' : 'success'; + + return ( + + + + + +
    {getTitleFromStatus(status)}
    +
    +
    + + + {getSubtitleFromStatus(status, burnRate, slo)} + + +
    + + + + + + {i18n.translate('xpack.observability.slo.burnRate.threshold', { + defaultMessage: 'Threshold is {threshold}x', + values: { threshold }, + })} + + + } + /> + + +
    +
    + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate_window.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate_window.tsx deleted file mode 100644 index 24d3a4e14468f..0000000000000 --- a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate_window.tsx +++ /dev/null @@ -1,127 +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 { - EuiSpacer, - EuiFlexGroup, - EuiPanel, - EuiFlexItem, - EuiStat, - EuiTextColor, - EuiText, - EuiIconTip, -} from '@elastic/eui'; -import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; - -export interface BurnRateWindowParams { - title: string; - target: number; - longWindow: { - label: string; - burnRate: number | null; - sli: number | null; - }; - shortWindow: { - label: string; - burnRate: number | null; - sli: number | null; - }; - isLoading?: boolean; - size?: 'xxxs' | 'xxs' | 'xs' | 's' | 'm' | 'l'; -} - -const SUBDUED = 'subdued'; -const DANGER = 'danger'; -const SUCCESS = 'success'; -const WARNING = 'warning'; - -function getColorBasedOnBurnRate(target: number, burnRate: number | null, sli: number | null) { - if (burnRate === null || sli === null || sli < 0) { - return SUBDUED; - } - if (burnRate > target) { - return DANGER; - } - return SUCCESS; -} - -export function BurnRateWindow({ - title, - target, - longWindow, - shortWindow, - isLoading, - size = 's', -}: BurnRateWindowParams) { - const longWindowColor = getColorBasedOnBurnRate(target, longWindow.burnRate, longWindow.sli); - const shortWindowColor = getColorBasedOnBurnRate(target, shortWindow.burnRate, shortWindow.sli); - - const overallColor = - longWindowColor === DANGER && shortWindowColor === DANGER - ? DANGER - : [longWindowColor, shortWindowColor].includes(DANGER) - ? WARNING - : longWindowColor === SUBDUED && shortWindowColor === SUBDUED - ? SUBDUED - : SUCCESS; - - const isLongWindowValid = - longWindow.burnRate != null && longWindow.sli != null && longWindow.sli >= 0; - - const isShortWindowValid = - shortWindow.burnRate != null && shortWindow.sli != null && shortWindow.sli >= 0; - - return ( - - -
    - {title} - -
    -
    - - - - - {longWindow.label} - - } - /> - - - - {shortWindow.label} - - } - /> - - -
    - ); -} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx index 9ea2c0c531087..54ee0dd11cf76 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx @@ -5,53 +5,78 @@ * 2.0. */ -import React from 'react'; -import { GetSLOBurnRatesResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import { - EuiSpacer, - EuiFlexGrid, - EuiPanel, - EuiTitle, + EuiBetaBadge, + EuiButtonGroup, EuiFlexGroup, EuiFlexItem, - EuiBetaBadge, + EuiPanel, + EuiTitle, + htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import moment from 'moment'; +import React, { useEffect, useState } from 'react'; +import { ErrorRateChart } from '../../../components/slo/error_rate_chart'; import { useFetchSloBurnRates } from '../../../hooks/slo/use_fetch_slo_burn_rates'; -import { BurnRateWindow, BurnRateWindowParams } from './burn_rate_window'; +import { BurnRate } from './burn_rate'; interface Props { slo: SLOWithSummaryResponse; isAutoRefreshing?: boolean; } -const CRITICAL_LONG = 'CRITICAL_LONG'; -const CRITICAL_SHORT = 'CRITICAL_SHORT'; -const HIGH_LONG = 'HIGH_LONG'; -const HIGH_SHORT = 'HIGH_SHORT'; -const MEDIUM_LONG = 'MEDIUM_LONG'; -const MEDIUM_SHORT = 'MEDIUM_SHORT'; -const LOW_LONG = 'LOW_LONG'; -const LOW_SHORT = 'LOW_SHORT'; +const CRITICAL = 'CRITICAL'; +const HIGH = 'HIGH'; +const MEDIUM = 'MEDIUM'; +const LOW = 'LOW'; const WINDOWS = [ - { name: CRITICAL_LONG, duration: '1h' }, - { name: CRITICAL_SHORT, duration: '5m' }, - { name: HIGH_LONG, duration: '6h' }, - { name: HIGH_SHORT, duration: '30m' }, - { name: MEDIUM_LONG, duration: '24h' }, - { name: MEDIUM_SHORT, duration: '120m' }, - { name: LOW_LONG, duration: '72h' }, - { name: LOW_SHORT, duration: '360m' }, + { name: CRITICAL, duration: '1h' }, + { name: HIGH, duration: '6h' }, + { name: MEDIUM, duration: '24h' }, + { name: LOW, duration: '72h' }, ]; -function getSliAndBurnRate(name: string, burnRates: GetSLOBurnRatesResponse['burnRates']) { - const data = burnRates.find((rate) => rate.name === name); - if (!data) { - return { burnRate: null, sli: null }; - } - return { burnRate: data.burnRate, sli: data.sli }; -} +const TIME_RANGE_OPTIONS = [ + { + id: htmlIdGenerator()(), + label: i18n.translate('xpack.observability.slo.burnRates.fromRange.1hLabel', { + defaultMessage: '1h', + }), + windowName: CRITICAL, + threshold: 14.4, + duration: 1, + }, + { + id: htmlIdGenerator()(), + label: i18n.translate('xpack.observability.slo.burnRates.fromRange.6hLabel', { + defaultMessage: '6h', + }), + windowName: HIGH, + threshold: 6, + duration: 6, + }, + { + id: htmlIdGenerator()(), + label: i18n.translate('xpack.observability.slo.burnRates.fromRange.24hLabel', { + defaultMessage: '24h', + }), + windowName: MEDIUM, + threshold: 3, + duration: 24, + }, + { + id: htmlIdGenerator()(), + label: i18n.translate('xpack.observability.slo.burnRates.fromRange.72hLabel', { + defaultMessage: '72h', + }), + windowName: LOW, + threshold: 1, + duration: 72, + }, +]; export function BurnRates({ slo, isAutoRefreshing }: Props) { const { isLoading, data } = useFetchSloBurnRates({ @@ -60,118 +85,77 @@ export function BurnRates({ slo, isAutoRefreshing }: Props) { windows: WINDOWS, }); - const criticalWindowParams: BurnRateWindowParams = { - title: i18n.translate('xpack.observability.slo.burnRate.criticalTitle', { - defaultMessage: 'Critical burn rate', - }), - target: 14.4, - longWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.criticalLongLabel', { - defaultMessage: '1 hour', - }), - ...getSliAndBurnRate(CRITICAL_LONG, data?.burnRates ?? []), - }, - shortWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.criticalShortLabel', { - defaultMessage: '5 minute', - }), - ...getSliAndBurnRate(CRITICAL_SHORT, data?.burnRates ?? []), - }, - }; - - const highWindowParams: BurnRateWindowParams = { - title: i18n.translate('xpack.observability.slo.burnRate.highTitle', { - defaultMessage: 'High burn rate', - }), - target: 6, - longWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.highLongLabel', { - defaultMessage: '6 hour', - }), - ...getSliAndBurnRate(HIGH_LONG, data?.burnRates ?? []), - }, - shortWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.highShortLabel', { - defaultMessage: '30 minute', - }), - ...getSliAndBurnRate(HIGH_SHORT, data?.burnRates ?? []), - }, + const [timeRangeIdSelected, setTimeRangeIdSelected] = useState(TIME_RANGE_OPTIONS[0].id); + const [timeRange, setTimeRange] = useState(TIME_RANGE_OPTIONS[0]); + const onChange = (optionId: string) => { + setTimeRangeIdSelected(optionId); }; - const mediumWindowParams: BurnRateWindowParams = { - title: i18n.translate('xpack.observability.slo.burnRate.mediumTitle', { - defaultMessage: 'Medium burn rate', - }), - target: 3, - longWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.mediumLongLabel', { - defaultMessage: '24 hours', - }), - ...getSliAndBurnRate(MEDIUM_LONG, data?.burnRates ?? []), - }, - shortWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.mediumShortLabel', { - defaultMessage: '2 hours', - }), - ...getSliAndBurnRate(MEDIUM_SHORT, data?.burnRates ?? []), - }, - }; + useEffect(() => { + const selected = + TIME_RANGE_OPTIONS.find((opt) => opt.id === timeRangeIdSelected) ?? TIME_RANGE_OPTIONS[0]; + setTimeRange(selected); + }, [timeRangeIdSelected]); - const lowWindowParams: BurnRateWindowParams = { - title: i18n.translate('xpack.observability.slo.burnRate.lowTitle', { - defaultMessage: 'Low burn rate', - }), - target: 1, - longWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.lowLongLabel', { - defaultMessage: '3 days', - }), - ...getSliAndBurnRate(LOW_LONG, data?.burnRates ?? []), - }, - shortWindow: { - label: i18n.translate('xpack.observability.slo.burnRate.lowShortLabel', { - defaultMessage: '6 hours', - }), - ...getSliAndBurnRate(LOW_SHORT, data?.burnRates ?? []), - }, - }; + const fromRange = moment().subtract(timeRange.duration, 'hour').toDate(); + const threshold = timeRange.threshold; + const burnRate = data?.burnRates.find((br) => br.name === timeRange.windowName)?.burnRate; return ( - - - -

    - {i18n.translate('xpack.observability.slo.burnRate.title', { - defaultMessage: 'Burn rate windows', + + + + + +

    + {i18n.translate('xpack.observability.slo.burnRate.title', { + defaultMessage: 'Burn rate', + })}{' '} +

    +
    +
    + + + +
    + + - - - - - + options={TIME_RANGE_OPTIONS} + idSelected={timeRangeIdSelected} + onChange={(id) => onChange(id)} + buttonSize="compressed" + /> + +
    + + + + + + + +
    - - - - - - - ); } diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx index b62b4c675691f..02556efaf396a 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { SloDeleteConfirmationModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; import { useCapabilities } from '../../../hooks/slo/use_capabilities'; import { useKibana } from '../../../utils/kibana_react'; import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; @@ -23,7 +24,6 @@ import { transformSloResponseToCreateSloForm, transformCreateSLOFormToCreateSLOInput, } from '../../slo_edit/helpers/process_slo_form_values'; -import { SloDeleteConfirmationModal } from '../../slos/components/slo_delete_confirmation_modal'; import type { RulesParams } from '../../../locators/rules'; export interface Props { diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx index f837627ed614f..d70a4cbbfcbe3 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx @@ -13,19 +13,19 @@ import { EuiTabbedContent, EuiTabbedContentTab, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; import React, { Fragment, useState } from 'react'; -import { i18n } from '@kbn/i18n'; import { useLocation } from 'react-router-dom'; import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; -import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter'; import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary'; +import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter'; +import { BurnRates } from './burn_rates'; import { ErrorBudgetChartPanel } from './error_budget_chart_panel'; import { Overview } from './overview/overview'; import { SliChartPanel } from './sli_chart_panel'; import { SloDetailsAlerts } from './slo_detail_alerts'; -import { BurnRates } from './burn_rates'; export interface Props { slo: SLOWithSummaryResponse; diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx index c40b6f5265fec..5dad78763fe04 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx @@ -65,6 +65,9 @@ const mockKibana = () => { useKibanaMock.mockReturnValue({ services: { theme: {}, + lens: { + EmbeddableComponent: () =>
    mocked component
    , + }, application: { navigateToUrl: mockNavigate }, charts: chartPluginMock.createStartContract(), http: { @@ -184,6 +187,7 @@ describe('SLO Details Page', () => { expect(screen.queryByTestId('overview')).toBeTruthy(); expect(screen.queryByTestId('sliChartPanel')).toBeTruthy(); expect(screen.queryByTestId('errorBudgetChartPanel')).toBeTruthy(); + expect(screen.queryByTestId('errorRateChart')).toBeTruthy(); expect(screen.queryAllByTestId('wideChartLoading').length).toBe(0); }); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx index 377f5e0dc0710..bfb629f3c8071 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx @@ -9,15 +9,19 @@ import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { useFormContext } from 'react-hook-form'; +import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; import { CreateSLOForm } from '../../types'; import { FieldSelector } from '../apm_common/field_selector'; import { DataPreviewChart } from '../common/data_preview_chart'; -import { GroupByFieldSelector } from '../common/group_by_field_selector'; +import { IndexFieldSelector } from '../common/index_field_selector'; import { QueryBuilder } from '../common/query_builder'; export function ApmAvailabilityIndicatorTypeForm() { const { watch } = useFormContext(); const index = watch('indicator.params.index'); + const { isLoading: isIndexFieldsLoading, data: indexFields = [] } = + useFetchIndexPatternFields(index); + const partitionByFields = indexFields.filter((field) => field.aggregatable); return ( @@ -121,7 +125,28 @@ export function ApmAvailabilityIndicatorTypeForm() { - + + {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { + defaultMessage: 'Partition by', + })}{' '} + + + } + placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', { + defaultMessage: 'Select an optional field to partition by', + })} + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + /> diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx index 316f17f5072e6..8d21a0c7d2546 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx @@ -9,15 +9,19 @@ import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip } fro import { i18n } from '@kbn/i18n'; import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; +import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; import { CreateSLOForm } from '../../types'; import { FieldSelector } from '../apm_common/field_selector'; import { DataPreviewChart } from '../common/data_preview_chart'; -import { GroupByFieldSelector } from '../common/group_by_field_selector'; +import { IndexFieldSelector } from '../common/index_field_selector'; import { QueryBuilder } from '../common/query_builder'; export function ApmLatencyIndicatorTypeForm() { const { control, watch, getFieldState } = useFormContext(); const index = watch('indicator.params.index'); + const { isLoading: isIndexFieldsLoading, data: indexFields = [] } = + useFetchIndexPatternFields(index); + const partitionByFields = indexFields.filter((field) => field.aggregatable); return ( @@ -164,7 +168,28 @@ export function ApmLatencyIndicatorTypeForm() { - + + {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { + defaultMessage: 'Partition by', + })}{' '} + + + } + placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', { + defaultMessage: 'Select an optional field to partition by', + })} + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + /> diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/group_by_field_selector.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/group_by_field_selector.tsx deleted file mode 100644 index 0733e682e9ba7..0000000000000 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/common/group_by_field_selector.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiComboBox, - EuiComboBoxOptionOption, - EuiFlexItem, - EuiFormRow, - EuiIconTip, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { ALL_VALUE } from '@kbn/slo-schema'; -import React from 'react'; -import { Controller, useFormContext } from 'react-hook-form'; -import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; -import { createOptionsFromFields } from '../../helpers/create_options'; -import { CreateSLOForm } from '../../types'; - -interface Props { - index?: string; -} -export function GroupByFieldSelector({ index }: Props) { - const { control, getFieldState } = useFormContext(); - const { isLoading, data: indexFields = [] } = useFetchIndexPatternFields(index); - const groupableFields = indexFields.filter((field) => field.aggregatable); - - const label = i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', { - defaultMessage: 'Select an optional field to partition by', - }); - - return ( - - - {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { - defaultMessage: 'Partition by', - })}{' '} - - - } - isInvalid={getFieldState('groupBy').invalid} - > - ( - { - if (selected.length) { - return field.onChange(selected[0].value); - } - - field.onChange(ALL_VALUE); - }} - options={createOptionsFromFields(groupableFields)} - selectedOptions={ - !!index && - !!field.value && - groupableFields.some((groupableField) => groupableField.name === field.value) - ? [{ value: field.value, label: field.value }] - : [] - } - singleSelection - /> - )} - /> - - - ); -} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/index_field_selector.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/index_field_selector.tsx new file mode 100644 index 0000000000000..6bb6996f95b2a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/common/index_field_selector.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiComboBox, EuiComboBoxOptionOption, EuiFlexItem, EuiFormRow } from '@elastic/eui'; +import { ALL_VALUE } from '@kbn/slo-schema'; +import React, { useEffect, useState } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; +import { createOptionsFromFields, Option } from '../../helpers/create_options'; +import { CreateSLOForm } from '../../types'; + +interface Props { + indexFields: Field[]; + name: 'groupBy' | 'indicator.params.timestampField'; + label: React.ReactNode | string; + placeholder: string; + isDisabled: boolean; + isLoading: boolean; + isRequired?: boolean; +} +export function IndexFieldSelector({ + indexFields, + name, + label, + placeholder, + isDisabled, + isLoading, + isRequired = false, +}: Props) { + const { control, getFieldState } = useFormContext(); + const [options, setOptions] = useState(createOptionsFromFields(indexFields)); + + useEffect(() => { + setOptions(createOptionsFromFields(indexFields)); + }, [indexFields]); + + return ( + + + ( + + {...field} + async + placeholder={placeholder} + aria-label={placeholder} + isClearable + isDisabled={isLoading || isDisabled} + isInvalid={fieldState.invalid} + isLoading={isLoading} + onChange={(selected: EuiComboBoxOptionOption[]) => { + if (selected.length) { + return field.onChange(selected[0].value); + } + + field.onChange(ALL_VALUE); + }} + options={options} + onSearchChange={(searchValue: string) => { + setOptions( + createOptionsFromFields(indexFields, ({ value }) => value.includes(searchValue)) + ); + }} + selectedOptions={ + !!indexFields && + !!field.value && + indexFields.some((indexField) => indexField.name === field.value) + ? [{ value: field.value, label: field.value }] + : [] + } + singleSelection + /> + )} + /> + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx index 6d7ae6c012a2e..8555750fd6ff1 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx @@ -5,31 +5,24 @@ * 2.0. */ -import { - EuiComboBox, - EuiComboBoxOptionOption, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiIconTip, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { Controller, useFormContext } from 'react-hook-form'; +import { useFormContext } from 'react-hook-form'; import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; -import { createOptionsFromFields } from '../../helpers/create_options'; import { CreateSLOForm } from '../../types'; import { DataPreviewChart } from '../common/data_preview_chart'; -import { GroupByFieldSelector } from '../common/group_by_field_selector'; +import { IndexFieldSelector } from '../common/index_field_selector'; import { QueryBuilder } from '../common/query_builder'; import { IndexSelection } from '../custom_common/index_selection'; export function CustomKqlIndicatorTypeForm() { - const { control, watch, getFieldState } = useFormContext(); - + const { watch } = useFormContext(); const index = watch('indicator.params.index'); - const { isLoading, data: indexFields } = useFetchIndexPatternFields(index); - const timestampFields = (indexFields ?? []).filter((field) => field.type === 'date'); + const { isLoading: isIndexFieldsLoading, data: indexFields = [] } = + useFetchIndexPatternFields(index); + const timestampFields = indexFields.filter((field) => field.type === 'date'); + const partitionByFields = indexFields.filter((field) => field.aggregatable); return ( @@ -37,56 +30,22 @@ export function CustomKqlIndicatorTypeForm() { + - - ( - { - if (selected.length) { - return field.onChange(selected[0].value); - } - - field.onChange(''); - }} - options={createOptionsFromFields(timestampFields)} - selectedOptions={ - !!index && - !!field.value && - timestampFields.some((timestampField) => timestampField.name === field.value) - ? [{ value: field.value, label: field.value }] - : [] - } - singleSelection - /> - )} - /> - + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + isRequired + /> @@ -176,7 +135,28 @@ export function CustomKqlIndicatorTypeForm() { /> - + + {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { + defaultMessage: 'Partition by', + })}{' '} + + + } + placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', { + defaultMessage: 'Select an optional field to partition by', + })} + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + /> diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx index 25de4568dc06c..c7fe7999f9be4 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx @@ -6,37 +6,34 @@ */ import { - EuiComboBox, - EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, - EuiFormRow, EuiHorizontalRule, EuiIconTip, EuiSpacer, EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { Controller, useFormContext } from 'react-hook-form'; import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { useFormContext } from 'react-hook-form'; import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; -import { createOptionsFromFields } from '../../helpers/create_options'; import { CreateSLOForm } from '../../types'; import { DataPreviewChart } from '../common/data_preview_chart'; +import { IndexFieldSelector } from '../common/index_field_selector'; import { QueryBuilder } from '../common/query_builder'; import { IndexSelection } from '../custom_common/index_selection'; import { MetricIndicator } from './metric_indicator'; -import { GroupByFieldSelector } from '../common/group_by_field_selector'; export { NEW_CUSTOM_METRIC } from './metric_indicator'; export function CustomMetricIndicatorTypeForm() { - const { control, watch, getFieldState } = useFormContext(); - + const { watch } = useFormContext(); const index = watch('indicator.params.index'); - const { isLoading, data: indexFields } = useFetchIndexPatternFields(index); - const timestampFields = (indexFields ?? []).filter((field) => field.type === 'date'); + const { isLoading: isIndexFieldsLoading, data: indexFields = [] } = + useFetchIndexPatternFields(index); + const timestampFields = indexFields.filter((field) => field.type === 'date'); + const partitionByFields = indexFields.filter((field) => field.aggregatable); return ( <> @@ -55,61 +52,20 @@ export function CustomMetricIndicatorTypeForm() { - - ( - { - if (selected.length) { - return field.onChange(selected[0].value); - } - - field.onChange(''); - }} - options={createOptionsFromFields(timestampFields)} - selectedOptions={ - !!watch('indicator.params.index') && - !!field.value && - timestampFields.some((timestampField) => timestampField.name === field.value) - ? [ - { - value: field.value, - label: field.value, - 'data-test-subj': `customMetricIndicatorFormTimestampFieldSelectedValue`, - }, - ] - : [] - } - singleSelection={{ asPlainText: true }} - /> - )} - /> - + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + isRequired + /> @@ -157,7 +113,11 @@ export function CustomMetricIndicatorTypeForm() {

    - +
    @@ -174,14 +134,39 @@ export function CustomMetricIndicatorTypeForm() { - + - + + {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { + defaultMessage: 'Partition by', + })}{' '} + + + } + placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', { + defaultMessage: 'Select an optional field to partition by', + })} + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + />
    diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx index 794726db2c711..a333a353f97d9 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx @@ -28,7 +28,7 @@ import { QueryBuilder } from '../common/query_builder'; interface MetricIndicatorProps { type: 'good' | 'total'; - indexFields: Field[] | undefined; + indexFields: Field[]; isLoadingIndex: boolean; } @@ -91,9 +91,7 @@ export function MetricIndicator({ type, indexFields, isLoadingIndex }: MetricInd ); const { control, watch, setValue, register } = useFormContext(); - const metricFields = (indexFields ?? []).filter((field) => - SUPPORTED_FIELD_TYPES.includes(field.type) - ); + const metricFields = indexFields.filter((field) => SUPPORTED_FIELD_TYPES.includes(field.type)); const { fields, append, remove } = useFieldArray({ control, @@ -215,6 +213,7 @@ export function MetricIndicator({ type, indexFields, isLoadingIndex }: MetricInd
    (); - const histogramFields = (indexFields ?? []).filter((field) => field.type === 'histogram'); + const histogramFields = indexFields.filter((field) => field.type === 'histogram'); const indexPattern = watch('indicator.params.index'); const aggregation = watch(`indicator.params.${type}.aggregation`); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx index 56a867ab2e332..dfbf305235b41 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx @@ -6,35 +6,33 @@ */ import { - EuiComboBox, - EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, - EuiFormRow, EuiHorizontalRule, EuiIconTip, EuiSpacer, EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { Controller, useFormContext } from 'react-hook-form'; import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { useFormContext } from 'react-hook-form'; import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; -import { createOptionsFromFields } from '../../helpers/create_options'; import { CreateSLOForm } from '../../types'; import { DataPreviewChart } from '../common/data_preview_chart'; +import { IndexFieldSelector } from '../common/index_field_selector'; import { QueryBuilder } from '../common/query_builder'; import { IndexSelection } from '../custom_common/index_selection'; import { HistogramIndicator } from './histogram_indicator'; -import { GroupByFieldSelector } from '../common/group_by_field_selector'; export function HistogramIndicatorTypeForm() { - const { control, watch, getFieldState } = useFormContext(); - + const { watch } = useFormContext(); const index = watch('indicator.params.index'); - const { isLoading, data: indexFields } = useFetchIndexPatternFields(index); - const timestampFields = (indexFields ?? []).filter((field) => field.type === 'date'); + + const { isLoading: isIndexFieldsLoading, data: indexFields = [] } = + useFetchIndexPatternFields(index); + const timestampFields = indexFields.filter((field) => field.type === 'date'); + const partitionByFields = indexFields.filter((field) => field.aggregatable); return ( <> @@ -53,55 +51,20 @@ export function HistogramIndicatorTypeForm() { - - ( - { - if (selected.length) { - return field.onChange(selected[0].value); - } - - field.onChange(''); - }} - options={createOptionsFromFields(timestampFields)} - selectedOptions={ - !!index && - !!field.value && - timestampFields.some((timestampField) => timestampField.name === field.value) - ? [{ value: field.value, label: field.value }] - : [] - } - singleSelection={{ asPlainText: true }} - /> - )} - /> - + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + isRequired + /> @@ -144,7 +107,11 @@ export function HistogramIndicatorTypeForm() { - +
    @@ -159,13 +126,38 @@ export function HistogramIndicatorTypeForm() { - + - + + {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { + defaultMessage: 'Partition by', + })}{' '} + + + } + placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', { + defaultMessage: 'Select an optional field to partition by', + })} + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + /> diff --git a/x-pack/plugins/observability/public/pages/slo_edit/helpers/create_options.ts b/x-pack/plugins/observability/public/pages/slo_edit/helpers/create_options.ts index c76b881ccc8aa..f090e92026176 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/helpers/create_options.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/helpers/create_options.ts @@ -7,13 +7,22 @@ import { Field } from '../../../hooks/slo/use_fetch_index_pattern_fields'; -interface Option { +export interface Option { label: string; value: string; } -export function createOptionsFromFields(fields: Field[]): Option[] { - return fields +export function createOptionsFromFields( + fields: Field[], + filterFn?: (option: Option) => boolean +): Option[] { + const options = fields .map((field) => ({ label: field.name, value: field.name })) .sort((a, b) => String(a.label).localeCompare(b.label)); + + if (filterFn) { + return options.filter(filterFn); + } + + return options; } diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx deleted file mode 100644 index e132661aa16ad..0000000000000 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx +++ /dev/null @@ -1,53 +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 { EuiConfirmModal } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; - -export interface SloDeleteConfirmationModalProps { - slo: SLOWithSummaryResponse; - onCancel: () => void; - onConfirm: () => void; -} - -export function SloDeleteConfirmationModal({ - slo: { name }, - onCancel, - onConfirm, -}: SloDeleteConfirmationModalProps) { - return ( - - {i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.descriptionText', { - defaultMessage: "You can't recover {name} after deleting.", - values: { name }, - })} - - ); -} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index 72656db6dcdc4..cd4b6e760ea09 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -11,7 +11,6 @@ import { EuiContextMenuPanel, EuiFlexGroup, EuiFlexItem, - EuiLink, EuiPanel, EuiPopover, EuiText, @@ -21,8 +20,10 @@ import { ALL_VALUE, HistoricalSummaryResponse, SLOWithSummaryResponse } from '@k import type { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import { useQueryClient } from '@tanstack/react-query'; import React, { useState } from 'react'; +import { SloDeleteConfirmationModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; import { rulesLocatorID, sloFeatureId } from '../../../../common'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../../../common/constants'; +import { paths } from '../../../../common/locators/paths'; import { sloKeys } from '../../../hooks/slo/query_key_factory'; import { useCapabilities } from '../../../hooks/slo/use_capabilities'; import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; @@ -30,14 +31,12 @@ import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; import type { SloRule } from '../../../hooks/slo/use_fetch_rules_for_slo'; import { useGetFilteredRuleTypes } from '../../../hooks/use_get_filtered_rule_types'; import type { RulesParams } from '../../../locators/rules'; -import { paths } from '../../../../common/locators/paths'; import { useKibana } from '../../../utils/kibana_react'; import { transformCreateSLOFormToCreateSLOInput, transformSloResponseToCreateSloForm, } from '../../slo_edit/helpers/process_slo_form_values'; import { SloBadges } from './badges/slo_badges'; -import { SloDeleteConfirmationModal } from './slo_delete_confirmation_modal'; import { SloSummary } from './slo_summary'; export interface SloListItemProps { @@ -79,15 +78,14 @@ export function SloListItem({ setIsActionsPopoverOpen(!isActionsPopoverOpen); }; + const sloDetailsUrl = basePath.prepend( + paths.observability.sloDetails( + slo.id, + slo.groupBy !== ALL_VALUE && slo.instanceId ? slo.instanceId : undefined + ) + ); const handleViewDetails = () => { - navigateToUrl( - basePath.prepend( - paths.observability.sloDetails( - slo.id, - slo.groupBy !== ALL_VALUE && slo.instanceId ? slo.instanceId : undefined - ) - ) - ); + navigateToUrl(sloDetailsUrl); }; const handleEdit = () => { @@ -142,9 +140,9 @@ export function SloListItem({ {slo.summary ? ( - +
    {slo.name} - + ) : ( {slo.name} )} @@ -178,6 +176,7 @@ export function SloListItem({ anchorPosition="downLeft" button={ ; @@ -296,6 +300,14 @@ export class Plugin icon: 'logoObservability', path: `${OBSERVABILITY_BASE_PATH}/`, order: 200, + isVisible: (capabilities) => { + const obs = capabilities.catalogue[observabilityFeatureId]; + const uptime = capabilities.catalogue.uptime; + const infra = capabilities.catalogue.infra; + const apm = capabilities.catalogue.apm; + + return obs || uptime || infra || apm; + }, }); } diff --git a/x-pack/plugins/observability/public/routes/routes.tsx b/x-pack/plugins/observability/public/routes/routes.tsx index f04b98227e78a..e1559050056f0 100644 --- a/x-pack/plugins/observability/public/routes/routes.tsx +++ b/x-pack/plugins/observability/public/routes/routes.tsx @@ -28,6 +28,7 @@ import { LANDING_PATH, OVERVIEW_PATH, ROOT_PATH, + RULES_LOGS_PATH, RULES_PATH, RULE_DETAIL_PATH, SLOS_PATH, @@ -108,6 +109,13 @@ export const routes = { params: {}, exact: true, }, + [RULES_LOGS_PATH]: { + handler: () => { + return ; + }, + params: {}, + exact: true, + }, [RULE_DETAIL_PATH]: { handler: () => { return ; diff --git a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts index c06bd84e02c77..e7195dfad76ac 100644 --- a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts @@ -17,8 +17,8 @@ import { SLO_BURN_RATE_RULE_TYPE_ID, } from '../../common/constants'; import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation'; -import { validateMetricThreshold } from '../components/threshold/components/validation'; -import { formatReason } from '../components/threshold/rule_data_formatters'; +import { validateMetricThreshold } from '../components/custom_threshold/components/validation'; +import { formatReason } from '../components/custom_threshold/rule_data_formatters'; const sloBurnRateDefaultActionMessage = i18n.translate( 'xpack.observability.slo.rules.burnRate.defaultActionMessage', @@ -54,7 +54,7 @@ const sloBurnRateDefaultRecoveryMessage = i18n.translate( ); const thresholdDefaultActionMessage = i18n.translate( - 'xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage', + 'xpack.observability.customThreshold.rule.alerting.threshold.defaultActionMessage', { defaultMessage: `\\{\\{context.reason\\}\\} @@ -65,7 +65,7 @@ const thresholdDefaultActionMessage = i18n.translate( } ); const thresholdDefaultRecoveryMessage = i18n.translate( - 'xpack.observability.threshold.rule.alerting.threshold.defaultRecoveryMessage', + 'xpack.observability.customThreshold.rule.alerting.threshold.defaultRecoveryMessage', { defaultMessage: `\\{\\{rule.name\\}\\} has recovered. @@ -106,7 +106,7 @@ export const registerObservabilityRuleTypes = ( observabilityRuleTypeRegistry.register({ id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, description: i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.alertDescription', + 'xpack.observability.customThreshold.rule.alertFlyout.alertDescription', { defaultMessage: 'Alert when any Observability data type reaches or exceeds a given value.', @@ -116,14 +116,16 @@ export const registerObservabilityRuleTypes = ( documentationUrl(docLinks) { return `${docLinks.links.observability.threshold}`; }, - ruleParamsExpression: lazy(() => import('../components/threshold/threshold_rule_expression')), + ruleParamsExpression: lazy( + () => import('../components/custom_threshold/custom_threshold_rule_expression') + ), validate: validateMetricThreshold, defaultActionMessage: thresholdDefaultActionMessage, defaultRecoveryMessage: thresholdDefaultRecoveryMessage, requiresAppContext: false, format: formatReason, alertDetailsAppSection: lazy( - () => import('../components/threshold/components/alert_details_app_section') + () => import('../components/custom_threshold/components/alert_details_app_section') ), }); } diff --git a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts index 14305e4f7ab91..120de8934fcff 100644 --- a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts @@ -66,6 +66,10 @@ export interface InfraLogsHasDataResponse { indices: string; } +interface UniversalProfilingHasDataResponse { + hasData: boolean; +} + export type FetchData = ( fetchDataParams: FetchDataParams ) => Promise; @@ -150,12 +154,15 @@ export interface UxFetchDataResponse extends FetchDataResponse { coreWebVitals: UXMetrics; } +export type UniversalProfilingDataResponse = FetchDataResponse; + export interface ObservabilityFetchDataResponse { apm: ApmFetchDataResponse; infra_metrics: MetricsFetchDataResponse; infra_logs: LogsFetchDataResponse; uptime: UptimeFetchDataResponse; ux: UxFetchDataResponse; + universal_profiling: UniversalProfilingDataResponse; } export interface ObservabilityHasDataResponse { @@ -164,4 +171,5 @@ export interface ObservabilityHasDataResponse { infra_logs: InfraLogsHasDataResponse; uptime: SyntheticsHasDataResponse; ux: UXHasDataResponse; + universal_profiling: UniversalProfilingHasDataResponse; } diff --git a/x-pack/plugins/observability/public/utils/metrics_explorer.ts b/x-pack/plugins/observability/public/utils/metrics_explorer.ts index 3994a1c4ee69b..136df77131811 100644 --- a/x-pack/plugins/observability/public/utils/metrics_explorer.ts +++ b/x-pack/plugins/observability/public/utils/metrics_explorer.ts @@ -8,7 +8,7 @@ import { MetricsExplorerResponse, MetricsExplorerSeries, -} from '../../common/threshold_rule/metrics_explorer'; +} from '../../common/custom_threshold_rule/metrics_explorer'; import { MetricsExplorerChartOptions, MetricsExplorerChartType, @@ -16,7 +16,7 @@ import { MetricsExplorerTimeOptions, MetricsExplorerTimestampsRT, MetricsExplorerYAxisMode, -} from '../components/threshold/hooks/use_metrics_explorer_options'; +} from '../components/custom_threshold/hooks/use_metrics_explorer_options'; export const options: MetricsExplorerOptions = { limit: 3, diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index d5d5604001aa9..e0444fe34f861 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -50,7 +50,7 @@ const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), }), }), - thresholdRule: schema.object({ + customThresholdRule: schema.object({ groupByPageSize: schema.number({ defaultValue: 10_000 }), }), enabled: schema.boolean({ defaultValue: true }), diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts new file mode 100644 index 0000000000000..4a4640e6e1f2f --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -0,0 +1,2017 @@ +/* + * Copyright 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 { AlertInstanceState as AlertState } from '@kbn/alerting-plugin/server'; +import { + AlertInstanceMock, + RuleExecutorServicesMock, + alertsMock, +} from '@kbn/alerting-plugin/server/mocks'; +import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server'; +import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; +import { + createMetricThresholdExecutor, + FIRED_ACTIONS, + MetricThresholdAlertContext, + NO_DATA_ACTIONS, +} from './custom_threshold_executor'; +import { Evaluation } from './lib/evaluate_rule'; +import type { LogMeta, Logger } from '@kbn/logging'; +import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; +import { + Aggregators, + Comparator, + CountMetricExpressionParams, + NonCountMetricExpressionParams, +} from '../../../../common/custom_threshold_rule/types'; + +jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); + +interface AlertTestInstance { + instance: AlertInstanceMock; + actionQueue: any[]; + state: any; +} + +const persistAlertInstances = false; + +type TestRuleState = Record & { + aRuleStateKey: string; + groups: string[]; + groupBy?: string | string[]; +}; + +const initialRuleState: TestRuleState = { + aRuleStateKey: 'INITIAL_RULE_STATE_VALUE', + groups: [], +}; + +const fakeLogger = (msg: string, meta?: Meta) => {}; + +const logger = { + trace: fakeLogger, + debug: fakeLogger, + info: fakeLogger, + warn: fakeLogger, + error: fakeLogger, + fatal: fakeLogger, + log: () => void 0, + get: () => logger, +} as unknown as Logger; + +const STARTED_AT_MOCK_DATE = new Date(); + +const mockOptions = { + executionId: '', + startedAt: STARTED_AT_MOCK_DATE, + previousStartedAt: null, + state: { + wrapped: initialRuleState, + trackedAlerts: { + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + started: '2020-01-01T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + started: '2020-01-02T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + }, + trackedAlertsRecovered: {}, + }, + spaceId: '', + rule: { + id: '', + name: '', + tags: [], + consumer: '', + enabled: true, + schedule: { + interval: '1h', + }, + actions: [], + createdBy: null, + updatedBy: null, + createdAt: new Date(), + updatedAt: new Date(), + throttle: null, + notifyWhen: null, + producer: '', + revision: 0, + ruleTypeId: '', + ruleTypeName: '', + muteAll: false, + snoozeSchedule: [], + }, + logger, + flappingSettings: DEFAULT_FLAPPING_SETTINGS, +}; + +const setEvaluationResults = (response: Array>) => { + jest.requireMock('./lib/evaluate_rule').evaluateRule.mockImplementation(() => response); +}; + +// FAILING: https://github.com/elastic/kibana/issues/155534 +describe.skip('The metric threshold alert type', () => { + describe('querying the entire infrastructure', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + }, + ], + }, + }); + const setResults = ( + comparator: Comparator, + threshold: number[], + shouldFire: boolean = false, + shouldWarn: boolean = false, + isNoData: boolean = false + ) => + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator, + threshold, + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire, + shouldWarn, + isNoData, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + test('alerts as expected with the > comparator', async () => { + setResults(Comparator.GT, [0.75], true); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.GT, [1.5], false); + await execute(Comparator.GT, [1.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the < comparator', async () => { + setResults(Comparator.LT, [1.5], true); + await execute(Comparator.LT, [1.5]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.LT, [0.75], false); + await execute(Comparator.LT, [0.75]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the >= comparator', async () => { + setResults(Comparator.GT_OR_EQ, [0.75], true); + await execute(Comparator.GT_OR_EQ, [0.75]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.GT_OR_EQ, [1.0], true); + await execute(Comparator.GT_OR_EQ, [1.0]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.GT_OR_EQ, [1.5], false); + await execute(Comparator.GT_OR_EQ, [1.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the <= comparator', async () => { + setResults(Comparator.LT_OR_EQ, [1.5], true); + await execute(Comparator.LT_OR_EQ, [1.5]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.LT_OR_EQ, [1.0], true); + await execute(Comparator.LT_OR_EQ, [1.0]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.LT_OR_EQ, [0.75], false); + await execute(Comparator.LT_OR_EQ, [0.75]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the between comparator', async () => { + setResults(Comparator.BETWEEN, [0, 1.5], true); + await execute(Comparator.BETWEEN, [0, 1.5]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.BETWEEN, [0, 0.75], false); + await execute(Comparator.BETWEEN, [0, 0.75]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts as expected with the outside range comparator', async () => { + setResults(Comparator.OUTSIDE_RANGE, [0, 0.75], true); + await execute(Comparator.OUTSIDE_RANGE, [0, 0.75]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setResults(Comparator.OUTSIDE_RANGE, [0, 1.5], false); + await execute(Comparator.OUTSIDE_RANGE, [0, 1.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('reports expected values to the action context', async () => { + setResults(Comparator.GT, [0.75], true); + await execute(Comparator.GT, [0.75]); + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toContain('is 1'); + expect(action.reason).toContain('Alert when > 0.75'); + expect(action.reason).toContain('test.metric.1'); + expect(action.reason).toContain('in the last 1 min'); + }); + }); + + describe('querying with a groupBy parameter', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string[] = ['something'], + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + }); + const instanceIdA = 'a'; + const instanceIdB = 'b'; + test('sends an alert when all groups pass the threshold', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + }); + test('sends an alert when only some groups pass the threshold', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1.5], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1.5], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.LT, [1.5]); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + }); + test('sends no alert when no groups pass the threshold', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [5], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [5], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.GT, [5]); + expect(mostRecentAction(instanceIdA)).toBe(undefined); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + }); + test('reports group values to the action context', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.group).toBe('a'); + expect(mostRecentAction(instanceIdB).action.group).toBe('b'); + }); + test('persists previous groups that go missing, until the groupBy param changes', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult1 } = await execute( + Comparator.GT, + [0.75], + ['something'], + 'test.metric.2' + ); + expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([])); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult2 } = await execute( + Comparator.GT, + [0.75], + ['something'], + 'test.metric.1', + stateResult1 + ); + expect(stateResult2.missingGroups).toEqual( + expect.arrayContaining([{ key: 'c', bucketKey: { groupBy0: 'c' } }]) + ); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const { state: stateResult3 } = await execute( + Comparator.GT, + [0.75], + ['something', 'something-else'], + 'test.metric.1', + stateResult2 + ); + expect(stateResult3.missingGroups).toEqual(expect.arrayContaining([])); + }); + + const executeWithFilter = ( + comparator: Comparator, + threshold: number[], + filterQuery: string, + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy: ['something'], + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + filterQuery, + }, + state: state ?? mockOptions.state.wrapped, + }); + test('persists previous groups that go missing, until the filterQuery param changes', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult1 } = await executeWithFilter( + Comparator.GT, + [0.75], + JSON.stringify({ query: 'q' }), + 'test.metric.2' + ); + expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([])); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const { state: stateResult2 } = await executeWithFilter( + Comparator.GT, + [0.75], + JSON.stringify({ query: 'q' }), + 'test.metric.1', + stateResult1 + ); + expect(stateResult2.missingGroups).toEqual( + expect.arrayContaining([{ key: 'c', bucketKey: { groupBy0: 'c' } }]) + ); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const { state: stateResult3 } = await executeWithFilter( + Comparator.GT, + [0.75], + JSON.stringify({ query: 'different' }), + 'test.metric.1', + stateResult2 + ); + expect(stateResult3.groups).toEqual(expect.arrayContaining([])); + }); + }); + + describe('querying with a groupBy parameter host.name and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string[] = ['host.name'], + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + const instanceIdA = 'host-01'; + const instanceIdB = 'host-02'; + + test('rule tags and source tags are combined in alert context', async () => { + setEvaluationResults([ + { + 'host-01': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-01' }, + context: { + tags: ['host-01_tag1', 'host-01_tag2'], + }, + }, + 'host-02': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-02' }, + context: { + tags: ['host-02_tag1', 'host-02_tag2'], + }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ + 'host-01_tag1', + 'host-01_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ + 'host-02_tag1', + 'host-02_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + }); + }); + + describe('querying without a groupBy parameter and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string = '', + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + + test('rule tags are added in alert context', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + + const instanceID = '*'; + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceID).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + }); + }); + + describe('querying with multiple criteria', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + thresholdA: number[], + thresholdB: number[], + groupBy: string = '', + sourceId: string = 'default' + ) => + executor({ + ...mockOptions, + services, + params: { + sourceId, + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold: thresholdA, + }, + { + ...baseNonCountCriterion, + comparator, + threshold: thresholdB, + metric: 'test.metric.2', + }, + ], + }, + }); + test('sends an alert when all criteria cross the threshold', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + const instanceID = '*'; + await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + }); + test('sends no alert when some, but not all, criteria cross the threshold', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + {}, + ]); + const instanceID = '*'; + await execute(Comparator.LT_OR_EQ, [1.0], [2.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + test('alerts only on groups that meet all criteria when querying with a groupBy parameter', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const instanceIdA = 'a'; + const instanceIdB = 'b'; + await execute(Comparator.GT_OR_EQ, [1.0], [3.0], 'something'); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + }); + test('sends all criteria to the action context', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [1.0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT_OR_EQ, + threshold: [3.0], + metric: 'test.metric.2', + currentValue: 3.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + const instanceID = '*'; + await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); + const { action } = mostRecentAction(instanceID); + const reasons = action.reason.split('\n'); + expect(reasons.length).toBe(2); + expect(reasons[0]).toContain('test.metric.1'); + expect(reasons[1]).toContain('test.metric.2'); + expect(reasons[0]).toContain('is 1'); + expect(reasons[1]).toContain('is 3'); + expect(reasons[0]).toContain('Alert when >= 1'); + expect(reasons[1]).toContain('Alert when >= 3'); + expect(reasons[0]).toContain('in the last 1 min'); + expect(reasons[1]).toContain('in the last 1 min'); + }); + }); + describe('querying with the count aggregator', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseCountCriterion, + comparator, + threshold, + } as CountMetricExpressionParams, + ], + }, + }); + test('alerts based on the doc_count value instead of the aggregatedValue', async () => { + setEvaluationResults([ + { + '*': { + ...baseCountCriterion, + comparator: Comparator.GT, + threshold: [0.9], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + }, + ]); + await execute(Comparator.GT, [0.9]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setEvaluationResults([ + { + '*': { + ...baseCountCriterion, + comparator: Comparator.LT, + threshold: [0.5], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + }, + ]); + await execute(Comparator.LT, [0.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + describe('with a groupBy parameter', () => { + const executeGroupBy = ( + comparator: Comparator, + threshold: number[], + sourceId: string = 'default', + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + sourceId, + groupBy: 'something', + criteria: [ + { + ...baseCountCriterion, + comparator, + threshold, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + }); + const instanceIdA = 'a'; + const instanceIdB = 'b'; + + test('successfully detects and alerts on a document count of 0', async () => { + setEvaluationResults([ + { + a: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + const resultState = await executeGroupBy(Comparator.LT_OR_EQ, [0]); + expect(mostRecentAction(instanceIdA)).toBe(undefined); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + setEvaluationResults([ + { + a: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseCountCriterion, + comparator: Comparator.LT_OR_EQ, + threshold: [0], + metric: 'count', + currentValue: 0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await executeGroupBy(Comparator.LT_OR_EQ, [0], 'empty-response', resultState); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + }); + }); + }); + describe('querying with the p99 aggregator', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + aggType: Aggregators.P99, + metric: 'test.metric.2', + }, + ], + }, + }); + test('alerts based on the p99 values', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [1], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.GT, [1]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.LT, [1]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + }); + describe('querying with the p95 aggregator', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + aggType: Aggregators.P95, + metric: 'test.metric.1', + }, + ], + }, + }); + test('alerts based on the p95 values', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.25], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.GT, [0.25]); + expect(mostRecentAction(instanceID)).toBeAlertAction(); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [0.95], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(Comparator.LT, [0.95]); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + }); + describe("querying a metric that hasn't reported data", () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (alertOnNoData: boolean, sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [1], + metric: 'test.metric.3', + }, + ], + alertOnNoData, + }, + }); + test('sends a No Data alert when configured to do so', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(true); + const recentAction = mostRecentAction(instanceID); + expect(recentAction.action.reason).toEqual('test.metric.3 reported no data in the last 1m'); + expect(recentAction).toBeNoDataAction(); + }); + test('does not send a No Data alert when not configured to do so', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + await execute(false); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + }); + + describe('alerts with NO_DATA where one condtion is an aggregation and the other is a document count', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (alertOnNoData: boolean, sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [1], + metric: 'test.metric.3', + }, + { + ...baseCountCriterion, + comparator: Comparator.GT, + threshold: [30], + }, + ], + alertOnNoData, + }, + }); + test('sends a No Data alert when configured to do so', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.3', + currentValue: null, + timestamp: STARTED_AT_MOCK_DATE.toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + {}, + ]); + await execute(true); + const recentAction = mostRecentAction(instanceID); + expect(recentAction.action).toEqual({ + alertDetailsUrl: '', + alertState: 'NO DATA', + group: '*', + groupByKeys: undefined, + metric: { condition0: 'test.metric.3', condition1: 'count' }, + reason: 'test.metric.3 reported no data in the last 1m', + threshold: { condition0: ['1'], condition1: [30] }, + timestamp: STARTED_AT_MOCK_DATE.toISOString(), + value: { condition0: '[NO DATA]', condition1: 0 }, + viewInAppUrl: 'http://localhost:5601/app/metrics/explorer', + tags: [], + }); + expect(recentAction).toBeNoDataAction(); + }); + }); + + describe('querying a groupBy alert that starts reporting no data, and then later reports data', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const instanceIdA = 'a'; + const instanceIdB = 'b'; + const instanceIdC = 'c'; + const execute = (metric: string, alertOnGroupDisappear: boolean = true, state?: any) => + executor({ + ...mockOptions, + services, + params: { + groupBy: 'something', + sourceId: 'default', + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric, + }, + ], + alertOnNoData: true, + alertOnGroupDisappear, + }, + state: state ?? mockOptions.state.wrapped, + }); + + const executeEmptyResponse = (...args: [boolean?, any?]) => execute('test.metric.3', ...args); + const execute3GroupsABCResponse = (...args: [boolean?, any?]) => + execute('test.metric.2', ...args); + const execute2GroupsABResponse = (...args: [boolean?, any?]) => + execute('test.metric.1', ...args); + + // Store state between tests. Jest won't preserve reassigning a let so use an array instead. + const interTestStateStorage: any[] = []; + + test('first sends a No Data alert with the * group, but then reports groups when data is available', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + let resultState = await executeEmptyResponse(); + expect(mostRecentAction(instanceID)).toBeNoDataAction(); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + resultState = await executeEmptyResponse(true, resultState); + expect(mostRecentAction(instanceID)).toBeNoDataAction(); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + resultState = await execute2GroupsABResponse(true, resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + interTestStateStorage.push(resultState); // Hand off resultState to the next test + }); + test('sends No Data alerts for the previously detected groups when they stop reporting data, but not the * group', async () => { + // Pop a previous execution result instead of defining it manually + // The type signature of alert executor states are complex + const resultState = interTestStateStorage.pop(); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await executeEmptyResponse(true, resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeNoDataAction(); + expect(mostRecentAction(instanceIdB)).toBeNoDataAction(); + }); + test('does not send individual No Data alerts when groups disappear if alertOnGroupDisappear is disabled', async () => { + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.2', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + c: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.2', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'c' }, + }, + }, + ]); + const resultState = await execute3GroupsABCResponse(false); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + expect(mostRecentAction(instanceIdC)).toBeAlertAction(); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await execute2GroupsABResponse(false, resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + expect(mostRecentAction(instanceIdC)).toBe(undefined); + }); + + describe('if alertOnNoData is disabled but alertOnGroupDisappear is enabled', () => { + const executeWeirdNoDataConfig = (metric: string, state?: any) => + executor({ + ...mockOptions, + services, + params: { + groupBy: 'something', + sourceId: 'default', + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric, + }, + ], + alertOnNoData: false, + alertOnGroupDisappear: true, + }, + state: state ?? mockOptions.state.wrapped, + }); + + const executeWeirdEmptyResponse = (...args: [any?]) => + executeWeirdNoDataConfig('test.metric.3', ...args); + const executeWeird2GroupsABResponse = (...args: [any?]) => + executeWeirdNoDataConfig('test.metric.1', ...args); + + test('does not send a No Data alert with the * group, but then reports groups when data is available', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + let resultState = await executeWeirdEmptyResponse(); + expect(mostRecentAction(instanceID)).toBe(undefined); + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + resultState = await executeWeirdEmptyResponse(resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 1, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + resultState = await executeWeird2GroupsABResponse(resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeAlertAction(); + expect(mostRecentAction(instanceIdB)).toBeAlertAction(); + interTestStateStorage.push(resultState); // Hand off resultState to the next test + }); + test('sends No Data alerts for the previously detected groups when they stop reporting data, but not the * group', async () => { + const resultState = interTestStateStorage.pop(); // Import the resultState from the previous test + setEvaluationResults([ + { + a: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'a' }, + }, + b: { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0], + metric: 'test.metric.3', + currentValue: null, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: 'b' }, + }, + }, + ]); + await executeWeirdEmptyResponse(resultState); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(mostRecentAction(instanceIdA)).toBeNoDataAction(); + expect(mostRecentAction(instanceIdB)).toBeNoDataAction(); + }); + }); + }); + + describe('attempting to use a malformed filterQuery', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = () => + executor({ + ...mockOptions, + services, + params: { + criteria: [ + { + ...baseNonCountCriterion, + }, + ], + sourceId: 'default', + filterQuery: + 'host.name:(look.there.is.no.space.after.these.parentheses)and uh.oh: "wow that is bad"', + }, + }); + test('reports an error', async () => { + await execute(); + expect(mostRecentAction(instanceID)).toBeErrorAction(); + }); + }); + + describe('querying the entire infrastructure with warning threshold', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + + const execute = () => + executor({ + ...mockOptions, + services, + params: { + sourceId: 'default', + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [9999], + }, + ], + }, + }); + + const setResults = ({ + comparator = Comparator.GT, + threshold = [9999], + warningComparator = Comparator.GT, + warningThreshold = [2.49], + metric = 'test.metric.1', + currentValue = 7.59, + shouldWarn = false, + }) => + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator, + threshold, + warningComparator, + warningThreshold, + metric, + currentValue, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + + test('warns as expected with the > comparator', async () => { + setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); + await execute(); + expect(mostRecentAction(instanceID)).toBeWarnAction(); + + setResults({ warningThreshold: [2.49], currentValue: 1.23, shouldWarn: false }); + await execute(); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + + test('reports expected warning values to the action context', async () => { + setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); + await execute(); + + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toBe('test.metric.1 is 2.5 in the last 1 min. Alert when > 2.49.'); + }); + + test('reports expected warning values to the action context for percentage metric', async () => { + setResults({ + warningThreshold: [0.81], + currentValue: 0.82, + shouldWarn: true, + metric: 'system.cpu.user.pct', + }); + await execute(); + + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toBe('system.cpu.user.pct is 82% in the last 1 min. Alert when > 81%.'); + }); + }); +}); + +const mockLibs: any = { + threshold_rule: { + group_by_page_size: 10000, + }, + basePath: { + publicBaseUrl: 'http://localhost:5601', + prepend: (path: string) => path, + }, + logger, + config: { + customThresholdRule: { + groupByPageSize: 10_000, + }, + }, +}; + +const executor = createMetricThresholdExecutor(mockLibs); + +const alertsServices = alertsMock.createRuleExecutorServices(); +const services: RuleExecutorServicesMock & + LifecycleAlertServices = { + ...alertsServices, + ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), +}; +services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => { + if (sourceId === 'alternate') + return { + id: 'alternate', + attributes: { metricAlias: 'alternatebeat-*' }, + type, + references: [], + }; + if (sourceId === 'empty-response') + return { + id: 'empty', + attributes: { metricAlias: 'empty-response' }, + type, + references: [], + }; + return { id: 'default', attributes: { metricAlias: 'metricbeat-*' }, type, references: [] }; +}); + +const alertInstances = new Map(); +services.alertFactory.create.mockImplementation((instanceID: string) => { + const newAlertInstance: AlertTestInstance = { + instance: alertsMock.createAlertFactory.create(), + actionQueue: [], + state: {}, + }; + const alertInstance: AlertTestInstance = persistAlertInstances + ? alertInstances.get(instanceID) || newAlertInstance + : newAlertInstance; + alertInstances.set(instanceID, alertInstance); + + alertInstance.instance.replaceState.mockImplementation((newState: any) => { + alertInstance.state = newState; + return alertInstance.instance; + }); + (alertInstance.instance.scheduleActions as jest.Mock).mockImplementation( + (id: string, action: any) => { + alertInstance.actionQueue.push({ id, action }); + return alertInstance.instance; + } + ); + return alertInstance.instance; +}); + +function mostRecentAction(id: string) { + const instance = alertInstances.get(id); + if (!instance) return undefined; + return instance.actionQueue.pop(); +} + +function clearInstances() { + alertInstances.clear(); +} + +interface Action { + id: string; + action: { alertState: string }; +} + +expect.extend({ + toBeAlertAction(action?: Action) { + const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ALERT'; + const message = () => `expected ${action} to be an ALERT action`; + return { + message, + pass, + }; + }, + toBeNoDataAction(action?: Action) { + const pass = action?.id === NO_DATA_ACTIONS.id && action?.action.alertState === 'NO DATA'; + const message = () => `expected ${action} to be a NO DATA action`; + return { + message, + pass, + }; + }, + toBeErrorAction(action?: Action) { + const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ERROR'; + const message = () => `expected ${action} to be an ERROR action`; + return { + message, + pass, + }; + }, +}); + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Matchers { + toBeAlertAction(action?: Action): R; + toBeWarnAction(action?: Action): R; + toBeNoDataAction(action?: Action): R; + toBeErrorAction(action?: Action): R; + } + } +} + +const baseNonCountCriterion = { + aggType: Aggregators.AVERAGE, + metric: 'test.metric.1', + timeSize: 1, + timeUnit: 'm', + threshold: [0], + comparator: Comparator.GT, +} as NonCountMetricExpressionParams; + +const baseCountCriterion = { + aggType: Aggregators.COUNT, + timeSize: 1, + timeUnit: 'm', + threshold: [0], + comparator: Comparator.GT, +} as CountMetricExpressionParams; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts new file mode 100644 index 0000000000000..bff471aaea2ec --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -0,0 +1,426 @@ +/* + * Copyright 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 { isEqual } from 'lodash'; +import { TypeOf } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { ALERT_ACTION_GROUP, ALERT_EVALUATION_VALUES, ALERT_REASON } from '@kbn/rule-data-utils'; +import { LocatorPublic } from '@kbn/share-plugin/common'; +import { + ActionGroupIdsOf, + AlertInstanceState as AlertState, + RecoveredActionGroup, +} from '@kbn/alerting-plugin/common'; +import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server'; +import { IBasePath, Logger } from '@kbn/core/server'; +import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server'; +import { AlertsLocatorParams, getAlertUrl, TimeUnitChar } from '../../../../common'; +import { createFormatter } from '../../../../common/custom_threshold_rule/formatters'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; +import { ObservabilityConfig } from '../../..'; +import { AlertStates, searchConfigurationSchema } from './types'; + +import { + buildFiredAlertReason, + buildNoDataAlertReason, + // buildRecoveredAlertReason, +} from './messages'; +import { + createScopedLogger, + AdditionalContext, + getContextForRecoveredAlerts, + UNGROUPED_FACTORY_KEY, + hasAdditionalContext, + validGroupByForContext, + flattenAdditionalContext, + getGroupByObject, +} from './utils'; + +import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; +import { MissingGroupsRecord } from './lib/check_missing_group'; +import { convertStringsToMissingGroupsRecord } from './lib/convert_strings_to_missing_groups_record'; + +export type SearchConfigurationType = TypeOf; +export type MetricThresholdRuleParams = Record; +export type MetricThresholdRuleTypeState = RuleTypeState & { + lastRunTimestamp?: number; + missingGroups?: Array; + groupBy?: string | string[]; + searchConfiguration?: SearchConfigurationType; +}; +export type MetricThresholdAlertState = AlertState; // no specific instance state used + +export interface MetricThresholdAlertContext extends Record { + alertDetailsUrl: string; + groupings?: object; + reason?: string; + timestamp: string; // ISO string + value?: Array | null; +} + +export const FIRED_ACTIONS_ID = 'custom_threshold.fired'; +export const NO_DATA_ACTIONS_ID = 'custom_threshold.nodata'; + +type MetricThresholdActionGroup = + | typeof FIRED_ACTIONS_ID + | typeof NO_DATA_ACTIONS_ID + | typeof RecoveredActionGroup.id; + +type MetricThresholdAllowedActionGroups = ActionGroupIdsOf< + typeof FIRED_ACTIONS | typeof NO_DATA_ACTIONS +>; + +type MetricThresholdAlert = Alert< + MetricThresholdAlertState, + MetricThresholdAlertContext, + MetricThresholdAllowedActionGroups +>; + +type MetricThresholdAlertFactory = ( + id: string, + reason: string, + actionGroup: MetricThresholdActionGroup, + additionalContext?: AdditionalContext | null, + evaluationValues?: Array +) => MetricThresholdAlert; + +export const createMetricThresholdExecutor = ({ + alertsLocator, + basePath, + logger, + config, +}: { + basePath: IBasePath; + logger: Logger; + config: ObservabilityConfig; + alertsLocator?: LocatorPublic; +}): LifecycleRuleExecutor< + MetricThresholdRuleParams, + MetricThresholdRuleTypeState, + MetricThresholdAlertState, + MetricThresholdAlertContext, + MetricThresholdAllowedActionGroups +> => + async function (options) { + const startTime = Date.now(); + + const { + services, + params, + state, + startedAt, + executionId, + spaceId, + rule: { id: ruleId }, + } = options; + + const { criteria } = params; + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const thresholdLogger = createScopedLogger(logger, 'thresholdRule', { + alertId: ruleId, + executionId, + }); + + // TODO: check if we need to use "savedObjectsClient"=> https://github.com/elastic/kibana/issues/159340 + const { + alertWithLifecycle, + getAlertUuid, + getAlertByAlertUuid, + getAlertStartedDate, + searchSourceClient, + } = services; + + const alertFactory: MetricThresholdAlertFactory = ( + id, + reason, + actionGroup, + additionalContext, + evaluationValues + ) => + alertWithLifecycle({ + id, + fields: { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + [ALERT_EVALUATION_VALUES]: evaluationValues, + ...flattenAdditionalContext(additionalContext), + }, + }); + + const { alertOnNoData, alertOnGroupDisappear: _alertOnGroupDisappear } = params as { + alertOnNoData: boolean; + alertOnGroupDisappear: boolean | undefined; + }; + + // For backwards-compatibility, interpret undefined alertOnGroupDisappear as true + const alertOnGroupDisappear = _alertOnGroupDisappear !== false; + const compositeSize = config.customThresholdRule.groupByPageSize; + const queryIsSame = isEqual( + state.searchConfiguration?.query.query, + params.searchConfiguration.query.query + ); + const groupByIsSame = isEqual(state.groupBy, params.groupBy); + const previousMissingGroups = + alertOnGroupDisappear && queryIsSame && groupByIsSame && state.missingGroups + ? state.missingGroups + : []; + + const initialSearchSource = await searchSourceClient.create(params.searchConfiguration!); + const dataView = initialSearchSource.getField('index')!.getIndexPattern(); + const timeFieldName = initialSearchSource.getField('index')?.timeFieldName; + if (!dataView) { + throw new Error('No matched data view'); + } else if (!timeFieldName) { + throw new Error('The selected data view does not have a timestamp field'); + } + + const alertResults = await evaluateRule( + services.scopedClusterClient.asCurrentUser, + params as EvaluatedRuleParams, + dataView, + timeFieldName, + compositeSize, + alertOnGroupDisappear, + logger, + state.lastRunTimestamp, + { end: startedAt.valueOf() }, + convertStringsToMissingGroupsRecord(previousMissingGroups) + ); + + const resultGroupSet = new Set(); + for (const resultSet of alertResults) { + for (const group of Object.keys(resultSet)) { + resultGroupSet.add(group); + } + } + + const groupByKeysObjectMapping = getGroupByObject(params.groupBy, resultGroupSet); + const groups = [...resultGroupSet]; + const nextMissingGroups = new Set(); + const hasGroups = !isEqual(groups, [UNGROUPED_FACTORY_KEY]); + let scheduledActionsCount = 0; + + // The key of `groups` is the alert instance ID. + for (const group of groups) { + // AND logic; all criteria must be across the threshold + const shouldAlertFire = alertResults.every((result) => result[group]?.shouldFire); + // AND logic; because we need to evaluate all criteria, if one of them reports no data then the + // whole alert is in a No Data/Error state + const isNoData = alertResults.some((result) => result[group]?.isNoData); + + if (isNoData && group !== UNGROUPED_FACTORY_KEY) { + nextMissingGroups.add({ key: group, bucketKey: alertResults[0][group].bucketKey }); + } + + const nextState = isNoData + ? AlertStates.NO_DATA + : shouldAlertFire + ? AlertStates.ALERT + : AlertStates.OK; + + let reason; + if (nextState === AlertStates.ALERT) { + reason = alertResults + .map((result) => + buildFiredAlertReason({ + ...formatAlertResult(result[group]), + group, + }) + ) + .join('\n'); + } + + /* NO DATA STATE HANDLING + * + * - `alertOnNoData` does not indicate IF the alert's next state is No Data, but whether or not the user WANTS TO BE ALERTED + * if the state were No Data. + * - `alertOnGroupDisappear`, on the other hand, determines whether or not it's possible to return a No Data state + * when a group disappears. + * + * This means we need to handle the possibility that `alertOnNoData` is false, but `alertOnGroupDisappear` is true + * + * nextState === NO_DATA would be true on both { '*': No Data } or, e.g. { 'a': No Data, 'b': OK, 'c': OK }, but if the user + * has for some reason disabled `alertOnNoData` and left `alertOnGroupDisappear` enabled, they would only care about the latter + * possibility. In this case, use hasGroups to determine whether to alert on a potential No Data state + * + * If `alertOnNoData` is true but `alertOnGroupDisappear` is false, we don't need to worry about the {a, b, c} possibility. + * At this point in the function, a false `alertOnGroupDisappear` would already have prevented group 'a' from being evaluated at all. + */ + if (alertOnNoData || (alertOnGroupDisappear && hasGroups)) { + // In the previous line we've determined if the user is interested in No Data states, so only now do we actually + // check to see if a No Data state has occurred + if (nextState === AlertStates.NO_DATA) { + reason = alertResults + .filter((result) => result[group]?.isNoData) + .map((result) => buildNoDataAlertReason({ ...result[group], group })) + .join('\n'); + } + } + + if (reason) { + const timestamp = startedAt.toISOString(); + const actionGroupId: MetricThresholdActionGroup = + nextState === AlertStates.OK + ? RecoveredActionGroup.id + : nextState === AlertStates.NO_DATA + ? NO_DATA_ACTIONS_ID + : FIRED_ACTIONS_ID; + + const additionalContext = hasAdditionalContext(params.groupBy, validGroupByForContext) + ? alertResults && alertResults.length > 0 + ? alertResults[0][group].context ?? {} + : {} + : {}; + + additionalContext.tags = Array.from( + new Set([...(additionalContext.tags ?? []), ...options.rule.tags]) + ); + + const evaluationValues = alertResults.reduce((acc: Array, result) => { + if (result[group]) { + acc.push(result[group].currentValue); + } + return acc; + }, []); + + const alert = alertFactory( + `${group}`, + reason, + actionGroupId, + additionalContext, + evaluationValues + ); + const alertUuid = getAlertUuid(group); + const indexedStartedAt = getAlertStartedDate(group) ?? startedAt.toISOString(); + scheduledActionsCount++; + + alert.scheduleActions(actionGroupId, { + alertDetailsUrl: await getAlertUrl( + alertUuid, + spaceId, + indexedStartedAt, + alertsLocator, + basePath.publicBaseUrl + ), + groupings: groupByKeysObjectMapping[group], + reason, + timestamp, + value: alertResults.map((result, index) => { + const evaluation = result[group]; + if (!evaluation && criteria[index].aggType === 'count') { + return 0; + } else if (!evaluation) { + return null; + } + return formatAlertResult(evaluation).currentValue; + }), + ...additionalContext, + }); + } + } + const { getRecoveredAlerts } = services.alertFactory.done(); + const recoveredAlerts = getRecoveredAlerts(); + + const groupByKeysObjectForRecovered = getGroupByObject( + params.groupBy, + new Set(recoveredAlerts.map((recoveredAlert) => recoveredAlert.getId())) + ); + + for (const alert of recoveredAlerts) { + const recoveredAlertId = alert.getId(); + const alertUuid = getAlertUuid(recoveredAlertId); + const timestamp = startedAt.toISOString(); + const indexedStartedAt = getAlertStartedDate(recoveredAlertId) ?? timestamp; + + const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; + const additionalContext = getContextForRecoveredAlerts(alertHits); + + alert.setContext({ + alertDetailsUrl: await getAlertUrl( + alertUuid, + spaceId, + indexedStartedAt, + alertsLocator, + basePath.publicBaseUrl + ), + groupings: groupByKeysObjectForRecovered[recoveredAlertId], + timestamp: startedAt.toISOString(), + ...additionalContext, + }); + } + + const stopTime = Date.now(); + thresholdLogger.debug( + `Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms` + ); + return { + state: { + lastRunTimestamp: startedAt.valueOf(), + missingGroups: [...nextMissingGroups], + groupBy: params.groupBy, + searchConfiguration: params.searchConfiguration, + }, + }; + }; + +export const FIRED_ACTIONS = { + id: 'custom_threshold.fired', + name: i18n.translate('xpack.observability.customThreshold.rule.alerting.custom_threshold.fired', { + defaultMessage: 'Alert', + }), +}; + +export const NO_DATA_ACTIONS = { + id: 'custom_threshold.nodata', + name: i18n.translate( + 'xpack.observability.customThreshold.rule.alerting.custom_threshold.nodata', + { + defaultMessage: 'No Data', + } + ), +}; + +const formatAlertResult = ( + alertResult: { + metric: string; + currentValue: number | null; + threshold: number[]; + comparator: Comparator; + timeSize: number; + timeUnit: TimeUnitChar; + } & AlertResult +) => { + const { metric, currentValue, threshold, comparator } = alertResult; + const noDataValue = i18n.translate( + 'xpack.observability.customThreshold.rule.alerting.threshold.noDataFormattedValue', + { defaultMessage: '[NO DATA]' } + ); + + if (metric.endsWith('.pct')) { + const formatter = createFormatter('percent'); + return { + ...alertResult, + currentValue: + currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, + threshold: Array.isArray(threshold) + ? threshold.map((v: number) => formatter(v)) + : formatter(threshold), + comparator, + }; + } + + const formatter = createFormatter('highPrecision'); + return { + ...alertResult, + currentValue: + currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, + threshold: Array.isArray(threshold) + ? threshold.map((v: number) => formatter(v)) + : formatter(threshold), + comparator, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts similarity index 96% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts index 4b8c47a1af866..7651130601b6c 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts @@ -8,7 +8,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { isString, get, identity } from 'lodash'; -import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import type { BucketKey } from './get_data'; import { calculateCurrentTimeframe, createBaseFilters } from './metric_query'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/convert_strings_to_missing_groups_record.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/convert_strings_to_missing_groups_record.ts similarity index 100% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/convert_strings_to_missing_groups_record.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/convert_strings_to_missing_groups_record.ts diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts similarity index 98% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts index 0af245df1a102..6300bfac703f3 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts @@ -9,7 +9,7 @@ import { Aggregators, Comparator, MetricExpressionParams, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; import { createConditionScript } from './create_condition_script'; import { createLastPeriod } from './wrap_in_period'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_condition_script.ts similarity index 92% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_condition_script.ts index 2355b2d301065..ad4aaa980aa63 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_condition_script.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Comparator } from '../../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../../common/custom_threshold_rule/types'; export const createConditionScript = (threshold: number[], comparator: Comparator) => { if (comparator === Comparator.BETWEEN && threshold.length === 2) { diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts similarity index 82% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts index 21329d18a1074..770d16c927e23 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts @@ -7,18 +7,18 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { isEmpty } from 'lodash'; -import { MetricExpressionCustomMetric } from '../../../../../common/threshold_rule/types'; +import { CustomThresholdExpressionMetric } from '../../../../../common/custom_threshold_rule/types'; import { MetricsExplorerCustomMetric } from './metrics_explorer'; const isMetricExpressionCustomMetric = ( - subject: MetricsExplorerCustomMetric | MetricExpressionCustomMetric -): subject is MetricExpressionCustomMetric => { - return (subject as MetricExpressionCustomMetric).aggType != null; + subject: MetricsExplorerCustomMetric | CustomThresholdExpressionMetric +): subject is CustomThresholdExpressionMetric => { + return 'aggType' in subject; }; export const createCustomMetricsAggregations = ( id: string, - customMetrics: Array, + customMetrics: Array, equation?: string ) => { const bucketsPath: { [id: string]: string } = {}; @@ -74,6 +74,6 @@ export const createCustomMetricsAggregations = ( const convertEquationToPainless = (bucketsPath: { [id: string]: string }, equation?: string) => { const workingEquation = equation || Object.keys(bucketsPath).join(' + '); return Object.keys(bucketsPath).reduce((acc, key) => { - return acc.replace(key, `params.${key}`); + return acc.replaceAll(key, `params.${key}`); }, workingEquation); }; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts similarity index 87% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts index d96dbe49f5a88..73db4a3e747ee 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Aggregators } from '../../../../../common/threshold_rule/types'; +import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; export const createPercentileAggregation = ( type: Aggregators.P95 | Aggregators.P99, diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_rate_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts similarity index 100% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_rate_aggregation.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts similarity index 98% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts index 06b1554706a93..c48ce1d9ab50d 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Aggregators } from '../../../../../common/threshold_rule/types'; +import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; import moment from 'moment'; import { createTimerange } from './create_timerange'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts similarity index 92% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts index 446299c4ba92b..75f4dda7ff8d6 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { Aggregators } from '../../../../../common/threshold_rule/types'; +import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; export const createTimerange = ( interval: number, diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts similarity index 90% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index 6300515059614..66007de19b622 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -5,22 +5,22 @@ * 2.0. */ -import { ElasticsearchClient } from '@kbn/core/server'; import moment from 'moment'; +import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import { isCustom } from './metric_expression_params'; -import { getIntervalInSeconds } from '../utils'; +import { AdditionalContext, getIntervalInSeconds } from '../utils'; +import { SearchConfigurationType } from '../custom_threshold_executor'; import { CUSTOM_EQUATION_I18N, DOCUMENT_COUNT_I18N } from '../messages'; import { createTimerange } from './create_timerange'; import { getData } from './get_data'; import { checkMissingGroups, MissingGroupsRecord } from './check_missing_group'; -import { AdditionalContext } from '../utils'; export interface EvaluatedRuleParams { criteria: MetricExpressionParams[]; groupBy: string | undefined | string[]; - filterQuery?: string; + searchConfiguration: SearchConfigurationType; } export type Evaluation = Omit & { @@ -46,7 +46,7 @@ export const evaluateRule = async >> => { - const { criteria, groupBy, filterQuery } = params; + const { criteria, groupBy, searchConfiguration } = params; return Promise.all( criteria.map(async (criterion) => { @@ -66,7 +66,7 @@ export const evaluateRule = async +>((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); + +export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); + +export const metricsExplorerMetricRequiredFieldsRT = rt.type({ + aggregation: metricsExplorerAggregationRT, +}); + +export const metricsExplorerCustomMetricRT = rt.intersection([ + rt.type({ + name: rt.string, + aggregation: metricsExplorerCustomMetricAggregationRT, + }), + rt.partial({ + field: rt.string, + filter: rt.string, + }), +]); + +export type MetricsExplorerCustomMetric = rt.TypeOf; + +export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ + field: rt.union([rt.string, rt.undefined]), + custom_metrics: rt.array(metricsExplorerCustomMetricRT), + equation: rt.string, +}); + +export const metricsExplorerMetricRT = rt.intersection([ + metricsExplorerMetricRequiredFieldsRT, + metricsExplorerMetricOptionalFieldsRT, +]); + +export const timeRangeRT = rt.type({ + from: rt.number, + to: rt.number, + interval: rt.string, +}); + +export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ + timerange: timeRangeRT, + indexPattern: rt.string, + metrics: rt.array(metricsExplorerMetricRT), +}); + +const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); +export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); + +export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ + groupBy: rt.union([groupByRT, rt.array(groupByRT)]), + afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), + limit: rt.union([rt.number, rt.null, rt.undefined]), + filterQuery: rt.union([rt.string, rt.null, rt.undefined]), + forceInterval: rt.boolean, + dropLastBucket: rt.boolean, +}); + +export const metricsExplorerRequestBodyRT = rt.intersection([ + metricsExplorerRequestBodyRequiredFieldsRT, + metricsExplorerRequestBodyOptionalFieldsRT, +]); + +export const metricsExplorerPageInfoRT = rt.type({ + total: rt.number, + afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), +}); + +export const metricsExplorerColumnTypeRT = rt.keyof({ + date: null, + number: null, + string: null, +}); + +export const metricsExplorerColumnRT = rt.type({ + name: rt.string, + type: metricsExplorerColumnTypeRT, +}); + +export const metricsExplorerRowRT = rt.intersection([ + rt.type({ + timestamp: rt.number, + }), + rt.record( + rt.string, + rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) + ), +]); + +export const metricsExplorerSeriesRT = rt.intersection([ + rt.type({ + id: rt.string, + columns: rt.array(metricsExplorerColumnRT), + rows: rt.array(metricsExplorerRowRT), + }), + rt.partial({ + keys: rt.array(rt.string), + }), +]); + +export const metricsExplorerResponseRT = rt.type({ + series: rt.array(metricsExplorerSeriesRT), + pageInfo: metricsExplorerPageInfoRT, +}); + +export type AfterKey = rt.TypeOf; + +export type MetricsExplorerColumnType = rt.TypeOf; + +export type MetricsExplorerPageInfo = rt.TypeOf; + +export type MetricsExplorerColumn = rt.TypeOf; + +export type MetricsExplorerRow = rt.TypeOf; + +export type MetricsExplorerRequestBody = rt.TypeOf; + +export type MetricsExplorerResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts similarity index 93% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts index d313e5efd9fcd..a76c45e4e4583 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; export const createLastPeriod = ( lastPeriodEnd: number, diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts new file mode 100644 index 0000000000000..80ca06c24e59f --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; +import { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../../common'; +import { UNGROUPED_FACTORY_KEY } from './utils'; + +export const DOCUMENT_COUNT_I18N = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.documentCount', + { + defaultMessage: 'Document count', + } +); + +export const CUSTOM_EQUATION_I18N = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.customEquation', + { + defaultMessage: 'Custom equation', + } +); + +const toNumber = (value: number | string) => + typeof value === 'string' ? parseFloat(value) : value; + +const recoveredComparatorToI18n = ( + comparator: Comparator, + threshold: number[], + currentValue: number +) => { + const belowText = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.belowRecovery', + { + defaultMessage: 'below', + } + ); + const aboveText = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.aboveRecovery', + { + defaultMessage: 'above', + } + ); + switch (comparator) { + case Comparator.BETWEEN: + return currentValue < threshold[0] ? belowText : aboveText; + case Comparator.OUTSIDE_RANGE: + return i18n.translate('xpack.observability.customThreshold.rule.threshold.betweenRecovery', { + defaultMessage: 'between', + }); + case Comparator.GT: + case Comparator.GT_OR_EQ: + return belowText; + case Comparator.LT: + case Comparator.LT_OR_EQ: + return aboveText; + } +}; + +const thresholdToI18n = ([a, b]: Array) => { + if (typeof b === 'undefined') return a; + return i18n.translate('xpack.observability.customThreshold.rule.threshold.thresholdRange', { + defaultMessage: '{a} and {b}', + values: { a, b }, + }); +}; + +const formatGroup = (group: string) => (group === UNGROUPED_FACTORY_KEY ? '' : ` for ${group}`); + +export const buildFiredAlertReason: (alertResult: { + group: string; + metric: string; + comparator: Comparator; + threshold: Array; + currentValue: number | string; + timeSize: number; + timeUnit: TimeUnitChar; +}) => string = ({ group, metric, comparator, threshold, currentValue, timeSize, timeUnit }) => + i18n.translate('xpack.observability.customThreshold.rule.threshold.firedAlertReason', { + defaultMessage: + '{metric} is {currentValue} in the last {duration}{group}. Alert when {comparator} {threshold}.', + values: { + group: formatGroup(group), + metric, + comparator, + threshold: thresholdToI18n(threshold), + currentValue, + duration: formatDurationFromTimeUnitChar(timeSize, timeUnit), + }, + }); + +// Once recovered reason messages are re-enabled, checkout this issue https://github.com/elastic/kibana/issues/121272 regarding latest reason format +export const buildRecoveredAlertReason: (alertResult: { + group: string; + metric: string; + comparator: Comparator; + threshold: Array; + currentValue: number | string; +}) => string = ({ group, metric, comparator, threshold, currentValue }) => + i18n.translate('xpack.observability.customThreshold.rule.threshold.recoveredAlertReason', { + defaultMessage: + '{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}', + values: { + metric, + comparator: recoveredComparatorToI18n( + comparator, + threshold.map(toNumber), + toNumber(currentValue) + ), + threshold: thresholdToI18n(threshold), + currentValue, + group, + }, + }); + +export const buildNoDataAlertReason: (alertResult: { + group: string; + metric: string; + timeSize: number; + timeUnit: string; +}) => string = ({ group, metric, timeSize, timeUnit }) => + i18n.translate('xpack.observability.customThreshold.rule.threshold.noDataAlertReason', { + defaultMessage: '{metric} reported no data in the last {interval}{group}', + values: { + metric, + interval: `${timeSize}${timeUnit}`, + group: formatGroup(group), + }, + }); + +export const buildErrorAlertReason = (metric: string) => + i18n.translate('xpack.observability.customThreshold.rule.threshold.errorAlertReason', { + defaultMessage: 'Elasticsearch failed when attempting to query data for {metric}', + values: { + metric, + }, + }); + +export const groupByKeysActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.groupByKeysActionVariableDescription', + { + defaultMessage: 'The object containing groups that are reporting data', + } +); + +export const alertDetailUrlActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.alertDetailUrlActionVariableDescription', + { + defaultMessage: + 'Link to the alert troubleshooting view for further context and details. This will be an empty string if the server.publicBaseUrl is not configured.', + } +); + +export const reasonActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.reasonActionVariableDescription', + { + defaultMessage: 'A concise description of the reason for the alert', + } +); + +export const timestampActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.timestampDescription', + { + defaultMessage: 'A timestamp of when the alert was detected.', + } +); + +export const valueActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.valueActionVariableDescription', + { + defaultMessage: 'List of the condition values.', + } +); + +export const viewInAppUrlActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.viewInAppUrlActionVariableDescription', + { + defaultMessage: 'Link to the alert source', + } +); + +export const cloudActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.cloudActionVariableDescription', + { + defaultMessage: 'The cloud object defined by ECS if available in the source.', + } +); + +export const hostActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.hostActionVariableDescription', + { + defaultMessage: 'The host object defined by ECS if available in the source.', + } +); + +export const containerActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.containerActionVariableDescription', + { + defaultMessage: 'The container object defined by ECS if available in the source.', + } +); + +export const orchestratorActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.orchestratorActionVariableDescription', + { + defaultMessage: 'The orchestrator object defined by ECS if available in the source.', + } +); + +export const labelsActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.labelsActionVariableDescription', + { + defaultMessage: 'List of labels associated with the entity where this alert triggered.', + } +); + +export const tagsActionVariableDescription = i18n.translate( + 'xpack.observability.customThreshold.rule.tagsActionVariableDescription', + { + defaultMessage: 'List of tags associated with the entity where this alert triggered.', + } +); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts new file mode 100644 index 0000000000000..3b1b01e4d2268 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.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 { schema } from '@kbn/config-schema'; +import { extractReferences, injectReferences } from '@kbn/data-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { IRuleTypeAlerts, GetViewInAppRelativeUrlFnOpts } from '@kbn/alerting-plugin/server'; +import { IBasePath, Logger } from '@kbn/core/server'; +import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; +import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plugin/server'; +import { LicenseType } from '@kbn/licensing-plugin/server'; +import { LocatorPublic } from '@kbn/share-plugin/common'; +import { EsQueryRuleParamsExtractedParams } from '@kbn/stack-alerts-plugin/server/rule_types/es_query/rule_type_params'; +import { searchConfigurationSchema } from './types'; +import { + AlertsLocatorParams, + observabilityFeatureId, + observabilityPaths, +} from '../../../../common'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; +import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants'; + +import { + alertDetailUrlActionVariableDescription, + cloudActionVariableDescription, + containerActionVariableDescription, + groupByKeysActionVariableDescription, + hostActionVariableDescription, + labelsActionVariableDescription, + orchestratorActionVariableDescription, + reasonActionVariableDescription, + tagsActionVariableDescription, + timestampActionVariableDescription, + valueActionVariableDescription, +} from './messages'; +import { oneOfLiterals, validateKQLStringFilter } from './utils'; +import { + createMetricThresholdExecutor, + FIRED_ACTIONS, + NO_DATA_ACTIONS, +} from './custom_threshold_executor'; +import { ObservabilityConfig } from '../../..'; +import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/custom_threshold_rule/constants'; + +export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { + context: THRESHOLD_RULE_REGISTRATION_CONTEXT, + mappings: { fieldMap: legacyExperimentalFieldMap }, + useEcs: true, + useLegacyAlerts: false, +}; + +type CreateLifecycleExecutor = ReturnType; + +export function thresholdRuleType( + createLifecycleRuleExecutor: CreateLifecycleExecutor, + basePath: IBasePath, + config: ObservabilityConfig, + logger: Logger, + ruleDataClient: IRuleDataClient, + alertsLocator?: LocatorPublic +) { + const baseCriterion = { + threshold: schema.arrayOf(schema.number()), + comparator: oneOfLiterals(Object.values(Comparator)), + timeUnit: schema.string(), + timeSize: schema.number(), + warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))), + }; + + const nonCountCriterion = schema.object({ + ...baseCriterion, + metric: schema.string(), + aggType: oneOfLiterals(METRIC_EXPLORER_AGGREGATIONS), + metrics: schema.never(), + equation: schema.never(), + label: schema.never(), + }); + + const countCriterion = schema.object({ + ...baseCriterion, + aggType: schema.literal('count'), + metric: schema.never(), + metrics: schema.never(), + equation: schema.never(), + label: schema.never(), + }); + + const customCriterion = schema.object({ + ...baseCriterion, + aggType: schema.literal('custom'), + metric: schema.never(), + metrics: schema.arrayOf( + schema.oneOf([ + schema.object({ + name: schema.string(), + aggType: oneOfLiterals(['avg', 'sum', 'max', 'min', 'cardinality']), + field: schema.string(), + filter: schema.never(), + }), + schema.object({ + name: schema.string(), + aggType: schema.literal('count'), + filter: schema.maybe( + schema.string({ + validate: validateKQLStringFilter, + }) + ), + field: schema.never(), + }), + ]) + ), + equation: schema.maybe(schema.string()), + label: schema.maybe(schema.string()), + }); + + return { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + name: i18n.translate('xpack.observability.threshold.ruleName', { + defaultMessage: 'Custom threshold (BETA)', + }), + validate: { + params: schema.object( + { + criteria: schema.arrayOf( + schema.oneOf([countCriterion, nonCountCriterion, customCriterion]) + ), + groupBy: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + alertOnNoData: schema.maybe(schema.boolean()), + alertOnGroupDisappear: schema.maybe(schema.boolean()), + searchConfiguration: searchConfigurationSchema, + }, + { unknowns: 'allow' } + ), + }, + defaultActionGroupId: FIRED_ACTIONS.id, + actionGroups: [FIRED_ACTIONS, NO_DATA_ACTIONS], + minimumLicenseRequired: 'basic' as LicenseType, + isExportable: true, + executor: createLifecycleRuleExecutor( + createMetricThresholdExecutor({ alertsLocator, basePath, logger, config }) + ), + doesSetRecoveryContext: true, + actionVariables: { + context: [ + { name: 'groupings', description: groupByKeysActionVariableDescription }, + { + name: 'alertDetailsUrl', + description: alertDetailUrlActionVariableDescription, + usesPublicBaseUrl: true, + }, + { name: 'reason', description: reasonActionVariableDescription }, + { name: 'timestamp', description: timestampActionVariableDescription }, + { name: 'value', description: valueActionVariableDescription }, + { name: 'cloud', description: cloudActionVariableDescription }, + { name: 'host', description: hostActionVariableDescription }, + { name: 'container', description: containerActionVariableDescription }, + { name: 'orchestrator', description: orchestratorActionVariableDescription }, + { name: 'labels', description: labelsActionVariableDescription }, + { name: 'tags', description: tagsActionVariableDescription }, + ], + }, + useSavedObjectReferences: { + // TODO revisit types https://github.com/elastic/kibana/issues/159714 + extractReferences: (params: any) => { + const [searchConfiguration, references] = extractReferences(params.searchConfiguration); + const newParams = { ...params, searchConfiguration } as EsQueryRuleParamsExtractedParams; + + return { params: newParams, references }; + }, + injectReferences: (params: any, references: any) => { + return { + ...params, + searchConfiguration: injectReferences(params.searchConfiguration, references), + }; + }, + }, + producer: observabilityFeatureId, + alerts: MetricsRulesTypeAlertDefinition, + getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) => + observabilityPaths.ruleDetails(rule.id), + }; +} diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts new file mode 100644 index 0000000000000..e66042bcb648d --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema } from '@kbn/config-schema'; +import * as rt from 'io-ts'; +import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; +import { validateKQLStringFilter } from './utils'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; +import { TimeUnitChar } from '../../../../common'; + +export enum InfraRuleType { + MetricThreshold = 'metrics.alert.threshold', + InventoryThreshold = 'metrics.alert.inventory.threshold', + Anomaly = 'metrics.alert.anomaly', +} + +export enum AlertStates { + OK, + ALERT, + NO_DATA, + ERROR, +} + +const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); +const metricAnomalyMetricRT = rt.union([ + rt.literal('memory_usage'), + rt.literal('network_in'), + rt.literal('network_out'), +]); +const metricAnomalyInfluencerFilterRT = rt.type({ + fieldName: rt.string, + fieldValue: rt.string, +}); + +export interface MetricAnomalyParams { + nodeType: rt.TypeOf; + metric: rt.TypeOf; + alertInterval?: string; + spaceId?: string; + threshold: Exclude; + influencerFilter: rt.TypeOf | undefined; +} + +// Types for the executor + +interface BaseMetricExpressionParams { + timeSize: number; + timeUnit: TimeUnitChar; + threshold: number[]; + comparator: Comparator; + warningComparator?: Comparator; + warningThreshold?: number[]; +} + +export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Exclude; + metric: string; +} + +export interface CountMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Aggregators.COUNT; +} + +export type CustomMetricAggTypes = Exclude< + Aggregators, + Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 +>; + +export interface AlertExecutionDetails { + alertId: string; + executionId: string; +} + +export const searchConfigurationSchema = schema.object({ + index: schema.string(), + query: schema.object({ + language: schema.string({ + validate: validateKQLStringFilter, + }), + query: schema.string(), + }), +}); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/utils.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.test.ts similarity index 100% rename from x-pack/plugins/observability/server/lib/rules/threshold/utils.test.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.test.ts diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts new file mode 100644 index 0000000000000..854b7cc497502 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts @@ -0,0 +1,310 @@ +/* + * Copyright 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 { isError } from 'lodash'; +import { buildEsQuery as kbnBuildEsQuery } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; +import { Logger, LogMeta } from '@kbn/logging'; +import type { ElasticsearchClient, IBasePath } from '@kbn/core/server'; +import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import { ES_FIELD_TYPES } from '@kbn/field-types'; +import { set } from '@kbn/safer-lodash-set'; +import { ParsedExperimentalFields } from '@kbn/rule-registry-plugin/common/parse_experimental_fields'; +import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; +import { ObservabilityConfig } from '../../..'; +import { AlertExecutionDetails } from './types'; + +const ALERT_CONTEXT_CONTAINER = 'container'; +const ALERT_CONTEXT_ORCHESTRATOR = 'orchestrator'; +const ALERT_CONTEXT_CLOUD = 'cloud'; +const ALERT_CONTEXT_HOST = 'host'; +const ALERT_CONTEXT_LABELS = 'labels'; +const ALERT_CONTEXT_TAGS = 'tags'; + +const HOST_NAME = 'host.name'; +const HOST_HOSTNAME = 'host.hostname'; +const HOST_ID = 'host.id'; +export const CONTAINER_ID = 'container.id'; + +const SUPPORTED_ES_FIELD_TYPES = [ + ES_FIELD_TYPES.KEYWORD, + ES_FIELD_TYPES.IP, + ES_FIELD_TYPES.BOOLEAN, +]; + +export const oneOfLiterals = (arrayOfLiterals: Readonly) => + schema.string({ + validate: (value) => + arrayOfLiterals.includes(value) ? undefined : `must be one of ${arrayOfLiterals.join(' | ')}`, + }); + +export const validateKQLStringFilter = (value: string) => { + if (value === '') { + // Allow clearing the filter. + return; + } + + try { + kbnBuildEsQuery(undefined, [{ query: value, language: 'kuery' }], []); + } catch (e) { + return i18n.translate('xpack.observability.customThreshold.rule.schema.invalidFilterQuery', { + defaultMessage: 'filterQuery must be a valid KQL filter', + }); + } +}; + +export const UNGROUPED_FACTORY_KEY = '*'; + +export const createScopedLogger = ( + logger: Logger, + scope: string, + alertExecutionDetails: AlertExecutionDetails +): Logger => { + const scopedLogger = logger.get(scope); + const fmtMsg = (msg: string) => + `[AlertId: ${alertExecutionDetails.alertId}][ExecutionId: ${alertExecutionDetails.executionId}] ${msg}`; + return { + ...scopedLogger, + info: (msg: string, meta?: Meta) => + scopedLogger.info(fmtMsg(msg), meta), + debug: (msg: string, meta?: Meta) => + scopedLogger.debug(fmtMsg(msg), meta), + trace: (msg: string, meta?: Meta) => + scopedLogger.trace(fmtMsg(msg), meta), + warn: (errorOrMessage: string | Error, meta?: Meta) => { + if (isError(errorOrMessage)) { + scopedLogger.warn(errorOrMessage, meta); + } else { + scopedLogger.warn(fmtMsg(errorOrMessage), meta); + } + }, + error: (errorOrMessage: string | Error, meta?: Meta) => { + if (isError(errorOrMessage)) { + scopedLogger.error(errorOrMessage, meta); + } else { + scopedLogger.error(fmtMsg(errorOrMessage), meta); + } + }, + fatal: (errorOrMessage: string | Error, meta?: Meta) => { + if (isError(errorOrMessage)) { + scopedLogger.fatal(errorOrMessage, meta); + } else { + scopedLogger.fatal(fmtMsg(errorOrMessage), meta); + } + }, + }; +}; + +export const getAlertDetailsPageEnabledForApp = ( + config: ObservabilityConfig['unsafe']['alertDetails'] | null, + appName: keyof ObservabilityConfig['unsafe']['alertDetails'] +): boolean => { + if (!config) return false; + + return config[appName].enabled; +}; + +export const getAlertDetailsUrl = ( + basePath: IBasePath, + spaceId: string, + alertUuid: string | null +) => addSpaceIdToPath(basePath.publicBaseUrl, spaceId, `/app/observability/alerts/${alertUuid}`); + +export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; +export const NUMBER_OF_DOCUMENTS = 10; + +export interface AdditionalContext { + [x: string]: any; +} + +export const doFieldsExist = async ( + esClient: ElasticsearchClient, + fields: string[], + index: string +): Promise> => { + // Get all supported fields + const respMapping = await esClient.fieldCaps({ + index, + fields: '*', + }); + + const fieldsExisted: Record = {}; + const acceptableFields: Set = new Set(); + + Object.entries(respMapping.fields).forEach(([key, value]) => { + const fieldTypes = Object.keys(value) as ES_FIELD_TYPES[]; + const isSupportedType = fieldTypes.some((type) => SUPPORTED_ES_FIELD_TYPES.includes(type)); + + // Check if fieldName is something we can aggregate on + if (isSupportedType) { + acceptableFields.add(key); + } + }); + + fields.forEach((field) => { + fieldsExisted[field] = acceptableFields.has(field); + }); + + return fieldsExisted; +}; + +export const validGroupByForContext: string[] = [ + HOST_NAME, + HOST_HOSTNAME, + HOST_ID, + KUBERNETES_POD_UID, + CONTAINER_ID, +]; + +export const hasAdditionalContext = ( + groupBy: string | string[] | undefined, + validGroups: string[] +): boolean => { + return groupBy + ? Array.isArray(groupBy) + ? groupBy.every((group) => validGroups.includes(group)) + : validGroups.includes(groupBy) + : false; +}; + +export const shouldTermsAggOnContainer = (groupBy: string | string[] | undefined) => { + return groupBy && Array.isArray(groupBy) + ? groupBy.includes(KUBERNETES_POD_UID) + : groupBy === KUBERNETES_POD_UID; +}; + +export const flattenAdditionalContext = ( + additionalContext: AdditionalContext | undefined | null +): AdditionalContext => { + return additionalContext ? flattenObject(additionalContext) : {}; +}; + +export const getContextForRecoveredAlerts = ( + alertHitSource: Partial | undefined | null +): AdditionalContext => { + const alert = alertHitSource ? unflattenObject(alertHitSource) : undefined; + + return { + cloud: alert?.[ALERT_CONTEXT_CLOUD], + host: alert?.[ALERT_CONTEXT_HOST], + orchestrator: alert?.[ALERT_CONTEXT_ORCHESTRATOR], + container: alert?.[ALERT_CONTEXT_CONTAINER], + labels: alert?.[ALERT_CONTEXT_LABELS], + tags: alert?.[ALERT_CONTEXT_TAGS], + }; +}; + +export const unflattenObject = (object: object): T => + Object.entries(object).reduce((acc, [key, value]) => { + set(acc, key, value); + return acc; + }, {} as T); + +export const flattenObject = (obj: AdditionalContext, prefix: string = ''): AdditionalContext => + Object.keys(obj).reduce((acc, key) => { + const nextValue = obj[key]; + + if (nextValue) { + if (typeof nextValue === 'object' && !Array.isArray(nextValue)) { + const dotSuffix = '.'; + if (Object.keys(nextValue).length > 0) { + return { + ...acc, + ...flattenObject(nextValue, `${prefix}${key}${dotSuffix}`), + }; + } + } + + const fullPath = `${prefix}${key}`; + acc[fullPath] = nextValue; + } + + return acc; + }, {}); + +export const getGroupByObject = ( + groupBy: string | string[] | undefined, + resultGroupSet: Set +): Record => { + const groupByKeysObjectMapping: Record = {}; + if (groupBy) { + resultGroupSet.forEach((groupSet) => { + const groupSetKeys = groupSet.split(','); + groupByKeysObjectMapping[groupSet] = unflattenObject( + Array.isArray(groupBy) + ? groupBy.reduce((result, group, index) => { + return { ...result, [group]: groupSetKeys[index]?.trim() }; + }, {}) + : { [groupBy]: groupSet } + ); + }); + } + return groupByKeysObjectMapping; +}; + +// TO BE MOVED +export const INFRA_ALERT_PREVIEW_PATH = '/api/infra/alerting/preview'; + +export const TOO_MANY_BUCKETS_PREVIEW_EXCEPTION = 'TOO_MANY_BUCKETS_PREVIEW_EXCEPTION'; +export interface TooManyBucketsPreviewExceptionMetadata { + TOO_MANY_BUCKETS_PREVIEW_EXCEPTION: boolean; + maxBuckets: any; +} +export const isTooManyBucketsPreviewException = ( + value: any +): value is TooManyBucketsPreviewExceptionMetadata => + Boolean(value && value.TOO_MANY_BUCKETS_PREVIEW_EXCEPTION); + +const intervalUnits = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms']; +const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + intervalUnits.join('|') + ')$'); + +interface UnitsToSeconds { + [unit: string]: number; +} + +const units: UnitsToSeconds = { + ms: 0.001, + s: 1, + m: 60, + h: 3600, + d: 86400, + w: 86400 * 7, + M: 86400 * 30, + y: 86400 * 356, +}; + +export const getIntervalInSeconds = (interval: string): number => { + const matches = interval.match(INTERVAL_STRING_RE); + if (matches) { + return parseFloat(matches[1]) * units[matches[2]]; + } + throw new Error('Invalid interval string format.'); +}; + +export const calculateRateTimeranges = (timerange: { to: number; from: number }) => { + // This is the total number of milliseconds for the entire timerange + const totalTime = timerange.to - timerange.from; + // Halfway is the to minus half the total time; + const halfway = Math.round(timerange.to - totalTime / 2); + // The interval is half the total time (divided by 1000 to convert to seconds) + const intervalInSeconds = Math.round(totalTime / (2 * 1000)); + + // The first bucket is from the beginning of the time range to the halfway point + const firstBucketRange = { + from: timerange.from, + to: halfway, + }; + + // The second bucket is from the halfway point to the end of the timerange + const secondBucketRange = { + from: halfway, + to: timerange.to, + }; + + return { firstBucketRange, secondBucketRange, intervalInSeconds }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts index 388ede407819f..0e1abfc1037d7 100644 --- a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts +++ b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts @@ -22,7 +22,7 @@ import { THRESHOLD_RULE_REGISTRATION_CONTEXT, } from '../../common/constants'; import { sloBurnRateRuleType } from './slo_burn_rate'; -import { thresholdRuleType } from './threshold/register_threshold_rule_type'; +import { thresholdRuleType } from './custom_threshold/register_custom_threshold_rule_type'; import { sloRuleFieldMap } from './slo_burn_rate/field_map'; export function registerRuleTypes( diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts b/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts deleted file mode 100644 index 2de1a21c3cd48..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as rt from 'io-ts'; -import { metricsExplorerCustomMetricAggregationRT } from '../../../../../common/threshold_rule/metrics_explorer'; - -export const METRIC_EXPLORER_AGGREGATIONS = [ - 'avg', - 'max', - 'min', - 'cardinality', - 'rate', - 'count', - 'sum', - 'p95', - 'p99', - 'custom', -] as const; - -type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; - -const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< - Record ->((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); - -export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); - -export const metricsExplorerMetricRequiredFieldsRT = rt.type({ - aggregation: metricsExplorerAggregationRT, -}); - -export const metricsExplorerCustomMetricRT = rt.intersection([ - rt.type({ - name: rt.string, - aggregation: metricsExplorerCustomMetricAggregationRT, - }), - rt.partial({ - field: rt.string, - filter: rt.string, - }), -]); - -export type MetricsExplorerCustomMetric = rt.TypeOf; - -export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ - field: rt.union([rt.string, rt.undefined]), - custom_metrics: rt.array(metricsExplorerCustomMetricRT), - equation: rt.string, -}); - -export const metricsExplorerMetricRT = rt.intersection([ - metricsExplorerMetricRequiredFieldsRT, - metricsExplorerMetricOptionalFieldsRT, -]); - -export const timeRangeRT = rt.type({ - from: rt.number, - to: rt.number, - interval: rt.string, -}); - -export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ - timerange: timeRangeRT, - indexPattern: rt.string, - metrics: rt.array(metricsExplorerMetricRT), -}); - -const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); -export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); - -export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ - groupBy: rt.union([groupByRT, rt.array(groupByRT)]), - afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), - limit: rt.union([rt.number, rt.null, rt.undefined]), - filterQuery: rt.union([rt.string, rt.null, rt.undefined]), - forceInterval: rt.boolean, - dropLastBucket: rt.boolean, -}); - -export const metricsExplorerRequestBodyRT = rt.intersection([ - metricsExplorerRequestBodyRequiredFieldsRT, - metricsExplorerRequestBodyOptionalFieldsRT, -]); - -export const metricsExplorerPageInfoRT = rt.type({ - total: rt.number, - afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), -}); - -export const metricsExplorerColumnTypeRT = rt.keyof({ - date: null, - number: null, - string: null, -}); - -export const metricsExplorerColumnRT = rt.type({ - name: rt.string, - type: metricsExplorerColumnTypeRT, -}); - -export const metricsExplorerRowRT = rt.intersection([ - rt.type({ - timestamp: rt.number, - }), - rt.record( - rt.string, - rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) - ), -]); - -export const metricsExplorerSeriesRT = rt.intersection([ - rt.type({ - id: rt.string, - columns: rt.array(metricsExplorerColumnRT), - rows: rt.array(metricsExplorerRowRT), - }), - rt.partial({ - keys: rt.array(rt.string), - }), -]); - -export const metricsExplorerResponseRT = rt.type({ - series: rt.array(metricsExplorerSeriesRT), - pageInfo: metricsExplorerPageInfoRT, -}); - -export type AfterKey = rt.TypeOf; - -export type MetricsExplorerColumnType = rt.TypeOf; - -export type MetricsExplorerPageInfo = rt.TypeOf; - -export type MetricsExplorerColumn = rt.TypeOf; - -export type MetricsExplorerRow = rt.TypeOf; - -export type MetricsExplorerRequestBody = rt.TypeOf; - -export type MetricsExplorerResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/messages.ts b/x-pack/plugins/observability/server/lib/rules/threshold/messages.ts deleted file mode 100644 index ac0f1ef8b98c4..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/threshold/messages.ts +++ /dev/null @@ -1,219 +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 { i18n } from '@kbn/i18n'; -import { Comparator } from '../../../../common/threshold_rule/types'; -import { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../../common'; -import { UNGROUPED_FACTORY_KEY } from './utils'; - -export const DOCUMENT_COUNT_I18N = i18n.translate( - 'xpack.observability.threshold.rule.threshold.documentCount', - { - defaultMessage: 'Document count', - } -); - -export const CUSTOM_EQUATION_I18N = i18n.translate( - 'xpack.observability.threshold.rule.threshold.customEquation', - { - defaultMessage: 'Custom equation', - } -); - -const toNumber = (value: number | string) => - typeof value === 'string' ? parseFloat(value) : value; - -const recoveredComparatorToI18n = ( - comparator: Comparator, - threshold: number[], - currentValue: number -) => { - const belowText = i18n.translate('xpack.observability.threshold.rule.threshold.belowRecovery', { - defaultMessage: 'below', - }); - const aboveText = i18n.translate('xpack.observability.threshold.rule.threshold.aboveRecovery', { - defaultMessage: 'above', - }); - switch (comparator) { - case Comparator.BETWEEN: - return currentValue < threshold[0] ? belowText : aboveText; - case Comparator.OUTSIDE_RANGE: - return i18n.translate('xpack.observability.threshold.rule.threshold.betweenRecovery', { - defaultMessage: 'between', - }); - case Comparator.GT: - case Comparator.GT_OR_EQ: - return belowText; - case Comparator.LT: - case Comparator.LT_OR_EQ: - return aboveText; - } -}; - -const thresholdToI18n = ([a, b]: Array) => { - if (typeof b === 'undefined') return a; - return i18n.translate('xpack.observability.threshold.rule.threshold.thresholdRange', { - defaultMessage: '{a} and {b}', - values: { a, b }, - }); -}; - -const formatGroup = (group: string) => (group === UNGROUPED_FACTORY_KEY ? '' : ` for ${group}`); - -export const buildFiredAlertReason: (alertResult: { - group: string; - metric: string; - comparator: Comparator; - threshold: Array; - currentValue: number | string; - timeSize: number; - timeUnit: TimeUnitChar; -}) => string = ({ group, metric, comparator, threshold, currentValue, timeSize, timeUnit }) => - i18n.translate('xpack.observability.threshold.rule.threshold.firedAlertReason', { - defaultMessage: - '{metric} is {currentValue} in the last {duration}{group}. Alert when {comparator} {threshold}.', - values: { - group: formatGroup(group), - metric, - comparator, - threshold: thresholdToI18n(threshold), - currentValue, - duration: formatDurationFromTimeUnitChar(timeSize, timeUnit), - }, - }); - -// Once recovered reason messages are re-enabled, checkout this issue https://github.com/elastic/kibana/issues/121272 regarding latest reason format -export const buildRecoveredAlertReason: (alertResult: { - group: string; - metric: string; - comparator: Comparator; - threshold: Array; - currentValue: number | string; -}) => string = ({ group, metric, comparator, threshold, currentValue }) => - i18n.translate('xpack.observability.threshold.rule.threshold.recoveredAlertReason', { - defaultMessage: - '{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}', - values: { - metric, - comparator: recoveredComparatorToI18n( - comparator, - threshold.map(toNumber), - toNumber(currentValue) - ), - threshold: thresholdToI18n(threshold), - currentValue, - group, - }, - }); - -export const buildNoDataAlertReason: (alertResult: { - group: string; - metric: string; - timeSize: number; - timeUnit: string; -}) => string = ({ group, metric, timeSize, timeUnit }) => - i18n.translate('xpack.observability.threshold.rule.threshold.noDataAlertReason', { - defaultMessage: '{metric} reported no data in the last {interval}{group}', - values: { - metric, - interval: `${timeSize}${timeUnit}`, - group: formatGroup(group), - }, - }); - -export const buildErrorAlertReason = (metric: string) => - i18n.translate('xpack.observability.threshold.rule.threshold.errorAlertReason', { - defaultMessage: 'Elasticsearch failed when attempting to query data for {metric}', - values: { - metric, - }, - }); - -export const groupByKeysActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.groupByKeysActionVariableDescription', - { - defaultMessage: 'The object containing groups that are reporting data', - } -); - -export const alertDetailUrlActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription', - { - defaultMessage: - 'Link to the alert troubleshooting view for further context and details. This will be an empty string if the server.publicBaseUrl is not configured.', - } -); - -export const reasonActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.reasonActionVariableDescription', - { - defaultMessage: 'A concise description of the reason for the alert', - } -); - -export const timestampActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.timestampDescription', - { - defaultMessage: 'A timestamp of when the alert was detected.', - } -); - -export const valueActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.valueActionVariableDescription', - { - defaultMessage: 'List of the condition values.', - } -); - -export const viewInAppUrlActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription', - { - defaultMessage: 'Link to the alert source', - } -); - -export const cloudActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.cloudActionVariableDescription', - { - defaultMessage: 'The cloud object defined by ECS if available in the source.', - } -); - -export const hostActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.hostActionVariableDescription', - { - defaultMessage: 'The host object defined by ECS if available in the source.', - } -); - -export const containerActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.containerActionVariableDescription', - { - defaultMessage: 'The container object defined by ECS if available in the source.', - } -); - -export const orchestratorActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.orchestratorActionVariableDescription', - { - defaultMessage: 'The orchestrator object defined by ECS if available in the source.', - } -); - -export const labelsActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.labelsActionVariableDescription', - { - defaultMessage: 'List of labels associated with the entity where this alert triggered.', - } -); - -export const tagsActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.tagsActionVariableDescription', - { - defaultMessage: 'List of tags associated with the entity where this alert triggered.', - } -); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts deleted file mode 100644 index fa75318530267..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts +++ /dev/null @@ -1,197 +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 { schema } from '@kbn/config-schema'; -import { extractReferences, injectReferences } from '@kbn/data-plugin/common'; -import { i18n } from '@kbn/i18n'; -import { IRuleTypeAlerts, GetViewInAppRelativeUrlFnOpts } from '@kbn/alerting-plugin/server'; -import { IBasePath, Logger } from '@kbn/core/server'; -import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; -import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plugin/server'; -import { LicenseType } from '@kbn/licensing-plugin/server'; -import { LocatorPublic } from '@kbn/share-plugin/common'; -import { EsQueryRuleParamsExtractedParams } from '@kbn/stack-alerts-plugin/server/rule_types/es_query/rule_type_params'; -import { - AlertsLocatorParams, - observabilityFeatureId, - observabilityPaths, -} from '../../../../common'; -import { Comparator } from '../../../../common/threshold_rule/types'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; -import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants'; - -import { - alertDetailUrlActionVariableDescription, - cloudActionVariableDescription, - containerActionVariableDescription, - groupByKeysActionVariableDescription, - hostActionVariableDescription, - labelsActionVariableDescription, - orchestratorActionVariableDescription, - reasonActionVariableDescription, - tagsActionVariableDescription, - timestampActionVariableDescription, - valueActionVariableDescription, -} from './messages'; -import { oneOfLiterals, validateKQLStringFilter } from './utils'; -import { - createMetricThresholdExecutor, - FIRED_ACTIONS, - NO_DATA_ACTIONS, -} from './threshold_executor'; -import { ObservabilityConfig } from '../../..'; -import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/threshold_rule/constants'; - -export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { - context: THRESHOLD_RULE_REGISTRATION_CONTEXT, - mappings: { fieldMap: legacyExperimentalFieldMap }, - useEcs: true, - useLegacyAlerts: false, -}; - -type CreateLifecycleExecutor = ReturnType; - -export function thresholdRuleType( - createLifecycleRuleExecutor: CreateLifecycleExecutor, - basePath: IBasePath, - config: ObservabilityConfig, - logger: Logger, - ruleDataClient: IRuleDataClient, - alertsLocator?: LocatorPublic -) { - const baseCriterion = { - threshold: schema.arrayOf(schema.number()), - comparator: oneOfLiterals(Object.values(Comparator)), - timeUnit: schema.string(), - timeSize: schema.number(), - warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))), - }; - - const nonCountCriterion = schema.object({ - ...baseCriterion, - metric: schema.string(), - aggType: oneOfLiterals(METRIC_EXPLORER_AGGREGATIONS), - customMetrics: schema.never(), - equation: schema.never(), - label: schema.never(), - }); - - const countCriterion = schema.object({ - ...baseCriterion, - aggType: schema.literal('count'), - metric: schema.never(), - customMetrics: schema.never(), - equation: schema.never(), - label: schema.never(), - }); - - const customCriterion = schema.object({ - ...baseCriterion, - aggType: schema.literal('custom'), - metric: schema.never(), - customMetrics: schema.arrayOf( - schema.oneOf([ - schema.object({ - name: schema.string(), - aggType: oneOfLiterals(['avg', 'sum', 'max', 'min', 'cardinality']), - field: schema.string(), - filter: schema.never(), - }), - schema.object({ - name: schema.string(), - aggType: schema.literal('count'), - filter: schema.maybe( - schema.string({ - validate: validateKQLStringFilter, - }) - ), - field: schema.never(), - }), - ]) - ), - equation: schema.maybe(schema.string()), - label: schema.maybe(schema.string()), - }); - - return { - id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - name: i18n.translate('xpack.observability.threshold.ruleName', { - defaultMessage: 'Threshold (Technical Preview)', - }), - validate: { - params: schema.object( - { - criteria: schema.arrayOf( - schema.oneOf([countCriterion, nonCountCriterion, customCriterion]) - ), - groupBy: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), - filterQuery: schema.maybe( - schema.string({ - validate: validateKQLStringFilter, - }) - ), - alertOnNoData: schema.maybe(schema.boolean()), - alertOnGroupDisappear: schema.maybe(schema.boolean()), - searchConfiguration: schema.object({ - index: schema.string(), - query: schema.object({ - language: schema.string(), - query: schema.string(), - }), - }), - }, - { unknowns: 'allow' } - ), - }, - defaultActionGroupId: FIRED_ACTIONS.id, - actionGroups: [FIRED_ACTIONS, NO_DATA_ACTIONS], - minimumLicenseRequired: 'basic' as LicenseType, - isExportable: true, - executor: createLifecycleRuleExecutor( - createMetricThresholdExecutor({ alertsLocator, basePath, logger, config }) - ), - doesSetRecoveryContext: true, - actionVariables: { - context: [ - { name: 'groupings', description: groupByKeysActionVariableDescription }, - { - name: 'alertDetailsUrl', - description: alertDetailUrlActionVariableDescription, - usesPublicBaseUrl: true, - }, - { name: 'reason', description: reasonActionVariableDescription }, - { name: 'timestamp', description: timestampActionVariableDescription }, - { name: 'value', description: valueActionVariableDescription }, - { name: 'cloud', description: cloudActionVariableDescription }, - { name: 'host', description: hostActionVariableDescription }, - { name: 'container', description: containerActionVariableDescription }, - { name: 'orchestrator', description: orchestratorActionVariableDescription }, - { name: 'labels', description: labelsActionVariableDescription }, - { name: 'tags', description: tagsActionVariableDescription }, - ], - }, - useSavedObjectReferences: { - // TODO revisit types https://github.com/elastic/kibana/issues/159714 - extractReferences: (params: any) => { - const [searchConfiguration, references] = extractReferences(params.searchConfiguration); - const newParams = { ...params, searchConfiguration } as EsQueryRuleParamsExtractedParams; - - return { params: newParams, references }; - }, - injectReferences: (params: any, references: any) => { - return { - ...params, - searchConfiguration: injectReferences(params.searchConfiguration, references), - }; - }, - }, - producer: observabilityFeatureId, - alerts: MetricsRulesTypeAlertDefinition, - getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) => - observabilityPaths.ruleDetails(rule.id), - }; -} diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts deleted file mode 100644 index 1cf093ab91eee..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts +++ /dev/null @@ -1,2017 +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 { AlertInstanceState as AlertState } from '@kbn/alerting-plugin/server'; -import { - AlertInstanceMock, - RuleExecutorServicesMock, - alertsMock, -} from '@kbn/alerting-plugin/server/mocks'; -import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server'; -import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; -import { - createMetricThresholdExecutor, - FIRED_ACTIONS, - MetricThresholdAlertContext, - NO_DATA_ACTIONS, -} from './threshold_executor'; -import { Evaluation } from './lib/evaluate_rule'; -import type { LogMeta, Logger } from '@kbn/logging'; -import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; -import { - Aggregators, - Comparator, - CountMetricExpressionParams, - NonCountMetricExpressionParams, -} from '../../../../common/threshold_rule/types'; - -jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); - -interface AlertTestInstance { - instance: AlertInstanceMock; - actionQueue: any[]; - state: any; -} - -const persistAlertInstances = false; - -type TestRuleState = Record & { - aRuleStateKey: string; - groups: string[]; - groupBy?: string | string[]; -}; - -const initialRuleState: TestRuleState = { - aRuleStateKey: 'INITIAL_RULE_STATE_VALUE', - groups: [], -}; - -const fakeLogger = (msg: string, meta?: Meta) => {}; - -const logger = { - trace: fakeLogger, - debug: fakeLogger, - info: fakeLogger, - warn: fakeLogger, - error: fakeLogger, - fatal: fakeLogger, - log: () => void 0, - get: () => logger, -} as unknown as Logger; - -const STARTED_AT_MOCK_DATE = new Date(); - -const mockOptions = { - executionId: '', - startedAt: STARTED_AT_MOCK_DATE, - previousStartedAt: null, - state: { - wrapped: initialRuleState, - trackedAlerts: { - TEST_ALERT_0: { - alertId: 'TEST_ALERT_0', - alertUuid: 'TEST_ALERT_0_UUID', - started: '2020-01-01T12:00:00.000Z', - flappingHistory: [], - flapping: false, - pendingRecoveredCount: 0, - }, - TEST_ALERT_1: { - alertId: 'TEST_ALERT_1', - alertUuid: 'TEST_ALERT_1_UUID', - started: '2020-01-02T12:00:00.000Z', - flappingHistory: [], - flapping: false, - pendingRecoveredCount: 0, - }, - }, - trackedAlertsRecovered: {}, - }, - spaceId: '', - rule: { - id: '', - name: '', - tags: [], - consumer: '', - enabled: true, - schedule: { - interval: '1h', - }, - actions: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - throttle: null, - notifyWhen: null, - producer: '', - revision: 0, - ruleTypeId: '', - ruleTypeName: '', - muteAll: false, - snoozeSchedule: [], - }, - logger, - flappingSettings: DEFAULT_FLAPPING_SETTINGS, -}; - -const setEvaluationResults = (response: Array>) => { - jest.requireMock('./lib/evaluate_rule').evaluateRule.mockImplementation(() => response); -}; - -// FAILING: https://github.com/elastic/kibana/issues/155534 -describe.skip('The metric threshold alert type', () => { - describe('querying the entire infrastructure', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - sourceId, - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold, - }, - ], - }, - }); - const setResults = ( - comparator: Comparator, - threshold: number[], - shouldFire: boolean = false, - shouldWarn: boolean = false, - isNoData: boolean = false - ) => - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator, - threshold, - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire, - shouldWarn, - isNoData, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - test('alerts as expected with the > comparator', async () => { - setResults(Comparator.GT, [0.75], true); - await execute(Comparator.GT, [0.75]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.GT, [1.5], false); - await execute(Comparator.GT, [1.5]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - test('alerts as expected with the < comparator', async () => { - setResults(Comparator.LT, [1.5], true); - await execute(Comparator.LT, [1.5]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.LT, [0.75], false); - await execute(Comparator.LT, [0.75]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - test('alerts as expected with the >= comparator', async () => { - setResults(Comparator.GT_OR_EQ, [0.75], true); - await execute(Comparator.GT_OR_EQ, [0.75]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.GT_OR_EQ, [1.0], true); - await execute(Comparator.GT_OR_EQ, [1.0]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.GT_OR_EQ, [1.5], false); - await execute(Comparator.GT_OR_EQ, [1.5]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - test('alerts as expected with the <= comparator', async () => { - setResults(Comparator.LT_OR_EQ, [1.5], true); - await execute(Comparator.LT_OR_EQ, [1.5]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.LT_OR_EQ, [1.0], true); - await execute(Comparator.LT_OR_EQ, [1.0]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.LT_OR_EQ, [0.75], false); - await execute(Comparator.LT_OR_EQ, [0.75]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - test('alerts as expected with the between comparator', async () => { - setResults(Comparator.BETWEEN, [0, 1.5], true); - await execute(Comparator.BETWEEN, [0, 1.5]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.BETWEEN, [0, 0.75], false); - await execute(Comparator.BETWEEN, [0, 0.75]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - test('alerts as expected with the outside range comparator', async () => { - setResults(Comparator.OUTSIDE_RANGE, [0, 0.75], true); - await execute(Comparator.OUTSIDE_RANGE, [0, 0.75]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setResults(Comparator.OUTSIDE_RANGE, [0, 1.5], false); - await execute(Comparator.OUTSIDE_RANGE, [0, 1.5]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - test('reports expected values to the action context', async () => { - setResults(Comparator.GT, [0.75], true); - await execute(Comparator.GT, [0.75]); - const { action } = mostRecentAction(instanceID); - expect(action.group).toBe('*'); - expect(action.reason).toContain('is 1'); - expect(action.reason).toContain('Alert when > 0.75'); - expect(action.reason).toContain('test.metric.1'); - expect(action.reason).toContain('in the last 1 min'); - }); - }); - - describe('querying with a groupBy parameter', () => { - afterAll(() => clearInstances()); - const execute = ( - comparator: Comparator, - threshold: number[], - groupBy: string[] = ['something'], - metric?: string, - state?: any - ) => - executor({ - ...mockOptions, - services, - params: { - groupBy, - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold, - metric: metric ?? baseNonCountCriterion.metric, - }, - ], - }, - state: state ?? mockOptions.state.wrapped, - }); - const instanceIdA = 'a'; - const instanceIdB = 'b'; - test('sends an alert when all groups pass the threshold', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await execute(Comparator.GT, [0.75]); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBeAlertAction(); - }); - test('sends an alert when only some groups pass the threshold', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.LT, - threshold: [1.5], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.LT, - threshold: [1.5], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await execute(Comparator.LT, [1.5]); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBe(undefined); - }); - test('sends no alert when no groups pass the threshold', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [5], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [5], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await execute(Comparator.GT, [5]); - expect(mostRecentAction(instanceIdA)).toBe(undefined); - expect(mostRecentAction(instanceIdB)).toBe(undefined); - }); - test('reports group values to the action context', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await execute(Comparator.GT, [0.75]); - expect(mostRecentAction(instanceIdA).action.group).toBe('a'); - expect(mostRecentAction(instanceIdB).action.group).toBe('b'); - }); - test('persists previous groups that go missing, until the groupBy param changes', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.2', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - c: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'c' }, - }, - }, - ]); - const { state: stateResult1 } = await execute( - Comparator.GT, - [0.75], - ['something'], - 'test.metric.2' - ); - expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([])); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - c: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: 'c' }, - }, - }, - ]); - const { state: stateResult2 } = await execute( - Comparator.GT, - [0.75], - ['something'], - 'test.metric.1', - stateResult1 - ); - expect(stateResult2.missingGroups).toEqual( - expect.arrayContaining([{ key: 'c', bucketKey: { groupBy0: 'c' } }]) - ); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - const { state: stateResult3 } = await execute( - Comparator.GT, - [0.75], - ['something', 'something-else'], - 'test.metric.1', - stateResult2 - ); - expect(stateResult3.missingGroups).toEqual(expect.arrayContaining([])); - }); - - const executeWithFilter = ( - comparator: Comparator, - threshold: number[], - filterQuery: string, - metric?: string, - state?: any - ) => - executor({ - ...mockOptions, - services, - params: { - groupBy: ['something'], - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold, - metric: metric ?? baseNonCountCriterion.metric, - }, - ], - filterQuery, - }, - state: state ?? mockOptions.state.wrapped, - }); - test('persists previous groups that go missing, until the filterQuery param changes', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.2', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - c: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'c' }, - }, - }, - ]); - const { state: stateResult1 } = await executeWithFilter( - Comparator.GT, - [0.75], - JSON.stringify({ query: 'q' }), - 'test.metric.2' - ); - expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([])); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - c: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: 'c' }, - }, - }, - ]); - const { state: stateResult2 } = await executeWithFilter( - Comparator.GT, - [0.75], - JSON.stringify({ query: 'q' }), - 'test.metric.1', - stateResult1 - ); - expect(stateResult2.missingGroups).toEqual( - expect.arrayContaining([{ key: 'c', bucketKey: { groupBy0: 'c' } }]) - ); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - const { state: stateResult3 } = await executeWithFilter( - Comparator.GT, - [0.75], - JSON.stringify({ query: 'different' }), - 'test.metric.1', - stateResult2 - ); - expect(stateResult3.groups).toEqual(expect.arrayContaining([])); - }); - }); - - describe('querying with a groupBy parameter host.name and rule tags', () => { - afterAll(() => clearInstances()); - const execute = ( - comparator: Comparator, - threshold: number[], - groupBy: string[] = ['host.name'], - metric?: string, - state?: any - ) => - executor({ - ...mockOptions, - services, - params: { - groupBy, - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold, - metric: metric ?? baseNonCountCriterion.metric, - }, - ], - }, - state: state ?? mockOptions.state.wrapped, - rule: { - ...mockOptions.rule, - tags: ['ruleTag1', 'ruleTag2'], - }, - }); - const instanceIdA = 'host-01'; - const instanceIdB = 'host-02'; - - test('rule tags and source tags are combined in alert context', async () => { - setEvaluationResults([ - { - 'host-01': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'host-01' }, - context: { - tags: ['host-01_tag1', 'host-01_tag2'], - }, - }, - 'host-02': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'host-02' }, - context: { - tags: ['host-02_tag1', 'host-02_tag2'], - }, - }, - }, - ]); - await execute(Comparator.GT, [0.75]); - expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ - 'host-01_tag1', - 'host-01_tag2', - 'ruleTag1', - 'ruleTag2', - ]); - expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ - 'host-02_tag1', - 'host-02_tag2', - 'ruleTag1', - 'ruleTag2', - ]); - }); - }); - - describe('querying without a groupBy parameter and rule tags', () => { - afterAll(() => clearInstances()); - const execute = ( - comparator: Comparator, - threshold: number[], - groupBy: string = '', - metric?: string, - state?: any - ) => - executor({ - ...mockOptions, - services, - params: { - groupBy, - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold, - metric: metric ?? baseNonCountCriterion.metric, - }, - ], - }, - state: state ?? mockOptions.state.wrapped, - rule: { - ...mockOptions.rule, - tags: ['ruleTag1', 'ruleTag2'], - }, - }); - - test('rule tags are added in alert context', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.75], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - - const instanceID = '*'; - await execute(Comparator.GT, [0.75]); - expect(mostRecentAction(instanceID).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); - }); - }); - - describe('querying with multiple criteria', () => { - afterAll(() => clearInstances()); - const execute = ( - comparator: Comparator, - thresholdA: number[], - thresholdB: number[], - groupBy: string = '', - sourceId: string = 'default' - ) => - executor({ - ...mockOptions, - services, - params: { - sourceId, - groupBy, - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold: thresholdA, - }, - { - ...baseNonCountCriterion, - comparator, - threshold: thresholdB, - metric: 'test.metric.2', - }, - ], - }, - }); - test('sends an alert when all criteria cross the threshold', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [1.0], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [3.0], - metric: 'test.metric.2', - currentValue: 3.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - const instanceID = '*'; - await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - }); - test('sends no alert when some, but not all, criteria cross the threshold', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.LT_OR_EQ, - threshold: [1.0], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - {}, - ]); - const instanceID = '*'; - await execute(Comparator.LT_OR_EQ, [1.0], [2.5]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - test('alerts only on groups that meet all criteria when querying with a groupBy parameter', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [1.0], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [1.0], - metric: 'test.metric.1', - currentValue: 3.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [3.0], - metric: 'test.metric.2', - currentValue: 3.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [3.0], - metric: 'test.metric.2', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - const instanceIdA = 'a'; - const instanceIdB = 'b'; - await execute(Comparator.GT_OR_EQ, [1.0], [3.0], 'something'); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBe(undefined); - }); - test('sends all criteria to the action context', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [1.0], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT_OR_EQ, - threshold: [3.0], - metric: 'test.metric.2', - currentValue: 3.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - const instanceID = '*'; - await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); - const { action } = mostRecentAction(instanceID); - const reasons = action.reason.split('\n'); - expect(reasons.length).toBe(2); - expect(reasons[0]).toContain('test.metric.1'); - expect(reasons[1]).toContain('test.metric.2'); - expect(reasons[0]).toContain('is 1'); - expect(reasons[1]).toContain('is 3'); - expect(reasons[0]).toContain('Alert when >= 1'); - expect(reasons[1]).toContain('Alert when >= 3'); - expect(reasons[0]).toContain('in the last 1 min'); - expect(reasons[1]).toContain('in the last 1 min'); - }); - }); - describe('querying with the count aggregator', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - sourceId, - criteria: [ - { - ...baseCountCriterion, - comparator, - threshold, - } as CountMetricExpressionParams, - ], - }, - }); - test('alerts based on the doc_count value instead of the aggregatedValue', async () => { - setEvaluationResults([ - { - '*': { - ...baseCountCriterion, - comparator: Comparator.GT, - threshold: [0.9], - metric: 'count', - currentValue: 1, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - }, - ]); - await execute(Comparator.GT, [0.9]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setEvaluationResults([ - { - '*': { - ...baseCountCriterion, - comparator: Comparator.LT, - threshold: [0.5], - metric: 'count', - currentValue: 1, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - }, - ]); - await execute(Comparator.LT, [0.5]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - describe('with a groupBy parameter', () => { - const executeGroupBy = ( - comparator: Comparator, - threshold: number[], - sourceId: string = 'default', - state?: any - ) => - executor({ - ...mockOptions, - services, - params: { - sourceId, - groupBy: 'something', - criteria: [ - { - ...baseCountCriterion, - comparator, - threshold, - }, - ], - }, - state: state ?? mockOptions.state.wrapped, - }); - const instanceIdA = 'a'; - const instanceIdB = 'b'; - - test('successfully detects and alerts on a document count of 0', async () => { - setEvaluationResults([ - { - a: { - ...baseCountCriterion, - comparator: Comparator.LT_OR_EQ, - threshold: [0], - metric: 'count', - currentValue: 1, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseCountCriterion, - comparator: Comparator.LT_OR_EQ, - threshold: [0], - metric: 'count', - currentValue: 1, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - const resultState = await executeGroupBy(Comparator.LT_OR_EQ, [0]); - expect(mostRecentAction(instanceIdA)).toBe(undefined); - expect(mostRecentAction(instanceIdB)).toBe(undefined); - setEvaluationResults([ - { - a: { - ...baseCountCriterion, - comparator: Comparator.LT_OR_EQ, - threshold: [0], - metric: 'count', - currentValue: 0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseCountCriterion, - comparator: Comparator.LT_OR_EQ, - threshold: [0], - metric: 'count', - currentValue: 0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await executeGroupBy(Comparator.LT_OR_EQ, [0], 'empty-response', resultState); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBeAlertAction(); - }); - }); - }); - describe('querying with the p99 aggregator', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold, - aggType: Aggregators.P99, - metric: 'test.metric.2', - }, - ], - }, - }); - test('alerts based on the p99 values', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [1], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.GT, [1]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.LT, - threshold: [1], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.LT, [1]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - }); - describe('querying with the p95 aggregator', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - sourceId, - criteria: [ - { - ...baseNonCountCriterion, - comparator, - threshold, - aggType: Aggregators.P95, - metric: 'test.metric.1', - }, - ], - }, - }); - test('alerts based on the p95 values', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.25], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.GT, [0.25]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.LT, - threshold: [0.95], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.LT, [0.95]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - }); - describe("querying a metric that hasn't reported data", () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (alertOnNoData: boolean, sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - sourceId, - criteria: [ - { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [1], - metric: 'test.metric.3', - }, - ], - alertOnNoData, - }, - }); - test('sends a No Data alert when configured to do so', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.LT, - threshold: [1], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(true); - const recentAction = mostRecentAction(instanceID); - expect(recentAction.action.reason).toEqual('test.metric.3 reported no data in the last 1m'); - expect(recentAction).toBeNoDataAction(); - }); - test('does not send a No Data alert when not configured to do so', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.LT, - threshold: [1], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(false); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - }); - - describe('alerts with NO_DATA where one condtion is an aggregation and the other is a document count', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (alertOnNoData: boolean, sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - sourceId, - criteria: [ - { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [1], - metric: 'test.metric.3', - }, - { - ...baseCountCriterion, - comparator: Comparator.GT, - threshold: [30], - }, - ], - alertOnNoData, - }, - }); - test('sends a No Data alert when configured to do so', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.LT, - threshold: [1], - metric: 'test.metric.3', - currentValue: null, - timestamp: STARTED_AT_MOCK_DATE.toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: '*' }, - }, - }, - {}, - ]); - await execute(true); - const recentAction = mostRecentAction(instanceID); - expect(recentAction.action).toEqual({ - alertDetailsUrl: '', - alertState: 'NO DATA', - group: '*', - groupByKeys: undefined, - metric: { condition0: 'test.metric.3', condition1: 'count' }, - reason: 'test.metric.3 reported no data in the last 1m', - threshold: { condition0: ['1'], condition1: [30] }, - timestamp: STARTED_AT_MOCK_DATE.toISOString(), - value: { condition0: '[NO DATA]', condition1: 0 }, - viewInAppUrl: 'http://localhost:5601/app/metrics/explorer', - tags: [], - }); - expect(recentAction).toBeNoDataAction(); - }); - }); - - describe('querying a groupBy alert that starts reporting no data, and then later reports data', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const instanceIdA = 'a'; - const instanceIdB = 'b'; - const instanceIdC = 'c'; - const execute = (metric: string, alertOnGroupDisappear: boolean = true, state?: any) => - executor({ - ...mockOptions, - services, - params: { - groupBy: 'something', - sourceId: 'default', - criteria: [ - { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric, - }, - ], - alertOnNoData: true, - alertOnGroupDisappear, - }, - state: state ?? mockOptions.state.wrapped, - }); - - const executeEmptyResponse = (...args: [boolean?, any?]) => execute('test.metric.3', ...args); - const execute3GroupsABCResponse = (...args: [boolean?, any?]) => - execute('test.metric.2', ...args); - const execute2GroupsABResponse = (...args: [boolean?, any?]) => - execute('test.metric.1', ...args); - - // Store state between tests. Jest won't preserve reassigning a let so use an array instead. - const interTestStateStorage: any[] = []; - - test('first sends a No Data alert with the * group, but then reports groups when data is available', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - let resultState = await executeEmptyResponse(); - expect(mostRecentAction(instanceID)).toBeNoDataAction(); - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - resultState = await executeEmptyResponse(true, resultState); - expect(mostRecentAction(instanceID)).toBeNoDataAction(); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.1', - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - resultState = await execute2GroupsABResponse(true, resultState); - expect(mostRecentAction(instanceID)).toBe(undefined); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBeAlertAction(); - interTestStateStorage.push(resultState); // Hand off resultState to the next test - }); - test('sends No Data alerts for the previously detected groups when they stop reporting data, but not the * group', async () => { - // Pop a previous execution result instead of defining it manually - // The type signature of alert executor states are complex - const resultState = interTestStateStorage.pop(); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await executeEmptyResponse(true, resultState); - expect(mostRecentAction(instanceID)).toBe(undefined); - expect(mostRecentAction(instanceIdA)).toBeNoDataAction(); - expect(mostRecentAction(instanceIdB)).toBeNoDataAction(); - }); - test('does not send individual No Data alerts when groups disappear if alertOnGroupDisappear is disabled', async () => { - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.2', - currentValue: 1, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - c: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.2', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'c' }, - }, - }, - ]); - const resultState = await execute3GroupsABCResponse(false); - expect(mostRecentAction(instanceID)).toBe(undefined); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBeAlertAction(); - expect(mostRecentAction(instanceIdC)).toBeAlertAction(); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.1', - currentValue: 1, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await execute2GroupsABResponse(false, resultState); - expect(mostRecentAction(instanceID)).toBe(undefined); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBeAlertAction(); - expect(mostRecentAction(instanceIdC)).toBe(undefined); - }); - - describe('if alertOnNoData is disabled but alertOnGroupDisappear is enabled', () => { - const executeWeirdNoDataConfig = (metric: string, state?: any) => - executor({ - ...mockOptions, - services, - params: { - groupBy: 'something', - sourceId: 'default', - criteria: [ - { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric, - }, - ], - alertOnNoData: false, - alertOnGroupDisappear: true, - }, - state: state ?? mockOptions.state.wrapped, - }); - - const executeWeirdEmptyResponse = (...args: [any?]) => - executeWeirdNoDataConfig('test.metric.3', ...args); - const executeWeird2GroupsABResponse = (...args: [any?]) => - executeWeirdNoDataConfig('test.metric.1', ...args); - - test('does not send a No Data alert with the * group, but then reports groups when data is available', async () => { - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - let resultState = await executeWeirdEmptyResponse(); - expect(mostRecentAction(instanceID)).toBe(undefined); - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - resultState = await executeWeirdEmptyResponse(resultState); - expect(mostRecentAction(instanceID)).toBe(undefined); - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.1', - currentValue: 1, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.1', - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - shouldWarn: false, - isNoData: false, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - resultState = await executeWeird2GroupsABResponse(resultState); - expect(mostRecentAction(instanceID)).toBe(undefined); - expect(mostRecentAction(instanceIdA)).toBeAlertAction(); - expect(mostRecentAction(instanceIdB)).toBeAlertAction(); - interTestStateStorage.push(resultState); // Hand off resultState to the next test - }); - test('sends No Data alerts for the previously detected groups when they stop reporting data, but not the * group', async () => { - const resultState = interTestStateStorage.pop(); // Import the resultState from the previous test - setEvaluationResults([ - { - a: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: 'a' }, - }, - b: { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [0], - metric: 'test.metric.3', - currentValue: null, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn: false, - isNoData: true, - bucketKey: { groupBy0: 'b' }, - }, - }, - ]); - await executeWeirdEmptyResponse(resultState); - expect(mostRecentAction(instanceID)).toBe(undefined); - expect(mostRecentAction(instanceIdA)).toBeNoDataAction(); - expect(mostRecentAction(instanceIdB)).toBeNoDataAction(); - }); - }); - }); - - describe('attempting to use a malformed filterQuery', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = () => - executor({ - ...mockOptions, - services, - params: { - criteria: [ - { - ...baseNonCountCriterion, - }, - ], - sourceId: 'default', - filterQuery: - 'host.name:(look.there.is.no.space.after.these.parentheses)and uh.oh: "wow that is bad"', - }, - }); - test('reports an error', async () => { - await execute(); - expect(mostRecentAction(instanceID)).toBeErrorAction(); - }); - }); - - describe('querying the entire infrastructure with warning threshold', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - - const execute = () => - executor({ - ...mockOptions, - services, - params: { - sourceId: 'default', - criteria: [ - { - ...baseNonCountCriterion, - comparator: Comparator.GT, - threshold: [9999], - }, - ], - }, - }); - - const setResults = ({ - comparator = Comparator.GT, - threshold = [9999], - warningComparator = Comparator.GT, - warningThreshold = [2.49], - metric = 'test.metric.1', - currentValue = 7.59, - shouldWarn = false, - }) => - setEvaluationResults([ - { - '*': { - ...baseNonCountCriterion, - comparator, - threshold, - warningComparator, - warningThreshold, - metric, - currentValue, - timestamp: new Date().toISOString(), - shouldFire: false, - shouldWarn, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - - test('warns as expected with the > comparator', async () => { - setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); - await execute(); - expect(mostRecentAction(instanceID)).toBeWarnAction(); - - setResults({ warningThreshold: [2.49], currentValue: 1.23, shouldWarn: false }); - await execute(); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - - test('reports expected warning values to the action context', async () => { - setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); - await execute(); - - const { action } = mostRecentAction(instanceID); - expect(action.group).toBe('*'); - expect(action.reason).toBe('test.metric.1 is 2.5 in the last 1 min. Alert when > 2.49.'); - }); - - test('reports expected warning values to the action context for percentage metric', async () => { - setResults({ - warningThreshold: [0.81], - currentValue: 0.82, - shouldWarn: true, - metric: 'system.cpu.user.pct', - }); - await execute(); - - const { action } = mostRecentAction(instanceID); - expect(action.group).toBe('*'); - expect(action.reason).toBe('system.cpu.user.pct is 82% in the last 1 min. Alert when > 81%.'); - }); - }); -}); - -const mockLibs: any = { - threshold_rule: { - group_by_page_size: 10000, - }, - basePath: { - publicBaseUrl: 'http://localhost:5601', - prepend: (path: string) => path, - }, - logger, - config: { - thresholdRule: { - groupByPageSize: 10_000, - }, - }, -}; - -const executor = createMetricThresholdExecutor(mockLibs); - -const alertsServices = alertsMock.createRuleExecutorServices(); -const services: RuleExecutorServicesMock & - LifecycleAlertServices = { - ...alertsServices, - ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), -}; -services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => { - if (sourceId === 'alternate') - return { - id: 'alternate', - attributes: { metricAlias: 'alternatebeat-*' }, - type, - references: [], - }; - if (sourceId === 'empty-response') - return { - id: 'empty', - attributes: { metricAlias: 'empty-response' }, - type, - references: [], - }; - return { id: 'default', attributes: { metricAlias: 'metricbeat-*' }, type, references: [] }; -}); - -const alertInstances = new Map(); -services.alertFactory.create.mockImplementation((instanceID: string) => { - const newAlertInstance: AlertTestInstance = { - instance: alertsMock.createAlertFactory.create(), - actionQueue: [], - state: {}, - }; - const alertInstance: AlertTestInstance = persistAlertInstances - ? alertInstances.get(instanceID) || newAlertInstance - : newAlertInstance; - alertInstances.set(instanceID, alertInstance); - - alertInstance.instance.replaceState.mockImplementation((newState: any) => { - alertInstance.state = newState; - return alertInstance.instance; - }); - (alertInstance.instance.scheduleActions as jest.Mock).mockImplementation( - (id: string, action: any) => { - alertInstance.actionQueue.push({ id, action }); - return alertInstance.instance; - } - ); - return alertInstance.instance; -}); - -function mostRecentAction(id: string) { - const instance = alertInstances.get(id); - if (!instance) return undefined; - return instance.actionQueue.pop(); -} - -function clearInstances() { - alertInstances.clear(); -} - -interface Action { - id: string; - action: { alertState: string }; -} - -expect.extend({ - toBeAlertAction(action?: Action) { - const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ALERT'; - const message = () => `expected ${action} to be an ALERT action`; - return { - message, - pass, - }; - }, - toBeNoDataAction(action?: Action) { - const pass = action?.id === NO_DATA_ACTIONS.id && action?.action.alertState === 'NO DATA'; - const message = () => `expected ${action} to be a NO DATA action`; - return { - message, - pass, - }; - }, - toBeErrorAction(action?: Action) { - const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ERROR'; - const message = () => `expected ${action} to be an ERROR action`; - return { - message, - pass, - }; - }, -}); - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace jest { - interface Matchers { - toBeAlertAction(action?: Action): R; - toBeWarnAction(action?: Action): R; - toBeNoDataAction(action?: Action): R; - toBeErrorAction(action?: Action): R; - } - } -} - -const baseNonCountCriterion = { - aggType: Aggregators.AVERAGE, - metric: 'test.metric.1', - timeSize: 1, - timeUnit: 'm', - threshold: [0], - comparator: Comparator.GT, -} as NonCountMetricExpressionParams; - -const baseCountCriterion = { - aggType: Aggregators.COUNT, - timeSize: 1, - timeUnit: 'm', - threshold: [0], - comparator: Comparator.GT, -} as CountMetricExpressionParams; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts deleted file mode 100644 index 6429cf164262c..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts +++ /dev/null @@ -1,417 +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 { i18n } from '@kbn/i18n'; -import { ALERT_ACTION_GROUP, ALERT_EVALUATION_VALUES, ALERT_REASON } from '@kbn/rule-data-utils'; -import { LocatorPublic } from '@kbn/share-plugin/common'; -import { isEqual } from 'lodash'; -import { - ActionGroupIdsOf, - AlertInstanceState as AlertState, - RecoveredActionGroup, -} from '@kbn/alerting-plugin/common'; -import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server'; -import { IBasePath, Logger } from '@kbn/core/server'; -import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server'; -import { AlertsLocatorParams, getAlertUrl, TimeUnitChar } from '../../../../common'; -import { createFormatter } from '../../../../common/threshold_rule/formatters'; -import { Comparator } from '../../../../common/threshold_rule/types'; -import { ObservabilityConfig } from '../../..'; -import { AlertStates } from './types'; - -import { - buildFiredAlertReason, - buildNoDataAlertReason, - // buildRecoveredAlertReason, -} from './messages'; -import { - createScopedLogger, - AdditionalContext, - getContextForRecoveredAlerts, - UNGROUPED_FACTORY_KEY, - hasAdditionalContext, - validGroupByForContext, - flattenAdditionalContext, - getGroupByObject, -} from './utils'; - -import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; -import { MissingGroupsRecord } from './lib/check_missing_group'; -import { convertStringsToMissingGroupsRecord } from './lib/convert_strings_to_missing_groups_record'; - -export type MetricThresholdRuleParams = Record; -export type MetricThresholdRuleTypeState = RuleTypeState & { - lastRunTimestamp?: number; - missingGroups?: Array; - groupBy?: string | string[]; -}; -export type MetricThresholdAlertState = AlertState; // no specific instance state used - -export interface MetricThresholdAlertContext extends Record { - alertDetailsUrl: string; - groupings?: object; - reason?: string; - timestamp: string; // ISO string - value?: Array | null; -} - -export const FIRED_ACTIONS_ID = 'threshold.fired'; -export const NO_DATA_ACTIONS_ID = 'threshold.nodata'; - -type MetricThresholdActionGroup = - | typeof FIRED_ACTIONS_ID - | typeof NO_DATA_ACTIONS_ID - | typeof RecoveredActionGroup.id; - -type MetricThresholdAllowedActionGroups = ActionGroupIdsOf< - typeof FIRED_ACTIONS | typeof NO_DATA_ACTIONS ->; - -type MetricThresholdAlert = Alert< - MetricThresholdAlertState, - MetricThresholdAlertContext, - MetricThresholdAllowedActionGroups ->; - -type MetricThresholdAlertFactory = ( - id: string, - reason: string, - actionGroup: MetricThresholdActionGroup, - additionalContext?: AdditionalContext | null, - evaluationValues?: Array -) => MetricThresholdAlert; - -export const createMetricThresholdExecutor = ({ - alertsLocator, - basePath, - logger, - config, -}: { - basePath: IBasePath; - logger: Logger; - config: ObservabilityConfig; - alertsLocator?: LocatorPublic; -}): LifecycleRuleExecutor< - MetricThresholdRuleParams, - MetricThresholdRuleTypeState, - MetricThresholdAlertState, - MetricThresholdAlertContext, - MetricThresholdAllowedActionGroups -> => - async function (options) { - const startTime = Date.now(); - - const { - services, - params, - state, - startedAt, - executionId, - spaceId, - rule: { id: ruleId }, - } = options; - - const { criteria } = params; - if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); - const thresholdLogger = createScopedLogger(logger, 'thresholdRule', { - alertId: ruleId, - executionId, - }); - - // TODO: check if we need to use "savedObjectsClient"=> https://github.com/elastic/kibana/issues/159340 - const { - alertWithLifecycle, - getAlertUuid, - getAlertByAlertUuid, - getAlertStartedDate, - searchSourceClient, - } = services; - - const alertFactory: MetricThresholdAlertFactory = ( - id, - reason, - actionGroup, - additionalContext, - evaluationValues - ) => - alertWithLifecycle({ - id, - fields: { - [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, - [ALERT_EVALUATION_VALUES]: evaluationValues, - ...flattenAdditionalContext(additionalContext), - }, - }); - - const { alertOnNoData, alertOnGroupDisappear: _alertOnGroupDisappear } = params as { - alertOnNoData: boolean; - alertOnGroupDisappear: boolean | undefined; - }; - - // For backwards-compatibility, interpret undefined alertOnGroupDisappear as true - const alertOnGroupDisappear = _alertOnGroupDisappear !== false; - const compositeSize = config.thresholdRule.groupByPageSize; - const filterQueryIsSame = isEqual(state.filterQuery, params.filterQuery); - const groupByIsSame = isEqual(state.groupBy, params.groupBy); - const previousMissingGroups = - alertOnGroupDisappear && filterQueryIsSame && groupByIsSame && state.missingGroups - ? state.missingGroups - : []; - - const initialSearchSource = await searchSourceClient.create(params.searchConfiguration!); - const dataView = initialSearchSource.getField('index')!.getIndexPattern(); - const timeFieldName = initialSearchSource.getField('index')?.timeFieldName; - if (!dataView) { - throw new Error('No matched data view'); - } else if (!timeFieldName) { - throw new Error('The selected data view does not have a timestamp field'); - } - - const alertResults = await evaluateRule( - services.scopedClusterClient.asCurrentUser, - params as EvaluatedRuleParams, - dataView, - timeFieldName, - compositeSize, - alertOnGroupDisappear, - logger, - state.lastRunTimestamp, - { end: startedAt.valueOf() }, - convertStringsToMissingGroupsRecord(previousMissingGroups) - ); - - const resultGroupSet = new Set(); - for (const resultSet of alertResults) { - for (const group of Object.keys(resultSet)) { - resultGroupSet.add(group); - } - } - - const groupByKeysObjectMapping = getGroupByObject(params.groupBy, resultGroupSet); - const groups = [...resultGroupSet]; - const nextMissingGroups = new Set(); - const hasGroups = !isEqual(groups, [UNGROUPED_FACTORY_KEY]); - let scheduledActionsCount = 0; - - // The key of `groups` is the alert instance ID. - for (const group of groups) { - // AND logic; all criteria must be across the threshold - const shouldAlertFire = alertResults.every((result) => result[group]?.shouldFire); - // AND logic; because we need to evaluate all criteria, if one of them reports no data then the - // whole alert is in a No Data/Error state - const isNoData = alertResults.some((result) => result[group]?.isNoData); - - if (isNoData && group !== UNGROUPED_FACTORY_KEY) { - nextMissingGroups.add({ key: group, bucketKey: alertResults[0][group].bucketKey }); - } - - const nextState = isNoData - ? AlertStates.NO_DATA - : shouldAlertFire - ? AlertStates.ALERT - : AlertStates.OK; - - let reason; - if (nextState === AlertStates.ALERT) { - reason = alertResults - .map((result) => - buildFiredAlertReason({ - ...formatAlertResult(result[group]), - group, - }) - ) - .join('\n'); - } - - /* NO DATA STATE HANDLING - * - * - `alertOnNoData` does not indicate IF the alert's next state is No Data, but whether or not the user WANTS TO BE ALERTED - * if the state were No Data. - * - `alertOnGroupDisappear`, on the other hand, determines whether or not it's possible to return a No Data state - * when a group disappears. - * - * This means we need to handle the possibility that `alertOnNoData` is false, but `alertOnGroupDisappear` is true - * - * nextState === NO_DATA would be true on both { '*': No Data } or, e.g. { 'a': No Data, 'b': OK, 'c': OK }, but if the user - * has for some reason disabled `alertOnNoData` and left `alertOnGroupDisappear` enabled, they would only care about the latter - * possibility. In this case, use hasGroups to determine whether to alert on a potential No Data state - * - * If `alertOnNoData` is true but `alertOnGroupDisappear` is false, we don't need to worry about the {a, b, c} possibility. - * At this point in the function, a false `alertOnGroupDisappear` would already have prevented group 'a' from being evaluated at all. - */ - if (alertOnNoData || (alertOnGroupDisappear && hasGroups)) { - // In the previous line we've determined if the user is interested in No Data states, so only now do we actually - // check to see if a No Data state has occurred - if (nextState === AlertStates.NO_DATA) { - reason = alertResults - .filter((result) => result[group]?.isNoData) - .map((result) => buildNoDataAlertReason({ ...result[group], group })) - .join('\n'); - } - } - - if (reason) { - const timestamp = startedAt.toISOString(); - const actionGroupId: MetricThresholdActionGroup = - nextState === AlertStates.OK - ? RecoveredActionGroup.id - : nextState === AlertStates.NO_DATA - ? NO_DATA_ACTIONS_ID - : FIRED_ACTIONS_ID; - - const additionalContext = hasAdditionalContext(params.groupBy, validGroupByForContext) - ? alertResults && alertResults.length > 0 - ? alertResults[0][group].context ?? {} - : {} - : {}; - - additionalContext.tags = Array.from( - new Set([...(additionalContext.tags ?? []), ...options.rule.tags]) - ); - - const evaluationValues = alertResults.reduce((acc: Array, result) => { - if (result[group]) { - acc.push(result[group].currentValue); - } - return acc; - }, []); - - const alert = alertFactory( - `${group}`, - reason, - actionGroupId, - additionalContext, - evaluationValues - ); - const alertUuid = getAlertUuid(group); - const indexedStartedAt = getAlertStartedDate(group) ?? startedAt.toISOString(); - scheduledActionsCount++; - - alert.scheduleActions(actionGroupId, { - alertDetailsUrl: await getAlertUrl( - alertUuid, - spaceId, - indexedStartedAt, - alertsLocator, - basePath.publicBaseUrl - ), - groupings: groupByKeysObjectMapping[group], - reason, - timestamp, - value: alertResults.map((result, index) => { - const evaluation = result[group]; - if (!evaluation && criteria[index].aggType === 'count') { - return 0; - } else if (!evaluation) { - return null; - } - return formatAlertResult(evaluation).currentValue; - }), - ...additionalContext, - }); - } - } - const { getRecoveredAlerts } = services.alertFactory.done(); - const recoveredAlerts = getRecoveredAlerts(); - - const groupByKeysObjectForRecovered = getGroupByObject( - params.groupBy, - new Set(recoveredAlerts.map((recoveredAlert) => recoveredAlert.getId())) - ); - - for (const alert of recoveredAlerts) { - const recoveredAlertId = alert.getId(); - const alertUuid = getAlertUuid(recoveredAlertId); - const timestamp = startedAt.toISOString(); - const indexedStartedAt = getAlertStartedDate(recoveredAlertId) ?? timestamp; - - const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; - const additionalContext = getContextForRecoveredAlerts(alertHits); - - alert.setContext({ - alertDetailsUrl: await getAlertUrl( - alertUuid, - spaceId, - indexedStartedAt, - alertsLocator, - basePath.publicBaseUrl - ), - groupings: groupByKeysObjectForRecovered[recoveredAlertId], - timestamp: startedAt.toISOString(), - ...additionalContext, - }); - } - - const stopTime = Date.now(); - thresholdLogger.debug( - `Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms` - ); - return { - state: { - lastRunTimestamp: startedAt.valueOf(), - missingGroups: [...nextMissingGroups], - groupBy: params.groupBy, - filterQuery: params.filterQuery, - }, - }; - }; - -export const FIRED_ACTIONS = { - id: 'threshold.fired', - name: i18n.translate('xpack.observability.threshold.rule.alerting.threshold.fired', { - defaultMessage: 'Alert', - }), -}; - -export const NO_DATA_ACTIONS = { - id: 'threshold.nodata', - name: i18n.translate('xpack.observability.threshold.rule.alerting.threshold.nodata', { - defaultMessage: 'No Data', - }), -}; - -const formatAlertResult = ( - alertResult: { - metric: string; - currentValue: number | null; - threshold: number[]; - comparator: Comparator; - timeSize: number; - timeUnit: TimeUnitChar; - } & AlertResult -) => { - const { metric, currentValue, threshold, comparator } = alertResult; - const noDataValue = i18n.translate( - 'xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue', - { defaultMessage: '[NO DATA]' } - ); - - if (metric.endsWith('.pct')) { - const formatter = createFormatter('percent'); - return { - ...alertResult, - currentValue: - currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, - threshold: Array.isArray(threshold) - ? threshold.map((v: number) => formatter(v)) - : formatter(threshold), - comparator, - }; - } - - const formatter = createFormatter('highPrecision'); - return { - ...alertResult, - currentValue: - currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, - threshold: Array.isArray(threshold) - ? threshold.map((v: number) => formatter(v)) - : formatter(threshold), - comparator, - }; -}; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/types.ts b/x-pack/plugins/observability/server/lib/rules/threshold/types.ts deleted file mode 100644 index d98a59bd50b9b..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/threshold/types.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import * as rt from 'io-ts'; -import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; -import { TimeUnitChar } from '../../../../common'; - -export enum InfraRuleType { - MetricThreshold = 'metrics.alert.threshold', - InventoryThreshold = 'metrics.alert.inventory.threshold', - Anomaly = 'metrics.alert.anomaly', -} - -export enum AlertStates { - OK, - ALERT, - NO_DATA, - ERROR, -} - -const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); -const metricAnomalyMetricRT = rt.union([ - rt.literal('memory_usage'), - rt.literal('network_in'), - rt.literal('network_out'), -]); -const metricAnomalyInfluencerFilterRT = rt.type({ - fieldName: rt.string, - fieldValue: rt.string, -}); - -export interface MetricAnomalyParams { - nodeType: rt.TypeOf; - metric: rt.TypeOf; - alertInterval?: string; - spaceId?: string; - threshold: Exclude; - influencerFilter: rt.TypeOf | undefined; -} - -// Types for the executor - -interface BaseMetricExpressionParams { - timeSize: number; - timeUnit: TimeUnitChar; - threshold: number[]; - comparator: Comparator; - warningComparator?: Comparator; - warningThreshold?: number[]; -} - -export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Exclude; - metric: string; -} - -export interface CountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Aggregators.COUNT; -} - -export type CustomMetricAggTypes = Exclude< - Aggregators, - Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 ->; - -export interface AlertExecutionDetails { - alertId: string; - executionId: string; -} diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts b/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts deleted file mode 100644 index 7c85c7ab98630..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts +++ /dev/null @@ -1,310 +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 { isError } from 'lodash'; -import { buildEsQuery as kbnBuildEsQuery } from '@kbn/es-query'; -import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; -import { Logger, LogMeta } from '@kbn/logging'; -import type { ElasticsearchClient, IBasePath } from '@kbn/core/server'; -import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; -import { ES_FIELD_TYPES } from '@kbn/field-types'; -import { set } from '@kbn/safer-lodash-set'; -import { ParsedExperimentalFields } from '@kbn/rule-registry-plugin/common/parse_experimental_fields'; -import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; -import { ObservabilityConfig } from '../../..'; -import { AlertExecutionDetails } from './types'; - -const ALERT_CONTEXT_CONTAINER = 'container'; -const ALERT_CONTEXT_ORCHESTRATOR = 'orchestrator'; -const ALERT_CONTEXT_CLOUD = 'cloud'; -const ALERT_CONTEXT_HOST = 'host'; -const ALERT_CONTEXT_LABELS = 'labels'; -const ALERT_CONTEXT_TAGS = 'tags'; - -const HOST_NAME = 'host.name'; -const HOST_HOSTNAME = 'host.hostname'; -const HOST_ID = 'host.id'; -export const CONTAINER_ID = 'container.id'; - -const SUPPORTED_ES_FIELD_TYPES = [ - ES_FIELD_TYPES.KEYWORD, - ES_FIELD_TYPES.IP, - ES_FIELD_TYPES.BOOLEAN, -]; - -export const oneOfLiterals = (arrayOfLiterals: Readonly) => - schema.string({ - validate: (value) => - arrayOfLiterals.includes(value) ? undefined : `must be one of ${arrayOfLiterals.join(' | ')}`, - }); - -export const validateKQLStringFilter = (value: string) => { - if (value === '') { - // Allow clearing the filter. - return; - } - - try { - kbnBuildEsQuery(undefined, [{ query: value, language: 'kuery' }], []); - } catch (e) { - return i18n.translate('xpack.observability.threshold.rule.schema.invalidFilterQuery', { - defaultMessage: 'filterQuery must be a valid KQL filter', - }); - } -}; - -export const UNGROUPED_FACTORY_KEY = '*'; - -export const createScopedLogger = ( - logger: Logger, - scope: string, - alertExecutionDetails: AlertExecutionDetails -): Logger => { - const scopedLogger = logger.get(scope); - const fmtMsg = (msg: string) => - `[AlertId: ${alertExecutionDetails.alertId}][ExecutionId: ${alertExecutionDetails.executionId}] ${msg}`; - return { - ...scopedLogger, - info: (msg: string, meta?: Meta) => - scopedLogger.info(fmtMsg(msg), meta), - debug: (msg: string, meta?: Meta) => - scopedLogger.debug(fmtMsg(msg), meta), - trace: (msg: string, meta?: Meta) => - scopedLogger.trace(fmtMsg(msg), meta), - warn: (errorOrMessage: string | Error, meta?: Meta) => { - if (isError(errorOrMessage)) { - scopedLogger.warn(errorOrMessage, meta); - } else { - scopedLogger.warn(fmtMsg(errorOrMessage), meta); - } - }, - error: (errorOrMessage: string | Error, meta?: Meta) => { - if (isError(errorOrMessage)) { - scopedLogger.error(errorOrMessage, meta); - } else { - scopedLogger.error(fmtMsg(errorOrMessage), meta); - } - }, - fatal: (errorOrMessage: string | Error, meta?: Meta) => { - if (isError(errorOrMessage)) { - scopedLogger.fatal(errorOrMessage, meta); - } else { - scopedLogger.fatal(fmtMsg(errorOrMessage), meta); - } - }, - }; -}; - -export const getAlertDetailsPageEnabledForApp = ( - config: ObservabilityConfig['unsafe']['alertDetails'] | null, - appName: keyof ObservabilityConfig['unsafe']['alertDetails'] -): boolean => { - if (!config) return false; - - return config[appName].enabled; -}; - -export const getAlertDetailsUrl = ( - basePath: IBasePath, - spaceId: string, - alertUuid: string | null -) => addSpaceIdToPath(basePath.publicBaseUrl, spaceId, `/app/observability/alerts/${alertUuid}`); - -export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; -export const NUMBER_OF_DOCUMENTS = 10; - -export interface AdditionalContext { - [x: string]: any; -} - -export const doFieldsExist = async ( - esClient: ElasticsearchClient, - fields: string[], - index: string -): Promise> => { - // Get all supported fields - const respMapping = await esClient.fieldCaps({ - index, - fields: '*', - }); - - const fieldsExisted: Record = {}; - const acceptableFields: Set = new Set(); - - Object.entries(respMapping.fields).forEach(([key, value]) => { - const fieldTypes = Object.keys(value) as ES_FIELD_TYPES[]; - const isSupportedType = fieldTypes.some((type) => SUPPORTED_ES_FIELD_TYPES.includes(type)); - - // Check if fieldName is something we can aggregate on - if (isSupportedType) { - acceptableFields.add(key); - } - }); - - fields.forEach((field) => { - fieldsExisted[field] = acceptableFields.has(field); - }); - - return fieldsExisted; -}; - -export const validGroupByForContext: string[] = [ - HOST_NAME, - HOST_HOSTNAME, - HOST_ID, - KUBERNETES_POD_UID, - CONTAINER_ID, -]; - -export const hasAdditionalContext = ( - groupBy: string | string[] | undefined, - validGroups: string[] -): boolean => { - return groupBy - ? Array.isArray(groupBy) - ? groupBy.every((group) => validGroups.includes(group)) - : validGroups.includes(groupBy) - : false; -}; - -export const shouldTermsAggOnContainer = (groupBy: string | string[] | undefined) => { - return groupBy && Array.isArray(groupBy) - ? groupBy.includes(KUBERNETES_POD_UID) - : groupBy === KUBERNETES_POD_UID; -}; - -export const flattenAdditionalContext = ( - additionalContext: AdditionalContext | undefined | null -): AdditionalContext => { - return additionalContext ? flattenObject(additionalContext) : {}; -}; - -export const getContextForRecoveredAlerts = ( - alertHitSource: Partial | undefined | null -): AdditionalContext => { - const alert = alertHitSource ? unflattenObject(alertHitSource) : undefined; - - return { - cloud: alert?.[ALERT_CONTEXT_CLOUD], - host: alert?.[ALERT_CONTEXT_HOST], - orchestrator: alert?.[ALERT_CONTEXT_ORCHESTRATOR], - container: alert?.[ALERT_CONTEXT_CONTAINER], - labels: alert?.[ALERT_CONTEXT_LABELS], - tags: alert?.[ALERT_CONTEXT_TAGS], - }; -}; - -export const unflattenObject = (object: object): T => - Object.entries(object).reduce((acc, [key, value]) => { - set(acc, key, value); - return acc; - }, {} as T); - -export const flattenObject = (obj: AdditionalContext, prefix: string = ''): AdditionalContext => - Object.keys(obj).reduce((acc, key) => { - const nextValue = obj[key]; - - if (nextValue) { - if (typeof nextValue === 'object' && !Array.isArray(nextValue)) { - const dotSuffix = '.'; - if (Object.keys(nextValue).length > 0) { - return { - ...acc, - ...flattenObject(nextValue, `${prefix}${key}${dotSuffix}`), - }; - } - } - - const fullPath = `${prefix}${key}`; - acc[fullPath] = nextValue; - } - - return acc; - }, {}); - -export const getGroupByObject = ( - groupBy: string | string[] | undefined, - resultGroupSet: Set -): Record => { - const groupByKeysObjectMapping: Record = {}; - if (groupBy) { - resultGroupSet.forEach((groupSet) => { - const groupSetKeys = groupSet.split(','); - groupByKeysObjectMapping[groupSet] = unflattenObject( - Array.isArray(groupBy) - ? groupBy.reduce((result, group, index) => { - return { ...result, [group]: groupSetKeys[index]?.trim() }; - }, {}) - : { [groupBy]: groupSet } - ); - }); - } - return groupByKeysObjectMapping; -}; - -// TO BE MOVED -export const INFRA_ALERT_PREVIEW_PATH = '/api/infra/alerting/preview'; - -export const TOO_MANY_BUCKETS_PREVIEW_EXCEPTION = 'TOO_MANY_BUCKETS_PREVIEW_EXCEPTION'; -export interface TooManyBucketsPreviewExceptionMetadata { - TOO_MANY_BUCKETS_PREVIEW_EXCEPTION: boolean; - maxBuckets: any; -} -export const isTooManyBucketsPreviewException = ( - value: any -): value is TooManyBucketsPreviewExceptionMetadata => - Boolean(value && value.TOO_MANY_BUCKETS_PREVIEW_EXCEPTION); - -const intervalUnits = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms']; -const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + intervalUnits.join('|') + ')$'); - -interface UnitsToSeconds { - [unit: string]: number; -} - -const units: UnitsToSeconds = { - ms: 0.001, - s: 1, - m: 60, - h: 3600, - d: 86400, - w: 86400 * 7, - M: 86400 * 30, - y: 86400 * 356, -}; - -export const getIntervalInSeconds = (interval: string): number => { - const matches = interval.match(INTERVAL_STRING_RE); - if (matches) { - return parseFloat(matches[1]) * units[matches[2]]; - } - throw new Error('Invalid interval string format.'); -}; - -export const calculateRateTimeranges = (timerange: { to: number; from: number }) => { - // This is the total number of milliseconds for the entire timerange - const totalTime = timerange.to - timerange.from; - // Halfway is the to minus half the total time; - const halfway = Math.round(timerange.to - totalTime / 2); - // The interval is half the total time (divided by 1000 to convert to seconds) - const intervalInSeconds = Math.round(totalTime / (2 * 1000)); - - // The first bucket is from the beginning of the time range to the halfway point - const firstBucketRange = { - from: timerange.from, - to: halfway, - }; - - // The second bucket is from the halfway point to the end of the timerange - const secondBucketRange = { - from: halfway, - to: timerange.to, - }; - - return { firstBucketRange, secondBucketRange, intervalInSeconds }; -}; diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index f77f173675c68..2dfdcb2308ee3 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -9,6 +9,7 @@ import { errors } from '@elastic/elasticsearch'; import { failedDependency, forbidden } from '@hapi/boom'; import { createSLOParamsSchema, + deleteSLOInstancesParamsSchema, deleteSLOParamsSchema, fetchHistoricalSummaryParamsSchema, findSloDefinitionsParamsSchema, @@ -26,6 +27,7 @@ import { DefaultSummaryClient, DefaultTransformManager, DeleteSLO, + DeleteSLOInstances, FindSLO, GetSLO, KibanaSavedObjectsSLORepository, @@ -72,6 +74,7 @@ const createSLORoute = createObservabilityServerRoute({ endpoint: 'POST /api/observability/slos 2023-10-31', options: { tags: ['access:slo_write'], + access: 'public', }, params: createSLOParamsSchema, handler: async ({ context, params, logger }) => { @@ -93,6 +96,7 @@ const updateSLORoute = createObservabilityServerRoute({ endpoint: 'PUT /api/observability/slos/{id} 2023-10-31', options: { tags: ['access:slo_write'], + access: 'public', }, params: updateSLOParamsSchema, handler: async ({ context, params, logger }) => { @@ -115,6 +119,7 @@ const deleteSLORoute = createObservabilityServerRoute({ endpoint: 'DELETE /api/observability/slos/{id} 2023-10-31', options: { tags: ['access:slo_write'], + access: 'public', }, params: deleteSLOParamsSchema, handler: async ({ @@ -143,6 +148,7 @@ const getSLORoute = createObservabilityServerRoute({ endpoint: 'GET /api/observability/slos/{id} 2023-10-31', options: { tags: ['access:slo_read'], + access: 'public', }, params: getSLOParamsSchema, handler: async ({ context, params }) => { @@ -164,6 +170,7 @@ const enableSLORoute = createObservabilityServerRoute({ endpoint: 'POST /api/observability/slos/{id}/enable 2023-10-31', options: { tags: ['access:slo_write'], + access: 'public', }, params: manageSLOParamsSchema, handler: async ({ context, params, logger }) => { @@ -186,6 +193,7 @@ const disableSLORoute = createObservabilityServerRoute({ endpoint: 'POST /api/observability/slos/{id}/disable 2023-10-31', options: { tags: ['access:slo_write'], + access: 'public', }, params: manageSLOParamsSchema, handler: async ({ context, params, logger }) => { @@ -208,6 +216,7 @@ const findSLORoute = createObservabilityServerRoute({ endpoint: 'GET /api/observability/slos 2023-10-31', options: { tags: ['access:slo_read'], + access: 'public', }, params: findSLOParamsSchema, handler: async ({ context, params, logger }) => { @@ -225,10 +234,27 @@ const findSLORoute = createObservabilityServerRoute({ }, }); +const deleteSloInstancesRoute = createObservabilityServerRoute({ + endpoint: 'POST /api/observability/slos/_delete_instances 2023-10-31', + options: { + tags: ['access:slo_write'], + }, + params: deleteSLOInstancesParamsSchema, + handler: async ({ context, params }) => { + await assertPlatinumLicense(context); + + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const deleteSloInstances = new DeleteSLOInstances(esClient); + + await deleteSloInstances.execute(params.body); + }, +}); + const findSloDefinitionsRoute = createObservabilityServerRoute({ endpoint: 'GET /internal/observability/slos/_definitions', options: { tags: ['access:slo_read'], + access: 'internal', }, params: findSloDefinitionsParamsSchema, handler: async ({ context, params }) => { @@ -270,6 +296,7 @@ const getSLOInstancesRoute = createObservabilityServerRoute({ endpoint: 'GET /internal/observability/slos/{id}/_instances', options: { tags: ['access:slo_read'], + access: 'internal', }, params: getSLOInstancesParamsSchema, handler: async ({ context, params }) => { @@ -291,6 +318,7 @@ const getDiagnosisRoute = createObservabilityServerRoute({ endpoint: 'GET /internal/observability/slos/_diagnosis', options: { tags: [], + access: 'internal', }, params: undefined, handler: async ({ context }) => { @@ -313,6 +341,7 @@ const getSloBurnRates = createObservabilityServerRoute({ endpoint: 'POST /internal/observability/slos/{id}/_burn_rates', options: { tags: ['access:slo_read'], + access: 'internal', }, params: getSLOBurnRatesParamsSchema, handler: async ({ context, params }) => { @@ -337,6 +366,7 @@ const getPreviewData = createObservabilityServerRoute({ endpoint: 'POST /internal/observability/slos/_preview', options: { tags: ['access:slo_read'], + access: 'internal', }, params: getPreviewDataParamsSchema, handler: async ({ context, params }) => { @@ -351,6 +381,7 @@ const getPreviewData = createObservabilityServerRoute({ export const sloRouteRepository = { ...createSLORoute, ...deleteSLORoute, + ...deleteSloInstancesRoute, ...disableSLORoute, ...enableSLORoute, ...fetchHistoricalSummary, diff --git a/x-pack/plugins/observability/server/routes/types.ts b/x-pack/plugins/observability/server/routes/types.ts index 866bd3c79c1d1..a0ef6ee6c0c74 100644 --- a/x-pack/plugins/observability/server/routes/types.ts +++ b/x-pack/plugins/observability/server/routes/types.ts @@ -25,6 +25,7 @@ export interface ObservabilityRouteHandlerResources { export interface ObservabilityRouteCreateOptions { options: { tags: string[]; + access?: 'public' | 'internal'; }; } diff --git a/x-pack/plugins/observability/server/services/slo/__snapshots__/create_slo.test.ts.snap b/x-pack/plugins/observability/server/services/slo/__snapshots__/create_slo.test.ts.snap index 77ef91abbbcf4..e66f1f8124a11 100644 --- a/x-pack/plugins/observability/server/services/slo/__snapshots__/create_slo.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/__snapshots__/create_slo.test.ts.snap @@ -42,6 +42,7 @@ Array [ }, "id": "slo-unique-id", "index": ".slo-observability.summary-v2.temp", + "refresh": true, }, ] `; diff --git a/x-pack/plugins/observability/server/services/slo/__snapshots__/update_slo.test.ts.snap b/x-pack/plugins/observability/server/services/slo/__snapshots__/update_slo.test.ts.snap index a3434de52f89d..ae7a966951f7c 100644 --- a/x-pack/plugins/observability/server/services/slo/__snapshots__/update_slo.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/__snapshots__/update_slo.test.ts.snap @@ -45,6 +45,7 @@ Array [ }, "id": "slo-unique-id", "index": ".slo-observability.summary-v2.temp", + "refresh": true, }, ] `; diff --git a/x-pack/plugins/observability/server/services/slo/create_slo.ts b/x-pack/plugins/observability/server/services/slo/create_slo.ts index b7ed87aef9051..2b90c425b8e70 100644 --- a/x-pack/plugins/observability/server/services/slo/create_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/create_slo.ts @@ -50,6 +50,7 @@ export class CreateSLO { index: SLO_SUMMARY_TEMP_INDEX_NAME, id: `slo-${slo.id}`, document: createTempSummaryDocument(slo), + refresh: true, }); return this.toResponse(slo); diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.ts index 508da3b4ae129..de908ceac7842 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.ts @@ -51,7 +51,7 @@ export class DeleteSLO { private async deleteSummaryData(sloId: string): Promise { await this.esClient.deleteByQuery({ index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN, - wait_for_completion: false, + refresh: true, query: { match: { 'slo.id': sloId, diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo_instances.test.ts b/x-pack/plugins/observability/server/services/slo/delete_slo_instances.test.ts new file mode 100644 index 0000000000000..8a9c64a6b441c --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/delete_slo_instances.test.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; +import { DeleteSLOInstances } from './delete_slo_instances'; + +describe('DeleteSLOInstances', () => { + let mockEsClient: jest.Mocked; + let deleteSLOInstances: DeleteSLOInstances; + + beforeEach(() => { + mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + deleteSLOInstances = new DeleteSLOInstances(mockEsClient); + }); + + describe('validation', () => { + it("forbids deleting an SLO with an '*' (all) instance id", async () => { + await expect( + deleteSLOInstances.execute({ + list: [ + { sloId: 'first', instanceId: 'irrelevant' }, + { sloId: 'second', instanceId: '*' }, + ], + }) + ).rejects.toThrowError("Cannot delete an SLO instance '*'"); + }); + }); + + it('deletes the roll up and the summary data for each tuple', async () => { + await deleteSLOInstances.execute({ + list: [ + { sloId: 'first', instanceId: 'host-foo' }, + { sloId: 'second', instanceId: 'host-foo' }, + { sloId: 'third', instanceId: 'cluster-eu' }, + ], + }); + + expect(mockEsClient.deleteByQuery).toHaveBeenCalledTimes(2); + expect(mockEsClient.deleteByQuery.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "index": ".slo-observability.sli-v2*", + "query": Object { + "bool": Object { + "should": Array [ + Object { + "bool": Object { + "must": Array [ + Object { + "term": Object { + "slo.id": "first", + }, + }, + Object { + "term": Object { + "slo.instanceId": "host-foo", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "term": Object { + "slo.id": "second", + }, + }, + Object { + "term": Object { + "slo.instanceId": "host-foo", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "term": Object { + "slo.id": "third", + }, + }, + Object { + "term": Object { + "slo.instanceId": "cluster-eu", + }, + }, + ], + }, + }, + ], + }, + }, + "wait_for_completion": false, + } + `); + expect(mockEsClient.deleteByQuery.mock.calls[1][0]).toMatchInlineSnapshot(` + Object { + "index": ".slo-observability.summary-v2*", + "query": Object { + "bool": Object { + "should": Array [ + Object { + "bool": Object { + "must": Array [ + Object { + "term": Object { + "slo.id": "first", + }, + }, + Object { + "term": Object { + "slo.instanceId": "host-foo", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "term": Object { + "slo.id": "second", + }, + }, + Object { + "term": Object { + "slo.instanceId": "host-foo", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "term": Object { + "slo.id": "third", + }, + }, + Object { + "term": Object { + "slo.instanceId": "cluster-eu", + }, + }, + ], + }, + }, + ], + }, + }, + "refresh": true, + } + `); + }); +}); diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo_instances.ts b/x-pack/plugins/observability/server/services/slo/delete_slo_instances.ts new file mode 100644 index 0000000000000..adbea2809bb28 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/delete_slo_instances.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { ALL_VALUE, DeleteSLOInstancesParams } from '@kbn/slo-schema'; +import { + SLO_DESTINATION_INDEX_PATTERN, + SLO_SUMMARY_DESTINATION_INDEX_PATTERN, +} from '../../assets/constants'; +import { IllegalArgumentError } from '../../errors'; + +interface SloInstanceTuple { + sloId: string; + instanceId: string; +} + +export class DeleteSLOInstances { + constructor(private esClient: ElasticsearchClient) {} + + public async execute(params: DeleteSLOInstancesParams): Promise { + const containsAllValueInstanceId = params.list.some((item) => item.instanceId === ALL_VALUE); + if (containsAllValueInstanceId) { + throw new IllegalArgumentError("Cannot delete an SLO instance '*'"); + } + + await this.deleteRollupData(params.list); + await this.deleteSummaryData(params.list); + } + + private async deleteRollupData(list: SloInstanceTuple[]): Promise { + await this.esClient.deleteByQuery({ + index: SLO_DESTINATION_INDEX_PATTERN, + wait_for_completion: false, + query: { + bool: { + should: list.map((item) => ({ + bool: { + must: [ + { term: { 'slo.id': item.sloId } }, + { term: { 'slo.instanceId': item.instanceId } }, + ], + }, + })), + }, + }, + }); + } + + private async deleteSummaryData(list: SloInstanceTuple[]): Promise { + await this.esClient.deleteByQuery({ + index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN, + refresh: true, + query: { + bool: { + should: list.map((item) => ({ + bool: { + must: [ + { term: { 'slo.id': item.sloId } }, + { term: { 'slo.instanceId': item.instanceId } }, + ], + }, + })), + }, + }, + }); + } +} diff --git a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts index bebd153690b50..494c54cd65741 100644 --- a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts +++ b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts @@ -58,7 +58,7 @@ export const createKQLCustomIndicator = ( ): Indicator => ({ type: 'sli.kql.custom', params: { - index: 'my-index*', + index: 'my-index*,my-other-index*', filter: 'labels.groupId: group-3', good: 'latency < 300', total: '', @@ -72,7 +72,7 @@ export const createMetricCustomIndicator = ( ): MetricCustomIndicator => ({ type: 'sli.metric.custom', params: { - index: 'my-index*', + index: 'my-index*,my-other-index*', filter: 'labels.groupId: group-3', good: { metrics: [ @@ -95,7 +95,7 @@ export const createHistogramIndicator = ( ): HistogramIndicator => ({ type: 'sli.histogram.custom', params: { - index: 'my-index*', + index: 'my-index*,my-other-index*', filter: 'labels.groupId: group-3', good: { field: 'latency', diff --git a/x-pack/plugins/observability/server/services/slo/index.ts b/x-pack/plugins/observability/server/services/slo/index.ts index 396b443be7eeb..7c99c289ae90b 100644 --- a/x-pack/plugins/observability/server/services/slo/index.ts +++ b/x-pack/plugins/observability/server/services/slo/index.ts @@ -7,6 +7,7 @@ export * from './create_slo'; export * from './delete_slo'; +export * from './delete_slo_instances'; export * from './fetch_historical_summary'; export * from './find_slo'; export * from './get_slo'; diff --git a/x-pack/plugins/observability/server/services/slo/slo_installer.ts b/x-pack/plugins/observability/server/services/slo/slo_installer.ts index d6e8b8295348f..44c2a7752e315 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_installer.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_installer.ts @@ -34,9 +34,7 @@ export class DefaultSLOInstaller implements SLOInstaller { await this.sloResourceInstaller.ensureCommonResourcesInstalled(); await this.sloSummaryInstaller.installAndStart(); } catch (error) { - this.logger.error('Failed to install SLO common resources and summary transforms', { - error, - }); + this.logger.error('Failed to install SLO common resources and summary transforms'); } finally { this.isInstalling = false; clearTimeout(installTimeout); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/histogram.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/histogram.test.ts.snap index ba7b00be25841..ee4001303fec5 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/histogram.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/histogram.test.ts.snap @@ -47,11 +47,24 @@ Object { exports[`Histogram Transform Generator filters the source using the kql query 1`] = ` Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ Object { - "match": Object { - "labels.groupId": "group-4", + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-4", + }, + }, + ], }, }, ], @@ -209,14 +222,30 @@ Object { "deduce_mappings": false, }, "source": Object { - "index": "my-index*", + "index": Array [ + "my-index*", + "my-other-index*", + ], "query": Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ Object { - "match": Object { - "labels.groupId": "group-3", + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-3", + }, + }, + ], }, }, ], @@ -449,14 +478,30 @@ Object { "deduce_mappings": false, }, "source": Object { - "index": "my-index*", + "index": Array [ + "my-index*", + "my-other-index*", + ], "query": Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ + Object { + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, Object { - "match": Object { - "labels.groupId": "group-3", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-3", + }, + }, + ], }, }, ], diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap index c9815496cdf46..126437173f84a 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap @@ -88,11 +88,24 @@ Object { exports[`KQL Custom Transform Generator filters the source using the kql query 1`] = ` Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ Object { - "match": Object { - "labels.groupId": "group-4", + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-4", + }, + }, + ], }, }, ], @@ -224,14 +237,30 @@ Object { "deduce_mappings": false, }, "source": Object { - "index": "my-index*", + "index": Array [ + "my-index*", + "my-other-index*", + ], "query": Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ + Object { + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, Object { - "match": Object { - "labels.groupId": "group-3", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-3", + }, + }, + ], }, }, ], @@ -438,14 +467,30 @@ Object { "deduce_mappings": false, }, "source": Object { - "index": "my-index*", + "index": Array [ + "my-index*", + "my-other-index*", + ], "query": Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ + Object { + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, Object { - "match": Object { - "labels.groupId": "group-3", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-3", + }, + }, + ], }, }, ], diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap index 2a4683cd32995..ced0801d859d4 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap @@ -59,11 +59,24 @@ Object { exports[`Metric Custom Transform Generator filters the source using the kql query 1`] = ` Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ Object { - "match": Object { - "labels.groupId": "group-4", + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-4", + }, + }, + ], }, }, ], @@ -233,14 +246,30 @@ Object { "deduce_mappings": false, }, "source": Object { - "index": "my-index*", + "index": Array [ + "my-index*", + "my-other-index*", + ], "query": Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ Object { - "match": Object { - "labels.groupId": "group-3", + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-3", + }, + }, + ], }, }, ], @@ -485,14 +514,30 @@ Object { "deduce_mappings": false, }, "source": Object { - "index": "my-index*", + "index": Array [ + "my-index*", + "my-other-index*", + ], "query": Object { "bool": Object { - "minimum_should_match": 1, - "should": Array [ + "filter": Array [ + Object { + "range": Object { + "log_timestamp": Object { + "gte": "now-7d", + }, + }, + }, Object { - "match": Object { - "labels.groupId": "group-3", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-3", + }, + }, + ], }, }, ], diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/histogram.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/histogram.ts index 7793123ff1595..654f8d67a3673 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/histogram.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/histogram.ts @@ -14,7 +14,7 @@ import { import { InvalidTransformError } from '../../../errors'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; -import { getElastichsearchQueryOrThrow, TransformGenerator } from '.'; +import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.'; import { SLO_DESTINATION_INDEX_NAME, SLO_INGEST_PIPELINE_NAME, @@ -45,11 +45,23 @@ export class HistogramTransformGenerator extends TransformGenerator { } private buildSource(slo: SLO, indicator: HistogramIndicator) { - const filter = getElastichsearchQueryOrThrow(indicator.params.filter); return { - index: indicator.params.index, + index: parseIndex(indicator.params.index), runtime_mappings: this.buildCommonRuntimeMappings(slo), - query: filter, + query: { + bool: { + filter: [ + { + range: { + [indicator.params.timestampField]: { + gte: `now-${slo.timeWindow.duration.format()}`, + }, + }, + }, + getElastichsearchQueryOrThrow(indicator.params.filter), + ], + }, + }, }; } diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts index e44bab97e1b78..8321a0cb7172e 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts @@ -10,7 +10,7 @@ import { kqlCustomIndicatorSchema, timeslicesBudgetingMethodSchema } from '@kbn/ import { InvalidTransformError } from '../../../errors'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; -import { getElastichsearchQueryOrThrow, TransformGenerator } from '.'; +import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.'; import { SLO_DESTINATION_INDEX_NAME, SLO_INGEST_PIPELINE_NAME, @@ -40,11 +40,23 @@ export class KQLCustomTransformGenerator extends TransformGenerator { } private buildSource(slo: SLO, indicator: KQLCustomIndicator) { - const filter = getElastichsearchQueryOrThrow(indicator.params.filter); return { - index: indicator.params.index, + index: parseIndex(indicator.params.index), runtime_mappings: this.buildCommonRuntimeMappings(slo), - query: filter, + query: { + bool: { + filter: [ + { + range: { + [indicator.params.timestampField]: { + gte: `now-${slo.timeWindow.duration.format()}`, + }, + }, + }, + getElastichsearchQueryOrThrow(indicator.params.filter), + ], + }, + }, }; } diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.ts index 8faa996272e46..52209533f828c 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.ts @@ -10,7 +10,7 @@ import { metricCustomIndicatorSchema, timeslicesBudgetingMethodSchema } from '@k import { InvalidTransformError } from '../../../errors'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; -import { getElastichsearchQueryOrThrow, TransformGenerator } from '.'; +import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.'; import { SLO_DESTINATION_INDEX_NAME, SLO_INGEST_PIPELINE_NAME, @@ -43,11 +43,23 @@ export class MetricCustomTransformGenerator extends TransformGenerator { } private buildSource(slo: SLO, indicator: MetricCustomIndicator) { - const filter = getElastichsearchQueryOrThrow(indicator.params.filter); return { - index: indicator.params.index, + index: parseIndex(indicator.params.index), runtime_mappings: this.buildCommonRuntimeMappings(slo), - query: filter, + query: { + bool: { + filter: [ + { + range: { + [indicator.params.timestampField]: { + gte: `now-${slo.timeWindow.duration.format()}`, + }, + }, + }, + getElastichsearchQueryOrThrow(indicator.params.filter), + ], + }, + }, }; } diff --git a/x-pack/plugins/observability/server/services/slo/update_slo.ts b/x-pack/plugins/observability/server/services/slo/update_slo.ts index 5bc10a7209106..271253f077928 100644 --- a/x-pack/plugins/observability/server/services/slo/update_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/update_slo.ts @@ -45,6 +45,7 @@ export class UpdateSLO { index: SLO_SUMMARY_TEMP_INDEX_NAME, id: `slo-${updatedSlo.id}`, document: createTempSummaryDocument(updatedSlo), + refresh: true, }); return this.toResponse(updatedSlo); @@ -73,7 +74,7 @@ export class UpdateSLO { private async deleteSummaryData(sloId: string, sloRevision: number): Promise { await this.esClient.deleteByQuery({ index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN, - wait_for_completion: false, + refresh: true, query: { bool: { filter: [{ term: { 'slo.id': sloId } }, { term: { 'slo.revision': sloRevision } }], diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 3fecda34a9ee5..53a2fd3815170 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -82,9 +82,11 @@ "@kbn/data-view-editor-plugin", "@kbn/actions-plugin", "@kbn/core-capabilities-common", - "@kbn/deeplinks-analytics", "@kbn/observability-ai-assistant-plugin", - "@kbn/osquery-plugin" + "@kbn/osquery-plugin", + "@kbn/aiops-plugin", + "@kbn/content-management-plugin", + "@kbn/deeplinks-observability" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability/typings/common.ts b/x-pack/plugins/observability/typings/common.ts index df51061b56a5c..06e78c4ec6e64 100644 --- a/x-pack/plugins/observability/typings/common.ts +++ b/x-pack/plugins/observability/typings/common.ts @@ -17,7 +17,8 @@ export type ObservabilityApp = | 'observability-overview' | 'stack_monitoring' | 'ux' - | 'fleet'; + | 'fleet' + | 'universal_profiling'; export type { Coordinates } from '../public/typings/fetch_overview_data'; diff --git a/x-pack/plugins/observability_ai_assistant/README.md b/x-pack/plugins/observability_ai_assistant/README.md index 8487cbf13ba10..11e6d7d38d659 100644 --- a/x-pack/plugins/observability_ai_assistant/README.md +++ b/x-pack/plugins/observability_ai_assistant/README.md @@ -66,8 +66,8 @@ The knowledge base is an Elasticsearch index, with an inference processor powere Both the user and the LLM are able to suggest functions, that are executed on behalf (and with the privileges of) the user. Functions allow both the user and the LLM to include relevant context into the conversation. This context can be text, data, or a visual component, like a timeseries graph. Some of the functions that are available are: -- `recall` and `summarise`: these functions query (with a semantic search) or write to (with a summarisation) the knowledge database. This allows the LLM to create a (partly) user-specific working memory, and access predefined embeddings that help improve its understanding of the Elastic platform. -- `lens`: a function that can be used to create Lens visualisations using Formulas. +- `recall` and `summarize`: these functions query (with a semantic search) or write to (with a summarisation) the knowledge database. This allows the LLM to create a (partly) user-specific working memory, and access predefined embeddings that help improve its understanding of the Elastic platform. +- `lens`: a function that can be used to create Lens vizualisations using Formulas. - `get_apm_timeseries`, `get_apm_service_summary`, `get_apm_downstream_dependencies` and `get_apm_error_document`: a set of APM functions, some with visual components, that are helpful in performing root cause analysis. -Function calling is completely transparent to the user - they can edit function suggestions from the LLM, or inspect a function response (but not edit it), or they can request a function themselves. +Function calling is completely transparent to the user - they can edit function suggestions from the LLM, or inspect a function response (but not edit it), or they can request a function themselves. diff --git a/x-pack/plugins/observability_ai_assistant/common/types.ts b/x-pack/plugins/observability_ai_assistant/common/types.ts index 1d8be57fd53b3..b14137dbaebf5 100644 --- a/x-pack/plugins/observability_ai_assistant/common/types.ts +++ b/x-pack/plugins/observability_ai_assistant/common/types.ts @@ -88,7 +88,7 @@ interface FunctionOptions = ( - options: { arguments: TArguments }, + options: { arguments: TArguments; messages: Message[] }, signal: AbortSignal ) => Promise; @@ -99,7 +99,10 @@ type RenderFunction = (options: export interface FunctionDefinition { options: FunctionOptions; - respond: (options: { arguments: any }, signal: AbortSignal) => Promise; + respond: ( + options: { arguments: any; messages: Message[] }, + signal: AbortSignal + ) => Promise; render?: RenderFunction; } diff --git a/x-pack/plugins/observability_ai_assistant/public/assets/illustration.png b/x-pack/plugins/observability_ai_assistant/public/assets/illustration.png new file mode 100644 index 0000000000000..6717f5f3e7509 Binary files /dev/null and b/x-pack/plugins/observability_ai_assistant/public/assets/illustration.png differ diff --git a/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx b/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx index 226c919d28fc1..ca47c242df495 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx @@ -49,6 +49,7 @@ export function ObservabilityAIAssistantActionMenuItem() { <> { setIsOpen(() => true); }} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.tsx index cbd95b9b80fe5..f3ac2d2a735ad 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.tsx @@ -56,14 +56,26 @@ export function AskAssistantButton({ switch (variant) { case 'basic': return ( - + {buttonLabel} ); case 'empty': return ( - + {buttonLabel} ); @@ -83,8 +95,15 @@ export function AskAssistantButton({ )} > + {props.isExpanded ? i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.hide', { defaultMessage: 'Hide chats', diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.tsx index 453c8da351119..d11451c78eaba 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.tsx @@ -10,7 +10,12 @@ import { i18n } from '@kbn/i18n'; export function NewChatButton(props: React.ComponentProps) { return ( - + {i18n.translate('xpack.observabilityAiAssistant.newChatButton', { defaultMessage: 'New chat', })} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.tsx index b0439c854e2ca..77d66081f7868 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.tsx @@ -11,7 +11,12 @@ import { i18n } from '@kbn/i18n'; export function RegenerateResponseButton(props: Partial) { return ( - + {i18n.translate('xpack.observabilityAiAssistant.regenerateResponseButtonLabel', { defaultMessage: 'Regenerate', })} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.tsx index dedbc827af4b2..15042a251302e 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.tsx @@ -10,7 +10,13 @@ import { i18n } from '@kbn/i18n'; export function StartChatButton(props: React.ComponentProps) { return ( - + {i18n.translate('xpack.observabilityAiAssistant.insight.response.startChat', { defaultMessage: 'Start chat', })} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.tsx index 3008ee884ae64..41023e276fcc5 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.tsx @@ -11,7 +11,13 @@ import { i18n } from '@kbn/i18n'; export function StopGeneratingButton(props: Partial) { return ( - + {i18n.translate('xpack.observabilityAiAssistant.stopGeneratingButtonLabel', { defaultMessage: 'Stop generating', })} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_actions_menu.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_actions_menu.tsx new file mode 100644 index 0000000000000..d33d622d4a0f8 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_actions_menu.tsx @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButton, + EuiButtonIcon, + EuiContextMenu, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiLoadingSpinner, + EuiPanel, + EuiPopover, + EuiSpacer, + EuiSwitch, + EuiText, +} from '@elastic/eui'; +import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base'; +import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; +import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; +import type { StartedFrom } from '../../utils/get_timeline_items_from_conversation'; + +export function ChatActionsMenu({ + connectors, + connectorsManagementHref, + conversationId, + disabled, + knowledgeBase, + modelsManagementHref, + startedFrom, + onCopyConversationClick, +}: { + connectors: UseGenAIConnectorsResult; + connectorsManagementHref: string; + conversationId?: string; + disabled: boolean; + knowledgeBase: UseKnowledgeBaseResult; + modelsManagementHref: string; + startedFrom?: StartedFrom; + onCopyConversationClick: () => void; +}) { + const [isOpen, setIsOpen] = useState(false); + + const toggleActionsMenu = () => { + setIsOpen(!isOpen); + }; + + return ( + + } + panelPaddingSize="none" + closePopover={toggleActionsMenu} + > + + {i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', { + defaultMessage: 'Connector', + })}{' '} + + { + connectors.connectors?.find(({ id }) => id === connectors.selectedConnector) + ?.name + } + +
    + ), + panel: 1, + }, + { + name: ( + + + {i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase', + { + defaultMessage: 'Knowledge base', + } + )} + + + {knowledgeBase.status.loading || knowledgeBase.isInstalling ? ( + + ) : knowledgeBase.status.value?.ready ? ( + + ) : ( + + )} + + + ), + panel: 2, + }, + { + name: i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.copyConversation', + { + defaultMessage: 'Copy conversation', + } + ), + disabled: !conversationId, + onClick: () => { + toggleActionsMenu(); + onCopyConversationClick(); + }, + }, + ], + }, + { + id: 1, + width: 256, + title: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', { + defaultMessage: 'Connector', + }), + content: ( + + + + + {i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.connectorManagement.button', + { + defaultMessage: 'Manage connectors', + } + )} + + + ), + }, + { + id: 2, + width: 256, + title: i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.title', + { + defaultMessage: 'Knowledge base', + } + ), + content: ( + + +

    + {i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.description.paragraph', + { + defaultMessage: + 'Using a knowledge base is optional but improves the experience of using the Assistant significantly.', + } + )}{' '} + + {i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.elser.learnMore', + { + defaultMessage: 'Learn more', + } + )} + +

    +
    + + + {knowledgeBase.isInstalling || knowledgeBase.status.loading ? ( + + ) : ( + <> + { + if (e.target.checked) { + knowledgeBase.install(); + } + }} + /> + + + + + {i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.connectorManagement', + { + defaultMessage: 'Go to Machine Learning', + } + )} + + + )} +
    + ), + }, + ]} + /> + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx index 14e9df56935dd..8a6227829c855 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { last } from 'lodash'; import { EuiFlexGroup, @@ -24,19 +24,14 @@ import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; import { useTimeline } from '../../hooks/use_timeline'; import { useLicense } from '../../hooks/use_license'; import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service'; -import { MissingCredentialsCallout } from '../missing_credentials_callout'; import { ExperimentalFeatureBanner } from './experimental_feature_banner'; +import { InitialSetupPanel } from './initial_setup_panel'; import { IncorrectLicensePanel } from './incorrect_license_panel'; import { ChatHeader } from './chat_header'; import { ChatPromptEditor } from './chat_prompt_editor'; import { ChatTimeline } from './chat_timeline'; import { StartedFrom } from '../../utils/get_timeline_items_from_conversation'; -const containerClassName = css` - max-height: 100%; - max-width: ${1200 - 250}px; // page template max width - conversation list width. -`; - const timelineClassName = css` overflow-y: auto; `; @@ -57,6 +52,7 @@ export function ChatBody({ connectors, knowledgeBase, connectorsManagementHref, + modelsManagementHref, conversationId, currentUser, startedFrom, @@ -70,6 +66,7 @@ export function ChatBody({ connectors: UseGenAIConnectorsResult; knowledgeBase: UseKnowledgeBaseResult; connectorsManagementHref: string; + modelsManagementHref: string; conversationId?: string; currentUser?: Pick; startedFrom?: StartedFrom; @@ -83,10 +80,10 @@ export function ChatBody({ const chatService = useObservabilityAIAssistantChatService(); const timeline = useTimeline({ - messages, + chatService, connectors, currentUser, - chatService, + messages, startedFrom, onChatUpdate, onChatComplete, @@ -100,49 +97,53 @@ export function ChatBody({ connectors.loading || knowledgeBase.status.loading || last(timeline.items)?.loading ); + const containerClassName = css` + max-height: 100%; + max-width: ${startedFrom === 'conversationView' + ? 1200 - 250 + 'px' // page template max width - conversation list width. + : '100%'}; + `; + + const [stickToBottom, setStickToBottom] = useState(true); + + const isAtBottom = (parent: HTMLElement) => + parent.scrollTop + parent.clientHeight >= parent.scrollHeight; + useEffect(() => { const parent = timelineContainerRef.current?.parentElement; if (!parent) { return; } - let rafId: number | undefined; - - const isAtBottom = () => parent.scrollTop >= parent.scrollHeight - parent.offsetHeight; - - const stick = () => { - if (!isAtBottom()) { - parent.scrollTop = parent.scrollHeight - parent.offsetHeight; - } - rafId = requestAnimationFrame(stick); - }; - - const unstick = () => { - if (rafId) { - cancelAnimationFrame(rafId); - rafId = undefined; - } - }; - - const onScroll = (event: Event) => { - if (isAtBottom()) { - stick(); - } else { - unstick(); - } - }; + function onScroll() { + setStickToBottom(isAtBottom(parent!)); + } parent.addEventListener('scroll', onScroll); - stick(); - return () => { - unstick(); parent.removeEventListener('scroll', onScroll); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [timelineContainerRef.current]); + useEffect(() => { + const parent = timelineContainerRef.current?.parentElement; + if (!parent) { + return; + } + + if (stickToBottom) { + parent.scrollTop = parent.scrollHeight; + } + }); + + const handleCopyConversation = () => { + const content = JSON.stringify({ title, messages }); + + navigator.clipboard?.writeText(content || ''); + }; + if (!hasCorrectLicense && !conversationId) { footer = ( <> @@ -166,12 +167,14 @@ export function ChatBody({ ); - } else if (connectors.connectors?.length === 0) { + } else if (connectors.connectors?.length === 0 && !conversationId) { footer = ( - <> - - - + ); } else { footer = ( @@ -181,6 +184,7 @@ export function ChatBody({ { + setStickToBottom(true); + return timeline.onSubmit(message); + }} /> @@ -208,16 +215,24 @@ export function ChatBody({ return ( - - - + {connectors.selectedConnector ? ( + + + + ) : null} + diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx index 7f8924995f07b..ef4635d873e8a 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx @@ -15,6 +15,7 @@ import { useKibana } from '../../hooks/use_kibana'; import { useKnowledgeBase } from '../../hooks/use_knowledge_base'; import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; import { getConnectorsManagementHref } from '../../utils/get_connectors_management_href'; +import { getModelsManagementHref } from '../../utils/get_models_management_href'; import { StartedFrom } from '../../utils/get_timeline_items_from_conversation'; import { ChatBody } from './chat_body'; @@ -77,6 +78,7 @@ export function ChatFlyout({ > {conversationId ? ( ) : ( - + {i18n.translate('xpack.observabilityAiAssistant.conversationListDeepLinkLabel', { defaultMessage: 'Go to conversations', })} @@ -102,6 +107,8 @@ export function ChatFlyout({ messages={messages} currentUser={currentUser} connectorsManagementHref={getConnectorsManagementHref(http)} + modelsManagementHref={getModelsManagementHref(http)} + conversationId={conversationId} knowledgeBase={knowledgeBase} startedFrom={startedFrom} onChatUpdate={(nextMessages) => { diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_header.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_header.tsx index 0314380a87657..0630f9c36d9dd 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_header.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_header.tsx @@ -16,12 +16,12 @@ import { import { i18n } from '@kbn/i18n'; import { css } from '@emotion/css'; import { AssistantAvatar } from '../assistant_avatar'; -import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base'; -import { EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../../i18n'; -import { KnowledgeBaseCallout } from './knowledge_base_callout'; +import { ChatActionsMenu } from './chat_actions_menu'; +import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../../i18n'; import { useUnmountAndRemountWhenPropChanges } from '../../hooks/use_unmount_and_remount_when_prop_changes'; import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; +import { StartedFrom } from '../../utils/get_timeline_items_from_conversation'; // needed to prevent InlineTextEdit component from expanding container const minWidthClassName = css` @@ -33,19 +33,33 @@ export function ChatHeader({ loading, licenseInvalid, connectors, + connectorsManagementHref, + modelsManagementHref, + conversationId, knowledgeBase, + startedFrom, onSaveTitle, + onCopyConversation, }: { title: string; loading: boolean; licenseInvalid: boolean; connectors: UseGenAIConnectorsResult; + connectorsManagementHref: string; + modelsManagementHref: string; + conversationId?: string; knowledgeBase: UseKnowledgeBaseResult; + startedFrom?: StartedFrom; + onCopyConversation: () => void; onSaveTitle?: (title: string) => void; }) { const hasTitle = !!title; - const displayedTitle = licenseInvalid ? UPGRADE_LICENSE_TITLE : title || EMPTY_CONVERSATION_TITLE; + const displayedTitle = !connectors.selectedConnector + ? ASSISTANT_SETUP_TITLE + : licenseInvalid + ? UPGRADE_LICENSE_TITLE + : title || EMPTY_CONVERSATION_TITLE; const theme = useEuiTheme(); @@ -57,45 +71,41 @@ export function ChatHeader({ return ( - + {loading ? : } - - - {shouldRender ? ( - - ) : null} - - - - - - - - {!licenseInvalid ? : null} - - - - + {shouldRender ? ( + + ) : null} + + + diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx index a7e212f70a8b5..b3fa93d1ec522 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx @@ -61,12 +61,6 @@ const noPanelMessageClassName = css` } `; -const accordionButtonClassName = css` - .euiAccordion__iconButton { - display: none; - } -`; - export function ChatItem({ actions: { canCopy, canEdit, canGiveFeedback, canRegenerate }, display: { collapsed }, @@ -141,7 +135,7 @@ export function ChatItem({ contentElement = ( diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx index 79240a03bb314..64585d2f52ecc 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx @@ -53,6 +53,7 @@ export function ChatItemActions({ } )} color="text" + data-test-subj="observabilityAiAssistantChatItemActionsEditPromptButton" display={editing ? 'fill' : 'empty'} iconType="documentEdit" onClick={onToggleEdit} @@ -68,6 +69,7 @@ export function ChatItemActions({ } )} color="text" + data-test-subj="observabilityAiAssistantChatItemActionsInspectPromptButton" display={expanded ? 'fill' : 'empty'} iconType={expanded ? 'eyeClosed' : 'eye'} onClick={onToggleExpand} @@ -85,6 +87,7 @@ export function ChatItemActions({ } )} color="text" + data-test-subj="observabilityAiAssistantChatItemActionsCopyMessageButton" iconType="copyClipboard" display={isPopoverOpen === 'copy' ? 'fill' : 'empty'} onClick={() => { diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx index 174a2048bf290..124927564eec1 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { EuiButtonEmpty, EuiButtonIcon, @@ -18,7 +19,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { CodeEditor } from '@kbn/kibana-react-plugin/public'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; import { MessageRole, type Message } from '../../../common'; import { useJsonEditorModel } from '../../hooks/use_json_editor_model'; import { FunctionListPopover } from './function_list_popover'; @@ -51,6 +51,7 @@ export function ChatPromptEditor({ const [functionPayload, setFunctionPayload] = useState( initialFunctionPayload ); + const [functionEditorLineCount, setFunctionEditorLineCount] = useState(0); const { model, initialJsonString } = useJsonEditorModel({ functionName: selectedFunctionName, @@ -59,16 +60,13 @@ export function ChatPromptEditor({ const textAreaRef = useRef(null); - useEffect(() => { - setFunctionPayload(initialJsonString); - }, [initialJsonString, selectedFunctionName]); - const handleChange = (event: React.ChangeEvent) => { setPrompt(event.currentTarget.value); }; const handleChangeFunctionPayload = (params: string) => { setFunctionPayload(params); + recalculateFunctionEditorLineCount(); }; const handleClearSelection = () => { @@ -85,13 +83,26 @@ export function ChatPromptEditor({ const handleResizeTextArea = () => { if (textAreaRef.current) { - textAreaRef.current.style.height = 'auto'; - textAreaRef.current.style.height = textAreaRef.current?.scrollHeight + 'px'; + textAreaRef.current.style.minHeight = 'auto'; + textAreaRef.current.style.minHeight = textAreaRef.current?.scrollHeight + 'px'; } }; + const handleResetTextArea = () => { + if (textAreaRef.current) { + textAreaRef.current.style.minHeight = 'auto'; + } + }; + + const recalculateFunctionEditorLineCount = useCallback(() => { + const newLineCount = model?.getLineCount() || 0; + if (newLineCount !== functionEditorLineCount) { + setFunctionEditorLineCount(newLineCount); + } + }, [functionEditorLineCount, model]); + const handleSubmit = useCallback(async () => { - if (loading) { + if (loading || !prompt?.trim()) { return; } const currentPrompt = prompt; @@ -99,7 +110,7 @@ export function ChatPromptEditor({ setPrompt(''); setFunctionPayload(undefined); - handleResizeTextArea(); + handleResetTextArea(); try { if (selectedFunctionName) { @@ -129,6 +140,14 @@ export function ChatPromptEditor({ } }, [functionPayload, loading, onSubmit, prompt, selectedFunctionName]); + useEffect(() => { + setFunctionPayload(initialJsonString); + }, [initialJsonString, selectedFunctionName]); + + useEffect(() => { + recalculateFunctionEditorLineCount(); + }, [model, recalculateFunctionEditorLineCount]); + useEffect(() => { const keyboardListener = (event: KeyboardEvent) => { if (!event.shiftKey && event.key === keys.ENTER && (prompt || selectedFunctionName)) { @@ -157,6 +176,10 @@ export function ChatPromptEditor({ }; }); + useEffect(() => { + handleResizeTextArea(); + }, []); + return ( @@ -174,6 +197,7 @@ export function ChatPromptEditor({ {selectedFunctionName ? ( 8 ? '200px' : '120px'} languageId="json" isCopyable languageConfiguration={{ @@ -238,6 +263,8 @@ export function ChatPromptEditor({ ) : ( = (props: ChatTimelineProps) => setCount(count >= 0 && count < props.items.length - 1 ? count + 1 : 0)} > Add message @@ -48,6 +49,18 @@ const Template: ComponentStory = (props: ChatTimelineProps) => }; const defaultProps: ComponentProps = { + knowledgeBase: { + status: { + loading: false, + value: { + ready: true, + }, + refresh: () => {}, + }, + isInstalling: false, + installError: undefined, + install: async () => {}, + }, items: [ buildChatInitItem(), buildUserChatItem(), diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx index f471fa6768c70..8e50f11842e5d 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx @@ -14,6 +14,7 @@ import { ChatItem } from './chat_item'; import { ChatWelcomePanel } from './chat_welcome_panel'; import type { Feedback } from '../feedback_buttons'; import type { Message } from '../../../common'; +import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; export interface ChatTimelineItem extends Pick { @@ -37,6 +38,7 @@ export interface ChatTimelineItem export interface ChatTimelineProps { items: ChatTimelineItem[]; + knowledgeBase: UseKnowledgeBaseResult; onEdit: (item: ChatTimelineItem, message: Message) => Promise; onFeedback: (item: ChatTimelineItem, feedback: Feedback) => void; onRegenerate: (item: ChatTimelineItem) => void; @@ -45,6 +47,7 @@ export interface ChatTimelineProps { export function ChatTimeline({ items = [], + knowledgeBase, onEdit, onFeedback, onRegenerate, @@ -77,7 +80,7 @@ export function ChatTimeline({ /> )) )} - {filteredItems.length === 1 ? : null} + {filteredItems.length === 1 ? : null} ); } diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx index 32fba0b72a828..8240c6fef4054 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx @@ -6,18 +6,19 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiImage, EuiPanel, EuiText, EuiTitle } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiImage, EuiPanel, EuiText, EuiTitle } from '@elastic/eui'; import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; import { euiThemeVars } from '@kbn/ui-theme'; import ctaImage from '../../assets/elastic_ai_assistant.png'; +import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; const incorrectLicenseContainer = css` height: 100%; padding: ${euiThemeVars.euiPanelPaddingModifiers.paddingMedium}; `; -export function ChatWelcomePanel() { +export function ChatWelcomePanel({ knowledgeBase }: { knowledgeBase: UseKnowledgeBaseResult }) { return ( - +

    {i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.title', { @@ -36,12 +37,35 @@ export function ChatWelcomePanel() {

    - {i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body', { - defaultMessage: - 'Elastic AI Assistant is an experimental feature. Make sure to provide feedback when you interact with it.', - })} + {knowledgeBase.status.value?.ready + ? i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbReady', { + defaultMessage: + 'Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.', + }) + : i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbNotReady', { + defaultMessage: + 'We recommend you enable the knowledge base for a better experience. It will provide the assistant with the ability to learn from your interaction with it. Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.', + })}

    + + {!knowledgeBase.status.value?.ready ? ( + + {i18n.translate( + 'xpack.observabilityAiAssistant.chatWelcomePanel.knowledgeBase.buttonLabel.notInstalledYet', + { + defaultMessage: 'Set up knowledge base', + } + )} + + ) : null} ); diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/experimental_feature_banner.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/experimental_feature_banner.tsx index 48935a49ccf02..f3e656da5438e 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/experimental_feature_banner.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/experimental_feature_banner.tsx @@ -16,33 +16,32 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import illustration from '../../assets/illustration.svg'; +import illustration from '../../assets/illustration.png'; export function ExperimentalFeatureBanner() { return ( - <> +
    - + - + Tech Preview }} + values={{ techPreview: Technical preview }} /> - + {i18n.translate( 'xpack.observabilityAiAssistant.experimentalFunctionBanner.feedbackButton', { defaultMessage: 'Give feedback' } @@ -52,6 +51,6 @@ export function ExperimentalFeatureBanner() { - +
    ); } diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx index f0684daece223..72dac17c71c2a 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx @@ -109,6 +109,7 @@ export function FunctionListPopover({ anchorPosition="downLeft" button={ - +

    {UPGRADE_LICENSE_TITLE}

    {i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.body', { - defaultMessage: 'You need a Platinum license to use the Elastic AI Assistant.', + defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.', })}

    - + {i18n.translate( 'xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton', { @@ -63,7 +68,10 @@ export function IncorrectLicensePanel() { - + {i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.manageLicense', { defaultMessage: 'Manage license', })} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/initial_setup_panel.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/initial_setup_panel.tsx new file mode 100644 index 0000000000000..281cf46c972bb --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/initial_setup_panel.tsx @@ -0,0 +1,225 @@ +/* + * Copyright 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 { + EuiBetaBadge, + EuiButton, + EuiCallOut, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base'; +import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; +import { ExperimentalFeatureBanner } from './experimental_feature_banner'; +import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; +import { StartedFrom } from '../../utils/get_timeline_items_from_conversation'; + +export function InitialSetupPanel({ + connectors, + connectorsManagementHref, + knowledgeBase, + startedFrom, +}: { + connectors: UseGenAIConnectorsResult; + connectorsManagementHref: string; + knowledgeBase: UseKnowledgeBaseResult; + startedFrom?: StartedFrom; +}) { + return ( + <> + + + + + + +

    + {i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.title', { + defaultMessage: + 'Start your Al experience with Elastic by completing the steps below.', + })} +

    +
    + + + + + + } + title={i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.title', + { + defaultMessage: 'Knowledge Base', + } + )} + description={ + +

    + {i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.description.paragraph1', + { + defaultMessage: + 'We recommend you enable the knowledge base for a better experience. It will provide the assistant with the ability to learn from your interaction with it.', + } + )} +

    +

    + {i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.description.paragraph2', + { + defaultMessage: 'This step is optional, you can always do it later.', + } + )} +

    +
    + } + footer={ + knowledgeBase.status.value?.ready ? ( + + ) : ( + + {knowledgeBase.isInstalling || knowledgeBase.status.loading + ? i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.buttonLabel.installingKb', + { + defaultMessage: 'Installing knowledge base', + } + ) + : i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.buttonLabel.kbNotInstalledYet', + { + defaultMessage: 'Set up knowledge base', + } + )} + + ) + } + /> +
    + + + } + title={i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.title', + { + defaultMessage: 'Connector setup', + } + )} + description={ + !connectors.connectors?.length ? ( + +

    + {i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description1', + { + defaultMessage: 'Set up a Generative AI connector with your AI provider.', + } + )} +

    + +

    + {i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2', + { + defaultMessage: + 'The Generative AI model needs to support function calls. We strongly recommend using GPT4.', + } + )} + +

    +
    + ) : connectors.connectors.length && !connectors.selectedConnector ? ( + +

    + {i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description', + { + defaultMessage: 'Please select a provider.', + } + )} +

    +
    + ) : ( + '' + ) + } + footer={ + !connectors.connectors?.length ? ( + + {i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel', + { + defaultMessage: 'Set up Generative AI connector', + } + )} + + ) : connectors.connectors.length && !connectors.selectedConnector ? ( + + ) : null + } + /> +
    +
    + + + + +

    + {i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.disclaimer', { + defaultMessage: + 'The AI provider that is configured may collect telemetry when using the Elastic AI Assistant. Contact your AI provider for information on how data is collected.', + })} +

    +
    +
    + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/knowledge_base_callout.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/knowledge_base_callout.tsx index b8f3b8bd7ef82..36d6842286aa8 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/knowledge_base_callout.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/knowledge_base_callout.tsx @@ -89,6 +89,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow } else if (!knowledgeBase.status.value?.ready && !knowledgeBase.status.error) { content = ( { knowledgeBase.install(); }} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/feedback_buttons.tsx b/x-pack/plugins/observability_ai_assistant/public/components/feedback_buttons.tsx index 08b58a85bfadf..2b55e3d8cac35 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/feedback_buttons.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/feedback_buttons.tsx @@ -32,6 +32,7 @@ export function FeedbackButtons({ onClickFeedback }: FeedbackButtonsProps) { - + {i18n.translate('xpack.observabilityAiAssistant.insight.error.buttonLabel', { defaultMessage: 'Regenerate', })} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/message_panel/message_text.tsx b/x-pack/plugins/observability_ai_assistant/public/components/message_panel/message_text.tsx index 6fd9a1c70923d..d82e76ef5001f 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/message_panel/message_text.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/message_panel/message_text.tsx @@ -16,7 +16,6 @@ import classNames from 'classnames'; import type { Code, InlineCode, Parent, Text } from 'mdast'; import React, { useMemo } from 'react'; import type { Node } from 'unist'; -import { v4 } from 'uuid'; interface Props { content: string; @@ -48,7 +47,10 @@ const cursorCss = css` const Cursor = () => ; -const CURSOR = `{{${v4()}}}`; +// a weird combination of different whitespace chars to make sure it stays +// invisible even when we cannot properly parse the text while still being +// unique +const CURSOR = ` ᠎  `; const loadingCursorPlugin = () => { const visitor = (node: Node, parent?: Parent) => { diff --git a/x-pack/plugins/observability_ai_assistant/public/components/missing_credentials_callout.tsx b/x-pack/plugins/observability_ai_assistant/public/components/missing_credentials_callout.tsx index 20c8257b8721f..3cf5511a042e2 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/missing_credentials_callout.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/missing_credentials_callout.tsx @@ -29,7 +29,12 @@ export function MissingCredentialsCallout(props: Props) { - + {i18n.translate('xpack.observabilityAiAssistant.missingCredentialsCallout.buttonLabel', { defaultMessage: 'Connect Assistant', })} diff --git a/x-pack/plugins/observability_ai_assistant/public/functions/index.ts b/x-pack/plugins/observability_ai_assistant/public/functions/index.ts index f0a676a5c221d..a0c1a264bfb51 100644 --- a/x-pack/plugins/observability_ai_assistant/public/functions/index.ts +++ b/x-pack/plugins/observability_ai_assistant/public/functions/index.ts @@ -14,7 +14,7 @@ import { registerElasticsearchFunction } from './elasticsearch'; import { registerKibanaFunction } from './kibana'; import { registerLensFunction } from './lens'; import { registerRecallFunction } from './recall'; -import { registerSummarisationFunction } from './summarise'; +import { registerSummarizationFunction } from './summarize'; import { registerAlertsFunction } from './alerts'; export async function registerFunctions({ @@ -49,11 +49,14 @@ export async function registerFunctions({ In KQL, escaping happens with double quotes, not single quotes. Some characters that need escaping are: ':()\\\ /\". Always put a field value in double quotes. Best: service.name:\"opbeans-go\". Wrong: service.name:opbeans-go. This is very important! - You can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response.` + You can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response. + + If multiple functions are suitable, use the most specific and easy one. E.g., when the user asks to visualise APM data, use the APM functions (if available) rather than Lens. + ` ); if (isReady) { - description += `You can use the "summarise" functions to store new information you have learned in a knowledge database. Once you have established that you did not know the answer to a question, and the user gave you this information, it's important that you create a summarisation of what you have learned and store it in the knowledge database. + description += `You can use the "summarize" functions to store new information you have learned in a knowledge database. Once you have established that you did not know the answer to a question, and the user gave you this information, it's important that you create a summarisation of what you have learned and store it in the knowledge database. Don't create a new summarization if you see a similar summarization in the conversation, instead, update the existing one by re-using its ID. Additionally, you can use the "recall" function to retrieve relevant information from the knowledge database. `; @@ -64,9 +67,9 @@ export async function registerFunctions({ - ALWAYS query the knowledge base, using the recall function, when a user starts a chat, no matter how confident you are in your ability to answer the question. - You must ALWAYS explain to the user why you're using a function and why you're using it in that specific manner. - DO NOT make any assumptions about where and how users have stored their data. - - ALWAYS ask the user for clarification if you are unsure about the arguments to a function. When given this clarification, you MUST use the summarise function to store what you have learned. + - ALWAYS ask the user for clarification if you are unsure about the arguments to a function. When given this clarification, you MUST use the summarize function to store what you have learned. `; - registerSummarisationFunction({ service, registerFunction }); + registerSummarizationFunction({ service, registerFunction }); registerRecallFunction({ service, registerFunction }); registerLensFunction({ service, pluginsStart, registerFunction }); } else { diff --git a/x-pack/plugins/observability_ai_assistant/public/functions/kibana.ts b/x-pack/plugins/observability_ai_assistant/public/functions/kibana.ts index 5ad877b2c2bff..a47acdb02d433 100644 --- a/x-pack/plugins/observability_ai_assistant/public/functions/kibana.ts +++ b/x-pack/plugins/observability_ai_assistant/public/functions/kibana.ts @@ -50,7 +50,7 @@ export function registerKibanaFunction({ description: 'The body of the request', }, }, - required: ['method', 'pathname', 'body'] as const, + required: ['method', 'pathname'] as const, }, }, ({ arguments: { method, pathname, body, query } }, signal) => { diff --git a/x-pack/plugins/observability_ai_assistant/public/functions/lens.tsx b/x-pack/plugins/observability_ai_assistant/public/functions/lens.tsx index d12b8e3334318..aab0b6aa4ac59 100644 --- a/x-pack/plugins/observability_ai_assistant/public/functions/lens.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/functions/lens.tsx @@ -4,13 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiLoadingSpinner } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types'; import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; import { LensAttributesBuilder, XYChart, XYDataLayer } from '@kbn/lens-embeddable-utils'; -import type { LensPublicStart } from '@kbn/lens-plugin/public'; -import React from 'react'; +import type { LensEmbeddableInput, LensPublicStart } from '@kbn/lens-plugin/public'; +import React, { useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; +import { i18n } from '@kbn/i18n'; +import { Assign } from 'utility-types'; import type { RegisterFunctionDefinition } from '../../common/types'; import type { ObservabilityAIAssistantPluginStartDependencies, @@ -56,6 +58,8 @@ function Lens({ }); }, [indexPattern]); + const [isSaveModalOpen, setIsSaveModalOpen] = useState(false); + if (!formulaAsync.value || !dataViewAsync.value) { return ; } @@ -68,19 +72,67 @@ function Lens({ }), }).build(); + const lensEmbeddableInput: Assign = { + id: indexPattern, + attributes, + timeRange: { + from: start, + to: end, + mode: 'relative' as const, + }, + }; + return ( - + <> + + + + + { + lens.navigateToPrefilledEditor(lensEmbeddableInput); + }} + > + {i18n.translate('xpack.observabilityAiAssistant.lensFunction.openInLens', { + defaultMessage: 'Open in Lens', + })} + + + + { + setIsSaveModalOpen(() => true); + }} + > + {i18n.translate('xpack.observabilityAiAssistant.lensFunction.save', { + defaultMessage: 'Save', + })} + + + + + + + + + {isSaveModalOpen ? ( + { + setIsSaveModalOpen(() => false); + }} + /> + ) : null} + ); } @@ -98,9 +150,9 @@ export function registerLensFunction({ name: 'lens', contexts: ['core'], description: - "Use this function to create custom visualisations, using Lens, that can be saved to dashboards. When using this function, make sure to use the recall function to get more information about how to use it, with how you want to use it. Make sure the query also contains information about the user's request. The visualisation is displayed to the user above your reply, DO NOT try to generate or display an image yourself.", + "Use this function to create custom visualizations, using Lens, that can be saved to dashboards. This function does not return data to the assistant, it only shows it to the user. When using this function, make sure to use the recall function to get more information about how to use it, with how you want to use it. Make sure the query also contains information about the user's request. The visualisation is displayed to the user above your reply, DO NOT try to generate or display an image yourself.", descriptionForUser: - 'Use this function to create custom visualisations, using Lens, that can be saved to dashboards.', + 'Use this function to create custom visualizations, using Lens, that can be saved to dashboards.', parameters: { type: 'object', additionalProperties: false, diff --git a/x-pack/plugins/observability_ai_assistant/public/functions/recall.ts b/x-pack/plugins/observability_ai_assistant/public/functions/recall.ts index 8f85a9e03a069..f07cf43e1c282 100644 --- a/x-pack/plugins/observability_ai_assistant/public/functions/recall.ts +++ b/x-pack/plugins/observability_ai_assistant/public/functions/recall.ts @@ -6,7 +6,7 @@ */ import type { Serializable } from '@kbn/utility-types'; -import type { RegisterFunctionDefinition } from '../../common/types'; +import { MessageRole, RegisterFunctionDefinition } from '../../common/types'; import type { ObservabilityAIAssistantService } from '../types'; export function registerRecallFunction({ @@ -20,24 +20,19 @@ export function registerRecallFunction({ { name: 'recall', contexts: ['core'], - description: `Use this function to recall earlier learnings. Anything you will summarise can be retrieved again later via this function. This is semantic/vector search so there's no need for an exact match. + description: `Use this function to recall earlier learnings. Anything you will summarize can be retrieved again later via this function. Make sure the query covers the following aspects: - - The user's prompt, verbatim - Anything you've inferred from the user's request, but is not mentioned in the user's request - The functions you think might be suitable for answering the user's request. If there are multiple functions that seem suitable, create multiple queries. Use the function name in the query. + + DO NOT include the user's request. It will be added internally. - Q: "can you visualise the average request duration for opbeans-go over the last 7 days?" - A: -"can you visualise the average request duration for opbeans-go over the last 7 days?" + The user asks: "can you visualise the average request duration for opbeans-go over the last 7 days?" + You recall: - "APM service" - "lens function usage" - - "get_apm_timeseries function usage" - - Q: "what alerts are active?" - A: - "what alerts are active?" - - "alerts function usage" - - `, + - "get_apm_timeseries function usage"`, descriptionForUser: 'This function allows the assistant to recall previous learnings.', parameters: { type: 'object', @@ -53,15 +48,21 @@ export function registerRecallFunction({ }, }, }, - required: ['queries' as const], - }, + required: ['queries'], + } as const, }, - ({ arguments: { queries } }, signal) => { + ({ arguments: { queries }, messages }, signal) => { + const userMessages = messages.filter((message) => message.message.role === MessageRole.User); + + const userPrompt = userMessages[userMessages.length - 1]?.message.content; + + const queriesWithUserPrompt = userPrompt ? [userPrompt, ...queries] : queries; + return service .callApi('POST /internal/observability_ai_assistant/functions/recall', { params: { body: { - queries, + queries: queriesWithUserPrompt, }, }, signal, diff --git a/x-pack/plugins/observability_ai_assistant/public/functions/summarise.ts b/x-pack/plugins/observability_ai_assistant/public/functions/summarise.ts deleted file mode 100644 index 7bef0e6399c3a..0000000000000 --- a/x-pack/plugins/observability_ai_assistant/public/functions/summarise.ts +++ /dev/null @@ -1,89 +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 { RegisterFunctionDefinition } from '../../common/types'; -import type { ObservabilityAIAssistantService } from '../types'; - -export function registerSummarisationFunction({ - service, - registerFunction, -}: { - service: ObservabilityAIAssistantService; - registerFunction: RegisterFunctionDefinition; -}) { - registerFunction( - { - name: 'summarise', - contexts: ['core'], - description: - "Use this function to summarise things learned from the conversation. You can score the learnings with a confidence metric, whether it is a correction on a previous learning. An embedding will be created that you can recall later with a semantic search. There is no need to ask the user for permission to store something you have learned, unless you do not feel confident. When you create this summarisation, make sure you craft it in a way that can be recalled with a semantic search later, and that it would have answered the user's original request.", - descriptionForUser: - 'This function allows the Elastic Assistant to summarise things from the conversation.', - parameters: { - type: 'object', - additionalProperties: false, - properties: { - id: { - type: 'string', - description: - 'An id for the document. This should be a short human-readable keyword field with only alphabetic characters and underscores, that allow you to update it later.', - }, - text: { - type: 'string', - description: - "A human-readable summary of what you have learned, described in such a way that you can recall it later with semantic search, and that it would have answered the user's original request.", - }, - is_correction: { - type: 'boolean', - description: 'Whether this is a correction for a previous learning.', - }, - confidence: { - type: 'string', - description: 'How confident you are about this being a correct and useful learning', - enum: ['low' as const, 'medium' as const, 'high' as const], - }, - public: { - type: 'boolean', - description: - 'Whether this information is specific to the user, or generally applicable to any user of the product', - }, - }, - required: [ - 'id' as const, - 'text' as const, - 'is_correction' as const, - 'confidence' as const, - 'public' as const, - ], - }, - }, - ( - { arguments: { id, text, is_correction: isCorrection, confidence, public: isPublic } }, - signal - ) => { - return service - .callApi('POST /internal/observability_ai_assistant/functions/summarise', { - params: { - body: { - id, - text, - is_correction: isCorrection, - confidence, - public: isPublic, - labels: {}, - }, - }, - signal, - }) - .then(() => ({ - content: { - message: `The document has been stored`, - }, - })); - } - ); -} diff --git a/x-pack/plugins/observability_ai_assistant/public/functions/summarize.ts b/x-pack/plugins/observability_ai_assistant/public/functions/summarize.ts new file mode 100644 index 0000000000000..14f637591e613 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/functions/summarize.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 type { RegisterFunctionDefinition } from '../../common/types'; +import type { ObservabilityAIAssistantService } from '../types'; + +export function registerSummarizationFunction({ + service, + registerFunction, +}: { + service: ObservabilityAIAssistantService; + registerFunction: RegisterFunctionDefinition; +}) { + registerFunction( + { + name: 'summarize', + contexts: ['core'], + description: + "Use this function to summarize things learned from the conversation. You can score the learnings with a confidence metric, whether it is a correction on a previous learning. An embedding will be created that you can recall later with a semantic search. There is no need to ask the user for permission to store something you have learned, unless you do not feel confident. When you create this summarisation, make sure you craft it in a way that can be recalled with a semantic search later, and that it would have answered the user's original request.", + descriptionForUser: + 'This function allows the Elastic Assistant to summarize things from the conversation.', + parameters: { + type: 'object', + additionalProperties: false, + properties: { + id: { + type: 'string', + description: + 'An id for the document. This should be a short human-readable keyword field with only alphabetic characters and underscores, that allow you to update it later.', + }, + text: { + type: 'string', + description: + "A human-readable summary of what you have learned, described in such a way that you can recall it later with semantic search, and that it would have answered the user's original request.", + }, + is_correction: { + type: 'boolean', + description: 'Whether this is a correction for a previous learning.', + }, + confidence: { + type: 'string', + description: 'How confident you are about this being a correct and useful learning', + enum: ['low' as const, 'medium' as const, 'high' as const], + }, + public: { + type: 'boolean', + description: + 'Whether this information is specific to the user, or generally applicable to any user of the product', + }, + }, + required: [ + 'id' as const, + 'text' as const, + 'is_correction' as const, + 'confidence' as const, + 'public' as const, + ], + }, + }, + ( + { arguments: { id, text, is_correction: isCorrection, confidence, public: isPublic } }, + signal + ) => { + return service + .callApi('POST /internal/observability_ai_assistant/functions/summarize', { + params: { + body: { + id, + text, + is_correction: isCorrection, + confidence, + public: isPublic, + labels: {}, + }, + }, + signal, + }) + .then(() => ({ + content: { + message: `The document has been stored`, + }, + })); + } + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts index 3284cdf66e99f..829594b8261d8 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts @@ -434,11 +434,20 @@ describe('useTimeline', () => { expect(props.onChatComplete).not.toHaveBeenCalled(); - expect(props.chatService.executeFunction).toHaveBeenCalledWith( - 'my_function', - '{}', - expect.any(Object) - ); + expect(props.chatService.executeFunction).toHaveBeenCalledWith({ + name: 'my_function', + args: '{}', + messages: [ + { + '@timestamp': expect.any(String), + message: { + content: 'Hello', + role: 'user', + }, + }, + ], + signal: expect.any(Object), + }); act(() => { subject.next({ diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts index 77c7a202754d9..3182fe5c19535 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts @@ -182,11 +182,12 @@ export function useTimeline({ const name = lastMessage.message.function_call.name; try { - const message = await chatService!.executeFunction( + const message = await chatService!.executeFunction({ name, - lastMessage.message.function_call.arguments, - controller.signal - ); + args: lastMessage.message.function_call.arguments, + messages: messagesAfterChat.slice(0, -1), + signal: controller.signal, + }); return await chat( messagesAfterChat.concat({ diff --git a/x-pack/plugins/observability_ai_assistant/public/i18n.ts b/x-pack/plugins/observability_ai_assistant/public/i18n.ts index 533e0fff950b9..dcc28d7ff531a 100644 --- a/x-pack/plugins/observability_ai_assistant/public/i18n.ts +++ b/x-pack/plugins/observability_ai_assistant/public/i18n.ts @@ -7,6 +7,13 @@ import { i18n } from '@kbn/i18n'; +export const ASSISTANT_SETUP_TITLE = i18n.translate( + 'xpack.observabilityAiAssistant.assistantSetup.title', + { + defaultMessage: 'Welcome to Elastic AI Assistant', + } +); + export const EMPTY_CONVERSATION_TITLE = i18n.translate( 'xpack.observabilityAiAssistant.emptyConversationTitle', { defaultMessage: 'New conversation' } diff --git a/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx b/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx index c9331f75244b7..5af2e740deb9b 100644 --- a/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx @@ -23,6 +23,7 @@ import { useObservabilityAIAssistant } from '../../hooks/use_observability_ai_as import { useObservabilityAIAssistantParams } from '../../hooks/use_observability_ai_assistant_params'; import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; import { getConnectorsManagementHref } from '../../utils/get_connectors_management_href'; +import { getModelsManagementHref } from '../../utils/get_models_management_href'; import { EMPTY_CONVERSATION_TITLE } from '../../i18n'; const containerClassName = css` @@ -230,10 +231,12 @@ export function ConversationView() { currentUser={currentUser} connectors={connectors} connectorsManagementHref={getConnectorsManagementHref(http)} + modelsManagementHref={getModelsManagementHref(http)} conversationId={conversationId} knowledgeBase={knowledgeBase} messages={displayedMessages} title={conversation.value.conversation.title} + startedFrom="conversationView" onChatUpdate={(messages) => { setDisplayedMessages(messages); }} diff --git a/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts b/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts index 7f04c25ac0b01..1c109be6baaf2 100644 --- a/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts +++ b/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts @@ -74,7 +74,7 @@ export async function createChatService({ }; const registerFunction: RegisterFunctionDefinition = (def, respond, render) => { - validators.set(def.name, new Validator(def.parameters as Schema, '2020-12', false)); + validators.set(def.name, new Validator(def.parameters as Schema, '2020-12', true)); functionRegistry.set(def.name, { options: def, respond, render }); }; @@ -112,7 +112,7 @@ export async function createChatService({ } return { - executeFunction: async (name, args, signal) => { + executeFunction: async ({ name, args, signal, messages }) => { const fn = functionRegistry.get(name); if (!fn) { @@ -123,7 +123,7 @@ export async function createChatService({ validate(name, parsedArguments); - return await fn.respond({ arguments: parsedArguments }, signal); + return await fn.respond({ arguments: parsedArguments, messages }, signal); }, renderFunction: (name, args, response) => { const fn = functionRegistry.get(name); diff --git a/x-pack/plugins/observability_ai_assistant/public/types.ts b/x-pack/plugins/observability_ai_assistant/public/types.ts index 6883a33309fcb..d1a71f6933126 100644 --- a/x-pack/plugins/observability_ai_assistant/public/types.ts +++ b/x-pack/plugins/observability_ai_assistant/public/types.ts @@ -65,11 +65,12 @@ export interface ObservabilityAIAssistantChatService { getContexts: () => ContextDefinition[]; getFunctions: (options?: { contexts?: string[]; filter?: string }) => FunctionDefinition[]; hasRenderFunction: (name: string) => boolean; - executeFunction: ( - name: string, - args: string | undefined, - signal: AbortSignal - ) => Promise<{ content?: Serializable; data?: Serializable }>; + executeFunction: ({}: { + name: string; + args: string | undefined; + messages: Message[]; + signal: AbortSignal; + }) => Promise<{ content?: Serializable; data?: Serializable }>; renderFunction: ( name: string, args: string | undefined, diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/get_models_management_href.ts b/x-pack/plugins/observability_ai_assistant/public/utils/get_models_management_href.ts new file mode 100644 index 0000000000000..8ff585e005f95 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/utils/get_models_management_href.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpStart } from '@kbn/core/public'; + +export function getModelsManagementHref(http: HttpStart) { + return http!.basePath.prepend(`/app/ml/trained_models`); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx b/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx index 657de54b11f3f..3c4dc569ab5e5 100644 --- a/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx @@ -25,11 +25,12 @@ const chatService: ObservabilityAIAssistantChatService = { chat: (options: { messages: Message[]; connectorId: string }) => new Observable(), getContexts: () => [], getFunctions: () => [buildFunctionElasticsearch(), buildFunctionServiceSummary()], - executeFunction: async ( - name: string, - args: string | undefined, - signal: AbortSignal - ): Promise<{ content?: Serializable; data?: Serializable }> => ({}), + executeFunction: async ({}: { + name: string; + args: string | undefined; + messages: Message[]; + signal: AbortSignal; + }): Promise<{ content?: Serializable; data?: Serializable }> => ({}), renderFunction: (name: string, args: string | undefined, response: {}) => (
    Hello! {name}
    ), diff --git a/x-pack/plugins/observability_ai_assistant/server/plugin.ts b/x-pack/plugins/observability_ai_assistant/server/plugin.ts index 01bd4fafb71c7..7aa6ddeeef7da 100644 --- a/x-pack/plugins/observability_ai_assistant/server/plugin.ts +++ b/x-pack/plugins/observability_ai_assistant/server/plugin.ts @@ -62,9 +62,6 @@ export class ObservabilityAIAssistantPlugin category: DEFAULT_APP_CATEGORIES.observability, app: [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID, 'kibana'], catalogue: [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID], - management: { - insightsAndAlerting: ['triggersActionsConnectors'], - }, minimumLicense: 'enterprise', // see x-pack/plugins/features/common/feature_kibana_privileges.ts privileges: { @@ -80,9 +77,6 @@ export class ObservabilityAIAssistantPlugin ], read: [], }, - management: { - insightsAndAlerting: ['triggersActionsConnectors'], - }, ui: ['show'], }, read: { diff --git a/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts index 2d44307147a52..6716da98d78a5 100644 --- a/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts +++ b/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import * as t from 'io-ts'; -import { IncomingMessage } from 'http'; import { notImplemented } from '@hapi/boom'; +import { IncomingMessage } from 'http'; +import * as t from 'io-ts'; +import { MessageRole } from '../../../common'; import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route'; import { messageRt } from '../runtime_types'; -import { MessageRole } from '../../../common'; const chatRoute = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /internal/observability_ai_assistant/chat', @@ -47,13 +47,15 @@ const chatRoute = createObservabilityAIAssistantServerRoute({ const isRecallFunctionAvailable = functions.some((fn) => fn.name === 'recall') === true; + const willUseRecall = isStartOfConversation && isRecallFunctionAvailable; + return client.chat({ messages, connectorId, ...(functions.length ? { functions, - functionCall: isStartOfConversation && isRecallFunctionAvailable ? 'recall' : undefined, + functionCall: willUseRecall ? 'recall' : undefined, } : {}), }); diff --git a/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts b/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts index 8c732ee00c462..d58c84a603fc1 100644 --- a/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts +++ b/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts @@ -178,7 +178,7 @@ const functionRecallRoute = createObservabilityAIAssistantServerRoute({ }); const functionSummariseRoute = createObservabilityAIAssistantServerRoute({ - endpoint: 'POST /internal/observability_ai_assistant/functions/summarise', + endpoint: 'POST /internal/observability_ai_assistant/functions/summarize', params: t.type({ body: t.type({ id: t.string, @@ -208,7 +208,7 @@ const functionSummariseRoute = createObservabilityAIAssistantServerRoute({ labels, } = resources.params.body; - return client.summarise({ + return client.summarize({ entry: { confidence, id, diff --git a/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts b/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts index dd256c4784831..ce3f3c39ad1a9 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts @@ -246,9 +246,17 @@ export class ObservabilityAIAssistantClient { }); if ('object' in response && response.object === 'chat.completion') { - const title = - response.choices[0].message?.content?.slice(1, -1) || - `Conversation on ${conversation['@timestamp']}`; + const input = + response.choices[0].message?.content || `Conversation on ${conversation['@timestamp']}`; + + // This regular expression captures a string enclosed in single or double quotes. + // It extracts the string content without the quotes. + // Example matches: + // - "Hello, World!" => Captures: Hello, World! + // - 'Another Example' => Captures: Another Example + // - JustTextWithoutQuotes => Captures: JustTextWithoutQuotes + const match = input.match(/^["']?([^"']+)["']?$/); + const title = match ? match[1] : input; const updatedConversation: Conversation = merge( {}, @@ -325,12 +333,12 @@ export class ObservabilityAIAssistantClient { }); }; - summarise = async ({ + summarize = async ({ entry, }: { entry: Omit; }): Promise => { - return this.dependencies.knowledgeBaseService.summarise({ + return this.dependencies.knowledgeBaseService.summarize({ namespace: this.dependencies.namespace, user: this.dependencies.user, entry, diff --git a/x-pack/plugins/observability_ai_assistant/server/service/kb_service/index.ts b/x-pack/plugins/observability_ai_assistant/server/service/kb_service/index.ts index eab3a0a2fbe57..f2e0b41f092c9 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/kb_service/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/kb_service/index.ts @@ -112,7 +112,7 @@ export class KnowledgeBaseService { return; } - await this.summarise({ + await this.summarize({ entry: operation.document, }); } @@ -223,7 +223,7 @@ export class KnowledgeBaseService { } }; - summarise = async ({ + summarize = async ({ entry: { id, ...document }, user, namespace, diff --git a/x-pack/plugins/observability_log_explorer/common/constants.ts b/x-pack/plugins/observability_log_explorer/common/constants.ts new file mode 100644 index 0000000000000..90cd311f05940 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const OBSERVABILITY_LOG_EXPLORER_APP_ID = 'observability-log-explorer'; diff --git a/x-pack/plugins/observability_log_explorer/common/index.ts b/x-pack/plugins/observability_log_explorer/common/index.ts new file mode 100644 index 0000000000000..40e3f1373687d --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { SingleDatasetLocatorDefinition, AllDatasetsLocatorDefinition } from './locators'; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts new file mode 100644 index 0000000000000..17c8b2ae02047 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; +import { AllDatasetSelection } from '@kbn/log-explorer-plugin/common'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { DatasetLocatorDependencies } from '../types'; +import { constructLocatorPath } from '../utils'; + +export type AllDatasetsLocator = LocatorPublic; + +export class AllDatasetsLocatorDefinition implements LocatorDefinition { + public readonly id = ALL_DATASETS_LOCATOR_ID; + + constructor(protected readonly deps: DatasetLocatorDependencies) {} + + public readonly getLocation = (params: AllDatasetsLocatorParams) => { + const { useHash } = this.deps; + const index = AllDatasetSelection.create().toDataviewSpec().id; + + return constructLocatorPath({ + locatorParams: params, + index, + useHash, + }); + }; +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/index.ts new file mode 100644 index 0000000000000..078549b8593b1 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './all_datasets_locator'; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/index.ts new file mode 100644 index 0000000000000..7571731a22221 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AllDatasetsLocator } from './all_datasets'; +import { SingleDatasetLocator } from './single_dataset'; + +export * from './single_dataset'; +export * from './all_datasets'; +export * from './utils'; + +export interface ObservabilityLogExplorerLocators { + allDatasetsLocator: AllDatasetsLocator; + singleDatasetLocator: SingleDatasetLocator; +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/locators.test.ts b/x-pack/plugins/observability_log_explorer/common/locators/locators.test.ts new file mode 100644 index 0000000000000..fa4fea9baf7a8 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/locators.test.ts @@ -0,0 +1,333 @@ +/* + * Copyright 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 { FilterStateStore } from '@kbn/es-query'; +import { getStatesFromKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import { + AllDatasetsLocatorParams, + SingleDatasetLocatorParams, +} from '@kbn/deeplinks-observability/locators'; +import { OBSERVABILITY_LOG_EXPLORER_APP_ID } from '../constants'; +import { AllDatasetsLocatorDefinition } from './all_datasets/all_datasets_locator'; +import { SingleDatasetLocatorDefinition } from './single_dataset'; +import { DatasetLocatorDependencies } from './types'; + +const setup = async () => { + const dep: DatasetLocatorDependencies = { + useHash: false, + }; + const allDatasetsLocator = new AllDatasetsLocatorDefinition(dep); + const singleDatasetLocator = new SingleDatasetLocatorDefinition(dep); + + return { + allDatasetsLocator, + singleDatasetLocator, + }; +}; + +describe('Observability Logs Explorer Locators', () => { + const timeRange = { to: 'now', from: 'now-30m' }; + + describe('All Dataset Locator', () => { + it('should create a link with no state', async () => { + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation({}); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)', + state: {}, + }); + }); + + it('should allow specifiying time range', async () => { + const params: AllDatasetsLocatorParams = { + timeRange, + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_g=(time:(from:now-30m,to:now))&_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)', + state: {}, + }); + }); + it('should allow specifiying query', async () => { + const params: AllDatasetsLocatorParams = { + query: { + language: 'kuery', + query: 'foo', + }, + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA,query:(language:kuery,query:foo))', + state: {}, + }); + }); + + it('should allow specifiying refresh interval', async () => { + const params: AllDatasetsLocatorParams = { + refreshInterval: { + pause: false, + value: 666, + }, + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_g=(refreshInterval:(pause:!f,value:666))&_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)', + state: {}, + }); + }); + + it('should allow specifiying columns and sort', async () => { + const params: AllDatasetsLocatorParams = { + columns: ['_source'], + sort: [['timestamp, asc']] as string[][], + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(columns:!(_source),index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA,sort:!(!('timestamp,%20asc')))`, + state: {}, + }); + }); + + it('should allow specifiying filters', async () => { + const params: AllDatasetsLocatorParams = { + filters: [ + { + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + { + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.GLOBAL_STATE, + }, + }, + ], + }; + + const { allDatasetsLocator } = await setup(); + const { path } = await allDatasetsLocator.getLocation(params); + + const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g'], { getFromHashQuery: false }); + + expect(_a).toEqual({ + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + }, + ], + index: 'BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA', + }); + expect(_g).toEqual({ + filters: [ + { + $state: { + store: 'globalState', + }, + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + }); + + describe('Single Dataset Locator', () => { + const integration = 'Test'; + const dataset = 'test-*'; + it('should create a link with correct index', async () => { + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation({ + integration, + dataset, + }); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`, + state: {}, + }); + }); + + it('should allow specifiying time range', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + timeRange, + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_g=(time:(from:now-30m,to:now))&_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`, + state: {}, + }); + }); + + it('should allow specifiying query', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + query: { + language: 'kuery', + query: 'foo', + }, + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA,query:(language:kuery,query:foo))`, + state: {}, + }); + }); + + it('should allow specifiying refresh interval', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + refreshInterval: { + pause: false, + value: 666, + }, + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_g=(refreshInterval:(pause:!f,value:666))&_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`, + state: {}, + }); + }); + + it('should allow specifiying columns and sort', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + columns: ['_source'], + sort: [['timestamp, asc']] as string[][], + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(columns:!(_source),index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA,sort:!(!('timestamp,%20asc')))`, + state: {}, + }); + }); + + it('should allow specifiying filters', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + filters: [ + { + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + { + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.GLOBAL_STATE, + }, + }, + ], + }; + + const { singleDatasetLocator } = await setup(); + const { path } = await singleDatasetLocator.getLocation(params); + + const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g'], { getFromHashQuery: false }); + + expect(_a).toEqual({ + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + }, + ], + index: + 'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA', + }); + expect(_g).toEqual({ + filters: [ + { + $state: { + store: 'globalState', + }, + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + }); +}); diff --git a/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/index.ts new file mode 100644 index 0000000000000..ae8e21e7c8296 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './single_dataset_locator'; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts new file mode 100644 index 0000000000000..632d0d8d93de6 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; +import { UnresolvedDatasetSelection } from '@kbn/log-explorer-plugin/common'; +import type { IndexPattern } from '@kbn/io-ts-utils'; +import { + SingleDatasetLocatorParams, + SINGLE_DATASET_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { DatasetLocatorDependencies } from '../types'; +import { constructLocatorPath } from '../utils'; + +export type SingleDatasetLocator = LocatorPublic; + +export class SingleDatasetLocatorDefinition + implements LocatorDefinition +{ + public readonly id = SINGLE_DATASET_LOCATOR_ID; + + constructor(protected readonly deps: DatasetLocatorDependencies) {} + + public readonly getLocation = (params: SingleDatasetLocatorParams) => { + const { useHash } = this.deps; + const { integration, dataset } = params; + + const unresolvedDatasetSelection = UnresolvedDatasetSelection.fromSelection({ + name: integration, + dataset: { + name: this.composeIndexPattern(dataset), + }, + }); + + const index = unresolvedDatasetSelection.toDataviewSpec().id; + + return constructLocatorPath({ + locatorParams: params, + index, + useHash, + }); + }; + + private composeIndexPattern(datasetName: SingleDatasetLocatorParams['dataset']) { + return `logs-${datasetName}-*` as IndexPattern; + } +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/types.ts b/x-pack/plugins/observability_log_explorer/common/locators/types.ts new file mode 100644 index 0000000000000..a181fdce10a02 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/types.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AggregateQuery, Filter, Query } from '@kbn/es-query'; + +export interface AppState { + index?: string; + query?: Query | AggregateQuery; + filters?: Filter[]; + columns?: string[]; + sort?: string[][]; +} + +export interface DatasetLocatorDependencies { + useHash: boolean; +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/utils/helpers.ts b/x-pack/plugins/observability_log_explorer/common/locators/utils/helpers.ts new file mode 100644 index 0000000000000..c22c000732839 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/utils/helpers.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; +import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common'; +import { DatasetLocatorParams } from '@kbn/deeplinks-observability/locators'; +import { AppState } from '../types'; + +interface LocatorPathCosntructionParams { + locatorParams: DatasetLocatorParams; + index: string; + useHash: boolean; +} + +export const constructLocatorPath = async (params: LocatorPathCosntructionParams) => { + const { isFilterPinned } = await import('@kbn/es-query'); + + const { + locatorParams: { filters, query, refreshInterval, timeRange, columns, sort }, + index, + useHash, + } = params; + const appState: AppState = {}; + const queryState: GlobalQueryStateFromUrl = {}; + + // App state + if (index) appState.index = index; + if (query) appState.query = query; + if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f)); + if (columns) appState.columns = columns; + if (sort) appState.sort = sort; + + // Global State + if (timeRange) queryState.time = timeRange; + if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + let path = '/'; + + if (Object.keys(queryState).length) { + path = setStateToKbnUrl( + '_g', + queryState, + { useHash, storeInHashQuery: false }, + path + ); + } + + path = setStateToKbnUrl('_a', appState, { useHash, storeInHashQuery: false }, path); + + return { + app: 'observability-log-explorer', + path, + state: {}, + }; +}; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/utils/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/utils/index.ts new file mode 100644 index 0000000000000..6c315f929b9bb --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/utils/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './helpers'; diff --git a/x-pack/plugins/observability_log_explorer/common/translations.ts b/x-pack/plugins/observability_log_explorer/common/translations.ts index 5ec1940fa8dff..8974c8a3f449e 100644 --- a/x-pack/plugins/observability_log_explorer/common/translations.ts +++ b/x-pack/plugins/observability_log_explorer/common/translations.ts @@ -21,3 +21,17 @@ export const betaBadgeDescription = i18n.translate( defaultMessage: 'This application is in beta and therefore subject to change.', } ); + +export const discoverLinkTitle = i18n.translate( + 'xpack.observabilityLogExplorer.discoverLinkTitle', + { + defaultMessage: 'Discover', + } +); + +export const onboardingLinkTitle = i18n.translate( + 'xpack.observabilityLogExplorer.onboardingLinkTitle', + { + defaultMessage: 'Add data', + } +); diff --git a/x-pack/plugins/observability_log_explorer/kibana.jsonc b/x-pack/plugins/observability_log_explorer/kibana.jsonc index 35121b578c39c..7ac940de86dd4 100644 --- a/x-pack/plugins/observability_log_explorer/kibana.jsonc +++ b/x-pack/plugins/observability_log_explorer/kibana.jsonc @@ -13,12 +13,18 @@ ], "requiredPlugins": [ "data", + "discover", "logExplorer", - "observabilityShared" + "observabilityShared", + "share", + "kibanaUtils", ], "optionalPlugins": [ "serverless" ], - "requiredBundles": [] + "requiredBundles": ["kibanaReact"], + "extraPublicDirs": [ + "common", + ] } } diff --git a/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx b/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx index 7d6863e4eb45a..562ff3ba9d109 100644 --- a/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx +++ b/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx @@ -5,28 +5,29 @@ * 2.0. */ -import { AppMountParameters, CoreStart, ScopedHistory } from '@kbn/core/public'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { Route, Router, Routes } from '@kbn/shared-ux-router'; import React from 'react'; import ReactDOM from 'react-dom'; import { ObservablityLogExplorerMainRoute } from '../routes/main'; import { ObservabilityLogExplorerPluginStart, ObservabilityLogExplorerStartDeps } from '../types'; +import { useKibanaContextForPluginProvider } from '../utils/use_kibana'; export const renderObservabilityLogExplorer = ( core: CoreStart, pluginsStart: ObservabilityLogExplorerStartDeps, ownPluginStart: ObservabilityLogExplorerPluginStart, - { element, history }: AppMountParameters + appParams: AppMountParameters ) => { ReactDOM.render( , - element + appParams.element ); return () => { @@ -34,40 +35,42 @@ export const renderObservabilityLogExplorer = ( // observable in the search session service pluginsStart.data.search.session.clear(); - ReactDOM.unmountComponentAtNode(element); + ReactDOM.unmountComponentAtNode(appParams.element); }; }; export interface ObservabilityLogExplorerAppProps { + appParams: AppMountParameters; core: CoreStart; plugins: ObservabilityLogExplorerStartDeps; pluginStart: ObservabilityLogExplorerPluginStart; - history: ScopedHistory; } export const ObservabilityLogExplorerApp = ({ + appParams, core, - plugins: { logExplorer, observabilityShared, serverless }, + plugins, pluginStart, - history, -}: ObservabilityLogExplorerAppProps) => ( - - - - ( - { + const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider( + core, + plugins, + pluginStart + ); + + return ( + + + + + } /> - )} - /> - - - -); + + + + + ); +}; diff --git a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx new file mode 100644 index 0000000000000..d9456e6ed253b --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx @@ -0,0 +1,234 @@ +/* + * Copyright 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, { useEffect } from 'react'; +import deepEqual from 'fast-deep-equal'; +import useObservable from 'react-use/lib/useObservable'; +import { type BehaviorSubject, distinctUntilChanged } from 'rxjs'; +import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; +import { AppMountParameters } from '@kbn/core-application-browser'; +import { + EuiBetaBadge, + EuiButton, + EuiHeader, + EuiHeaderLink, + EuiHeaderLinks, + EuiHeaderSection, + EuiHeaderSectionItem, + useEuiTheme, +} from '@elastic/eui'; +import { LogExplorerStateContainer } from '@kbn/log-explorer-plugin/public'; +import { + OBSERVABILITY_ONBOARDING_LOCATOR, + ObservabilityOnboardingLocatorParams, +} from '@kbn/deeplinks-observability/locators'; +import { KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { css } from '@emotion/react'; +import { PluginKibanaContextValue } from '../utils/use_kibana'; +import { + betaBadgeDescription, + betaBadgeTitle, + discoverLinkTitle, + onboardingLinkTitle, +} from '../../common/translations'; +import { getRouterLinkProps } from '../utils/get_router_link_props'; + +interface LogExplorerTopNavMenuProps { + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + services: KibanaReactContextValue['services']; + state$: BehaviorSubject; + theme$: AppMountParameters['theme$']; +} + +export const LogExplorerTopNavMenu = ({ + setHeaderActionMenu, + services, + state$, + theme$, +}: LogExplorerTopNavMenuProps) => { + const { serverless } = services; + + return Boolean(serverless) ? ( + + ) : ( + + ); +}; + +const ServerlessTopNav = ({ + services, + state$, +}: Pick) => { + const { euiTheme } = useEuiTheme(); + + return ( + + + + + + + + + + + + + + + + + + ); +}; + +const StatefulTopNav = ({ + setHeaderActionMenu, + services, + state$, + theme$, +}: LogExplorerTopNavMenuProps) => { + const { euiTheme } = useEuiTheme(); + + useEffect(() => { + const { chrome, i18n, theme } = services; + + if (chrome) { + chrome.setBreadcrumbsAppendExtension({ + content: toMountPoint( + + + + + , + { theme, i18n } + ), + }); + } + }, [euiTheme, services]); + + return ( + + + + + + + + + + + ); +}; + +const DiscoverLink = React.memo( + ({ services, state$ }: Pick) => { + const { appState, logExplorerState } = useObservable( + state$.pipe( + distinctUntilChanged((prev, curr) => { + if (!prev.appState || !curr.appState) return false; + return deepEqual( + [ + prev.appState.columns, + prev.appState.filters, + prev.appState.index, + prev.appState.query, + ], + [curr.appState.columns, curr.appState.filters, curr.appState.index, curr.appState.query] + ); + }) + ), + { appState: {}, logExplorerState: {} } + ); + + const discoverLinkParams = { + columns: appState?.columns, + filters: appState?.filters, + query: appState?.query, + dataViewSpec: logExplorerState?.datasetSelection?.selection.dataset.toDataviewSpec(), + }; + + const discoverUrl = services.discover.locator?.getRedirectUrl(discoverLinkParams); + + const navigateToDiscover = () => { + services.discover.locator?.navigate(discoverLinkParams); + }; + + const discoverLinkProps = getRouterLinkProps({ + href: discoverUrl, + onClick: navigateToDiscover, + }); + + return ( + + {discoverLinkTitle} + + ); + } +); + +const OnboardingLink = React.memo(({ services }: Pick) => { + const locator = services.share.url.locators.get( + OBSERVABILITY_ONBOARDING_LOCATOR + ); + + const onboardingUrl = locator?.useUrl({}); + + const navigateToOnboarding = () => { + locator?.navigate({}); + }; + + const onboardingLinkProps = getRouterLinkProps({ + href: onboardingUrl, + onClick: navigateToOnboarding, + }); + + return ( + + {onboardingLinkTitle} + + ); +}); diff --git a/x-pack/plugins/observability_log_explorer/public/plugin.ts b/x-pack/plugins/observability_log_explorer/public/plugin.ts index 6afb62235ba15..e82d863727a60 100644 --- a/x-pack/plugins/observability_log_explorer/public/plugin.ts +++ b/x-pack/plugins/observability_log_explorer/public/plugin.ts @@ -13,7 +13,13 @@ import { Plugin, PluginInitializerContext, } from '@kbn/core/public'; +import { + ObservabilityLogExplorerLocators, + SingleDatasetLocatorDefinition, + AllDatasetsLocatorDefinition, +} from '../common/locators'; import { type ObservabilityLogExplorerConfig } from '../common/plugin_config'; +import { OBSERVABILITY_LOG_EXPLORER_APP_ID } from '../common/constants'; import { logExplorerAppTitle } from '../common/translations'; import { renderObservabilityLogExplorer } from './applications/observability_log_explorer'; import type { @@ -27,6 +33,7 @@ export class ObservabilityLogExplorerPlugin implements Plugin { private config: ObservabilityLogExplorerConfig; + private locators?: ObservabilityLogExplorerLocators; constructor(context: PluginInitializerContext) { this.config = context.config.get(); @@ -36,8 +43,11 @@ export class ObservabilityLogExplorerPlugin core: CoreSetup, _pluginsSetup: ObservabilityLogExplorerSetupDeps ) { + const { share } = _pluginsSetup; + const useHash = core.uiSettings.get('state:storeInSessionStorage'); + core.application.register({ - id: 'observability-log-explorer', + id: OBSERVABILITY_LOG_EXPLORER_APP_ID, title: logExplorerAppTitle, category: DEFAULT_APP_CATEGORIES.observability, euiIconType: 'logoLogging', @@ -57,7 +67,26 @@ export class ObservabilityLogExplorerPlugin }, }); - return {}; + // Register Locators + const singleDatasetLocator = share.url.locators.create( + new SingleDatasetLocatorDefinition({ + useHash, + }) + ); + const allDatasetsLocator = share.url.locators.create( + new AllDatasetsLocatorDefinition({ + useHash, + }) + ); + + this.locators = { + singleDatasetLocator, + allDatasetsLocator, + }; + + return { + locators: this.locators, + }; } public start(_core: CoreStart, _pluginsStart: ObservabilityLogExplorerStartDeps) { diff --git a/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx b/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx index 5e9b22fb1ad5d..b4eb120ba3cae 100644 --- a/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx +++ b/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx @@ -5,34 +5,42 @@ * 2.0. */ -import { CoreStart, ScopedHistory } from '@kbn/core/public'; -import { LogExplorerPluginStart } from '@kbn/log-explorer-plugin/public'; -import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; -import { ServerlessPluginStart } from '@kbn/serverless/public'; -import React from 'react'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; +import React, { useState } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { LogExplorerTopNavMenu } from '../../components/log_explorer_top_nav_menu'; import { ObservabilityLogExplorerPageTemplate } from '../../components/page_template'; import { noBreadcrumbs, useBreadcrumbs } from '../../utils/breadcrumbs'; +import { useKibanaContextForPlugin } from '../../utils/use_kibana'; export interface ObservablityLogExplorerMainRouteProps { + appParams: AppMountParameters; core: CoreStart; - history: ScopedHistory; - logExplorer: LogExplorerPluginStart; - observabilityShared: ObservabilitySharedPluginStart; - serverless?: ServerlessPluginStart; } export const ObservablityLogExplorerMainRoute = ({ + appParams, core, - history, - logExplorer, - observabilityShared, - serverless, }: ObservablityLogExplorerMainRouteProps) => { + const { services } = useKibanaContextForPlugin(); + const { logExplorer, observabilityShared, serverless } = services; useBreadcrumbs(noBreadcrumbs, core.chrome, serverless); + const { history, setHeaderActionMenu, theme$ } = appParams; + + const [state$] = useState(() => new BehaviorSubject({})); + return ( - - - + <> + + + + + ); }; diff --git a/x-pack/plugins/observability_log_explorer/public/types.ts b/x-pack/plugins/observability_log_explorer/public/types.ts index f5e6526c502d9..a4596995a4a7b 100644 --- a/x-pack/plugins/observability_log_explorer/public/types.ts +++ b/x-pack/plugins/observability_log_explorer/public/types.ts @@ -7,22 +7,29 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { LogExplorerPluginStart } from '@kbn/log-explorer-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import { ServerlessPluginStart } from '@kbn/serverless/public'; +import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; +import { ObservabilityLogExplorerLocators } from '../common/locators'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ObservabilityLogExplorerPluginSetup {} +export interface ObservabilityLogExplorerPluginSetup { + locators: ObservabilityLogExplorerLocators; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityLogExplorerPluginStart {} export interface ObservabilityLogExplorerSetupDeps { serverless?: ServerlessPluginStart; + share: SharePluginSetup; } export interface ObservabilityLogExplorerStartDeps { data: DataPublicPluginStart; + discover: DiscoverStart; logExplorer: LogExplorerPluginStart; observabilityShared: ObservabilitySharedPluginStart; serverless?: ServerlessPluginStart; + share: SharePluginStart; } diff --git a/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx b/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx index a8b575d5341dc..c1eaca45b7855 100644 --- a/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx +++ b/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx @@ -9,11 +9,7 @@ import { EuiBreadcrumb } from '@elastic/eui'; import type { ChromeStart } from '@kbn/core-chrome-browser'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { useEffect } from 'react'; -import { - betaBadgeDescription, - betaBadgeTitle, - logExplorerAppTitle, -} from '../../common/translations'; +import { logExplorerAppTitle } from '../../common/translations'; export const useBreadcrumbs = ( breadcrumbs: EuiBreadcrumb[], @@ -40,10 +36,6 @@ export function setBreadcrumbs( ...breadcrumbs, ]); } - chromeService.setBadge({ - text: betaBadgeTitle, - tooltip: betaBadgeDescription, - }); } export const noBreadcrumbs: EuiBreadcrumb[] = []; diff --git a/x-pack/plugins/observability_log_explorer/public/utils/get_router_link_props.ts b/x-pack/plugins/observability_log_explorer/public/utils/get_router_link_props.ts new file mode 100644 index 0000000000000..a325df1a7e86f --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/public/utils/get_router_link_props.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +interface GetRouterLinkPropsDeps { + href?: string; + onClick(): void; +} + +const isModifiedEvent = (event: React.MouseEvent) => + !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); + +const isLeftClickEvent = (event: React.MouseEvent) => event.button === 0; + +export const getRouterLinkProps = ({ href, onClick }: GetRouterLinkPropsDeps) => { + const guardedClickHandler = (event: React.MouseEvent) => { + if (event.defaultPrevented) { + return; + } + + if (isModifiedEvent(event) || !isLeftClickEvent(event)) { + return; + } + + // Prevent regular link behavior, which causes a browser refresh. + event.preventDefault(); + + onClick(); + }; + + return { href, onClick: guardedClickHandler }; +}; diff --git a/x-pack/plugins/observability_log_explorer/tsconfig.json b/x-pack/plugins/observability_log_explorer/tsconfig.json index 5f94d15d30fea..9a0fb5d43e7b9 100644 --- a/x-pack/plugins/observability_log_explorer/tsconfig.json +++ b/x-pack/plugins/observability_log_explorer/tsconfig.json @@ -22,6 +22,14 @@ "@kbn/serverless", "@kbn/core-chrome-browser", "@kbn/config-schema", + "@kbn/kibana-utils-plugin", + "@kbn/core-application-browser", + "@kbn/discover-plugin", + "@kbn/es-query", + "@kbn/react-kibana-mount", + "@kbn/share-plugin", + "@kbn/io-ts-utils", + "@kbn/deeplinks-observability" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/home.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/home.cy.ts index 71aa6a0eb09e6..8c7d433f48a4d 100644 --- a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/home.cy.ts +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/home.cy.ts @@ -29,6 +29,6 @@ describe('[Observability onboarding] Landing page', () => { it('when user navigates to observability onboarding landing page is showed', () => { cy.visitKibana('/app/observabilityOnboarding'); - cy.contains('Get started with Observability'); + cy.contains('Collect and analyze logs'); }); }); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/configure.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/configure.cy.ts new file mode 100644 index 0000000000000..0d345d2eb2a90 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/configure.cy.ts @@ -0,0 +1,344 @@ +/* + * Copyright 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. + */ + +describe('[Logs onboarding] Custom logs - configure step', () => { + describe('logFilePaths', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + }); + + it('Users shouldnt be able to continue if logFilePaths is empty', () => { + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .should('not.have.text'); + cy.getByTestSubj('obltOnboardingCustomLogsContinue').should( + 'be.disabled' + ); + }); + + it('Users should be able to continue if logFilePaths is not empty', () => { + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type('myLogs.log'); + cy.getByTestSubj('obltOnboardingCustomLogsContinue').should( + 'not.be.disabled' + ); + }); + + it('Users can add multiple logFilePaths', () => { + cy.getByTestSubj('obltOnboardingCustomLogsAddFilePath').click(); + cy.getByTestSubj('obltOnboardingLogFilePath-0').should('exist'); + cy.getByTestSubj('obltOnboardingLogFilePath-1').should('exist'); + }); + + it('Users can delete logFilePaths', () => { + cy.getByTestSubj('obltOnboardingCustomLogsAddFilePath').click(); + cy.get('*[data-test-subj^="obltOnboardingLogFilePath-"]').should( + 'have.length', + 2 + ); + + cy.getByTestSubj('obltOnboardingLogFilePathDelete-1').click(); + cy.get('*[data-test-subj^="obltOnboardingLogFilePath-"]').should( + 'have.length', + 1 + ); + }); + + describe('when users fill logFilePaths', () => { + it('datasetname and integration name are auto generated if it is the first path', () => { + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type('myLogs.log'); + cy.getByTestSubj('obltOnboardingCustomLogsIntegrationsName').should( + 'have.value', + 'mylogs' + ); + cy.getByTestSubj('obltOnboardingCustomLogsDatasetName').should( + 'have.value', + 'mylogs' + ); + }); + + it('datasetname and integration name are not generated if it is not the first path', () => { + cy.getByTestSubj('obltOnboardingCustomLogsAddFilePath').click(); + cy.getByTestSubj('obltOnboardingLogFilePath-1') + .find('input') + .type('myLogs.log'); + cy.getByTestSubj('obltOnboardingCustomLogsIntegrationsName').should( + 'be.empty' + ); + cy.getByTestSubj('obltOnboardingCustomLogsDatasetName').should( + 'be.empty' + ); + }); + }); + }); + + describe('serviceName', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type('myLogs.log'); + }); + + it('should be optional allowing user to continue if it is empty', () => { + cy.getByTestSubj('obltOnboardingCustomLogsServiceName').should( + 'not.have.text' + ); + cy.getByTestSubj('obltOnboardingCustomLogsContinue').should('be.enabled'); + }); + }); + + describe('advancedSettings', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type('myLogs.log'); + }); + + it('Users should expand the content when clicking it', () => { + cy.getByTestSubj('obltOnboardingCustomLogsAdvancedSettings').click(); + + cy.getByTestSubj('obltOnboardingCustomLogsNamespace').should( + 'be.visible' + ); + cy.getByTestSubj('obltOnboardingCustomLogsCustomConfig').should( + 'be.visible' + ); + }); + + it('Users should hide the content when clicking it', () => { + cy.getByTestSubj('obltOnboardingCustomLogsAdvancedSettings').click(); + + cy.getByTestSubj('obltOnboardingCustomLogsNamespace').should( + 'not.be.visible' + ); + cy.getByTestSubj('obltOnboardingCustomLogsCustomConfig').should( + 'not.be.visible' + ); + }); + + describe('Namespace', () => { + beforeEach(() => { + cy.getByTestSubj('obltOnboardingCustomLogsAdvancedSettings').click(); + }); + + afterEach(() => { + cy.getByTestSubj('obltOnboardingCustomLogsAdvancedSettings').click(); + }); + + it('Users should see a default namespace', () => { + cy.getByTestSubj('obltOnboardingCustomLogsNamespace').should( + 'have.value', + 'default' + ); + }); + + it('Users should not be able to continue if they do not specify a namespace', () => { + cy.getByTestSubj('obltOnboardingCustomLogsNamespace').clear(); + + cy.getByTestSubj('obltOnboardingCustomLogsContinue').should( + 'be.disabled' + ); + }); + }); + + describe('customConfig', () => { + beforeEach(() => { + cy.getByTestSubj('obltOnboardingCustomLogsAdvancedSettings').click(); + }); + + afterEach(() => { + cy.getByTestSubj('obltOnboardingCustomLogsAdvancedSettings').click(); + }); + + it('should be optional allowing user to continue if it is empty', () => { + cy.getByTestSubj('obltOnboardingCustomLogsCustomConfig').should( + 'not.have.text' + ); + cy.getByTestSubj('obltOnboardingCustomLogsContinue').should( + 'be.enabled' + ); + }); + }); + }); + + describe('integrationName', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type('myLogs.log'); + }); + + it('Users should not be able to continue if they do not specify an integrationName', () => { + cy.getByTestSubj('obltOnboardingCustomLogsIntegrationsName').clear(); + + cy.getByTestSubj('obltOnboardingCustomLogsContinue').should( + 'be.disabled' + ); + }); + + it('value will contain _ instead of special chars', () => { + cy.getByTestSubj('obltOnboardingCustomLogsIntegrationsName') + .clear() + .type('hello$world'); + + cy.getByTestSubj('obltOnboardingCustomLogsIntegrationsName').should( + 'have.value', + 'hello_world' + ); + }); + + it('value will be invalid if it is not lowercase', () => { + cy.getByTestSubj('obltOnboardingCustomLogsIntegrationsName') + .clear() + .type('H3llowOrld'); + + cy.contains('An integration name should be lowercase.'); + }); + }); + + describe('datasetName', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type('myLogs.log'); + }); + + it('Users should not be able to continue if they do not specify a datasetName', () => { + cy.getByTestSubj('obltOnboardingCustomLogsDatasetName').clear(); + + cy.getByTestSubj('obltOnboardingCustomLogsContinue').should( + 'be.disabled' + ); + }); + + it('value will contain _ instead of special chars', () => { + cy.getByTestSubj('obltOnboardingCustomLogsDatasetName') + .clear() + .type('hello$world'); + + cy.getByTestSubj('obltOnboardingCustomLogsDatasetName').should( + 'have.value', + 'hello_world' + ); + }); + + it('value will be invalid if it is not lowercase', () => { + cy.getByTestSubj('obltOnboardingCustomLogsDatasetName') + .clear() + .type('H3llowOrld'); + + cy.contains('A dataset name should be lowercase.'); + }); + }); + + describe('custom integration', () => { + const CUSTOM_INTEGRATION_NAME = 'mylogs'; + + beforeEach(() => { + cy.deleteIntegration(CUSTOM_INTEGRATION_NAME); + }); + + describe('when user is missing privileges', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type(`${CUSTOM_INTEGRATION_NAME}.log`); + + cy.getByTestSubj('obltOnboardingCustomLogsContinue').click(); + }); + + it('installation fails', () => { + cy.getByTestSubj('obltOnboardingCustomIntegrationErrorCallout').should( + 'exist' + ); + }); + }); + + describe('when user has proper privileges', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type(`${CUSTOM_INTEGRATION_NAME}.log`); + + cy.getByTestSubj('obltOnboardingCustomLogsContinue').click(); + }); + + afterEach(() => { + cy.deleteIntegration(CUSTOM_INTEGRATION_NAME); + }); + + it('installation succeed and user is redirected install elastic agent step', () => { + cy.url().should( + 'include', + '/app/observabilityOnboarding/customLogs/installElasticAgent' + ); + }); + }); + + it('installation fails if integration already exists', () => { + cy.loginAsEditorUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.installCustomIntegration(CUSTOM_INTEGRATION_NAME); + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type(`${CUSTOM_INTEGRATION_NAME}.log`); + cy.getByTestSubj('obltOnboardingCustomLogsContinue').click(); + + cy.contains( + 'Failed to create the integration as an installation with the name mylogs already exists.' + ); + }); + + describe('when an error occurred on creation', () => { + before(() => { + cy.intercept('/api/fleet/epm/custom_integrations', { + statusCode: 500, + body: { + message: 'Internal error', + }, + }); + + cy.loginAsEditorUser(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type(`${CUSTOM_INTEGRATION_NAME}.log`); + cy.getByTestSubj('obltOnboardingCustomLogsContinue').click(); + }); + + it('user should see the error displayed', () => { + cy.getByTestSubj('obltOnboardingCustomIntegrationErrorCallout').should( + 'exist' + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts new file mode 100644 index 0000000000000..19e84284b671b --- /dev/null +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts @@ -0,0 +1,629 @@ +/* + * Copyright 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. + */ + +describe('[Logs onboarding] Custom logs - install elastic agent', () => { + const CUSTOM_INTEGRATION_NAME = 'mylogs'; + + const configureCustomLogs = ( + loginFn = () => cy.loginAsLogMonitoringUser() + ) => { + loginFn(); + cy.visitKibana('/app/observabilityOnboarding/customLogs'); + + cy.deleteIntegration(CUSTOM_INTEGRATION_NAME); + + cy.getByTestSubj('obltOnboardingLogFilePath-0') + .find('input') + .type('mylogs.log'); + + cy.getByTestSubj('obltOnboardingCustomLogsContinue').click(); + }; + + describe('custom integration', () => { + beforeEach(() => { + configureCustomLogs(() => cy.loginAsEditorUser()); + }); + + it('Users should be able to see the custom integration success callout', () => { + cy.getByTestSubj('obltOnboardingCustomIntegrationInstalled').should( + 'be.visible' + ); + }); + }); + + describe('ApiKey generation', () => { + describe('when user is missing privileges', () => { + beforeEach(() => { + configureCustomLogs(() => cy.loginAsEditorUser()); + }); + + it('apiKey is not generated', () => { + cy.getByTestSubj('obltOnboardingLogsApiKeyCreationNoPrivileges').should( + 'exist' + ); + }); + }); + + describe('when user has proper privileges', () => { + beforeEach(() => { + configureCustomLogs(); + }); + + it('apiKey is generated', () => { + cy.getByTestSubj('obltOnboardingLogsApiKeyCreated').should('exist'); + }); + }); + + describe('when an error occurred on creation', () => { + before(() => { + cy.intercept('/internal/observability_onboarding/logs/flow', { + statusCode: 500, + body: { + message: 'Internal error', + }, + }); + + configureCustomLogs(); + }); + + it('apiKey is not generated', () => { + cy.getByTestSubj('obltOnboardingLogsApiKeyCreationFailed').should( + 'exist' + ); + }); + }); + }); + + describe('Install the Elastic Agent step', () => { + beforeEach(() => { + cy.intercept('POST', '/internal/observability_onboarding/logs/flow').as( + 'createOnboardingFlow' + ); + configureCustomLogs(); + }); + + describe('When user select Linux OS', () => { + it('Auto download config to host is disabled by default', () => { + cy.get('.euiButtonGroup').contains('Linux').click(); + cy.getByTestSubj('obltOnboardingInstallElasticAgentAutoDownloadConfig') + .should('be.enabled') + .should('not.be.checked'); + }); + + it('Installation script is shown', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('exist'); + }); + }); + + describe('When user select Mac OS', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('MacOS').click(); + }); + + it('Auto download config to host is disabled by default', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentAutoDownloadConfig') + .should('be.enabled') + .should('not.be.checked'); + }); + + it('Installation script is shown', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('exist'); + }); + }); + + describe('When user select Windows OS', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('Windows').click(); + }); + + it('Auto download config to host is disabled by default', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentAutoDownloadConfig') + .should('be.disabled') + .should('not.be.checked'); + }); + + it('A link to the documentation is shown instead of installation script', () => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentWindowsDocsLink' + ).should('exist'); + + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('not.exist'); + }); + }); + + describe('When Auto download config', () => { + describe('is selected', () => { + it('autoDownloadConfig flag is added to installation script', () => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfigCallout' + ).should('exist'); + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('contain', 'autoDownloadConfig=1'); + }); + + it('Download config button is disabled', () => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.getByTestSubj( + 'obltOnboardingConfigureElasticAgentStepDownloadConfig' + ).should('be.disabled'); + }); + }); + + it('is not selected autoDownloadConfig flag is not added to installation script', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('not.contain', 'autoDownloadConfig=1'); + }); + }); + + describe('When user executes the installation script in the host', () => { + let onboardingId: string; + + describe('updates on steps are shown in the flow', () => { + beforeEach(() => { + cy.wait('@createOnboardingFlow') + .its('response.body') + .then((body) => { + onboardingId = body.onboardingId; + }); + }); + + describe('Download elastic Agent step', () => { + it('shows a loading callout when elastic agent is downloading', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Downloading Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent is downloaded', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Elastic Agent downloaded') + .should('exist'); + }); + + it('shows a danger callout when elastic agent was not downloaded', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'danger' + ); + cy.getByTestSubj('obltOnboardingStepStatus-danger') + .contains('Download Elastic Agent') + .should('exist'); + }); + }); + + describe('Extract elastic Agent step', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + }); + + it('shows a loading callout when elastic agent is extracting', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Extracting Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent is extracted', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Elastic Agent extracted') + .should('exist'); + }); + + it('shows a danger callout when elastic agent was not extracted', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'danger' + ); + cy.getByTestSubj('obltOnboardingStepStatus-danger') + .contains('Extract Elastic Agent') + .should('exist'); + }); + }); + + describe('Install elastic Agent step', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + }); + + it('shows a loading callout when elastic agent is installing', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Installing Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent is installed', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Elastic Agent installed') + .should('exist'); + }); + + it('shows a danger callout when elastic agent was not installed', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'danger' + ); + cy.getByTestSubj('obltOnboardingStepStatus-danger') + .contains('Install Elastic Agent') + .should('exist'); + }); + }); + + describe('Check elastic Agent status step', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + }); + + it('shows a loading callout when getting elastic agent status', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Connecting to the Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent status is healthy', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Connected to the Elastic Agent') + .should('exist'); + }); + + it('shows a warning callout when elastic agent status is not healthy', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'warning' + ); + cy.getByTestSubj('obltOnboardingStepStatus-warning') + .contains('Connect to the Elastic Agent') + .should('exist'); + }); + }); + }); + }); + }); + + describe('Configure Elastic Agent step', () => { + let onboardingId: string; + + beforeEach(() => { + cy.intercept('POST', '/internal/observability_onboarding/logs/flow').as( + 'createOnboardingFlow' + ); + configureCustomLogs(); + cy.wait('@createOnboardingFlow') + .its('response.body') + .then((body) => { + onboardingId = body.onboardingId; + }); + }); + + describe('When user select Linux OS', () => { + beforeEach(() => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete'); + }); + + it('shows loading callout when config is being downloaded to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-loading"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Downloading Elastic Agent config') + .should('exist'); + }); + + it('shows success callout when the configuration has been written to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-complete"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains( + 'Elastic Agent config written to /opt/Elastic/Agent/elastic-agent.yml' + ) + .should('exist'); + }); + + it('shows warning callout when the configuration was not written in the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'warning'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-warning"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-warning') + .contains('Configure the agent') + .should('exist'); + }); + }); + + describe('When user select Mac OS', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('MacOS').click(); + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete'); + }); + + it('shows loading callout when config is being downloaded to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-loading"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Downloading Elastic Agent config') + .should('exist'); + }); + + it('shows success callout when the configuration has been written to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-complete"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains( + 'Elastic Agent config written to /Library/Elastic/Agent/elastic-agent.yml' + ) + .should('exist'); + }); + + it('shows warning callout when the configuration was not written in the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'warning'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-warning"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-warning') + .contains('Configure the agent') + .should('exist'); + }); + }); + + describe('When user select Windows', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('Windows').click(); + }); + + it('step is disabled', () => { + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-disabled"]' + ).should('exist'); + }); + }); + }); + + describe('Check logs step', () => { + let onboardingId: string; + + beforeEach(() => { + cy.intercept('POST', '/internal/observability_onboarding/logs/flow').as( + 'createOnboardingFlow' + ); + configureCustomLogs(); + cy.wait('@createOnboardingFlow') + .its('response.body') + .then((body) => { + onboardingId = body.onboardingId; + }); + }); + + describe('When user select Linux OS or MacOS', () => { + describe('When configure Elastic Agent step is not finished', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + cy.updateInstallationStepStatus(onboardingId, 'ea-status', 'loading'); + }); + + it('check logs is not triggered', () => { + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-incomplete"]' + ).should('exist'); + cy.get('.euiStep__title') + .contains('Ship logs to Elastic Observability') + .should('exist'); + }); + }); + + describe('When configure Elastic Agent step has finished', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-config', + 'complete' + ); + }); + + it('shows loading callout when logs are being checked', () => { + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-loading"]' + ).should('exist'); + cy.get('.euiStep__title') + .contains('Waiting for logs to be shipped...') + .should('exist'); + }); + }); + }); + + describe('When user select Windows', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('Windows').click(); + }); + + it('step is disabled', () => { + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-disabled"]' + ).should('exist'); + }); + }); + }); + + describe('When logs are being shipped', () => { + beforeEach(() => { + cy.intercept('GET', '**/progress', { + status: 200, + body: { + progress: { + 'ea-download': { status: 'complete' }, + 'ea-extract': { status: 'complete' }, + 'ea-install': { status: 'complete' }, + 'ea-status': { status: 'complete' }, + 'ea-config': { status: 'complete' }, + 'logs-ingest': { status: 'complete' }, + }, + }, + }).as('checkOnboardingProgress'); + configureCustomLogs(); + }); + + it('shows success callout when logs has arrived to elastic', () => { + cy.wait('@checkOnboardingProgress'); + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-complete"]' + ).should('exist'); + cy.get('.euiStep__title') + .contains('Logs are being shipped!') + .should('exist'); + }); + + it('when user clicks on Explore Logs it navigates to observability log explorer', () => { + cy.wait('@checkOnboardingProgress'); + cy.getByTestSubj('obltOnboardingExploreLogs').should('exist').click(); + + cy.url().should('include', '/app/observability-log-explorer'); + cy.get('button').contains('[Mylogs] mylogs').should('exist'); + }); + }); +}); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/feedback.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/feedback.cy.ts new file mode 100644 index 0000000000000..495fd5672bfb9 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/feedback.cy.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +describe('[Logs onboarding] Give Feedback', () => { + beforeEach(() => { + cy.loginAsElastic(); + cy.visitKibana('/app/observabilityOnboarding'); + }); + + it('feedback button is present in system logs onboarding', () => { + cy.getByTestSubj('obltOnboardingHomeStartSystemLogStream').click(); + cy.getByTestSubj('observabilityOnboardingPageGiveFeedback').should('exist'); + }); + + it('feedback button is present in custom logs onboarding', () => { + cy.getByTestSubj('obltOnboardingHomeStartLogFileStream').click(); + cy.getByTestSubj('observabilityOnboardingPageGiveFeedback').should('exist'); + }); + + it('feedback button is not present in the landing page', () => { + cy.getByTestSubj('observabilityOnboardingPageGiveFeedback').should( + 'not.exist' + ); + }); +}); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/system_logs.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/system_logs.cy.ts new file mode 100644 index 0000000000000..41347191561ca --- /dev/null +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/system_logs.cy.ts @@ -0,0 +1,657 @@ +/* + * Copyright 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. + */ + +describe('[Logs onboarding] System logs', () => { + describe('System integration', () => { + beforeEach(() => { + cy.deleteIntegration('system'); + }); + + describe('when user is missing privileges', () => { + beforeEach(() => { + cy.loginAsViewerUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + }); + + it('installation fails', () => { + cy.getByTestSubj( + 'obltOnboardingSystemLogsIntegrationInstallationFailed' + ).should('exist'); + }); + }); + + describe('when user has proper privileges', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + }); + + after(() => { + cy.deleteIntegration('system'); + }); + + it('installation succeed', () => { + cy.getByTestSubj('obltOnboardingSystemLogsIntegrationInstalled').should( + 'exist' + ); + }); + + it('show link to navigate to system integration when clicking info icon', () => { + cy.getByTestSubj('obltOnboardingSystemLogsIntegrationInstalled').should( + 'exist' + ); + cy.getByTestSubj('obltOnboardingSystemLogsIntegrationInfo') + .should('exist') + .click(); + cy.getByTestSubj( + 'observabilityOnboardingSystemIntegrationLearnMore' + ).should('exist'); + }); + }); + }); + + describe('ApiKey generation', () => { + describe('when user is missing privileges', () => { + it('apiKey is not generated', () => { + cy.loginAsEditorUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + + cy.getByTestSubj('obltOnboardingLogsApiKeyCreationNoPrivileges').should( + 'exist' + ); + }); + }); + + describe('when user has proper privileges', () => { + beforeEach(() => { + cy.loginAsLogMonitoringUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + }); + + it('apiKey is generated', () => { + cy.getByTestSubj('obltOnboardingLogsApiKeyCreated').should('exist'); + }); + }); + + describe('when an error occurred on creation', () => { + before(() => { + cy.intercept('/internal/observability_onboarding/logs/flow', { + statusCode: 500, + body: { + message: 'Internal error', + }, + }); + + cy.loginAsLogMonitoringUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + }); + + it('apiKey is not generated', () => { + cy.getByTestSubj('obltOnboardingLogsApiKeyCreationFailed').should( + 'exist' + ); + }); + }); + }); + + describe('Install the Elastic Agent step', () => { + beforeEach(() => { + cy.intercept('POST', '/internal/observability_onboarding/logs/flow').as( + 'createOnboardingFlow' + ); + cy.loginAsLogMonitoringUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + }); + + describe('When user select Linux OS', () => { + it('Auto download config to host is disabled by default', () => { + cy.get('.euiButtonGroup').contains('Linux').click(); + cy.getByTestSubj('obltOnboardingInstallElasticAgentAutoDownloadConfig') + .should('be.enabled') + .should('not.be.checked'); + }); + + it('Installation script is shown', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('exist'); + }); + }); + + describe('When user select Mac OS', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('MacOS').click(); + }); + + it('Auto download config to host is disabled by default', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentAutoDownloadConfig') + .should('be.enabled') + .should('not.be.checked'); + }); + + it('Installation script is shown', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('exist'); + }); + }); + + describe('When user select Windows OS', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('Windows').click(); + }); + + it('Auto download config to host is disabled by default', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentAutoDownloadConfig') + .should('be.disabled') + .should('not.be.checked'); + }); + + it('A link to the documentation is shown instead of installation script', () => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentWindowsDocsLink' + ).should('exist'); + + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('not.exist'); + }); + }); + + describe('When Auto download config', () => { + describe('is selected', () => { + it('autoDownloadConfig flag is added to installation script', () => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfigCallout' + ).should('exist'); + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('contain', 'autoDownloadConfig=1'); + }); + + it('Download config button is disabled', () => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.getByTestSubj( + 'obltOnboardingConfigureElasticAgentStepDownloadConfig' + ).should('be.disabled'); + }); + }); + + it('is not selected autoDownloadConfig flag is not added to installation script', () => { + cy.getByTestSubj('obltOnboardingInstallElasticAgentStep') + .get('.euiCodeBlock') + .should('not.contain', 'autoDownloadConfig=1'); + }); + }); + + describe('When user executes the installation script in the host', () => { + let onboardingId: string; + + describe('updates on steps are shown in the flow', () => { + beforeEach(() => { + cy.wait('@createOnboardingFlow') + .its('response.body') + .then((body) => { + onboardingId = body.onboardingId; + }); + }); + + describe('Download elastic Agent step', () => { + it('shows a loading callout when elastic agent is downloading', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Downloading Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent is downloaded', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Elastic Agent downloaded') + .should('exist'); + }); + + it('shows a danger callout when elastic agent was not downloaded', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'danger' + ); + cy.getByTestSubj('obltOnboardingStepStatus-danger') + .contains('Download Elastic Agent') + .should('exist'); + }); + }); + + describe('Extract elastic Agent step', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + }); + + it('shows a loading callout when elastic agent is extracting', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Extracting Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent is extracted', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Elastic Agent extracted') + .should('exist'); + }); + + it('shows a danger callout when elastic agent was not extracted', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'danger' + ); + cy.getByTestSubj('obltOnboardingStepStatus-danger') + .contains('Extract Elastic Agent') + .should('exist'); + }); + }); + + describe('Install elastic Agent step', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + }); + + it('shows a loading callout when elastic agent is installing', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Installing Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent is installed', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Elastic Agent installed') + .should('exist'); + }); + + it('shows a danger callout when elastic agent was not installed', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'danger' + ); + cy.getByTestSubj('obltOnboardingStepStatus-danger') + .contains('Install Elastic Agent') + .should('exist'); + }); + }); + + describe('Check elastic Agent status step', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + }); + + it('shows a loading callout when getting elastic agent status', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'loading' + ); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Connecting to the Elastic Agent') + .should('exist'); + }); + + it('shows a success callout when elastic agent status is healthy', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'complete' + ); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains('Connected to the Elastic Agent') + .should('exist'); + }); + + it('shows a warning callout when elastic agent status is not healthy', () => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'warning' + ); + cy.getByTestSubj('obltOnboardingStepStatus-warning') + .contains('Connect to the Elastic Agent') + .should('exist'); + }); + }); + }); + }); + }); + + describe('Configure Elastic Agent step', () => { + let onboardingId: string; + + beforeEach(() => { + cy.intercept('POST', '/internal/observability_onboarding/logs/flow').as( + 'createOnboardingFlow' + ); + cy.loginAsLogMonitoringUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + cy.wait('@createOnboardingFlow') + .its('response.body') + .then((body) => { + onboardingId = body.onboardingId; + }); + }); + + describe('When user select Linux OS', () => { + beforeEach(() => { + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete'); + }); + + it('shows loading callout when config is being downloaded to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-loading"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Downloading Elastic Agent config') + .should('exist'); + }); + + it('shows success callout when the configuration has been written to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-complete"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains( + 'Elastic Agent config written to /opt/Elastic/Agent/elastic-agent.yml' + ) + .should('exist'); + }); + + it('shows warning callout when the configuration was not written in the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'warning'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-warning"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-warning') + .contains('Configure the agent') + .should('exist'); + }); + }); + + describe('When user select Mac OS', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('MacOS').click(); + cy.getByTestSubj( + 'obltOnboardingInstallElasticAgentAutoDownloadConfig' + ).click(); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete'); + cy.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete'); + }); + + it('shows loading callout when config is being downloaded to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-loading"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-loading') + .contains('Downloading Elastic Agent config') + .should('exist'); + }); + + it('shows success callout when the configuration has been written to the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-complete"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-complete') + .contains( + 'Elastic Agent config written to /Library/Elastic/Agent/elastic-agent.yml' + ) + .should('exist'); + }); + + it('shows warning callout when the configuration was not written in the host', () => { + cy.updateInstallationStepStatus(onboardingId, 'ea-config', 'warning'); + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-warning"]' + ).should('exist'); + cy.getByTestSubj('obltOnboardingStepStatus-warning') + .contains('Configure the agent') + .should('exist'); + }); + }); + + describe('When user select Windows', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('Windows').click(); + }); + + it('step is disabled', () => { + cy.get( + '[data-test-subj="obltOnboardingConfigureElasticAgentStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-disabled"]' + ).should('exist'); + }); + }); + }); + + describe('Check logs step', () => { + let onboardingId: string; + + beforeEach(() => { + cy.intercept('POST', '/internal/observability_onboarding/logs/flow').as( + 'createOnboardingFlow' + ); + cy.loginAsLogMonitoringUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + cy.wait('@createOnboardingFlow') + .its('response.body') + .then((body) => { + onboardingId = body.onboardingId; + }); + }); + + describe('When user select Linux OS or MacOS', () => { + describe('When configure Elastic Agent step is not finished', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + cy.updateInstallationStepStatus(onboardingId, 'ea-status', 'loading'); + }); + + it('check logs is not triggered', () => { + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-incomplete"]' + ).should('exist'); + cy.get('.euiStep__title') + .contains('Ship logs to Elastic Observability') + .should('exist'); + }); + }); + + describe('When configure Elastic Agent step has finished', () => { + beforeEach(() => { + cy.updateInstallationStepStatus( + onboardingId, + 'ea-download', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-extract', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-install', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-status', + 'complete' + ); + cy.updateInstallationStepStatus( + onboardingId, + 'ea-config', + 'complete' + ); + }); + + it('shows loading callout when logs are being checked', () => { + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-loading"]' + ).should('exist'); + cy.get('.euiStep__title') + .contains('Waiting for logs to be shipped...') + .should('exist'); + }); + }); + }); + + describe('When user select Windows', () => { + beforeEach(() => { + cy.get('.euiButtonGroup').contains('Windows').click(); + }); + + it('step is disabled', () => { + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-disabled"]' + ).should('exist'); + }); + }); + }); + + describe('When logs are being shipped', () => { + beforeEach(() => { + cy.intercept('GET', '**/progress', { + status: 200, + body: { + progress: { + 'ea-download': { status: 'complete' }, + 'ea-extract': { status: 'complete' }, + 'ea-install': { status: 'complete' }, + 'ea-status': { status: 'complete' }, + 'ea-config': { status: 'complete' }, + 'logs-ingest': { status: 'complete' }, + }, + }, + }).as('checkOnboardingProgress'); + cy.intercept('GET', '/api/fleet/epm/packages/system').as( + 'systemIntegrationInstall' + ); + cy.loginAsLogMonitoringUser(); + cy.visitKibana('/app/observabilityOnboarding/systemLogs'); + }); + + it('shows success callout when logs has arrived to elastic', () => { + cy.wait('@checkOnboardingProgress'); + cy.get( + '[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-complete"]' + ).should('exist'); + cy.get('.euiStep__title') + .contains('Logs are being shipped!') + .should('exist'); + }); + + it('when user clicks on Explore Logs it navigates to observability log explorer', () => { + cy.wait('@systemIntegrationInstall'); + cy.wait('@checkOnboardingProgress'); + cy.getByTestSubj('obltOnboardingExploreLogs').should('exist').click(); + + cy.url().should('include', '/app/observability-log-explorer'); + cy.get('button').contains('[System] syslog').should('exist'); + }); + }); +}); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/navigation.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/navigation.cy.ts new file mode 100644 index 0000000000000..cc5d0e2500ad1 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/navigation.cy.ts @@ -0,0 +1,62 @@ +/* + * Copyright 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. + */ + +describe('[Observability onboarding] Navigation', () => { + beforeEach(() => { + cy.loginAsElastic(); + cy.visitKibana('/app/observabilityOnboarding/'); + }); + + describe('When user clicks on the card', () => { + it('navigates to system logs onboarding', () => { + cy.getByTestSubj('obltOnboardingHomeStartSystemLogStream').click(); + + cy.url().should('include', '/app/observabilityOnboarding/systemLogs'); + }); + + it('navigates to custom logs onboarding', () => { + cy.getByTestSubj('obltOnboardingHomeStartLogFileStream').click(); + + cy.url().should('include', '/app/observabilityOnboarding/customLogs'); + }); + + it('navigates to apm tutorial', () => { + cy.getByTestSubj('obltOnboardingHomeStartApmTutorial').click(); + + cy.url().should('include', '/app/home#/tutorial/apm'); + }); + + it('navigates to kubernetes integration', () => { + cy.getByTestSubj('obltOnboardingHomeGoToKubernetesIntegration').click(); + + cy.url().should( + 'include', + '/app/integrations/detail/kubernetes/overview' + ); + }); + + it('navigates to integrations', () => { + cy.getByTestSubj('obltOnboardingHomeExploreIntegrations').click(); + + cy.url().should('include', '/app/integrations/browse'); + }); + }); + + describe('When user clicks on Quick links', () => { + it('navigates to use sample data', () => { + cy.getByTestSubj('obltOnboardingHomeUseSampleData').click(); + + cy.url().should('include', '/app/home#/tutorial_directory/sampleData'); + }); + + it('navigates to upload a file', () => { + cy.getByTestSubj('obltOnboardingHomeUploadAFile').click(); + + cy.url().should('include', '/app/home#/tutorial_directory/fileDataViz'); + }); + }); +}); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts index 299729f1d8fa7..e989089f491eb 100644 --- a/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts @@ -6,6 +6,51 @@ */ import URL from 'url'; +import { ObservabilityOnboardingUsername } from '../../../server/test_helpers/create_observability_onboarding_users/authentication'; + +export type InstallationStep = + | 'ea-download' + | 'ea-extract' + | 'ea-install' + | 'ea-status' + | 'ea-config'; + +export type InstallationStepStatus = + | 'incomplete' + | 'complete' + | 'disabled' + | 'loading' + | 'warning' + | 'danger' + | 'current'; + +Cypress.Commands.add('loginAsViewerUser', () => { + return cy.loginAs({ + username: ObservabilityOnboardingUsername.viewerUser, + password: 'changeme', + }); +}); + +Cypress.Commands.add('loginAsEditorUser', () => { + return cy.loginAs({ + username: ObservabilityOnboardingUsername.editorUser, + password: 'changeme', + }); +}); + +Cypress.Commands.add('loginAsLogMonitoringUser', () => { + return cy.loginAs({ + username: ObservabilityOnboardingUsername.logMonitoringUser, + password: 'changeme', + }); +}); + +Cypress.Commands.add('loginAsElastic', () => { + return cy.loginAs({ + username: 'elastic', + password: 'changeme', + }); +}); Cypress.Commands.add( 'loginAs', @@ -31,13 +76,6 @@ Cypress.Commands.add( } ); -Cypress.Commands.add('loginAsElastic', () => { - return cy.loginAs({ - username: 'elastic', - password: 'changeme', - }); -}); - Cypress.Commands.add('getByTestSubj', (selector: string) => { return cy.get(`[data-test-subj="${selector}"]`); }); @@ -57,3 +95,85 @@ Cypress.Commands.add( }); } ); + +Cypress.Commands.add('installCustomIntegration', (integrationName: string) => { + const kibanaUrl = Cypress.env('KIBANA_URL'); + + cy.request({ + log: false, + method: 'POST', + url: `${kibanaUrl}/api/fleet/epm/custom_integrations`, + body: { + force: true, + integrationName, + datasets: [ + { name: `${integrationName}.access`, type: 'logs' }, + { name: `${integrationName}.error`, type: 'metrics' }, + { name: `${integrationName}.warning`, type: 'logs' }, + ], + }, + headers: { + 'kbn-xsrf': 'e2e_test', + 'Elastic-Api-Version': '2023-10-31', + }, + auth: { user: 'editor', pass: 'changeme' }, + }); +}); + +Cypress.Commands.add('deleteIntegration', (integrationName: string) => { + const kibanaUrl = Cypress.env('KIBANA_URL'); + + cy.request({ + log: false, + method: 'GET', + url: `${kibanaUrl}/api/fleet/epm/packages/${integrationName}`, + headers: { + 'kbn-xsrf': 'e2e_test', + }, + auth: { user: 'editor', pass: 'changeme' }, + failOnStatusCode: false, + }).then((response) => { + const status = response.body.item?.status; + if (status === 'installed') { + cy.request({ + log: false, + method: 'DELETE', + url: `${kibanaUrl}/api/fleet/epm/packages/${integrationName}`, + body: { + force: false, + }, + headers: { + 'kbn-xsrf': 'e2e_test', + 'Elastic-Api-Version': '1', + }, + auth: { user: 'editor', pass: 'changeme' }, + }); + } + }); +}); + +Cypress.Commands.add( + 'updateInstallationStepStatus', + ( + onboardingId: string, + step: InstallationStep, + status: InstallationStepStatus + ) => { + const kibanaUrl = Cypress.env('KIBANA_URL'); + + cy.log(onboardingId, step, status); + + cy.request({ + log: false, + method: 'POST', + url: `${kibanaUrl}/internal/observability_onboarding/flow/${onboardingId}/step/${step}`, + headers: { + 'kbn-xsrf': 'e2e_test', + }, + auth: { user: 'editor', pass: 'changeme' }, + body: { + status, + }, + }); + } +); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/support/types.d.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/support/types.d.ts index 8db14ad70561a..dbc28bb442bb9 100644 --- a/x-pack/plugins/observability_onboarding/e2e/cypress/support/types.d.ts +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/support/types.d.ts @@ -11,8 +11,18 @@ declare namespace Cypress { username: string; password: string; }): Cypress.Chainable>; + loginAsViewerUser(): Cypress.Chainable>; + loginAsEditorUser(): Cypress.Chainable>; + loginAsLogMonitoringUser(): Cypress.Chainable>; loginAsElastic(): Cypress.Chainable>; getByTestSubj(selector: string): Chainable>; visitKibana(url: string, rangeFrom?: string, rangeTo?: string): void; + installCustomIntegration(integrationName: string): void; + deleteIntegration(integrationName: string): void; + updateInstallationStepStatus( + onboardingId: string, + step: InstallationStep, + status: InstallationStepStatus + ): void; } } diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress_test_runner.ts b/x-pack/plugins/observability_onboarding/e2e/cypress_test_runner.ts index 1d056897a6d02..34765a892cc1c 100644 --- a/x-pack/plugins/observability_onboarding/e2e/cypress_test_runner.ts +++ b/x-pack/plugins/observability_onboarding/e2e/cypress_test_runner.ts @@ -9,6 +9,7 @@ import cypress from 'cypress'; import path from 'path'; import Url from 'url'; import { FtrProviderContext } from './ftr_provider_context'; +import { createObservabilityOnboardingUsers } from '../server/test_helpers/create_observability_onboarding_users'; export async function cypressTestRunner({ ftrProviderContext: { getService }, @@ -22,6 +23,13 @@ export async function cypressTestRunner({ const username = config.get('servers.elasticsearch.username'); const password = config.get('servers.elasticsearch.password'); + const kibanaUrl = Url.format({ + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: config.get('servers.kibana.port'), + auth: `${username}:${password}`, + }); + const esNode = Url.format({ protocol: config.get('servers.elasticsearch.protocol'), port: config.get('servers.elasticsearch.port'), @@ -29,6 +37,12 @@ export async function cypressTestRunner({ auth: `${username}:${password}`, }); + // Creates ObservabilityOnboarding users + await createObservabilityOnboardingUsers({ + elasticsearch: { node: esNode, username, password }, + kibana: { hostname: kibanaUrl }, + }); + const esRequestTimeout = config.get('timeouts.esRequestTimeout'); const kibanaUrlWithoutAuth = Url.format({ diff --git a/x-pack/plugins/observability_onboarding/kibana.jsonc b/x-pack/plugins/observability_onboarding/kibana.jsonc index 97689407aff41..5c1615c3a95ba 100644 --- a/x-pack/plugins/observability_onboarding/kibana.jsonc +++ b/x-pack/plugins/observability_onboarding/kibana.jsonc @@ -7,7 +7,7 @@ "server": true, "browser": true, "configPath": ["xpack", "observability_onboarding"], - "requiredPlugins": ["data", "observability", "observabilityShared", "discover"], + "requiredPlugins": ["data", "observability", "observabilityShared", "discover", "share", "fleet"], "optionalPlugins": ["cloud", "usageCollection"], "requiredBundles": ["kibanaReact"], "extraPublicDirs": ["common"] diff --git a/x-pack/plugins/observability_onboarding/public/application/app.tsx b/x-pack/plugins/observability_onboarding/public/application/app.tsx index 0646e0d800924..93a62f5f378e3 100644 --- a/x-pack/plugins/observability_onboarding/public/application/app.tsx +++ b/x-pack/plugins/observability_onboarding/public/application/app.tsx @@ -26,7 +26,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { RouteComponentProps, RouteProps } from 'react-router-dom'; import { ConfigSchema } from '..'; -import { customLogsRoutes } from '../components/app/custom_logs/wizard'; +import { customLogsRoutes } from '../components/app/custom_logs'; import { systemLogsRoutes } from '../components/app/system_logs'; import { ObservabilityOnboardingHeaderActionMenu } from '../components/app/header_action_menu'; import { diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/api_key_banner.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/api_key_banner.tsx similarity index 92% rename from x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/api_key_banner.tsx rename to x-pack/plugins/observability_onboarding/public/components/app/custom_logs/api_key_banner.tsx index 57a7ba7b4cab1..46d39cd70c31c 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/api_key_banner.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/api_key_banner.tsx @@ -18,7 +18,7 @@ import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; import { i18n } from '@kbn/i18n'; import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public'; import React from 'react'; -import { APIReturnType } from '../../../../services/rest/create_call_api'; +import { APIReturnType } from '../../../services/rest/create_call_api'; type ApiKeyPayload = APIReturnType<'POST /internal/observability_onboarding/logs/flow'>; @@ -54,6 +54,7 @@ export function ApiKeyBanner({
    } color="primary" + data-test-subj="obltOnboardingLogsCreatingApiKey" /> ); @@ -67,6 +68,7 @@ export function ApiKeyBanner({ )} color="success" iconType="check" + data-test-subj="obltOnboardingLogsApiKeyCreated" >

    {i18n.translate( @@ -91,6 +93,7 @@ export function ApiKeyBanner({ {(copy) => (

    {i18n.translate( @@ -148,6 +152,7 @@ export function ApiKeyBanner({ )} color="warning" iconType="warning" + data-test-subj="obltOnboardingLogsApiKeyCreationNoPrivileges" >

    {i18n.translate( diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/back_button.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/back_button.tsx similarity index 92% rename from x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/back_button.tsx rename to x-pack/plugins/observability_onboarding/public/components/app/custom_logs/back_button.tsx index 16be8f65e0099..23afda21ef6be 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/back_button.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/back_button.tsx @@ -17,6 +17,7 @@ export function BackButton({ onBack }: { onBack: () => void }) { return ( { + const { + integrationName: createdIntegrationName, + datasets: createdDatasets, + } = integrationOptions; + setState((state) => ({ + ...state, + integrationName: createdIntegrationName, + datasetName: createdDatasets[0].name, + lastCreatedIntegrationOptions: integrationOptions, + })); + goToStep('installElasticAgent'); + }; + + return ( + + + + ); +} + +export function ConfigureLogsContent() { + const { + dispatchableEvents: { updateCreateFields }, + } = useConsumerCustomIntegrations(); + const { euiTheme } = useEuiTheme(); + const xsFontSize = useEuiFontSize('xs').fontSize; + + const { goBack, getState, setState } = useWizard(); + const wizardState = getState(); + const [serviceName, setServiceName] = useState(wizardState.serviceName); + const [logFilePaths, setLogFilePaths] = useState(wizardState.logFilePaths); + const [namespace, setNamespace] = useState(wizardState.namespace); + const [customConfigurations, setCustomConfigurations] = useState( + wizardState.customConfigurations + ); + const logFilePathNotConfigured = logFilePaths.every((filepath) => !filepath); + + const onContinue = useCallback(() => { + setState((state) => ({ + ...state, + serviceName, + logFilePaths: logFilePaths.filter((filepath) => !!filepath), + namespace, + customConfigurations, + })); + }, [customConfigurations, logFilePaths, namespace, serviceName, setState]); + + function addLogFilePath() { + setLogFilePaths((prev) => [...prev, '']); + } + + function removeLogFilePath(index: number) { + setLogFilePaths((prev) => prev.filter((_, i) => i !== index)); + } + + function onLogFilePathChanges( + index: number, + event: React.FormEvent + ) { + const filepath = event.currentTarget?.value; + setLogFilePaths((prev) => + prev.map((path, i) => (i === index ? filepath : path)) + ); + + if (index === 0) { + if (updateCreateFields) { + updateCreateFields({ + integrationName: getFilename(filepath).toLowerCase(), + datasets: [ + { + name: getFilename(filepath).toLowerCase(), + type: 'logs' as const, + }, + ], + }); + } + } + } + + return ( + , + , + ]} + /> + } + > + + +

    + {i18n.translate( + 'xpack.observability_onboarding.configureLogs.description', + { + defaultMessage: 'Configure inputs', + } + )} +

    + + + + + <> + {logFilePaths.map((filepath, index) => ( +
    + {index > 0 && } + + + onLogFilePathChanges(index, ev)} + /> + + {index > 0 && ( + + removeLogFilePath(index)} + data-test-subj={`obltOnboardingLogFilePathDelete-${index}`} + /> + + )} + +
    + ))} + +
    + + + + + {i18n.translate( + 'xpack.observability_onboarding.configureLogs.logFile.addRow', + { + defaultMessage: 'Add row', + } + )} + + + + + + + {i18n.translate( + 'xpack.observability_onboarding.configureLogs.serviceName', + { + defaultMessage: 'Service name', + } + )} + + + + +
    + } + helpText={ + + } + > + setServiceName(event.target.value)} + data-test-subj="obltOnboardingCustomLogsServiceName" + /> + + + + + + + + + {i18n.translate( + 'xpack.observability_onboarding.configureLogs.namespace', + { + defaultMessage: 'Namespace', + } + )} + + + + + + } + helpText={ + + {i18n.translate( + 'xpack.observability_onboarding.configureLogs.learnMore', + { + defaultMessage: 'Learn more', + } + )} + + ), + }} + /> + } + > + setNamespace(event.target.value)} + data-test-subj="obltOnboardingCustomLogsNamespace" + /> + + + + {i18n.translate( + 'xpack.observability_onboarding.configureLogs.learnMore', + { + defaultMessage: 'Learn more', + } + )} + + ), + }} + /> + } + > + + setCustomConfigurations(event.target.value) + } + data-test-subj="obltOnboardingCustomLogsCustomConfig" + /> + + +
    + + + + + + + + ); +} diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/get_filename.ts b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/get_filename.ts similarity index 100% rename from x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/get_filename.ts rename to x-pack/plugins/observability_onboarding/public/components/app/custom_logs/get_filename.ts diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/index.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/index.tsx new file mode 100644 index 0000000000000..afe575c08018c --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/index.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CustomIntegrationOptions } from '@kbn/custom-integrations'; +import { i18n } from '@kbn/i18n'; +import { + createWizardContext, + Step, +} from '../../../context/create_wizard_context'; +import { ConfigureLogs } from './configure_logs'; +import { Inspect } from './inspect'; +import { InstallElasticAgent } from './install_elastic_agent'; +import { SelectLogs } from './select_logs'; + +interface WizardState { + integrationName?: string; + lastCreatedIntegrationOptions?: CustomIntegrationOptions; + datasetName?: string; + serviceName: string; + logFilePaths: string[]; + namespace: string; + customConfigurations: string; + logsType?: + | 'system' + | 'sys' + | 'http-endpoint' + | 'opentelemetry' + | 'amazon-firehose' + | 'log-file' + | 'service'; + uploadType?: 'log-file' | 'api-key'; + elasticAgentPlatform: 'linux-tar' | 'macos' | 'windows' | 'deb' | 'rpm'; + autoDownloadConfig: boolean; + apiKeyEncoded: string; + onboardingId: string; +} + +const initialState: WizardState = { + integrationName: undefined, + datasetName: undefined, + serviceName: '', + logFilePaths: [''], + namespace: 'default', + customConfigurations: '', + elasticAgentPlatform: 'linux-tar', + autoDownloadConfig: false, + apiKeyEncoded: '', + onboardingId: '', +}; + +export type CustomLogsSteps = + | 'selectLogs' + | 'configureLogs' + | 'installElasticAgent' + | 'inspect'; + +const steps: Record = { + selectLogs: { component: SelectLogs }, + configureLogs: { component: ConfigureLogs }, + installElasticAgent: { + component: InstallElasticAgent, + title: i18n.translate( + 'xpack.observability_onboarding.customLogs.installShipper.title', + { + defaultMessage: 'Install shipper to collect logs', + } + ), + }, + inspect: { component: Inspect }, +}; + +const { + Provider, + useWizard, + routes: customLogsRoutes, +} = createWizardContext({ + initialState, + initialStep: 'configureLogs', + steps, + basePath: '/customLogs', +}); + +export { Provider, useWizard, customLogsRoutes }; diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/inspect.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/inspect.tsx similarity index 97% rename from x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/inspect.tsx rename to x-pack/plugins/observability_onboarding/public/components/app/custom_logs/inspect.tsx index 2a9074e6976e1..b632fd6e84ffc 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/inspect.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/inspect.tsx @@ -11,7 +11,7 @@ import { StepPanel, StepPanelContent, StepPanelFooter, -} from '../../../shared/step_panel'; +} from '../../shared/step_panel'; import { useWizard } from '.'; import { BackButton } from './back_button'; diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/install_elastic_agent.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx similarity index 84% rename from x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/install_elastic_agent.tsx rename to x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx index 184ef62d81277..aea68cdd6711c 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx @@ -7,10 +7,7 @@ import { EuiButton, - EuiButtonEmpty, EuiCallOut, - EuiFlexGroup, - EuiFlexItem, EuiHorizontalRule, EuiSpacer, EuiText, @@ -18,47 +15,61 @@ import { import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { default as React, useCallback, useEffect, useState } from 'react'; -import { ObservabilityOnboardingPluginSetupDeps } from '../../../../plugin'; +import { + SingleDatasetLocatorParams, + SINGLE_DATASET_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin'; import { useWizard } from '.'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { ElasticAgentPlatform, getElasticAgentSetupCommand, -} from '../../../shared/get_elastic_agent_setup_command'; +} from '../../shared/get_elastic_agent_setup_command'; import { InstallElasticAgentSteps, ProgressStepId, EuiStepStatus, -} from '../../../shared/install_elastic_agent_steps'; +} from '../../shared/install_elastic_agent_steps'; import { StepPanel, StepPanelContent, StepPanelFooter, -} from '../../../shared/step_panel'; +} from '../../shared/step_panel'; import { ApiKeyBanner } from './api_key_banner'; import { BackButton } from './back_button'; -import { getDiscoverNavigationParams } from '../../utils'; -import { WindowsInstallStep } from '../../../shared/windows_install_step'; -import { TroubleshootingLink } from '../../../shared/troubleshooting_link'; +import { WindowsInstallStep } from '../../shared/windows_install_step'; +import { TroubleshootingLink } from '../../shared/troubleshooting_link'; export function InstallElasticAgent() { const { - services: { - discover: { locator }, - }, + services: { share }, } = useKibana(); - const { goBack, goToStep, getState, setState } = useWizard(); + + const singleDatasetLocator = + share.url.locators.get( + SINGLE_DATASET_LOCATOR_ID + ); + + const { goBack, getState, setState } = useWizard(); const wizardState = getState(); + const { + integrationName: integration, + datasetName: dataset, + autoDownloadConfig, + } = wizardState; + const [elasticAgentPlatform, setElasticAgentPlatform] = useState('linux-tar'); - function onInspect() { - goToStep('inspect'); - } + const enforcedDatasetName = + integration === dataset ? dataset : `${integration}.${dataset}`; + async function onContinue() { - await locator?.navigate( - getDiscoverNavigationParams([wizardState.datasetName]) - ); + await singleDatasetLocator!.navigate({ + integration, + dataset: enforcedDatasetName, + }); } function onAutoDownloadConfig() { @@ -211,7 +222,11 @@ export function InstallElasticAgent() { : stepStatus === 'complete' ? CHECK_LOGS_LABELS.completed : CHECK_LOGS_LABELS.incomplete; - return { title, status: stepStatus }; + return { + title, + status: stepStatus, + 'data-test-subj': 'obltOnboardingCheckLogsStep', + }; } return { title: CHECK_LOGS_LABELS.incomplete, @@ -231,29 +246,18 @@ export function InstallElasticAgent() { , - - - - {i18n.translate( - 'xpack.observability_onboarding.steps.inspect', - { defaultMessage: 'Inspect' } - )} - - - - - {i18n.translate( - 'xpack.observability_onboarding.steps.exploreLogs', - { defaultMessage: 'Explore logs' } - )} - - - , + + {i18n.translate( + 'xpack.observability_onboarding.steps.exploreLogs', + { defaultMessage: 'Explore logs' } + )} + , ]} /> } @@ -271,7 +275,7 @@ export function InstallElasticAgent() {

    - {wizardState.integrationName && ( + {integration && ( <> @@ -343,10 +348,10 @@ export function InstallElasticAgent() { apiEndpoint: setup?.apiEndpoint, scriptDownloadUrl: setup?.scriptDownloadUrl, elasticAgentVersion: setup?.elasticAgentVersion, - autoDownloadConfig: wizardState.autoDownloadConfig, + autoDownloadConfig, onboardingId, })} - autoDownloadConfig={wizardState.autoDownloadConfig} + autoDownloadConfig={autoDownloadConfig} onToggleAutoDownloadConfig={onAutoDownloadConfig} installAgentStatus={ installShipperSetupStatus === FETCH_STATUS.LOADING @@ -391,7 +396,7 @@ const CHECK_LOGS_LABELS = { ), loading: i18n.translate( 'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.loadingTitle', - { defaultMessage: 'Waiting for Logs to be shipped...' } + { defaultMessage: 'Waiting for logs to be shipped...' } ), completed: i18n.translate( 'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.completedTitle', diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/select_logs.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/select_logs.tsx similarity index 94% rename from x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/select_logs.tsx rename to x-pack/plugins/observability_onboarding/public/components/app/custom_logs/select_logs.tsx index b1a7569255007..7bed69c1c8f9b 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/select_logs.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/select_logs.tsx @@ -25,9 +25,9 @@ import { StepPanel, StepPanelContent, StepPanelFooter, -} from '../../../shared/step_panel'; +} from '../../shared/step_panel'; import { useWizard } from '.'; -import { useKibanaNavigation } from '../../../../hooks/use_kibana_navigation'; +import { useKibanaNavigation } from '../../../hooks/use_kibana_navigation'; export function SelectLogs() { const { navigateToKibanaUrl, navigateToAppUrl } = useKibanaNavigation(); @@ -48,7 +48,11 @@ export function SelectLogs() { panelFooter={ + {i18n.translate('xpack.observability_onboarding.steps.back', { defaultMessage: 'Back', })} @@ -183,6 +187,7 @@ export function SelectLogs() { { diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/configure_logs.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/configure_logs.tsx deleted file mode 100644 index a29286f84336a..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/configure_logs.tsx +++ /dev/null @@ -1,663 +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 { - EuiAccordion, - EuiButton, - EuiButtonEmpty, - EuiButtonIcon, - EuiCallOut, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiHorizontalRule, - EuiIconTip, - EuiLink, - EuiSpacer, - EuiText, - EuiTextArea, - useEuiFontSize, - useEuiTheme, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { isEmpty } from 'lodash'; -import React, { useCallback, useState } from 'react'; -import { - IntegrationError, - IntegrationOptions, - useCreateIntegration, -} from '../../../../hooks/use_create_integration'; -import { useWizard } from '.'; -import { OptionalFormRow } from '../../../shared/optional_form_row'; -import { - StepPanel, - StepPanelContent, - StepPanelFooter, -} from '../../../shared/step_panel'; -import { BackButton } from './back_button'; -import { getFilename, replaceSpecialChars } from './get_filename'; - -export function ConfigureLogs() { - const [datasetNameTouched, setDatasetNameTouched] = useState(false); - const { euiTheme } = useEuiTheme(); - const xsFontSize = useEuiFontSize('xs').fontSize; - - const { goToStep, goBack, getState, setState } = useWizard(); - const wizardState = getState(); - const [integrationName, setIntegrationName] = useState( - wizardState.integrationName - ); - const [integrationNameTouched, setIntegrationNameTouched] = useState(false); - const [integrationError, setIntegrationError] = useState< - IntegrationError | undefined - >(); - const [datasetName, setDatasetName] = useState(wizardState.datasetName); - const [serviceName, setServiceName] = useState(wizardState.serviceName); - const [logFilePaths, setLogFilePaths] = useState(wizardState.logFilePaths); - const [namespace, setNamespace] = useState(wizardState.namespace); - const [customConfigurations, setCustomConfigurations] = useState( - wizardState.customConfigurations - ); - const logFilePathNotConfigured = logFilePaths.every((filepath) => !filepath); - - const onIntegrationCreationSuccess = useCallback( - (integration: IntegrationOptions) => { - setState((state) => ({ - ...state, - lastCreatedIntegration: integration, - })); - goToStep('installElasticAgent'); - }, - [goToStep, setState] - ); - - const onIntegrationCreationFailure = useCallback( - (error: IntegrationError) => { - setIntegrationError(error); - }, - [setIntegrationError] - ); - - const { createIntegration, createIntegrationRequest } = useCreateIntegration({ - onIntegrationCreationSuccess, - onIntegrationCreationFailure, - initialLastCreatedIntegration: wizardState.lastCreatedIntegration, - }); - - const isCreatingIntegration = createIntegrationRequest.state === 'pending'; - const hasFailedCreatingIntegration = - createIntegrationRequest.state === 'rejected'; - - const onContinue = useCallback(() => { - setState((state) => ({ - ...state, - datasetName, - integrationName, - serviceName, - logFilePaths: logFilePaths.filter((filepath) => !!filepath), - namespace, - customConfigurations, - })); - createIntegration({ - integrationName, - datasets: [ - { - name: datasetName, - type: 'logs' as const, - }, - ], - }); - }, [ - createIntegration, - customConfigurations, - datasetName, - integrationName, - logFilePaths, - namespace, - serviceName, - setState, - ]); - - function addLogFilePath() { - setLogFilePaths((prev) => [...prev, '']); - } - - function removeLogFilePath(index: number) { - setLogFilePaths((prev) => prev.filter((_, i) => i !== index)); - } - - function onLogFilePathChanges( - index: number, - event: React.FormEvent - ) { - const filepath = event.currentTarget?.value; - setLogFilePaths((prev) => - prev.map((path, i) => (i === index ? filepath : path)) - ); - - if (index === 0) { - setIntegrationName(getFilename(filepath)); - setDatasetName(getFilename(filepath)); - } - } - - const hasNamingCollision = - integrationError && integrationError.type === 'NamingCollision'; - - const isIntegrationNameInvalid = - (integrationNameTouched && - (isEmpty(integrationName) || !isLowerCase(integrationName))) || - hasNamingCollision; - - const integrationNameError = getIntegrationNameError( - integrationName, - integrationNameTouched, - integrationError - ); - - const isDatasetNameInvalid = - datasetNameTouched && (isEmpty(datasetName) || !isLowerCase(datasetName)); - - const datasetNameError = getDatasetNameError(datasetName, datasetNameTouched); - - return ( - , - - {isCreatingIntegration - ? i18n.translate( - 'xpack.observability_onboarding.steps.loading', - { - defaultMessage: 'Creating integration...', - } - ) - : i18n.translate( - 'xpack.observability_onboarding.steps.continue', - { - defaultMessage: 'Continue', - } - )} - , - ]} - /> - } - > - - -

    - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.description', - { - defaultMessage: 'Configure inputs', - } - )} -

    -
    - - - - <> - {logFilePaths.map((filepath, index) => ( -
    - {index > 0 && } - - - onLogFilePathChanges(index, ev)} - /> - - {index > 0 && ( - - removeLogFilePath(index)} - /> - - )} - -
    - ))} - -
    - - - - - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.logFile.addRow', - { - defaultMessage: 'Add row', - } - )} - - - - - - - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.serviceName', - { - defaultMessage: 'Service name', - } - )} - - - - - - } - helpText={ - - } - > - setServiceName(event.target.value)} - /> - - - - - - - - - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.namespace', - { - defaultMessage: 'Namespace', - } - )} - - - - - - } - helpText={ - - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.learnMore', - { - defaultMessage: 'Learn more', - } - )} -
    - ), - }} - /> - } - > - setNamespace(event.target.value)} - /> - - - - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.learnMore', - { - defaultMessage: 'Learn more', - } - )} - - ), - }} - /> - } - > - - setCustomConfigurations(event.target.value) - } - /> - - - - - - - - -

    - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.configureIntegrationDescription', - { - defaultMessage: 'Configure integration', - } - )} -

    -
    - - - - - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.integration.name', - { - defaultMessage: 'Integration name', - } - )} - - - - - - } - helpText={i18n.translate( - 'xpack.observability_onboarding.configureLogs.integration.helper', - { - defaultMessage: - "All lowercase, max 100 chars, special characters will be replaced with '_'.", - } - )} - isInvalid={isIntegrationNameInvalid} - error={integrationNameError} - > - - setIntegrationName(replaceSpecialChars(event.target.value)) - } - isInvalid={isIntegrationNameInvalid} - onInput={() => setIntegrationNameTouched(true)} - /> - - - - {i18n.translate( - 'xpack.observability_onboarding.configureLogs.dataset.name', - { - defaultMessage: 'Dataset name', - } - )} - - - - - - } - helpText={i18n.translate( - 'xpack.observability_onboarding.configureLogs.dataset.helper', - { - defaultMessage: - "All lowercase, max 100 chars, special characters will be replaced with '_'.", - } - )} - isInvalid={isDatasetNameInvalid} - error={datasetNameError} - > - - setDatasetName(replaceSpecialChars(event.target.value)) - } - isInvalid={isDatasetNameInvalid} - onInput={() => setDatasetNameTouched(true)} - /> - - - {hasFailedCreatingIntegration && integrationError && ( - <> - - {getIntegrationErrorCallout(integrationError)} - - )} - - - ); -} - -const getIntegrationErrorCallout = (integrationError: IntegrationError) => { - const title = i18n.translate( - 'xpack.observability_onboarding.configureLogs.integrationCreation.error.title', - { defaultMessage: 'Sorry, there was an error' } - ); - - switch (integrationError.type) { - case 'AuthorizationError': - const authorizationDescription = i18n.translate( - 'xpack.observability_onboarding.configureLogs.integrationCreation.error.authorization.description', - { - defaultMessage: - 'This user does not have permissions to create an integration.', - } - ); - return ( - -

    {authorizationDescription}

    -
    - ); - case 'UnknownError': - return ( - -

    {integrationError.message}

    -
    - ); - } -}; - -const isLowerCase = (str: string) => str.toLowerCase() === str; - -const getIntegrationNameError = ( - integrationName: string, - touched: boolean, - integrationError?: IntegrationError -) => { - if (touched && isEmpty(integrationName)) { - return i18n.translate( - 'xpack.observability_onboarding.configureLogs.integration.emptyError', - { defaultMessage: 'An integration name is required.' } - ); - } - if (touched && !isLowerCase(integrationName)) { - return i18n.translate( - 'xpack.observability_onboarding.configureLogs.integration.lowercaseError', - { defaultMessage: 'An integration name should be lowercase.' } - ); - } - if (integrationError && integrationError.type === 'NamingCollision') { - return integrationError.message; - } -}; - -const getDatasetNameError = (datasetName: string, touched: boolean) => { - if (touched && isEmpty(datasetName)) { - return i18n.translate( - 'xpack.observability_onboarding.configureLogs.dataset.emptyError', - { defaultMessage: 'A dataset name is required.' } - ); - } - if (touched && !isLowerCase(datasetName)) { - return i18n.translate( - 'xpack.observability_onboarding.configureLogs.dataset.lowercaseError', - { defaultMessage: 'A dataset name should be lowercase.' } - ); - } -}; diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/index.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/index.tsx deleted file mode 100644 index fecd9c4de8384..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/wizard/index.tsx +++ /dev/null @@ -1,87 +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 { i18n } from '@kbn/i18n'; -import { IntegrationOptions } from '../../../../hooks/use_create_integration'; -import { - createWizardContext, - Step, -} from '../../../../context/create_wizard_context'; -import { ConfigureLogs } from './configure_logs'; -import { Inspect } from './inspect'; -import { InstallElasticAgent } from './install_elastic_agent'; -import { SelectLogs } from './select_logs'; - -interface WizardState { - integrationName: string; - lastCreatedIntegration?: IntegrationOptions; - datasetName: string; - serviceName: string; - logFilePaths: string[]; - namespace: string; - customConfigurations: string; - logsType?: - | 'system' - | 'sys' - | 'http-endpoint' - | 'opentelemetry' - | 'amazon-firehose' - | 'log-file' - | 'service'; - uploadType?: 'log-file' | 'api-key'; - elasticAgentPlatform: 'linux-tar' | 'macos' | 'windows' | 'deb' | 'rpm'; - autoDownloadConfig: boolean; - apiKeyEncoded: string; - onboardingId: string; -} - -const initialState: WizardState = { - integrationName: '', - datasetName: '', - serviceName: '', - logFilePaths: [''], - namespace: 'default', - customConfigurations: '', - elasticAgentPlatform: 'linux-tar', - autoDownloadConfig: false, - apiKeyEncoded: '', - onboardingId: '', -}; - -export type CustomLogsSteps = - | 'selectLogs' - | 'configureLogs' - | 'installElasticAgent' - | 'inspect'; - -const steps: Record = { - selectLogs: { component: SelectLogs }, - configureLogs: { component: ConfigureLogs }, - installElasticAgent: { - component: InstallElasticAgent, - title: i18n.translate( - 'xpack.observability_onboarding.customLogs.installShipper.title', - { - defaultMessage: 'Install shipper to collect logs', - } - ), - }, - inspect: { component: Inspect }, -}; - -const { - Provider, - useWizard, - routes: customLogsRoutes, -} = createWizardContext({ - initialState, - initialStep: 'configureLogs', - steps, - basePath: '/customLogs', -}); - -export { Provider, useWizard, customLogsRoutes }; diff --git a/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx b/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx index 27a44e94f07b6..d1b05433b9d82 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx @@ -89,7 +89,7 @@ export function Home() {

    {i18n.translate('xpack.observability_onboarding.home.title', { - defaultMessage: 'Get started with Observability', + defaultMessage: 'Collect and analyze logs', })}

    @@ -123,7 +123,12 @@ export function Home() { { defaultMessage: 'Stream host system logs' } )} footer={ - + {getStartedLabel} } @@ -134,11 +139,12 @@ export function Home() { paddingSize="l" display="plain" hasBorder + onClick={handleClickSystemLogs} > {elasticAgentLabel} - +

    {i18n.translate( 'xpack.observability_onboarding.card.systemLogs.description1', @@ -172,11 +178,12 @@ export function Home() { paddingSize="l" display="plain" hasBorder + onClick={handleClickCustomLogs} > {elasticAgentLabel} - +

    {i18n.translate( 'xpack.observability_onboarding.card.customLogs.description.text', @@ -216,7 +223,11 @@ export function Home() { } )} footer={ - + {getStartedLabel} } @@ -245,6 +256,7 @@ export function Home() { {getStartedLabel} @@ -253,6 +265,7 @@ export function Home() { paddingSize="m" display="plain" hasBorder + onClick={handleClickKubernetesSetupGuide} /> @@ -282,7 +295,11 @@ export function Home() { )} footer={ <> - + {i18n.translate( 'xpack.observability_onboarding.card.integrations.start', { defaultMessage: 'Start exploring' } @@ -298,14 +315,20 @@ export function Home() { - + {i18n.translate( 'xpack.observability_onboarding.card.integrations.sampleData', { defaultMessage: 'Use sample data' } )} - + {i18n.translate( 'xpack.observability_onboarding.card.integrations.uploadFile', { defaultMessage: 'Upload a file' } @@ -314,6 +337,7 @@ export function Home() { (); + const singleDatasetLocator = + share.url.locators.get( + SINGLE_DATASET_LOCATOR_ID + ); + const { navigateToKibanaUrl } = useKibanaNavigation(); const { getState, setState } = useWizard(); const wizardState = getState(); @@ -60,11 +65,10 @@ export function InstallElasticAgent() { navigateToKibanaUrl('/app/observabilityOnboarding'); } async function onContinue() { - const dataStreams = getSystemLogsDataStreams(); - const dataSets = dataStreams.map( - (dataSream) => dataSream.data_stream.dataset - ); - await locator?.navigate(getDiscoverNavigationParams(dataSets)); + await singleDatasetLocator!.navigate({ + integration: 'system', + dataset: 'system.syslog', + }); } function onAutoDownloadConfig() { @@ -171,7 +175,11 @@ export function InstallElasticAgent() { : stepStatus === 'complete' ? CHECK_LOGS_LABELS.completed : CHECK_LOGS_LABELS.incomplete; - return { title, status: stepStatus }; + return { + title, + status: stepStatus, + 'data-test-subj': 'obltOnboardingCheckLogsStep', + }; } return { title: CHECK_LOGS_LABELS.incomplete, @@ -190,7 +198,11 @@ export function InstallElasticAgent() { panelFooter={ + {i18n.translate( 'xpack.observability_onboarding.systemLogs.back', { defaultMessage: 'Back' } @@ -203,6 +215,7 @@ export function InstallElasticAgent() { fill iconType="magnifyWithPlus" onClick={onContinue} + data-test-subj="obltOnboardingExploreLogs" > {i18n.translate( 'xpack.observability_onboarding.steps.exploreLogs', diff --git a/x-pack/plugins/observability_onboarding/public/components/app/system_logs/system_integration_banner.tsx b/x-pack/plugins/observability_onboarding/public/components/app/system_logs/system_integration_banner.tsx index d567a13f3d3a4..61ebaca8056ea 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/system_logs/system_integration_banner.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/system_logs/system_integration_banner.tsx @@ -74,6 +74,7 @@ export function SystemIntegrationBanner() { } color="primary" + data-test-subj="obltOnboardingSystemLogsInstallingIntegration" /> ); } @@ -89,6 +90,7 @@ export function SystemIntegrationBanner() { )} color="warning" iconType="warning" + data-test-subj="obltOnboardingSystemLogsIntegrationInstallationFailed" > {error?.message} @@ -106,6 +108,7 @@ export function SystemIntegrationBanner() { values={{ systemIntegrationTooltip: ( ); diff --git a/x-pack/plugins/observability_onboarding/public/components/app/utils.ts b/x-pack/plugins/observability_onboarding/public/components/app/utils.ts deleted file mode 100644 index 843002cb1fcc6..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/components/app/utils.ts +++ /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 type { DataViewSpec } from '@kbn/data-views-plugin/common'; -import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; -import { Filter, FilterStateStore } from '@kbn/es-query'; - -type DiscoverPropertiesToPick = 'dataViewId' | 'dataViewSpec' | 'filters'; - -type DiscoverNavigationParams = Pick< - DiscoverAppLocatorParams, - DiscoverPropertiesToPick ->; - -const defaultFilterKey = 'data_stream.dataset'; -const defaultLogsDataViewId = 'logs-*'; -const defaultLogsDataView: DataViewSpec = { - id: defaultLogsDataViewId, - title: defaultLogsDataViewId, -}; - -const getDefaultDatasetFilter = (datasets: string[]): Filter[] => [ - { - meta: { - index: defaultLogsDataViewId, - key: defaultFilterKey, - params: datasets, - type: 'phrases', - }, - query: { - bool: { - minimum_should_match: 1, - should: datasets.map((dataset) => ({ - match_phrase: { - [defaultFilterKey]: dataset, - }, - })), - }, - }, - $state: { - store: FilterStateStore.APP_STATE, - }, - }, -]; - -export const getDiscoverNavigationParams = ( - datasets: string[] -): DiscoverNavigationParams => ({ - dataViewId: defaultLogsDataViewId, - dataViewSpec: defaultLogsDataView, - filters: getDefaultDatasetFilter(datasets), -}); diff --git a/x-pack/plugins/observability_onboarding/public/components/shared/filmstrip_transition.tsx b/x-pack/plugins/observability_onboarding/public/components/shared/filmstrip_transition.tsx index df877c2856733..39389ce5ec94b 100644 --- a/x-pack/plugins/observability_onboarding/public/components/shared/filmstrip_transition.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/shared/filmstrip_transition.tsx @@ -23,7 +23,7 @@ export function FilmstripTransition({ flexGrow: 1, position: 'relative', zIndex: 0, - transitionTimingFunction: 'ease-out', + transitionTimingFunction: 'ease-in-out', transition: transition !== 'ready' ? `transform ${duration}ms` : undefined, transform: diff --git a/x-pack/plugins/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx b/x-pack/plugins/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx index 16996a18d05b0..aedd66f6d7245 100644 --- a/x-pack/plugins/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx @@ -74,15 +74,20 @@ export function InstallElasticAgentSteps({ configureAgentYaml, appendedSteps = [], }: Props) { + const configPath = + selectedPlatform === 'macos' + ? '/Library/Elastic/Agent/elastic-agent.yml' + : '/opt/Elastic/Agent/elastic-agent.yml'; + const isInstallStarted = intersection( Object.keys(installProgressSteps), - Object.keys(PROGRESS_STEP_TITLES(selectedPlatform)) + Object.keys(PROGRESS_STEP_TITLES(configPath)) ).length > 0; const autoDownloadConfigStep = getStep( 'ea-config', installProgressSteps, - selectedPlatform + configPath ); const customInstallStep = installAgentPlatformOptions.find( @@ -108,15 +113,10 @@ export function InstallElasticAgentSteps({ const { title, status, message } = getStep( stepId, installProgressSteps, - selectedPlatform + configPath ); return ( - + ); })} @@ -136,7 +136,7 @@ export function InstallElasticAgentSteps({ defaultMessage: 'The agent config below will be downloaded by the install script and written to ({configPath}). This will overwrite any existing agent configuration.', values: { - configPath: '/opt/Elastic/Agent/elastic-agent.yml', + configPath, }, } ) @@ -146,7 +146,7 @@ export function InstallElasticAgentSteps({ defaultMessage: 'Add the following configuration to {configPath} on the host where you installed the Elastic agent.', values: { - configPath: '/opt/Elastic/Agent/elastic-agent.yml', + configPath, }, } )} @@ -184,6 +184,7 @@ export function InstallElasticAgentSteps({ download="elastic-agent.yml" target="_blank" isDisabled={autoDownloadConfig} + data-test-subj="obltOnboardingConfigureElasticAgentStepDownloadConfig" > {i18n.translate( 'xpack.observability_onboarding.installElasticAgent.configStep.downloadConfigButton', @@ -209,6 +210,7 @@ export function InstallElasticAgentSteps({ ({ values={{ hostRequirementsLink: ( @@ -274,6 +277,7 @@ export function InstallElasticAgentSteps({ checked={autoDownloadConfig} onChange={onToggleAutoDownloadConfig} disabled={disableSteps || isInstallStarted} + data-test-subj="obltOnboardingInstallElasticAgentAutoDownloadConfig" /> {autoDownloadConfig && ( @@ -288,6 +292,7 @@ export function InstallElasticAgentSteps({ )} color="warning" iconType="warning" + data-test-subj="obltOnboardingInstallElasticAgentAutoDownloadConfigCallout" /> @@ -318,6 +323,7 @@ export function InstallElasticAgentSteps({ ), }, { + 'data-test-subj': 'obltOnboardingConfigureElasticAgentStep', title: i18n.translate( 'xpack.observability_onboarding.installElasticAgent.configureStep.title', { defaultMessage: 'Configure the Elastic agent' } @@ -329,6 +335,7 @@ export function InstallElasticAgentSteps({ children: null, ...euiStep, status: disableSteps ? 'disabled' : euiStep.status, + 'data-test-subj': euiStep['data-test-subj'], })), ]} /> @@ -338,10 +345,10 @@ export function InstallElasticAgentSteps({ function getStep( id: ProgressStepId, installProgressSteps: Props['installProgressSteps'], - selectedPlatform: string + configPath: string ): { title: string; status: EuiStepStatus; message?: string } { const { loadingTitle, completedTitle, incompleteTitle } = - PROGRESS_STEP_TITLES(selectedPlatform)[id]; + PROGRESS_STEP_TITLES(configPath)[id]; const stepProgress = installProgressSteps[id]; if (stepProgress) { const { status, message } = stepProgress; @@ -365,11 +372,11 @@ function getStep( } const PROGRESS_STEP_TITLES: ( - selectedPlatform: string + configPath: string ) => Record< ProgressStepId, Record<'incompleteTitle' | 'loadingTitle' | 'completedTitle', string> -> = (selectedPlatform: string) => ({ +> = (configPath: string) => ({ 'ea-download': { incompleteTitle: i18n.translate( 'xpack.observability_onboarding.installElasticAgent.progress.eaDownload.incompleteTitle', @@ -444,10 +451,7 @@ const PROGRESS_STEP_TITLES: ( { defaultMessage: 'Elastic Agent config written to {configPath}', values: { - configPath: - selectedPlatform === 'macos' - ? '/Library/Elastic/Agent/elastic-agent.yml' - : '/opt/Elastic/Agent/elastic-agent.yml', + configPath, }, } ), diff --git a/x-pack/plugins/observability_onboarding/public/components/shared/popover_tooltip.tsx b/x-pack/plugins/observability_onboarding/public/components/shared/popover_tooltip.tsx index 66165edc8e133..8a0e8b53e7350 100644 --- a/x-pack/plugins/observability_onboarding/public/components/shared/popover_tooltip.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/shared/popover_tooltip.tsx @@ -13,6 +13,7 @@ interface PopoverTooltipProps { iconType?: string; title?: string; children: React.ReactNode; + dataTestSubj: string; } export function PopoverTooltip({ @@ -20,6 +21,7 @@ export function PopoverTooltip({ iconType = 'iInCircle', title, children, + dataTestSubj, }: PopoverTooltipProps) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -32,6 +34,7 @@ export function PopoverTooltip({ style={{ margin: '-5px 0 0 -5px' }} button={ ) => { setIsPopoverOpen(!isPopoverOpen); diff --git a/x-pack/plugins/observability_onboarding/public/components/shared/step_status.tsx b/x-pack/plugins/observability_onboarding/public/components/shared/step_status.tsx index 59ad5d202cf89..d2a5341fda731 100644 --- a/x-pack/plugins/observability_onboarding/public/components/shared/step_status.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/shared/step_status.tsx @@ -27,7 +27,7 @@ export function StepStatus({ }) { if (status === 'loading') { return ( - + @@ -43,7 +43,7 @@ export function StepStatus({ } if (status === 'complete') { return ( - + {message} @@ -52,7 +52,7 @@ export function StepStatus({ } if (status === 'danger') { return ( - + {message} @@ -61,7 +61,7 @@ export function StepStatus({ } if (status === 'warning') { return ( - + {message} diff --git a/x-pack/plugins/observability_onboarding/public/components/shared/troubleshooting_link.tsx b/x-pack/plugins/observability_onboarding/public/components/shared/troubleshooting_link.tsx index 5b6a1588d643c..71ab94a9bade8 100644 --- a/x-pack/plugins/observability_onboarding/public/components/shared/troubleshooting_link.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/shared/troubleshooting_link.tsx @@ -13,6 +13,7 @@ export function TroubleshootingLink() { return ( {i18n.translate( 'xpack.observability_onboarding.windows.installStep.link.label', diff --git a/x-pack/plugins/observability_onboarding/public/hooks/use_create_integration.ts b/x-pack/plugins/observability_onboarding/public/hooks/use_create_integration.ts deleted file mode 100644 index 15d383cb4aa42..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/hooks/use_create_integration.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useState } from 'react'; -import deepEqual from 'react-fast-compare'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useTrackedPromise } from '@kbn/use-tracked-promise'; -import { i18n } from '@kbn/i18n'; - -export interface IntegrationOptions { - integrationName: string; - datasets: Array<{ - name: string; - type: 'logs'; - }>; -} - -// Errors -const GENERIC_ERROR_MESSAGE = i18n.translate( - 'xpack.observability_onboarding.useCreateIntegration.integrationError.genericError', - { - defaultMessage: 'Unable to create an integration', - } -); - -type ErrorType = 'NamingCollision' | 'AuthorizationError' | 'UnknownError'; -export interface IntegrationError { - type: ErrorType; - message: string; -} - -export const useCreateIntegration = ({ - onIntegrationCreationSuccess, - onIntegrationCreationFailure, - initialLastCreatedIntegration, - deletePreviousIntegration = true, -}: { - integrationOptions?: IntegrationOptions; - onIntegrationCreationSuccess: (integration: IntegrationOptions) => void; - onIntegrationCreationFailure: (error: IntegrationError) => void; - initialLastCreatedIntegration?: IntegrationOptions; - deletePreviousIntegration?: boolean; -}) => { - const { - services: { http }, - } = useKibana(); - const [lastCreatedIntegration, setLastCreatedIntegration] = useState< - IntegrationOptions | undefined - >(initialLastCreatedIntegration); - - const [createIntegrationRequest, callCreateIntegration] = useTrackedPromise( - { - cancelPreviousOn: 'creation', - createPromise: async (integrationOptions) => { - if (lastCreatedIntegration && deletePreviousIntegration) { - await http?.delete( - `/api/fleet/epm/packages/${lastCreatedIntegration.integrationName}/1.0.0`, - {} - ); - } - await http?.post('/api/fleet/epm/custom_integrations', { - body: JSON.stringify(integrationOptions), - }); - - return integrationOptions; - }, - onResolve: (integrationOptions: IntegrationOptions) => { - setLastCreatedIntegration(integrationOptions); - onIntegrationCreationSuccess(integrationOptions!); - }, - onReject: (requestError: any) => { - if (requestError?.body?.statusCode === 409) { - onIntegrationCreationFailure({ - type: 'NamingCollision' as const, - message: requestError.body.message, - }); - } else if (requestError?.body?.statusCode === 403) { - onIntegrationCreationFailure({ - type: 'AuthorizationError' as const, - message: requestError?.body?.message, - }); - } else { - onIntegrationCreationFailure({ - type: 'UnknownError' as const, - message: requestError?.body?.message ?? GENERIC_ERROR_MESSAGE, - }); - } - }, - }, - [ - lastCreatedIntegration, - deletePreviousIntegration, - onIntegrationCreationSuccess, - onIntegrationCreationFailure, - setLastCreatedIntegration, - ] - ); - - const createIntegration = useCallback( - (integrationOptions: IntegrationOptions) => { - // Bypass creating the integration again - if (deepEqual(integrationOptions, lastCreatedIntegration)) { - onIntegrationCreationSuccess(integrationOptions); - } else { - callCreateIntegration(integrationOptions); - } - }, - [ - callCreateIntegration, - lastCreatedIntegration, - onIntegrationCreationSuccess, - ] - ); - - return { - createIntegration, - createIntegrationRequest, - }; -}; diff --git a/x-pack/plugins/observability_onboarding/public/hooks/use_install_system_integration.ts b/x-pack/plugins/observability_onboarding/public/hooks/use_install_system_integration.ts index 5473ebd09c240..6f1fee6f92be5 100644 --- a/x-pack/plugins/observability_onboarding/public/hooks/use_install_system_integration.ts +++ b/x-pack/plugins/observability_onboarding/public/hooks/use_install_system_integration.ts @@ -48,12 +48,16 @@ export const useInstallSystemIntegration = ({ { cancelPreviousOn: 'creation', createPromise: async () => { + const options = { + headers: { 'Elastic-Api-Version': '2023-10-31' }, + }; + const { item: systemIntegration } = await http.get<{ item: { version: string; status: IntegrationInstallStatus }; - }>('/api/fleet/epm/packages/system'); + }>('/api/fleet/epm/packages/system', options); if (systemIntegration.status !== 'installed') { - await http.post('/api/fleet/epm/packages/system'); + await http.post('/api/fleet/epm/packages/system', options); } return { diff --git a/x-pack/plugins/observability_onboarding/public/icons/universal_profiling.svg b/x-pack/plugins/observability_onboarding/public/icons/universal_profiling.svg new file mode 100644 index 0000000000000..819ab070e7396 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/icons/universal_profiling.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/x-pack/plugins/observability_onboarding/public/index.ts b/x-pack/plugins/observability_onboarding/public/index.ts index 9454626e80dc8..b83c5b6d5cad0 100644 --- a/x-pack/plugins/observability_onboarding/public/index.ts +++ b/x-pack/plugins/observability_onboarding/public/index.ts @@ -17,6 +17,9 @@ import { ObservabilityOnboardingPluginStart, } from './plugin'; +export { OBSERVABILITY_ONBOARDING_LOCATOR } from './locators/onboarding_locator/locator_definition'; +export type { ObservabilityOnboardingLocatorParams } from './locators/onboarding_locator/types'; + export interface ConfigSchema { ui: { enabled: boolean; diff --git a/x-pack/plugins/observability_onboarding/public/locators/index.ts b/x-pack/plugins/observability_onboarding/public/locators/index.ts new file mode 100644 index 0000000000000..5f82c40181259 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/locators/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ObservabilityOnboardingLocator } from './onboarding_locator/types'; + +export interface ObservabilityOnboardingPluginLocators { + onboarding: ObservabilityOnboardingLocator; +} diff --git a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/get_location.ts b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/get_location.ts new file mode 100644 index 0000000000000..93bb3861740cd --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/get_location.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ObservabilityOnboardingLocatorParams } from './types'; +import { PLUGIN_ID } from '../../../common'; + +export function getLocation(params: ObservabilityOnboardingLocatorParams) { + const { source } = params; + + const path = ['/', source].filter(Boolean).join(''); + + return { + app: PLUGIN_ID, + path, + state: {}, + }; +} diff --git a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.test.ts b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.test.ts new file mode 100644 index 0000000000000..6caf9222a239f --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ObservabilityOnboardingLocatorDefinition } from './locator_definition'; + +describe('Observability onboarding locator', () => { + test('should create a link to the overview page', async () => { + const locator = new ObservabilityOnboardingLocatorDefinition(); + const location = await locator.getLocation(); + + expect(location).toMatchObject({ + app: 'observabilityOnboarding', + path: '/', + state: {}, + }); + }); + + test('should create a link to specified log source onboarding', async () => { + const locator = new ObservabilityOnboardingLocatorDefinition(); + const systemLocation = await locator.getLocation({ source: 'systemLogs' }); + + expect(systemLocation).toMatchObject({ + app: 'observabilityOnboarding', + path: '/systemLogs', + state: {}, + }); + }); +}); diff --git a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.ts b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.ts new file mode 100644 index 0000000000000..a4f8965dda5a3 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorDefinition } from '@kbn/share-plugin/public'; +import { + ObservabilityOnboardingLocatorParams, + OBSERVABILITY_ONBOARDING_LOCATOR, +} from '@kbn/deeplinks-observability/locators'; + +export class ObservabilityOnboardingLocatorDefinition + implements LocatorDefinition +{ + public readonly id = OBSERVABILITY_ONBOARDING_LOCATOR; + + public readonly getLocation = async ( + params: ObservabilityOnboardingLocatorParams = {} + ) => { + const { getLocation } = await import('./get_location'); + return getLocation(params); + }; +} diff --git a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/types.ts b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/types.ts new file mode 100644 index 0000000000000..61f6d923db49d --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorPublic } from '@kbn/share-plugin/public'; +import type { ObservabilityOnboardingLocatorParams } from '@kbn/deeplinks-observability/locators'; + +export type ObservabilityOnboardingLocator = + LocatorPublic; diff --git a/x-pack/plugins/observability_onboarding/public/plugin.ts b/x-pack/plugins/observability_onboarding/public/plugin.ts index 8769991169090..05806059b12e3 100644 --- a/x-pack/plugins/observability_onboarding/public/plugin.ts +++ b/x-pack/plugins/observability_onboarding/public/plugin.ts @@ -23,8 +23,11 @@ import { DataPublicPluginSetup, DataPublicPluginStart, } from '@kbn/data-plugin/public'; -import type { DiscoverSetup } from '@kbn/discover-plugin/public'; +import { SharePluginSetup } from '@kbn/share-plugin/public'; import type { ObservabilityOnboardingConfig } from '../server'; +import { PLUGIN_ID } from '../common'; +import { ObservabilityOnboardingLocatorDefinition } from './locators/onboarding_locator/locator_definition'; +import { ObservabilityOnboardingPluginLocators } from './locators'; export type ObservabilityOnboardingPluginSetup = void; export type ObservabilityOnboardingPluginStart = void; @@ -32,7 +35,7 @@ export type ObservabilityOnboardingPluginStart = void; export interface ObservabilityOnboardingPluginSetupDeps { data: DataPublicPluginSetup; observability: ObservabilityPublicSetup; - discover: DiscoverSetup; + share: SharePluginSetup; } export interface ObservabilityOnboardingPluginStartDeps { @@ -48,6 +51,8 @@ export class ObservabilityOnboardingPlugin ObservabilityOnboardingPluginStart > { + private locators?: ObservabilityOnboardingPluginLocators; + constructor(private ctx: PluginInitializerContext) {} public setup( @@ -69,7 +74,7 @@ export class ObservabilityOnboardingPlugin navLinkStatus: isServerlessEnabled ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden, - id: 'observabilityOnboarding', + id: PLUGIN_ID, title: 'Observability Onboarding', order: 8500, euiIconType: 'logoObservability', @@ -98,9 +103,23 @@ export class ObservabilityOnboardingPlugin }, }); } + + this.locators = { + onboarding: plugins.share.url.locators.create( + new ObservabilityOnboardingLocatorDefinition() + ), + }; + + return { + locators: this.locators, + }; } public start( core: CoreStart, plugins: ObservabilityOnboardingPluginStartDeps - ) {} + ) { + return { + locators: this.locators, + }; + } } diff --git a/x-pack/plugins/observability_onboarding/public/routes/index.tsx b/x-pack/plugins/observability_onboarding/public/routes/index.tsx index 66a99343be2b2..35044a98df050 100644 --- a/x-pack/plugins/observability_onboarding/public/routes/index.tsx +++ b/x-pack/plugins/observability_onboarding/public/routes/index.tsx @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import React from 'react'; import { Redirect } from 'react-router-dom'; -import { customLogsRoutes } from '../components/app/custom_logs/wizard'; +import { customLogsRoutes } from '../components/app/custom_logs'; import { systemLogsRoutes } from '../components/app/system_logs'; import { Home } from '../components/app/home'; diff --git a/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx b/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx index da30df8a11eac..c2a18238943d2 100644 --- a/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx +++ b/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import React, { ComponentType, useRef, useState } from 'react'; import { breadcrumbsApp } from '../../application/app'; -import { Provider as WizardProvider } from '../../components/app/custom_logs/wizard'; +import { Provider as WizardProvider } from '../../components/app/custom_logs'; import { FilmstripFrame, FilmstripTransition, @@ -89,18 +89,16 @@ function AnimatedTransitionsWizard({ children }: Props) { duration={TRANSITION_DURATION} transition={transition} > - - { - // eslint-disable-next-line react/jsx-pascal-case - transition === 'back' ? : null - } - - {children} - - { - // eslint-disable-next-line react/jsx-pascal-case - transition === 'next' ? : null + + {children} diff --git a/x-pack/plugins/observability_onboarding/scripts/test/e2e.js b/x-pack/plugins/observability_onboarding/scripts/test/e2e.js index d5cd56175d223..8a584a84bdc87 100644 --- a/x-pack/plugins/observability_onboarding/scripts/test/e2e.js +++ b/x-pack/plugins/observability_onboarding/scripts/test/e2e.js @@ -76,7 +76,11 @@ function runTests() { return childProcess.spawnSync('node', spawnArgs, { cwd: e2eDir, - env: { ...process.env, CYPRESS_CLI_ARGS: JSON.stringify(cypressCliArgs) }, + env: { + ...process.env, + CYPRESS_CLI_ARGS: JSON.stringify(cypressCliArgs), + NODE_OPTIONS: '--openssl-legacy-provider', + }, encoding: 'utf8', stdio: 'inherit', }); diff --git a/x-pack/plugins/observability_onboarding/server/routes/logs/route.ts b/x-pack/plugins/observability_onboarding/server/routes/logs/route.ts index c28c393382930..085f7cece0d8a 100644 --- a/x-pack/plugins/observability_onboarding/server/routes/logs/route.ts +++ b/x-pack/plugins/observability_onboarding/server/routes/logs/route.ts @@ -40,7 +40,7 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({ scriptDownloadUrl: string; elasticAgentVersion: string; }> { - const { core, plugins, kibanaVersion } = resources; + const { core, plugins } = resources; const coreStart = await core.start(); const kibanaUrl = @@ -53,7 +53,7 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({ return { apiEndpoint, scriptDownloadUrl, - elasticAgentVersion: kibanaVersion, + elasticAgentVersion: '8.9.1', }; }, }); diff --git a/x-pack/plugins/observability_onboarding/tsconfig.json b/x-pack/plugins/observability_onboarding/tsconfig.json index 2119563923cb9..4e1e2bacdf437 100644 --- a/x-pack/plugins/observability_onboarding/tsconfig.json +++ b/x-pack/plugins/observability_onboarding/tsconfig.json @@ -14,7 +14,6 @@ "kbn_references": [ "@kbn/core", "@kbn/data-plugin", - "@kbn/discover-plugin", "@kbn/kibana-react-plugin", "@kbn/observability-plugin", "@kbn/i18n", @@ -30,9 +29,10 @@ "@kbn/core-http-server", "@kbn/security-plugin", "@kbn/std", - "@kbn/data-views-plugin", - "@kbn/es-query", "@kbn/use-tracked-promise", + "@kbn/custom-integrations", + "@kbn/share-plugin", + "@kbn/deeplinks-observability" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/observability_shared/kibana.jsonc b/x-pack/plugins/observability_shared/kibana.jsonc index 4206097c764e8..f4e97551031bf 100644 --- a/x-pack/plugins/observability_shared/kibana.jsonc +++ b/x-pack/plugins/observability_shared/kibana.jsonc @@ -7,7 +7,7 @@ "server": false, "browser": true, "configPath": ["xpack", "observability_shared"], - "requiredPlugins": ["cases", "guidedOnboarding", "uiActions"], + "requiredPlugins": ["cases", "guidedOnboarding", "uiActions", "embeddable"], "optionalPlugins": [], "requiredBundles": ["data", "inspector", "kibanaReact", "kibanaUtils"], "extraPublicDirs": ["common"] diff --git a/x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx new file mode 100644 index 0000000000000..70bc9b4b7df48 --- /dev/null +++ b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx @@ -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 type { BaseFlameGraph } from '@kbn/profiling-utils'; +import React from 'react'; +import { ProfilingEmbeddable, ProfilingEmbeddableProps } from './profiling_embeddable'; +import { EMBEDDABLE_FLAMEGRAPH } from '.'; + +type Props = Omit, 'embeddableFactoryId'>; + +export function EmbeddableFlamegraph(props: Props) { + return ; +} diff --git a/x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx new file mode 100644 index 0000000000000..4869eab74d508 --- /dev/null +++ b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TopNFunctions } from '@kbn/profiling-utils'; +import React from 'react'; +import { ProfilingEmbeddable, ProfilingEmbeddableProps } from './profiling_embeddable'; +import { EMBEDDABLE_FUNCTIONS } from '.'; + +type Props = Omit, 'embeddableFactoryId'> & { + rangeFrom: number; + rangeTo: number; +}; + +export function EmbeddableFunctions(props: Props) { + return ; +} diff --git a/x-pack/plugins/observability_shared/public/components/profiling/embeddables/index.ts b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/index.ts new file mode 100644 index 0000000000000..2e346d55c835a --- /dev/null +++ b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** Profiling flamegraph embeddable key */ +export const EMBEDDABLE_FLAMEGRAPH = 'EMBEDDABLE_FLAMEGRAPH'; +/** Profiling flamegraph embeddable */ +export { EmbeddableFlamegraph } from './embeddable_flamegraph'; + +/** Profiling functions embeddable key */ +export const EMBEDDABLE_FUNCTIONS = 'EMBEDDABLE_FUNCTIONS'; +/** Profiling functions embeddable */ +export { EmbeddableFunctions } from './embeddable_functions'; diff --git a/x-pack/plugins/observability_shared/public/components/profiling/embeddables/profiling_embeddable.tsx b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/profiling_embeddable.tsx new file mode 100644 index 0000000000000..1a703d11ed6c9 --- /dev/null +++ b/x-pack/plugins/observability_shared/public/components/profiling/embeddables/profiling_embeddable.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 { css } from '@emotion/react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React, { useEffect, useRef, useState } from 'react'; +import { ObservabilitySharedStart } from '../../../plugin'; + +export interface ProfilingEmbeddableProps { + data?: T; + embeddableFactoryId: string; + isLoading: boolean; + height?: string; +} + +export function ProfilingEmbeddable({ + embeddableFactoryId, + data, + isLoading, + height, + ...props +}: ProfilingEmbeddableProps) { + const { embeddable: embeddablePlugin } = useKibana().services; + const [embeddable, setEmbeddable] = useState(); + const embeddableRoot: React.RefObject = useRef(null); + + useEffect(() => { + async function createEmbeddable() { + const factory = embeddablePlugin?.getEmbeddableFactory(embeddableFactoryId); + const input = { id: 'embeddable_profiling', data, isLoading }; + const embeddableObject = await factory?.create(input); + setEmbeddable(embeddableObject); + } + createEmbeddable(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (embeddableRoot.current && embeddable) { + embeddable.render(embeddableRoot.current); + } + }, [embeddable, embeddableRoot]); + + useEffect(() => { + if (embeddable) { + embeddable.updateInput({ data, isLoading, ...props }); + embeddable.reload(); + } + }, [data, embeddable, isLoading, props]); + + return ( +

    + ); +} diff --git a/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts b/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts index f230a7097cf6d..cea6bd835b985 100644 --- a/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts +++ b/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts @@ -19,6 +19,7 @@ export interface LinkDescriptor { pathname?: string; hash?: string; search?: Search; + state?: unknown; } export interface LinkProps { @@ -31,7 +32,7 @@ export interface Options { } export const useLinkProps = ( - { app, pathname, hash, search }: LinkDescriptor, + { app, pathname, hash, search, state }: LinkDescriptor, options: Options = {} ): LinkProps => { validateParams({ app, pathname, hash, search }); @@ -77,7 +78,7 @@ export const useLinkProps = ( const navigate = () => { if (navigateToApp) { const navigationPath = mergedHash ? `#${mergedHash}` : mergedPathname; - navigateToApp(app, { path: navigationPath ? navigationPath : undefined }); + navigateToApp(app, { path: navigationPath ? navigationPath : undefined, state }); } }; @@ -94,7 +95,7 @@ export const useLinkProps = ( navigate(); } }; - }, [navigateToApp, mergedHash, mergedPathname, app, prompt]); + }, [prompt, navigateToApp, mergedHash, mergedPathname, app, state]); return { href, diff --git a/x-pack/plugins/observability_shared/public/index.ts b/x-pack/plugins/observability_shared/public/index.ts index 7308265827d09..7e5fc02d0a0b5 100644 --- a/x-pack/plugins/observability_shared/public/index.ts +++ b/x-pack/plugins/observability_shared/public/index.ts @@ -77,3 +77,10 @@ export { casesFeatureId, sloFeatureId, } from '../common'; + +export { + EMBEDDABLE_FLAMEGRAPH, + EMBEDDABLE_FUNCTIONS, + EmbeddableFlamegraph, + EmbeddableFunctions, +} from './components/profiling/embeddables'; diff --git a/x-pack/plugins/observability_shared/public/plugin.ts b/x-pack/plugins/observability_shared/public/plugin.ts index 2e982f27dd35a..b2f886a2368d7 100644 --- a/x-pack/plugins/observability_shared/public/plugin.ts +++ b/x-pack/plugins/observability_shared/public/plugin.ts @@ -11,6 +11,7 @@ import type { CoreStart, Plugin } from '@kbn/core/public'; import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import { CasesUiStart } from '@kbn/cases-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { createNavigationRegistry } from './components/page_template/helpers/navigation_registry'; import { createLazyObservabilityPageTemplate } from './components/page_template'; import { updateGlobalNavigation } from './services/update_global_navigation'; @@ -20,6 +21,7 @@ export interface ObservabilitySharedStart { cases: CasesUiStart; guidedOnboarding: GuidedOnboardingPluginStart; setIsSidebarEnabled: (isEnabled: boolean) => void; + embeddable: EmbeddableStart; } export type ObservabilitySharedPluginSetup = ReturnType; diff --git a/x-pack/plugins/observability_shared/tsconfig.json b/x-pack/plugins/observability_shared/tsconfig.json index 6a49b5e24b9f6..f5ca4094ee961 100644 --- a/x-pack/plugins/observability_shared/tsconfig.json +++ b/x-pack/plugins/observability_shared/tsconfig.json @@ -32,6 +32,8 @@ "@kbn/rison", "@kbn/kibana-utils-plugin", "@kbn/shared-ux-router", + "@kbn/embeddable-plugin", + "@kbn/profiling-utils" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/osquery/common/api/model/schema/common_attributes.schema.yaml b/x-pack/plugins/osquery/common/api/model/schema/common_attributes.schema.yaml index f0408cad9929a..ba236b8601e7d 100644 --- a/x-pack/plugins/osquery/common/api/model/schema/common_attributes.schema.yaml +++ b/x-pack/plugins/osquery/common/api/model/schema/common_attributes.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Common Osquery Attributes - version: '2023-10-31 + version: '2023-10-31' paths: { } components: schemas: diff --git a/x-pack/plugins/osquery/common/api/packs/create_pack.schema.yaml b/x-pack/plugins/osquery/common/api/packs/create_pack.schema.yaml index 5176992407877..da04d037b1d56 100644 --- a/x-pack/plugins/osquery/common/api/packs/create_pack.schema.yaml +++ b/x-pack/plugins/osquery/common/api/packs/create_pack.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Create Pack Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: schemas: diff --git a/x-pack/plugins/osquery/common/api/packs/create_pack_route.ts b/x-pack/plugins/osquery/common/api/packs/create_pack_route.ts index 9205668d6f73d..b4f89e7701dd8 100644 --- a/x-pack/plugins/osquery/common/api/packs/create_pack_route.ts +++ b/x-pack/plugins/osquery/common/api/packs/create_pack_route.ts @@ -13,7 +13,7 @@ export const createPackRequestBodySchema = t.type({ description: t.union([t.string, t.undefined]), enabled: t.union([t.boolean, t.undefined]), policy_ids: t.union([t.array(t.string), t.undefined]), - shards: t.record(t.string, toNumberRt), + shards: t.union([t.record(t.string, toNumberRt), t.undefined]), queries: t.record( t.string, t.type({ diff --git a/x-pack/plugins/osquery/common/api/packs/delete_packs.schema.yaml b/x-pack/plugins/osquery/common/api/packs/delete_packs.schema.yaml index e84a2672e5be0..3286aa0b1bb7a 100644 --- a/x-pack/plugins/osquery/common/api/packs/delete_packs.schema.yaml +++ b/x-pack/plugins/osquery/common/api/packs/delete_packs.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Delete Saved Queries Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: diff --git a/x-pack/plugins/osquery/common/api/packs/find_packs.schema.yaml b/x-pack/plugins/osquery/common/api/packs/find_packs.schema.yaml index ead543e842703..4cd1c222bc6d2 100644 --- a/x-pack/plugins/osquery/common/api/packs/find_packs.schema.yaml +++ b/x-pack/plugins/osquery/common/api/packs/find_packs.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Find Saved Queries Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: diff --git a/x-pack/plugins/osquery/common/api/packs/packs.schema.yaml b/x-pack/plugins/osquery/common/api/packs/packs.schema.yaml index d35d849497dcc..006e0ebd75286 100644 --- a/x-pack/plugins/osquery/common/api/packs/packs.schema.yaml +++ b/x-pack/plugins/osquery/common/api/packs/packs.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Packs Schema - version: '2023-10-31 + version: '2023-10-31' paths: /api/osquery/packs: get: @@ -43,25 +43,30 @@ paths: schema: $ref: './read_packs.schema.yaml#/components/schemas/SuccessResponse' delete: - summary: Delete packs - parameters: - - $ref: './delete_packs.schema.yaml#/components/parameters/DeletePacksRequestQueryParameter' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: './find_packs.schema.yaml#/components/schemas/SuccessResponse' + summary: Delete packs + parameters: + - $ref: './delete_packs.schema.yaml#/components/parameters/DeletePacksRequestQueryParameter' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './find_packs.schema.yaml#/components/schemas/SuccessResponse' put: - summary: Update packs - parameters: - - $ref: './update_packs.schema.yaml#/components/parameters/UpdatePacksRequestQueryBody' - - $ref: './update_packs.schema.yaml#/components/parameters/UpdatePacksRequestQueryParameter' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: './update_packs.schema.yaml#/components/schemas/SuccessResponse' + summary: Update packs + requestBody: + required: true + content: + application/json: + schema: + $ref: './update_packs.schema.yaml#/components/schemas/UpdatePacksRequestBody' + parameters: + - $ref: './update_packs.schema.yaml#/components/parameters/UpdatePacksRequestQueryParameter' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './update_packs.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/osquery/common/api/packs/read_packs.schema.yaml b/x-pack/plugins/osquery/common/api/packs/read_packs.schema.yaml index 4929cc3e61848..8cfe415848c92 100644 --- a/x-pack/plugins/osquery/common/api/packs/read_packs.schema.yaml +++ b/x-pack/plugins/osquery/common/api/packs/read_packs.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Read Saved Queries Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: diff --git a/x-pack/plugins/osquery/common/api/packs/update_packs.schema.yaml b/x-pack/plugins/osquery/common/api/packs/update_packs.schema.yaml index 7632f21f2cb69..0b0510b4773ab 100644 --- a/x-pack/plugins/osquery/common/api/packs/update_packs.schema.yaml +++ b/x-pack/plugins/osquery/common/api/packs/update_packs.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Update Saved Query Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: @@ -11,12 +11,6 @@ components: required: true schema: $ref: '#/components/schemas/UpdatePacksRequestParams' - UpdatePacksRequestQueryBody: - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/UpdatePacksRequestBody' schemas: UpdatePacksRequestParams: type: object diff --git a/x-pack/plugins/osquery/common/api/saved_query/create_saved_query.schema.yaml b/x-pack/plugins/osquery/common/api/saved_query/create_saved_query.schema.yaml index 6fd278383e680..9735b0139754e 100644 --- a/x-pack/plugins/osquery/common/api/saved_query/create_saved_query.schema.yaml +++ b/x-pack/plugins/osquery/common/api/saved_query/create_saved_query.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Create Saved Query Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: schemas: diff --git a/x-pack/plugins/osquery/common/api/saved_query/delete_saved_query.schema.yaml b/x-pack/plugins/osquery/common/api/saved_query/delete_saved_query.schema.yaml index 39cb1b8ef71f5..7a180c542aa23 100644 --- a/x-pack/plugins/osquery/common/api/saved_query/delete_saved_query.schema.yaml +++ b/x-pack/plugins/osquery/common/api/saved_query/delete_saved_query.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Delete Saved Queries Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: diff --git a/x-pack/plugins/osquery/common/api/saved_query/find_saved_query.schema.yaml b/x-pack/plugins/osquery/common/api/saved_query/find_saved_query.schema.yaml index 3b1300825ee12..dbebf003a4696 100644 --- a/x-pack/plugins/osquery/common/api/saved_query/find_saved_query.schema.yaml +++ b/x-pack/plugins/osquery/common/api/saved_query/find_saved_query.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Find Saved Queries Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: diff --git a/x-pack/plugins/osquery/common/api/saved_query/read_saved_query.schema.yaml b/x-pack/plugins/osquery/common/api/saved_query/read_saved_query.schema.yaml index cac351670b65f..a5fed00a37e0c 100644 --- a/x-pack/plugins/osquery/common/api/saved_query/read_saved_query.schema.yaml +++ b/x-pack/plugins/osquery/common/api/saved_query/read_saved_query.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Read Saved Queries Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: diff --git a/x-pack/plugins/osquery/common/api/saved_query/saved_query.schema.yaml b/x-pack/plugins/osquery/common/api/saved_query/saved_query.schema.yaml index 8a846cf874a55..d8cef82ac7103 100644 --- a/x-pack/plugins/osquery/common/api/saved_query/saved_query.schema.yaml +++ b/x-pack/plugins/osquery/common/api/saved_query/saved_query.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Saved Queries Schema - version: '2023-10-31 + version: '2023-10-31' paths: /api/osquery/saved_queries: get: @@ -55,8 +55,13 @@ paths: $ref: './find_saved_query.schema.yaml#/components/schemas/SuccessResponse' put: summary: Update saved query + requestBody: + required: true + content: + application/json: + schema: + $ref: './update_saved_query.schema.yaml#/components/schemas/UpdateSavedQueryRequestBody' parameters: - - $ref: './update_saved_query.schema.yaml#/components/parameters/UpdateSavedQueryRequestQueryBody' - $ref: './update_saved_query.schema.yaml#/components/parameters/UpdateSavedQueryRequestQueryParameter' responses: '200': diff --git a/x-pack/plugins/osquery/common/api/saved_query/update_saved_query.schema.yaml b/x-pack/plugins/osquery/common/api/saved_query/update_saved_query.schema.yaml index 3f87f3fa14d66..b91359b5bbeef 100644 --- a/x-pack/plugins/osquery/common/api/saved_query/update_saved_query.schema.yaml +++ b/x-pack/plugins/osquery/common/api/saved_query/update_saved_query.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Update Saved Query Schema - version: '2023-10-31 + version: '2023-10-31' paths: { } components: parameters: @@ -11,12 +11,6 @@ components: required: true schema: $ref: '#/components/schemas/UpdateSavedQueryRequestParams' - UpdateSavedQueryRequestQueryBody: - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/UpdateSavedQueryRequestBody' schemas: UpdateSavedQueryRequestParams: type: object diff --git a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts index 4011d2db427a2..a84cdb5013047 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts @@ -111,6 +111,7 @@ describe('ALL - Add Integration', { tags: ['@ess', '@brokenInServerless'] }, () cy.getBySel('createAgentPolicyFlyoutBtn').click(); cy.getBySel('agentPolicyNameLink').contains(policyName).click(); cy.getBySel('addPackagePolicyButton').click(); + cy.getBySel('epmList.searchBar').type('osquery'); cy.getBySel('integration-card:epr:osquery_manager').click(); cy.getBySel('addIntegrationPolicyButton').click(); cy.getBySel('agentPolicySelect').within(() => { diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts index f120791fe1160..c56eb97a02050 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts @@ -39,7 +39,7 @@ describe('Alert Event Details - dynamic params', { tags: ['@ess', '@serverless'] it('should substitute parameters in investigation guide', () => { cy.getBySel('expand-event').first().click(); - cy.getBySel('securitySolutionDocumentDetailsFlyoutInvestigationGuideButton').click(); + cy.getBySel('securitySolutionFlyoutInvestigationGuideButton').click(); cy.contains('Get processes').click(); cy.getBySel('flyout-body-osquery').within(() => { cy.contains("SELECT * FROM os_version where name='Ubuntu';"); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts index 96cf4a285ccc7..15fe843139174 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts @@ -55,6 +55,7 @@ describe('ALL - Packs', { tags: ['@ess', '@serverless'] }, () => { cy.visit(FLEET_AGENT_POLICIES); cy.contains(AGENT_POLICY_NAME).click(); cy.contains('Add integration').click(); + cy.getBySel('epmList.searchBar').type('osquery'); cy.contains(integration).click(); addIntegration(AGENT_POLICY_NAME); cy.contains('Add Elastic Agent later').click(); @@ -221,6 +222,7 @@ describe('ALL - Packs', { tags: ['@ess', '@serverless'] }, () => { cy.contains(`Agent policy '${agentPolicy}' created`).click(); cy.contains(agentPolicy).click(); cy.contains('Add integration').click(); + cy.getBySel('epmList.searchBar').type('osquery'); cy.contains(integration).click(); addIntegration(agentPolicy); cy.contains('Add Elastic Agent later').click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/timelines.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/timelines.cy.ts index 460a79a7467de..08a1c0925ffb9 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/timelines.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/timelines.cy.ts @@ -8,7 +8,7 @@ import { takeOsqueryActionWithParams } from '../../tasks/live_query'; import { ServerlessRoleName } from '../../support/roles'; -describe('ALL - Timelines', { tags: ['@ess'] }, () => { +describe.skip('ALL - Timelines', { tags: ['@ess'] }, () => { beforeEach(() => { cy.login(ServerlessRoleName.SOC_MANAGER); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts index fcaf812b2c5f3..4abdf9dd05592 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts @@ -21,6 +21,12 @@ describe('Alert Test', { tags: ['@ess'] }, () => { }); describe('t1_analyst role', () => { + before(() => { + cy.login(ServerlessRoleName.SOC_MANAGER); + + cy.visit('/app/security/rules'); + clickRuleName(ruleName); + }); beforeEach(() => { cy.login(ServerlessRoleName.T1_ANALYST); @@ -29,7 +35,7 @@ describe('Alert Test', { tags: ['@ess'] }, () => { cy.getBySel('expand-event').first().click({ force: true }); cy.wait(500); - cy.getBySel('securitySolutionDocumentDetailsFlyoutInvestigationGuideButton').click(); + cy.getBySel('securitySolutionFlyoutInvestigationGuideButton').click(); cy.contains('Get processes').click(); }); diff --git a/x-pack/plugins/osquery/cypress/support/e2e.ts b/x-pack/plugins/osquery/cypress/support/e2e.ts index 76a705cb5977c..ed267eaff8ac6 100644 --- a/x-pack/plugins/osquery/cypress/support/e2e.ts +++ b/x-pack/plugins/osquery/cypress/support/e2e.ts @@ -30,8 +30,8 @@ export {}; import 'cypress-react-selector'; import registerCypressGrep from '@cypress/grep'; -import type { ServerlessRoleName } from './roles'; import { login } from '../../../../test_serverless/functional/test_suites/security/cypress/tasks/login'; +import type { ServerlessRoleName } from './roles'; registerCypressGrep(); diff --git a/x-pack/plugins/osquery/cypress/tasks/api_fixtures.ts b/x-pack/plugins/osquery/cypress/tasks/api_fixtures.ts index 9a139eadf9adf..d7b9f7d43ce43 100644 --- a/x-pack/plugins/osquery/cypress/tasks/api_fixtures.ts +++ b/x-pack/plugins/osquery/cypress/tasks/api_fixtures.ts @@ -294,6 +294,9 @@ export const loadAgentPolicy = () => monitoring_enabled: ['logs', 'metrics'], inactivity_timeout: 1209600, }, + headers: { + 'Elastic-Api-Version': API_VERSIONS.public.v1, + }, url: '/api/fleet/agent_policies', }).then((response) => response.body.item); @@ -301,5 +304,8 @@ export const cleanupAgentPolicy = (agentPolicyId: string) => request({ method: 'POST', body: { agentPolicyId }, + headers: { + 'Elastic-Api-Version': API_VERSIONS.public.v1, + }, url: '/api/fleet/agent_policies/delete', }); diff --git a/x-pack/plugins/osquery/cypress/tasks/integrations.ts b/x-pack/plugins/osquery/cypress/tasks/integrations.ts index 09948eca70538..c8bb688dac0f5 100644 --- a/x-pack/plugins/osquery/cypress/tasks/integrations.ts +++ b/x-pack/plugins/osquery/cypress/tasks/integrations.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { API_VERSIONS } from '../../common/constants'; import { DEFAULT_POLICY } from '../screens/fleet'; import { ADD_POLICY_BTN, @@ -125,7 +126,7 @@ export const deleteIntegrations = async (integrationName: string) => { .then(() => { cy.request({ url: `/api/fleet/package_policies/delete`, - headers: { 'kbn-xsrf': 'cypress' }, + headers: { 'kbn-xsrf': 'cypress', 'Elastic-Api-Version': API_VERSIONS.public.v1 }, body: `{ "packagePolicyIds": ${JSON.stringify(ids)} }`, method: 'POST', }); @@ -135,7 +136,7 @@ export const deleteIntegrations = async (integrationName: string) => { export const installPackageWithVersion = (integration: string, version: string) => { cy.request({ url: `/api/fleet/epm/packages/${integration}-${version}`, - headers: { 'kbn-xsrf': 'cypress' }, + headers: { 'kbn-xsrf': 'cypress', 'Elastic-Api-Version': API_VERSIONS.public.v1 }, body: '{ "force": true }', method: 'POST', }); diff --git a/x-pack/plugins/osquery/cypress/tasks/inventory.ts b/x-pack/plugins/osquery/cypress/tasks/inventory.ts index 30ffdede7a347..8ba6fc0702d21 100644 --- a/x-pack/plugins/osquery/cypress/tasks/inventory.ts +++ b/x-pack/plugins/osquery/cypress/tasks/inventory.ts @@ -9,7 +9,7 @@ export const triggerLoadData = () => { cy.getBySel('infraWaffleTimeControlsAutoRefreshButton').should('exist'); cy.wait(1000); cy.getBySel('infraWaffleTimeControlsAutoRefreshButton').click(); - cy.getBySel('nodeContainer').eq(2).should('exist'); + cy.getBySel('nodeContainer').last().should('exist'); cy.getBySel('infraWaffleTimeControlsStopRefreshingButton').click(); - cy.getBySel('nodeContainer').eq(2).click(); + cy.getBySel('nodeContainer').last().click(); }; diff --git a/x-pack/plugins/osquery/cypress/tsconfig.json b/x-pack/plugins/osquery/cypress/tsconfig.json index b8e92e55bbd2e..11f17f033495b 100644 --- a/x-pack/plugins/osquery/cypress/tsconfig.json +++ b/x-pack/plugins/osquery/cypress/tsconfig.json @@ -29,6 +29,6 @@ }, "@kbn/security-solution-plugin", "@kbn/fleet-plugin", - "@kbn/cases-plugin" + "@kbn/cases-plugin", ] } diff --git a/x-pack/plugins/osquery/package.json b/x-pack/plugins/osquery/package.json index bd21bb192545a..32db6010c6573 100644 --- a/x-pack/plugins/osquery/package.json +++ b/x-pack/plugins/osquery/package.json @@ -7,10 +7,10 @@ "scripts": { "cypress:burn": "yarn cypress:run --env burn=2 --headed", "cypress:changed-specs-only": "yarn cypress:run --changed-specs-only --env burn=2", - "cypress": "node ../security_solution/scripts/start_cypress_parallel --config-file ../osquery/cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/cli_config", + "cypress": "NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel --config-file ../osquery/cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/cli_config", "cypress:open": "yarn cypress open", "cypress:run": "yarn cypress run", - "cypress:serverless": "node ../security_solution/scripts/start_cypress_parallel --config-file ../osquery/serverless_cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/serverless_cli_config", + "cypress:serverless": "NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel --config-file ../osquery/serverless_cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/serverless_cli_config", "cypress:serverless:open": "yarn cypress:serverless open", "cypress:serverless:run": "yarn cypress:serverless run", "nyc": "../../../node_modules/.bin/nyc report --reporter=text-summary", diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 9f1d6e59b8168..6bf041bf9d9d0 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -7,10 +7,20 @@ import { find } from 'lodash/fp'; import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { EuiComboBox, EuiHealth, EuiFormRow, EuiHighlight, EuiSpacer } from '@elastic/eui'; +import { + EuiComboBox, + EuiHealth, + EuiFormRow, + EuiHighlight, + EuiSpacer, + EuiCallOut, + EuiLink, +} from '@elastic/eui'; import deepEqual from 'fast-deep-equal'; import useDebounce from 'react-use/lib/useDebounce'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '../common/lib/kibana'; import { useAllAgents } from './use_all_agents'; import { useAgentGroups } from './use_agent_groups'; import { AgentGrouper } from './agent_grouper'; @@ -27,6 +37,7 @@ import { ALL_AGENTS_LABEL, AGENT_POLICY_LABEL, AGENT_SELECTION_LABEL, + NO_AGENT_AVAILABLE_TITLE, } from './translations'; import type { SelectedGroups, AgentOptionValue, GroupOption, AgentSelection } from './types'; @@ -42,6 +53,7 @@ const perPage = 10; const DEBOUNCE_DELAY = 300; // ms const AgentsTableComponent: React.FC = ({ agentSelection, onChange, error }) => { + const { docLinks } = useKibana().services; // search related const [searchValue, setSearchValue] = useState(''); const [modifyingSearch, setModifyingSearch] = useState(false); @@ -148,17 +160,23 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh useEffect(() => { if (agentsFetched && groupsFetched && agentGroupsData) { + // Cap policies to 10 on init dropdown + const policies = (agentGroupsData?.groups.policies || []).slice( + 0, + searchValue === '' ? 10 : undefined + ); + const grouper = new AgentGrouper(); // update the groups when groups or agents have changed grouper.setTotalAgents(agentGroupsData?.total); grouper.updateGroup(AGENT_GROUP_KEY.Platform, agentGroupsData?.groups.platforms); - grouper.updateGroup(AGENT_GROUP_KEY.Policy, agentGroupsData?.groups.policies); + grouper.updateGroup(AGENT_GROUP_KEY.Policy, policies); // @ts-expect-error update types grouper.updateGroup(AGENT_GROUP_KEY.Agent, agents); const newOptions = grouper.generateOptions(); setOptions((prevOptions) => (!deepEqual(prevOptions, newOptions) ? newOptions : prevOptions)); } - }, [groupsLoading, agents, agentsFetched, groupsFetched, agentGroupsData]); + }, [groupsLoading, agents, agentsFetched, groupsFetched, agentGroupsData, searchValue]); const renderOption = useCallback((option, searchVal, contentClassName) => { const { label, value } = option; @@ -183,22 +201,58 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh setModifyingSearch(v !== ''); setSearchValue(v); }, []); + const isFetched = groupsFetched && agentsFetched && agentGroupsData; + + const renderNoAgentAvailableWarning = () => { + if (isFetched && !options.length) { + return ( + <> + + + + + ), + }} + /> + + + + ); + } + + return null; + }; return (
    - + <> + {renderNoAgentAvailableWarning()} + + {numAgentsSelected > 0 ? {generateSelectedAgentsMessage(numAgentsSelected)} : ''} diff --git a/x-pack/plugins/osquery/public/agents/translations.ts b/x-pack/plugins/osquery/public/agents/translations.ts index deabe5ecd36e5..55f1611bdf592 100644 --- a/x-pack/plugins/osquery/public/agents/translations.ts +++ b/x-pack/plugins/osquery/public/agents/translations.ts @@ -42,6 +42,14 @@ export const AGENT = i18n.translate('xpack.osquery.agents.agent', { export const AGENT_SELECTION_LABEL = i18n.translate('xpack.osquery.agents.selectionLabel', { defaultMessage: `Agents`, }); + +export const NO_AGENT_AVAILABLE_TITLE = i18n.translate( + 'xpack.osquery.agents.noAgentAvailableTitle', + { + defaultMessage: `No agents available`, + } +); + export const AGENT_QUERY = i18n.translate('xpack.osquery.agents.query', { defaultMessage: `Query`, }); diff --git a/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx index af7ad511b061b..e14b73242d6d5 100644 --- a/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx +++ b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { EuiLoadingSpinner } from '@elastic/eui'; import React, { lazy, Suspense } from 'react'; import type { OsqueryResponseActionsParamsFormProps } from './osquery_response_action_type'; @@ -16,7 +17,7 @@ export const getLazyOsqueryResponseActionTypeForm = const { onError, defaultValues, onChange } = props; return ( - + }> { @@ -131,12 +131,7 @@ export const createActionHandler = async ( : []; if (fleetActions.length) { - await esClientInternal.bulk({ - refresh: 'wait_for', - body: flatten( - fleetActions.map((action) => [{ index: { _index: AGENT_ACTIONS_INDEX } }, action]) - ), - }); + await osqueryContext.service.getFleetActionsClient()?.bulkCreate(fleetActions); } const actionsComponentTemplateExists = await esClientInternal.indices.exists({ @@ -146,7 +141,7 @@ export const createActionHandler = async ( if (actionsComponentTemplateExists) { await esClientInternal.bulk({ refresh: 'wait_for', - body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], + operations: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], }); } diff --git a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts index 25d668c16d93a..779715ea48aea 100644 --- a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts +++ b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts @@ -16,13 +16,18 @@ import type { } from '@kbn/fleet-plugin/server'; import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server'; import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; +import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions'; import type { ConfigType } from '../../common/config'; import type { TelemetryEventsSender } from './telemetry/sender'; export type OsqueryAppContextServiceStartContract = Partial< Pick< FleetStartContract, - 'agentService' | 'packageService' | 'packagePolicyService' | 'agentPolicyService' + | 'agentService' + | 'packageService' + | 'packagePolicyService' + | 'agentPolicyService' + | 'createFleetActionsClient' > > & { logger: Logger; @@ -41,6 +46,7 @@ export class OsqueryAppContextService { private packagePolicyService: PackagePolicyClient | undefined; private agentPolicyService: AgentPolicyServiceInterface | undefined; private ruleRegistryService: RuleRegistryPluginStartContract | undefined; + private fleetActionsClient: FleetActionsClientInterface | undefined; public start(dependencies: OsqueryAppContextServiceStartContract) { this.agentService = dependencies.agentService; @@ -48,6 +54,7 @@ export class OsqueryAppContextService { this.packagePolicyService = dependencies.packagePolicyService; this.agentPolicyService = dependencies.agentPolicyService; this.ruleRegistryService = dependencies.ruleRegistryService; + this.fleetActionsClient = dependencies.createFleetActionsClient?.('osquery'); } // eslint-disable-next-line @typescript-eslint/no-empty-function @@ -72,6 +79,10 @@ export class OsqueryAppContextService { public getRuleRegistryService(): RuleRegistryPluginStartContract | undefined { return this.ruleRegistryService; } + + public getFleetActionsClient(): FleetActionsClientInterface | undefined { + return this.fleetActionsClient; + } } /** diff --git a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts index 8429e7a91f68b..ea94b8783bd45 100644 --- a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts @@ -67,7 +67,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte const currentUser = await osqueryContext.security.authc.getCurrentUser(request)?.username; // eslint-disable-next-line @typescript-eslint/naming-convention - const { name, description, queries, enabled, policy_ids, shards } = request.body; + const { name, description, queries, enabled, policy_ids, shards = {} } = request.body; const conflictingEntries = await savedObjectsClient.find({ type: packSavedObjectType, filter: `${packSavedObjectType}.attributes.name: "${name}"`, diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts index 20a5f07c5d11d..b06f54900d259 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts @@ -13,7 +13,7 @@ import type { AgentsRequestOptions } from '../../../../../common/search_strategy export const buildAgentsQuery = ({ kuery, - pagination: { cursorStart, querySize }, + pagination: { cursorStart }, sort, }: AgentsRequestOptions): ISearchRequestParams => { const activeQuery = `active: true`; @@ -24,7 +24,7 @@ export const buildAgentsQuery = ({ const filterQuery = getQueryFilter({ filter }); - const dslQuery = { + return { allow_no_indices: true, index: AGENTS_INDEX, ignore_unavailable: true, @@ -39,17 +39,11 @@ export const buildAgentsQuery = ({ terms: { field: 'local_metadata.os.platform', }, - aggs: { - policies: { - terms: { - field: 'policy_id', - }, - }, - }, }, policies: { terms: { field: 'policy_id', + size: 2000, }, }, }, @@ -61,10 +55,8 @@ export const buildAgentsQuery = ({ }, }, ], - size: querySize, + size: 0, from: cursorStart, }, }; - - return dslQuery; }; diff --git a/x-pack/plugins/painless_lab/server/index.ts b/x-pack/plugins/painless_lab/server/index.ts index b94cccf8480cf..d81147ed9e395 100644 --- a/x-pack/plugins/painless_lab/server/index.ts +++ b/x-pack/plugins/painless_lab/server/index.ts @@ -5,9 +5,21 @@ * 2.0. */ -import { PluginInitializerContext } from '@kbn/core/server'; +import { offeringBasedSchema, schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext, PluginConfigDescriptor } from '@kbn/core/server'; import { PainlessLabServerPlugin } from './plugin'; +export const configSchema = schema.object({ + enabled: offeringBasedSchema({ + serverless: schema.boolean({ defaultValue: true }), + }), +}); +export type ConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; + export const plugin = (ctx: PluginInitializerContext) => { return new PainlessLabServerPlugin(ctx); }; diff --git a/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts b/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts index 4873435e81f49..d831f5f20d48c 100644 --- a/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts +++ b/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { StackTraceResponse } from '../stack_traces'; +import type { StackTraceResponse } from '@kbn/profiling-utils'; import stackTraces1x from './stacktraces_60s_1x.json'; import stackTraces5x from './stacktraces_3600s_5x.json'; diff --git a/x-pack/plugins/profiling/common/base64.ts b/x-pack/plugins/profiling/common/base64.ts deleted file mode 100644 index 0d724c142271a..0000000000000 --- a/x-pack/plugins/profiling/common/base64.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const safeBase64Decoder = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, -]; - -export const safeBase64Encoder = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234456789-_'; - -/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ -export function charCodeAt(input: string, i: number): number { - return safeBase64Decoder[input.charCodeAt(i) & 0x7f]; -} diff --git a/x-pack/plugins/profiling/common/columnar_view_model.test.ts b/x-pack/plugins/profiling/common/columnar_view_model.test.ts index c43a1aa86ba63..12c86401e9de3 100644 --- a/x-pack/plugins/profiling/common/columnar_view_model.test.ts +++ b/x-pack/plugins/profiling/common/columnar_view_model.test.ts @@ -5,13 +5,14 @@ * 2.0. */ +import { + createBaseFlameGraph, + createCalleeTree, + createFlameGraph, + decodeStackTraceResponse, +} from '@kbn/profiling-utils'; import { sum } from 'lodash'; - -import { createCalleeTree } from './callee'; import { createColumnarViewModel } from './columnar_view_model'; -import { createBaseFlameGraph, createFlameGraph } from './flamegraph'; -import { decodeStackTraceResponse } from './stack_traces'; - import { stackTraceFixtures } from './__fixtures__/stacktraces'; describe('Columnar view model operations', () => { diff --git a/x-pack/plugins/profiling/common/columnar_view_model.ts b/x-pack/plugins/profiling/common/columnar_view_model.ts index 20bf2b2761855..f553d61361b8a 100644 --- a/x-pack/plugins/profiling/common/columnar_view_model.ts +++ b/x-pack/plugins/profiling/common/columnar_view_model.ts @@ -6,8 +6,7 @@ */ import { ColumnarViewModel } from '@elastic/charts'; - -import { ElasticFlameGraph } from './flamegraph'; +import type { ElasticFlameGraph } from '@kbn/profiling-utils'; import { frameTypeToRGB, rgbToRGBA } from './frame_type_colors'; function normalize(n: number, lower: number, upper: number): number { diff --git a/x-pack/plugins/profiling/common/frame_group.ts b/x-pack/plugins/profiling/common/frame_group.ts deleted file mode 100644 index 6881b14ed98fe..0000000000000 --- a/x-pack/plugins/profiling/common/frame_group.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { takeRight } from 'lodash'; -import { StackFrameMetadata } from './profiling'; - -export type FrameGroupID = string; - -function stripLeadingSubdirs(sourceFileName: string) { - return takeRight(sourceFileName.split('/'), 2).join('/'); -} - -// createFrameGroupID is the "standard" way of grouping frames, by commonly -// shared group identifiers. -// -// For ELF-symbolized frames, group by FunctionName, ExeFileName and FileID. -// For non-symbolized frames, group by FileID and AddressOrLine. -// otherwise group by ExeFileName, SourceFilename and FunctionName. -export function createFrameGroupID( - fileID: StackFrameMetadata['FileID'], - addressOrLine: StackFrameMetadata['AddressOrLine'], - exeFilename: StackFrameMetadata['ExeFileName'], - sourceFilename: StackFrameMetadata['SourceFilename'], - functionName: StackFrameMetadata['FunctionName'] -): FrameGroupID { - if (functionName === '') { - return `empty;${fileID};${addressOrLine}`; - } - - if (sourceFilename === '') { - return `elf;${exeFilename};${functionName}`; - } - - return `full;${exeFilename};${functionName};${stripLeadingSubdirs(sourceFilename || '')}`; -} diff --git a/x-pack/plugins/profiling/common/frame_type_colors.ts b/x-pack/plugins/profiling/common/frame_type_colors.ts index 11c8cbaca9f30..81571c459bed4 100644 --- a/x-pack/plugins/profiling/common/frame_type_colors.ts +++ b/x-pack/plugins/profiling/common/frame_type_colors.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FrameType } from './profiling'; +import { FrameType } from '@kbn/profiling-utils'; /* * Helper to calculate the color of a given block to be drawn. The desirable outcomes of this are: diff --git a/x-pack/plugins/profiling/common/hash.ts b/x-pack/plugins/profiling/common/hash.ts deleted file mode 100644 index 3eab4bde871e0..0000000000000 --- a/x-pack/plugins/profiling/common/hash.ts +++ /dev/null @@ -1,79 +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. - */ - -// prettier-ignore -const lowerHex = [ - '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', - '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', - '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', - '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', - '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', - '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', - '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', - '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', - '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', - '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', - 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', - 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', - 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', - 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', - 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', - 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff', -]; - -// fnv1a64 computes a 64-bit hash of a byte array using the FNV-1a hash function [1]. -// -// Due to the lack of a native uint64 in JavaScript, we operate on 64-bit values using an array -// of 4 uint16s instead. This method follows Knuth's Algorithm M in section 4.3.1 [2] using a -// modified multiword multiplication implementation described in [3]. The modifications include: -// -// * rewrite default algorithm for the special case m = n = 4 -// * unroll loops -// * simplify expressions -// * create pre-computed lookup table for serialization to hexadecimal -// -// 1. https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function -// 2. Knuth, Donald E. The Art of Computer Programming, Volume 2, Third Edition: Seminumerical -// Algorithms. Addison-Wesley, 1998. -// 3. Warren, Henry S. Hacker's Delight. Upper Saddle River, NJ: Addison-Wesley, 2013. - -/* eslint no-bitwise: ["error", { "allow": ["^=", ">>", "&"] }] */ -export function fnv1a64(bytes: Uint8Array): string { - const n = bytes.length; - let [h0, h1, h2, h3] = [0x2325, 0x8422, 0x9ce4, 0xcbf2]; - let [t0, t1, t2, t3] = [0, 0, 0, 0]; - - for (let i = 0; i < n; i++) { - h0 ^= bytes[i]; - - t0 = h0 * 0x01b3; - t1 = h1 * 0x01b3; - t2 = h2 * 0x01b3; - t3 = h3 * 0x01b3; - - t1 += t0 >> 16; - t2 += t1 >> 16; - t2 += h0 * 0x0100; - t3 += h1 * 0x0100; - - h0 = t0 & 0xffff; - h1 = t1 & 0xffff; - h2 = t2 & 0xffff; - h3 = (t3 + (t2 >> 16)) & 0xffff; - } - - return ( - lowerHex[h3 >> 8] + - lowerHex[h3 & 0xff] + - lowerHex[h2 >> 8] + - lowerHex[h2 & 0xff] + - lowerHex[h1 >> 8] + - lowerHex[h1 & 0xff] + - lowerHex[h0 >> 8] + - lowerHex[h0 & 0xff] - ); -} diff --git a/x-pack/plugins/profiling/common/index.ts b/x-pack/plugins/profiling/common/index.ts index 2713ed3f98a13..f6266b606ee5a 100644 --- a/x-pack/plugins/profiling/common/index.ts +++ b/x-pack/plugins/profiling/common/index.ts @@ -41,18 +41,6 @@ export function timeRangeFromRequest(request: any): [number, number] { return [timeFrom, timeTo]; } -// Converts from a Map object to a Record object since Map objects are not -// serializable to JSON by default -export function fromMapToRecord(m: Map): Record { - const output: Record = {}; - - for (const [key, value] of m) { - output[key] = value; - } - - return output; -} - export const NOT_AVAILABLE_LABEL = i18n.translate('xpack.profiling.notAvailableLabel', { defaultMessage: 'N/A', }); diff --git a/x-pack/plugins/profiling/common/profiling.ts b/x-pack/plugins/profiling/common/profiling.ts deleted file mode 100644 index 22480a26f8ab2..0000000000000 --- a/x-pack/plugins/profiling/common/profiling.ts +++ /dev/null @@ -1,329 +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 { charCodeAt, safeBase64Encoder } from './base64'; - -export type StackTraceID = string; -export type StackFrameID = string; -export type FileID = string; - -export function createStackFrameID(fileID: FileID, addressOrLine: number): StackFrameID { - const buf = Buffer.alloc(24); - Buffer.from(fileID, 'base64url').copy(buf); - buf.writeBigUInt64BE(BigInt(addressOrLine), 16); - return buf.toString('base64url'); -} - -/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ -export function getFileIDFromStackFrameID(frameID: StackFrameID): FileID { - return frameID.slice(0, 21) + safeBase64Encoder[frameID.charCodeAt(21) & 0x30]; -} - -/* eslint no-bitwise: ["error", { "allow": ["<<=", "&"] }] */ -export function getAddressFromStackFrameID(frameID: StackFrameID): number { - let address = charCodeAt(frameID, 21) & 0xf; - address <<= 6; - address += charCodeAt(frameID, 22); - address <<= 6; - address += charCodeAt(frameID, 23); - address <<= 6; - address += charCodeAt(frameID, 24); - address <<= 6; - address += charCodeAt(frameID, 25); - address <<= 6; - address += charCodeAt(frameID, 26); - address <<= 6; - address += charCodeAt(frameID, 27); - address <<= 6; - address += charCodeAt(frameID, 28); - address <<= 6; - address += charCodeAt(frameID, 29); - address <<= 6; - address += charCodeAt(frameID, 30); - address <<= 6; - address += charCodeAt(frameID, 31); - return address; -} - -export enum FrameType { - Unsymbolized = 0, - Python, - PHP, - Native, - Kernel, - JVM, - Ruby, - Perl, - JavaScript, - PHPJIT, -} - -const frameTypeDescriptions = { - [FrameType.Unsymbolized]: '', - [FrameType.Python]: 'Python', - [FrameType.PHP]: 'PHP', - [FrameType.Native]: 'Native', - [FrameType.Kernel]: 'Kernel', - [FrameType.JVM]: 'JVM/Hotspot', - [FrameType.Ruby]: 'Ruby', - [FrameType.Perl]: 'Perl', - [FrameType.JavaScript]: 'JavaScript', - [FrameType.PHPJIT]: 'PHP JIT', -}; - -export function describeFrameType(ft: FrameType): string { - return frameTypeDescriptions[ft]; -} - -export interface StackTraceEvent { - StackTraceID: StackTraceID; - Count: number; -} - -export interface StackTrace { - FrameIDs: string[]; - FileIDs: string[]; - AddressOrLines: number[]; - Types: number[]; -} - -export const emptyStackTrace: StackTrace = { - FrameIDs: [], - FileIDs: [], - AddressOrLines: [], - Types: [], -}; - -export interface StackFrame { - FileName: string; - FunctionName: string; - FunctionOffset: number; - LineNumber: number; - Inline: boolean; -} - -export const emptyStackFrame: StackFrame = { - FileName: '', - FunctionName: '', - FunctionOffset: 0, - LineNumber: 0, - Inline: false, -}; - -export interface Executable { - FileName: string; -} - -export const emptyExecutable: Executable = { - FileName: '', -}; - -export interface StackFrameMetadata { - // StackTrace.FrameID - FrameID: string; - // StackTrace.FileID - FileID: FileID; - // StackTrace.Type - FrameType: FrameType; - // StackFrame.Inline - Inline: boolean; - - // StackTrace.AddressOrLine - AddressOrLine: number; - // StackFrame.FunctionName - FunctionName: string; - // StackFrame.FunctionOffset - FunctionOffset: number; - // should this be StackFrame.SourceID? - SourceID: FileID; - // StackFrame.Filename - SourceFilename: string; - // StackFrame.LineNumber - SourceLine: number; - // auto-generated - see createStackFrameMetadata - FunctionSourceLine: number; - - // Executable.FileName - ExeFileName: string; - - // unused atm due to lack of symbolization metadata - CommitHash: string; - // unused atm due to lack of symbolization metadata - SourceCodeURL: string; - // unused atm due to lack of symbolization metadata - SourcePackageHash: string; - // unused atm due to lack of symbolization metadata - SourcePackageURL: string; - // unused atm due to lack of symbolization metadata - - SamplingRate: number; -} - -export function createStackFrameMetadata( - options: Partial = {} -): StackFrameMetadata { - const metadata = {} as StackFrameMetadata; - - metadata.FrameID = options.FrameID ?? ''; - metadata.FileID = options.FileID ?? ''; - metadata.FrameType = options.FrameType ?? 0; - metadata.Inline = options.Inline ?? false; - metadata.AddressOrLine = options.AddressOrLine ?? 0; - metadata.FunctionName = options.FunctionName ?? ''; - metadata.FunctionOffset = options.FunctionOffset ?? 0; - metadata.SourceID = options.SourceID ?? ''; - metadata.SourceLine = options.SourceLine ?? 0; - metadata.ExeFileName = options.ExeFileName ?? ''; - metadata.CommitHash = options.CommitHash ?? ''; - metadata.SourceCodeURL = options.SourceCodeURL ?? ''; - metadata.SourceFilename = options.SourceFilename ?? ''; - metadata.SourcePackageHash = options.SourcePackageHash ?? ''; - metadata.SourcePackageURL = options.SourcePackageURL ?? ''; - metadata.SamplingRate = options.SamplingRate ?? 1.0; - - // Unknown/invalid offsets are currently set to 0. - // - // In this case we leave FunctionSourceLine=0 as a flag for the UI that the - // FunctionSourceLine should not be displayed. - // - // As FunctionOffset=0 could also be a legit value, this work-around needs - // a real fix. The idea for after GA is to change FunctionOffset=-1 to - // indicate unknown/invalid. - if (metadata.FunctionOffset > 0) { - metadata.FunctionSourceLine = metadata.SourceLine - metadata.FunctionOffset; - } else { - metadata.FunctionSourceLine = 0; - } - - return metadata; -} - -function checkIfStringHasParentheses(s: string) { - return /\(|\)/.test(s); -} - -function getFunctionName(metadata: StackFrameMetadata) { - return metadata.FunctionName !== '' && !checkIfStringHasParentheses(metadata.FunctionName) - ? `${metadata.FunctionName}()` - : metadata.FunctionName; -} - -function getExeFileName(metadata: StackFrameMetadata) { - if (metadata?.ExeFileName === undefined) { - return ''; - } - if (metadata.ExeFileName !== '') { - return metadata.ExeFileName; - } - return describeFrameType(metadata.FrameType); -} - -export function getCalleeLabel(metadata: StackFrameMetadata) { - if (metadata.FunctionName !== '') { - const sourceFilename = metadata.SourceFilename; - const sourceURL = sourceFilename ? sourceFilename.split('/').pop() : ''; - return `${getExeFileName(metadata)}: ${getFunctionName(metadata)} in ${sourceURL}#${ - metadata.SourceLine - }`; - } - return getExeFileName(metadata); -} - -export function getCalleeFunction(frame: StackFrameMetadata): string { - // In the best case scenario, we have the file names, source lines, - // and function names. However we need to deal with missing function or - // executable info. - const exeDisplayName = frame.ExeFileName ? frame.ExeFileName : describeFrameType(frame.FrameType); - - // When there is no function name, only use the executable name - return frame.FunctionName ? exeDisplayName + ': ' + frame.FunctionName : exeDisplayName; -} -export enum FrameSymbolStatus { - PARTIALLY_SYMBOLYZED = 'PARTIALLY_SYMBOLYZED', - NOT_SYMBOLIZED = 'NOT_SYMBOLIZED', - SYMBOLIZED = 'SYMBOLIZED', -} -export function getFrameSymbolStatus({ - sourceFilename, - sourceLine, - exeFileName, -}: { - sourceFilename: string; - sourceLine: number; - exeFileName?: string; -}) { - if (sourceFilename === '' && sourceLine === 0) { - if (exeFileName) { - return FrameSymbolStatus.PARTIALLY_SYMBOLYZED; - } - - return FrameSymbolStatus.NOT_SYMBOLIZED; - } - - return FrameSymbolStatus.SYMBOLIZED; -} - -const nativeLanguages = [FrameType.Native, FrameType.Kernel]; -export function getLanguageType({ frameType }: { frameType: FrameType }) { - return nativeLanguages.includes(frameType) ? 'NATIVE' : 'INTERPRETED'; -} - -export function getCalleeSource(frame: StackFrameMetadata): string { - const frameSymbolStatus = getFrameSymbolStatus({ - sourceFilename: frame.SourceFilename, - sourceLine: frame.SourceLine, - exeFileName: frame.ExeFileName, - }); - - switch (frameSymbolStatus) { - case FrameSymbolStatus.NOT_SYMBOLIZED: { - // If we don't have the executable filename, display - return ''; - } - case FrameSymbolStatus.PARTIALLY_SYMBOLYZED: { - // If no source line or filename available, display the executable offset - return frame.ExeFileName + '+0x' + frame.AddressOrLine.toString(16); - } - case FrameSymbolStatus.SYMBOLIZED: { - return frame.SourceFilename + (frame.SourceLine !== 0 ? `#${frame.SourceLine}` : ''); - } - } -} - -export function groupStackFrameMetadataByStackTrace( - stackTraces: Map, - stackFrames: Map, - executables: Map -): Record { - const stackTraceMap: Record = {}; - for (const [stackTraceID, trace] of stackTraces) { - const numFramesPerTrace = trace.FrameIDs.length; - const frameMetadata = new Array(numFramesPerTrace); - for (let i = 0; i < numFramesPerTrace; i++) { - const frameID = trace.FrameIDs[i]; - const fileID = trace.FileIDs[i]; - const addressOrLine = trace.AddressOrLines[i]; - const frame = stackFrames.get(frameID) ?? emptyStackFrame; - const executable = executables.get(fileID) ?? emptyExecutable; - - frameMetadata[i] = createStackFrameMetadata({ - FrameID: frameID, - FileID: fileID, - AddressOrLine: addressOrLine, - FrameType: trace.Types[i], - Inline: frame.Inline, - FunctionName: frame.FunctionName, - FunctionOffset: frame.FunctionOffset, - SourceLine: frame.LineNumber, - SourceFilename: frame.FileName, - ExeFileName: executable.FileName, - }); - } - stackTraceMap[stackTraceID] = frameMetadata; - } - return stackTraceMap; -} diff --git a/x-pack/plugins/profiling/common/run_length_encoding.test.ts b/x-pack/plugins/profiling/common/run_length_encoding.test.ts deleted file mode 100644 index 4831177075fc2..0000000000000 --- a/x-pack/plugins/profiling/common/run_length_encoding.test.ts +++ /dev/null @@ -1,170 +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 { runLengthDecode, runLengthDecodeBase64Url, runLengthEncode } from './run_length_encoding'; - -describe('Run-length encoding operations', () => { - test('run length is fully reversible', () => { - const tests: number[][] = [[], [0], [0, 1, 2, 3], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]]; - - for (const t of tests) { - expect(runLengthDecode(runLengthEncode(t))).toEqual(t); - } - }); - - test('runLengthDecode with optional parameter', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes, t.expected.length)).toEqual(t.expected); - } - }); - - test('runLengthDecode with larger output than available input', () => { - const bytes = Buffer.from([0x5, 0x0, 0x2, 0x2]); - const decoded = [0, 0, 0, 0, 0, 2, 2]; - const expected = decoded.concat(Array(decoded.length).fill(0)); - - expect(runLengthDecode(bytes, expected.length)).toEqual(expected); - }); - - test('runLengthDecode without optional parameter', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthDecode works for very long runs', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x2, 0xff, 0x0]), - expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - }, - { - bytes: Buffer.from([0xff, 0x2, 0x1, 0x2]), - expected: Array(256).fill(2), - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthEncode works for very long runs', () => { - const tests: Array<{ - numbers: number[]; - expected: Buffer; - }> = [ - { - numbers: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - expected: Buffer.from([0x5, 0x2, 0xff, 0x0]), - }, - { - numbers: Array(256).fill(2), - expected: Buffer.from([0xff, 0x2, 0x1, 0x2]), - }, - ]; - - for (const t of tests) { - expect(runLengthEncode(t.numbers)).toEqual(t.expected); - } - }); - - test('runLengthDecodeBase64Url', () => { - const tests: Array<{ - data: string; - expected: number[]; - }> = [ - { - data: 'CQM', - expected: [3, 3, 3, 3, 3, 3, 3, 3, 3], - }, - { - data: 'AQkBCAEHAQYBBQEEAQMBAgEBAQA', - expected: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], - }, - { - data: 'EgMHBA', - expected: Array(18).fill(3).concat(Array(7).fill(4)), - }, - { - data: 'CAMfBQIDEAQ', - expected: Array(8) - .fill(3) - .concat(Array(31).fill(5)) - .concat([3, 3]) - .concat(Array(16).fill(4)), - }, - ]; - - for (const t of tests) { - expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( - t.expected - ); - } - }); - - test('runLengthDecodeBase64Url with larger output than available input', () => { - const data = Buffer.from([0x5, 0x0, 0x3, 0x2]).toString('base64url'); - const decoded = [0, 0, 0, 0, 0, 2, 2, 2]; - const expected = decoded.concat(Array(decoded.length).fill(0)); - - expect(runLengthDecodeBase64Url(data, data.length, expected.length)).toEqual(expected); - }); - - test('runLengthDecodeBase64Url works for very long runs', () => { - const tests: Array<{ - data: string; - expected: number[]; - }> = [ - { - data: Buffer.from([0x5, 0x2, 0xff, 0x0]).toString('base64url'), - expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - }, - { - data: Buffer.from([0xff, 0x2, 0x1, 0x2]).toString('base64url'), - expected: Array(256).fill(2), - }, - ]; - - for (const t of tests) { - expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( - t.expected - ); - } - }); -}); diff --git a/x-pack/plugins/profiling/common/run_length_encoding.ts b/x-pack/plugins/profiling/common/run_length_encoding.ts deleted file mode 100644 index 813c46eb09d91..0000000000000 --- a/x-pack/plugins/profiling/common/run_length_encoding.ts +++ /dev/null @@ -1,199 +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 { charCodeAt } from './base64'; - -// runLengthEncode run-length encodes the input array. -// -// The input is a list of uint8s. The output is a binary stream of -// 2-byte pairs (first byte is the length and the second byte is the -// binary representation of the object) in reverse order. -// -// E.g. uint8 array [0, 0, 0, 0, 0, 2, 2, 2] is converted into the byte -// array [5, 0, 3, 2]. -export function runLengthEncode(input: number[]): Buffer { - const output: number[] = []; - - if (input.length === 0) { - return Buffer.from(output); - } - - let count = 1; - let current = input[0]; - - for (let i = 1; i < input.length; i++) { - const next = input[i]; - - if (next === current && count < 255) { - count++; - continue; - } - - output.push(count, current); - - count = 1; - current = next; - } - - output.push(count, current); - - return Buffer.from(output); -} - -function copyNumber(target: number[], value: number, offset: number, end: number) { - for (let i = offset; i < end; i++) { - target[i] = value; - } -} - -// runLengthDecode decodes a run-length encoding for the input array. -// -// The input is a binary stream of 2-byte pairs (first byte is the length and the -// second byte is the binary representation of the object). The output is a list of -// uint8s. -// -// E.g. byte array [5, 0, 3, 2] is converted into an uint8 array like -// [0, 0, 0, 0, 0, 2, 2, 2]. -export function runLengthDecode(input: Buffer, outputSize?: number): number[] { - let size; - - if (typeof outputSize === 'undefined') { - size = 0; - for (let i = 0; i < input.length; i += 2) { - size += input[i]; - } - } else { - size = outputSize; - } - - const output: number[] = new Array(size); - - let idx = 0; - for (let i = 0; i < input.length; i += 2) { - for (let j = 0; j < input[i]; j++) { - output[idx] = input[i + 1]; - idx++; - } - } - - // Due to truncation of the frame types for stacktraces longer than 255, - // the expected output size and the actual decoded size can be different. - // Ordinarily, these two values should be the same. - // - // We have decided to fill in the remainder of the output array with zeroes - // as a reasonable default. Without this step, the output array would have - // undefined values. - copyNumber(output, 0, idx, size); - - return output; -} - -// runLengthDecodeBase64Url decodes a run-length encoding for the -// base64-encoded input string. -// -// The input is a base64-encoded string. The output is a list of uint8s. -// -// E.g. string 'BQADAg' is converted into an uint8 array like -// [0, 0, 0, 0, 0, 2, 2, 2]. -// -// The motivating intent for this method is to unpack a base64-encoded -// run-length encoding without using intermediate storage. -// -// This method relies on these assumptions and details: -// - array encoded using run-length and base64 always returns string of length -// 0, 3, or 6 (mod 8) -// - since original array is composed of uint8s, we ignore Unicode codepoints -// - JavaScript bitwise operators operate on 32-bits so decoding must be done -// in 32-bit chunks - -/* eslint no-bitwise: ["error", { "allow": ["<<", ">>", ">>=", "&", "|"] }] */ -export function runLengthDecodeBase64Url(input: string, size: number, capacity: number): number[] { - const output = new Array(capacity); - const multipleOf8 = Math.floor(size / 8); - const remainder = size % 8; - - let n = 0; - let count = 0; - let value = 0; - let i = 0; - let j = 0; - - for (i = 0; i < multipleOf8 * 8; i += 8) { - n = - (charCodeAt(input, i) << 26) | - (charCodeAt(input, i + 1) << 20) | - (charCodeAt(input, i + 2) << 14) | - (charCodeAt(input, i + 3) << 8) | - (charCodeAt(input, i + 4) << 2) | - (charCodeAt(input, i + 5) >> 4); - - count = (n >> 24) & 0xff; - value = (n >> 16) & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - - n = - ((charCodeAt(input, i + 5) & 0xf) << 12) | - (charCodeAt(input, i + 6) << 6) | - charCodeAt(input, i + 7); - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - } - - if (remainder === 6) { - n = - (charCodeAt(input, i) << 26) | - (charCodeAt(input, i + 1) << 20) | - (charCodeAt(input, i + 2) << 14) | - (charCodeAt(input, i + 3) << 8) | - (charCodeAt(input, i + 4) << 2) | - (charCodeAt(input, i + 5) >> 4); - - count = (n >> 24) & 0xff; - value = (n >> 16) & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - } else if (remainder === 3) { - n = (charCodeAt(input, i) << 12) | (charCodeAt(input, i + 1) << 6) | charCodeAt(input, i + 2); - n >>= 2; - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - } - - // Due to truncation of the frame types for stacktraces longer than 255, - // the expected output size and the actual decoded size can be different. - // Ordinarily, these two values should be the same. - // - // We have decided to fill in the remainder of the output array with zeroes - // as a reasonable default. Without this step, the output array would have - // undefined values. - copyNumber(output, 0, j, capacity); - - return output; -} diff --git a/x-pack/plugins/profiling/common/topn.ts b/x-pack/plugins/profiling/common/topn.ts index 41d820729b48f..bbf04c8a39226 100644 --- a/x-pack/plugins/profiling/common/topn.ts +++ b/x-pack/plugins/profiling/common/topn.ts @@ -9,9 +9,9 @@ import { euiPaletteColorBlind } from '@elastic/eui'; import { InferSearchResponseOf } from '@kbn/es-types'; import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; -import { ProfilingESField } from './elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-utils'; +import type { StackFrameMetadata } from '@kbn/profiling-utils'; import { createUniformBucketsForTimeRange } from './histogram'; -import { StackFrameMetadata } from './profiling'; export const OTHER_BUCKET_LABEL = i18n.translate('xpack.profiling.topn.otherBucketLabel', { defaultMessage: 'Other', diff --git a/x-pack/plugins/profiling/kibana.jsonc b/x-pack/plugins/profiling/kibana.jsonc index 2985456d4ec8b..6ebaf5e99832f 100644 --- a/x-pack/plugins/profiling/kibana.jsonc +++ b/x-pack/plugins/profiling/kibana.jsonc @@ -23,12 +23,14 @@ "observabilityShared", "observabilityAIAssistant", "unifiedSearch", - "share" + "share", + "embeddable", + "profilingDataAccess" ], "requiredBundles": [ "kibanaReact", "kibanaUtils", - "observabilityAIAssistant", + "observabilityAIAssistant" ] } } diff --git a/x-pack/plugins/profiling/public/components/check_setup.tsx b/x-pack/plugins/profiling/public/components/check_setup.tsx index cec2cc6f81c96..3bfb920f25eb1 100644 --- a/x-pack/plugins/profiling/public/components/check_setup.tsx +++ b/x-pack/plugins/profiling/public/components/check_setup.tsx @@ -130,6 +130,7 @@ export function CheckSetup({ children }: { children: React.ReactElement }) { values={{ dataRetentionLink: ( @@ -152,6 +153,7 @@ export function CheckSetup({ children }: { children: React.ReactElement }) { }, button: ( { event.preventDefault(); diff --git a/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx b/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx index ae37ebf65c9f0..7a3b661cdac6d 100644 --- a/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx +++ b/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx @@ -160,7 +160,12 @@ export function FlameGraphTooltip({ style={{ background: theme.euiTheme.border.color }} /> - + {i18n.translate('xpack.profiling.flameGraphTooltip.showMoreButton', { defaultMessage: `Show more information`, diff --git a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx index 5f4bc8cfab9cc..76105907f8d52 100644 --- a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx +++ b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx @@ -19,7 +19,7 @@ import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; import { Maybe } from '@kbn/observability-plugin/common/typings'; import React, { useEffect, useMemo, useState } from 'react'; import { useUiTracker } from '@kbn/observability-shared-plugin/public'; -import { ElasticFlameGraph } from '../../../common/flamegraph'; +import type { ElasticFlameGraph } from '@kbn/profiling-utils'; import { getFlamegraphModel } from '../../utils/get_flamegraph_model'; import { FlameGraphLegend } from './flame_graph_legend'; import { FrameInformationWindow } from '../frame_information_window'; @@ -34,10 +34,9 @@ interface Props { comparisonFlamegraph?: ElasticFlameGraph; baseline?: number; comparison?: number; - showInformationWindow: boolean; - toggleShowInformationWindow: () => void; searchText?: string; onChangeSearchText?: FlameSpec['onSearchTextChange']; + isEmbedded?: boolean; } export function FlameGraph({ @@ -47,11 +46,14 @@ export function FlameGraph({ comparisonFlamegraph, baseline, comparison, - showInformationWindow, - toggleShowInformationWindow, searchText, onChangeSearchText, + isEmbedded = false, }: Props) { + const [showInformationWindow, setShowInformationWindow] = useState(false); + function toggleShowInformationWindow() { + setShowInformationWindow((prev) => !prev); + } const theme = useEuiTheme(); const trackProfilingEvent = useUiTracker({ app: 'profiling' }); @@ -157,9 +159,7 @@ export function FlameGraph({ comparisonScaleFactor={comparison} onShowMoreClick={() => { trackProfilingEvent({ metric: 'flamegraph_node_details_click' }); - if (!showInformationWindow) { - toggleShowInformationWindow(); - } + toggleShowInformationWindow(); setHighlightedVmIndex(valueIndex); }} /> @@ -194,6 +194,8 @@ export function FlameGraph({ frame={selected} totalSeconds={primaryFlamegraph?.TotalSeconds ?? 0} totalSamples={totalSamples} + showAIAssistant={!isEmbedded} + showSymbolsStatus={!isEmbedded} /> )} diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx new file mode 100644 index 0000000000000..01f27ef54e72f --- /dev/null +++ b/x-pack/plugins/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { + ContextualInsight, + Message, + MessageRole, + useObservabilityAIAssistant, +} from '@kbn/observability-ai-assistant-plugin/public'; +import React, { useMemo } from 'react'; +import { Frame } from '.'; + +interface Props { + frame?: Frame; +} + +export function FrameInformationAIAssistant({ frame }: Props) { + const aiAssistant = useObservabilityAIAssistant(); + + const promptMessages = useMemo(() => { + if (frame?.functionName && frame.exeFileName) { + const functionName = frame.functionName; + const library = frame.exeFileName; + + const now = new Date().toISOString(); + + return [ + { + '@timestamp': now, + message: { + role: MessageRole.System, + content: `You are perf-gpt, a helpful assistant for performance analysis and optimisation + of software. Answer as concisely as possible.`, + }, + }, + { + '@timestamp': now, + message: { + role: MessageRole.User, + content: `I am a software engineer. I am trying to understand what a function in a particular + software library does. + + The library is: ${library} + The function is: ${functionName} + + Your have two tasks. Your first task is to desribe what the library is and what its use cases are, and to + describe what the function does. The output format should look as follows: + + Library description: Provide a concise description of the library + Library use-cases: Provide a concise description of what the library is typically used for. + Function description: Provide a concise, technical, description of what the function does. + + Assume the function ${functionName} from the library ${library} is consuming significant CPU resources. + Your second task is to suggest ways to optimize or improve the system that involve the ${functionName} function from the + ${library} library. Types of improvements that would be useful to me are improvements that result in: + + - Higher performance so that the system runs faster or uses less CPU + - Better memory efficient so that the system uses less RAM + - Better storage efficient so that the system stores less data on disk. + - Better network I/O efficiency so that less data is sent over the network + - Better disk I/O efficiency so that less data is read and written from disk + + Make up to five suggestions. Your suggestions must meet all of the following criteria: + 1. Your suggestions should detailed, technical and include concrete examples. + 2. Your suggestions should be specific to improving performance of a system in which the ${functionName} function from + the ${library} library is consuming significant CPU. + 3. If you suggest replacing the function or library with a more efficient replacement you must suggest at least + one concrete replacement. + + If you know of fewer than five ways to improve the performance of a system in which the ${functionName} function from the + ${library} library is consuming significant CPU, then provide fewer than five suggestions. If you do not know of any + way in which to improve the performance then say "I do not know how to improve the performance of systems where + this function is consuming a significant amount of CPU". + + Do not suggest using a CPU profiler. I have already profiled my code. The profiler I used is Elastic Universal Profiler. + If there is specific information I should look for in the profiler output then tell me what information to look for + in the output of Elastic Universal Profiler. + + You must not include URLs, web addresses or websites of any kind in your output. + + If you have suggestions, the output format should look as follows: + + Here are some suggestions as to how you might optimize your system if ${functionName} in ${library} is consuming + significant CPU resources: + 1. Insert first suggestion + 2. Insert second suggestion`, + }, + }, + ]; + } + + return undefined; + }, [frame?.functionName, frame?.exeFileName]); + + return ( + <> + {aiAssistant.isEnabled() && promptMessages ? ( + + ) : null} + + ); +} diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts b/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts index 057f56f9c2f06..431933c0125ec 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts +++ b/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts @@ -6,8 +6,8 @@ */ import { i18n } from '@kbn/i18n'; +import { describeFrameType } from '@kbn/profiling-utils'; import { NOT_AVAILABLE_LABEL } from '../../../common'; -import { describeFrameType } from '../../../common/profiling'; export function getInformationRows({ fileID, diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx index d0925897877fc..856e30001becd 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx +++ b/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx @@ -6,114 +6,42 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { - ContextualInsight, - Message, - MessageRole, - useObservabilityAIAssistant, -} from '@kbn/observability-ai-assistant-plugin/public'; -import React, { useMemo } from 'react'; -import { FrameSymbolStatus, getFrameSymbolStatus } from '../../../common/profiling'; +import { FrameSymbolStatus, getFrameSymbolStatus } from '@kbn/profiling-utils'; +import React from 'react'; +import { FrameInformationAIAssistant } from './frame_information_ai_assistant'; import { FrameInformationPanel } from './frame_information_panel'; import { getImpactRows } from './get_impact_rows'; import { getInformationRows } from './get_information_rows'; import { KeyValueList } from './key_value_list'; import { MissingSymbolsCallout } from './missing_symbols_callout'; +export interface Frame { + fileID: string; + frameType: number; + exeFileName: string; + addressOrLine: number; + functionName: string; + sourceFileName: string; + sourceLine: number; + countInclusive: number; + countExclusive: number; +} + export interface Props { - frame?: { - fileID: string; - frameType: number; - exeFileName: string; - addressOrLine: number; - functionName: string; - sourceFileName: string; - sourceLine: number; - countInclusive: number; - countExclusive: number; - }; + frame?: Frame; totalSamples: number; totalSeconds: number; + showAIAssistant?: boolean; + showSymbolsStatus?: boolean; } -export function FrameInformationWindow({ frame, totalSamples, totalSeconds }: Props) { - const aiAssistant = useObservabilityAIAssistant(); - - const promptMessages = useMemo(() => { - if (frame?.functionName && frame.exeFileName) { - const functionName = frame.functionName; - const library = frame.exeFileName; - - const now = new Date().toISOString(); - - return [ - { - '@timestamp': now, - message: { - role: MessageRole.System, - content: `You are perf-gpt, a helpful assistant for performance analysis and optimisation - of software. Answer as concisely as possible.`, - }, - }, - { - '@timestamp': now, - message: { - role: MessageRole.User, - content: `I am a software engineer. I am trying to understand what a function in a particular - software library does. - - The library is: ${library} - The function is: ${functionName} - - Your have two tasks. Your first task is to desribe what the library is and what its use cases are, and to - describe what the function does. The output format should look as follows: - - Library description: Provide a concise description of the library - Library use-cases: Provide a concise description of what the library is typically used for. - Function description: Provide a concise, technical, description of what the function does. - - Assume the function ${functionName} from the library ${library} is consuming significant CPU resources. - Your second task is to suggest ways to optimize or improve the system that involve the ${functionName} function from the - ${library} library. Types of improvements that would be useful to me are improvements that result in: - - - Higher performance so that the system runs faster or uses less CPU - - Better memory efficient so that the system uses less RAM - - Better storage efficient so that the system stores less data on disk. - - Better network I/O efficiency so that less data is sent over the network - - Better disk I/O efficiency so that less data is read and written from disk - - Make up to five suggestions. Your suggestions must meet all of the following criteria: - 1. Your suggestions should detailed, technical and include concrete examples. - 2. Your suggestions should be specific to improving performance of a system in which the ${functionName} function from - the ${library} library is consuming significant CPU. - 3. If you suggest replacing the function or library with a more efficient replacement you must suggest at least - one concrete replacement. - - If you know of fewer than five ways to improve the performance of a system in which the ${functionName} function from the - ${library} library is consuming significant CPU, then provide fewer than five suggestions. If you do not know of any - way in which to improve the performance then say "I do not know how to improve the performance of systems where - this function is consuming a significant amount of CPU". - - Do not suggest using a CPU profiler. I have already profiled my code. The profiler I used is Elastic Universal Profiler. - If there is specific information I should look for in the profiler output then tell me what information to look for - in the output of Elastic Universal Profiler. - - You must not include URLs, web addresses or websites of any kind in your output. - - If you have suggestions, the output format should look as follows: - - Here are some suggestions as to how you might optimize your system if ${functionName} in ${library} is consuming - significant CPU resources: - 1. Insert first suggestion - 2. Insert second suggestion`, - }, - }, - ]; - } - - return undefined; - }, [frame?.functionName, frame?.exeFileName]); - +export function FrameInformationWindow({ + frame, + totalSamples, + totalSeconds, + showAIAssistant = true, + showSymbolsStatus = true, +}: Props) { if (!frame) { return ( @@ -167,23 +95,16 @@ export function FrameInformationWindow({ frame, totalSamples, totalSeconds }: Pr - {aiAssistant.isEnabled() && promptMessages ? ( - <> - - - - - ) : undefined} - {symbolStatus !== FrameSymbolStatus.SYMBOLIZED && ( + {showAIAssistant ? ( + + + + ) : null} + {showSymbolsStatus && symbolStatus !== FrameSymbolStatus.SYMBOLIZED ? ( - )} + ) : null} diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx index ba353d48c6995..3a6a29d2f2ed5 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx +++ b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Meta } from '@storybook/react'; import React from 'react'; -import { FrameType } from '../../../common/profiling'; +import { FrameType } from '@kbn/profiling-utils'; import { MockProfilingDependenciesStorybook } from '../contexts/profiling_dependencies/mock_profiling_dependencies_storybook'; import { MissingSymbolsCallout } from './missing_symbols_callout'; diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx index ff86ba5628c27..922fd070f3130 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx +++ b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx @@ -9,7 +9,7 @@ import { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import { FrameType, getLanguageType } from '../../../common/profiling'; +import { FrameType, getLanguageType } from '@kbn/profiling-utils'; import { PROFILING_FEEDBACK_LINK } from '../profiling_app_page_template'; import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; import { useProfilingRouter } from '../../hooks/use_profiling_router'; @@ -41,6 +41,7 @@ export function MissingSymbolsCallout({ frameType }: Props) { values={{ readMore: ( @@ -54,6 +55,7 @@ export function MissingSymbolsCallout({ frameType }: Props) { />

    - + {i18n.translate( 'xpack.profiling.frameInformationWindow.missingSymbols.interpreted.reportProblem', { defaultMessage: 'Report a problem' } diff --git a/x-pack/plugins/profiling/public/components/license_prompt/index.tsx b/x-pack/plugins/profiling/public/components/license_prompt/index.tsx index 5ec11800d176e..43af8257efea3 100644 --- a/x-pack/plugins/profiling/public/components/license_prompt/index.tsx +++ b/x-pack/plugins/profiling/public/components/license_prompt/index.tsx @@ -38,7 +38,11 @@ export function LicensePrompt() {

    } actions={[ - + {i18n.translate('xpack.profiling.invalidLicense.subscriptionManagementLink', { defaultMessage: 'Upgrade subscription', })} diff --git a/x-pack/plugins/profiling/public/components/normalization_menu/index.tsx b/x-pack/plugins/profiling/public/components/normalization_menu/index.tsx index 3d80d43bbe89c..da17c339814e4 100644 --- a/x-pack/plugins/profiling/public/components/normalization_menu/index.tsx +++ b/x-pack/plugins/profiling/public/components/normalization_menu/index.tsx @@ -95,6 +95,7 @@ export function NormalizationMenu(props: Props) { prepend={NORMALIZE_BY_LABEL} append={ {SCALE_LABEL}} > {SCALE_LABEL}} > { props.onChange(mode, options); setIsPopoverOpen(false); diff --git a/x-pack/plugins/profiling/public/components/primary_and_comparison_search_bar.tsx b/x-pack/plugins/profiling/public/components/primary_and_comparison_search_bar.tsx index 12375925bb606..e5a60a7d95149 100644 --- a/x-pack/plugins/profiling/public/components/primary_and_comparison_search_bar.tsx +++ b/x-pack/plugins/profiling/public/components/primary_and_comparison_search_bar.tsx @@ -91,6 +91,7 @@ export function PrimaryAndComparisonSearchBar() { { diff --git a/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx b/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx index a62d342b3919e..e094038e2f190 100644 --- a/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx +++ b/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx @@ -70,6 +70,7 @@ export function ProfilingAppPageTemplate({ 'data-test-subj': 'profilingPageTemplate', rightSideItems: [ { setPrivilegesWarningDismissed(true); }} diff --git a/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx b/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx index a92934df79009..f7afb09c5457a 100644 --- a/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx +++ b/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx @@ -6,7 +6,8 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; import React from 'react'; -import { getCalleeFunction, getCalleeSource, StackFrameMetadata } from '../../../common/profiling'; +import { getCalleeFunction, getCalleeSource } from '@kbn/profiling-utils'; +import type { StackFrameMetadata } from '@kbn/profiling-utils'; interface Props { frame: StackFrameMetadata; @@ -35,7 +36,7 @@ export function StackFrameSummary({ frame, onFrameClick }: Props) {
    {onFrameClick ? ( - + ) : ( diff --git a/x-pack/plugins/profiling/public/components/subchart.tsx b/x-pack/plugins/profiling/public/components/subchart.tsx index 72c869e85c3a3..9100af82d1f4b 100644 --- a/x-pack/plugins/profiling/public/components/subchart.tsx +++ b/x-pack/plugins/profiling/public/components/subchart.tsx @@ -32,7 +32,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { StackFrameMetadata } from '../../common/profiling'; +import type { StackFrameMetadata } from '@kbn/profiling-utils'; import { CountPerTime, OTHER_BUCKET_LABEL, TopNSample } from '../../common/topn'; import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_timezone_setting'; import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme'; @@ -135,7 +135,7 @@ export function SubChart({ {hasMoreFrames && !!onShowMoreClick && ( - + {i18n.translate('xpack.profiling.stackTracesView.showMoreTracesButton', { defaultMessage: 'Show more', })} @@ -185,13 +185,13 @@ export function SubChart({ {showFrames ? ( - onShowMoreClick?.()}> + onShowMoreClick?.()}> {label} ) : category === OTHER_BUCKET_LABEL ? ( {label} ) : ( - + {label} )} diff --git a/x-pack/plugins/profiling/public/components/topn_functions/function_row.tsx b/x-pack/plugins/profiling/public/components/topn_functions/function_row.tsx index 4aa4b836eac8a..0099cbc82ffec 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions/function_row.tsx +++ b/x-pack/plugins/profiling/public/components/topn_functions/function_row.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { TopNFunctionSortField } from '../../../common/functions'; +import { TopNFunctionSortField } from '@kbn/profiling-utils'; import { asCost } from '../../utils/formatters/as_cost'; import { asWeight } from '../../utils/formatters/as_weight'; import { StackFrameSummary } from '../stack_frame_summary'; diff --git a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx index ec99236cb135f..7dbc6c38fb0eb 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx +++ b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx @@ -20,7 +20,7 @@ import { last } from 'lodash'; import React, { forwardRef, Ref, useMemo, useState } from 'react'; import { GridOnScrollProps } from 'react-window'; import { useUiTracker } from '@kbn/observability-shared-plugin/public'; -import { TopNFunctions, TopNFunctionSortField } from '../../../common/functions'; +import { TopNFunctions, TopNFunctionSortField } from '@kbn/profiling-utils'; import { CPULabelWithHint } from '../cpu_label_with_hint'; import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip'; import { LabelWithHint } from '../label_with_hint'; @@ -43,6 +43,7 @@ interface Props { sortDirection: 'asc' | 'desc'; onChangeSort: (sorting: EuiDataGridSorting['columns'][0]) => void; dataTestSubj?: string; + isEmbedded?: boolean; } export const TopNFunctionsGrid = forwardRef( @@ -63,6 +64,7 @@ export const TopNFunctionsGrid = forwardRef( sortDirection, onChangeSort, dataTestSubj = 'topNFunctionsGrid', + isEmbedded = false, }: Props, ref: Ref | undefined ) => { @@ -227,6 +229,7 @@ export const TopNFunctionsGrid = forwardRef( } return ( 100 ? 100 : totalCount} + rowCount={rows.length} renderCellValue={RenderCellValue} inMemory={{ level: 'sorting' }} sorting={{ columns: [{ id: sortField, direction: sortDirection }], onSort }} @@ -280,6 +283,7 @@ export const TopNFunctionsGrid = forwardRef( // Left it empty on purpose as it is a required property on the pagination onChangeItemsPerPage: () => {}, onChangePage, + pageSizeOptions: [], }} rowHeightsOptions={{ defaultHeight: 'auto' }} toolbarVisibility={{ @@ -334,6 +338,8 @@ export const TopNFunctionsGrid = forwardRef( }} totalSeconds={totalSeconds} totalSamples={totalCount} + showAIAssistant={!isEmbedded} + showSymbolsStatus={!isEmbedded} /> )} diff --git a/x-pack/plugins/profiling/public/components/topn_functions/mock/top_n_functions.ts b/x-pack/plugins/profiling/public/components/topn_functions/mock/top_n_functions.ts index d9c8530ea2eef..b76c236e975c8 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions/mock/top_n_functions.ts +++ b/x-pack/plugins/profiling/public/components/topn_functions/mock/top_n_functions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { TopNFunctions } from '../../../../common/functions'; +import type { TopNFunctions } from '@kbn/profiling-utils'; export const data = { TotalCount: 4, diff --git a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts index f44454d255601..b6603ab7c203e 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts +++ b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts @@ -5,8 +5,7 @@ * 2.0. */ import { keyBy } from 'lodash'; -import { TopNFunctions } from '../../../common/functions'; -import { StackFrameMetadata } from '../../../common/profiling'; +import type { StackFrameMetadata, TopNFunctions } from '@kbn/profiling-utils'; import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates'; export function getColorLabel(percent: number) { @@ -71,62 +70,65 @@ export function getFunctionsRows({ ? keyBy(comparisonTopNFunctions.TopN, 'Id') : {}; - return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0).map((topN, i) => { - const comparisonRow = comparisonDataById?.[topN.Id]; + return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0) + .slice(0, 100) + .map((topN, i) => { + const comparisonRow = comparisonDataById?.[topN.Id]; + + const scaledSelfCPU = scaleValue({ + value: topN.CountExclusive, + scaleFactor: baselineScaleFactor, + }); + + const totalCPUPerc = (topN.CountInclusive / topNFunctions.TotalCount) * 100; + const selfCPUPerc = (topN.CountExclusive / topNFunctions.TotalCount) * 100; + + const impactEstimates = + totalSeconds > 0 + ? calculateImpactEstimates({ + countExclusive: topN.CountExclusive, + countInclusive: topN.CountInclusive, + totalSamples: topNFunctions.TotalCount, + totalSeconds, + }) + : undefined; + + function calculateDiff() { + if (comparisonTopNFunctions && comparisonRow) { + const comparisonScaledSelfCPU = scaleValue({ + value: comparisonRow.CountExclusive, + scaleFactor: comparisonScaleFactor, + }); + + const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU; + + return { + rank: topN.Rank - comparisonRow.Rank, + samples: scaledDiffSamples, + selfCPU: comparisonRow.CountExclusive, + totalCPU: comparisonRow.CountInclusive, + selfCPUPerc: + selfCPUPerc - + (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100, + totalCPUPerc: + totalCPUPerc - + (comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100, + }; + } + } - const scaledSelfCPU = scaleValue({ - value: topN.CountExclusive, - scaleFactor: baselineScaleFactor, + return { + rank: topN.Rank, + frame: topN.Frame, + samples: scaledSelfCPU, + selfCPUPerc, + totalCPUPerc, + selfCPU: topN.CountExclusive, + totalCPU: topN.CountInclusive, + impactEstimates, + diff: calculateDiff(), + }; }); - - const totalCPUPerc = (topN.CountInclusive / topNFunctions.TotalCount) * 100; - const selfCPUPerc = (topN.CountExclusive / topNFunctions.TotalCount) * 100; - - const impactEstimates = - totalSeconds > 0 - ? calculateImpactEstimates({ - countExclusive: topN.CountExclusive, - countInclusive: topN.CountInclusive, - totalSamples: topNFunctions.TotalCount, - totalSeconds, - }) - : undefined; - - function calculateDiff() { - if (comparisonTopNFunctions && comparisonRow) { - const comparisonScaledSelfCPU = scaleValue({ - value: comparisonRow.CountExclusive, - scaleFactor: comparisonScaleFactor, - }); - - const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU; - - return { - rank: topN.Rank - comparisonRow.Rank, - samples: scaledDiffSamples, - selfCPU: comparisonRow.CountExclusive, - totalCPU: comparisonRow.CountInclusive, - selfCPUPerc: - selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100, - totalCPUPerc: - totalCPUPerc - - (comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100, - }; - } - } - - return { - rank: topN.Rank, - frame: topN.Frame, - samples: scaledSelfCPU, - selfCPUPerc, - totalCPUPerc, - selfCPU: topN.CountExclusive, - totalCPU: topN.CountInclusive, - impactEstimates, - diff: calculateDiff(), - }; - }); } export function calculateBaseComparisonDiff({ diff --git a/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx b/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx index 960392dfa0bae..bd1387c2576a9 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx +++ b/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; +import type { TopNFunctions } from '@kbn/profiling-utils'; import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates'; -import { TopNFunctions } from '../../../common/functions'; import { asCost } from '../../utils/formatters/as_cost'; import { asWeight } from '../../utils/formatters/as_weight'; import { calculateBaseComparisonDiff } from '../topn_functions/utils'; diff --git a/x-pack/plugins/profiling/public/embeddables/async_embeddable_component.tsx b/x-pack/plugins/profiling/public/embeddables/async_embeddable_component.tsx new file mode 100644 index 0000000000000..4c8ba90e332c7 --- /dev/null +++ b/x-pack/plugins/profiling/public/embeddables/async_embeddable_component.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLoadingChart } from '@elastic/eui'; +import React from 'react'; + +interface Props { + isLoading: boolean; + children: React.ReactElement; +} + +export function AsyncEmbeddableComponent({ children, isLoading }: Props) { + return ( + <> + {isLoading ? ( +
    + +
    + ) : ( + <>{children} + )} + + ); +} diff --git a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx new file mode 100644 index 0000000000000..8e491af0afe63 --- /dev/null +++ b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Embeddable, EmbeddableOutput } from '@kbn/embeddable-plugin/public'; +import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { createFlameGraph } from '@kbn/profiling-utils'; +import { FlameGraph } from '../../components/flamegraph'; +import { EmbeddableFlamegraphEmbeddableInput } from './embeddable_flamegraph_factory'; +import { AsyncEmbeddableComponent } from '../async_embeddable_component'; + +export class EmbeddableFlamegraph extends Embeddable< + EmbeddableFlamegraphEmbeddableInput, + EmbeddableOutput +> { + readonly type = EMBEDDABLE_FLAMEGRAPH; + private _domNode?: HTMLElement; + + render(domNode: HTMLElement) { + this._domNode = domNode; + const { data, isLoading } = this.input; + const flamegraph = !isLoading && data ? createFlameGraph(data) : undefined; + render( + + <> + {flamegraph && ( + + )} + + , + domNode + ); + } + + public destroy() { + if (this._domNode) { + unmountComponentAtNode(this._domNode); + } + } + + reload() { + if (this._domNode) { + this.render(this._domNode); + } + } +} diff --git a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts new file mode 100644 index 0000000000000..568a4d20acc7b --- /dev/null +++ b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + IContainer, + EmbeddableInput, + EmbeddableFactoryDefinition, +} from '@kbn/embeddable-plugin/public'; +import type { BaseFlameGraph } from '@kbn/profiling-utils'; +import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public'; + +interface EmbeddableFlamegraphInput { + data?: BaseFlameGraph; + isLoading: boolean; +} + +export type EmbeddableFlamegraphEmbeddableInput = EmbeddableFlamegraphInput & EmbeddableInput; + +export class EmbeddableFlamegraphFactory + implements EmbeddableFactoryDefinition +{ + readonly type = EMBEDDABLE_FLAMEGRAPH; + + async isEditable() { + return false; + } + + async create(input: EmbeddableFlamegraphEmbeddableInput, parent?: IContainer) { + const { EmbeddableFlamegraph } = await import('./embeddable_flamegraph'); + return new EmbeddableFlamegraph(input, {}, parent); + } + + getDisplayName() { + return 'Universal Profiling Flamegraph'; + } +} diff --git a/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions.tsx b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions.tsx new file mode 100644 index 0000000000000..fca243f81f56a --- /dev/null +++ b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Embeddable, EmbeddableOutput } from '@kbn/embeddable-plugin/public'; +import { EMBEDDABLE_FUNCTIONS } from '@kbn/observability-shared-plugin/public'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { AsyncEmbeddableComponent } from '../async_embeddable_component'; +import { EmbeddableFunctionsEmbeddableInput } from './embeddable_functions_factory'; +import { EmbeddableFunctionsGrid } from './embeddable_functions_grid'; + +export class EmbeddableFunctions extends Embeddable< + EmbeddableFunctionsEmbeddableInput, + EmbeddableOutput +> { + readonly type = EMBEDDABLE_FUNCTIONS; + private _domNode?: HTMLElement; + + render(domNode: HTMLElement) { + this._domNode = domNode; + const { data, isLoading, rangeFrom, rangeTo } = this.input; + const totalSeconds = (rangeTo - rangeFrom) / 1000; + render( + +
    + +
    +
    , + domNode + ); + } + + public destroy() { + if (this._domNode) { + unmountComponentAtNode(this._domNode); + } + } + + reload() { + if (this._domNode) { + this.render(this._domNode); + } + } +} diff --git a/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_factory.ts b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_factory.ts new file mode 100644 index 0000000000000..8f7397b087c38 --- /dev/null +++ b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_factory.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EmbeddableFactoryDefinition, + EmbeddableInput, + IContainer, +} from '@kbn/embeddable-plugin/public'; +import { EMBEDDABLE_FUNCTIONS } from '@kbn/observability-shared-plugin/public'; +import type { TopNFunctions } from '@kbn/profiling-utils'; + +interface EmbeddableFunctionsInput { + data?: TopNFunctions; + isLoading: boolean; + rangeFrom: number; + rangeTo: number; +} + +export type EmbeddableFunctionsEmbeddableInput = EmbeddableFunctionsInput & EmbeddableInput; + +export class EmbeddableFunctionsFactory + implements EmbeddableFactoryDefinition +{ + readonly type = EMBEDDABLE_FUNCTIONS; + + async isEditable() { + return false; + } + + async create(input: EmbeddableFunctionsEmbeddableInput, parent?: IContainer) { + const { EmbeddableFunctions } = await import('./embeddable_functions'); + return new EmbeddableFunctions(input, {}, parent); + } + + getDisplayName() { + return 'Universal Profiling Functions'; + } +} diff --git a/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_grid.tsx b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_grid.tsx new file mode 100644 index 0000000000000..5579217a59d22 --- /dev/null +++ b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_grid.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TopNFunctionSortField, TopNFunctions } from '@kbn/profiling-utils'; +import React, { useState } from 'react'; +import { EuiDataGridSorting } from '@elastic/eui'; +import { TopNFunctionsGrid } from '../../components/topn_functions'; + +interface Props { + data?: TopNFunctions; + totalSeconds: number; +} + +export function EmbeddableFunctionsGrid({ data, totalSeconds }: Props) { + const [sortField, setSortField] = useState(TopNFunctionSortField.Rank); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + const [pageIndex, setPageIndex] = useState(0); + + return ( + { + setSortField(sorting.id as TopNFunctionSortField); + setSortDirection(sorting.direction); + }} + isEmbedded + /> + ); +} diff --git a/x-pack/plugins/profiling/public/embeddables/register_embeddables.ts b/x-pack/plugins/profiling/public/embeddables/register_embeddables.ts new file mode 100644 index 0000000000000..93d57a4e721a4 --- /dev/null +++ b/x-pack/plugins/profiling/public/embeddables/register_embeddables.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; +import { + EMBEDDABLE_FLAMEGRAPH, + EMBEDDABLE_FUNCTIONS, +} from '@kbn/observability-shared-plugin/public'; +import { EmbeddableFlamegraphFactory } from './flamegraph/embeddable_flamegraph_factory'; +import { EmbeddableFunctionsFactory } from './functions/embeddable_functions_factory'; + +export function registerEmbeddables(embeddable: EmbeddableSetup) { + embeddable.registerEmbeddableFactory(EMBEDDABLE_FLAMEGRAPH, new EmbeddableFlamegraphFactory()); + embeddable.registerEmbeddableFactory(EMBEDDABLE_FUNCTIONS, new EmbeddableFunctionsFactory()); +} diff --git a/x-pack/plugins/profiling/public/plugin.tsx b/x-pack/plugins/profiling/public/plugin.tsx index a59f991df58d8..5534519e93877 100644 --- a/x-pack/plugins/profiling/public/plugin.tsx +++ b/x-pack/plugins/profiling/public/plugin.tsx @@ -16,6 +16,7 @@ import { i18n } from '@kbn/i18n'; import type { NavigationSection } from '@kbn/observability-shared-plugin/public'; import type { Location } from 'history'; import { BehaviorSubject, combineLatest, from, map } from 'rxjs'; +import { registerEmbeddables } from './embeddables/register_embeddables'; import { FlamegraphLocatorDefinition } from './locators/flamegraph_locator'; import { StacktracesLocatorDefinition } from './locators/stacktraces_locator'; import { TopNFunctionsLocatorDefinition } from './locators/topn_functions_locator'; @@ -130,6 +131,8 @@ export class ProfilingPlugin implements Plugin { }, }); + registerEmbeddables(pluginsSetup.embeddable); + return { locators: { flamegraphLocator: pluginsSetup.share.url.locators.create( @@ -143,11 +146,18 @@ export class ProfilingPlugin implements Plugin { ), }, hasSetup: async () => { - const response = (await coreSetup.http.get('/internal/profiling/setup/es_resources')) as { - has_setup: boolean; - has_data: boolean; - }; - return response.has_setup; + try { + const response = (await coreSetup.http.get('/internal/profiling/setup/es_resources')) as { + has_setup: boolean; + has_data: boolean; + unauthorized: boolean; + }; + + return response.has_setup; + } catch (e) { + // If any error happens while checking return as it has not been set up + return false; + } }, }; } diff --git a/x-pack/plugins/profiling/public/routing/index.tsx b/x-pack/plugins/profiling/public/routing/index.tsx index 9e08cfbc53fec..9e40cdcb6dc07 100644 --- a/x-pack/plugins/profiling/public/routing/index.tsx +++ b/x-pack/plugins/profiling/public/routing/index.tsx @@ -9,8 +9,12 @@ import { toNumberRt } from '@kbn/io-ts-utils'; import { createRouter, Outlet } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React from 'react'; -import { TopNFunctionSortField, topNFunctionSortFieldRt } from '../../common/functions'; -import { StackTracesDisplayOption, TopNType } from '../../common/stack_traces'; +import { + StackTracesDisplayOption, + TopNType, + TopNFunctionSortField, + topNFunctionSortFieldRt, +} from '@kbn/profiling-utils'; import { indexLifecyclePhaseRt, IndexLifecyclePhaseSelectOption, diff --git a/x-pack/plugins/profiling/public/services.ts b/x-pack/plugins/profiling/public/services.ts index 1a2a684f96e15..785179b5bc65c 100644 --- a/x-pack/plugins/profiling/public/services.ts +++ b/x-pack/plugins/profiling/public/services.ts @@ -5,9 +5,13 @@ * 2.0. */ import { HttpFetchQuery } from '@kbn/core/public'; +import { + createFlameGraph, + TopNFunctions, + type BaseFlameGraph, + type ElasticFlameGraph, +} from '@kbn/profiling-utils'; import { getRoutePaths } from '../common'; -import { BaseFlameGraph, createFlameGraph, ElasticFlameGraph } from '../common/flamegraph'; -import { TopNFunctions } from '../common/functions'; import type { IndexLifecyclePhaseSelectOption, IndicesStorageDetailsAPIResponse, @@ -102,6 +106,7 @@ export function getServices(): Services { timeTo, kuery, }; + const baseFlamegraph = (await http.get(paths.Flamechart, { query })) as BaseFlameGraph; return createFlameGraph(baseFlamegraph); }, diff --git a/x-pack/plugins/profiling/public/types.ts b/x-pack/plugins/profiling/public/types.ts index 1d914c2d31a72..e583a4962dc68 100644 --- a/x-pack/plugins/profiling/public/types.ts +++ b/x-pack/plugins/profiling/public/types.ts @@ -24,6 +24,7 @@ import { ObservabilityAIAssistantPluginSetup, ObservabilityAIAssistantPluginStart, } from '@kbn/observability-ai-assistant-plugin/public'; +import { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; export interface ProfilingPluginPublicSetupDeps { observability: ObservabilityPublicSetup; @@ -34,6 +35,7 @@ export interface ProfilingPluginPublicSetupDeps { charts: ChartsPluginSetup; licensing: LicensingPluginSetup; share: SharePluginSetup; + embeddable: EmbeddableSetup; } export interface ProfilingPluginPublicStartDeps { diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts index 9e0ec087523ad..32ae0471dd8d5 100644 --- a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts @@ -8,10 +8,10 @@ import { ColumnarViewModel } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; import { compact, range, sum, uniqueId } from 'lodash'; +import { describeFrameType, FrameType } from '@kbn/profiling-utils'; +import type { ElasticFlameGraph } from '@kbn/profiling-utils'; import { createColumnarViewModel } from '../../../common/columnar_view_model'; -import { ElasticFlameGraph } from '../../../common/flamegraph'; import { FRAME_TYPE_COLOR_MAP, rgbToRGBA } from '../../../common/frame_type_colors'; -import { describeFrameType, FrameType } from '../../../common/profiling'; import { ComparisonMode } from '../../components/normalization_menu'; import { getInterpolationValue } from './get_interpolation_value'; diff --git a/x-pack/plugins/profiling/public/views/add_data_view/index.tsx b/x-pack/plugins/profiling/public/views/add_data_view/index.tsx index 6025e6685c19d..de5713f30c21a 100644 --- a/x-pack/plugins/profiling/public/views/add_data_view/index.tsx +++ b/x-pack/plugins/profiling/public/views/add_data_view/index.tsx @@ -345,6 +345,7 @@ EOF`} }), content: ( @@ -430,6 +432,7 @@ EOF`} values={{ link: ( @@ -493,6 +496,7 @@ EOF`} versionTo: 6.4, linuxLink: ( @@ -503,6 +507,7 @@ EOF`} ), debianLink: ( @@ -513,6 +518,7 @@ EOF`} ), fedoraLink: ( @@ -523,6 +529,7 @@ EOF`} ), advancedLink: ( diff --git a/x-pack/plugins/profiling/public/views/delete_data_view/index.tsx b/x-pack/plugins/profiling/public/views/delete_data_view/index.tsx index 7e07ba4a1ec86..c4f1399edafe7 100644 --- a/x-pack/plugins/profiling/public/views/delete_data_view/index.tsx +++ b/x-pack/plugins/profiling/public/views/delete_data_view/index.tsx @@ -28,6 +28,7 @@ export function DeleteDataView() { footer={
    diff --git a/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx index 78509345e8f72..c176826baa4fe 100644 --- a/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx +++ b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx @@ -5,14 +5,14 @@ * 2.0. */ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import React, { useState } from 'react'; +import React from 'react'; import { AsyncComponent } from '../../../components/async_component'; import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; import { FlameGraph } from '../../../components/flamegraph'; import { NormalizationMode, NormalizationOptions } from '../../../components/normalization_menu'; import { useProfilingParams } from '../../../hooks/use_profiling_params'; -import { useProfilingRouter } from '../../../hooks/use_profiling_router'; import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path'; +import { useProfilingRouter } from '../../../hooks/use_profiling_router'; import { useTimeRange } from '../../../hooks/use_time_range'; import { useTimeRangeAsync } from '../../../hooks/use_time_range_async'; import { DifferentialFlameGraphSearchPanel } from './differential_flame_graph_search_panel'; @@ -36,7 +36,6 @@ export function DifferentialFlameGraphsView() { } = useProfilingParams('/flamegraphs/differential'); const routePath = useProfilingRoutePath(); const profilingRouter = useProfilingRouter(); - const [showInformationWindow, setShowInformationWindow] = useState(false); const timeRange = useTimeRange({ rangeFrom, rangeTo }); @@ -55,15 +54,15 @@ export function DifferentialFlameGraphsView() { return Promise.all([ fetchElasticFlamechart({ http, - timeFrom: timeRange.inSeconds.start, - timeTo: timeRange.inSeconds.end, + timeFrom: new Date(timeRange.start).getTime(), + timeTo: new Date(timeRange.end).getTime(), kuery, }), - comparisonTimeRange.inSeconds.start && comparisonTimeRange.inSeconds.end + comparisonTimeRange.start && comparisonTimeRange.end ? fetchElasticFlamechart({ http, - timeFrom: comparisonTimeRange.inSeconds.start, - timeTo: comparisonTimeRange.inSeconds.end, + timeFrom: new Date(comparisonTimeRange.start).getTime(), + timeTo: new Date(comparisonTimeRange.end).getTime(), kuery: comparisonKuery, }) : Promise.resolve(undefined), @@ -75,13 +74,13 @@ export function DifferentialFlameGraphsView() { }); }, [ - timeRange.inSeconds.start, - timeRange.inSeconds.end, + fetchElasticFlamechart, + timeRange.start, + timeRange.end, kuery, - comparisonTimeRange.inSeconds.start, - comparisonTimeRange.inSeconds.end, + comparisonTimeRange.start, + comparisonTimeRange.end, comparisonKuery, - fetchElasticFlamechart, ] ); @@ -105,10 +104,6 @@ export function DifferentialFlameGraphsView() { const isNormalizedByTime = normalizationMode === NormalizationMode.Time; - function toggleShowInformationWindow() { - setShowInformationWindow((prev) => !prev); - } - function handleSearchTextChange(newSearchText: string) { // @ts-expect-error Code gets too complicated to satisfy TS constraints profilingRouter.push(routePath, { query: { ...query, searchText: newSearchText } }); @@ -134,8 +129,6 @@ export function DifferentialFlameGraphsView() { comparisonMode={comparisonMode} baseline={isNormalizedByTime ? baselineTime : baseline} comparison={isNormalizedByTime ? comparisonTime : comparison} - showInformationWindow={showInformationWindow} - toggleShowInformationWindow={toggleShowInformationWindow} searchText={searchText} onChangeSearchText={handleSearchTextChange} /> diff --git a/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx index 8a19cc538e14a..fae9504b00b01 100644 --- a/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx +++ b/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx @@ -5,13 +5,13 @@ * 2.0. */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { useState } from 'react'; +import React from 'react'; import { AsyncComponent } from '../../../components/async_component'; import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; import { FlameGraph } from '../../../components/flamegraph'; import { useProfilingParams } from '../../../hooks/use_profiling_params'; -import { useProfilingRouter } from '../../../hooks/use_profiling_router'; import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path'; +import { useProfilingRouter } from '../../../hooks/use_profiling_router'; import { useTimeRange } from '../../../hooks/use_time_range'; import { useTimeRangeAsync } from '../../../hooks/use_time_range_async'; @@ -31,12 +31,12 @@ export function FlameGraphView() { ({ http }) => { return fetchElasticFlamechart({ http, - timeFrom: timeRange.inSeconds.start, - timeTo: timeRange.inSeconds.end, + timeFrom: new Date(timeRange.start).getTime(), + timeTo: new Date(timeRange.end).getTime(), kuery, }); }, - [timeRange.inSeconds.start, timeRange.inSeconds.end, kuery, fetchElasticFlamechart] + [fetchElasticFlamechart, timeRange.start, timeRange.end, kuery] ); const { data } = state; @@ -45,11 +45,6 @@ export function FlameGraphView() { const profilingRouter = useProfilingRouter(); - const [showInformationWindow, setShowInformationWindow] = useState(false); - function toggleShowInformationWindow() { - setShowInformationWindow((prev) => !prev); - } - function handleSearchTextChange(newSearchText: string) { // @ts-expect-error Code gets too complicated to satisfy TS constraints profilingRouter.push(routePath, { query: { ...query, searchText: newSearchText } }); @@ -62,8 +57,6 @@ export function FlameGraphView() { diff --git a/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx b/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx index da84528b2ffc2..b853bbc5eb0d2 100644 --- a/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx +++ b/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx @@ -15,7 +15,7 @@ import { } from '@elastic/eui'; import React, { useRef } from 'react'; import { GridOnScrollProps } from 'react-window'; -import { TopNFunctionSortField } from '../../../../common/functions'; +import { TopNFunctionSortField } from '@kbn/profiling-utils'; import { AsyncComponent } from '../../../components/async_component'; import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; import { @@ -83,36 +83,31 @@ export function DifferentialTopNFunctionsView() { ({ http }) => { return fetchTopNFunctions({ http, - timeFrom: timeRange.inSeconds.start, - timeTo: timeRange.inSeconds.end, + timeFrom: new Date(timeRange.start).getTime(), + timeTo: new Date(timeRange.end).getTime(), startIndex: 0, endIndex: 100000, kuery, }); }, - [timeRange.inSeconds.start, timeRange.inSeconds.end, kuery, fetchTopNFunctions] + [fetchTopNFunctions, timeRange.start, timeRange.end, kuery] ); const comparisonState = useTimeRangeAsync( ({ http }) => { - if (!comparisonTimeRange.inSeconds.start || !comparisonTimeRange.inSeconds.end) { + if (!comparisonTimeRange.start || !comparisonTimeRange.end) { return undefined; } return fetchTopNFunctions({ http, - timeFrom: comparisonTimeRange.inSeconds.start, - timeTo: comparisonTimeRange.inSeconds.end, + timeFrom: new Date(comparisonTimeRange.start).getTime(), + timeTo: new Date(comparisonTimeRange.end).getTime(), startIndex: 0, endIndex: 100000, kuery: comparisonKuery, }); }, - [ - comparisonTimeRange.inSeconds.start, - comparisonTimeRange.inSeconds.end, - comparisonKuery, - fetchTopNFunctions, - ] + [comparisonTimeRange.start, comparisonTimeRange.end, fetchTopNFunctions, comparisonKuery] ); const routePath = useProfilingRoutePath() as diff --git a/x-pack/plugins/profiling/public/views/functions/topn/index.tsx b/x-pack/plugins/profiling/public/views/functions/topn/index.tsx index 74e23d4c172ce..b5c6d0b3a3a8b 100644 --- a/x-pack/plugins/profiling/public/views/functions/topn/index.tsx +++ b/x-pack/plugins/profiling/public/views/functions/topn/index.tsx @@ -6,7 +6,7 @@ */ import { EuiDataGridSorting, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import { TopNFunctionSortField } from '../../../../common/functions'; +import { TopNFunctionSortField } from '@kbn/profiling-utils'; import { AsyncComponent } from '../../../components/async_component'; import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; import { TopNFunctionsGrid } from '../../../components/topn_functions'; @@ -29,14 +29,14 @@ export function TopNFunctionsView() { ({ http }) => { return fetchTopNFunctions({ http, - timeFrom: timeRange.inSeconds.start, - timeTo: timeRange.inSeconds.end, + timeFrom: new Date(timeRange.start).getTime(), + timeTo: new Date(timeRange.end).getTime(), startIndex: 0, endIndex: 100000, kuery, }); }, - [timeRange.inSeconds.start, timeRange.inSeconds.end, kuery, fetchTopNFunctions] + [fetchTopNFunctions, timeRange.start, timeRange.end, kuery] ); const profilingRouter = useProfilingRouter(); diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts b/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts index 3b06074ba9db9..81f13d070ddcd 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts @@ -8,7 +8,7 @@ import { EuiPageHeaderContentProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TypeOf } from '@kbn/typed-react-router-config'; -import { TopNType } from '../../../common/stack_traces'; +import { TopNType } from '@kbn/profiling-utils'; import { StatefulProfilingRouter } from '../../hooks/use_profiling_router'; import { ProfilingRoutes } from '../../routing'; diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx b/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx index 638df8712e115..d5a2c3a851f8f 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx @@ -7,7 +7,7 @@ import { EuiButton, EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces'; +import { StackTracesDisplayOption, TopNType } from '@kbn/profiling-utils'; import { groupSamplesByCategory, TopNResponse } from '../../../common/topn'; import { useProfilingParams } from '../../hooks/use_profiling_params'; import { useProfilingRouter } from '../../hooks/use_profiling_router'; @@ -167,6 +167,7 @@ export function StackTracesView() { {(data?.charts.length ?? 0) > limit && ( { profilingRouter.push(routePath, { path, diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts index 915b24c53429d..b5c8db80c0f8a 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces'; +import { StackTracesDisplayOption, TopNType } from '@kbn/profiling-utils'; import { getTracesViewRouteParams } from './utils'; describe('stack traces view utils', () => { diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts index 74e13f249108c..7aa4829d28164 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts @@ -6,7 +6,7 @@ */ import { TypeOf } from '@kbn/typed-react-router-config'; -import { getFieldNameForTopNType, TopNType } from '../../../common/stack_traces'; +import { getFieldNameForTopNType, TopNType } from '@kbn/profiling-utils'; import { ProfilingRoutes } from '../../routing'; export function getTracesViewRouteParams({ diff --git a/x-pack/plugins/profiling/public/views/storage_explorer/distinct_probabilistic_values_warning.tsx b/x-pack/plugins/profiling/public/views/storage_explorer/distinct_probabilistic_values_warning.tsx index 1427eed54de41..4c963c79a2e7a 100644 --- a/x-pack/plugins/profiling/public/views/storage_explorer/distinct_probabilistic_values_warning.tsx +++ b/x-pack/plugins/profiling/public/views/storage_explorer/distinct_probabilistic_values_warning.tsx @@ -43,6 +43,7 @@ export function DistinctProbabilisticValuesWarning({ { - const apmPolicy = await getApmPolicy({ packagePolicyClient, soClient }); - - return { - policies: { - apm: { - profilingEnabled: !!( - apmPolicy && apmPolicy?.inputs[0].config?.['apm-server'].value?.profiling - ), + try { + const apmPolicy = await getApmPolicy({ packagePolicyClient, soClient }); + return { + policies: { + apm: { + profilingEnabled: !!( + apmPolicy && apmPolicy?.inputs[0].config?.['apm-server'].value?.profiling + ), + }, }, - }, - }; + }; + } catch (e) { + // In case apm integration is not available ignore the error and return as profiling is not enabled on the integration + return { + policies: { apm: { profilingEnabled: false } }, + }; + } } export async function removeProfilingFromApmPackagePolicy({ diff --git a/x-pack/plugins/profiling/server/routes/flamechart.ts b/x-pack/plugins/profiling/server/routes/flamechart.ts index 0f78ca0288d81..28af937dc65f2 100644 --- a/x-pack/plugins/profiling/server/routes/flamechart.ts +++ b/x-pack/plugins/profiling/server/routes/flamechart.ts @@ -6,21 +6,17 @@ */ import { schema } from '@kbn/config-schema'; - import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; -import { createCalleeTree } from '../../common/callee'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; -import { createBaseFlameGraph } from '../../common/flamegraph'; -import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; -import { createCommonFilter } from './query'; -import { searchStackTraces } from './search_stacktraces'; export function registerFlameChartSearchRoute({ router, logger, - services: { createProfilingEsClient }, + dependencies: { + start: { profilingDataAccess }, + }, }: RouteRegisterParameters) { const paths = getRoutePaths(); router.get( @@ -37,39 +33,15 @@ export function registerFlameChartSearchRoute({ }, async (context, request, response) => { const { timeFrom, timeTo, kuery } = request.query; - const targetSampleSize = 20000; // minimum number of samples to get statistically sound results try { const esClient = await getClient(context); - const profilingElasticsearchClient = createProfilingEsClient({ request, esClient }); - const filter = createCommonFilter({ - timeFrom, - timeTo, + const flamegraph = await profilingDataAccess.services.fetchFlamechartData({ + esClient, + rangeFromMs: timeFrom, + rangeToMs: timeTo, kuery, }); - const totalSeconds = timeTo - timeFrom; - - const { events, stackTraces, executables, stackFrames, totalFrames, samplingRate } = - await searchStackTraces({ - client: profilingElasticsearchClient, - filter, - sampleSize: targetSampleSize, - }); - - const flamegraph = await withProfilingSpan('create_flamegraph', async () => { - const tree = createCalleeTree( - events, - stackTraces, - stackFrames, - executables, - totalFrames, - samplingRate - ); - - const fg = createBaseFlameGraph(tree, samplingRate, totalSeconds); - - return fg; - }); return response.ok({ body: flamegraph }); } catch (error) { diff --git a/x-pack/plugins/profiling/server/routes/functions.ts b/x-pack/plugins/profiling/server/routes/functions.ts index 2a5b01855d1e9..ecd8b3ee2af0c 100644 --- a/x-pack/plugins/profiling/server/routes/functions.ts +++ b/x-pack/plugins/profiling/server/routes/functions.ts @@ -8,12 +8,8 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; -import { createTopNFunctions } from '../../common/functions'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; -import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; -import { createCommonFilter } from './query'; -import { searchStackTraces } from './search_stacktraces'; const querySchema = schema.object({ timeFrom: schema.number(), @@ -28,46 +24,28 @@ type QuerySchemaType = TypeOf; export function registerTopNFunctionsSearchRoute({ router, logger, - services: { createProfilingEsClient }, + dependencies: { + start: { profilingDataAccess }, + }, }: RouteRegisterParameters) { const paths = getRoutePaths(); router.get( { path: paths.TopNFunctions, options: { tags: ['access:profiling'] }, - validate: { - query: querySchema, - }, + validate: { query: querySchema }, }, async (context, request, response) => { try { const { timeFrom, timeTo, startIndex, endIndex, kuery }: QuerySchemaType = request.query; - const targetSampleSize = 20000; // minimum number of samples to get statistically sound results const esClient = await getClient(context); - const profilingElasticsearchClient = createProfilingEsClient({ request, esClient }); - const filter = createCommonFilter({ - timeFrom, - timeTo, + const topNFunctions = await profilingDataAccess.services.fetchFunction({ + esClient, + rangeFromMs: timeFrom, + rangeToMs: timeTo, kuery, - }); - - const { events, stackTraces, executables, stackFrames, samplingRate } = - await searchStackTraces({ - client: profilingElasticsearchClient, - filter, - sampleSize: targetSampleSize, - }); - - const topNFunctions = await withProfilingSpan('create_topn_functions', async () => { - return createTopNFunctions({ - endIndex, - events, - executables, - samplingRate, - stackFrames, - stackTraces, - startIndex, - }); + startIndex, + endIndex, }); return response.ok({ diff --git a/x-pack/plugins/profiling/server/routes/query.ts b/x-pack/plugins/profiling/server/routes/query.ts index f8a776ee68ce7..79520b0520ffb 100644 --- a/x-pack/plugins/profiling/server/routes/query.ts +++ b/x-pack/plugins/profiling/server/routes/query.ts @@ -7,7 +7,7 @@ import { QueryDslBoolQuery } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { kqlQuery } from '@kbn/observability-plugin/server'; -import { ProfilingESField } from '../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-utils'; export interface ProjectTimeQuery { bool: QueryDslBoolQuery; diff --git a/x-pack/plugins/profiling/server/routes/search_stacktraces.ts b/x-pack/plugins/profiling/server/routes/search_stacktraces.ts index b90cbaf78bbe3..84e0da898534e 100644 --- a/x-pack/plugins/profiling/server/routes/search_stacktraces.ts +++ b/x-pack/plugins/profiling/server/routes/search_stacktraces.ts @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { decodeStackTraceResponse } from '../../common/stack_traces'; +import { decodeStackTraceResponse } from '@kbn/profiling-utils'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { ProjectTimeQuery } from './query'; diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts deleted file mode 100644 index 91b55312928c3..0000000000000 --- a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts +++ /dev/null @@ -1,88 +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 { createStackFrameID, StackTrace } from '../../common/profiling'; -import { runLengthEncode } from '../../common/run_length_encoding'; -import { decodeStackTrace, EncodedStackTrace } from './stacktrace'; - -enum fileID { - A = 'aQpJmTLWydNvOapSFZOwKg', - B = 'hz_u-HGyrN6qeIk6UIJeCA', - C = 'AJ8qrcXSoJbl_haPhlc4og', - D = 'lHZiv7a58px6Gumcpo-6yA', - E = 'fkbxUTZgljnk71ZMnqJnyA', - F = 'gnEsgxvvEODj6iFYMQWYlA', -} - -enum addressOrLine { - A = 515512, - B = 26278522, - C = 6712518, - D = 105806025, - E = 111, - F = 106182663, - G = 100965370, -} - -const frameID: Record = { - A: createStackFrameID(fileID.A, addressOrLine.A), - B: createStackFrameID(fileID.B, addressOrLine.B), - C: createStackFrameID(fileID.C, addressOrLine.C), - D: createStackFrameID(fileID.D, addressOrLine.D), - E: createStackFrameID(fileID.E, addressOrLine.E), - F: createStackFrameID(fileID.F, addressOrLine.F), - G: createStackFrameID(fileID.F, addressOrLine.G), -}; - -const frameTypeA = [0, 0, 0]; -const frameTypeB = [8, 8, 8, 8]; - -describe('Stack trace operations', () => { - test('decodeStackTrace', () => { - const tests: Array<{ - original: EncodedStackTrace; - expected: StackTrace; - }> = [ - { - original: { - Stacktrace: { - frame: { - ids: frameID.A + frameID.B + frameID.C, - types: runLengthEncode(frameTypeA).toString('base64url'), - }, - }, - } as EncodedStackTrace, - expected: { - FrameIDs: [frameID.A, frameID.B, frameID.C], - FileIDs: [fileID.A, fileID.B, fileID.C], - AddressOrLines: [addressOrLine.A, addressOrLine.B, addressOrLine.C], - Types: frameTypeA, - } as StackTrace, - }, - { - original: { - Stacktrace: { - frame: { - ids: frameID.D + frameID.E + frameID.F + frameID.G, - types: runLengthEncode(frameTypeB).toString('base64url'), - }, - }, - } as EncodedStackTrace, - expected: { - FrameIDs: [frameID.D, frameID.E, frameID.F, frameID.G], - FileIDs: [fileID.D, fileID.E, fileID.F, fileID.F], - AddressOrLines: [addressOrLine.D, addressOrLine.E, addressOrLine.F, addressOrLine.G], - Types: frameTypeB, - } as StackTrace, - }, - ]; - - for (const t of tests) { - expect(decodeStackTrace(t.original)).toEqual(t.expected); - } - }); -}); diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts deleted file mode 100644 index 0777d8c741161..0000000000000 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DedotObject, ProfilingESField } from '../../common/elasticsearch'; -import { - getAddressFromStackFrameID, - getFileIDFromStackFrameID, - StackTrace, -} from '../../common/profiling'; -import { runLengthDecodeBase64Url } from '../../common/run_length_encoding'; - -const BASE64_FRAME_ID_LENGTH = 32; - -export type EncodedStackTrace = DedotObject<{ - // This field is a base64-encoded byte string. The string represents a - // serialized list of frame IDs in which the order of frames are - // reversed to allow for prefix compression (leaf frame last). Each - // frame ID is composed of two concatenated values: a 16-byte file ID - // and an 8-byte address or line number (depending on the context of - // the downstream reader). - // - // Frame ID #1 Frame ID #2 - // +----------------+--------+----------------+--------+---- - // | File ID | Addr | File ID | Addr | - // +----------------+--------+----------------+--------+---- - [ProfilingESField.StacktraceFrameIDs]: string; - - // This field is a run-length encoding of a list of uint8s. The order is - // reversed from the original input. - [ProfilingESField.StacktraceFrameTypes]: string; -}>; - -// decodeStackTrace unpacks an encoded stack trace from Elasticsearch -export function decodeStackTrace(input: EncodedStackTrace): StackTrace { - const inputFrameIDs = input.Stacktrace.frame.ids; - const inputFrameTypes = input.Stacktrace.frame.types; - const countsFrameIDs = inputFrameIDs.length / BASE64_FRAME_ID_LENGTH; - - const fileIDs: string[] = new Array(countsFrameIDs); - const frameIDs: string[] = new Array(countsFrameIDs); - const addressOrLines: number[] = new Array(countsFrameIDs); - - // Step 1: Convert the base64-encoded frameID list into two separate - // lists (frame IDs and file IDs), both of which are also base64-encoded. - // - // To get the frame ID, we grab the next 32 bytes. - // - // To get the file ID, we grab the first 22 bytes of the frame ID. - // However, since the file ID is base64-encoded using 21.33 bytes - // (16 * 4 / 3), then the 22 bytes have an extra 4 bits from the - // address (see diagram in definition of EncodedStackTrace). - for (let i = 0, pos = 0; i < countsFrameIDs; i++, pos += BASE64_FRAME_ID_LENGTH) { - const frameID = inputFrameIDs.slice(pos, pos + BASE64_FRAME_ID_LENGTH); - frameIDs[i] = frameID; - fileIDs[i] = getFileIDFromStackFrameID(frameID); - addressOrLines[i] = getAddressFromStackFrameID(frameID); - } - - // Step 2: Convert the run-length byte encoding into a list of uint8s. - const typeIDs = runLengthDecodeBase64Url(inputFrameTypes, inputFrameTypes.length, countsFrameIDs); - - return { - AddressOrLines: addressOrLines, - FileIDs: fileIDs, - FrameIDs: frameIDs, - Types: typeIDs, - } as StackTrace; -} diff --git a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts index 57ec31e6bcfbd..88e6a365c80da 100644 --- a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts +++ b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts @@ -6,7 +6,7 @@ */ import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; -import { ProfilingESField } from '../../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-utils'; import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../../common/histogram'; import { IndexLifecyclePhaseSelectOption, diff --git a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts index 65a66071431f1..565fc76210516 100644 --- a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts +++ b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts @@ -6,7 +6,7 @@ */ import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; -import { ProfilingESField } from '../../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-utils'; import { IndexLifecyclePhaseSelectOption, indexLifeCyclePhaseToDataTier, diff --git a/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts b/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts index 6e3216dc0fd43..a2d2c7b2abcd1 100644 --- a/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts +++ b/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts @@ -6,7 +6,7 @@ */ import { kqlQuery } from '@kbn/observability-plugin/server'; import { keyBy } from 'lodash'; -import { ProfilingESField } from '../../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-utils'; import { ProfilingESClient } from '../../utils/create_profiling_es_client'; interface HostDetails { diff --git a/x-pack/plugins/profiling/server/routes/topn.test.ts b/x-pack/plugins/profiling/server/routes/topn.test.ts index 1276c5dcc94dd..9e28b87b51679 100644 --- a/x-pack/plugins/profiling/server/routes/topn.test.ts +++ b/x-pack/plugins/profiling/server/routes/topn.test.ts @@ -8,7 +8,7 @@ import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/types'; import { coreMock } from '@kbn/core/server/mocks'; import { loggerMock } from '@kbn/logging-mocks'; -import { ProfilingESField } from '../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-utils'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { topNElasticSearchQuery } from './topn'; diff --git a/x-pack/plugins/profiling/server/routes/topn.ts b/x-pack/plugins/profiling/server/routes/topn.ts index ac4621cf0ed14..7790bad3b23d5 100644 --- a/x-pack/plugins/profiling/server/routes/topn.ts +++ b/x-pack/plugins/profiling/server/routes/topn.ts @@ -7,15 +7,18 @@ import { schema } from '@kbn/config-schema'; import type { Logger } from '@kbn/core/server'; +import { + getFieldNameForTopNType, + groupStackFrameMetadataByStackTrace, + ProfilingESField, + TopNType, +} from '@kbn/profiling-utils'; import { RouteRegisterParameters } from '.'; import { getRoutePaths, INDEX_EVENTS } from '../../common'; -import { ProfilingESField } from '../../common/elasticsearch'; import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../common/histogram'; -import { groupStackFrameMetadataByStackTrace } from '../../common/profiling'; -import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces'; import { createTopNSamples, getTopNAggregationRequest, TopNResponse } from '../../common/topn'; -import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; +import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; import { findDownsampledIndex } from './downsampling'; diff --git a/x-pack/plugins/profiling/server/types.ts b/x-pack/plugins/profiling/server/types.ts index e1542e6939896..6ee94e238effa 100644 --- a/x-pack/plugins/profiling/server/types.ts +++ b/x-pack/plugins/profiling/server/types.ts @@ -12,6 +12,10 @@ import { SpacesPluginStart, SpacesPluginSetup } from '@kbn/spaces-plugin/server' import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server'; import { FleetSetupContract, FleetStartContract } from '@kbn/fleet-plugin/server'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import { + ProfilingDataAccessPluginSetup, + ProfilingDataAccessPluginStart, +} from '@kbn/profiling-data-access-plugin/server'; export interface ProfilingPluginSetupDeps { observability: ObservabilityPluginSetup; @@ -20,6 +24,7 @@ export interface ProfilingPluginSetupDeps { fleet: FleetSetupContract; spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; + profilingDataAccess: ProfilingDataAccessPluginSetup; } export interface ProfilingPluginStartDeps { @@ -28,6 +33,7 @@ export interface ProfilingPluginStartDeps { cloud: CloudStart; fleet: FleetStartContract; spaces?: SpacesPluginStart; + profilingDataAccess: ProfilingDataAccessPluginStart; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts index 8fa67eb23ecfe..dc62411f2fb8f 100644 --- a/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts @@ -10,8 +10,8 @@ import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; import type { KibanaRequest } from '@kbn/core/server'; import { unwrapEsResponse } from '@kbn/observability-plugin/server'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { ProfilingStatusResponse, StackTraceResponse } from '@kbn/profiling-utils'; import { withProfilingSpan } from './with_profiling_span'; -import { ProfilingStatusResponse, StackTraceResponse } from '../../common/stack_traces'; export function cancelEsRequestOnAbort>( promise: T, diff --git a/x-pack/plugins/profiling/tsconfig.json b/x-pack/plugins/profiling/tsconfig.json index 980249959b215..b5de94dc4f559 100644 --- a/x-pack/plugins/profiling/tsconfig.json +++ b/x-pack/plugins/profiling/tsconfig.json @@ -47,7 +47,10 @@ "@kbn/licensing-plugin", "@kbn/utility-types", "@kbn/usage-collection-plugin", - "@kbn/observability-ai-assistant-plugin" + "@kbn/observability-ai-assistant-plugin", + "@kbn/profiling-data-access-plugin", + "@kbn/embeddable-plugin", + "@kbn/profiling-utils" // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json diff --git a/x-pack/plugins/profiling_data_access/.i18nrc.json b/x-pack/plugins/profiling_data_access/.i18nrc.json new file mode 100644 index 0000000000000..de8ac3249413e --- /dev/null +++ b/x-pack/plugins/profiling_data_access/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "profiling", + "paths": { + "profiling": "." + }, + "translations": [] +} diff --git a/x-pack/plugins/profiling_data_access/jest.config.js b/x-pack/plugins/profiling_data_access/jest.config.js new file mode 100644 index 0000000000000..c87c047a5ea73 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/jest.config.js @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const path = require('path'); + +module.exports = { + preset: '@kbn/test', + rootDir: path.resolve(__dirname, '../../..'), + roots: ['/x-pack/plugins/profiling_data_access'], +}; diff --git a/x-pack/plugins/profiling_data_access/kibana.jsonc b/x-pack/plugins/profiling_data_access/kibana.jsonc new file mode 100644 index 0000000000000..3f0254ba92647 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/kibana.jsonc @@ -0,0 +1,14 @@ +{ + "type": "plugin", + "id": "@kbn/profiling-data-access-plugin", + "owner": "@elastic/profiling-ui", + "plugin": { + "id": "profilingDataAccess", + "server": true, + "browser": false, + "configPath": ["xpack", "profiling"], + "requiredPlugins": ["data"], + "optionalPlugins": [], + "requiredBundles": [] + } +} diff --git a/x-pack/plugins/profiling_data_access/server/index.ts b/x-pack/plugins/profiling_data_access/server/index.ts new file mode 100644 index 0000000000000..d979d22f4a011 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema, TypeOf } from '@kbn/config-schema'; + +import type { PluginInitializerContext } from '@kbn/core/server'; +import { ProfilingDataAccessPlugin } from './plugin'; +import type { ProfilingDataAccessPluginSetup, ProfilingDataAccessPluginStart } from './plugin'; + +const configSchema = schema.object({ + elasticsearch: schema.conditional( + schema.contextRef('dist'), + schema.literal(true), + schema.never(), + schema.maybe( + schema.object({ + hosts: schema.string(), + username: schema.string(), + password: schema.string(), + }) + ) + ), +}); + +export type ProfilingConfig = TypeOf; + +export type { ProfilingDataAccessPluginSetup, ProfilingDataAccessPluginStart }; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ProfilingDataAccessPlugin(initializerContext); +} diff --git a/x-pack/plugins/profiling_data_access/server/plugin.ts b/x-pack/plugins/profiling_data_access/server/plugin.ts new file mode 100644 index 0000000000000..23d72f39caee6 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/plugin.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { ProfilingConfig } from '.'; +import { registerServices } from './services/register_services'; +import { createProfilingEsClient } from './utils/create_profiling_es_client'; + +export type ProfilingDataAccessPluginSetup = ReturnType; +export type ProfilingDataAccessPluginStart = ReturnType; + +export class ProfilingDataAccessPlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + public setup(core: CoreSetup) {} + + public start(core: CoreStart) { + const config = this.initializerContext.config.get(); + + const profilingSpecificEsClient = config.elasticsearch + ? core.elasticsearch.createClient('profiling', { + hosts: [config.elasticsearch.hosts], + username: config.elasticsearch.username, + password: config.elasticsearch.password, + }) + : undefined; + + const services = registerServices({ + createProfilingEsClient: ({ esClient: defaultEsClient, useDefaultAuth = false }) => { + const esClient = + profilingSpecificEsClient && !useDefaultAuth + ? profilingSpecificEsClient.asInternalUser + : defaultEsClient; + + return createProfilingEsClient({ esClient }); + }, + }); + + // called after all plugins are set up + return { + services, + }; + } +} diff --git a/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts new file mode 100644 index 0000000000000..c83063bcf7e0b --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { createBaseFlameGraph, createCalleeTree } from '@kbn/profiling-utils'; +import { withProfilingSpan } from '../../utils/with_profiling_span'; +import { RegisterServicesParams } from '../register_services'; +import { searchStackTraces } from '../search_stack_traces'; + +export interface FetchFlamechartParams { + esClient: ElasticsearchClient; + rangeFromMs: number; + rangeToMs: number; + kuery: string; +} + +export function createFetchFlamechart({ createProfilingEsClient }: RegisterServicesParams) { + return async ({ esClient, rangeFromMs, rangeToMs, kuery }: FetchFlamechartParams) => { + const rangeFromSecs = rangeFromMs / 1000; + const rangeToSecs = rangeToMs / 1000; + + const profilingEsClient = createProfilingEsClient({ esClient }); + const targetSampleSize = 20000; // minimum number of samples to get statistically sound results + + const totalSeconds = rangeToSecs - rangeFromSecs; + + const { events, stackTraces, executables, stackFrames, totalFrames, samplingRate } = + await searchStackTraces({ + client: profilingEsClient, + rangeFrom: rangeFromSecs, + rangeTo: rangeToSecs, + kuery, + sampleSize: targetSampleSize, + }); + + const flamegraph = await withProfilingSpan('create_flamegraph', async () => { + const tree = createCalleeTree( + events, + stackTraces, + stackFrames, + executables, + totalFrames, + samplingRate + ); + + return createBaseFlameGraph(tree, samplingRate, totalSeconds); + }); + + return flamegraph; + }; +} diff --git a/x-pack/plugins/profiling_data_access/server/services/functions/index.ts b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts new file mode 100644 index 0000000000000..ed50b368ee927 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { createTopNFunctions } from '@kbn/profiling-utils'; +import { withProfilingSpan } from '../../utils/with_profiling_span'; +import { RegisterServicesParams } from '../register_services'; +import { searchStackTraces } from '../search_stack_traces'; + +export interface FetchFunctionsParams { + esClient: ElasticsearchClient; + rangeFromMs: number; + rangeToMs: number; + kuery: string; + startIndex: number; + endIndex: number; +} + +const targetSampleSize = 20000; // minimum number of samples to get statistically sound results + +export function createFetchFunctions({ createProfilingEsClient }: RegisterServicesParams) { + return async ({ + esClient, + rangeFromMs, + rangeToMs, + kuery, + startIndex, + endIndex, + }: FetchFunctionsParams) => { + const rangeFromSecs = rangeFromMs / 1000; + const rangeToSecs = rangeToMs / 1000; + + const profilingEsClient = createProfilingEsClient({ esClient }); + + const { events, stackTraces, executables, stackFrames, samplingRate } = await searchStackTraces( + { + client: profilingEsClient, + rangeFrom: rangeFromSecs, + rangeTo: rangeToSecs, + kuery, + sampleSize: targetSampleSize, + } + ); + + const topNFunctions = await withProfilingSpan('create_topn_functions', async () => { + return createTopNFunctions({ + endIndex, + events, + executables, + samplingRate, + stackFrames, + stackTraces, + startIndex, + }); + }); + + return topNFunctions; + }; +} diff --git a/x-pack/plugins/profiling_data_access/server/services/register_services.ts b/x-pack/plugins/profiling_data_access/server/services/register_services.ts new file mode 100644 index 0000000000000..371e998d3398d --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/register_services.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { createFetchFlamechart } from './fetch_flamechart'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; +import { createFetchFunctions } from './functions'; + +export interface RegisterServicesParams { + createProfilingEsClient: (params: { + esClient: ElasticsearchClient; + useDefaultAuth?: boolean; + }) => ProfilingESClient; +} + +export function registerServices(params: RegisterServicesParams) { + return { + fetchFlamechartData: createFetchFlamechart(params), + fetchFunction: createFetchFunctions(params), + }; +} diff --git a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts new file mode 100644 index 0000000000000..3585086bff69b --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { decodeStackTraceResponse } from '@kbn/profiling-utils'; +import { ProfilingESClient } from '../../utils/create_profiling_es_client'; +import { kqlQuery } from '../../utils/query'; + +export async function searchStackTraces({ + client, + sampleSize, + rangeFrom, + rangeTo, + kuery, +}: { + client: ProfilingESClient; + sampleSize: number; + rangeFrom: number; + rangeTo: number; + kuery: string; +}) { + const response = await client.profilingStacktraces({ + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + ['@timestamp']: { + gte: String(rangeFrom), + lt: String(rangeTo), + format: 'epoch_second', + boost: 1.0, + }, + }, + }, + ], + }, + }, + sampleSize, + }); + + return decodeStackTraceResponse(response); +} diff --git a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts new file mode 100644 index 0000000000000..4b7f785bb6f3f --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts @@ -0,0 +1,94 @@ +/* + * Copyright 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ElasticsearchClient } from '@kbn/core/server'; +import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; +import type { ProfilingStatusResponse, StackTraceResponse } from '@kbn/profiling-utils'; +import { unwrapEsResponse } from './unwrap_es_response'; +import { withProfilingSpan } from './with_profiling_span'; + +export interface ProfilingESClient { + search( + operationName: string, + searchRequest: TSearchRequest + ): Promise>; + profilingStacktraces({}: { + query: QueryDslQueryContainer; + sampleSize: number; + }): Promise; + profilingStatus(): Promise; + getEsClient(): ElasticsearchClient; +} + +export function createProfilingEsClient({ + esClient, +}: { + esClient: ElasticsearchClient; +}): ProfilingESClient { + return { + search( + operationName: string, + searchRequest: TSearchRequest + ): Promise> { + const controller = new AbortController(); + + const promise = withProfilingSpan(operationName, () => { + return esClient.search(searchRequest, { + signal: controller.signal, + meta: true, + }) as unknown as Promise<{ + body: InferSearchResponseOf; + }>; + }); + + return unwrapEsResponse(promise); + }, + profilingStacktraces({ query, sampleSize }) { + const controller = new AbortController(); + const promise = withProfilingSpan('_profiling/stacktraces', () => { + return esClient.transport.request( + { + method: 'POST', + path: encodeURI('/_profiling/stacktraces'), + body: { + query, + sample_size: sampleSize, + }, + }, + { + signal: controller.signal, + meta: true, + } + ); + }); + + return unwrapEsResponse(promise) as Promise; + }, + profilingStatus() { + const controller = new AbortController(); + + const promise = withProfilingSpan('_profiling/status', () => { + return esClient.transport.request( + { + method: 'GET', + path: encodeURI('/_profiling/status'), + }, + { + signal: controller.signal, + meta: true, + } + ); + }); + + return unwrapEsResponse(promise) as Promise; + }, + getEsClient() { + return esClient; + }, + }; +} diff --git a/x-pack/plugins/profiling_data_access/server/utils/query.ts b/x-pack/plugins/profiling_data_access/server/utils/query.ts new file mode 100644 index 0000000000000..106e95c40a7f7 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/utils/query.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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; + +export function kqlQuery(kql?: string): estypes.QueryDslQueryContainer[] { + if (!kql) { + return []; + } + + const ast = fromKueryExpression(kql); + return [toElasticsearchQuery(ast)]; +} diff --git a/x-pack/plugins/profiling_data_access/server/utils/unwrap_es_response.ts b/x-pack/plugins/profiling_data_access/server/utils/unwrap_es_response.ts new file mode 100644 index 0000000000000..1448a0fe027c8 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/utils/unwrap_es_response.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { errors } from '@elastic/elasticsearch'; +import { inspect } from 'util'; + +export class WrappedElasticsearchClientError extends Error { + originalError: errors.ElasticsearchClientError; + constructor(originalError: errors.ElasticsearchClientError) { + super(originalError.message); + + const stack = this.stack; + + this.originalError = originalError; + + if (originalError instanceof errors.ResponseError) { + // make sure ES response body is visible when logged to the console + // @ts-expect-error + this.stack = { + valueOf() { + const value = stack?.valueOf() ?? ''; + return value; + }, + toString() { + const value = + stack?.toString() + + `\nResponse: ${inspect(originalError.meta.body, { depth: null })}\n`; + return value; + }, + }; + } + } +} + +export function unwrapEsResponse>( + responsePromise: T +): Promise['body']> { + return responsePromise + .then((res) => res.body) + .catch((err) => { + // make sure stacktrace is relative to where client was called + throw new WrappedElasticsearchClientError(err); + }); +} diff --git a/x-pack/plugins/profiling_data_access/server/utils/with_profiling_span.ts b/x-pack/plugins/profiling_data_access/server/utils/with_profiling_span.ts new file mode 100644 index 0000000000000..6d366799780e7 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/utils/with_profiling_span.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { withSpan, SpanOptions, parseSpanOptions } from '@kbn/apm-utils'; + +export function withProfilingSpan( + optionsOrName: SpanOptions | string, + cb: () => Promise +): Promise { + const options = parseSpanOptions(optionsOrName); + + const optionsWithDefaults = { + ...(options.intercept ? {} : { type: 'plugin:profiling' }), + ...options, + labels: { + plugin: 'profiling', + ...options.labels, + }, + }; + + return withSpan(optionsWithDefaults, cb); +} diff --git a/x-pack/plugins/profiling_data_access/tsconfig.json b/x-pack/plugins/profiling_data_access/tsconfig.json new file mode 100644 index 0000000000000..9075978c5a927 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "server/**/*", + "jest.config.js" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/config-schema", + "@kbn/core", + "@kbn/es-query", + "@kbn/es-types", + "@kbn/apm-utils", + "@kbn/profiling-utils" + ] +} diff --git a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx index fc1b0f25d9c01..2d24736d7e13b 100644 --- a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx +++ b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx @@ -70,6 +70,7 @@ export const mockConfig = { roles: { enabled: false, }, + statefulSettings: { enabled: true }, }; const validCheck = { diff --git a/x-pack/plugins/reporting/public/management/mount_management_section.tsx b/x-pack/plugins/reporting/public/management/mount_management_section.tsx index 604885e8be360..4c994a5bb61b4 100644 --- a/x-pack/plugins/reporting/public/management/mount_management_section.tsx +++ b/x-pack/plugins/reporting/public/management/mount_management_section.tsx @@ -11,6 +11,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { Observable } from 'rxjs'; import { CoreSetup, CoreStart } from '@kbn/core/public'; import { ILicense } from '@kbn/licensing-plugin/public'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { ReportingAPIClient, InternalApiClientProvider } from '../lib/reporting_api_client'; import { IlmPolicyStatusContextProvider } from '../lib/ilm_policy_status_context'; import { ClientConfigType } from '../plugin'; @@ -28,29 +29,31 @@ export async function mountManagementSection( params: ManagementAppMountParams ) { render( - - - - - - - - - , + + + + + + + + + + + , params.element ); diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx index 74753437f54ce..49607c6f38710 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -109,7 +109,7 @@ class ReportListingUi extends Component { } /> - + {config.statefulSettings.enabled ? : null}
    {this.renderTable()}
    diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index 912f519d60002..46e425b54239d 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -47,6 +47,7 @@ export interface ClientConfigType { poll: { jobsRefresh: { interval: number; intervalErrorMultiplier: number } }; roles: { enabled: boolean }; export_types: { pdf: { enabled: boolean }; png: { enabled: boolean }; csv: { enabled: boolean } }; + statefulSettings: { enabled: boolean }; } function getStored(): JobId[] { diff --git a/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap b/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap index b290215981024..518c7bb261426 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap +++ b/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap @@ -94,17 +94,17 @@ exports[`ScreenCapturePanelContent properly renders a view with "canvas" layout style="width: auto; margin-left: -16px; margin-right: -16px;" />
    -
    +
    +
    -
    -
    + + Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. + +

    +
    +
    +
    +

    -

    - - Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. - -

    -
    -
    +
    -
    +
    +
    -
    -
    + + Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. + +

    +
    +
    +
    +

    -

    - - Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. - -

    -
    -
    +
    -
    +
    +
    -
    -
    + + Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. + +

    +
    +
    +
    +

    -

    - - Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. - -

    -
    -
    +
    - - - - - - +
    ); } diff --git a/x-pack/plugins/screenshotting/public/plugin.tsx b/x-pack/plugins/screenshotting/public/plugin.tsx index 68707cc16ed72..f50291eff46fd 100755 --- a/x-pack/plugins/screenshotting/public/plugin.tsx +++ b/x-pack/plugins/screenshotting/public/plugin.tsx @@ -21,26 +21,24 @@ interface SetupDeps { export class ScreenshottingPlugin implements Plugin { setup({ application }: CoreSetup, { screenshotMode }: SetupDeps) { - if (!screenshotMode.isScreenshotMode()) { - return; - } - - application.register({ - id: SCREENSHOTTING_APP_ID, - title: 'Screenshotting Expressions Renderer', - navLinkStatus: AppNavLinkStatus.hidden, - chromeless: true, + if (screenshotMode.isScreenshotMode()) { + application.register({ + id: SCREENSHOTTING_APP_ID, + title: 'Screenshotting Expressions Renderer', + navLinkStatus: AppNavLinkStatus.hidden, + chromeless: true, - mount: async ({ element }: AppMountParameters) => { - ReactDOM.render( - - - , - element - ); - return () => ReactDOM.unmountComponentAtNode(element); - }, - }); + mount: async ({ element }: AppMountParameters) => { + ReactDOM.render( + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }, + }); + } } start() {} diff --git a/x-pack/plugins/screenshotting/server/screenshots/event_logger/index.ts b/x-pack/plugins/screenshotting/server/screenshots/event_logger/index.ts index 96fcb092155fb..d067b99750d19 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/event_logger/index.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/event_logger/index.ts @@ -174,8 +174,8 @@ export class EventLogger { public startTransaction( action: Transactions.SCREENSHOTTING | Transactions.PDF ): TransactionEndFn { - this.transactions[action] = apm.startTransaction(action, PLUGIN_ID); - const transaction = this.transactions[action]; + const transaction = apm.startTransaction(action, PLUGIN_ID); + this.transactions[action] = transaction; this.startTiming(action); this.logEvent(action, 'start', { action }); @@ -184,10 +184,10 @@ export class EventLogger { Object.entries(labels).forEach(([label]) => { const labelField = label as keyof SimpleEvent; const labelValue = labels[labelField]; - transaction?.setLabel(label, labelValue, false); + transaction.setLabel(label, labelValue, false); }); - transaction?.end(); + transaction.end(); this.logEvent(action, 'complete', { ...labels, action }, this.timings[action]); }; diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts index 8208d7e2c451f..f8bbbc081f38f 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -28,6 +28,7 @@ import { CONTEXT_ELEMENTATTRIBUTES } from './constants'; * Tests */ describe('Screenshot Observable Pipeline', () => { + const originalCreateLayout = Layouts.createLayout; let driver: ReturnType; let driverFactory: jest.Mocked; let http: ReturnType; @@ -82,13 +83,18 @@ describe('Screenshot Observable Pipeline', () => { screenshots = new Screenshots(driverFactory, logger, packageInfo, http, config, cloud); - jest.spyOn(Layouts, 'createLayout').mockReturnValue(layout); - + // Using this patch instead of using `jest.spyOn`. This way we avoid calling + // `jest.restoraAllMocks()` which removes implementations from other mocks not + // explicit in this test (like apm mock object) + // @ts-expect-error + Layouts.createLayout = () => layout; driver.isPageOpen.mockReturnValue(true); }); afterEach(() => { - jest.restoreAllMocks(); + // @ts-expect-error + Layouts.createLayout = originalCreateLayout; + jest.clearAllMocks(); }); it('pipelines a single url into screenshot and timeRange', async () => { @@ -218,10 +224,6 @@ describe('Screenshot Observable Pipeline', () => { cloud.isCloudEnabled = true; }); - afterEach(() => { - jest.resetAllMocks(); - }); - it('throws an error when OS memory is under 1GB on cloud', async () => { await expect( lastValueFrom( diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx index bae93d048af88..aa8371ac46e32 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx @@ -266,9 +266,8 @@ describe('useUserProfileForm', () => { const themeOptions = themeMenu.find('EuiKeyPadMenuItem'); expect(themeOptions).toHaveLength(3); themeOptions.forEach((option) => { - expect(option.getDOMNode().classList.contains('euiKeyPadMenuItem-isDisabled')).toEqual( - false - ); + const menuItemEl = (option.getDOMNode() as unknown as Element[])[1]; + expect(menuItemEl.className).not.toContain('disabled'); }); }); @@ -353,9 +352,8 @@ describe('useUserProfileForm', () => { const themeOptions = themeMenu.find('EuiKeyPadMenuItem'); expect(themeOptions).toHaveLength(3); themeOptions.forEach((option) => { - expect(option.getDOMNode().classList.contains('euiKeyPadMenuItem-isDisabled')).toEqual( - true - ); + const menuItemEl = (option.getDOMNode() as unknown as Element[])[1]; + expect(menuItemEl.className).toContain('disabled'); }); }); @@ -391,9 +389,8 @@ describe('useUserProfileForm', () => { const themeOptions = themeMenu.find('EuiKeyPadMenuItem'); expect(themeOptions).toHaveLength(3); themeOptions.forEach((option) => { - expect(option.getDOMNode().classList.contains('euiKeyPadMenuItem-isDisabled')).toEqual( - true - ); + const menuItemEl = (option.getDOMNode() as unknown as Element[])[1]; + expect(menuItemEl.className).toContain('disabled'); }); }); }); diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index b6e5caaf0d95d..ccedfc1b02171 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

    Some Title

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

    Some Title

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

    Some Title

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

    Some Title

    Some Body
    Action#1
    Action#2
    "`; diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap index 27612db490f4b..b16535269e1c9 100644 --- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

    We hit an authentication error

    Try logging in again, and if the problem persists, contact your system administrator.

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

    We hit an authentication error

    Try logging in again, and if the problem persists, contact your system administrator.

    "`; -exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

    We hit an authentication error

    Try logging in again, and if the problem persists, contact your system administrator.

    "`; +exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

    We hit an authentication error

    Try logging in again, and if the problem persists, contact your system administrator.

    "`; diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index a6b592367d47b..255cc8b4963a0 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts
    "`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts"`; -exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts"`; +exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts"`; diff --git a/x-pack/plugins/security/server/authorization/reset_session_page.tsx b/x-pack/plugins/security/server/authorization/reset_session_page.tsx index 9cb85f324fab9..85c78ddfcbaec 100644 --- a/x-pack/plugins/security/server/authorization/reset_session_page.tsx +++ b/x-pack/plugins/security/server/authorization/reset_session_page.tsx @@ -15,6 +15,11 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { PromptPage } from '../prompt_page'; +/** + * Static error page (rendered server-side) when user does not have permission to access the requested page. + * + * To trigger this error create a user without any roles and try to login. + */ export function ResetSessionPage({ logoutUrl, buildNumber, diff --git a/x-pack/plugins/security/server/prompt_page.tsx b/x-pack/plugins/security/server/prompt_page.tsx index 31c1f057942de..c0947aed8477b 100644 --- a/x-pack/plugins/security/server/prompt_page.tsx +++ b/x-pack/plugins/security/server/prompt_page.tsx @@ -5,13 +5,8 @@ * 2.0. */ -import { - EuiEmptyPrompt, - EuiPage, - EuiPageBody, - EuiPageContent_Deprecated as EuiPageContent, - EuiProvider, -} from '@elastic/eui'; +import 'css.escape'; // Polyfill required to render `EuiPageTemplate` server-side +import { EuiPageTemplate, EuiProvider } from '@elastic/eui'; // @ts-expect-error no definitions in component folder import { icon as EuiIconWarning } from '@elastic/eui/lib/components/icon/assets/warning'; // @ts-expect-error no definitions in component folder @@ -61,19 +56,15 @@ export function PromptPage({ const content = ( - - - - {title}

    } - body={body} - actions={actions} - /> - - - + + {title}} + body={body} + actions={actions} + /> + ); @@ -88,8 +79,6 @@ export function PromptPage({ const styleSheetPaths = [ `${regularBundlePath}/kbn-ui-shared-deps-src/${UiSharedDepsSrc.cssDistFilename}`, `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename('v8')}`, - `${basePath.serverBasePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, - `${basePath.serverBasePath}/ui/legacy_light_theme.css`, ]; return ( diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/urls.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/urls.ts index 1cc8e2ded7483..aeddf2c9117e6 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/urls.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/urls.ts @@ -10,8 +10,8 @@ import { INTERNAL_DETECTION_ENGINE_URL as INTERNAL_URL, } from '../../../constants'; -const INTERNAL_RULES_URL = `${INTERNAL_URL}/rules`; +const INTERNAL_RULES_URL = `${INTERNAL_URL}/rules` as const; -export const CREATE_RULE_EXCEPTIONS_URL = `${PUBLIC_RULES_URL}/{id}/exceptions`; +export const CREATE_RULE_EXCEPTIONS_URL = `${PUBLIC_RULES_URL}/{id}/exceptions` as const; export const DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL = `${INTERNAL_RULES_URL}/exceptions/_find_references` as const; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts index 0cb49804b13a6..c8efaa8dd85b8 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts @@ -18,8 +18,9 @@ const getMessageEvent = (props: Partial = {}): RuleExecution timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, level: LogLevel.debug, + execution_id: 'execution-id-1', message: 'Some message', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event type: RuleExecutionEventType.message, @@ -31,8 +32,9 @@ const getRunningStatusChange = (props: Partial = {}): RuleEx // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "running"', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.info, @@ -47,8 +49,9 @@ const getPartialFailureStatusChange = ( // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "partial failure". Unknown error', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.warn, @@ -61,8 +64,9 @@ const getFailedStatusChange = (props: Partial = {}): RuleExe // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "failed". Unknown error', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.error, @@ -75,8 +79,9 @@ const getSucceededStatusChange = (props: Partial = {}): Rule // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule executed successfully', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.info, @@ -89,8 +94,9 @@ const getExecutionMetricsEvent = (props: Partial = {}): Rule // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, - message: '', - // Overriden values + execution_id: 'execution-id-1', + message: JSON.stringify({ some_metric_ms: 10 }), + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.debug, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts index 3eaa2ca7efad0..64acfb01e2e2a 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts @@ -6,7 +6,7 @@ */ import * as t from 'io-ts'; -import { enumeration, IsoDateString } from '@kbn/securitysolution-io-ts-types'; +import { enumeration, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; import { enumFromString } from '../../../../utils/enum_from_string'; import { TLogLevel } from './log_level'; @@ -56,5 +56,6 @@ export const RuleExecutionEvent = t.type({ sequence: t.number, level: TLogLevel, type: TRuleExecutionEventType, + execution_id: NonEmptyString, message: t.string, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts index 9f5bd8d273efc..628e71cf51790 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; -import { defaultCsvArray, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; +import { defaultCsvArray, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; import { DefaultSortOrderDesc, PaginationResult } from '../../../model'; import { RuleExecutionEvent, TRuleExecutionEventType, TLogLevel } from '../../model'; @@ -32,13 +32,20 @@ export type GetRuleExecutionEventsRequestQuery = t.TypeOf< typeof GetRuleExecutionEventsRequestQuery >; export const GetRuleExecutionEventsRequestQuery = t.exact( - t.type({ - event_types: defaultCsvArray(TRuleExecutionEventType), - log_levels: defaultCsvArray(TLogLevel), - sort_order: DefaultSortOrderDesc, // defaults to 'desc' - page: DefaultPage, // defaults to 1 - per_page: DefaultPerPage, // defaults to 20 - }) + t.intersection([ + t.partial({ + search_term: NonEmptyString, + event_types: defaultCsvArray(TRuleExecutionEventType), + log_levels: defaultCsvArray(TLogLevel), + date_start: IsoDateString, + date_end: IsoDateString, + }), + t.type({ + sort_order: DefaultSortOrderDesc, // defaults to 'desc' + page: DefaultPage, // defaults to 1 + per_page: DefaultPerPage, // defaults to 20 + }), + ]) ); /** diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml new file mode 100644 index 0000000000000..206a293715355 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml @@ -0,0 +1,113 @@ +openapi: 3.0.0 +info: + title: Endpoint Actions Schema + version: '2023-10-31' +paths: + /api/endpoint/action/state: + get: + summary: Get Action State schema + operationId: EndpointGetActionsState + x-codegen-enabled: false + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + /api/endpoint/action/running_procs: + post: + summary: Get Running Processes Action + operationId: EndpointGetRunningProcessesAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + /api/endpoint/action/isolate: + post: + summary: Isolate host Action + operationId: EndpointIsolateHostAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + /api/endpoint/action/unisolate: + post: + summary: Unisolate host Action + operationId: EndpointUnisolateHostAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + /api/endpoint/action/kill_process: + post: + summary: Kill process Action + operationId: EndpointKillProcessAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/ProcessActionSchemas' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + + /api/endpoint/action/suspend_process: + post: + summary: Suspend process Action + operationId: EndpointSuspendProcessAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/ProcessActionSchemas' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml new file mode 100644 index 0000000000000..88f911a49a714 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.0 +info: + title: Get Action status schema + version: '2023-10-31' +paths: + /api/endpoint/action_status: + get: + summary: Get Actions status schema + operationId: EndpointGetActionsStatus + x-codegen-enabled: false + parameters: + - name: query + in: query + required: true + schema: + type: object + properties: + agent_ids: + $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentIds' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts new file mode 100644 index 0000000000000..a36476054dfdc --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +import { Page, PageSize, StartDate, EndDate, AgentId } from '../model/schema/common.gen'; + +export type AuditLogRequestQuery = z.infer; +export const AuditLogRequestQuery = z.object({ + page: Page.optional(), + page_size: PageSize.optional(), + start_date: StartDate.optional(), + end_date: EndDate.optional(), +}); + +export type AuditLogRequestParams = z.infer; +export const AuditLogRequestParams = z.object({ + agent_id: AgentId.optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml new file mode 100644 index 0000000000000..7deb6dab317a8 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml @@ -0,0 +1,48 @@ +openapi: 3.0.0 +info: + title: Audit Log Schema + version: '2023-10-31' +paths: + /api/endpoint/action_log/{agent_id}: + get: + summary: Get action audit log schema + operationId: EndpointGetActionAuditLog + x-codegen-enabled: false + parameters: + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/AuditLogRequestQuery' + - name: query + in: path + required: true + schema: + $ref: '#/components/schemas/AuditLogRequestParams' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + AuditLogRequestQuery: + type: object + properties: + page: + $ref: '../model/schema/common.schema.yaml#/components/schemas/Page' + page_size: + $ref: '../model/schema/common.schema.yaml#/components/schemas/PageSize' + start_date: + $ref: '../model/schema/common.schema.yaml#/components/schemas/StartDate' + end_date: + $ref: '../model/schema/common.schema.yaml#/components/schemas/EndDate' + + AuditLogRequestParams: + type: object + properties: + agent_id: + $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts new file mode 100644 index 0000000000000..b9557d98e87f9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +export type DetailsRequestParams = z.infer; +export const DetailsRequestParams = z.object({ + action_id: z.string().optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml new file mode 100644 index 0000000000000..4f37c0a23c149 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: Details Schema + version: '2023-10-31' +paths: + /api/endpoint/action/{action_id}: + get: + summary: Get Action details schema + operationId: EndpointGetActionsDetails + x-codegen-enabled: false + parameters: + - name: query + in: path + required: true + schema: + $ref: '#/components/schemas/DetailsRequestParams' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' +components: + schemas: + DetailsRequestParams: + type: object + properties: + action_id: + type: string + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts new file mode 100644 index 0000000000000..e1176c167fcf4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +import { BaseActionSchema, Command, Timeout } from '../model/schema/common.gen'; + +export type ExecuteActionRequestBody = z.infer; +export const ExecuteActionRequestBody = BaseActionSchema.and( + z.object({ + parameters: z.object({ + command: Command, + timeout: Timeout.optional(), + }), + }) +); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml new file mode 100644 index 0000000000000..1af01e1b7876b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml @@ -0,0 +1,42 @@ +openapi: 3.0.0 +info: + title: Execute Action Schema + version: '2023-10-31' +paths: + /api/endpoint/action/execute: + post: + summary: Execute Action + operationId: EndpointExecuteAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteActionRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + ExecuteActionRequestBody: + allOf: + - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - type: object + required: + - parameters + properties: + parameters: + required: + - command + type: object + properties: + command: + $ref: '../model/schema/common.schema.yaml#/components/schemas/Command' + timeout: + $ref: '../model/schema/common.schema.yaml#/components/schemas/Timeout' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts new file mode 100644 index 0000000000000..0b70a7676e069 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +export type FileDownloadRequestParams = z.infer; +export const FileDownloadRequestParams = z.object({ + action_id: z.string(), + file_id: z.string(), +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml new file mode 100644 index 0000000000000..d34aaaf2a50ab --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.0 +info: + title: File Download Schema + version: '2023-10-31' +paths: + /api/endpoint/action/{action_id}/file/{file_id}/download`: + get: + summary: File Download schema + operationId: EndpointFileDownload + x-codegen-enabled: false + parameters: + - name: query + in: path + required: true + schema: + $ref: '#/components/schemas/FileDownloadRequestParams' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' +components: + schemas: + FileDownloadRequestParams: + type: object + required: + - action_id + - file_id + properties: + action_id: + type: string + file_id: + type: string + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts new file mode 100644 index 0000000000000..1e4e7813d35b8 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +export type FileInfoRequestParams = z.infer; +export const FileInfoRequestParams = z.object({ + action_id: z.string(), + file_id: z.string(), +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml new file mode 100644 index 0000000000000..892c2df012cbd --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.0 +info: + title: File Info Schema + version: '2023-10-31' +paths: + /api/endpoint/action/{action_id}/file/{file_id}`: + get: + summary: File Info schema + operationId: EndpointFileInfo + x-codegen-enabled: false + parameters: + - name: query + in: path + required: true + schema: + $ref: '#/components/schemas/FileInfoRequestParams' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + FileInfoRequestParams: + type: object + required: + - action_id + - file_id + properties: + action_id: + type: string + file_id: + type: string + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts new file mode 100644 index 0000000000000..af03b239d3913 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +import { BaseActionSchema } from '../model/schema/common.gen'; + +export type FileUploadActionRequestBody = z.infer; +export const FileUploadActionRequestBody = BaseActionSchema.and( + z.object({ + parameters: z.object({ + overwrite: z.boolean().optional().default(false), + }), + file: z.string(), + }) +); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml new file mode 100644 index 0000000000000..e62ffee79b7e7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml @@ -0,0 +1,44 @@ +openapi: 3.0.0 +info: + title: File Upload Schema + version: '2023-10-31' +paths: + /api/endpoint/action/upload: + post: + summary: Upload Action + operationId: EndpointUploadAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FileUploadActionRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + FileUploadActionRequestBody: + allOf: + - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - type: object + required: + - parameters + - file + properties: + parameters: + type: object + properties: + overwrite: + type: boolean + default: false + # File extends Blob - any binary data will be base-64 encoded + file: + type: string + format: binary diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts new file mode 100644 index 0000000000000..d8109d433fab4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +import { BaseActionSchema } from '../model/schema/common.gen'; + +export type GetFileActionRequestBody = z.infer; +export const GetFileActionRequestBody = BaseActionSchema.and( + z.object({ + parameters: z.object({ + path: z.string(), + }), + }) +); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml new file mode 100644 index 0000000000000..87b7b834e2077 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml @@ -0,0 +1,42 @@ +openapi: 3.0.0 +info: + title: Get File Schema + version: '2023-10-31' +paths: + /api/endpoint/action/get_file: + post: + summary: Get File Action + operationId: EndpointGetFileAction + x-codegen-enabled: false + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetFileActionRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + GetFileActionRequestBody: + allOf: + - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - type: object + required: + - parameters + - file + properties: + parameters: + required: + - path + type: object + properties: + path: + type: string + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts new file mode 100644 index 0000000000000..f734092c22058 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +import { + AgentIds, + Commands, + Page, + StartDate, + EndDate, + UserIds, + Types, + WithOutputs, +} from '../model/schema/common.gen'; + +export type ListRequestQuery = z.infer; +export const ListRequestQuery = z.object({ + agentIds: AgentIds.optional(), + commands: Commands.optional(), + page: Page.optional(), + /** + * Number of items per page + */ + pageSize: z.number().min(1).max(10000).optional().default(10), + startDate: StartDate.optional(), + endDate: EndDate.optional(), + userIds: UserIds.optional(), + types: Types.optional(), + withOutputs: WithOutputs.optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml new file mode 100644 index 0000000000000..c07ad4eb253b0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.0 +info: + title: Actions List Schema + version: '2023-10-31' +paths: + /api/endpoint/action: + get: + summary: Get Actions List schema + operationId: EndpointGetActionsList + x-codegen-enabled: false + parameters: + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/ListRequestQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' +components: + schemas: + ListRequestQuery: + type: object + properties: + agentIds: + $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentIds' + commands: + $ref: '../model/schema/common.schema.yaml#/components/schemas/Commands' + page: + $ref: '../model/schema/common.schema.yaml#/components/schemas/Page' + pageSize: + type: integer + default: 10 + minimum: 1 + maximum: 10000 + description: Number of items per page + startDate: + $ref: '../model/schema/common.schema.yaml#/components/schemas/StartDate' + endDate: + $ref: '../model/schema/common.schema.yaml#/components/schemas/EndDate' + userIds: + $ref: '../model/schema/common.schema.yaml#/components/schemas/UserIds' + types: + $ref: '../model/schema/common.schema.yaml#/components/schemas/Types' + withOutputs: + $ref: '../model/schema/common.schema.yaml#/components/schemas/WithOutputs' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts new file mode 100644 index 0000000000000..fca1370559ada --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +export type ListRequestQuery = z.infer; +export const ListRequestQuery = z.object({ + /** + * Page number + */ + page: z.number().min(0).optional().default(0), + /** + * Number of items per page + */ + pageSize: z.number().min(1).max(10000).optional().default(10), + kuery: z.string().nullable().optional(), + sortField: z + .enum([ + 'enrolled_at', + 'metadata.host.hostname', + 'host_status', + 'metadata.Endpoint.policy.applied.name', + 'metadata.Endpoint.policy.applied.status', + 'metadata.host.os.name', + 'metadata.host.ip', + 'metadata.agent.version', + 'last_checkin', + ]) + .optional(), + sortDirection: z.enum(['asc', 'desc']).nullable().optional(), + hostStatuses: z.array(z.enum(['healthy', 'offline', 'updating', 'inactive', 'unenrolled'])), +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.schema.yaml new file mode 100644 index 0000000000000..295d8cf3ca143 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.schema.yaml @@ -0,0 +1,54 @@ +openapi: 3.0.0 +info: + title: List Metadata Schema + version: '2023-10-31' +paths: { } +components: + schemas: + ListRequestQuery: + type: object + required: + - hostStatuses + properties: + page: + type: integer + default: 0 + minimum: 0 + description: Page number + pageSize: + type: integer + default: 10 + minimum: 1 + maximum: 10000 + description: Number of items per page + kuery: + type: string + nullable: true + sortField: + type: string + enum: + - enrolled_at + - metadata.host.hostname + - host_status + - metadata.Endpoint.policy.applied.name + - metadata.Endpoint.policy.applied.status + - metadata.host.os.name + - metadata.host.ip + - metadata.agent.version + - last_checkin + sortDirection: + type: string + enum: + - 'asc' + - 'desc' + nullable: true + hostStatuses: + type: array + items: + type: string + enum: + - healthy + - offline + - updating + - inactive + - unenrolled diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml new file mode 100644 index 0000000000000..55a3cfc57c351 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml @@ -0,0 +1,58 @@ +openapi: 3.0.0 +info: + title: Endpoint Metadata Schema + version: '2023-10-31' +paths: + /api/endpoint/metadata: + get: + summary: Get Metadata List schema + operationId: GetEndpointMetadataList + x-codegen-enabled: false + parameters: + - name: query + in: query + required: true + schema: + $ref: './list_metadata.schema.yaml#/components/schemas/ListRequestQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + /api/endpoint/metadata/transforms: + get: + summary: Get Metadata Transform schema + operationId: GetEndpointMetadataTransform + x-codegen-enabled: false + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + /api/endpoint/metadata/{id}: + get: + summary: Get Metadata schema + operationId: GetEndpointMetadata + x-codegen-enabled: false + parameters: + - name: query + in: path + required: true + schema: + type: object + properties: + id: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts new file mode 100644 index 0000000000000..89f15504c4be5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts @@ -0,0 +1,156 @@ +/* + * Copyright 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 { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +export type Id = z.infer; +export const Id = z.string(); + +export type IdOrUndefined = z.infer; +export const IdOrUndefined = Id.nullable(); + +/** + * Page number + */ +export type Page = z.infer; +export const Page = z.number().min(1).default(1); + +/** + * Number of items per page + */ +export type PageSize = z.infer; +export const PageSize = z.number().min(1).max(100).default(10); + +/** + * Start date + */ +export type StartDate = z.infer; +export const StartDate = z.string(); + +/** + * End date + */ +export type EndDate = z.infer; +export const EndDate = z.string(); + +/** + * Agent ID + */ +export type AgentId = z.infer; +export const AgentId = z.string(); + +export type AgentIds = z.infer; +export const AgentIds = z.union([z.array(z.string().min(1)).min(1).max(50), z.string().min(1)]); + +/** + * The command to be executed (cannot be an empty string) + */ +export type Command = z.infer; +export const Command = z.enum([ + 'isolate', + 'unisolate', + 'kill-process', + 'suspend-process', + 'running-processes', + 'get-file', + 'execute', + 'upload', +]); + +export type Commands = z.infer; +export const Commands = z.array(Command); + +/** + * The maximum timeout value in milliseconds (optional) + */ +export type Timeout = z.infer; +export const Timeout = z.number().min(1); + +export type Status = z.infer; +export const Status = z.enum(['failed', 'pending', 'successful']); + +export type Statuses = z.infer; +export const Statuses = z.array(Status); + +/** + * User IDs + */ +export type UserIds = z.infer; +export const UserIds = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); + +/** + * With Outputs + */ +export type WithOutputs = z.infer; +export const WithOutputs = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); + +export type Type = z.infer; +export const Type = z.enum(['automated', 'manual']); + +export type Types = z.infer; +export const Types = z.array(Type); + +/** + * List of endpoint IDs (cannot contain empty strings) + */ +export type EndpointIds = z.infer; +export const EndpointIds = z.array(z.string().min(1)).min(1); + +/** + * If defined, any case associated with the given IDs will be updated (cannot contain empty strings) + */ +export type AlertIds = z.infer; +export const AlertIds = z.array(z.string().min(1)).min(1); + +/** + * Case IDs to be updated (cannot contain empty strings) + */ +export type CaseIds = z.infer; +export const CaseIds = z.array(z.string().min(1)).min(1); + +/** + * Optional comment + */ +export type Comment = z.infer; +export const Comment = z.string(); + +/** + * Optional parameters object + */ +export type Parameters = z.infer; +export const Parameters = z.object({}); + +export type BaseActionSchema = z.infer; +export const BaseActionSchema = z.object({ + endpoint_ids: EndpointIds.optional(), + alert_ids: AlertIds.optional(), + case_ids: CaseIds.optional(), + comment: Comment.optional(), + parameters: Parameters.optional(), +}); + +export type ProcessActionSchemas = z.infer; +export const ProcessActionSchemas = BaseActionSchema.and( + z.object({ + parameters: z.union([ + z.object({ + pid: z.number().min(1).optional(), + }), + z.object({ + entity_id: z.string().min(1).optional(), + }), + ]), + }) +); + +export type SuccessResponse = z.infer; +export const SuccessResponse = z.object({}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml new file mode 100644 index 0000000000000..15d69f3639d1b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml @@ -0,0 +1,184 @@ +openapi: 3.0.0 +info: + title: Common Endpoint Attributes + version: '2023-10-31' +paths: { } +components: + schemas: + Id: + type: string + IdOrUndefined: + $ref: '#/components/schemas/Id' + nullable: true + Page: + type: integer + default: 1 + minimum: 1 + description: Page number + PageSize: + type: integer + default: 10 + minimum: 1 + maximum: 100 + description: Number of items per page + StartDate: + type: string + description: Start date + EndDate: + type: string + description: End date + AgentId: + type: string + description: Agent ID + + AgentIds: + oneOf: + - type: array + items: + type: string + minLength: 1 + minItems: 1 + maxItems: 50 + - type: string + minLength: 1 + minLength: 1 + + Command: + type: string + enum: + - isolate + - unisolate + - kill-process + - suspend-process + - running-processes + - get-file + - execute + - upload + minLength: 1 + description: The command to be executed (cannot be an empty string) + + Commands: + type: array + items: + $ref: '#/components/schemas/Command' + + Timeout: + type: integer + minimum: 1 + description: The maximum timeout value in milliseconds (optional) + + Status: + type: string + enum: + - failed + - pending + - successful + + Statuses: + type: array + items: + $ref: '#/components/schemas/Status' + minLength: 1 + maxLength: 3 + + UserIds: + oneOf: + - type: array + items: + type: string + minLength: 1 + minItems: 1 + - type: string + minLength: 1 + description: User IDs + + WithOutputs: + oneOf: + - type: array + items: + type: string + minLength: 1 + minItems: 1 + - type: string + minLength: 1 + description: With Outputs + + Type: + type: string + enum: + - automated + - manual + + Types: + type: array + items: + $ref: '#/components/schemas/Type' + minLength: 1 + maxLength: 2 + + EndpointIds: + type: array + items: + type: string + minLength: 1 + minItems: 1 + description: List of endpoint IDs (cannot contain empty strings) + AlertIds: + type: array + items: + type: string + minLength: 1 + minItems: 1 + description: If defined, any case associated with the given IDs will be updated (cannot contain empty strings) + CaseIds: + type: array + items: + type: string + minLength: 1 + minItems: 1 + description: Case IDs to be updated (cannot contain empty strings) + Comment: + type: string + description: Optional comment + Parameters: + type: object + description: Optional parameters object + + BaseActionSchema: + type: object + properties: + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + parameters: + $ref: '#/components/schemas/Parameters' + + ProcessActionSchemas: + allOf: + - $ref: '#/components/schemas/BaseActionSchema' + - type: object + required: + - parameters + properties: + parameters: + oneOf: + - type: object + properties: + pid: + type: integer + minimum: 1 + - type: object + properties: + entity_id: + type: string + minLength: 1 + SuccessResponse: + type: object + properties: {} + # Define properties for the success response if needed + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts new file mode 100644 index 0000000000000..3e511f7b4aad4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +import { SuccessResponse, AgentId } from '../model/schema/common.gen'; + +export type GetAgentPolicySummaryRequestQuery = z.infer; +export const GetAgentPolicySummaryRequestQuery = z.object({ + query: z.object({ + package_name: z.string().optional(), + policy_id: z.string().nullable().optional(), + }), +}); +export type GetAgentPolicySummaryRequestQueryInput = z.input< + typeof GetAgentPolicySummaryRequestQuery +>; + +export type GetAgentPolicySummaryResponse = z.infer; +export const GetAgentPolicySummaryResponse = SuccessResponse; +export type GetPolicyResponseRequestQuery = z.infer; +export const GetPolicyResponseRequestQuery = z.object({ + query: z.object({ + agentId: AgentId.optional(), + }), +}); +export type GetPolicyResponseRequestQueryInput = z.input; + +export type GetPolicyResponseResponse = z.infer; +export const GetPolicyResponseResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml new file mode 100644 index 0000000000000..c054e54d99c7f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml @@ -0,0 +1,52 @@ +openapi: 3.0.0 +info: + title: Endpoint Policy Schema + version: '2023-10-31' +paths: + /api/endpoint/policy/summaries: + get: + summary: Get Agent Policy Summary schema + operationId: GetAgentPolicySummary + x-codegen-enabled: true + parameters: + - name: query + in: query + required: true + schema: + type: object + properties: + package_name: + type: string + policy_id: + type: string + nullable: true + + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + + /api/endpoint/policy_response: + get: + summary: Get Policy Response schema + operationId: GetPolicyResponse + x-codegen-enabled: true + parameters: + - name: query + in: query + required: true + schema: + type: object + properties: + agentId: + $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts new file mode 100644 index 0000000000000..1207d3d2418fa --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const GetProtectionUpdatesNoteSchema = { + params: schema.object({ + package_policy_id: schema.string(), + }), +}; + +export const CreateUpdateProtectionUpdatesNoteSchema = { + body: schema.object({ + note: schema.string(), + }), + params: schema.object({ + package_policy_id: schema.string(), + }), +}; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts new file mode 100644 index 0000000000000..a827c94d3b5fd --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + */ + +import { SuccessResponse } from '../model/schema/common.gen'; + +export type GetEndpointSuggestionsRequestParams = z.infer< + typeof GetEndpointSuggestionsRequestParams +>; +export const GetEndpointSuggestionsRequestParams = z.object({ + query: z.object({ + suggestion_type: z.enum(['eventFilters']).optional(), + }), +}); +export type GetEndpointSuggestionsRequestParamsInput = z.input< + typeof GetEndpointSuggestionsRequestParams +>; + +export type GetEndpointSuggestionsRequestBody = z.infer; +export const GetEndpointSuggestionsRequestBody = z.object({ + field: z.string().optional(), + query: z.string().optional(), + filters: z.unknown(), + fieldMeta: z.unknown(), +}); +export type GetEndpointSuggestionsRequestBodyInput = z.input< + typeof GetEndpointSuggestionsRequestBody +>; + +export type GetEndpointSuggestionsResponse = z.infer; +export const GetEndpointSuggestionsResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml new file mode 100644 index 0000000000000..d4db065121768 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.0 +info: + title: Get Suggestions Schema + version: '2023-10-31' +paths: + /api/endpoint/suggestions/{suggestion_type}: + post: + summary: Get suggestions + operationId: GetEndpointSuggestions + x-codegen-enabled: true + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - parameters + properties: + field: + type: string + query: + type: string + filters: {} + fieldMeta: {} + parameters: + - name: query + in: path + required: true + schema: + type: object + properties: + suggestion_type: + type: string + enum: + - eventFilters + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/cti/cti.ts b/x-pack/plugins/security_solution/common/api/search_strategy/cti/cti.ts new file mode 100644 index 0000000000000..19d949c171a49 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/cti/cti.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './event_enrichment'; + +export * from './threat_intel_source'; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/cti/event_enrichment.ts b/x-pack/plugins/security_solution/common/api/search_strategy/cti/event_enrichment.ts new file mode 100644 index 0000000000000..ecf86bc742e6e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/cti/event_enrichment.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { CtiQueries } from '../model/factory_query_type'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { timerange } from '../model/timerange'; + +export const eventEnrichmentRequestOptionsSchema = requestBasicOptionsSchema.extend({ + eventFields: z.record(z.unknown()), + timerange, + factoryQueryType: z.literal(CtiQueries.eventEnrichment), +}); + +export type EventEnrichmentRequestOptionsInput = z.input< + typeof eventEnrichmentRequestOptionsSchema +>; + +export type EventEnrichmentRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/cti/threat_intel_source.ts b/x-pack/plugins/security_solution/common/api/search_strategy/cti/threat_intel_source.ts new file mode 100644 index 0000000000000..ad82371bdda62 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/cti/threat_intel_source.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { CtiQueries } from '../model/factory_query_type'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; + +export const threatIntelSourceRequestOptionsSchema = requestBasicOptionsSchema.extend({ + factoryQueryType: z.literal(CtiQueries.dataSource), +}); + +export type ThreatIntelSourceRequestOptionsInput = z.input< + typeof threatIntelSourceRequestOptionsSchema +>; + +export type ThreatIntelSourceRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/endpoint_fields/endpoint_fields.ts b/x-pack/plugins/security_solution/common/api/search_strategy/endpoint_fields/endpoint_fields.ts index d4e9ce80710ca..cdef3c0716d7f 100644 --- a/x-pack/plugins/security_solution/common/api/search_strategy/endpoint_fields/endpoint_fields.ts +++ b/x-pack/plugins/security_solution/common/api/search_strategy/endpoint_fields/endpoint_fields.ts @@ -12,4 +12,6 @@ export const endpointFieldsRequestSchema = z.object({ onlyCheckIfIndicesExist: z.boolean(), }); +export type EndpointFieldsRequestSchemaInput = z.input; + export type EndpointFieldsRequestSchema = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/first_seen_last_seen/first_seen_last_seen.ts b/x-pack/plugins/security_solution/common/api/search_strategy/first_seen_last_seen/first_seen_last_seen.ts index d161aac9af18f..52a6afe56a604 100644 --- a/x-pack/plugins/security_solution/common/api/search_strategy/first_seen_last_seen/first_seen_last_seen.ts +++ b/x-pack/plugins/security_solution/common/api/search_strategy/first_seen_last_seen/first_seen_last_seen.ts @@ -11,28 +11,27 @@ import type { IKibanaSearchResponse } from '@kbn/data-plugin/common'; import { order } from '../model/order'; import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { inspect } from '../model/inspect'; +import { FirstLastSeenQuery } from '../model/factory_query_type'; + +export const firstLastSeenRequestOptionsSchema = requestBasicOptionsSchema.extend({ + order, + field: z.string(), + value: z.string(), + factoryQueryType: z.literal(FirstLastSeenQuery), +}); -export const firstLastSeenRequestOptionsSchema = z - .object({ - order, - field: z.string(), - value: z.string(), - }) - .extend(requestBasicOptionsSchema.partial().shape); +export type FirstLastSeenRequestOptionsInput = z.input; export type FirstLastSeenRequestOptions = z.infer; -const inspectSchema = z.object({ - dsl: z.array(z.string()), -}); - export const firstLastSeenResponseSchema = z .object({ firstSeen: z.string().nullable(), lastSeen: z.string().nullable(), - inspect: inspectSchema, + inspect, }) .partial(); -export type FirstLastSeenStrategyResponse = z.infer & +export type FirstLastSeenStrategyResponse = z.input & IKibanaSearchResponse; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/all.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/all.ts new file mode 100644 index 0000000000000..b79c4b3afe93d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/all.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { HostsQueries } from '../model/factory_query_type'; +import { pagination } from '../model/pagination'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { timerange } from '../model/timerange'; +import { sort } from './model/sort'; + +export const allHostsSchema = requestBasicOptionsSchema.extend({ + sort, + pagination, + timerange, + isNewRiskScoreModuleAvailable: z.boolean().default(false), + factoryQueryType: z.literal(HostsQueries.hosts), +}); + +export type HostsRequestOptionsInput = z.input; + +export type HostsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/details.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/details.ts new file mode 100644 index 0000000000000..01aedce487772 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/details.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { HostsQueries } from '../model/factory_query_type'; +import { inspect } from '../model/inspect'; +import { pagination } from '../model/pagination'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { timerange } from '../model/timerange'; +import { sort } from './model/sort'; + +export const hostDetailsSchema = requestBasicOptionsSchema.extend({ + hostName: z.string(), + skip: z.boolean().optional(), + inspect, + pagination: pagination.optional(), + timerange, + sort, + factoryQueryType: z.literal(HostsQueries.details), +}); + +export type HostDetailsRequestOptionsInput = z.input; + +export type HostDetailsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/hosts.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/hosts.ts new file mode 100644 index 0000000000000..a5931bd42972e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/hosts.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './all'; + +export * from './details'; + +export * from './overview'; + +export * from './uncommon_processes'; + +export * from './kpi_hosts'; + +export * from './kpi_unique_ips'; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/kpi_hosts.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/kpi_hosts.ts new file mode 100644 index 0000000000000..e49741efe0d24 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/kpi_hosts.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { HostsKpiQueries } from '../model/factory_query_type'; +import { pagination } from '../model/pagination'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { timerange } from '../model/timerange'; +import { sort } from './model/sort'; + +export const kpiHostsSchema = requestBasicOptionsSchema.extend({ + sort, + pagination, + timerange, + factoryQueryType: z.literal(HostsKpiQueries.kpiHosts), +}); + +export type KpiHostsRequestOptionsInput = z.input; + +export type KpiHostsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/kpi_unique_ips.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/kpi_unique_ips.ts new file mode 100644 index 0000000000000..998b6a076bd9a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/kpi_unique_ips.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { HostsKpiQueries } from '../model/factory_query_type'; +import { pagination } from '../model/pagination'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { timerange } from '../model/timerange'; +import { sort } from './model/sort'; + +export const kpiUniqueIpsSchema = requestBasicOptionsSchema.extend({ + sort, + pagination, + timerange, + factoryQueryType: z.literal(HostsKpiQueries.kpiUniqueIps), +}); + +export type KpiUniqueIpsRequestOptionsInput = z.input; + +export type KpiUniqueIpsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/model/sort.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/model/sort.ts new file mode 100644 index 0000000000000..8a547765fe266 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/model/sort.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum HostsFields { + lastSeen = 'lastSeen', + hostName = 'hostName', + success = 'success', +} + +import { sort as baseSort } from '../../model/sort'; + +export const sort = baseSort; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/overview.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/overview.ts new file mode 100644 index 0000000000000..1b85066860388 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/overview.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { HostsQueries } from '../model/factory_query_type'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { timerange } from '../model/timerange'; + +export const hostOverviewSchema = requestBasicOptionsSchema.extend({ + factoryQueryType: z.literal(HostsQueries.overview), + timerange, +}); + +export type HostOverviewRequestOptionsInput = z.input; + +export type HostOverviewRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/hosts/uncommon_processes.ts b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/uncommon_processes.ts new file mode 100644 index 0000000000000..0a24abab5be7c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/hosts/uncommon_processes.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { HostsQueries } from '../model/factory_query_type'; +import { pagination } from '../model/pagination'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; + +export const hostUncommonProcessesSchema = requestBasicOptionsSchema.extend({ + sort, + pagination, + timerange, + factoryQueryType: z.literal(HostsQueries.uncommonProcesses), +}); + +export type HostUncommonProcessesRequestOptionsInput = z.input; + +export type HostUncommonProcessesRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/index.ts b/x-pack/plugins/security_solution/common/api/search_strategy/index.ts index 31945f4bbf7ec..54baa7c46ed2d 100644 --- a/x-pack/plugins/security_solution/common/api/search_strategy/index.ts +++ b/x-pack/plugins/security_solution/common/api/search_strategy/index.ts @@ -5,4 +5,114 @@ * 2.0. */ +import { z } from 'zod'; + +import { + threatIntelSourceRequestOptionsSchema, + eventEnrichmentRequestOptionsSchema, +} from './cti/cti'; + +import { firstLastSeenRequestOptionsSchema } from './first_seen_last_seen/first_seen_last_seen'; +import { + allHostsSchema, + hostDetailsSchema, + hostOverviewSchema, + hostUncommonProcessesSchema, + kpiHostsSchema, + kpiUniqueIpsSchema, +} from './hosts/hosts'; +import { matrixHistogramSchema } from './matrix_histogram/matrix_histogram'; +import { networkDetailsSchema } from './network/details'; +import { networkDnsSchema } from './network/dns'; +import { networkHttpSchema } from './network/http'; +import { + networkKpiDns, + networkKpiEvents, + networkKpiTlsHandshakes, + networkKpiUniqueFlows, + networkKpiUniquePrivateIps, +} from './network/kpi'; +import { networkOverviewSchema } from './network/overview'; +import { networkTlsSchema } from './network/tls'; +import { networkTopCountriesSchema } from './network/top_countries'; +import { networkTopNFlowSchema } from './network/top_n_flow'; +import { networkUsersSchema } from './network/users'; + +import { + relatedHostsRequestOptionsSchema, + relatedUsersRequestOptionsSchema, +} from './related_entities/related_entities'; + +import { + hostsRiskScoreRequestOptionsSchema, + riskScoreKpiRequestOptionsSchema, + usersRiskScoreRequestOptionsSchema, +} from './risk_score/risk_score'; + +import { + authenticationsKpiSchema, + managedUserDetailsSchema, + observedUserDetailsSchema, + totalUsersKpiSchema, + userAuthenticationsSchema, + usersSchema, +} from './users/users'; + export * from './first_seen_last_seen/first_seen_last_seen'; + +export * from './hosts/hosts'; + +export * from './users/users'; + +export * from './matrix_histogram/matrix_histogram'; + +export * from './network/network'; + +export * from './related_entities/related_entities'; + +export * from './risk_score/risk_score'; + +export * from './cti/cti'; + +export * from './model/pagination'; + +export * from './model/factory_query_type'; + +export * from './model/runtime_mappings'; + +export const searchStrategyRequestSchema = z.discriminatedUnion('factoryQueryType', [ + firstLastSeenRequestOptionsSchema, + allHostsSchema, + hostDetailsSchema, + kpiHostsSchema, + kpiUniqueIpsSchema, + hostOverviewSchema, + hostUncommonProcessesSchema, + usersSchema, + observedUserDetailsSchema, + managedUserDetailsSchema, + totalUsersKpiSchema, + authenticationsKpiSchema, + userAuthenticationsSchema, + hostsRiskScoreRequestOptionsSchema, + usersRiskScoreRequestOptionsSchema, + riskScoreKpiRequestOptionsSchema, + relatedHostsRequestOptionsSchema, + relatedUsersRequestOptionsSchema, + networkDetailsSchema, + networkDnsSchema, + networkHttpSchema, + networkOverviewSchema, + networkTlsSchema, + networkTopCountriesSchema, + networkTopNFlowSchema, + networkUsersSchema, + networkKpiDns, + networkKpiEvents, + networkKpiTlsHandshakes, + networkKpiUniqueFlows, + networkKpiUniquePrivateIps, + matrixHistogramSchema, + threatIntelSourceRequestOptionsSchema, + eventEnrichmentRequestOptionsSchema, +]); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/matrix_histogram/matrix_histogram.ts b/x-pack/plugins/security_solution/common/api/search_strategy/matrix_histogram/matrix_histogram.ts new file mode 100644 index 0000000000000..7ad60f8d8b6fc --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/matrix_histogram/matrix_histogram.ts @@ -0,0 +1,56 @@ +/* + * Copyright 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 { z } from 'zod'; +import { MatrixHistogramQuery } from '../model/factory_query_type'; +import { inspect } from '../model/inspect'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { runtimeMappings } from '../model/runtime_mappings'; +import { timerange } from '../model/timerange'; + +export enum MatrixHistogramType { + authentications = 'authentications', + anomalies = 'anomalies', + events = 'events', + alerts = 'alerts', + dns = 'dns', + preview = 'preview', +} + +export const matrixHistogramSchema = requestBasicOptionsSchema.extend({ + histogramType: z.enum([ + MatrixHistogramType.alerts, + MatrixHistogramType.anomalies, + MatrixHistogramType.authentications, + MatrixHistogramType.dns, + MatrixHistogramType.events, + MatrixHistogramType.preview, + ]), + stackByField: z.string().optional(), + threshold: z + .object({ + field: z.array(z.string()), + value: z.string(), + cardinality: z + .object({ + field: z.array(z.string()), + value: z.string(), + }) + .optional(), + }) + .optional(), + inspect, + isPtrIncluded: z.boolean().default(false), + includeMissingData: z.boolean().default(true), + runtimeMappings, + timerange, + factoryQueryType: z.literal(MatrixHistogramQuery), +}); + +export type MatrixHistogramRequestOptionsInput = z.input; + +export type MatrixHistogramRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/factory_query_type.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/factory_query_type.ts new file mode 100644 index 0000000000000..b71296af8482f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/factory_query_type.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum HostsQueries { + details = 'hostDetails', + hosts = 'hosts', + overview = 'overviewHost', + uncommonProcesses = 'uncommonProcesses', +} + +export enum NetworkKpiQueries { + dns = 'networkKpiDns', + networkEvents = 'networkKpiNetworkEvents', + tlsHandshakes = 'networkKpiTlsHandshakes', + uniqueFlows = 'networkKpiUniqueFlows', + uniquePrivateIps = 'networkKpiUniquePrivateIps', +} + +export enum HostsKpiQueries { + kpiHosts = 'hostsKpiHosts', + kpiUniqueIps = 'hostsKpiUniqueIps', +} + +export enum UsersQueries { + observedDetails = 'observedUserDetails', + managedDetails = 'managedUserDetails', + kpiTotalUsers = 'usersKpiTotalUsers', + users = 'allUsers', + authentications = 'authentications', + kpiAuthentications = 'usersKpiAuthentications', +} + +export enum NetworkQueries { + details = 'networkDetails', + dns = 'dns', + http = 'http', + overview = 'overviewNetwork', + tls = 'tls', + topCountries = 'topCountries', + topNFlow = 'topNFlow', + users = 'users', +} + +export enum RiskQueries { + hostsRiskScore = 'hostsRiskScore', + usersRiskScore = 'usersRiskScore', + kpiRiskScore = 'kpiRiskScore', +} + +export enum CtiQueries { + eventEnrichment = 'eventEnrichment', + dataSource = 'dataSource', +} + +export const MatrixHistogramQuery = 'matrixHistogram'; + +export const FirstLastSeenQuery = 'firstlastseen'; + +export enum RelatedEntitiesQueries { + relatedHosts = 'relatedHosts', + relatedUsers = 'relatedUsers', +} + +export type FactoryQueryTypes = + | HostsQueries + | HostsKpiQueries + | UsersQueries + | NetworkQueries + | NetworkKpiQueries + | RiskQueries + | CtiQueries + | typeof MatrixHistogramQuery + | typeof FirstLastSeenQuery + | RelatedEntitiesQueries; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/filter_query.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/filter_query.ts index 89c6e24dad231..73bcf041aadf9 100644 --- a/x-pack/plugins/security_solution/common/api/search_strategy/model/filter_query.ts +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/filter_query.ts @@ -71,7 +71,7 @@ export type ESQuery = | ESBoolQuery | JsonObject; -const esQuerySchema = z.union([ +export const esQuerySchema = z.union([ esRangeQuerySchema, esQueryStringQuerySchema, esMatchQuerySchema, @@ -80,4 +80,4 @@ const esQuerySchema = z.union([ jsonObjectSchema, ]); -export const filterQuery = z.union([z.string(), z.undefined(), esQuerySchema]); +export const filterQuery = z.union([z.string(), z.any()]).optional(); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/inspect.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/inspect.ts new file mode 100644 index 0000000000000..d6aa7aa9782e2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/inspect.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export const inspect = z + .union([ + z + .object({ + dsl: z.array(z.string()), + }) + .nullable(), + z.boolean(), + ]) + .optional(); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/order.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/order.ts index d93aa97188ee2..e163c82cef737 100644 --- a/x-pack/plugins/security_solution/common/api/search_strategy/model/order.ts +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/order.ts @@ -7,6 +7,8 @@ import { Direction } from '@kbn/timelines-plugin/common'; +export { Direction }; + import { z } from 'zod'; export const order = z.enum([Direction.asc, Direction.desc]); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/pagination.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/pagination.ts new file mode 100644 index 0000000000000..8a0e0dcbb1340 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/pagination.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export type PaginationInputPaginatedInput = z.input; + +export const pagination = z + .object({ + /** The activePage parameter defines the page of results you want to fetch */ + activePage: z.number(), + /** The cursorStart parameter defines the start of the results to be displayed */ + cursorStart: z.number(), + /** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */ + fakePossibleCount: z.number(), + /** The querySize parameter is the number of items to be returned */ + querySize: z.number(), + }) + .default({ + activePage: 0, + cursorStart: 0, + fakePossibleCount: 0, + querySize: 0, + }); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/request_basic_options.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/request_basic_options.ts index 49227ac1de509..6226efded40c6 100644 --- a/x-pack/plugins/security_solution/common/api/search_strategy/model/request_basic_options.ts +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/request_basic_options.ts @@ -7,20 +7,16 @@ import { z } from 'zod'; import { filterQuery } from './filter_query'; +import { timerange } from './timerange'; export const requestBasicOptionsSchema = z.object({ - timerange: z.object({ - interval: z.string(), - from: z.string(), - to: z.string(), - }), + timerange: timerange.optional(), filterQuery, - defaultIndex: z.array(z.string()), - - // This comes from the IKibanaSearchRequest - factoryQueryType: z.union([z.string(), z.undefined()]), - id: z.union([z.string(), z.undefined()]), - params: z.union([z.object({}), z.undefined()]), + defaultIndex: z.array(z.string()).optional(), + id: z.string().optional(), + params: z.any().optional(), }); +export type RequestBasicOptionsInput = z.input; + export type RequestBasicOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/request_paginated_options.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/request_paginated_options.ts new file mode 100644 index 0000000000000..3edff2596f0f3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/request_paginated_options.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sort } from '../hosts/model/sort'; +import { pagination } from './pagination'; +import { requestBasicOptionsSchema } from './request_basic_options'; + +export const requestOptionsPaginatedSchema = requestBasicOptionsSchema.extend({ + pagination, + sort, +}); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/runtime_mappings.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/runtime_mappings.ts new file mode 100644 index 0000000000000..b3f16c1ed1236 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/runtime_mappings.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export type MappingRuntimeFieldType = + | 'boolean' + | 'date' + | 'double' + | 'geo_point' + | 'ip' + | 'keyword' + | 'long' + | 'lookup'; + +export const runtimeMappings = z + .record( + z.object({ + type: z.union([ + z.literal('boolean'), + z.literal('date'), + z.literal('double'), + z.literal('geo_point'), + z.literal('ip'), + z.literal('keyword'), + z.literal('long'), + z.literal('lookup'), + ]), + script: z + .union([ + z.string(), + z.object({ source: z.string() }), + z.object({ id: z.string(), params: z.record(z.any()) }), + ]) + .optional(), + fetch_fields: z.array(z.string()).optional(), + format: z.string().optional(), + input_field: z.string().optional(), + target_field: z.string().optional(), + target_index: z.string().optional(), + }) + ) + .optional(); + +export type RunTimeMappings = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/sort.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/sort.ts new file mode 100644 index 0000000000000..a7ed1e44c1260 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/sort.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { Direction, order } from './order'; + +export const sort = z + .object({ + direction: order.default(Direction.desc), + field: z.string().default('@timestamp'), + }) + .default({ direction: Direction.desc, field: '@timestamp' }); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/model/timerange.ts b/x-pack/plugins/security_solution/common/api/search_strategy/model/timerange.ts new file mode 100644 index 0000000000000..f04ad37a82839 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/model/timerange.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export const timerange = z.object({ + interval: z.string(), + from: z.string(), + to: z.string(), +}); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/details.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/details.ts new file mode 100644 index 0000000000000..5cabd022eebcd --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/details.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; + +export const networkDetailsSchema = requestBasicOptionsSchema.extend({ + ip: z.string().ip(), + factoryQueryType: z.literal(NetworkQueries.details), +}); + +export type NetworkDetailsRequestOptionsInput = z.input; + +export type NetworkDetailsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/dns.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/dns.ts new file mode 100644 index 0000000000000..95b914a277ae9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/dns.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; + +export enum NetworkDnsFields { + dnsName = 'dnsName', + queryCount = 'queryCount', + uniqueDomains = 'uniqueDomains', + dnsBytesIn = 'dnsBytesIn', + dnsBytesOut = 'dnsBytesOut', +} + +export const networkDnsSchema = requestOptionsPaginatedSchema.extend({ + isPtrIncluded: z.boolean().default(false), + stackByField: z.string().optional(), + sort, + timerange, + factoryQueryType: z.literal(NetworkQueries.dns), +}); + +export type NetworkDnsRequestOptionsInput = z.input; + +export type NetworkDnsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/http.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/http.ts new file mode 100644 index 0000000000000..42e2096c92fd1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/http.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; + +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; + +export const networkHttpSchema = requestOptionsPaginatedSchema.extend({ + ip: z.string().ip().optional(), + defaultIndex: z.array(z.string()).min(1).optional(), + timerange, + sort, + factoryQueryType: z.literal(NetworkQueries.http), +}); + +export type NetworkHttpRequestOptionsInput = z.input; + +export type NetworkHttpRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/dns.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/dns.ts new file mode 100644 index 0000000000000..fd614dd76e224 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/dns.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkKpiQueries } from '../../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../../model/request_basic_options'; +import { timerange } from '../../model/timerange'; + +export const networkKpiDns = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(NetworkKpiQueries.dns), +}); + +export type NetworkKpiDnsRequestOptionsInput = z.input; + +export type NetworkKpiDnsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/events.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/events.ts new file mode 100644 index 0000000000000..7aef866065f29 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/events.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkKpiQueries } from '../../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../../model/request_basic_options'; +import { timerange } from '../../model/timerange'; + +export const networkKpiEvents = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(NetworkKpiQueries.networkEvents), +}); + +export type NetworkKpiEventsRequestOptionsInput = z.input; + +export type NetworkKpiEventsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/index.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/index.ts new file mode 100644 index 0000000000000..2fce614035fd6 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './dns'; + +export * from './events'; + +export * from './tls_handshakes'; + +export * from './unique_flows'; + +export * from './unique_private_ips'; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/tls_handshakes.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/tls_handshakes.ts new file mode 100644 index 0000000000000..6847824032390 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/tls_handshakes.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkKpiQueries } from '../../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../../model/request_basic_options'; +import { timerange } from '../../model/timerange'; + +export const networkKpiTlsHandshakes = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(NetworkKpiQueries.tlsHandshakes), +}); + +export type NetworkKpiTlsHandshakesRequestOptionsInput = z.input; + +export type NetworkKpiTlsHandshakesRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/unique_flows.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/unique_flows.ts new file mode 100644 index 0000000000000..2ecc2f9d699de --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/unique_flows.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkKpiQueries } from '../../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../../model/request_basic_options'; +import { timerange } from '../../model/timerange'; + +export const networkKpiUniqueFlows = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(NetworkKpiQueries.uniqueFlows), +}); + +export type NetworkKpiUniqueFlowsRequestOptionsInput = z.input; + +export type NetworkKpiUniqueFlowsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/unique_private_ips.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/unique_private_ips.ts new file mode 100644 index 0000000000000..f870cd921c308 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/kpi/unique_private_ips.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkKpiQueries } from '../../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../../model/request_basic_options'; +import { timerange } from '../../model/timerange'; + +export const networkKpiUniquePrivateIps = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(NetworkKpiQueries.uniquePrivateIps), +}); + +export type NetworkKpiUniquePrivateIpsRequestOptionsInput = z.input< + typeof networkKpiUniquePrivateIps +>; + +export type NetworkKpiUniquePrivateIpsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/model/flow_target.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/model/flow_target.ts new file mode 100644 index 0000000000000..6064af231675b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/model/flow_target.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export enum FlowTargetSourceDest { + destination = 'destination', + source = 'source', +} + +export const flowTarget = z.enum([FlowTargetSourceDest.destination, FlowTargetSourceDest.source]); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/model/top_tables_fields.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/model/top_tables_fields.ts new file mode 100644 index 0000000000000..ce7951c7229ee --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/model/top_tables_fields.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export enum NetworkTopTablesFields { + bytes_in = 'bytes_in', + bytes_out = 'bytes_out', + flows = 'flows', + destination_ips = 'destination_ips', + source_ips = 'source_ips', +} + +export const topTablesFields = z.enum([ + NetworkTopTablesFields.bytes_in, + NetworkTopTablesFields.bytes_out, + NetworkTopTablesFields.flows, + NetworkTopTablesFields.destination_ips, + NetworkTopTablesFields.source_ips, +]); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/network.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/network.ts new file mode 100644 index 0000000000000..82cb6f78e8875 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/network.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './details'; + +export * from './dns'; + +export * from './http'; + +export * from './kpi'; + +export * from './overview'; + +export * from './tls'; + +export * from './top_countries'; + +export * from './top_n_flow'; + +export * from './users'; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/overview.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/overview.ts new file mode 100644 index 0000000000000..275ac5fa8146e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/overview.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { timerange } from '../model/timerange'; + +export const networkOverviewSchema = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(NetworkQueries.overview), +}); + +export type NetworkOverviewRequestOptionsInput = z.input; + +export type NetworkOverviewRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/tls.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/tls.ts new file mode 100644 index 0000000000000..e1c0edcd5a3c7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/tls.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; +import { flowTarget } from './model/flow_target'; + +export enum NetworkTlsFields { + _id = '_id', +} + +export const networkTlsSchema = requestOptionsPaginatedSchema.extend({ + ip: z.string().optional(), + flowTarget, + sort, + timerange, + factoryQueryType: z.literal(NetworkQueries.tls), +}); + +export type NetworkTlsRequestOptionsInput = z.input; + +export type NetworkTlsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/top_countries.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/top_countries.ts new file mode 100644 index 0000000000000..2c8a5d6b60e93 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/top_countries.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; +import { filterQuery } from '../model/filter_query'; +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; +import { flowTarget } from './model/flow_target'; + +export const networkTopCountriesSchema = requestOptionsPaginatedSchema.extend({ + ip: z.string().ip().optional(), + flowTarget, + sort, + filterQuery, + timerange, + factoryQueryType: z.literal(NetworkQueries.topCountries), +}); + +export type NetworkTopCountriesRequestOptionsInput = z.input; + +export type NetworkTopCountriesRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/top_n_flow.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/top_n_flow.ts new file mode 100644 index 0000000000000..afd5bba1bbc47 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/top_n_flow.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; +import { flowTarget } from './model/flow_target'; + +export const networkTopNFlowSchema = requestOptionsPaginatedSchema.extend({ + ip: z.string().ip().nullable().optional(), + flowTarget, + sort, + timerange, + factoryQueryType: z.literal(NetworkQueries.topNFlow), +}); + +export type NetworkTopNFlowRequestOptionsInput = z.input; + +export type NetworkTopNFlowRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/network/users.ts b/x-pack/plugins/security_solution/common/api/search_strategy/network/users.ts new file mode 100644 index 0000000000000..eed52071ceaca --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/network/users.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { NetworkQueries } from '../model/factory_query_type'; +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; +import { flowTarget } from './model/flow_target'; + +export enum NetworkUsersFields { + name = 'name', + count = 'count', +} + +export const networkUsersSchema = requestOptionsPaginatedSchema.extend({ + ip: z.string().ip(), + flowTarget, + sort, + timerange, + factoryQueryType: z.literal(NetworkQueries.users), +}); + +export type NetworkUsersRequestOptionsInput = z.input; + +export type NetworkUsersRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_entities.ts b/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_entities.ts new file mode 100644 index 0000000000000..75de66e7f40cb --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_entities.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './related_hosts'; + +export * from './related_users'; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_hosts.ts b/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_hosts.ts new file mode 100644 index 0000000000000..0db0effe8e3b1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_hosts.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { RelatedEntitiesQueries } from '../model/factory_query_type'; +import { inspect } from '../model/inspect'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; + +export const relatedHostsRequestOptionsSchema = requestBasicOptionsSchema.extend({ + userName: z.string(), + skip: z.boolean().optional(), + from: z.string(), + inspect, + isNewRiskScoreModuleAvailable: z.boolean().default(false), + factoryQueryType: z.literal(RelatedEntitiesQueries.relatedHosts), +}); + +export type RelatedHostsRequestOptionsInput = z.input; + +export type RelatedHostsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_users.ts b/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_users.ts new file mode 100644 index 0000000000000..f1386591836ba --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/related_entities/related_users.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { RelatedEntitiesQueries } from '../model/factory_query_type'; +import { inspect } from '../model/inspect'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; + +export const relatedUsersRequestOptionsSchema = requestBasicOptionsSchema.extend({ + hostName: z.string(), + skip: z.boolean().optional(), + from: z.string(), + inspect, + isNewRiskScoreModuleAvailable: z.boolean().default(false), + factoryQueryType: z.literal(RelatedEntitiesQueries.relatedUsers), +}); + +export type RelatedUsersRequestOptionsInput = z.input; + +export type RelatedUsersRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/all.ts b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/all.ts new file mode 100644 index 0000000000000..fcd7bc8601490 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/all.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { RiskQueries } from '../model/factory_query_type'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; +import { riskScoreEntity } from './model/risk_score_entity'; + +export enum RiskScoreFields { + timestamp = '@timestamp', + hostName = 'host.name', + hostRiskScore = 'host.risk.calculated_score_norm', + hostRisk = 'host.risk.calculated_level', + userName = 'user.name', + userRiskScore = 'user.risk.calculated_score_norm', + userRisk = 'user.risk.calculated_level', + alertsCount = 'alertsCount', +} + +const baseRiskScoreRequestOptionsSchema = requestBasicOptionsSchema.extend({ + alertsTimerange: timerange.optional(), + riskScoreEntity, + includeAlertsCount: z.boolean().optional(), + onlyLatest: z.boolean().optional(), + pagination: z + .object({ + cursorStart: z.number(), + querySize: z.number(), + }) + .optional(), + sort: sort + .removeDefault() + .extend({ + field: z.enum([ + RiskScoreFields.timestamp, + RiskScoreFields.hostName, + RiskScoreFields.hostRiskScore, + RiskScoreFields.hostRisk, + RiskScoreFields.userName, + RiskScoreFields.userRiskScore, + RiskScoreFields.userRisk, + RiskScoreFields.alertsCount, + ]), + }) + .optional(), +}); + +export const hostsRiskScoreRequestOptionsSchema = baseRiskScoreRequestOptionsSchema.extend({ + factoryQueryType: z.literal(RiskQueries.hostsRiskScore), +}); + +export const usersRiskScoreRequestOptionsSchema = baseRiskScoreRequestOptionsSchema.extend({ + factoryQueryType: z.literal(RiskQueries.usersRiskScore), +}); + +export const riskScoreRequestOptionsSchema = z.union([ + hostsRiskScoreRequestOptionsSchema, + usersRiskScoreRequestOptionsSchema, +]); + +export type RiskScoreRequestOptionsInput = z.input; + +export type RiskScoreRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/kpi.ts b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/kpi.ts new file mode 100644 index 0000000000000..062556f86c95f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/kpi.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { RiskQueries } from '../model/factory_query_type'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { riskScoreEntity } from './model/risk_score_entity'; + +export const riskScoreKpiRequestOptionsSchema = requestBasicOptionsSchema.extend({ + entity: riskScoreEntity, + factoryQueryType: z.literal(RiskQueries.kpiRiskScore), +}); + +export type RiskScoreKpiRequestOptionsInput = z.input; + +export type RiskScoreKpiRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/model/risk_score_entity.ts b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/model/risk_score_entity.ts new file mode 100644 index 0000000000000..6c9e8682140f6 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/model/risk_score_entity.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export enum RiskScoreEntity { + host = 'host', + user = 'user', +} + +export const riskScoreEntity = z.enum([RiskScoreEntity.host, RiskScoreEntity.user]); diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/risk_score.ts b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/risk_score.ts new file mode 100644 index 0000000000000..05cd9bed8f979 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/risk_score/risk_score.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './all'; + +export * from './kpi'; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/users/all.ts b/x-pack/plugins/security_solution/common/api/search_strategy/users/all.ts new file mode 100644 index 0000000000000..433c0ca7259cc --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/users/all.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { UsersQueries } from '../model/factory_query_type'; +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { sort } from '../model/sort'; +import { timerange } from '../model/timerange'; + +export enum UsersFields { + name = 'name', + domain = 'domain', + lastSeen = 'lastSeen', +} + +export const usersSchema = requestOptionsPaginatedSchema.extend({ + sort: sort.removeDefault().extend({ + field: z.enum([UsersFields.name, UsersFields.lastSeen]), + }), + timerange, + isNewRiskScoreModuleAvailable: z.boolean().default(false), + factoryQueryType: z.literal(UsersQueries.users), +}); + +export type UsersRequestOptionsInput = z.input; + +export type UsersRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/users/authentications.ts b/x-pack/plugins/security_solution/common/api/search_strategy/users/authentications.ts new file mode 100644 index 0000000000000..684dea6c83349 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/users/authentications.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { UsersQueries } from '../model/factory_query_type'; + +import { requestOptionsPaginatedSchema } from '../model/request_paginated_options'; +import { timerange } from '../model/timerange'; + +export enum AuthStackByField { + userName = 'user.name', + hostName = 'host.name', +} + +export const userAuthenticationsSchema = requestOptionsPaginatedSchema.extend({ + stackByField: z.enum([AuthStackByField.userName, AuthStackByField.hostName]), + timerange, + factoryQueryType: z.literal(UsersQueries.authentications), +}); + +export type UserAuthenticationsRequestOptionsInput = z.input; + +export type UserAuthenticationsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/users/kpi/authentications.ts b/x-pack/plugins/security_solution/common/api/search_strategy/users/kpi/authentications.ts new file mode 100644 index 0000000000000..42919d8c5111f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/users/kpi/authentications.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { UsersQueries } from '../../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../../model/request_basic_options'; +import { timerange } from '../../model/timerange'; + +export const authenticationsKpiSchema = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(UsersQueries.kpiAuthentications), +}); + +export type AuthenticationsKpiRequestOptionsInput = z.input; + +export type AuthenticationsKpiRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/users/kpi/total_users.ts b/x-pack/plugins/security_solution/common/api/search_strategy/users/kpi/total_users.ts new file mode 100644 index 0000000000000..3822845cc58ac --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/users/kpi/total_users.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { UsersQueries } from '../../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../../model/request_basic_options'; +import { timerange } from '../../model/timerange'; + +export const totalUsersKpiSchema = requestBasicOptionsSchema.extend({ + timerange, + factoryQueryType: z.literal(UsersQueries.kpiTotalUsers), +}); + +export type TotalUsersKpiRequestOptionsInput = z.input; + +export type TotalUsersKpiRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/users/managed_details.ts b/x-pack/plugins/security_solution/common/api/search_strategy/users/managed_details.ts new file mode 100644 index 0000000000000..b4d7d3bcb2a4f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/users/managed_details.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { UsersQueries } from '../model/factory_query_type'; + +import { requestBasicOptionsSchema } from '../model/request_basic_options'; + +export const managedUserDetailsSchema = requestBasicOptionsSchema.extend({ + userName: z.string(), + factoryQueryType: z.literal(UsersQueries.managedDetails), +}); + +export type ManagedUserDetailsRequestOptionsInput = z.input; + +export type ManagedUserDetailsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/users/observed_details.ts b/x-pack/plugins/security_solution/common/api/search_strategy/users/observed_details.ts new file mode 100644 index 0000000000000..df48317109856 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/users/observed_details.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +import { requestBasicOptionsSchema } from '../model/request_basic_options'; +import { inspect } from '../model/inspect'; +import { timerange } from '../model/timerange'; +import { UsersQueries } from '../model/factory_query_type'; + +export const observedUserDetailsSchema = requestBasicOptionsSchema.extend({ + userName: z.string(), + skip: z.boolean().optional(), + timerange, + inspect, + factoryQueryType: z.literal(UsersQueries.observedDetails), +}); + +export type ObservedUserDetailsRequestOptionsInput = z.input; + +export type ObservedUserDetailsRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/users/users.ts b/x-pack/plugins/security_solution/common/api/search_strategy/users/users.ts new file mode 100644 index 0000000000000..198af6ad7703f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/users/users.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './observed_details'; + +export * from './managed_details'; + +export * from './kpi/total_users'; + +export * from './kpi/authentications'; + +export * from './all'; + +export * from './authentications'; diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index 19c77f230eea5..e4effec5bf4d3 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -63,6 +63,7 @@ export const METADATA_TRANSFORMS_STATUS_ROUTE = `${BASE_ENDPOINT_ROUTE}/metadata export const BASE_POLICY_RESPONSE_ROUTE = `${BASE_ENDPOINT_ROUTE}/policy_response`; export const BASE_POLICY_ROUTE = `${BASE_ENDPOINT_ROUTE}/policy`; export const AGENT_POLICY_SUMMARY_ROUTE = `${BASE_POLICY_ROUTE}/summaries`; +export const PROTECTION_UPDATES_NOTE_ROUTE = `${BASE_ENDPOINT_ROUTE}/protection_updates_note/{package_policy_id}`; /** Suggestions routes */ export const SUGGESTIONS_ROUTE = `${BASE_ENDPOINT_ROUTE}/suggestions/{suggestion_type}`; @@ -110,3 +111,6 @@ export const ENDPOINT_ERROR_CODES: Record = { export const ENDPOINT_FIELDS_SEARCH_STRATEGY = 'endpointFields'; export const ENDPOINT_SEARCH_STRATEGY = 'endpointSearchStrategy'; + +/** Search strategy keys */ +export const ENDPOINT_PACKAGE_POLICIES_STATS_STRATEGY = 'endpointPackagePoliciesStatsStrategy'; diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts index 1c5883c052135..656deff84ec6d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts @@ -127,14 +127,17 @@ const ensureEndpointRuleAlertsIndexExists = async (esClient: Client): Promise { index: { auto_expand_replicas: '0-1', hidden: 'true', - lifecycle: { - name: '.alerts-ilm-policy', - rollover_alias: '.alerts-security.alerts-default', - }, mapping: { total_fields: { limit: 1900, diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts index 0b988d831c923..781d6243384e9 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts @@ -10,7 +10,7 @@ import type { AxiosResponse } from 'axios'; import type { DeleteByQueryResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { KbnClient } from '@kbn/test'; import type { Agent, FleetServerAgent, GetOneAgentResponse } from '@kbn/fleet-plugin/common'; -import { AGENT_API_ROUTES } from '@kbn/fleet-plugin/common'; +import { AGENT_API_ROUTES, API_VERSIONS } from '@kbn/fleet-plugin/common'; import type { HostMetadata } from '../types'; import { FleetAgentGenerator } from '../data_generators/fleet_agent_generator'; import { wrapErrorAndRejectPromise } from './utils'; @@ -90,6 +90,7 @@ const fetchFleetAgent = async (kbnClient: KbnClient, agentId: string): Promise ).data.item; diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts index 0730eba99306c..fda862d247bae 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts @@ -16,7 +16,11 @@ import type { DeleteAgentPolicyResponse, PostDeletePackagePoliciesResponse, } from '@kbn/fleet-plugin/common'; -import { AGENT_POLICY_API_ROUTES, PACKAGE_POLICY_API_ROUTES } from '@kbn/fleet-plugin/common'; +import { + AGENT_POLICY_API_ROUTES, + PACKAGE_POLICY_API_ROUTES, + API_VERSIONS, +} from '@kbn/fleet-plugin/common'; import { memoize } from 'lodash'; import { getEndpointPackageInfo } from '../utils/package'; import type { PolicyData } from '../types'; @@ -61,6 +65,9 @@ export const indexFleetEndpointPolicy = async ( agentPolicy = (await kbnClient .request({ path: AGENT_POLICY_API_ROUTES.CREATE_PATTERN, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, method: 'POST', body: newAgentPolicyData, }) @@ -101,6 +108,9 @@ export const indexFleetEndpointPolicy = async ( path: PACKAGE_POLICY_API_ROUTES.CREATE_PATTERN, method: 'POST', body: newPackagePolicyData, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }) .catch(wrapErrorAndRejectPromise)) as AxiosResponse; @@ -135,6 +145,9 @@ export const deleteIndexedFleetEndpointPolicies = async ( (await kbnClient .request({ path: PACKAGE_POLICY_API_ROUTES.DELETE_PATTERN, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, method: 'POST', body: { packagePolicyIds: indexData.integrationPolicies.map((policy) => policy.id), @@ -153,6 +166,9 @@ export const deleteIndexedFleetEndpointPolicies = async ( (await kbnClient .request({ path: AGENT_POLICY_API_ROUTES.DELETE_PATTERN, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, method: 'POST', body: { agentPolicyId: agentPolicy.id, diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts index 177dccce73714..234be86e4aa02 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts @@ -13,10 +13,20 @@ import type { IBulkInstallPackageHTTPError, PostFleetSetupResponse, } from '@kbn/fleet-plugin/common'; -import { AGENTS_SETUP_API_ROUTES, EPM_API_ROUTES, SETUP_API_ROUTE } from '@kbn/fleet-plugin/common'; +import { + AGENTS_SETUP_API_ROUTES, + EPM_API_ROUTES, + SETUP_API_ROUTE, + API_VERSIONS, +} from '@kbn/fleet-plugin/common'; import { ToolingLog } from '@kbn/tooling-log'; import { UsageTracker } from './usage_tracker'; -import { EndpointDataLoadingError, retryOnError, wrapErrorAndRejectPromise } from './utils'; +import { + EndpointDataLoadingError, + RETRYABLE_TRANSIENT_ERRORS, + retryOnError, + wrapErrorAndRejectPromise, +} from './utils'; const usageTracker = new UsageTracker({ dumpOnProcessExit: true }); @@ -43,6 +53,7 @@ export const setupFleetForEndpoint = async ( const setupResponse = (await kbnClient .request({ path: SETUP_API_ROUTE, + headers: { 'Elastic-Api-Version': API_VERSIONS.public.v1 }, method: 'POST', }) .catch(wrapErrorAndRejectPromise)) as AxiosResponse; @@ -64,6 +75,9 @@ export const setupFleetForEndpoint = async ( .request({ path: AGENTS_SETUP_API_ROUTES.CREATE_PATTERN, method: 'POST', + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }) .catch(wrapErrorAndRejectPromise)) as AxiosResponse; @@ -118,6 +132,9 @@ export const installOrUpgradeEndpointFleetPackage = async ( query: { prerelease: true, }, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }) .catch(wrapErrorAndRejectPromise)) as AxiosResponse; @@ -153,13 +170,7 @@ export const installOrUpgradeEndpointFleetPackage = async ( return bulkResp[0] as BulkInstallPackageInfo; }; - return retryOnError( - updatePackages, - ['no_shard_available_action_exception', 'illegal_index_shard_state_exception'], - logger, - 5, - 10000 - ) + return retryOnError(updatePackages, RETRYABLE_TRANSIENT_ERRORS, logger, 5, 10000) .then((result) => { usageRecord.set('success'); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/utils.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/utils.ts index 0db9fb6f82561..b7f7385a5f119 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/utils.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/utils.ts @@ -8,6 +8,11 @@ import { mergeWith } from 'lodash'; import { ToolingLog } from '@kbn/tooling-log'; +export const RETRYABLE_TRANSIENT_ERRORS: Readonly> = [ + 'no_shard_available_action_exception', + 'illegal_index_shard_state_exception', +]; + export class EndpointDataLoadingError extends Error { constructor(message: string, public meta?: unknown) { super(message); @@ -43,7 +48,7 @@ export const mergeAndAppendArrays = (destinationObj: T, srcObj: S): T => { */ export const retryOnError = async ( callback: () => Promise, - errors: Array, + errors: Array | Readonly>, logger?: ToolingLog, tryCount: number = 5, interval: number = 10000 @@ -60,6 +65,8 @@ export const retryOnError = async ( }); }; + log.indent(4); + let attempt = 1; let responsePromise: Promise; @@ -71,13 +78,20 @@ export const retryOnError = async ( try { responsePromise = callback(); // store promise so that if it fails and no more attempts, we return the last failure - return await responsePromise; + const result = await responsePromise; + + log.info(msg(`attempt ${thisAttempt} was successful. Exiting retry`)); + log.indent(-4); + + return result; } catch (err) { log.info(msg(`attempt ${thisAttempt} failed with: ${err.message}`), err); // If not an error that is retryable, then end loop here and return that error; if (!isRetryableError(err)) { log.error(err); + log.error(msg('non-retryable error encountered')); + log.indent(-4); return Promise.reject(err); } } @@ -85,6 +99,10 @@ export const retryOnError = async ( await new Promise((resolve) => setTimeout(resolve, interval)); } + log.error(msg(`max retry attempts reached. returning last failure`)); + log.indent(-4); + + // Last resort: return the last rejected Promise. // @ts-expect-error TS2454: Variable 'responsePromise' is used before being assigned. return responsePromise; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/utils/package.ts b/x-pack/plugins/security_solution/common/endpoint/utils/package.ts index c45c44fa9c561..599fcabe24c9c 100644 --- a/x-pack/plugins/security_solution/common/endpoint/utils/package.ts +++ b/x-pack/plugins/security_solution/common/endpoint/utils/package.ts @@ -9,7 +9,7 @@ import type { AxiosResponse } from 'axios'; import type { KbnClient } from '@kbn/test'; import type { GetInfoResponse } from '@kbn/fleet-plugin/common'; -import { epmRouteService } from '@kbn/fleet-plugin/common'; +import { API_VERSIONS, epmRouteService } from '@kbn/fleet-plugin/common'; export const getEndpointPackageInfo = async ( kbnClient: KbnClient @@ -18,6 +18,7 @@ export const getEndpointPackageInfo = async ( const endpointPackage = ( (await kbnClient.request({ path, + headers: { 'Elastic-Api-Version': API_VERSIONS.public.v1 }, method: 'GET', })) as AxiosResponse ).data.item; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index cbc84c3314e88..27e9f4f918cba 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -108,7 +108,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables Protection Updates tab in the Endpoint Policy Details page */ - protectionUpdatesEnabled: true, + protectionUpdatesEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.test.ts b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.test.ts new file mode 100644 index 0000000000000..5000244c994a5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.test.ts @@ -0,0 +1,230 @@ +/* + * Copyright 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 { defaultGuideTranslations, getSiemGuideConfig, siemGuideId } from './siem_guide_config'; +import * as i18n from './translations'; + +describe('getSiemGuideConfig', () => { + it('returns a GuideConfig object with default values when no arguments are passed', () => { + const result = getSiemGuideConfig(); + expect(result).toEqual({ + title: defaultGuideTranslations.title, + guideName: 'Security', + telemetryId: siemGuideId, + completedGuideRedirectLocation: { + appID: 'securitySolutionUI', + path: '/dashboards', + }, + description: defaultGuideTranslations.description, + docs: { + text: defaultGuideTranslations.docs, + url: 'https://www.elastic.co/guide/en/security/current/ingest-data.html', + }, + steps: [ + { + id: 'add_data', + title: defaultGuideTranslations.steps.add_data.title, + description: { + descriptionText: defaultGuideTranslations.steps.add_data.description, + linkUrl: 'https://docs.elastic.co/en/integrations/endpoint', + isLinkExternal: true, + linkText: i18n.LINK_TEXT, + }, + integration: 'endpoint', + location: { + appID: 'integrations', + path: '/browse/security', + }, + }, + { + id: 'rules', + title: defaultGuideTranslations.steps.rules.title, + description: defaultGuideTranslations.steps.rules.description, + location: { + appID: 'securitySolutionUI', + path: '/rules', + }, + manualCompletion: defaultGuideTranslations.steps.rules.manualCompletion, + }, + { + id: 'alertsCases', + title: defaultGuideTranslations.steps.alertsCases.title, + description: defaultGuideTranslations.steps.alertsCases.description, + location: { + appID: 'securitySolutionUI', + path: '/alerts', + }, + manualCompletion: defaultGuideTranslations.steps.alertsCases.manualCompletion, + }, + ], + }); + }); + + it('returns a GuideConfig object with values from the launchDarkly argument when it is passed', () => { + const launchDarkly = { + title: 'Custom Title', + description: 'Custom Description', + docs: 'Custom Docs', + steps: { + add_data: { + title: 'Custom Add Data Title', + description: 'Custom Add Data Description', + }, + rules: { + title: 'Custom Rules Title', + description: 'Custom Rules Description', + manualCompletion: { + title: 'Custom Rules Manual Title', + description: 'Custom Rules Manual Description', + }, + }, + alertsCases: { + title: 'Custom Alerts Cases Title', + description: 'Custom Alerts Cases Description', + manualCompletion: { + title: 'Custom Alerts Cases Manual Title', + description: 'Custom Alerts Cases Manual Description', + }, + }, + }, + }; + const result = getSiemGuideConfig(launchDarkly); + expect(result).toEqual({ + title: launchDarkly.title, + guideName: 'Security', + telemetryId: siemGuideId, + completedGuideRedirectLocation: { + appID: 'securitySolutionUI', + path: '/dashboards', + }, + description: launchDarkly.description, + docs: { + text: launchDarkly.docs, + url: 'https://www.elastic.co/guide/en/security/current/ingest-data.html', + }, + steps: [ + { + id: 'add_data', + title: launchDarkly.steps.add_data.title, + description: { + descriptionText: launchDarkly.steps.add_data.description, + linkUrl: 'https://docs.elastic.co/en/integrations/endpoint', + isLinkExternal: true, + linkText: i18n.LINK_TEXT, + }, + integration: 'endpoint', + location: { + appID: 'integrations', + path: '/browse/security', + }, + }, + { + id: 'rules', + title: launchDarkly.steps.rules.title, + description: launchDarkly.steps.rules.description, + manualCompletion: { + title: launchDarkly.steps.rules.manualCompletion.title, + description: launchDarkly.steps.rules.manualCompletion.description, + }, + location: { + appID: 'securitySolutionUI', + path: '/rules', + }, + }, + { + id: 'alertsCases', + title: launchDarkly.steps.alertsCases.title, + description: launchDarkly.steps.alertsCases.description, + manualCompletion: { + title: launchDarkly.steps.alertsCases.manualCompletion.title, + description: launchDarkly.steps.alertsCases.manualCompletion.description, + }, + location: { + appID: 'securitySolutionUI', + path: '/alerts', + }, + }, + ], + }); + }); + + it('returns a GuideConfig object with values from the launchDarkly argument and default values when some properties are missing', () => { + const launchDarkly = { + steps: { + add_data: { + title: 'Custom Add Data Title', + }, + rules: { + description: 'Custom Rules Description', + }, + alertsCases: { + manualCompletion: { + title: 'Custom Alerts Cases Manual Title', + }, + }, + }, + }; + // Ignore because intentionally passing an incomplete object to test that we handle missing properties + // since there is no validation on the object from LaunchDarkly + // @ts-ignore + const result = getSiemGuideConfig(launchDarkly); + expect(result).toEqual({ + title: defaultGuideTranslations.title, + guideName: 'Security', + telemetryId: siemGuideId, + completedGuideRedirectLocation: { + appID: 'securitySolutionUI', + path: '/dashboards', + }, + description: defaultGuideTranslations.description, + docs: { + text: defaultGuideTranslations.docs, + url: 'https://www.elastic.co/guide/en/security/current/ingest-data.html', + }, + steps: [ + { + id: 'add_data', + title: launchDarkly.steps.add_data.title, + description: { + descriptionText: defaultGuideTranslations.steps.add_data.description, + linkUrl: 'https://docs.elastic.co/en/integrations/endpoint', + isLinkExternal: true, + linkText: i18n.LINK_TEXT, + }, + integration: 'endpoint', + location: { + appID: 'integrations', + path: '/browse/security', + }, + }, + { + id: 'rules', + title: defaultGuideTranslations.steps.rules.title, + description: launchDarkly.steps.rules.description, + location: { + appID: 'securitySolutionUI', + path: '/rules', + }, + manualCompletion: defaultGuideTranslations.steps.rules.manualCompletion, + }, + { + id: 'alertsCases', + title: defaultGuideTranslations.steps.alertsCases.title, + description: defaultGuideTranslations.steps.alertsCases.description, + manualCompletion: { + title: launchDarkly.steps.alertsCases.manualCompletion.title, + description: defaultGuideTranslations.steps.alertsCases.manualCompletion.description, + }, + location: { + appID: 'securitySolutionUI', + path: '/alerts', + }, + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts index 707595d0bd8f8..f260c94262513 100644 --- a/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts +++ b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts @@ -6,50 +6,63 @@ */ import type { GuideConfig } from '@kbn/guided-onboarding'; -import { i18n } from '@kbn/i18n'; +import * as i18n from './translations'; export const siemGuideId = 'siem'; -export const siemGuideConfig: GuideConfig = { - title: i18n.translate('xpack.securitySolution.guideConfig.title', { - defaultMessage: 'Detect threats in my data with SIEM', - }), + +export const defaultGuideTranslations = { + title: i18n.TITLE, + description: i18n.DESCRIPTION, + docs: i18n.DOCS, + steps: { + add_data: { + title: i18n.ADD_DATA_TITLE, + description: i18n.ADD_DATA_DESCRIPTION, + }, + rules: { + title: i18n.RULES_TITLE, + description: i18n.RULES_DESCRIPTION, + manualCompletion: { + title: i18n.RULES_MANUAL_TITLE, + description: i18n.RULES_MANUAL_DESCRIPTION, + }, + }, + alertsCases: { + title: i18n.CASES_TITLE, + description: i18n.CASES_DESCRIPTION, + manualCompletion: { + title: i18n.CASES_MANUAL_TITLE, + description: i18n.CASES_MANUAL_DESCRIPTION, + }, + }, + }, +}; + +export const getSiemGuideConfig = (launchDarkly = defaultGuideTranslations): GuideConfig => ({ + // check each launchDarkly property in case data is misformatted + title: launchDarkly.title ?? defaultGuideTranslations.title, guideName: 'Security', - telemetryId: 'siem', + telemetryId: siemGuideId, completedGuideRedirectLocation: { appID: 'securitySolutionUI', path: '/dashboards', }, - description: i18n.translate('xpack.securitySolution.guideConfig.description', { - defaultMessage: `There are many ways to get your SIEM data into Elastic. In this guide, we'll help you get set up quickly using the Elastic Defend integration.`, - }), + description: launchDarkly.description ?? defaultGuideTranslations.description, docs: { - text: i18n.translate('xpack.securitySolution.guideConfig.documentationLink', { - defaultMessage: 'Learn more', - }), + text: launchDarkly.docs ?? defaultGuideTranslations.docs, url: 'https://www.elastic.co/guide/en/security/current/ingest-data.html', }, steps: [ { id: 'add_data', - title: i18n.translate('xpack.securitySolution.guideConfig.addDataStep.title', { - defaultMessage: 'Add data with Elastic Defend', - }), + title: launchDarkly.steps?.add_data?.title ?? defaultGuideTranslations.steps.add_data.title, description: { - descriptionText: i18n.translate( - 'xpack.securitySolution.guideConfig.addDataStep.description', - { - defaultMessage: - 'Install Elastic Agent and its Elastic Defend integration on one of your computers to get SIEM data flowing.', - } - ), + descriptionText: + launchDarkly.steps?.add_data?.description ?? + defaultGuideTranslations.steps.add_data.description, linkUrl: 'https://docs.elastic.co/en/integrations/endpoint', isLinkExternal: true, - linkText: i18n.translate( - 'xpack.securitySolution.guideConfig.addDataStep.description.linkText', - { - defaultMessage: 'Learn more', - } - ), + linkText: i18n.LINK_TEXT, }, integration: 'endpoint', location: { @@ -59,26 +72,16 @@ export const siemGuideConfig: GuideConfig = { }, { id: 'rules', - title: i18n.translate('xpack.securitySolution.guideConfig.rulesStep.title', { - defaultMessage: 'Turn on rules', - }), - description: i18n.translate('xpack.securitySolution.guideConfig.rulesStep.description', { - defaultMessage: - 'Load the Elastic prebuilt rules, select the rules you want, and enable them to generate alerts.', - }), + title: launchDarkly.steps?.rules?.title ?? defaultGuideTranslations.steps.rules.title, + description: + launchDarkly.steps?.rules?.description ?? defaultGuideTranslations.steps.rules.description, manualCompletion: { - title: i18n.translate( - 'xpack.securitySolution.guideConfig.rulesStep.manualCompletion.title', - { - defaultMessage: 'Continue with the guide', - } - ), - description: i18n.translate( - 'xpack.securitySolution.guideConfig.rulesStep.manualCompletion.description', - { - defaultMessage: `After you've enabled the rules you need, continue.`, - } - ), + title: + launchDarkly.steps?.rules?.manualCompletion?.title ?? + defaultGuideTranslations.steps.rules.manualCompletion.title, + description: + launchDarkly.steps?.rules?.manualCompletion?.description ?? + defaultGuideTranslations.steps.rules.manualCompletion.description, }, location: { appID: 'securitySolutionUI', @@ -87,30 +90,23 @@ export const siemGuideConfig: GuideConfig = { }, { id: 'alertsCases', - title: i18n.translate('xpack.securitySolution.guideConfig.alertsStep.title', { - defaultMessage: 'Manage alerts and cases', - }), - description: i18n.translate('xpack.securitySolution.guideConfig.alertsStep.description', { - defaultMessage: 'Learn how to view and triage alerts with cases.', - }), + title: + launchDarkly.steps?.alertsCases?.title ?? defaultGuideTranslations.steps.alertsCases.title, + description: + launchDarkly.steps?.alertsCases?.description ?? + defaultGuideTranslations.steps.alertsCases.description, location: { appID: 'securitySolutionUI', path: '/alerts', }, manualCompletion: { - title: i18n.translate( - 'xpack.securitySolution.guideConfig.alertsStep.manualCompletion.title', - { - defaultMessage: 'Continue the guide', - } - ), - description: i18n.translate( - 'xpack.securitySolution.guideConfig.alertsStep.manualCompletion.description', - { - defaultMessage: `After you've explored the case, continue.`, - } - ), + title: + launchDarkly.steps?.alertsCases?.manualCompletion?.title ?? + defaultGuideTranslations.steps.alertsCases.manualCompletion.title, + description: + launchDarkly.steps?.alertsCases?.manualCompletion?.description ?? + defaultGuideTranslations.steps.alertsCases.manualCompletion.description, }, }, ], -}; +}); diff --git a/x-pack/plugins/security_solution/common/guided_onboarding/translations.ts b/x-pack/plugins/security_solution/common/guided_onboarding/translations.ts new file mode 100644 index 0000000000000..9d96333796a34 --- /dev/null +++ b/x-pack/plugins/security_solution/common/guided_onboarding/translations.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const TITLE = i18n.translate('xpack.securitySolution.guideConfig.title', { + defaultMessage: 'Detect threats in my data with SIEM', +}); + +export const DESCRIPTION = i18n.translate('xpack.securitySolution.guideConfig.description', { + defaultMessage: `There are many ways to get your SIEM data into Elastic. In this guide, we'll help you get set up quickly using the Elastic Defend integration.`, +}); + +export const DOCS = i18n.translate('xpack.securitySolution.guideConfig.documentationLink', { + defaultMessage: 'Learn more', +}); + +export const LINK_TEXT = i18n.translate( + 'xpack.securitySolution.guideConfig.addDataStep.description.linkText', + { + defaultMessage: 'Learn more', + } +); + +export const ADD_DATA_TITLE = i18n.translate( + 'xpack.securitySolution.guideConfig.addDataStep.title', + { + defaultMessage: 'Add data with Elastic Defend', + } +); + +export const ADD_DATA_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.guideConfig.addDataStep.description', + { + defaultMessage: + 'Install Elastic Agent and its Elastic Defend integration on one of your computers to get SIEM data flowing.', + } +); + +export const RULES_TITLE = i18n.translate('xpack.securitySolution.guideConfig.rulesStep.title', { + defaultMessage: 'Turn on rules', +}); + +export const RULES_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.guideConfig.rulesStep.description', + { + defaultMessage: + 'Load the Elastic prebuilt rules, select the rules you want, and enable them to generate alerts.', + } +); + +export const RULES_MANUAL_TITLE = i18n.translate( + 'xpack.securitySolution.guideConfig.rulesStep.manualCompletion.title', + { + defaultMessage: 'Continue with the guide', + } +); + +export const RULES_MANUAL_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.guideConfig.rulesStep.manualCompletion.description', + { + defaultMessage: `After you've enabled the rules you need, continue.`, + } +); + +export const CASES_TITLE = i18n.translate('xpack.securitySolution.guideConfig.alertsStep.title', { + defaultMessage: 'Manage alerts and cases', +}); +export const CASES_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.guideConfig.alertsStep.description', + { + defaultMessage: 'Learn how to view and triage alerts with cases.', + } +); + +export const CASES_MANUAL_TITLE = i18n.translate( + 'xpack.securitySolution.guideConfig.alertsStep.manualCompletion.title', + { + defaultMessage: 'Continue the guide', + } +); + +export const CASES_MANUAL_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.guideConfig.alertsStep.manualCompletion.description', + { + defaultMessage: `After you've explored the case, continue.`, + } +); diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts index f047d201dfd14..4f6b8c78f86a0 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts @@ -6,13 +6,14 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/public'; +import type { EventEnrichmentRequestOptions } from '../../../api/search_strategy'; -import type { CtiEnrichment, CtiEventEnrichmentRequestOptions } from '.'; +import type { CtiEnrichment } from '.'; import { CtiQueries } from '.'; export const buildEventEnrichmentRequestOptionsMock = ( - overrides: Partial = {} -): CtiEventEnrichmentRequestOptions => ({ + overrides: Partial = {} +): EventEnrichmentRequestOptions => ({ defaultIndex: ['filebeat-*'], eventFields: { 'file.hash.md5': '1eee2bf3f56d8abed72da2bc523e7431', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts index f2fe3e3fee37d..fce4e7bdd661d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts @@ -6,20 +6,11 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { IEsSearchResponse, IEsSearchRequest } from '@kbn/data-plugin/public'; -import type { FactoryQueryTypes } from '../..'; +import type { IEsSearchResponse } from '@kbn/data-plugin/public'; import { EVENT_ENRICHMENT_INDICATOR_FIELD_MAP } from '../../../cti/constants'; -import type { Inspect, Maybe, TimerangeInput } from '../../common'; -import type { RequestBasicOptions } from '..'; +import type { Inspect, Maybe } from '../../common'; -export enum CtiQueries { - eventEnrichment = 'eventEnrichment', - dataSource = 'dataSource', -} - -export interface CtiEventEnrichmentRequestOptions extends RequestBasicOptions { - eventFields: Record; -} +export { CtiQueries } from '../../../api/search_strategy'; export type CtiEnrichment = Record; export type EventFields = Record; @@ -44,12 +35,6 @@ export const validEventFields = Object.keys(EVENT_ENRICHMENT_INDICATOR_FIELD_MAP export const isValidEventField = (field: string): field is EventField => validEventFields.includes(field as EventField); -export interface CtiDataSourceRequestOptions extends IEsSearchRequest { - defaultIndex: string[]; - factoryQueryType?: FactoryQueryTypes; - timerange?: TimerangeInput; -} - export interface BucketItem { key: string; doc_count: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/first_last_seen/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/first_last_seen/index.ts index 3750345091ee1..d71dc2011eb5b 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/first_last_seen/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/first_last_seen/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export const FirstLastSeenQuery = 'firstlastseen'; +export { FirstLastSeenQuery } from '../../../api/search_strategy'; export type { FirstLastSeenRequestOptions, diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts index ea7daf750de4a..888aa7a11c7ee 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts @@ -7,9 +7,9 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { HostItem, HostsFields } from '../common'; +import type { HostsFields } from '../../../../api/search_strategy/hosts/model/sort'; +import type { HostItem } from '../common'; import type { CursorType, Direction, Inspect, Maybe, PageInfoPaginated } from '../../../common'; -import type { RequestOptionsPaginated } from '../..'; export interface HostsEdges { node: HostItem; @@ -23,11 +23,6 @@ export interface HostsStrategyResponse extends IEsSearchResponse { inspect?: Maybe; } -export interface HostsRequestOptions extends RequestOptionsPaginated { - defaultIndex: string[]; - isNewRiskScoreModuleAvailable: boolean; -} - export interface HostsSortField { field: HostsFields; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts index 5bd4238ff0fd7..b434ee20cfb03 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -17,11 +17,6 @@ export enum HostPolicyResponseActionStatus { unsupported = 'unsupported', } -export enum HostsFields { - lastSeen = 'lastSeen', - hostName = 'hostName', -} - export interface EndpointFields { /** A count of pending endpoint actions against the host */ pendingActions?: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts index 4a1f6384214ca..52a4b2b531717 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts @@ -8,21 +8,15 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { Inspect, Maybe, TimerangeInput } from '../../../common'; -import type { HostItem, HostsFields } from '../common'; -import type { RequestOptionsPaginated } from '../..'; +import type { Inspect, Maybe } from '../../../common'; +import type { HostItem } from '../common'; export interface HostDetailsStrategyResponse extends IEsSearchResponse { hostDetails: HostItem; inspect?: Maybe; } -export interface HostDetailsRequestOptions extends Partial> { - hostName: string; - skip?: boolean; - timerange: TimerangeInput; - inspect?: Maybe; -} +export type { HostDetailsRequestOptions } from '../../../../api/search_strategy'; export interface AggregationRequest { [aggField: string]: estypes.AggregationsAggregationContainer; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index 52ec1aa5b76e8..3643f036ad563 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { HostsFields } from '../../../api/search_strategy/hosts/model/sort'; + export * from './all'; export * from './common'; export * from './details'; @@ -12,9 +14,6 @@ export * from './kpi'; export * from './overview'; export * from './uncommon_processes'; -export enum HostsQueries { - details = 'hostDetails', - hosts = 'hosts', - overview = 'overviewHost', - uncommonProcesses = 'uncommonProcesses', -} +export { HostsQueries } from '../../../api/search_strategy'; + +export { HostsFields }; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts index 9f92e0b91fe99..313275ce3c944 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts @@ -7,11 +7,8 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; import type { HostsKpiHistogramData } from '../common'; -export type HostsKpiHostsRequestOptions = RequestBasicOptions; - export interface HostsKpiHostsStrategyResponse extends IEsSearchResponse { hosts: Maybe; hostsHistogram: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts index beab78122e2a2..22cbad1ffd44a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts @@ -7,11 +7,8 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; import type { HostsKpiHistogramData } from '../common'; -export type HostsKpiUniqueIpsRequestOptions = RequestBasicOptions; - export interface HostsKpiUniqueIpsStrategyResponse extends IEsSearchResponse { uniqueSourceIps: Maybe; uniqueSourceIpsHistogram: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts index 87b348db6c304..9bd64e6ec1dda 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe, SearchHit } from '../../../common'; -import type { RequestBasicOptions } from '../..'; - -export type HostOverviewRequestOptions = RequestBasicOptions; export interface HostsOverviewStrategyResponse extends IEsSearchResponse { inspect?: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/uncommon_processes/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/uncommon_processes/index.ts index 584cf223eb38b..568de6cd75bf4 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/uncommon_processes/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/uncommon_processes/index.ts @@ -9,8 +9,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { HostEcs, ProcessEcs, UserEcs } from '@kbn/securitysolution-ecs'; import type { - RequestOptionsPaginated, - SortField, CursorType, Inspect, Maybe, @@ -22,11 +20,6 @@ import type { CommonFields, } from '../../..'; -export interface HostsUncommonProcessesRequestOptions extends RequestOptionsPaginated { - sort: SortField; - defaultIndex: string[]; -} - export interface HostsUncommonProcessesStrategyResponse extends IEsSearchResponse { edges: HostsUncommonProcessesEdges[]; totalCount: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index c9220132f9a54..c34006b2ab081 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -4,114 +4,129 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { IEsSearchRequest } from '@kbn/data-plugin/common'; -import type { ESQuery } from '../../typed_json'; + import type { HostDetailsStrategyResponse, - HostDetailsRequestOptions, HostsOverviewStrategyResponse, - HostOverviewRequestOptions, HostsQueries, - HostsRequestOptions, HostsStrategyResponse, HostsUncommonProcessesStrategyResponse, - HostsUncommonProcessesRequestOptions, HostsKpiQueries, HostsKpiHostsStrategyResponse, - HostsKpiHostsRequestOptions, HostsKpiUniqueIpsStrategyResponse, - HostsKpiUniqueIpsRequestOptions, } from './hosts'; import type { NetworkQueries, NetworkDetailsStrategyResponse, - NetworkDetailsRequestOptions, NetworkDnsStrategyResponse, - NetworkDnsRequestOptions, NetworkTlsStrategyResponse, - NetworkTlsRequestOptions, NetworkHttpStrategyResponse, - NetworkHttpRequestOptions, NetworkOverviewStrategyResponse, - NetworkOverviewRequestOptions, NetworkTopCountriesStrategyResponse, - NetworkTopCountriesRequestOptions, NetworkTopNFlowStrategyResponse, - NetworkTopNFlowRequestOptions, NetworkUsersStrategyResponse, - NetworkUsersRequestOptions, NetworkKpiQueries, NetworkKpiDnsStrategyResponse, - NetworkKpiDnsRequestOptions, NetworkKpiNetworkEventsStrategyResponse, - NetworkKpiNetworkEventsRequestOptions, NetworkKpiTlsHandshakesStrategyResponse, - NetworkKpiTlsHandshakesRequestOptions, NetworkKpiUniqueFlowsStrategyResponse, - NetworkKpiUniqueFlowsRequestOptions, NetworkKpiUniquePrivateIpsStrategyResponse, - NetworkKpiUniquePrivateIpsRequestOptions, } from './network'; +import type { MatrixHistogramQuery, MatrixHistogramStrategyResponse } from './matrix_histogram'; import type { - MatrixHistogramQuery, - MatrixHistogramRequestOptions, - MatrixHistogramStrategyResponse, -} from './matrix_histogram'; -import type { TimerangeInput, SortField, PaginationInputPaginated } from '../common'; -import type { - CtiEventEnrichmentRequestOptions, CtiEventEnrichmentStrategyResponse, CtiQueries, - CtiDataSourceRequestOptions, CtiDataSourceStrategyResponse, } from './cti'; import type { RiskQueries, KpiRiskScoreStrategyResponse, - KpiRiskScoreRequestOptions, HostsRiskScoreStrategyResponse, UsersRiskScoreStrategyResponse, - RiskScoreRequestOptions, } from './risk_score'; import type { UsersQueries } from './users'; -import type { - ObservedUserDetailsRequestOptions, - ObservedUserDetailsStrategyResponse, -} from './users/observed_details'; -import type { - TotalUsersKpiRequestOptions, - TotalUsersKpiStrategyResponse, -} from './users/kpi/total_users'; +import type { ObservedUserDetailsStrategyResponse } from './users/observed_details'; +import type { TotalUsersKpiStrategyResponse } from './users/kpi/total_users'; -import type { - UsersKpiAuthenticationsRequestOptions, - UsersKpiAuthenticationsStrategyResponse, -} from './users/kpi/authentications'; +import type { UsersKpiAuthenticationsStrategyResponse } from './users/kpi/authentications'; + +import type { UsersStrategyResponse } from './users/all'; +import type { UserAuthenticationsStrategyResponse } from './users/authentications'; +import type { FirstLastSeenQuery, FirstLastSeenStrategyResponse } from './first_last_seen'; +import type { ManagedUserDetailsStrategyResponse } from './users/managed_details'; +import type { RelatedEntitiesQueries } from './related_entities'; +import type { UsersRelatedHostsStrategyResponse } from './related_entities/related_hosts'; +import type { HostsRelatedUsersStrategyResponse } from './related_entities/related_users'; -import type { UsersRequestOptions, UsersStrategyResponse } from './users/all'; -import type { - UserAuthenticationsRequestOptions, - UserAuthenticationsStrategyResponse, -} from './users/authentications'; import type { - FirstLastSeenQuery, + AuthenticationsKpiRequestOptions, + AuthenticationsKpiRequestOptionsInput, + EventEnrichmentRequestOptions, + EventEnrichmentRequestOptionsInput, FirstLastSeenRequestOptions, - FirstLastSeenStrategyResponse, -} from './first_last_seen'; -import type { + FirstLastSeenRequestOptionsInput, + HostDetailsRequestOptions, + HostDetailsRequestOptionsInput, + HostOverviewRequestOptions, + HostOverviewRequestOptionsInput, + HostsRequestOptions, + HostsRequestOptionsInput, + HostUncommonProcessesRequestOptions, + HostUncommonProcessesRequestOptionsInput, + KpiHostsRequestOptions, + KpiHostsRequestOptionsInput, + KpiUniqueIpsRequestOptions, + KpiUniqueIpsRequestOptionsInput, ManagedUserDetailsRequestOptions, - ManagedUserDetailsStrategyResponse, -} from './users/managed_details'; -import type { RelatedEntitiesQueries } from './related_entities'; -import type { - UsersRelatedHostsRequestOptions, - UsersRelatedHostsStrategyResponse, -} from './related_entities/related_hosts'; -import type { - HostsRelatedUsersRequestOptions, - HostsRelatedUsersStrategyResponse, -} from './related_entities/related_users'; + ManagedUserDetailsRequestOptionsInput, + MatrixHistogramRequestOptions, + MatrixHistogramRequestOptionsInput, + NetworkDetailsRequestOptions, + NetworkDetailsRequestOptionsInput, + NetworkDnsRequestOptions, + NetworkDnsRequestOptionsInput, + NetworkHttpRequestOptions, + NetworkHttpRequestOptionsInput, + NetworkKpiDnsRequestOptions, + NetworkKpiDnsRequestOptionsInput, + NetworkKpiEventsRequestOptions, + NetworkKpiEventsRequestOptionsInput, + NetworkKpiTlsHandshakesRequestOptions, + NetworkKpiTlsHandshakesRequestOptionsInput, + NetworkKpiUniqueFlowsRequestOptions, + NetworkKpiUniqueFlowsRequestOptionsInput, + NetworkKpiUniquePrivateIpsRequestOptions, + NetworkKpiUniquePrivateIpsRequestOptionsInput, + NetworkOverviewRequestOptions, + NetworkOverviewRequestOptionsInput, + NetworkTlsRequestOptions, + NetworkTlsRequestOptionsInput, + NetworkTopCountriesRequestOptions, + NetworkTopCountriesRequestOptionsInput, + NetworkTopNFlowRequestOptions, + NetworkTopNFlowRequestOptionsInput, + NetworkUsersRequestOptions, + NetworkUsersRequestOptionsInput, + ObservedUserDetailsRequestOptions, + ObservedUserDetailsRequestOptionsInput, + RelatedHostsRequestOptions, + RelatedHostsRequestOptionsInput, + RelatedUsersRequestOptions, + RelatedUsersRequestOptionsInput, + RiskScoreKpiRequestOptions, + RiskScoreKpiRequestOptionsInput, + RiskScoreRequestOptions, + RiskScoreRequestOptionsInput, + ThreatIntelSourceRequestOptions, + ThreatIntelSourceRequestOptionsInput, + TotalUsersKpiRequestOptions, + TotalUsersKpiRequestOptionsInput, + UserAuthenticationsRequestOptions, + UserAuthenticationsRequestOptionsInput, + UsersRequestOptions, + UsersRequestOptionsInput, +} from '../../api/search_strategy'; export * from './cti'; export * from './hosts'; @@ -134,20 +149,6 @@ export type FactoryQueryTypes = | typeof FirstLastSeenQuery | RelatedEntitiesQueries; -export interface RequestBasicOptions extends IEsSearchRequest { - timerange: TimerangeInput; - filterQuery: ESQuery | string | undefined; - defaultIndex: string[]; - factoryQueryType?: FactoryQueryTypes; -} - -/** A mapping of semantic fields to their document counterparts */ - -export interface RequestOptionsPaginated extends RequestBasicOptions { - pagination: PaginationInputPaginated; - sort: SortField; -} - export type StrategyResponseType = T extends HostsQueries.hosts ? HostsStrategyResponse : T extends HostsQueries.details @@ -218,6 +219,76 @@ export type StrategyResponseType = T extends HostsQ ? UsersRelatedHostsStrategyResponse : never; +export type StrategyRequestInputType = T extends HostsQueries.hosts + ? HostsRequestOptionsInput + : T extends HostsQueries.details + ? HostDetailsRequestOptionsInput + : T extends HostsQueries.overview + ? HostOverviewRequestOptionsInput + : T extends typeof FirstLastSeenQuery + ? FirstLastSeenRequestOptionsInput + : T extends HostsQueries.uncommonProcesses + ? HostUncommonProcessesRequestOptionsInput + : T extends HostsKpiQueries.kpiHosts + ? KpiHostsRequestOptionsInput + : T extends HostsKpiQueries.kpiUniqueIps + ? KpiUniqueIpsRequestOptionsInput + : T extends UsersQueries.authentications + ? UserAuthenticationsRequestOptionsInput + : T extends UsersQueries.observedDetails + ? ObservedUserDetailsRequestOptionsInput + : T extends UsersQueries.managedDetails + ? ManagedUserDetailsRequestOptionsInput + : T extends UsersQueries.kpiTotalUsers + ? TotalUsersKpiRequestOptionsInput + : T extends UsersQueries.users + ? UsersRequestOptionsInput + : T extends UsersQueries.kpiAuthentications + ? AuthenticationsKpiRequestOptionsInput + : T extends NetworkQueries.details + ? NetworkDetailsRequestOptionsInput + : T extends NetworkQueries.dns + ? NetworkDnsRequestOptionsInput + : T extends NetworkQueries.http + ? NetworkHttpRequestOptionsInput + : T extends NetworkQueries.overview + ? NetworkOverviewRequestOptionsInput + : T extends NetworkQueries.tls + ? NetworkTlsRequestOptionsInput + : T extends NetworkQueries.topCountries + ? NetworkTopCountriesRequestOptionsInput + : T extends NetworkQueries.topNFlow + ? NetworkTopNFlowRequestOptionsInput + : T extends NetworkQueries.users + ? NetworkUsersRequestOptionsInput + : T extends NetworkKpiQueries.dns + ? NetworkKpiDnsRequestOptionsInput + : T extends NetworkKpiQueries.networkEvents + ? NetworkKpiEventsRequestOptionsInput + : T extends NetworkKpiQueries.tlsHandshakes + ? NetworkKpiTlsHandshakesRequestOptionsInput + : T extends NetworkKpiQueries.uniqueFlows + ? NetworkKpiUniqueFlowsRequestOptionsInput + : T extends NetworkKpiQueries.uniquePrivateIps + ? NetworkKpiUniquePrivateIpsRequestOptionsInput + : T extends typeof MatrixHistogramQuery + ? MatrixHistogramRequestOptionsInput + : T extends CtiQueries.eventEnrichment + ? EventEnrichmentRequestOptionsInput + : T extends CtiQueries.dataSource + ? ThreatIntelSourceRequestOptionsInput + : T extends RiskQueries.hostsRiskScore + ? RiskScoreRequestOptionsInput + : T extends RiskQueries.usersRiskScore + ? RiskScoreRequestOptionsInput + : T extends RiskQueries.kpiRiskScore + ? RiskScoreKpiRequestOptionsInput + : T extends RelatedEntitiesQueries.relatedHosts + ? RelatedHostsRequestOptionsInput + : T extends RelatedEntitiesQueries.relatedUsers + ? RelatedUsersRequestOptionsInput + : never; + export type StrategyRequestType = T extends HostsQueries.hosts ? HostsRequestOptions : T extends HostsQueries.details @@ -227,11 +298,11 @@ export type StrategyRequestType = T extends HostsQu : T extends typeof FirstLastSeenQuery ? FirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses - ? HostsUncommonProcessesRequestOptions + ? HostUncommonProcessesRequestOptions : T extends HostsKpiQueries.kpiHosts - ? HostsKpiHostsRequestOptions + ? KpiHostsRequestOptions : T extends HostsKpiQueries.kpiUniqueIps - ? HostsKpiUniqueIpsRequestOptions + ? KpiUniqueIpsRequestOptions : T extends UsersQueries.authentications ? UserAuthenticationsRequestOptions : T extends UsersQueries.observedDetails @@ -243,7 +314,7 @@ export type StrategyRequestType = T extends HostsQu : T extends UsersQueries.users ? UsersRequestOptions : T extends UsersQueries.kpiAuthentications - ? UsersKpiAuthenticationsRequestOptions + ? AuthenticationsKpiRequestOptions : T extends NetworkQueries.details ? NetworkDetailsRequestOptions : T extends NetworkQueries.dns @@ -263,7 +334,7 @@ export type StrategyRequestType = T extends HostsQu : T extends NetworkKpiQueries.dns ? NetworkKpiDnsRequestOptions : T extends NetworkKpiQueries.networkEvents - ? NetworkKpiNetworkEventsRequestOptions + ? NetworkKpiEventsRequestOptions : T extends NetworkKpiQueries.tlsHandshakes ? NetworkKpiTlsHandshakesRequestOptions : T extends NetworkKpiQueries.uniqueFlows @@ -273,19 +344,19 @@ export type StrategyRequestType = T extends HostsQu : T extends typeof MatrixHistogramQuery ? MatrixHistogramRequestOptions : T extends CtiQueries.eventEnrichment - ? CtiEventEnrichmentRequestOptions + ? EventEnrichmentRequestOptions : T extends CtiQueries.dataSource - ? CtiDataSourceRequestOptions + ? ThreatIntelSourceRequestOptions : T extends RiskQueries.hostsRiskScore ? RiskScoreRequestOptions : T extends RiskQueries.usersRiskScore ? RiskScoreRequestOptions : T extends RiskQueries.kpiRiskScore - ? KpiRiskScoreRequestOptions + ? RiskScoreKpiRequestOptions : T extends RelatedEntitiesQueries.relatedHosts - ? UsersRelatedHostsRequestOptions + ? RelatedHostsRequestOptions : T extends RelatedEntitiesQueries.relatedUsers - ? HostsRelatedUsersRequestOptions + ? RelatedUsersRequestOptions : never; export interface CommonFields { diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts index 882cbd1717f83..7677b4b5b8824 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts @@ -5,10 +5,9 @@ * 2.0. */ -import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { Inspect, Maybe, TimerangeInput } from '../../common'; -import type { RequestBasicOptions } from '..'; +import type { MatrixHistogramRequestOptions } from '../../../api/search_strategy/matrix_histogram/matrix_histogram'; +import type { Inspect, Maybe } from '../../common'; import type { AlertsGroupData } from './alerts'; import type { AnomaliesActionGroupData } from './anomalies'; import type { DnsHistogramGroupData } from './dns'; @@ -24,7 +23,7 @@ export * from './dns'; export * from './events'; export * from './preview'; -export const MatrixHistogramQuery = 'matrixHistogram'; +export { MatrixHistogramQuery } from '../../../api/search_strategy'; export enum MatrixHistogramType { authentications = 'authentications', @@ -44,26 +43,6 @@ export const MatrixHistogramTypeToAggName = { [MatrixHistogramType.preview]: 'aggregations.preview.buckets', }; -export interface MatrixHistogramRequestOptions extends RequestBasicOptions { - timerange: TimerangeInput; - histogramType: MatrixHistogramType; - stackByField: string; - threshold?: - | { - field: string[]; - value: string; - cardinality?: { - field: string[]; - value: string; - }; - } - | undefined; - inspect?: Maybe; - isPtrIncluded?: boolean; - includeMissingData?: boolean; - runtimeMappings?: MappingRuntimeFields; -} - export interface MatrixHistogramStrategyResponse extends IEsSearchResponse { inspect?: Maybe; matrixHistogramData: MatrixHistogramData[]; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/details/index.ts index da3b7cf9cbac3..e6f6b8fc12acd 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/details/index.ts @@ -8,11 +8,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { HostEcs, GeoEcs } from '@kbn/securitysolution-ecs'; import type { Inspect, Maybe, TotalValue, Hit, ShardsResponse } from '../../../common'; -import type { RequestBasicOptions } from '../..'; - -export interface NetworkDetailsRequestOptions extends Omit { - ip: string; -} export interface NetworkDetailsStrategyResponse extends IEsSearchResponse { networkDetails: { diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/dns/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/dns/index.ts index cb0fa20deb36d..e36dca6658772 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/dns/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/dns/index.ts @@ -6,8 +6,7 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { CursorType, Inspect, Maybe, PageInfoPaginated, SortField } from '../../../common'; -import type { RequestOptionsPaginated } from '../..'; +import type { CursorType, Inspect, Maybe, PageInfoPaginated } from '../../../common'; export enum NetworkDnsFields { dnsName = 'dnsName', @@ -17,12 +16,6 @@ export enum NetworkDnsFields { dnsBytesOut = 'dnsBytesOut', } -export interface NetworkDnsRequestOptions extends RequestOptionsPaginated { - isPtrIncluded: boolean; - sort: SortField; - stackByField?: Maybe; -} - export interface NetworkDnsStrategyResponse extends IEsSearchResponse { edges: NetworkDnsEdges[]; totalCount: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/http/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/http/index.ts index e523b4a1f45c4..a2a62ec7bee49 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/http/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/http/index.ts @@ -13,7 +13,6 @@ import type { PageInfoPaginated, GenericBuckets, } from '../../../common'; -import type { RequestOptionsPaginated } from '../..'; export enum NetworkHttpFields { domains = 'domains', @@ -25,11 +24,6 @@ export enum NetworkHttpFields { statuses = 'statuses', } -export interface NetworkHttpRequestOptions extends RequestOptionsPaginated { - ip?: string; - defaultIndex: string[]; -} - export interface NetworkHttpStrategyResponse extends IEsSearchResponse { edges: NetworkHttpEdges[]; totalCount: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts index 24c6484f94e71..469bd0eaf38bd 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts @@ -16,13 +16,4 @@ export * from './top_countries'; export * from './top_n_flow'; export * from './users'; -export enum NetworkQueries { - details = 'networkDetails', - dns = 'dns', - http = 'http', - overview = 'overviewNetwork', - tls = 'tls', - topCountries = 'topCountries', - topNFlow = 'topNFlow', - users = 'users', -} +export { NetworkQueries } from '../../../api/search_strategy'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts index 3c068014221be..3f006530aab19 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; - -export type NetworkKpiDnsRequestOptions = RequestBasicOptions; export interface NetworkKpiDnsStrategyResponse extends IEsSearchResponse { dnsQueries: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts index aa237a6b9e74e..781fd8ddfd90a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; - -export type NetworkKpiNetworkEventsRequestOptions = RequestBasicOptions; export interface NetworkKpiNetworkEventsStrategyResponse extends IEsSearchResponse { networkEvents: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts index 9c50f565806a9..c2219c6ec8233 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; - -export type NetworkKpiTlsHandshakesRequestOptions = RequestBasicOptions; export interface NetworkKpiTlsHandshakesStrategyResponse extends IEsSearchResponse { tlsHandshakes: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts index 339463323f72b..eba5f19ddc246 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; - -export type NetworkKpiUniqueFlowsRequestOptions = RequestBasicOptions; export interface NetworkKpiUniqueFlowsStrategyResponse extends IEsSearchResponse { uniqueFlowId: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts index 7df7050821526..d002452702332 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts @@ -7,15 +7,12 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; export interface NetworkKpiHistogramData { x?: Maybe; y?: Maybe; } -export type NetworkKpiUniquePrivateIpsRequestOptions = RequestBasicOptions; - export interface NetworkKpiUniquePrivateIpsStrategyResponse extends IEsSearchResponse { uniqueSourcePrivateIps: number; uniqueSourcePrivateIpsHistogram: NetworkKpiHistogramData[] | null; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts index 9548cf0f890c5..871bf38be855b 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, Maybe, SearchHit } from '../../../common'; -import type { RequestBasicOptions } from '../..'; - -export type NetworkOverviewRequestOptions = RequestBasicOptions; export interface NetworkOverviewStrategyResponse extends IEsSearchResponse { inspect?: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts index 7c854f6f29fa2..48ca7a133af1d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts @@ -7,8 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { CursorType, Inspect, Maybe, PageInfoPaginated } from '../../../common'; -import type { RequestOptionsPaginated } from '../..'; -import type { FlowTargetSourceDest } from '../common'; export interface NetworkTlsBuckets { key: string; @@ -48,12 +46,6 @@ export interface NetworkTlsEdges { cursor: CursorType; } -export interface NetworkTlsRequestOptions extends RequestOptionsPaginated { - ip: string; - flowTarget: FlowTargetSourceDest; - defaultIndex: string[]; -} - export interface NetworkTlsStrategyResponse extends IEsSearchResponse { edges: NetworkTlsEdges[]; totalCount: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_countries/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_countries/index.ts index 47989aa6ba49a..31aed363f3275 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_countries/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_countries/index.ts @@ -7,13 +7,7 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { CursorType, Inspect, Maybe, PageInfoPaginated } from '../../../common'; -import type { RequestOptionsPaginated } from '../..'; -import type { - GeoItem, - FlowTargetSourceDest, - NetworkTopTablesFields, - TopNetworkTablesEcsField, -} from '../common'; +import type { GeoItem, TopNetworkTablesEcsField } from '../common'; export interface TopCountriesItemSource { country?: Maybe; @@ -23,12 +17,6 @@ export interface TopCountriesItemSource { source_ips?: Maybe; } -export interface NetworkTopCountriesRequestOptions - extends RequestOptionsPaginated { - flowTarget: FlowTargetSourceDest; - ip?: string; -} - export interface NetworkTopCountriesStrategyResponse extends IEsSearchResponse { edges: NetworkTopCountriesEdges[]; totalCount: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_n_flow/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_n_flow/index.ts index 310476a470d30..0633dbc756828 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_n_flow/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/top_n_flow/index.ts @@ -6,12 +6,7 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { - GeoItem, - FlowTargetSourceDest, - TopNetworkTablesEcsField, - NetworkTopTablesFields, -} from '../common'; +import type { GeoItem, TopNetworkTablesEcsField } from '../common'; import type { CursorType, Inspect, @@ -20,13 +15,6 @@ import type { TotalValue, GenericBuckets, } from '../../../common'; -import type { RequestOptionsPaginated } from '../..'; - -export interface NetworkTopNFlowRequestOptions - extends RequestOptionsPaginated { - flowTarget: FlowTargetSourceDest; - ip?: Maybe; -} export interface NetworkTopNFlowStrategyResponse extends IEsSearchResponse { edges: NetworkTopNFlowEdges[]; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/users/index.ts index 49720c298338e..2ee9fd244e4bf 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/users/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/users/index.ts @@ -6,21 +6,13 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { CursorType, Inspect, Maybe, PageInfoPaginated, SortField } from '../../../common'; -import type { FlowTargetSourceDest } from '../common'; -import type { RequestOptionsPaginated } from '../..'; +import type { CursorType, Inspect, Maybe, PageInfoPaginated } from '../../../common'; export enum NetworkUsersFields { name = 'name', count = 'count', } -export interface NetworkUsersRequestOptions extends RequestOptionsPaginated { - ip: string; - sort: SortField; - flowTarget: FlowTargetSourceDest; -} - export interface NetworkUsersStrategyResponse extends IEsSearchResponse { edges: NetworkUsersEdges[]; totalCount: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/index.ts index d4f4507c1d577..06b2de80a0a0c 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/index.ts @@ -8,7 +8,4 @@ export * from './related_hosts'; export * from './related_users'; -export enum RelatedEntitiesQueries { - relatedHosts = 'relatedHosts', - relatedUsers = 'relatedUsers', -} +export { RelatedEntitiesQueries } from '../../../api/search_strategy'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_hosts/index.tsx b/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_hosts/index.tsx index 34cc6349dad1d..0ed809655e6d1 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_hosts/index.tsx +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_hosts/index.tsx @@ -7,7 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { RiskSeverity, Inspect, Maybe } from '../../..'; -import type { RequestBasicOptions } from '../..'; import type { BucketItem } from '../../cti'; export interface RelatedHost { @@ -33,11 +32,3 @@ export interface UsersRelatedHostsStrategyResponse extends IEsSearchResponse { relatedHosts: RelatedHost[]; inspect?: Maybe; } - -export interface UsersRelatedHostsRequestOptions extends Partial { - userName: string; - skip?: boolean; - from: string; - inspect?: Maybe; - isNewRiskScoreModuleAvailable: boolean; -} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_users/index.tsx b/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_users/index.tsx index da1708a2d7d8e..c5508dad58c4a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_users/index.tsx +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/related_entities/related_users/index.tsx @@ -7,7 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { RiskSeverity, Inspect, Maybe } from '../../..'; -import type { RequestBasicOptions } from '../..'; import type { BucketItem } from '../../cti'; export interface RelatedUser { @@ -33,11 +32,3 @@ export interface HostsRelatedUsersStrategyResponse extends IEsSearchResponse { relatedUsers: RelatedUser[]; inspect?: Maybe; } - -export interface HostsRelatedUsersRequestOptions extends Partial { - hostName: string; - skip?: boolean; - from: string; - inspect?: Maybe; - isNewRiskScoreModuleAvailable: boolean; -} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts index cd15bc763a391..28058b29eaada 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts @@ -5,28 +5,11 @@ * 2.0. */ -import type { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { ESQuery } from '../../../../typed_json'; +import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { Inspect, Maybe, SortField, TimerangeInput } from '../../../common'; -import type { RiskScoreEntity } from '../common'; +import type { Inspect, Maybe, SortField } from '../../../common'; import type { RiskInputs } from '../../../../risk_engine'; -export interface RiskScoreRequestOptions extends IEsSearchRequest { - defaultIndex: string[]; - riskScoreEntity: RiskScoreEntity; - timerange?: TimerangeInput; - alertsTimerange?: TimerangeInput; - includeAlertsCount?: boolean; - onlyLatest?: boolean; - pagination?: { - cursorStart: number; - querySize: number; - }; - sort?: RiskScoreSortField; - filterQuery?: ESQuery | string | undefined; -} - export interface HostsRiskScoreStrategyResponse extends IEsSearchResponse { inspect?: Maybe; totalCount: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts index 0cfef914b3638..1353ec7dd14bd 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts @@ -8,6 +8,7 @@ import type { ESQuery } from '../../../../typed_json'; import { RISKY_HOSTS_INDEX_PREFIX, RISKY_USERS_INDEX_PREFIX } from '../../../../constants'; import { RiskScoreEntity, getRiskScoreLatestIndex } from '../../../../risk_engine'; +export { RiskQueries } from '../../../../api/search_strategy'; /** * Make sure this aligns with the index in step 6, 9 in @@ -50,10 +51,4 @@ export const buildEntityNameFilter = ( : { terms: { 'user.name': entityNames } }; }; -export enum RiskQueries { - hostsRiskScore = 'hostsRiskScore', - usersRiskScore = 'usersRiskScore', - kpiRiskScore = 'kpiRiskScore', -} - export { RiskScoreEntity }; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/kpi/index.ts index 4d95846a4f740..25b5068700a41 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/kpi/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/kpi/index.ts @@ -5,19 +5,11 @@ * 2.0. */ -import type { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { FactoryQueryTypes, RiskScoreEntity, RiskSeverity } from '../..'; -import type { ESQuery } from '../../../../typed_json'; +import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { RiskSeverity } from '../..'; import type { Inspect, Maybe } from '../../../common'; -export interface KpiRiskScoreRequestOptions extends IEsSearchRequest { - defaultIndex: string[]; - factoryQueryType?: FactoryQueryTypes; - filterQuery?: ESQuery | string | undefined; - entity: RiskScoreEntity; -} - export interface KpiRiskScoreStrategyResponse extends IEsSearchResponse { inspect?: Maybe; kpiRiskScore: { diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts index 1ab46c75fa67b..264244962f46c 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts @@ -6,10 +6,7 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; - import type { Inspect, Maybe, PageInfoPaginated } from '../../../common'; -import type { RequestOptionsPaginated } from '../..'; -import type { SortableUsersFields } from '../common'; import type { RiskSeverity } from '../../risk_score'; export interface User { @@ -25,8 +22,3 @@ export interface UsersStrategyResponse extends IEsSearchResponse { pageInfo: PageInfoPaginated; inspect?: Maybe; } - -export interface UsersRequestOptions extends RequestOptionsPaginated { - defaultIndex: string[]; - isNewRiskScoreModuleAvailable: boolean; -} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/authentications/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/authentications/index.ts index 92d54ca56ba64..96d804d8e440a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/authentications/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/authentications/index.ts @@ -17,7 +17,7 @@ import type { Hit, TotalHit, } from '../../../common'; -import type { CommonFields, RequestOptionsPaginated } from '../..'; +import type { CommonFields } from '../..'; export interface UserAuthenticationsStrategyResponse extends IEsSearchResponse { edges: AuthenticationsEdges[]; @@ -26,11 +26,6 @@ export interface UserAuthenticationsStrategyResponse extends IEsSearchResponse { inspect?: Maybe; } -export interface UserAuthenticationsRequestOptions extends RequestOptionsPaginated { - defaultIndex: string[]; - stackByField: AuthStackByField; -} - export enum AuthStackByField { userName = 'user.name', hostName = 'host.name', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts index 9683b71babf7a..9989f2eb6d331 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts @@ -13,13 +13,6 @@ export * from './kpi'; export * from './observed_details'; export * from './authentications'; -export enum UsersQueries { - observedDetails = 'observedUserDetails', - managedDetails = 'managedUserDetails', - kpiTotalUsers = 'usersKpiTotalUsers', - users = 'allUsers', - authentications = 'authentications', - kpiAuthentications = 'usersKpiAuthentications', -} +export { UsersQueries } from '../../../api/search_strategy'; export type UsersKpiStrategyResponse = Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/authentications/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/authentications/index.ts index 84090bb7ab49f..cd6fd18b86c87 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/authentications/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/authentications/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, KpiHistogramData, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; - -export type UsersKpiAuthenticationsRequestOptions = RequestBasicOptions; export interface UsersKpiAuthenticationsStrategyResponse extends IEsSearchResponse { authenticationsSuccess: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts index 5fffe4ebe40c7..690678c1d78d9 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts @@ -7,9 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { Inspect, KpiHistogramData, Maybe } from '../../../../common'; -import type { RequestBasicOptions } from '../../..'; - -export type TotalUsersKpiRequestOptions = RequestBasicOptions; export interface TotalUsersKpiStrategyResponse extends IEsSearchResponse { users: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/index.ts index 06fdac65bc155..ea23e10709838 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/index.ts @@ -5,22 +5,15 @@ * 2.0. */ -import type { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { EcsBase, EcsEvent, EcsHost, EcsUser, EcsAgent } from '@kbn/ecs'; import type { Inspect, Maybe } from '../../../common'; -import type { RequestBasicOptions } from '../..'; export interface ManagedUserDetailsStrategyResponse extends IEsSearchResponse { userDetails?: AzureManagedUser; inspect?: Maybe; } -export interface ManagedUserDetailsRequestOptions - extends Pick, - IEsSearchRequest { - userName: string; -} - export interface AzureManagedUser extends Pick { agent: EcsAgent; host: EcsHost; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/observed_details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/observed_details/index.ts index a8c74932b0492..47aff3b0091fd 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/observed_details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/observed_details/index.ts @@ -7,18 +7,10 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { Inspect, Maybe, TimerangeInput } from '../../../common'; +import type { Inspect, Maybe } from '../../../common'; import type { UserItem } from '../common'; -import type { RequestBasicOptions } from '../..'; export interface ObservedUserDetailsStrategyResponse extends IEsSearchResponse { userDetails: UserItem; inspect?: Maybe; } - -export interface ObservedUserDetailsRequestOptions extends Partial { - userName: string; - skip?: boolean; - timerange: TimerangeInput; - inspect?: Maybe; -} diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts index e85679e6c5dd8..3fdf2cc22de6c 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts @@ -10,5 +10,4 @@ export type { TimelineItem, TimelineNonEcsData, TimelineEventsAllStrategyResponse, - TimelineEventsAllRequestOptions, } from '@kbn/timelines-plugin/common'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts index b8994774a1887..3cb50c2869935 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts @@ -8,5 +8,4 @@ export type { TimelineEventsDetailsItem, TimelineEventsDetailsStrategyResponse, - TimelineEventsDetailsRequestOptions, } from '@kbn/timelines-plugin/common'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts index 38ebb27e0416a..10f993b468189 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts @@ -6,7 +6,6 @@ */ export type { - TimelineEqlRequestOptions, TimelineEqlResponse, EqlOptionsData, EqlOptionsSelected, diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts index 924fff0230a14..9b95e7606d954 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts @@ -10,7 +10,7 @@ export { LastEventIndexKey } from '@kbn/timelines-plugin/common'; export type { LastTimeDetails, TimelineEventsLastEventTimeStrategyResponse, - TimelineKpiStrategyRequest, + TimelineKpiRequestOptionsInput, TimelineKpiStrategyResponse, - TimelineEventsLastEventTimeRequestOptions, + TimelineEventsLastEventTimeRequestOptionsInput, } from '@kbn/timelines-plugin/common'; diff --git a/x-pack/plugins/security_solution/kibana.jsonc b/x-pack/plugins/security_solution/kibana.jsonc index 91f366b5dd94f..a2e7af427da08 100644 --- a/x-pack/plugins/security_solution/kibana.jsonc +++ b/x-pack/plugins/security_solution/kibana.jsonc @@ -17,6 +17,7 @@ "cloud", "cloudDefend", "cloudSecurityPosture", + "contentManagement", "dashboard", "data", "dataViews", @@ -46,7 +47,6 @@ "files", "controls", "dataViewEditor", - "savedObjectsManagement", "stackConnectors", "discover", "notifications" diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index d29baae15d470..04ee03ecfed2e 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -7,15 +7,18 @@ "scripts": { "extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ./public/detections/mitre/mitre_tactics_techniques.ts --fix", "build-beat-doc": "node scripts/beat_docs/build.js && node ../../../scripts/eslint ../timelines/server/utils/beat_schema/fields.ts --fix", - "cypress": "../../../node_modules/.bin/cypress", + "cypress": "NODE_OPTIONS=--openssl-legacy-provider ../../../node_modules/.bin/cypress", "cypress:burn": "yarn cypress:dw run --env burn=2 --headed", "cypress:changed-specs-only": "yarn cypress:dw run --changed-specs-only --env burn=2", - "cypress:dw": "node ./scripts/start_cypress_parallel --config-file ./public/management/cypress.config.ts ts --ftr-config-file ../../test/defend_workflows_cypress/cli_config", + "cypress:dw": "NODE_OPTIONS=--openssl-legacy-provider node ./scripts/start_cypress_parallel --config-file ./public/management/cypress/cypress.config.ts --ftr-config-file ../../test/defend_workflows_cypress/cli_config", "cypress:dw:open": "yarn cypress:dw open", "cypress:dw:run": "yarn cypress:dw run", - "cypress:dw:endpoint": "node ./scripts/start_cypress_parallel --config-file ./public/management/cypress_endpoint.config.ts --ftr-config-file ../../test/defend_workflows_cypress/endpoint_config", - "cypress:dw:endpoint:run": "yarn cypress:dw:endpoint run", - "cypress:dw:endpoint:open": "yarn cypress:dw:endpoint open ", + "cypress:dw:serverless": "NODE_OPTIONS=--openssl-legacy-provider node ./scripts/start_cypress_parallel --config-file ./public/management/cypress/cypress_serverless.config.ts --ftr-config-file ../../../x-pack/test_serverless/functional/test_suites/security/cypress/security_config", + "cypress:dw:serverless:open": "yarn cypress:dw:serverless open", + "cypress:dw:serverless:run": "yarn cypress:dw:serverless run", + "cypress:dw:endpoint": "echo '\n** WARNING **: Run script `cypress:dw:endpoint` no longer valid! Use `cypress:dw` instead\n'", + "cypress:dw:endpoint:run": "echo '\n** WARNING **: Run script `cypress:dw:endpoint:run` no longer valid! Use `cypress:dw:run` instead\n'", + "cypress:dw:endpoint:open": "echo '\n** WARNING **: Run script `cypress:dw:endpoint:open` no longer valid! Use `cypress:dw:open` instead\n'", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results && yarn junit:transform && mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/", "test:generate": "node scripts/endpoint/resolver_generator", "mappings:generate": "node scripts/mappings/mappings_generator", @@ -24,4 +27,4 @@ "openapi:generate": "node scripts/openapi/generate", "openapi:generate:debug": "node --inspect-brk scripts/openapi/generate" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/app/404.tsx b/x-pack/plugins/security_solution/public/app/404.tsx index 562a542bb2a06..ce77baa895f12 100644 --- a/x-pack/plugins/security_solution/public/app/404.tsx +++ b/x-pack/plugins/security_solution/public/app/404.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiEmptyPrompt, EuiPageTemplate_Deprecated as EuiPageTemplate } from '@elastic/eui'; +import { EuiPageTemplate } from '@elastic/eui'; import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper'; export const NotFoundPage = React.memo(() => ( - - + = ({ const { i18n, application: { capabilities }, - http, - triggersActionsUi: { actionTypeRegistry }, uiActions, upselling, } = services; - const assistantAvailability = useAssistantAvailability(); - const { conversations, setConversations } = useConversationStore(); - const { defaultAllow, defaultAllowReplacement, setDefaultAllow, setDefaultAllowReplacement } = - useAnonymizationStore(); - - const getInitialConversation = useCallback(() => { - return conversations; - }, [conversations]); - - const nameSpace = `${APP_ID}.${LOCAL_STORAGE_KEY}`; - const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = useKibana().services.docLinks; - - const assistantTelemetry = useAssistantTelemetry(); - return ( @@ -95,29 +67,7 @@ const StartAppComponent: FC = ({ - + diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/assistant_header_link.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/assistant_header_link.tsx deleted file mode 100644 index 00e1acf7e45d5..0000000000000 --- a/x-pack/plugins/security_solution/public/app/home/global_header/assistant_header_link.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 { EuiFlexGroup, EuiFlexItem, EuiHeaderLink, EuiToolTip } from '@elastic/eui'; -import React, { useCallback } from 'react'; - -import { i18n } from '@kbn/i18n'; -import { useAssistantContext } from '@kbn/elastic-assistant/impl/assistant_context'; -import { AssistantAvatar } from '@kbn/elastic-assistant'; - -const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; - -/** - * Elastic AI Assistant header link - */ -export const AssistantHeaderLink = React.memo(() => { - const { showAssistantOverlay } = useAssistantContext(); - - const keyboardShortcut = isMac ? '⌘ ;' : 'Ctrl ;'; - - const tooltipContent = i18n.translate( - 'xpack.securitySolution.globalHeader.assistantHeaderLinkShortcutTooltip', - { - values: { keyboardShortcut }, - defaultMessage: 'Keyboard shortcut {keyboardShortcut}', - } - ); - - const showOverlay = useCallback( - () => showAssistantOverlay({ showOverlay: true }), - [showAssistantOverlay] - ); - - return ( - - - - - - - - {i18n.translate('xpack.securitySolution.globalHeader.assistantHeaderLink', { - defaultMessage: 'AI Assistant', - })} - - - - - ); -}); - -AssistantHeaderLink.displayName = 'AssistantHeaderLink'; diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx index 7dc0b338193a9..fe0b5bd500dc8 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx @@ -26,7 +26,6 @@ import { TimelineId } from '../../../../common/types/timeline'; import { createStore } from '../../../common/store'; import { kibanaObservable } from '@kbn/timelines-plugin/public/mock'; import { sourcererPaths } from '../../../common/containers/sourcerer'; -import { useAssistantAvailability } from '../../../assistant/use_assistant_availability'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -49,15 +48,6 @@ jest.mock('react-reverse-portal', () => ({ createHtmlPortalNode: () => ({ unmount: jest.fn() }), })); -jest.mock('../../../assistant/use_assistant_availability'); - -jest.mocked(useAssistantAvailability).mockReturnValue({ - hasAssistantPrivilege: false, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - isAssistantEnabled: true, -}); - describe('global header', () => { const mockSetHeaderActionMenu = jest.fn(); const state = { @@ -183,18 +173,11 @@ describe('global header', () => { expect(queryByTestId('sourcerer-trigger')).not.toBeInTheDocument(); }); - it('shows AI Assistant header link if user has necessary privileges', () => { + it('shows AI Assistant header link', () => { (useLocation as jest.Mock).mockReturnValue([ { pageName: SecurityPageName.overview, detailName: undefined }, ]); - jest.mocked(useAssistantAvailability).mockReturnValue({ - hasAssistantPrivilege: true, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - isAssistantEnabled: true, - }); - const { findByTestId } = render( @@ -203,25 +186,4 @@ describe('global header', () => { waitFor(() => expect(findByTestId('assistantHeaderLink')).toBeInTheDocument()); }); - - it('does not show AI Assistant header link if user does not have necessary privileges', () => { - (useLocation as jest.Mock).mockReturnValue([ - { pageName: SecurityPageName.overview, detailName: undefined }, - ]); - - jest.mocked(useAssistantAvailability).mockReturnValue({ - hasAssistantPrivilege: false, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - isAssistantEnabled: true, - }); - - const { findByTestId } = render( - - - - ); - - waitFor(() => expect(findByTestId('assistantHeaderLink')).not.toBeInTheDocument()); - }); }); diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx index 615d4e8161786..bde0b71a43270 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx @@ -27,8 +27,7 @@ import { timelineSelectors } from '../../../timelines/store/timeline'; import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { getScopeFromPath, showSourcererByPath } from '../../../common/containers/sourcerer'; import { useAddIntegrationsUrl } from '../../../common/hooks/use_add_integrations_url'; -import { AssistantHeaderLink } from './assistant_header_link'; -import { useAssistantAvailability } from '../../../assistant/use_assistant_availability'; +import { AssistantHeaderLink } from '../../../assistant/header_link'; const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', { defaultMessage: 'Add integrations', @@ -54,8 +53,6 @@ export const GlobalHeader = React.memo( const { href, onClick } = useAddIntegrationsUrl(); - const { hasAssistantPrivilege } = useAssistantAvailability(); - useEffect(() => { setHeaderActionMenu((element) => { const mount = toMountPoint(, { theme$: theme.theme$ }); @@ -91,7 +88,7 @@ export const GlobalHeader = React.memo( {showSourcerer && !showTimeline && ( )} - {hasAssistantPrivilege && } + diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 1e98b1c438957..b951501b16cb7 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -29,6 +29,7 @@ import { useUpdateExecutionContext } from '../../common/hooks/use_update_executi import { useUpgradeSecurityPackages } from '../../detection_engine/rule_management/logic/use_upgrade_security_packages'; import { useSetupDetectionEngineHealthApi } from '../../detection_engine/rule_monitoring'; import { TopValuesPopover } from '../components/top_values_popover/top_values_popover'; +import { AssistantOverlay } from '../../assistant/overlay'; interface HomePageProps { children: React.ReactNode; @@ -63,6 +64,7 @@ const HomePageComponent: React.FC = ({ children, setHeaderActionM + diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx index ac49d8e90498d..f5f9b55e59e7c 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx @@ -12,7 +12,7 @@ import { useGlobalHeaderPortal } from '../../../../common/hooks/use_global_heade const StyledStickyWrapper = styled.div` position: sticky; z-index: ${(props) => props.theme.eui.euiZHeaderBelowDataGrid}; - // TOP location is declared in src/public/rendering/_base.scss to keep in line with Kibana Chrome + top: var(--euiFixedHeadersOffset, 0); `; export const GlobalKQLHeader = React.memo(() => { diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 91eca678f6d3c..e635c2d9fd3d3 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { SubscriptionTrackingProvider } from '@kbn/subscription-tracking'; import { SecurityApp } from './app'; import type { RenderAppProps } from './types'; import { AppRoutes } from './app_routes'; @@ -21,6 +22,7 @@ export const renderApp = ({ usageCollection, subPluginRoutes, theme$, + subscriptionTrackingServices, }: RenderAppProps): (() => void) => { const ApplicationUsageTrackingProvider = usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; @@ -34,7 +36,12 @@ export const renderApp = ({ theme$={theme$} > - + + + , element diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index fcee866a2da3a..516239d164632 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -7,10 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const ASSISTANT_TITLE = i18n.translate('xpack.securitySolution.assistant.title', { - defaultMessage: 'Elastic AI Assistant', -}); - export const OVERVIEW = i18n.translate('xpack.securitySolution.navigation.overview', { defaultMessage: 'Overview', }); diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 578a4800f7f64..66bab19c945fe 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -19,6 +19,7 @@ import type { RouteProps } from 'react-router-dom'; import type { AppMountParameters } from '@kbn/core/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { TableState } from '@kbn/securitysolution-data-table'; +import type { Services as SubscriptionTrackingServices } from '@kbn/subscription-tracking'; import type { ExploreReducer, ExploreState } from '../explore'; import type { StartServices } from '../types'; @@ -29,6 +30,7 @@ export interface RenderAppProps extends AppMountParameters { services: StartServices; store: Store; subPluginRoutes: RouteProps[]; + subscriptionTrackingServices: SubscriptionTrackingServices; usageCollection?: UsageCollectionSetup; } diff --git a/x-pack/plugins/security_solution/public/assistant/header_link.test.tsx b/x-pack/plugins/security_solution/public/assistant/header_link.test.tsx new file mode 100644 index 0000000000000..e6b8cfea388a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/header_link.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { AssistantHeaderLink } from './header_link'; + +const mockShowAssistantOverlay = jest.fn(); +const mockAssistantAvailability = jest.fn(() => ({ + hasAssistantPrivilege: true, +})); +jest.mock('@kbn/elastic-assistant/impl/assistant_context', () => ({ + useAssistantContext: () => ({ + assistantAvailability: mockAssistantAvailability(), + showAssistantOverlay: mockShowAssistantOverlay, + }), +})); + +describe('AssistantHeaderLink', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render the header link text', () => { + const { queryByText, queryByTestId } = render(); + expect(queryByTestId('assistantHeaderLink')).toBeInTheDocument(); + expect(queryByText('AI Assistant')).toBeInTheDocument(); + }); + + it('should not render the header link if not authorized', () => { + mockAssistantAvailability.mockReturnValueOnce({ hasAssistantPrivilege: false }); + + const { queryByText, queryByTestId } = render(); + expect(queryByTestId('assistantHeaderLink')).not.toBeInTheDocument(); + expect(queryByText('AI Assistant')).not.toBeInTheDocument(); + }); + + it('should call the assistant overlay to show on click', () => { + const { queryByTestId } = render(); + queryByTestId('assistantHeaderLink')?.click(); + expect(mockShowAssistantOverlay).toHaveBeenCalledTimes(1); + expect(mockShowAssistantOverlay).toHaveBeenCalledWith({ showOverlay: true }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/assistant/header_link.tsx b/x-pack/plugins/security_solution/public/assistant/header_link.tsx new file mode 100644 index 0000000000000..342a95454cdb4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/header_link.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiHeaderLink, EuiToolTip } from '@elastic/eui'; +import React, { useCallback } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { useAssistantContext } from '@kbn/elastic-assistant/impl/assistant_context'; +import { AssistantAvatar } from '@kbn/elastic-assistant'; + +const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; + +const TOOLTIP_CONTENT = i18n.translate( + 'xpack.securitySolution.globalHeader.assistantHeaderLinkShortcutTooltip', + { + values: { keyboardShortcut: isMac ? '⌘ ;' : 'Ctrl ;' }, + defaultMessage: 'Keyboard shortcut {keyboardShortcut}', + } +); +const LINK_LABEL = i18n.translate('xpack.securitySolution.globalHeader.assistantHeaderLink', { + defaultMessage: 'AI Assistant', +}); + +/** + * Elastic AI Assistant header link + */ +export const AssistantHeaderLink = () => { + const { showAssistantOverlay, assistantAvailability } = useAssistantContext(); + + const showOverlay = useCallback( + () => showAssistantOverlay({ showOverlay: true }), + [showAssistantOverlay] + ); + + if (!assistantAvailability.hasAssistantPrivilege) { + return null; + } + + return ( + + + + + + + {LINK_LABEL} + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/assistant/overlay.test.tsx b/x-pack/plugins/security_solution/public/assistant/overlay.test.tsx new file mode 100644 index 0000000000000..fb082a4677595 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/overlay.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { AssistantOverlay } from './overlay'; + +const mockAssistantAvailability = jest.fn(() => ({ + hasAssistantPrivilege: true, +})); +jest.mock('@kbn/elastic-assistant', () => ({ + AssistantOverlay: () =>
    , + useAssistantContext: () => ({ + assistantAvailability: mockAssistantAvailability(), + }), +})); + +describe('AssistantOverlay', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render the header link text', () => { + const { queryByTestId } = render(); + expect(queryByTestId('assistantOverlay')).toBeInTheDocument(); + }); + + it('should not render the header link if not authorized', () => { + mockAssistantAvailability.mockReturnValueOnce({ hasAssistantPrivilege: false }); + + const { queryByTestId } = render(); + expect(queryByTestId('assistantOverlay')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/assistant/overlay.tsx b/x-pack/plugins/security_solution/public/assistant/overlay.tsx new file mode 100644 index 0000000000000..3ffaddaf2e8f5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/overlay.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { + AssistantOverlay as ElasticAssistantOverlay, + useAssistantContext, +} from '@kbn/elastic-assistant'; + +export const AssistantOverlay: React.FC = () => { + const { assistantAvailability } = useAssistantContext(); + if (!assistantAvailability.hasAssistantPrivilege) { + return null; + } + return ; +}; diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx new file mode 100644 index 0000000000000..cde9d5b58524d --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { AssistantProvider as ElasticAssistantProvider } from '@kbn/elastic-assistant'; +import { useKibana } from '../common/lib/kibana'; +import { useAssistantTelemetry } from './use_assistant_telemetry'; +import { getComments } from './get_comments'; +import { augmentMessageCodeBlocks, LOCAL_STORAGE_KEY } from './helpers'; +import { useConversationStore } from './use_conversation_store'; +import { DEFAULT_ALLOW, DEFAULT_ALLOW_REPLACEMENT } from './content/anonymization'; +import { PROMPT_CONTEXTS } from './content/prompt_contexts'; +import { BASE_SECURITY_QUICK_PROMPTS } from './content/quick_prompts'; +import { BASE_SECURITY_SYSTEM_PROMPTS } from './content/prompts/system'; +import { useAnonymizationStore } from './use_anonymization_store'; +import { useAssistantAvailability } from './use_assistant_availability'; +import { APP_ID } from '../../common/constants'; + +const ASSISTANT_TITLE = i18n.translate('xpack.securitySolution.assistant.title', { + defaultMessage: 'Elastic AI Assistant', +}); + +/** + * This component configures the Elastic AI Assistant context provider for the Security Solution app. + */ +export const AssistantProvider: React.FC = ({ children }) => { + const { + http, + triggersActionsUi: { actionTypeRegistry }, + docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + } = useKibana().services; + + const { conversations, setConversations } = useConversationStore(); + const getInitialConversation = useCallback(() => { + return conversations; + }, [conversations]); + + const assistantAvailability = useAssistantAvailability(); + const assistantTelemetry = useAssistantTelemetry(); + + const { defaultAllow, defaultAllowReplacement, setDefaultAllow, setDefaultAllowReplacement } = + useAnonymizationStore(); + + const nameSpace = `${APP_ID}.${LOCAL_STORAGE_KEY}`; + + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx index ba225495a032b..303e55ff66b97 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx @@ -5,12 +5,18 @@ * 2.0. */ -import React, { useCallback } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiIcon, EuiText } from '@elastic/eui'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { SubscriptionLink } from '@kbn/subscription-tracking'; +import type { SubscriptionContextData } from '@kbn/subscription-tracking'; import { INSIGHTS_UPSELL } from './translations'; -import { useNavigation } from '../../../lib/kibana'; + +const subscriptionContext: SubscriptionContextData = { + feature: 'alert-details-insights', + source: 'security__alert-details-flyout', +}; const UpsellContainer = euiStyled.div` border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; @@ -23,15 +29,6 @@ const StyledIcon = euiStyled(EuiIcon)` `; export const RelatedAlertsUpsell = React.memo(() => { - const { getAppUrl, navigateTo } = useNavigation(); - const subscriptionUrl = getAppUrl({ - appId: 'management', - path: 'stack/license_management', - }); - const goToSubscription = useCallback(() => { - navigateTo({ url: subscriptionUrl }); - }, [navigateTo, subscriptionUrl]); - return ( @@ -40,9 +37,13 @@ export const RelatedAlertsUpsell = React.memo(() => { - + {INSIGHTS_UPSELL} - + diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/use_timelines_events.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/use_timelines_events.tsx index 2b234879ccc50..691d0e95cb924 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/use_timelines_events.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/use_timelines_events.tsx @@ -17,7 +17,8 @@ import type { Inspect, PaginationInputPaginated, TimelineEdges, - TimelineEventsAllRequestOptions, + TimelineEqlRequestOptionsInput, + TimelineEventsAllOptionsInput, TimelineEventsAllStrategyResponse, TimelineItem, } from '@kbn/timelines-plugin/common'; @@ -58,7 +59,7 @@ type TimelineEventsSearchHandler = (onNextResponse?: OnNextResponseHandler) => v type LoadPage = (newActivePage: number) => void; -type TimelineRequest = TimelineEventsAllRequestOptions; +type TimelineRequest = TimelineEventsAllOptionsInput | TimelineEqlRequestOptionsInput; type TimelineResponse = TimelineEventsAllStrategyResponse; @@ -161,11 +162,9 @@ export const useTimelineEventsHandler = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(0); - const [timelineRequest, setTimelineRequest] = useState | null>( - null - ); + const [timelineRequest, setTimelineRequest] = useState(null); const [prevFilterStatus, setFilterStatus] = useState(filterStatus); - const prevTimelineRequest = useRef | null>(null); + const prevTimelineRequest = useRef(null); const clearSignalsState = useCallback(() => { if (id != null && detectionsTimelineIds.some((timelineId) => timelineId === id)) { @@ -220,7 +219,7 @@ export const useTimelineEventsHandler = ({ }); const timelineSearch = useCallback( - (request: TimelineRequest | null, onNextHandler?: OnNextResponseHandler) => { + (request: TimelineRequest | null, onNextHandler?: OnNextResponseHandler) => { if (request == null || skip) { return; } @@ -233,7 +232,7 @@ export const useTimelineEventsHandler = ({ startTracking(); const abortSignal = abortCtrl.current.signal; searchSubscription$.current = data.search - .search, TimelineResponse>( + .search>( { ...request, entityType }, { strategy: @@ -296,12 +295,12 @@ export const useTimelineEventsHandler = ({ const prevSearchParameters = { defaultIndex: prevRequest?.defaultIndex ?? [], filterQuery: prevRequest?.filterQuery ?? '', - querySize: prevRequest?.pagination.querySize ?? 0, + querySize: prevRequest?.pagination?.querySize ?? 0, sort: prevRequest?.sort ?? initSortDefault, timerange: prevRequest?.timerange ?? {}, - runtimeMappings: (prevRequest?.runtimeMappings ?? {}) as RunTimeMappings, + runtimeMappings: (prevRequest?.runtimeMappings ?? {}) as unknown as RunTimeMappings, filterStatus: prevRequest?.filterStatus, - }; + } as const; const currentSearchParameters = { defaultIndex: indexNames, @@ -315,7 +314,7 @@ export const useTimelineEventsHandler = ({ to: endDate, }, filterStatus, - }; + } as const; const newActivePage = deepEqual(prevSearchParameters, currentSearchParameters) ? activePage @@ -333,7 +332,7 @@ export const useTimelineEventsHandler = ({ activePage: newActivePage, querySize: limit, }, - language, + language: language as TimelineRequest['language'], runtimeMappings, sort, timerange: { @@ -348,7 +347,7 @@ export const useTimelineEventsHandler = ({ setActivePage(newActivePage); } if (!deepEqual(prevRequest, currentRequest)) { - return currentRequest; + return currentRequest as TimelineRequest; } return prevRequest; }); diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx index b9e27c921704a..9cb27d7b6a7ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx @@ -30,20 +30,6 @@ import * as i18n from './translations'; import { getScopeFromPath, useSourcererDataView } from '../../containers/sourcerer'; import { SourcererScopeName } from '../../store/sourcerer/model'; -const DescriptionListStyled = styled(EuiDescriptionList)` - @media only screen and (min-width: ${(props) => props.theme.eui.euiBreakpoints.s}) { - .euiDescriptionList__title { - width: 30% !important; - } - - .euiDescriptionList__description { - width: 70% !important; - } - } -`; - -DescriptionListStyled.displayName = 'DescriptionListStyled'; - export interface ModalInspectProps { adHocDataViews?: string[] | null; additionalRequests?: string[] | null; @@ -209,7 +195,11 @@ export const ModalInspectQuery = ({ content: ( <> - + ), }, diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index 49ac62cb572e4..6d5df295dbc41 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -8,8 +8,8 @@ import type React from 'react'; import type { EuiTitleSize } from '@elastic/eui'; import type { ScaleType, Position, TickFormatter } from '@elastic/charts'; -import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ActionCreator } from 'redux'; +import type { RunTimeMappings } from '@kbn/timelines-plugin/common/api/search_strategy'; import type { ESQuery } from '../../../../common/typed_json'; import type { InputsModelId } from '../../store/inputs/constants'; import type { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; @@ -83,7 +83,7 @@ export interface MatrixHistogramQueryProps { skip?: boolean; isPtrIncluded?: boolean; includeMissingData?: boolean; - runtimeMappings?: MappingRuntimeFields; + runtimeMappings?: RunTimeMappings; } export interface MatrixHistogramProps extends MatrixHistogramBasicProps { diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap index ddf64d1c066c3..d472238b65359 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/create_descriptions_list.test.tsx.snap @@ -6,7 +6,7 @@ exports[`create_description_list renders correctly against snapshot 1`] = ` Object { "align": "left", "compressed": false, - "gutterSize": "m", + "rowGutterSize": "s", "textStyle": "normal", "type": "row", } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/tab_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/tab_navigation.tsx index 24f50df4a5c67..996fdcd1bb176 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/tab_navigation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/tab_navigation.tsx @@ -31,7 +31,7 @@ const TabNavigationItemComponent = ({ const handleClick = useCallback( (ev) => { ev.preventDefault(); - navigateTo({ path: hrefWithSearch }); + navigateTo({ path: hrefWithSearch, restoreScroll: true }); track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`); }, [navigateTo, hrefWithSearch, id] diff --git a/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx index d893674d49af5..acedbe669f36a 100644 --- a/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx @@ -5,13 +5,11 @@ * 2.0. */ -import { AssistantOverlay } from '@kbn/elastic-assistant'; import classNames from 'classnames'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import type { CommonProps } from '@elastic/eui'; -import { useAssistantAvailability } from '../../../assistant/use_assistant_availability'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { AppGlobalStyle } from '../page'; @@ -42,7 +40,6 @@ interface SecuritySolutionPageWrapperProps { const SecuritySolutionPageWrapperComponent: React.FC< SecuritySolutionPageWrapperProps & CommonProps > = ({ children, className, style, noPadding, noTimeline, ...otherProps }) => { - const { isAssistantEnabled, hasAssistantPrivilege } = useAssistantAvailability(); const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); useEffect(() => { setGlobalFullScreen(false); // exit full screen mode on page load @@ -59,7 +56,6 @@ const SecuritySolutionPageWrapperComponent: React.FC< {children} - {hasAssistantPrivilege && } ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index 118c78e290759..934e923d5724d 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -16,9 +16,45 @@ import { SearchBar } from '@kbn/unified-search-plugin/public'; import type { QueryBarComponentProps } from '.'; import { QueryBar } from '.'; +import type { DataViewFieldMap } from '@kbn/data-views-plugin/common'; +import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { fields } from '@kbn/data-views-plugin/common/mocks'; +import { useKibana } from '../../lib/kibana'; + +const getMockIndexPattern = () => ({ + ...createStubDataView({ + spec: { + id: '1234', + title: 'logstash-*', + fields: ((): DataViewFieldMap => { + const fieldMap: DataViewFieldMap = Object.create(null); + for (const field of fields) { + fieldMap[field.name] = { ...field }; + } + return fieldMap; + })(), + }, + }), +}); + const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; +jest.mock('../../lib/kibana'); describe('QueryBar ', () => { + const mockClearInstanceCache = jest.fn().mockImplementation(({ id }: { id: string }) => { + return id; + }); + + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + dataViews: { + create: jest.fn().mockResolvedValue(getMockIndexPattern()), + clearInstanceCache: mockClearInstanceCache, + }, + }, + }, + }); const mockOnChangeQuery = jest.fn(); const mockOnSubmitQuery = jest.fn(); const mockOnSavedQuery = jest.fn(); @@ -52,10 +88,10 @@ describe('QueryBar ', () => { mockOnSavedQuery.mockClear(); }); - test('check if we format the appropriate props to QueryBar', () => { - const wrapper = mount( - - { + await act(async () => { + const wrapper = await getWrapper( + { onSubmitQuery={mockOnSubmitQuery} onSavedQuery={mockOnSavedQuery} /> - - ); - const { - customSubmitButton, - timeHistory, - onClearSavedQuery, - onFiltersUpdated, - onQueryChange, - onQuerySubmit, - onSaved, - onSavedQueryUpdated, - ...searchBarProps - } = wrapper.find(SearchBar).props(); + ); - expect(searchBarProps).toEqual({ - dataTestSubj: undefined, - dateRangeFrom: 'now/d', - dateRangeTo: 'now/d', - displayStyle: undefined, - filters: [], - indexPatterns: [ - { - fields: [ - { - aggregatable: true, - name: '@timestamp', - searchable: true, - type: 'date', - }, - { - aggregatable: true, - name: '@version', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.ephemeral_id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.hostname', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test1', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test2', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test3', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test4', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test5', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test6', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test7', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test8', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'host.name', - searchable: true, - type: 'string', - }, - { - aggregatable: false, - name: 'nestedField.firstAttributes', - searchable: true, - type: 'string', - }, - { - aggregatable: false, - name: 'nestedField.secondAttributes', - searchable: true, - type: 'string', - }, - ], - title: 'filebeat-*,auditbeat-*,packetbeat-*', - }, - ], - isLoading: false, - isRefreshPaused: true, - query: { - language: 'kuery', - query: 'here: query', - }, - refreshInterval: undefined, - savedQuery: undefined, - showAutoRefreshOnly: false, - showDatePicker: false, - showFilterBar: true, - showQueryInput: true, - showSaveQuery: true, - showSubmitButton: false, + await waitFor(() => { + wrapper.update(); + const { + customSubmitButton, + timeHistory, + onClearSavedQuery, + onFiltersUpdated, + onQueryChange, + onQuerySubmit, + onSaved, + onSavedQueryUpdated, + ...searchBarProps + } = wrapper.find(SearchBar).props(); + expect((searchBarProps?.indexPatterns ?? [{ id: 'unknown' }])[0].id).toEqual( + getMockIndexPattern().id + ); + }); + // ensure useEffect cleanup is called correctly after component unmounts + wrapper.unmount(); + expect(mockClearInstanceCache).toHaveBeenCalledWith(getMockIndexPattern().id); }); }); @@ -294,7 +215,6 @@ describe('QueryBar ', () => { const onSubmitQueryRef = searchBarProps.onQuerySubmit; const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; wrapper.setProps({ onSavedQuery: jest.fn() }); - wrapper.update(); expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated); expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange); diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx index d86f3de10b549..9356956c23d56 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useMemo, useCallback } from 'react'; +import React, { memo, useMemo, useCallback, useState, useEffect } from 'react'; import deepEqual from 'fast-deep-equal'; import type { DataViewBase, Filter, Query, TimeRange } from '@kbn/es-query'; @@ -16,6 +16,8 @@ import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import { SearchBar } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { useKibana } from '../../lib/kibana'; + export interface QueryBarComponentProps { dataTestSubj?: string; dateRangeFrom?: string; @@ -36,6 +38,9 @@ export interface QueryBarComponentProps { isDisabled?: boolean; } +export const isDataView = (obj: unknown): obj is DataView => + obj != null && typeof obj === 'object' && Object.hasOwn(obj, 'getName'); + export const QueryBar = memo( ({ dateRangeFrom, @@ -56,6 +61,8 @@ export const QueryBar = memo( displayStyle, isDisabled, }) => { + const { data } = useKibana().services; + const [dataView, setDataView] = useState(); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { if (payload.query != null && !deepEqual(payload.query, filterQuery)) { @@ -102,16 +109,33 @@ export const QueryBar = memo( [filterManager] ); - const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); - const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); + useEffect(() => { + let dv: DataView; + if (isDataView(indexPattern)) { + setDataView(indexPattern); + } else { + const createDataView = async () => { + dv = await data.dataViews.create({ title: indexPattern.title }); + setDataView(dv); + }; + createDataView(); + } + return () => { + if (dv?.id) { + data.dataViews.clearInstanceCache(dv?.id); + } + }; + }, [data.dataViews, indexPattern]); + const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); + const arrDataView = useMemo(() => (dataView != null ? [dataView] : []), [dataView]); return ( & { data: DataPublicPluginStart; signal: AbortSignal; }; @@ -29,7 +27,7 @@ export const getEventEnrichment = ({ timerange, signal, }: GetEventEnrichmentProps): Observable => - data.search.search( + data.search.search( { defaultIndex, eventFields, diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/api.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/api.ts index e9a443a2f1b8a..b4ca92bb4705b 100644 --- a/x-pack/plugins/security_solution/public/common/containers/dashboards/api.ts +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/api.ts @@ -23,6 +23,7 @@ export const getDashboardsByTagIds = ( abortSignal?: AbortSignal ): Promise => http.post(INTERNAL_DASHBOARDS_URL, { + version: '1', body: JSON.stringify({ tagIds }), signal: abortSignal, }); diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts index 8904ccd5ad8bb..471d43c928458 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts @@ -14,10 +14,10 @@ import { isCompleteResponse } from '@kbn/data-plugin/common'; import type { inputsModel } from '../../../store'; import { useKibana } from '../../../lib/kibana'; import type { - TimelineEventsLastEventTimeRequestOptions, TimelineEventsLastEventTimeStrategyResponse, LastTimeDetails, LastEventIndexKey, + TimelineEventsLastEventTimeRequestOptionsInput, } from '../../../../../common/search_strategy/timeline'; import { TimelineEventsQueries } from '../../../../../common/search_strategy/timeline'; import * as i18n from './translations'; @@ -46,7 +46,7 @@ export const useTimelineLastEventTime = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [TimelineLastEventTimeRequest, setTimelineLastEventTimeRequest] = - useState({ + useState({ defaultIndex: indexNames, factoryQueryType: TimelineEventsQueries.lastEventTime, indexKey, @@ -62,14 +62,14 @@ export const useTimelineLastEventTime = ({ const { addError } = useAppToasts(); const timelineLastEventTimeSearch = useCallback( - (request: TimelineEventsLastEventTimeRequestOptions) => { + (request: TimelineEventsLastEventTimeRequestOptionsInput) => { const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); searchSubscription$.current = data.search .search< - TimelineEventsLastEventTimeRequestOptions, + TimelineEventsLastEventTimeRequestOptionsInput, TimelineEventsLastEventTimeStrategyResponse >(request, { strategy: 'timelineSearchStrategy', diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 137acc4f1c9e7..51128621da177 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -11,12 +11,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { isCompleteResponse } from '@kbn/data-plugin/common'; +import type { MatrixHistogramRequestOptionsInput } from '../../../../common/api/search_strategy'; import type { MatrixHistogramQueryProps } from '../../components/matrix_histogram/types'; import type { inputsModel } from '../../store'; import { createFilter } from '../helpers'; import { useKibana } from '../../lib/kibana'; import type { - MatrixHistogramRequestOptions, MatrixHistogramStrategyResponse, MatrixHistogramData, } from '../../../../common/search_strategy/security_solution'; @@ -76,7 +76,7 @@ export const useMatrixHistogram = ({ const { startTracking } = useTrackHttpRequest(); const [matrixHistogramRequest, setMatrixHistogramRequest] = - useState({ + useState({ defaultIndex: indexNames, factoryQueryType: MatrixHistogramQuery, filterQuery: createFilter(filterQuery), @@ -106,7 +106,7 @@ export const useMatrixHistogram = ({ }); const search = useCallback( - (request: MatrixHistogramRequestOptions) => { + (request: MatrixHistogramRequestOptionsInput) => { const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); @@ -115,7 +115,7 @@ export const useMatrixHistogram = ({ }); searchSubscription$.current = data.search - .search(request, { + .search(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) diff --git a/x-pack/plugins/security_solution/public/common/containers/tags/api.ts b/x-pack/plugins/security_solution/public/common/containers/tags/api.ts index 479ae07cc4eb7..6a402c150b84a 100644 --- a/x-pack/plugins/security_solution/public/common/containers/tags/api.ts +++ b/x-pack/plugins/security_solution/public/common/containers/tags/api.ts @@ -21,7 +21,12 @@ export interface Tag { export const getTagsByName = ( { http, tagName }: { http: HttpSetup; tagName: string }, abortSignal?: AbortSignal -): Promise => http.get(INTERNAL_TAGS_URL, { query: { name: tagName }, signal: abortSignal }); +): Promise => + http.get(INTERNAL_TAGS_URL, { + version: '1', + query: { name: tagName }, + signal: abortSignal, + }); // Dashboard listing needs savedObjectsTaggingClient to work correctly with cache. // https://github.com/elastic/kibana/issues/160723#issuecomment-1641904984 diff --git a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.test.ts b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.test.ts index a561493f8cac0..ec49319e87734 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.test.ts @@ -73,7 +73,6 @@ describe('useFistLastSeen', () => { expect(mockSearch).toHaveBeenCalledWith({ defaultIndex: [], - factoryQueryType: 'firstlastseen', field: 'host.name', order: 'asc', value: 'some-host', @@ -103,7 +102,6 @@ describe('useFistLastSeen', () => { expect(mockSearch).toHaveBeenCalledWith({ defaultIndex: [], - factoryQueryType: 'firstlastseen', field: 'host.name', order: 'desc', value: 'some-host', diff --git a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx index 1eccfd125e2c9..18d6cb1ec7f0f 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx @@ -45,7 +45,6 @@ export const useFirstLastSeen = ({ useEffect(() => { search({ defaultIndex, - factoryQueryType: FirstLastSeenQuery, field, value, order, diff --git a/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.test.ts b/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.test.ts index 6a3b48dce6615..b86eaaf386149 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.test.ts @@ -9,7 +9,10 @@ import { useSearch, useSearchStrategy } from '.'; import { act, renderHook } from '@testing-library/react-hooks'; import { useObservable } from '@kbn/securitysolution-hook-utils'; -import type { FactoryQueryTypes, StrategyRequestType } from '../../../../common/search_strategy'; +import type { + FactoryQueryTypes, + StrategyRequestInputType, +} from '../../../../common/search_strategy'; import { Observable } from 'rxjs'; jest.mock('@kbn/securitysolution-hook-utils'); @@ -83,7 +86,7 @@ const userSearchStrategyProps = { const request = { fake: 'request', search: 'parameters', -} as unknown as StrategyRequestType; +} as unknown as StrategyRequestInputType; describe('useSearchStrategy', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.tsx index b9cd06e77e27e..320339b0ec4df 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_search_strategy/index.tsx @@ -15,7 +15,7 @@ import * as i18n from './translations'; import type { FactoryQueryTypes, - StrategyRequestType, + StrategyRequestInputType, StrategyResponseType, } from '../../../../common/search_strategy/security_solution'; import { getInspectResponse } from '../../../helpers'; @@ -26,7 +26,7 @@ import { useTrackHttpRequest } from '../../lib/apm/use_track_http_request'; import { APP_UI_ID } from '../../../../common/constants'; interface UseSearchFunctionParams { - request: StrategyRequestType; + request: Omit, 'factoryQueryType'>; abortSignal: AbortSignal; } @@ -35,7 +35,7 @@ type UseSearchFunction = ( ) => Observable>; type SearchFunction = ( - params: StrategyRequestType + params: Omit, 'factoryQueryType'> ) => void; const EMPTY_INSPECT = { @@ -57,8 +57,8 @@ export const useSearch = ( }); const observable = data.search - .search, StrategyResponseType>( - { ...request, factoryQueryType }, + .search, StrategyResponseType>( + { ...request, factoryQueryType } as StrategyRequestInputType, { strategy: 'securitySolutionSearchStrategy', abortSignal, diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index e02dcef6b58ce..2d8e49ad096e8 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -7,11 +7,11 @@ import { TableId } from '@kbn/securitysolution-data-table'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; +import { HostsFields } from '../../../common/api/search_strategy/hosts/model/sort'; import { InputsModelId } from '../store/inputs/constants'; import { Direction, FlowTarget, - HostsFields, NetworkDnsFields, NetworkTopTablesFields, NetworkTlsFields, diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 21312f9c24f3f..88aebe587a8d9 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -21,6 +21,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { Action } from '@kbn/ui-actions-plugin/public'; import { CellActionsProvider } from '@kbn/cell-actions'; import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; +import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks'; import { useKibana } from '../lib/kibana'; import { UpsellingProvider } from '../components/upselling_provider'; import { MockAssistantProvider } from './mock_assistant_provider'; @@ -74,25 +75,27 @@ export const TestProvidersComponent: React.FC = ({ return ( - - - ({ eui: euiDarkVars, darkMode: true })}> - - - - - Promise.resolve(cellActions)} - > - {children} - - - - - - - - + + + + ({ eui: euiDarkVars, darkMode: true })}> + + + + + Promise.resolve(cellActions)} + > + {children} + + + + + + + + + ); @@ -117,27 +120,29 @@ const TestProvidersWithPrivilegesComponent: React.FC = ({ return ( - - ({ eui: euiDarkVars, darkMode: true })}> - - - Promise.resolve(cellActions)} + + + ({ eui: euiDarkVars, darkMode: true })}> + + - {children} - - - - - + Promise.resolve(cellActions)} + > + {children} + + + + + + ); diff --git a/x-pack/plugins/security_solution/public/common/store/store.ts b/x-pack/plugins/security_solution/public/common/store/store.ts index 87c208a45e506..9b7f59bd2f978 100644 --- a/x-pack/plugins/security_solution/public/common/store/store.ts +++ b/x-pack/plugins/security_solution/public/common/store/store.ts @@ -69,6 +69,7 @@ export const createStoreFactory = async ( try { if (coreStart.application.capabilities[SERVER_APP_ID].show === true) { signal = await coreStart.http.fetch(DETECTION_ENGINE_INDEX_URL, { + version: '2023-10-31', method: 'GET', }); } diff --git a/x-pack/plugins/security_solution/public/dashboards/containers/use_fetch_security_tags.test.ts b/x-pack/plugins/security_solution/public/dashboards/containers/use_fetch_security_tags.test.ts index 4cf79cf4ce10b..3dadc008a9efa 100644 --- a/x-pack/plugins/security_solution/public/dashboards/containers/use_fetch_security_tags.test.ts +++ b/x-pack/plugins/security_solution/public/dashboards/containers/use_fetch_security_tags.test.ts @@ -56,10 +56,13 @@ describe('useFetchSecurityTags', () => { mockGet.mockResolvedValue([]); await asyncRenderUseCreateSecurityDashboardLink(); - expect(mockGet).toHaveBeenCalledWith(INTERNAL_TAGS_URL, { - query: { name: SECURITY_TAG_NAME }, - signal: mockAbortSignal, - }); + expect(mockGet).toHaveBeenCalledWith( + INTERNAL_TAGS_URL, + expect.objectContaining({ + query: { name: SECURITY_TAG_NAME }, + signal: mockAbortSignal, + }) + ); }); test('should create a Security Solution tag if no Security Solution tags were found', async () => { diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx index 41479adb42623..ac26f9038d5d4 100644 --- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx @@ -31,6 +31,13 @@ jest.mock('@kbn/dashboard-plugin/public', () => { }; }); +const mockUseObservable = jest.fn(); + +jest.mock('react-use', () => ({ + ...jest.requireActual('react-use'), + useObservable: () => mockUseObservable(), +})); + const DEFAULT_DASHBOARD_CAPABILITIES = { show: true, createNew: true }; const mockUseCapabilities = useCapabilities as jest.Mock; mockUseCapabilities.mockReturnValue(DEFAULT_DASHBOARD_CAPABILITIES); @@ -210,4 +217,13 @@ describe('Dashboards landing', () => { }); }); }); + + it('should render callout when available', async () => { + const DummyComponent = () => ; + mockUseObservable.mockReturnValue(); + + await renderDashboardLanding(); + + expect(screen.queryByTestId('test')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx index 7d579c66e8191..8cb12d5895021 100644 --- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx @@ -17,10 +17,11 @@ import React, { useCallback, useMemo } from 'react'; import type { DashboardCapabilities } from '@kbn/dashboard-plugin/common/types'; import { DashboardListingTable, LEGACY_DASHBOARD_APP_ID } from '@kbn/dashboard-plugin/public'; import { LandingLinksImageCards } from '@kbn/security-solution-navigation/landing_links'; +import { useObservable } from 'react-use'; import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { SecurityPageName } from '../../../../common/constants'; -import { useCapabilities, useNavigateTo } from '../../../common/lib/kibana'; +import { useCapabilities, useKibana, useNavigateTo } from '../../../common/lib/kibana'; import { useRootNavLink } from '../../../common/links/nav_links'; import { Title } from '../../../common/components/header_page/title'; import { LinkButton } from '../../../common/components/links/helpers'; @@ -82,6 +83,8 @@ const Header: React.FC<{ canCreateDashboard: boolean }> = ({ canCreateDashboard }; export const DashboardsLandingPage = () => { + const { dashboardsLandingCalloutComponent$ } = useKibana().services; + const dashboardLandingCallout = useObservable(dashboardsLandingCalloutComponent$); const { links = [] } = useRootNavLink(SecurityPageName.dashboards) ?? {}; const urlState = useGlobalQueryString(); const { show: canReadDashboard, createNew: canCreateDashboard } = @@ -121,6 +124,13 @@ export const DashboardsLandingPage = () => {
    + {dashboardLandingCallout && ( + <> + {dashboardLandingCallout} + + + )} +

    {i18n.DASHBOARDS_PAGE_SECTION_DEFAULT}

    diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts index a90f2b0531948..192683f84fc46 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts @@ -22,6 +22,7 @@ export const fleetIntegrationsApi: IFleetIntegrationsApiClient = { return http().fetch(GET_INSTALLED_INTEGRATIONS_URL, { method: 'GET', + version: '1', query: { packages: packages?.sort()?.join(','), }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 44e41f0f9ebd3..674d29b128413 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -144,6 +144,13 @@ import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { useLegacyUrlRedirect } from './use_redirect_legacy_url'; import { RuleDetailTabs, useRuleDetailsTabs } from './use_rule_details_tabs'; +const RULE_EXCEPTION_LIST_TYPES = [ + ExceptionListTypeEnum.DETECTION, + ExceptionListTypeEnum.RULE_DEFAULT, +]; + +const RULE_ENDPOINT_EXCEPTION_LIST_TYPE = [ExceptionListTypeEnum.ENDPOINT]; + /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. */ @@ -603,6 +610,7 @@ const RuleDetailsPageComponent: React.FC = ({ = ({ = ({ > ', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts index 454904a251bf4..ee0da0edbfdb9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts @@ -10,11 +10,12 @@ import { renderHook, cleanup } from '@testing-library/react-hooks'; import type { UseLegacyUrlRedirectParams } from './use_redirect_legacy_url'; import { useLegacyUrlRedirect } from './use_redirect_legacy_url'; import type { Rule } from '../../../rule_management/logic'; +import type { SpacesApi } from '@kbn/spaces-plugin/public'; const mockRedirectLegacyUrl = jest.fn(); const mockGetLegacyUrlConflict = jest.fn(); -const mockSpacesApi = { +const mockSpacesApi: SpacesApi = { getActiveSpace$: jest.fn(), getActiveSpace: jest.fn(), ui: { @@ -30,6 +31,7 @@ const mockSpacesApi = { redirectLegacyUrl: mockRedirectLegacyUrl, useSpaces: jest.fn(), }, + hasOnlyDefaultSpace: false, }; describe('useLegacyUrlRedirect', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx index a324c19ff994d..6053b56bf5d5a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx @@ -22,13 +22,10 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import styled from 'styled-components'; +import { LinkToRuleDetails, LinkToListDetails } from '../../../../exceptions/components'; import * as i18n from './translations'; import { FormattedDate } from '../../../../common/components/formatted_date'; -import { SecurityPageName } from '../../../../../common/constants'; import type { ExceptionListRuleReferencesSchema } from '../../../../../common/api/detection_engine/rule_exceptions'; -import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; -import { RuleDetailTabs } from '../../../rule_details_ui/pages/rule_details/use_rule_details_tabs'; -import { getRuleDetailsTabUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; const StyledFlexItem = styled(EuiFlexItem)` border-right: 1px solid #d3dae6; @@ -66,14 +63,7 @@ export const ExceptionItemCardMetaInfo = memo( key={reference.id} > - - {reference.name} - + )); @@ -136,15 +126,12 @@ export const ExceptionItemCardMetaInfo = memo( key={listAndReferences.id} > - - {listAndReferences.name} - + /> , ]} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx index 1b6f7bcb069b1..e963c8d7707f9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx @@ -24,8 +24,7 @@ describe('ExceptionsLinkedToRule', () => { /> ); - expect(wrapper.find('[data-test-subj="ruleNameCell"]').at(0).text()).toEqual('NameMy rule'); - expect(wrapper.find('[data-test-subj="ruleAction-viewDetails"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="linkToRuleSecuritySolutionLink"]').exists()).toBeTruthy(); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx index 712da253477ab..0a92a46592dfd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx @@ -20,8 +20,6 @@ import { generateLinkedRulesMenuItems, } from '@kbn/securitysolution-exception-list-components'; import { PopoverItems } from '../../../../common/components/popover_items'; -import { SecurityPageName } from '../../../../../common/constants'; -import { ListDetailsLinkAnchor } from '../../../../exceptions/components'; import { enrichExceptionItemsWithOS, enrichNewExceptionItemsWithComments, @@ -31,14 +29,12 @@ import { enrichSharedExceptions, lowercaseHashValues, } from '../../utils/helpers'; -import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; -import { getRuleDetailsTabUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import { RuleDetailTabs } from '../../../rule_details_ui/pages/rule_details/use_rule_details_tabs'; import type { ExceptionListRuleReferencesInfoSchema, ExceptionListRuleReferencesSchema, } from '../../../../../common/api/detection_engine/rule_exceptions'; import type { Rule } from '../../../rule_management/logic/types'; +import { LinkToRuleDetails, LinkToListDetails } from '../../../../exceptions/components'; import * as i18n from './translations'; /** @@ -222,7 +218,7 @@ export const getSharedListsTableColumns = () => [ actions={generateLinkedRulesMenuItems({ dataTestSubj: 'addToSharedListsLinkedRulesMenu', linkedRules: references, - securityLinkAnchorComponent: ListDetailsLinkAnchor, + securityLinkAnchorComponent: LinkToRuleDetails, })} panelPaddingSize="none" disableActions={false} @@ -237,14 +233,12 @@ export const getSharedListsTableColumns = () => [ 'data-test-subj': 'exceptionListRulesActionCell', render: (list: ExceptionListRuleReferencesSchema) => { return ( - - {i18n.VIEW_LIST_DETAIL_ACTION} - + /> ); }, }, @@ -294,14 +288,11 @@ export const getRulesTableColumn = () => [ 'data-test-subj': 'ruleAction-view', render: (rule: Rule) => { return ( - - {i18n.VIEW_RULE_DETAIL_ACTION} - + referenceId={rule.id} + referenceName={i18n.VIEW_RULE_DETAIL_ACTION} + /> ); }, }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index c90b48860bbf6..78a788c482654 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -153,6 +153,7 @@ export const patchRule = async ({ export const previewRule = async ({ rule, signal }: PreviewRulesProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_PREVIEW, { method: 'POST', + version: '2023-10-31', body: JSON.stringify(rule), signal, }); @@ -542,6 +543,7 @@ export const findRuleExceptionReferences = async ({ DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL, { method: 'GET', + version: '1', query, signal, } @@ -570,6 +572,7 @@ export const addRuleExceptions = async ({ `${DETECTION_ENGINE_RULES_URL}/${ruleId}/exceptions`, { method: 'POST', + version: '2023-10-31', body: JSON.stringify({ items }), signal, } @@ -602,6 +605,7 @@ export const installFleetPackage = ({ epmRouteService.getInstallPath(packageName, packageVersion), { query: { prerelease }, + version: '2023-10-31', body: JSON.stringify({ force }), } ); @@ -628,6 +632,7 @@ export const bulkInstallFleetPackages = ({ epmRouteService.getBulkInstallPath(), { query: { prerelease }, + version: '2023-10-31', body: JSON.stringify({ packages }), } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts new file mode 100644 index 0000000000000..1561be4f4d8a4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const DESCRIPTION_LIST_COLUMN_WIDTHS: [string, string] = ['50%', '50%']; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx index 5c3002946baf6..aef4eccaa4299 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx @@ -32,6 +32,7 @@ import { filterEmptyThreats } from '../../../rule_creation_ui/pages/rule_creatio import { ThreatEuiFlexGroup } from '../../../../detections/components/rules/description_step/threat_description'; import { BadgeList } from './badge_list'; +import { DESCRIPTION_LIST_COLUMN_WIDTHS } from './constants'; import * as i18n from './translations'; const OverrideColumn = styled(EuiFlexItem)` @@ -357,7 +358,12 @@ export const RuleAboutSection = ({ rule }: RuleAboutSectionProps) => { /> )} - +
    ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index de9e257c798bc..72057da4d963a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -44,6 +44,7 @@ import { MlJobLink } from '../../../../detections/components/rules/ml_job_link/m import { useSecurityJobs } from '../../../../common/components/ml_popover/hooks/use_security_jobs'; import { useKibana } from '../../../../common/lib/kibana/kibana_react'; import { BadgeList } from './badge_list'; +import { DESCRIPTION_LIST_COLUMN_WIDTHS } from './constants'; import * as i18n from './translations'; interface SavedQueryNameProps { @@ -497,7 +498,12 @@ export const RuleDefinitionSection = ({ rule }: RuleDefinitionSectionProps) => { return (
    - +
    ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx index e7d2bbaeb831d..1d4f4290c4b03 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiDescriptionList, EuiText } from '@elastic/eui'; import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema/rule_schemas'; import { getHumanizedDuration } from '../../../../detections/pages/detection_engine/rules/helpers'; +import { DESCRIPTION_LIST_COLUMN_WIDTHS } from './constants'; import * as i18n from './translations'; interface IntervalProps { @@ -46,7 +47,12 @@ export const RuleScheduleSection = ({ rule }: RuleScheduleSectionProps) => { return (
    - +
    ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_dashboard_model.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_dashboard_model.ts index d1586362553c6..04d9c2e4cf4d0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_dashboard_model.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_dashboard_model.ts @@ -30,7 +30,7 @@ export async function buildCoverageOverviewDashboardModel( apiResponse: CoverageOverviewResponse ): Promise { const mitreConfig = await lazyMitreConfiguration(); - const { tactics, technique: techniques, subtechniques } = mitreConfig; + const { tactics, techniques, subtechniques } = mitreConfig; const mitreTactics = buildCoverageOverviewMitreGraph(tactics, techniques, subtechniques); for (const tactic of mitreTactics) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.test.ts index 8039da45b80a6..3a95160c96356 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.test.ts @@ -14,11 +14,15 @@ describe('buildCoverageOverviewModel', () => { name: 'Tactic 1', id: 'TA001', reference: 'https://some-link/TA001', + label: 'Tactic 1', + value: 'tactic1', }, { name: 'Tactic 2', id: 'TA002', reference: 'https://some-link/TA002', + label: 'Tactic 2', + value: 'tactic2', }, ]; const techniques = [ @@ -27,12 +31,16 @@ describe('buildCoverageOverviewModel', () => { id: 'T001', reference: 'https://some-link/T001', tactics: ['tactic-1'], + label: 'Technique 1', + value: 'technique1', }, { name: 'Technique 2', id: 'T002', reference: 'https://some-link/T002', tactics: ['tactic-1', 'tactic-2'], + label: 'Technique 2', + value: 'technique2', }, ]; const subtechniques = [ @@ -42,6 +50,8 @@ describe('buildCoverageOverviewModel', () => { reference: 'https://some-link/T001/001', tactics: ['tactic-1'], techniqueId: 'T001', + label: 'Subtechnique 1', + value: 'subtechnique1', }, { name: 'Subtechnique 2', @@ -49,6 +59,8 @@ describe('buildCoverageOverviewModel', () => { reference: 'https://some-link/T001/002', tactics: ['tactic-1'], techniqueId: 'T001', + label: 'Subtechnique 2', + value: 'subtechnique2', }, ]; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx index 791a4b9872fb5..1bf260aff69ed 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx @@ -295,6 +295,9 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide pagination, }, { + // We don't need refreshes on windows focus and reconnects if auto-refresh if off + refetchOnWindowFocus: isRefreshOn && !isActionInProgress, + refetchOnReconnect: isRefreshOn && !isActionInProgress, refetchInterval: isRefreshOn && !isActionInProgress && autoRefreshSettings.value, keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts index 18328e5b9b901..b5b8f201f0ff3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts @@ -32,6 +32,7 @@ export const api: jest.Mocked = { sequence: 0, level: LogLevel.info, type: RuleExecutionEventType['status-change'], + execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts index 9fac84959f7dc..d1317e2f74252 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts @@ -72,14 +72,51 @@ describe('Rule Monitoring API Client', () => { ); }); - it('calls API correctly with filter and pagination options', async () => { + const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; + + it.each([ + [ + 'search term filter', + { searchTerm: 'something to search' }, + { search_term: 'something to search' }, + ], + [ + 'event types filter', + { eventTypes: [RuleExecutionEventType.message] }, + { event_types: 'message' }, + ], + [ + 'log level filter', + { logLevels: [LogLevel.warn, LogLevel.error] }, + { log_levels: 'warn,error' }, + ], + [ + 'start date filter in relative format', + { dateRange: { start: 'now-1d/d' } }, + { date_start: expect.stringMatching(ISO_PATTERN) }, + ], + [ + 'end date filter', + { dateRange: { end: 'now-3d/d' } }, + { date_end: expect.stringMatching(ISO_PATTERN) }, + ], + [ + 'date range filter in relative format', + { dateRange: { start: new Date().toISOString(), end: new Date().toISOString() } }, + { + date_start: expect.stringMatching(ISO_PATTERN), + date_end: expect.stringMatching(ISO_PATTERN), + }, + ], + [ + 'pagination', + { sortOrder: 'asc', page: 42, perPage: 146 } as const, + { sort_order: 'asc', page: 42, per_page: 146 }, + ], + ])('calls API correctly with %s', async (_, params, expectedParams) => { await api.fetchRuleExecutionEvents({ ruleId: '42', - eventTypes: [RuleExecutionEventType.message], - logLevels: [LogLevel.warn, LogLevel.error], - sortOrder: 'asc', - page: 42, - perPage: 146, + ...params, }); expect(fetchMock).toHaveBeenCalledWith( @@ -87,11 +124,7 @@ describe('Rule Monitoring API Client', () => { expect.objectContaining({ method: 'GET', query: { - event_types: 'message', - log_levels: 'warn,error', - sort_order: 'asc', - page: 42, - per_page: 146, + ...expectedParams, }, }) ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts index d5d34447d2bf4..bd1bf80a0f4c3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { omitBy, isUndefined } from 'lodash'; import dateMath from '@kbn/datemath'; import { KibanaServices } from '../../../common/lib/kibana'; @@ -36,20 +37,38 @@ export const api: IRuleMonitoringApiClient = { fetchRuleExecutionEvents: ( args: FetchRuleExecutionEventsArgs ): Promise => { - const { ruleId, eventTypes, logLevels, sortOrder, page, perPage, signal } = args; + const { + ruleId, + searchTerm, + eventTypes, + logLevels, + dateRange, + sortOrder, + page, + perPage, + signal, + } = args; const url = getRuleExecutionEventsUrl(ruleId); + const startDate = dateMath.parse(dateRange?.start ?? '')?.toISOString(); + const endDate = dateMath.parse(dateRange?.end ?? '', { roundUp: true })?.toISOString(); return http().fetch(url, { method: 'GET', version: '1', - query: { - event_types: eventTypes?.join(','), - log_levels: logLevels?.join(','), - sort_order: sortOrder, - page, - per_page: perPage, - }, + query: omitBy( + { + search_term: searchTerm?.length ? searchTerm : undefined, + event_types: eventTypes?.length ? eventTypes.join(',') : undefined, + log_levels: logLevels?.length ? logLevels.join(',') : undefined, + date_start: startDate, + date_end: endDate, + sort_order: sortOrder, + page, + per_page: perPage, + }, + isUndefined + ), signal, }); }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts index 4ded970a28e4d..a51059a16ef42 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts @@ -46,12 +46,22 @@ export interface RuleMonitoringApiCallArgs { signal?: AbortSignal; } +export interface DateRange { + start?: string; + end?: string; +} + export interface FetchRuleExecutionEventsArgs extends RuleMonitoringApiCallArgs { /** * Saved Object ID of the rule (`rule.id`, not static `rule.rule_id`). */ ruleId: string; + /** + * Filter by event message. If set, result will include events matching the search term. + */ + searchTerm?: string; + /** * Filter by event type. If set, result will include only events matching any of these. */ @@ -62,6 +72,11 @@ export interface FetchRuleExecutionEventsArgs extends RuleMonitoringApiCallArgs */ logLevels?: LogLevel[]; + /** + * Filter by date range. If set, result will include only events recorded in the specified date range. + */ + dateRange?: DateRange; + /** * What order to sort by (e.g. `asc` or `desc`). */ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/event_message_filter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/event_message_filter.tsx new file mode 100644 index 0000000000000..b8d45ed5fb1a3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/event_message_filter.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. + */ + +import type { ChangeEvent } from 'react'; +import React, { useCallback } from 'react'; +import { EuiFieldSearch } from '@elastic/eui'; +import * as i18n from './translations'; + +interface EventMessageFilterProps { + value: string; + onChange: (value: string) => void; +} + +export function EventMessageFilter({ value, onChange }: EventMessageFilterProps): JSX.Element { + const handleChange = useCallback( + (e: ChangeEvent) => onChange(e.target.value), + [onChange] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/index.ts new file mode 100644 index 0000000000000..2b07bf7cf86a8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './event_message_filter'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/translations.ts new file mode 100644 index 0000000000000..f82c812d8208c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SEARCH_BY_EVENT_MESSAGE_ARIA_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.eventLog.searchAriaLabel', + { + defaultMessage: 'Search by event message', + } +); + +export const SEARCH_BY_EVENT_MESSAGE_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.eventLog.searchPlaceholder', + { + defaultMessage: 'Search by event message', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx index 27f500c26dac6..f05636f9aced1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx @@ -7,7 +7,13 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import type { CriteriaWithPagination } from '@elastic/eui'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { + EuiBasicTable, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSuperDatePicker, +} from '@elastic/eui'; import type { RuleExecutionEvent } from '../../../../../common/api/detection_engine/rule_monitoring'; @@ -22,6 +28,7 @@ import { usePagination } from '../basic/tables/use_pagination'; import { useColumns } from './use_columns'; import { useExpandableRows } from '../basic/tables/use_expandable_rows'; import { useExecutionEvents } from './use_execution_events'; +import { EventMessageFilter } from './event_message_filter'; import * as i18n from './translations'; @@ -56,8 +63,10 @@ const ExecutionEventsTableComponent: React.FC = ({ ru const executionEvents = useExecutionEvents({ ruleId, + searchTerm: filters.state.searchTerm, eventTypes: filters.state.eventTypes, logLevels: filters.state.logLevels, + dateRange: filters.state.dateRange, sortOrder: sorting.state.sort.direction, page: pagination.state.pageNumber, perPage: pagination.state.pageSize, @@ -86,6 +95,9 @@ const ExecutionEventsTableComponent: React.FC = ({ ru + + + @@ -95,6 +107,14 @@ const ExecutionEventsTableComponent: React.FC = ({ ru onChange={filters.setEventTypes} /> + + + {/* Table with items */} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx index b701b3bc48f74..866e0e44b6c77 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx @@ -87,6 +87,7 @@ describe('useExecutionEvents', () => { sequence: 0, level: LogLevel.info, type: RuleExecutionEventType['status-change'], + execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts index 36fa393467d5e..3079ff19db160 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts @@ -11,15 +11,29 @@ import type { LogLevel, RuleExecutionEventType, } from '../../../../../common/api/detection_engine/rule_monitoring'; +import type { DateRange } from '../../api'; export const useFilters = () => { + const [searchTerm, setSearchTerm] = useState(''); const [logLevels, setLogLevels] = useState([]); const [eventTypes, setEventTypes] = useState([]); - - const state = useMemo(() => ({ logLevels, eventTypes }), [logLevels, eventTypes]); + const [dateRange, setDateRange] = useState({ + start: 'now-24h', + end: 'now', + }); + const state = useMemo( + () => ({ searchTerm, logLevels, eventTypes, dateRange }), + [searchTerm, logLevels, eventTypes, dateRange] + ); return useMemo( - () => ({ state, setLogLevels, setEventTypes }), - [state, setLogLevels, setEventTypes] + () => ({ + state, + setSearchTerm, + setLogLevels, + setEventTypes, + setDateRange, + }), + [state, setSearchTerm, setLogLevels, setEventTypes, setDateRange] ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx index 9f9ad505ff332..69564861c748a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { EuiCode, EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useIsMounted } from '@kbn/securitysolution-hook-utils'; @@ -25,10 +25,6 @@ const GhostFormField = () => <>; export const OsqueryResponseAction = React.memo((props: OsqueryResponseActionProps) => { const { osquery, application } = useKibana().services; - const OsqueryForm = useMemo( - () => osquery?.OsqueryResponseActionTypeForm, - [osquery?.OsqueryResponseActionTypeForm] - ); const isMounted = useIsMounted(); // serverless component that is returned when users do not have Endpoint.Complete tier @@ -85,8 +81,7 @@ export const OsqueryResponseAction = React.memo((props: OsqueryResponseActionPro ); } - // @ts-expect-error ts upgrade v4.7.4 - if (isMounted() && OsqueryForm) { + if (isMounted()) { return ( ( { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/accordion_title/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/accordion_title/index.tsx index 8e25d444ebd2b..86280bd98d58f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/accordion_title/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/accordion_title/index.tsx @@ -21,7 +21,7 @@ const AccordionTitleComponent: React.FC = ({ name, title, t - +
    {title}
    diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index 8d7c21e386e40..a55d229bb1b97 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -60,11 +60,7 @@ import type { LicenseService } from '../../../../../common/license'; const DescriptionListContainer = styled(EuiDescriptionList)` max-width: 600px; - &.euiDescriptionList--column .euiDescriptionList__title { - width: 30%; - } - &.euiDescriptionList--column .euiDescriptionList__description { - width: 70%; + .euiDescriptionList__description { overflow-wrap: anywhere; } `; @@ -76,6 +72,8 @@ const panelViewStyle = css` text-overflow: ellipsis; `; +const DESCRIPTION_LIST_COLUMN_WIDTHS: [string, string] = ['50%', '50%']; + interface StepRuleDescriptionProps { columns?: 'multi' | 'single' | 'singleSplit'; data: unknown; @@ -155,6 +153,8 @@ export const StepRuleDescriptionComponent = ({ )} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx index 375113977c9f8..b0390e43f7582 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx @@ -8,11 +8,7 @@ import { EuiFlexItem, EuiLink, EuiFlexGroup, EuiButtonEmpty } from '@elastic/eui import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import type { BuildThreatDescription } from './types'; -import type { - MitreSubtechniquesOptions, - MitreTacticsOptions, - MitreTechniquesOptions, -} from '../../../mitre/types'; +import type { MitreSubTechnique, MitreTactic, MitreTechnique } from '../../../mitre/types'; import ListTreeIcon from './assets/list_tree_icon.svg'; const lazyMitreConfiguration = () => { @@ -45,16 +41,16 @@ const TechniqueLinkItem = styled(EuiButtonEmpty)` `; export const ThreatEuiFlexGroup = ({ label, threat }: BuildThreatDescription) => { - const [techniquesOptions, setTechniquesOptions] = useState([]); - const [tacticsOptions, setTacticsOptions] = useState([]); - const [subtechniquesOptions, setSubtechniquesOptions] = useState([]); + const [techniquesOptions, setTechniquesOptions] = useState([]); + const [tacticsOptions, setTacticsOptions] = useState([]); + const [subtechniquesOptions, setSubtechniquesOptions] = useState([]); useEffect(() => { async function getMitre() { const mitreConfig = await lazyMitreConfiguration(); - setSubtechniquesOptions(mitreConfig.subtechniquesOptions); - setTechniquesOptions(mitreConfig.techniquesOptions); - setTacticsOptions(mitreConfig.tacticsOptions); + setSubtechniquesOptions(mitreConfig.subtechniques); + setTechniquesOptions(mitreConfig.techniques); + setTacticsOptions(mitreConfig.tactics); } getMitre(); }, []); @@ -70,7 +66,7 @@ export const ThreatEuiFlexGroup = ({ label, threat }: BuildThreatDescription) => target="_blank" > {tactic != null - ? tactic.text + ? tactic.label : `${singleThreat.tactic.name} (${singleThreat.tactic.id})`} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/helpers.ts index 6f697a24f92bc..b8f979007b767 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/helpers.ts @@ -22,7 +22,7 @@ const lazyMitreConfiguration = () => { * Returns true if the given mitre technique has any subtechniques */ export const hasSubtechniqueOptions = async (technique: ThreatTechnique) => { - return (await lazyMitreConfiguration()).subtechniquesOptions.some( + return (await lazyMitreConfiguration()).subtechniques.some( (subtechnique) => subtechnique.techniqueId === technique.id ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.tsx index 8fd822829001e..9f4d8d811eaa6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.tsx @@ -18,7 +18,7 @@ import { threatDefault } from '../step_about_rule/default_value'; import { MyAddItemButton } from '../add_item_form'; import * as i18n from './translations'; import { MitreAttackTechniqueFields } from './technique_fields'; -import type { MitreTacticsOptions } from '../../../mitre/types'; +import type { MitreTactic } from '../../../mitre/types'; const lazyMitreConfiguration = () => { /** @@ -77,12 +77,12 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem } }, [field]); - const [tacticsOptions, setTacticsOptions] = useState([]); + const [tacticsOptions, setTacticsOptions] = useState([]); useEffect(() => { async function getMitre() { const mitreConfig = await lazyMitreConfiguration(); - setTacticsOptions(mitreConfig.tacticsOptions); + setTacticsOptions(mitreConfig.tactics); } getMitre(); }, []); @@ -128,7 +128,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem ] : []), ...tacticsOptions.map((t) => ({ - inputDisplay: <>{t.text}, + inputDisplay: <>{t.label}, value: t.value, disabled, })), diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx index 4f6ae0299a344..efc542f9757a1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx @@ -22,7 +22,7 @@ import * as Rulei18n from '../../../pages/detection_engine/rules/translations'; import type { FieldHook } from '../../../../shared_imports'; import { MyAddItemButton } from '../add_item_form'; import * as i18n from './translations'; -import type { MitreSubtechniquesOptions } from '../../../mitre/types'; +import type { MitreSubTechnique } from '../../../mitre/types'; const lazyMitreConfiguration = () => { /** @@ -57,12 +57,12 @@ export const MitreAttackSubtechniqueFields: React.FC = ({ onFieldChange, }): JSX.Element => { const values = field.value as Threats; - const [subtechniquesOptions, setSubtechniquesOptions] = useState([]); + const [subtechniquesOptions, setSubtechniquesOptions] = useState([]); useEffect(() => { async function getMitre() { const mitreConfig = await lazyMitreConfiguration(); - setSubtechniquesOptions(mitreConfig.subtechniquesOptions); + setSubtechniquesOptions(mitreConfig.subtechniques); } getMitre(); }, []); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx index 92f4dfdb9b17d..0215204a98cbd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx @@ -23,7 +23,7 @@ import type { FieldHook } from '../../../../shared_imports'; import { MyAddItemButton } from '../add_item_form'; import * as i18n from './translations'; import { MitreAttackSubtechniqueFields } from './subtechnique_fields'; -import type { MitreSubtechniquesOptions, MitreTechniquesOptions } from '../../../mitre/types'; +import type { MitreTechnique, MitreSubTechnique } from '../../../mitre/types'; const lazyMitreConfiguration = () => { /** @@ -37,7 +37,7 @@ const lazyMitreConfiguration = () => { }; const hasSubtechniqueOptions = ( - subtechniquesOptions: MitreSubtechniquesOptions[], + subtechniquesOptions: MitreSubTechnique[], technique: ThreatTechnique ) => subtechniquesOptions.some((subtechnique) => subtechnique.techniqueId === technique.id); @@ -66,14 +66,14 @@ export const MitreAttackTechniqueFields: React.FC = ({ }): JSX.Element => { const values = field.value as Threats; - const [techniquesOptions, setTechniquesOptions] = useState([]); - const [subtechniquesOptions, setSubtechniquesOptions] = useState([]); + const [techniquesOptions, setTechniquesOptions] = useState([]); + const [subtechniquesOptions, setSubtechniquesOptions] = useState([]); useEffect(() => { async function getMitre() { const mitreConfig = await lazyMitreConfiguration(); - setTechniquesOptions(mitreConfig.techniquesOptions); - setSubtechniquesOptions(mitreConfig.subtechniquesOptions); + setTechniquesOptions(mitreConfig.techniques); + setSubtechniquesOptions(mitreConfig.subtechniques); } getMitre(); }, []); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx index c4d50062675d2..2ee3cbd169ecf 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx @@ -12,7 +12,6 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem, - EuiFormRow, EuiSpacer, EuiSuperDatePicker, EuiSuperUpdateButton, @@ -232,36 +231,32 @@ const RulePreviewComponent: React.FC = ({ )} - - - +

    {i18n.QUERY_PREVIEW_LABEL}

    + + + + + + - - - - -
    + +
    {isPreviewRequestInProgress && } {!isPreviewRequestInProgress && previewId && spaceId && ( diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts index 601bbf9498617..92801daeba514 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts @@ -42,11 +42,14 @@ describe('Detections Alerts API', () => { test('check parameter url, body', async () => { await fetchQueryAlerts({ query: mockAlertsQuery, signal: abortCtrl.signal }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/search', { - body: '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', - method: 'POST', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/signals/search', + expect.objectContaining({ + body: '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', + method: 'POST', + signal: abortCtrl.signal, + }) + ); }); test('happy path', async () => { @@ -70,11 +73,14 @@ describe('Detections Alerts API', () => { signal: abortCtrl.signal, status: 'closed', }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: '{"conflicts":"proceed","status":"closed","query":{"bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}}', - method: 'POST', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/signals/status', + expect.objectContaining({ + body: '{"conflicts":"proceed","status":"closed","query":{"bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}}', + method: 'POST', + signal: abortCtrl.signal, + }) + ); }); test('check parameter url, body when opening an alert', async () => { @@ -83,11 +89,14 @@ describe('Detections Alerts API', () => { signal: abortCtrl.signal, status: 'open', }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: '{"conflicts":"proceed","status":"open","query":{"bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}}', - method: 'POST', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/signals/status', + expect.objectContaining({ + body: '{"conflicts":"proceed","status":"open","query":{"bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}}', + method: 'POST', + signal: abortCtrl.signal, + }) + ); }); test('happy path', async () => { @@ -112,11 +121,14 @@ describe('Detections Alerts API', () => { signal: abortCtrl.signal, status: 'closed', }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: '{"status":"closed","signal_ids":["123"]}', - method: 'POST', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/signals/status', + expect.objectContaining({ + body: '{"status":"closed","signal_ids":["123"]}', + method: 'POST', + signal: abortCtrl.signal, + }) + ); }); test('check parameter url, body when opening an alert', async () => { @@ -125,11 +137,14 @@ describe('Detections Alerts API', () => { signal: abortCtrl.signal, status: 'open', }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: '{"status":"open","signal_ids":["123"]}', - method: 'POST', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/signals/status', + expect.objectContaining({ + body: '{"status":"open","signal_ids":["123"]}', + method: 'POST', + signal: abortCtrl.signal, + }) + ); }); test('happy path', async () => { @@ -150,10 +165,13 @@ describe('Detections Alerts API', () => { test('check parameter url', async () => { await getSignalIndex({ signal: abortCtrl.signal }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/index', { - method: 'GET', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/index', + expect.objectContaining({ + method: 'GET', + signal: abortCtrl.signal, + }) + ); }); test('happy path', async () => { @@ -172,10 +190,13 @@ describe('Detections Alerts API', () => { test('check parameter url', async () => { await getUserPrivilege({ signal: abortCtrl.signal }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/privileges', { - method: 'GET', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/privileges', + expect.objectContaining({ + method: 'GET', + signal: abortCtrl.signal, + }) + ); }); test('happy path', async () => { @@ -194,10 +215,13 @@ describe('Detections Alerts API', () => { test('check parameter url', async () => { await createSignalIndex({ signal: abortCtrl.signal }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/index', { - method: 'POST', - signal: abortCtrl.signal, - }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/detection_engine/index', + expect.objectContaining({ + method: 'POST', + signal: abortCtrl.signal, + }) + ); }); test('happy path', async () => { @@ -222,10 +246,13 @@ describe('Detections Alerts API', () => { comment: 'commento', caseIds: ['88c04a90-b19c-11eb-b838-bf3c7840b969'], }); - expect(postMock).toHaveBeenCalledWith('/api/endpoint/action/isolate', { - body: '{"endpoint_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', - version: '2023-10-31', - }); + expect(postMock).toHaveBeenCalledWith( + '/api/endpoint/action/isolate', + expect.objectContaining({ + body: '{"endpoint_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', + version: '2023-10-31', + }) + ); }); test('happy path', async () => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts index 20f258679f76f..ecd53bbf76a89 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts @@ -47,6 +47,7 @@ export const fetchQueryAlerts = async ({ return KibanaServices.get().http.fetch>( DETECTION_ENGINE_QUERY_SIGNALS_URL, { + version: '2023-10-31', method: 'POST', body: JSON.stringify(query), signal, @@ -91,6 +92,7 @@ export const updateAlertStatusByQuery = async ({ signal, }: UpdateAlertStatusByQueryProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { + version: '2023-10-31', method: 'POST', body: JSON.stringify({ conflicts: 'proceed', status, query }), signal, @@ -111,6 +113,7 @@ export const updateAlertStatusByIds = async ({ signal, }: UpdateAlertStatusByIdsProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { + version: '2023-10-31', method: 'POST', body: JSON.stringify({ status, signal_ids: signalIds }), signal, @@ -125,6 +128,7 @@ export const updateAlertStatusByIds = async ({ */ export const getSignalIndex = async ({ signal }: BasicSignals): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { + version: '2023-10-31', method: 'GET', signal, }); @@ -138,6 +142,7 @@ export const getSignalIndex = async ({ signal }: BasicSignals): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_ALERTS_INDEX_URL, { + version: '2023-10-31', method: 'GET', signal, }); @@ -151,6 +156,7 @@ export const checkSignalIndex = async ({ signal }: BasicSignals): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_PRIVILEGES_URL, { + version: '2023-10-31', method: 'GET', signal, }); @@ -164,6 +170,7 @@ export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { + version: '2023-10-31', method: 'POST', signal, }); diff --git a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts index b30111b06bdcb..5c4657baeafeb 100644 --- a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts +++ b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts @@ -11,91 +11,14 @@ import { i18n } from '@kbn/i18n'; -import type { - MitreTacticsOptions, - MitreTechniquesOptions, - MitreSubtechniquesOptions, -} from './types'; +import type { MitreTactic, MitreTechnique, MitreSubTechnique } from './types'; -export const tactics = [ - { - name: 'Collection', - id: 'TA0009', - reference: 'https://attack.mitre.org/tactics/TA0009', - }, - { - name: 'Command and Control', - id: 'TA0011', - reference: 'https://attack.mitre.org/tactics/TA0011', - }, - { - name: 'Credential Access', - id: 'TA0006', - reference: 'https://attack.mitre.org/tactics/TA0006', - }, - { - name: 'Defense Evasion', - id: 'TA0005', - reference: 'https://attack.mitre.org/tactics/TA0005', - }, - { - name: 'Discovery', - id: 'TA0007', - reference: 'https://attack.mitre.org/tactics/TA0007', - }, - { - name: 'Execution', - id: 'TA0002', - reference: 'https://attack.mitre.org/tactics/TA0002', - }, - { - name: 'Exfiltration', - id: 'TA0010', - reference: 'https://attack.mitre.org/tactics/TA0010', - }, - { - name: 'Impact', - id: 'TA0040', - reference: 'https://attack.mitre.org/tactics/TA0040', - }, - { - name: 'Initial Access', - id: 'TA0001', - reference: 'https://attack.mitre.org/tactics/TA0001', - }, - { - name: 'Lateral Movement', - id: 'TA0008', - reference: 'https://attack.mitre.org/tactics/TA0008', - }, - { - name: 'Persistence', - id: 'TA0003', - reference: 'https://attack.mitre.org/tactics/TA0003', - }, - { - name: 'Privilege Escalation', - id: 'TA0004', - reference: 'https://attack.mitre.org/tactics/TA0004', - }, - { - name: 'Reconnaissance', - id: 'TA0043', - reference: 'https://attack.mitre.org/tactics/TA0043', - }, - { - name: 'Resource Development', - id: 'TA0042', - reference: 'https://attack.mitre.org/tactics/TA0042', - }, -]; - -export const tacticsOptions: MitreTacticsOptions[] = [ +export const tactics: MitreTactic[] = [ { id: 'TA0009', name: 'Collection', reference: 'https://attack.mitre.org/tactics/TA0009', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.collectionDescription', { defaultMessage: 'Collection (TA0009)' } ), @@ -105,7 +28,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0011', name: 'Command and Control', reference: 'https://attack.mitre.org/tactics/TA0011', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.commandAndControlDescription', { defaultMessage: 'Command and Control (TA0011)' } ), @@ -115,7 +38,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0006', name: 'Credential Access', reference: 'https://attack.mitre.org/tactics/TA0006', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.credentialAccessDescription', { defaultMessage: 'Credential Access (TA0006)' } ), @@ -125,7 +48,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0005', name: 'Defense Evasion', reference: 'https://attack.mitre.org/tactics/TA0005', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.defenseEvasionDescription', { defaultMessage: 'Defense Evasion (TA0005)' } ), @@ -135,7 +58,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0007', name: 'Discovery', reference: 'https://attack.mitre.org/tactics/TA0007', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.discoveryDescription', { defaultMessage: 'Discovery (TA0007)' } ), @@ -145,7 +68,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0002', name: 'Execution', reference: 'https://attack.mitre.org/tactics/TA0002', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.executionDescription', { defaultMessage: 'Execution (TA0002)' } ), @@ -155,7 +78,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0010', name: 'Exfiltration', reference: 'https://attack.mitre.org/tactics/TA0010', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.exfiltrationDescription', { defaultMessage: 'Exfiltration (TA0010)' } ), @@ -165,7 +88,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0040', name: 'Impact', reference: 'https://attack.mitre.org/tactics/TA0040', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.impactDescription', { defaultMessage: 'Impact (TA0040)' } ), @@ -175,7 +98,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0001', name: 'Initial Access', reference: 'https://attack.mitre.org/tactics/TA0001', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.initialAccessDescription', { defaultMessage: 'Initial Access (TA0001)' } ), @@ -185,7 +108,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0008', name: 'Lateral Movement', reference: 'https://attack.mitre.org/tactics/TA0008', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.lateralMovementDescription', { defaultMessage: 'Lateral Movement (TA0008)' } ), @@ -195,7 +118,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0003', name: 'Persistence', reference: 'https://attack.mitre.org/tactics/TA0003', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.persistenceDescription', { defaultMessage: 'Persistence (TA0003)' } ), @@ -205,7 +128,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0004', name: 'Privilege Escalation', reference: 'https://attack.mitre.org/tactics/TA0004', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.privilegeEscalationDescription', { defaultMessage: 'Privilege Escalation (TA0004)' } ), @@ -215,7 +138,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0043', name: 'Reconnaissance', reference: 'https://attack.mitre.org/tactics/TA0043', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.reconnaissanceDescription', { defaultMessage: 'Reconnaissance (TA0043)' } ), @@ -225,7 +148,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ id: 'TA0042', name: 'Resource Development', reference: 'https://attack.mitre.org/tactics/TA0042', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.resourceDevelopmentDescription', { defaultMessage: 'Resource Development (TA0042)' } ), @@ -233,8505 +156,3706 @@ export const tacticsOptions: MitreTacticsOptions[] = [ }, ]; -export const technique = [ +export const techniques: MitreTechnique[] = [ { - name: 'Abuse Elevation Control Mechanism', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.abuseElevationControlMechanismDescription', + { defaultMessage: 'Abuse Elevation Control Mechanism (T1548)' } + ), id: 'T1548', + name: 'Abuse Elevation Control Mechanism', reference: 'https://attack.mitre.org/techniques/T1548', tactics: ['privilege-escalation', 'defense-evasion'], + value: 'abuseElevationControlMechanism', }, { - name: 'Access Token Manipulation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accessTokenManipulationDescription', + { defaultMessage: 'Access Token Manipulation (T1134)' } + ), id: 'T1134', + name: 'Access Token Manipulation', reference: 'https://attack.mitre.org/techniques/T1134', tactics: ['defense-evasion', 'privilege-escalation'], + value: 'accessTokenManipulation', }, { - name: 'Accessibility Features', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accessibilityFeaturesDescription', + { defaultMessage: 'Accessibility Features (T1015)' } + ), id: 'T1015', + name: 'Accessibility Features', reference: 'https://attack.mitre.org/techniques/T1015', tactics: ['persistence', 'privilege-escalation'], + value: 'accessibilityFeatures', }, { - name: 'Account Access Removal', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountAccessRemovalDescription', + { defaultMessage: 'Account Access Removal (T1531)' } + ), id: 'T1531', + name: 'Account Access Removal', reference: 'https://attack.mitre.org/techniques/T1531', tactics: ['impact'], + value: 'accountAccessRemoval', }, { - name: 'Account Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountDiscoveryDescription', + { defaultMessage: 'Account Discovery (T1087)' } + ), id: 'T1087', + name: 'Account Discovery', reference: 'https://attack.mitre.org/techniques/T1087', tactics: ['discovery'], + value: 'accountDiscovery', }, { - name: 'Account Manipulation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountManipulationDescription', + { defaultMessage: 'Account Manipulation (T1098)' } + ), id: 'T1098', + name: 'Account Manipulation', reference: 'https://attack.mitre.org/techniques/T1098', tactics: ['persistence'], + value: 'accountManipulation', }, { - name: 'Acquire Infrastructure', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.acquireAccessDescription', + { defaultMessage: 'Acquire Access (T1650)' } + ), + id: 'T1650', + name: 'Acquire Access', + reference: 'https://attack.mitre.org/techniques/T1650', + tactics: ['resource-development'], + value: 'acquireAccess', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.acquireInfrastructureDescription', + { defaultMessage: 'Acquire Infrastructure (T1583)' } + ), id: 'T1583', + name: 'Acquire Infrastructure', reference: 'https://attack.mitre.org/techniques/T1583', tactics: ['resource-development'], + value: 'acquireInfrastructure', }, { - name: 'Active Scanning', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.activeScanningDescription', + { defaultMessage: 'Active Scanning (T1595)' } + ), id: 'T1595', + name: 'Active Scanning', reference: 'https://attack.mitre.org/techniques/T1595', tactics: ['reconnaissance'], + value: 'activeScanning', }, { - name: 'Adversary-in-the-Middle', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.adversaryInTheMiddleDescription', + { defaultMessage: 'Adversary-in-the-Middle (T1557)' } + ), id: 'T1557', + name: 'Adversary-in-the-Middle', reference: 'https://attack.mitre.org/techniques/T1557', tactics: ['credential-access', 'collection'], + value: 'adversaryInTheMiddle', }, { - name: 'AppCert DLLs', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appCertDlLsDescription', + { defaultMessage: 'AppCert DLLs (T1182)' } + ), id: 'T1182', + name: 'AppCert DLLs', reference: 'https://attack.mitre.org/techniques/T1182', tactics: ['persistence', 'privilege-escalation'], + value: 'appCertDlLs', }, { - name: 'AppInit DLLs', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appInitDlLsDescription', + { defaultMessage: 'AppInit DLLs (T1103)' } + ), id: 'T1103', + name: 'AppInit DLLs', reference: 'https://attack.mitre.org/techniques/T1103', tactics: ['persistence', 'privilege-escalation'], + value: 'appInitDlLs', }, { - name: 'AppleScript', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appleScriptDescription', + { defaultMessage: 'AppleScript (T1155)' } + ), id: 'T1155', + name: 'AppleScript', reference: 'https://attack.mitre.org/techniques/T1155', tactics: ['execution'], + value: 'appleScript', }, { - name: 'Application Access Token', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationAccessTokenDescription', + { defaultMessage: 'Application Access Token (T1527)' } + ), id: 'T1527', + name: 'Application Access Token', reference: 'https://attack.mitre.org/techniques/T1527', tactics: ['defense-evasion', 'lateral-movement'], + value: 'applicationAccessToken', }, { - name: 'Application Deployment Software', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationDeploymentSoftwareDescription', + { defaultMessage: 'Application Deployment Software (T1017)' } + ), id: 'T1017', + name: 'Application Deployment Software', reference: 'https://attack.mitre.org/techniques/T1017', tactics: ['lateral-movement'], + value: 'applicationDeploymentSoftware', }, { - name: 'Application Layer Protocol', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationLayerProtocolDescription', + { defaultMessage: 'Application Layer Protocol (T1071)' } + ), id: 'T1071', + name: 'Application Layer Protocol', reference: 'https://attack.mitre.org/techniques/T1071', tactics: ['command-and-control'], + value: 'applicationLayerProtocol', }, { - name: 'Application Shimming', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationShimmingDescription', + { defaultMessage: 'Application Shimming (T1138)' } + ), id: 'T1138', + name: 'Application Shimming', reference: 'https://attack.mitre.org/techniques/T1138', tactics: ['persistence', 'privilege-escalation'], + value: 'applicationShimming', }, { - name: 'Application Window Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationWindowDiscoveryDescription', + { defaultMessage: 'Application Window Discovery (T1010)' } + ), id: 'T1010', + name: 'Application Window Discovery', reference: 'https://attack.mitre.org/techniques/T1010', tactics: ['discovery'], + value: 'applicationWindowDiscovery', }, { - name: 'Archive Collected Data', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.archiveCollectedDataDescription', + { defaultMessage: 'Archive Collected Data (T1560)' } + ), id: 'T1560', + name: 'Archive Collected Data', reference: 'https://attack.mitre.org/techniques/T1560', tactics: ['collection'], + value: 'archiveCollectedData', }, { - name: 'Audio Capture', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.audioCaptureDescription', + { defaultMessage: 'Audio Capture (T1123)' } + ), id: 'T1123', + name: 'Audio Capture', reference: 'https://attack.mitre.org/techniques/T1123', tactics: ['collection'], + value: 'audioCapture', }, { - name: 'Authentication Package', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.authenticationPackageDescription', + { defaultMessage: 'Authentication Package (T1131)' } + ), id: 'T1131', + name: 'Authentication Package', reference: 'https://attack.mitre.org/techniques/T1131', tactics: ['persistence'], + value: 'authenticationPackage', }, { - name: 'Automated Collection', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.automatedCollectionDescription', + { defaultMessage: 'Automated Collection (T1119)' } + ), id: 'T1119', + name: 'Automated Collection', reference: 'https://attack.mitre.org/techniques/T1119', tactics: ['collection'], + value: 'automatedCollection', }, { - name: 'Automated Exfiltration', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.automatedExfiltrationDescription', + { defaultMessage: 'Automated Exfiltration (T1020)' } + ), id: 'T1020', + name: 'Automated Exfiltration', reference: 'https://attack.mitre.org/techniques/T1020', tactics: ['exfiltration'], + value: 'automatedExfiltration', }, { - name: 'BITS Jobs', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bitsJobsDescription', + { defaultMessage: 'BITS Jobs (T1197)' } + ), id: 'T1197', + name: 'BITS Jobs', reference: 'https://attack.mitre.org/techniques/T1197', tactics: ['defense-evasion', 'persistence'], + value: 'bitsJobs', }, { - name: 'Bash History', - id: 'T1139', - reference: 'https://attack.mitre.org/techniques/T1139', - tactics: ['credential-access'], + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bashHistoryDescription', + { defaultMessage: 'Bash History (T1139)' } + ), + id: 'T1139', + name: 'Bash History', + reference: 'https://attack.mitre.org/techniques/T1139', + tactics: ['credential-access'], + value: 'bashHistory', }, { - name: 'Binary Padding', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.binaryPaddingDescription', + { defaultMessage: 'Binary Padding (T1009)' } + ), id: 'T1009', + name: 'Binary Padding', reference: 'https://attack.mitre.org/techniques/T1009', tactics: ['defense-evasion'], + value: 'binaryPadding', }, { - name: 'Boot or Logon Autostart Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonAutostartExecutionDescription', + { defaultMessage: 'Boot or Logon Autostart Execution (T1547)' } + ), id: 'T1547', + name: 'Boot or Logon Autostart Execution', reference: 'https://attack.mitre.org/techniques/T1547', tactics: ['persistence', 'privilege-escalation'], + value: 'bootOrLogonAutostartExecution', }, { - name: 'Boot or Logon Initialization Scripts', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonInitializationScriptsDescription', + { defaultMessage: 'Boot or Logon Initialization Scripts (T1037)' } + ), id: 'T1037', + name: 'Boot or Logon Initialization Scripts', reference: 'https://attack.mitre.org/techniques/T1037', tactics: ['persistence', 'privilege-escalation'], + value: 'bootOrLogonInitializationScripts', }, { - name: 'Bootkit', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootkitDescription', + { defaultMessage: 'Bootkit (T1067)' } + ), id: 'T1067', + name: 'Bootkit', reference: 'https://attack.mitre.org/techniques/T1067', tactics: ['persistence'], + value: 'bootkit', }, { - name: 'Browser Bookmark Discovery', - id: 'T1217', - reference: 'https://attack.mitre.org/techniques/T1217', - tactics: ['discovery'], - }, - { - name: 'Browser Extensions', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserExtensionsDescription', + { defaultMessage: 'Browser Extensions (T1176)' } + ), id: 'T1176', + name: 'Browser Extensions', reference: 'https://attack.mitre.org/techniques/T1176', tactics: ['persistence'], + value: 'browserExtensions', }, { - name: 'Browser Session Hijacking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserInformationDiscoveryDescription', + { defaultMessage: 'Browser Information Discovery (T1217)' } + ), + id: 'T1217', + name: 'Browser Information Discovery', + reference: 'https://attack.mitre.org/techniques/T1217', + tactics: ['discovery'], + value: 'browserInformationDiscovery', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserSessionHijackingDescription', + { defaultMessage: 'Browser Session Hijacking (T1185)' } + ), id: 'T1185', + name: 'Browser Session Hijacking', reference: 'https://attack.mitre.org/techniques/T1185', tactics: ['collection'], + value: 'browserSessionHijacking', }, { - name: 'Brute Force', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bruteForceDescription', + { defaultMessage: 'Brute Force (T1110)' } + ), id: 'T1110', + name: 'Brute Force', reference: 'https://attack.mitre.org/techniques/T1110', tactics: ['credential-access'], + value: 'bruteForce', }, { - name: 'Build Image on Host', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.buildImageOnHostDescription', + { defaultMessage: 'Build Image on Host (T1612)' } + ), id: 'T1612', + name: 'Build Image on Host', reference: 'https://attack.mitre.org/techniques/T1612', tactics: ['defense-evasion'], + value: 'buildImageOnHost', }, { - name: 'Bypass User Account Control', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bypassUserAccountControlDescription', + { defaultMessage: 'Bypass User Account Control (T1088)' } + ), id: 'T1088', + name: 'Bypass User Account Control', reference: 'https://attack.mitre.org/techniques/T1088', tactics: ['defense-evasion', 'privilege-escalation'], + value: 'bypassUserAccountControl', }, { - name: 'CMSTP', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cmstpDescription', + { defaultMessage: 'CMSTP (T1191)' } + ), id: 'T1191', + name: 'CMSTP', reference: 'https://attack.mitre.org/techniques/T1191', tactics: ['defense-evasion', 'execution'], + value: 'cmstp', }, { - name: 'Change Default File Association', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.changeDefaultFileAssociationDescription', + { defaultMessage: 'Change Default File Association (T1042)' } + ), id: 'T1042', + name: 'Change Default File Association', reference: 'https://attack.mitre.org/techniques/T1042', tactics: ['persistence'], + value: 'changeDefaultFileAssociation', }, { - name: 'Clear Command History', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.clearCommandHistoryDescription', + { defaultMessage: 'Clear Command History (T1146)' } + ), id: 'T1146', + name: 'Clear Command History', reference: 'https://attack.mitre.org/techniques/T1146', tactics: ['defense-evasion'], + value: 'clearCommandHistory', }, { - name: 'Clipboard Data', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.clipboardDataDescription', + { defaultMessage: 'Clipboard Data (T1115)' } + ), id: 'T1115', + name: 'Clipboard Data', reference: 'https://attack.mitre.org/techniques/T1115', tactics: ['collection'], + value: 'clipboardData', }, { - name: 'Cloud Infrastructure Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudAdministrationCommandDescription', + { defaultMessage: 'Cloud Administration Command (T1651)' } + ), + id: 'T1651', + name: 'Cloud Administration Command', + reference: 'https://attack.mitre.org/techniques/T1651', + tactics: ['execution'], + value: 'cloudAdministrationCommand', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudInfrastructureDiscoveryDescription', + { defaultMessage: 'Cloud Infrastructure Discovery (T1580)' } + ), id: 'T1580', + name: 'Cloud Infrastructure Discovery', reference: 'https://attack.mitre.org/techniques/T1580', tactics: ['discovery'], + value: 'cloudInfrastructureDiscovery', }, { - name: 'Cloud Instance Metadata API', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudInstanceMetadataApiDescription', + { defaultMessage: 'Cloud Instance Metadata API (T1522)' } + ), id: 'T1522', + name: 'Cloud Instance Metadata API', reference: 'https://attack.mitre.org/techniques/T1522', tactics: ['credential-access'], + value: 'cloudInstanceMetadataApi', }, { - name: 'Cloud Service Dashboard', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudServiceDashboardDescription', + { defaultMessage: 'Cloud Service Dashboard (T1538)' } + ), id: 'T1538', + name: 'Cloud Service Dashboard', reference: 'https://attack.mitre.org/techniques/T1538', tactics: ['discovery'], + value: 'cloudServiceDashboard', }, { - name: 'Cloud Service Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudServiceDiscoveryDescription', + { defaultMessage: 'Cloud Service Discovery (T1526)' } + ), id: 'T1526', + name: 'Cloud Service Discovery', reference: 'https://attack.mitre.org/techniques/T1526', tactics: ['discovery'], + value: 'cloudServiceDiscovery', }, { - name: 'Cloud Storage Object Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudStorageObjectDiscoveryDescription', + { defaultMessage: 'Cloud Storage Object Discovery (T1619)' } + ), id: 'T1619', + name: 'Cloud Storage Object Discovery', reference: 'https://attack.mitre.org/techniques/T1619', tactics: ['discovery'], + value: 'cloudStorageObjectDiscovery', }, { - name: 'Code Signing', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.codeSigningDescription', + { defaultMessage: 'Code Signing (T1116)' } + ), id: 'T1116', + name: 'Code Signing', reference: 'https://attack.mitre.org/techniques/T1116', tactics: ['defense-evasion'], + value: 'codeSigning', }, { - name: 'Command and Scripting Interpreter', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.commandAndScriptingInterpreterDescription', + { defaultMessage: 'Command and Scripting Interpreter (T1059)' } + ), id: 'T1059', + name: 'Command and Scripting Interpreter', reference: 'https://attack.mitre.org/techniques/T1059', tactics: ['execution'], + value: 'commandAndScriptingInterpreter', }, { - name: 'Commonly Used Port', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.commonlyUsedPortDescription', + { defaultMessage: 'Commonly Used Port (T1043)' } + ), id: 'T1043', + name: 'Commonly Used Port', reference: 'https://attack.mitre.org/techniques/T1043', tactics: ['command-and-control'], + value: 'commonlyUsedPort', }, { - name: 'Communication Through Removable Media', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.communicationThroughRemovableMediaDescription', + { defaultMessage: 'Communication Through Removable Media (T1092)' } + ), id: 'T1092', + name: 'Communication Through Removable Media', reference: 'https://attack.mitre.org/techniques/T1092', tactics: ['command-and-control'], + value: 'communicationThroughRemovableMedia', }, { - name: 'Compile After Delivery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compileAfterDeliveryDescription', + { defaultMessage: 'Compile After Delivery (T1500)' } + ), id: 'T1500', + name: 'Compile After Delivery', reference: 'https://attack.mitre.org/techniques/T1500', tactics: ['defense-evasion'], + value: 'compileAfterDelivery', }, { - name: 'Compiled HTML File', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compiledHtmlFileDescription', + { defaultMessage: 'Compiled HTML File (T1223)' } + ), id: 'T1223', + name: 'Compiled HTML File', reference: 'https://attack.mitre.org/techniques/T1223', tactics: ['defense-evasion', 'execution'], + value: 'compiledHtmlFile', }, { - name: 'Component Firmware', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentFirmwareDescription', + { defaultMessage: 'Component Firmware (T1109)' } + ), id: 'T1109', + name: 'Component Firmware', reference: 'https://attack.mitre.org/techniques/T1109', tactics: ['defense-evasion', 'persistence'], + value: 'componentFirmware', }, { - name: 'Component Object Model Hijacking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentObjectModelHijackingDescription', + { defaultMessage: 'Component Object Model Hijacking (T1122)' } + ), id: 'T1122', + name: 'Component Object Model Hijacking', reference: 'https://attack.mitre.org/techniques/T1122', tactics: ['defense-evasion', 'persistence'], + value: 'componentObjectModelHijacking', }, { - name: 'Component Object Model and Distributed COM', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentObjectModelAndDistributedComDescription', + { defaultMessage: 'Component Object Model and Distributed COM (T1175)' } + ), id: 'T1175', + name: 'Component Object Model and Distributed COM', reference: 'https://attack.mitre.org/techniques/T1175', tactics: ['lateral-movement', 'execution'], + value: 'componentObjectModelAndDistributedCom', }, { - name: 'Compromise Accounts', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compromiseAccountsDescription', + { defaultMessage: 'Compromise Accounts (T1586)' } + ), id: 'T1586', + name: 'Compromise Accounts', reference: 'https://attack.mitre.org/techniques/T1586', tactics: ['resource-development'], + value: 'compromiseAccounts', }, { - name: 'Compromise Client Software Binary', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compromiseClientSoftwareBinaryDescription', + { defaultMessage: 'Compromise Client Software Binary (T1554)' } + ), id: 'T1554', + name: 'Compromise Client Software Binary', reference: 'https://attack.mitre.org/techniques/T1554', tactics: ['persistence'], + value: 'compromiseClientSoftwareBinary', }, { - name: 'Compromise Infrastructure', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compromiseInfrastructureDescription', + { defaultMessage: 'Compromise Infrastructure (T1584)' } + ), id: 'T1584', + name: 'Compromise Infrastructure', reference: 'https://attack.mitre.org/techniques/T1584', tactics: ['resource-development'], + value: 'compromiseInfrastructure', }, { - name: 'Container Administration Command', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.containerAdministrationCommandDescription', + { defaultMessage: 'Container Administration Command (T1609)' } + ), id: 'T1609', + name: 'Container Administration Command', reference: 'https://attack.mitre.org/techniques/T1609', tactics: ['execution'], + value: 'containerAdministrationCommand', }, { - name: 'Container and Resource Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.containerAndResourceDiscoveryDescription', + { defaultMessage: 'Container and Resource Discovery (T1613)' } + ), id: 'T1613', + name: 'Container and Resource Discovery', reference: 'https://attack.mitre.org/techniques/T1613', tactics: ['discovery'], + value: 'containerAndResourceDiscovery', }, { - name: 'Control Panel Items', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.controlPanelItemsDescription', + { defaultMessage: 'Control Panel Items (T1196)' } + ), id: 'T1196', + name: 'Control Panel Items', reference: 'https://attack.mitre.org/techniques/T1196', tactics: ['defense-evasion', 'execution'], + value: 'controlPanelItems', }, { - name: 'Create Account', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.createAccountDescription', + { defaultMessage: 'Create Account (T1136)' } + ), id: 'T1136', + name: 'Create Account', reference: 'https://attack.mitre.org/techniques/T1136', tactics: ['persistence'], + value: 'createAccount', }, { - name: 'Create or Modify System Process', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.createOrModifySystemProcessDescription', + { defaultMessage: 'Create or Modify System Process (T1543)' } + ), id: 'T1543', + name: 'Create or Modify System Process', reference: 'https://attack.mitre.org/techniques/T1543', tactics: ['persistence', 'privilege-escalation'], + value: 'createOrModifySystemProcess', }, { - name: 'Credentials from Password Stores', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsFromPasswordStoresDescription', + { defaultMessage: 'Credentials from Password Stores (T1555)' } + ), id: 'T1555', + name: 'Credentials from Password Stores', reference: 'https://attack.mitre.org/techniques/T1555', tactics: ['credential-access'], + value: 'credentialsFromPasswordStores', }, { - name: 'Credentials from Web Browsers', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsFromWebBrowsersDescription', + { defaultMessage: 'Credentials from Web Browsers (T1503)' } + ), id: 'T1503', + name: 'Credentials from Web Browsers', reference: 'https://attack.mitre.org/techniques/T1503', tactics: ['credential-access'], + value: 'credentialsFromWebBrowsers', }, { - name: 'Credentials in Files', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsInFilesDescription', + { defaultMessage: 'Credentials in Files (T1081)' } + ), id: 'T1081', + name: 'Credentials in Files', reference: 'https://attack.mitre.org/techniques/T1081', tactics: ['credential-access'], + value: 'credentialsInFiles', }, { - name: 'Credentials in Registry', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsInRegistryDescription', + { defaultMessage: 'Credentials in Registry (T1214)' } + ), id: 'T1214', + name: 'Credentials in Registry', reference: 'https://attack.mitre.org/techniques/T1214', tactics: ['credential-access'], + value: 'credentialsInRegistry', }, { - name: 'Custom Command and Control Protocol', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.customCommandAndControlProtocolDescription', + { defaultMessage: 'Custom Command and Control Protocol (T1094)' } + ), id: 'T1094', + name: 'Custom Command and Control Protocol', reference: 'https://attack.mitre.org/techniques/T1094', tactics: ['command-and-control'], + value: 'customCommandAndControlProtocol', }, { - name: 'Custom Cryptographic Protocol', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.customCryptographicProtocolDescription', + { defaultMessage: 'Custom Cryptographic Protocol (T1024)' } + ), id: 'T1024', + name: 'Custom Cryptographic Protocol', reference: 'https://attack.mitre.org/techniques/T1024', tactics: ['command-and-control'], + value: 'customCryptographicProtocol', }, { - name: 'DLL Search Order Hijacking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dllSearchOrderHijackingDescription', + { defaultMessage: 'DLL Search Order Hijacking (T1038)' } + ), id: 'T1038', + name: 'DLL Search Order Hijacking', reference: 'https://attack.mitre.org/techniques/T1038', tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], + value: 'dllSearchOrderHijacking', }, { - name: 'DLL Side-Loading', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dllSideLoadingDescription', + { defaultMessage: 'DLL Side-Loading (T1073)' } + ), id: 'T1073', + name: 'DLL Side-Loading', reference: 'https://attack.mitre.org/techniques/T1073', tactics: ['defense-evasion'], + value: 'dllSideLoading', }, { - name: 'Data Compressed', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataCompressedDescription', + { defaultMessage: 'Data Compressed (T1002)' } + ), id: 'T1002', + name: 'Data Compressed', reference: 'https://attack.mitre.org/techniques/T1002', tactics: ['exfiltration'], + value: 'dataCompressed', }, { - name: 'Data Destruction', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataDestructionDescription', + { defaultMessage: 'Data Destruction (T1485)' } + ), id: 'T1485', + name: 'Data Destruction', reference: 'https://attack.mitre.org/techniques/T1485', tactics: ['impact'], + value: 'dataDestruction', }, { - name: 'Data Encoding', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncodingDescription', + { defaultMessage: 'Data Encoding (T1132)' } + ), id: 'T1132', + name: 'Data Encoding', reference: 'https://attack.mitre.org/techniques/T1132', tactics: ['command-and-control'], + value: 'dataEncoding', }, { - name: 'Data Encrypted', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncryptedDescription', + { defaultMessage: 'Data Encrypted (T1022)' } + ), id: 'T1022', + name: 'Data Encrypted', reference: 'https://attack.mitre.org/techniques/T1022', tactics: ['exfiltration'], + value: 'dataEncrypted', }, { - name: 'Data Encrypted for Impact', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncryptedForImpactDescription', + { defaultMessage: 'Data Encrypted for Impact (T1486)' } + ), id: 'T1486', + name: 'Data Encrypted for Impact', reference: 'https://attack.mitre.org/techniques/T1486', tactics: ['impact'], + value: 'dataEncryptedForImpact', }, { - name: 'Data Manipulation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataManipulationDescription', + { defaultMessage: 'Data Manipulation (T1565)' } + ), id: 'T1565', + name: 'Data Manipulation', reference: 'https://attack.mitre.org/techniques/T1565', tactics: ['impact'], + value: 'dataManipulation', }, { - name: 'Data Obfuscation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataObfuscationDescription', + { defaultMessage: 'Data Obfuscation (T1001)' } + ), id: 'T1001', + name: 'Data Obfuscation', reference: 'https://attack.mitre.org/techniques/T1001', tactics: ['command-and-control'], + value: 'dataObfuscation', }, { - name: 'Data Staged', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataStagedDescription', + { defaultMessage: 'Data Staged (T1074)' } + ), id: 'T1074', + name: 'Data Staged', reference: 'https://attack.mitre.org/techniques/T1074', tactics: ['collection'], + value: 'dataStaged', }, { - name: 'Data Transfer Size Limits', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataTransferSizeLimitsDescription', + { defaultMessage: 'Data Transfer Size Limits (T1030)' } + ), id: 'T1030', + name: 'Data Transfer Size Limits', reference: 'https://attack.mitre.org/techniques/T1030', tactics: ['exfiltration'], + value: 'dataTransferSizeLimits', }, { - name: 'Data from Cloud Storage', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromCloudStorageDescription', + { defaultMessage: 'Data from Cloud Storage (T1530)' } + ), id: 'T1530', + name: 'Data from Cloud Storage', reference: 'https://attack.mitre.org/techniques/T1530', tactics: ['collection'], + value: 'dataFromCloudStorage', }, { - name: 'Data from Configuration Repository', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromConfigurationRepositoryDescription', + { defaultMessage: 'Data from Configuration Repository (T1602)' } + ), id: 'T1602', + name: 'Data from Configuration Repository', reference: 'https://attack.mitre.org/techniques/T1602', tactics: ['collection'], + value: 'dataFromConfigurationRepository', }, { - name: 'Data from Information Repositories', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromInformationRepositoriesDescription', + { defaultMessage: 'Data from Information Repositories (T1213)' } + ), id: 'T1213', + name: 'Data from Information Repositories', reference: 'https://attack.mitre.org/techniques/T1213', tactics: ['collection'], + value: 'dataFromInformationRepositories', }, { - name: 'Data from Local System', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromLocalSystemDescription', + { defaultMessage: 'Data from Local System (T1005)' } + ), id: 'T1005', + name: 'Data from Local System', reference: 'https://attack.mitre.org/techniques/T1005', tactics: ['collection'], + value: 'dataFromLocalSystem', }, { - name: 'Data from Network Shared Drive', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromNetworkSharedDriveDescription', + { defaultMessage: 'Data from Network Shared Drive (T1039)' } + ), id: 'T1039', + name: 'Data from Network Shared Drive', reference: 'https://attack.mitre.org/techniques/T1039', tactics: ['collection'], + value: 'dataFromNetworkSharedDrive', }, { - name: 'Data from Removable Media', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromRemovableMediaDescription', + { defaultMessage: 'Data from Removable Media (T1025)' } + ), id: 'T1025', + name: 'Data from Removable Media', reference: 'https://attack.mitre.org/techniques/T1025', tactics: ['collection'], + value: 'dataFromRemovableMedia', }, { - name: 'Debugger Evasion', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.debuggerEvasionDescription', + { defaultMessage: 'Debugger Evasion (T1622)' } + ), id: 'T1622', + name: 'Debugger Evasion', reference: 'https://attack.mitre.org/techniques/T1622', tactics: ['defense-evasion', 'discovery'], + value: 'debuggerEvasion', }, { - name: 'Defacement', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.defacementDescription', + { defaultMessage: 'Defacement (T1491)' } + ), id: 'T1491', + name: 'Defacement', reference: 'https://attack.mitre.org/techniques/T1491', tactics: ['impact'], + value: 'defacement', }, { - name: 'Deobfuscate/Decode Files or Information', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.deobfuscateDecodeFilesOrInformationDescription', + { defaultMessage: 'Deobfuscate/Decode Files or Information (T1140)' } + ), id: 'T1140', + name: 'Deobfuscate/Decode Files or Information', reference: 'https://attack.mitre.org/techniques/T1140', tactics: ['defense-evasion'], + value: 'deobfuscateDecodeFilesOrInformation', }, { - name: 'Deploy Container', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.deployContainerDescription', + { defaultMessage: 'Deploy Container (T1610)' } + ), id: 'T1610', + name: 'Deploy Container', reference: 'https://attack.mitre.org/techniques/T1610', tactics: ['defense-evasion', 'execution'], + value: 'deployContainer', }, { - name: 'Develop Capabilities', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.developCapabilitiesDescription', + { defaultMessage: 'Develop Capabilities (T1587)' } + ), id: 'T1587', + name: 'Develop Capabilities', reference: 'https://attack.mitre.org/techniques/T1587', tactics: ['resource-development'], + value: 'developCapabilities', }, { - name: 'Direct Volume Access', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.deviceDriverDiscoveryDescription', + { defaultMessage: 'Device Driver Discovery (T1652)' } + ), + id: 'T1652', + name: 'Device Driver Discovery', + reference: 'https://attack.mitre.org/techniques/T1652', + tactics: ['discovery'], + value: 'deviceDriverDiscovery', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.directVolumeAccessDescription', + { defaultMessage: 'Direct Volume Access (T1006)' } + ), id: 'T1006', + name: 'Direct Volume Access', reference: 'https://attack.mitre.org/techniques/T1006', tactics: ['defense-evasion'], + value: 'directVolumeAccess', }, { - name: 'Disabling Security Tools', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.disablingSecurityToolsDescription', + { defaultMessage: 'Disabling Security Tools (T1089)' } + ), id: 'T1089', + name: 'Disabling Security Tools', reference: 'https://attack.mitre.org/techniques/T1089', tactics: ['defense-evasion'], + value: 'disablingSecurityTools', }, { - name: 'Disk Content Wipe', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskContentWipeDescription', + { defaultMessage: 'Disk Content Wipe (T1488)' } + ), id: 'T1488', + name: 'Disk Content Wipe', reference: 'https://attack.mitre.org/techniques/T1488', tactics: ['impact'], + value: 'diskContentWipe', }, { - name: 'Disk Structure Wipe', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskStructureWipeDescription', + { defaultMessage: 'Disk Structure Wipe (T1487)' } + ), id: 'T1487', + name: 'Disk Structure Wipe', reference: 'https://attack.mitre.org/techniques/T1487', tactics: ['impact'], + value: 'diskStructureWipe', }, { - name: 'Disk Wipe', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskWipeDescription', + { defaultMessage: 'Disk Wipe (T1561)' } + ), id: 'T1561', + name: 'Disk Wipe', reference: 'https://attack.mitre.org/techniques/T1561', tactics: ['impact'], + value: 'diskWipe', }, { - name: 'Domain Fronting', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainFrontingDescription', + { defaultMessage: 'Domain Fronting (T1172)' } + ), id: 'T1172', + name: 'Domain Fronting', reference: 'https://attack.mitre.org/techniques/T1172', tactics: ['command-and-control'], + value: 'domainFronting', }, { - name: 'Domain Generation Algorithms', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainGenerationAlgorithmsDescription', + { defaultMessage: 'Domain Generation Algorithms (T1483)' } + ), id: 'T1483', + name: 'Domain Generation Algorithms', reference: 'https://attack.mitre.org/techniques/T1483', tactics: ['command-and-control'], + value: 'domainGenerationAlgorithms', }, { - name: 'Domain Policy Modification', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainPolicyModificationDescription', + { defaultMessage: 'Domain Policy Modification (T1484)' } + ), id: 'T1484', + name: 'Domain Policy Modification', reference: 'https://attack.mitre.org/techniques/T1484', tactics: ['defense-evasion', 'privilege-escalation'], + value: 'domainPolicyModification', }, { - name: 'Domain Trust Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainTrustDiscoveryDescription', + { defaultMessage: 'Domain Trust Discovery (T1482)' } + ), id: 'T1482', + name: 'Domain Trust Discovery', reference: 'https://attack.mitre.org/techniques/T1482', tactics: ['discovery'], + value: 'domainTrustDiscovery', }, { - name: 'Drive-by Compromise', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.driveByCompromiseDescription', + { defaultMessage: 'Drive-by Compromise (T1189)' } + ), id: 'T1189', + name: 'Drive-by Compromise', reference: 'https://attack.mitre.org/techniques/T1189', tactics: ['initial-access'], + value: 'driveByCompromise', }, { - name: 'Dylib Hijacking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dylibHijackingDescription', + { defaultMessage: 'Dylib Hijacking (T1157)' } + ), id: 'T1157', + name: 'Dylib Hijacking', reference: 'https://attack.mitre.org/techniques/T1157', tactics: ['persistence', 'privilege-escalation'], + value: 'dylibHijacking', }, { - name: 'Dynamic Data Exchange', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dynamicDataExchangeDescription', + { defaultMessage: 'Dynamic Data Exchange (T1173)' } + ), id: 'T1173', + name: 'Dynamic Data Exchange', reference: 'https://attack.mitre.org/techniques/T1173', tactics: ['execution'], + value: 'dynamicDataExchange', }, { - name: 'Dynamic Resolution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dynamicResolutionDescription', + { defaultMessage: 'Dynamic Resolution (T1568)' } + ), id: 'T1568', + name: 'Dynamic Resolution', reference: 'https://attack.mitre.org/techniques/T1568', tactics: ['command-and-control'], + value: 'dynamicResolution', }, { - name: 'Elevated Execution with Prompt', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.elevatedExecutionWithPromptDescription', + { defaultMessage: 'Elevated Execution with Prompt (T1514)' } + ), id: 'T1514', + name: 'Elevated Execution with Prompt', reference: 'https://attack.mitre.org/techniques/T1514', tactics: ['privilege-escalation'], + value: 'elevatedExecutionWithPrompt', }, { - name: 'Email Collection', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.emailCollectionDescription', + { defaultMessage: 'Email Collection (T1114)' } + ), id: 'T1114', + name: 'Email Collection', reference: 'https://attack.mitre.org/techniques/T1114', tactics: ['collection'], + value: 'emailCollection', }, { - name: 'Emond', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.emondDescription', + { defaultMessage: 'Emond (T1519)' } + ), id: 'T1519', + name: 'Emond', reference: 'https://attack.mitre.org/techniques/T1519', tactics: ['persistence', 'privilege-escalation'], + value: 'emond', }, { - name: 'Encrypted Channel', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.encryptedChannelDescription', + { defaultMessage: 'Encrypted Channel (T1573)' } + ), id: 'T1573', + name: 'Encrypted Channel', reference: 'https://attack.mitre.org/techniques/T1573', tactics: ['command-and-control'], + value: 'encryptedChannel', }, { - name: 'Endpoint Denial of Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.endpointDenialOfServiceDescription', + { defaultMessage: 'Endpoint Denial of Service (T1499)' } + ), id: 'T1499', + name: 'Endpoint Denial of Service', reference: 'https://attack.mitre.org/techniques/T1499', tactics: ['impact'], + value: 'endpointDenialOfService', }, { - name: 'Escape to Host', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.escapeToHostDescription', + { defaultMessage: 'Escape to Host (T1611)' } + ), id: 'T1611', + name: 'Escape to Host', reference: 'https://attack.mitre.org/techniques/T1611', tactics: ['privilege-escalation'], + value: 'escapeToHost', }, { - name: 'Establish Accounts', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.establishAccountsDescription', + { defaultMessage: 'Establish Accounts (T1585)' } + ), id: 'T1585', + name: 'Establish Accounts', reference: 'https://attack.mitre.org/techniques/T1585', tactics: ['resource-development'], + value: 'establishAccounts', }, { - name: 'Event Triggered Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.eventTriggeredExecutionDescription', + { defaultMessage: 'Event Triggered Execution (T1546)' } + ), id: 'T1546', + name: 'Event Triggered Execution', reference: 'https://attack.mitre.org/techniques/T1546', tactics: ['privilege-escalation', 'persistence'], + value: 'eventTriggeredExecution', }, { - name: 'Execution Guardrails', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.executionGuardrailsDescription', + { defaultMessage: 'Execution Guardrails (T1480)' } + ), id: 'T1480', + name: 'Execution Guardrails', reference: 'https://attack.mitre.org/techniques/T1480', tactics: ['defense-evasion'], + value: 'executionGuardrails', }, { - name: 'Exfiltration Over Alternative Protocol', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverAlternativeProtocolDescription', + { defaultMessage: 'Exfiltration Over Alternative Protocol (T1048)' } + ), id: 'T1048', + name: 'Exfiltration Over Alternative Protocol', reference: 'https://attack.mitre.org/techniques/T1048', tactics: ['exfiltration'], + value: 'exfiltrationOverAlternativeProtocol', }, { - name: 'Exfiltration Over C2 Channel', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverC2ChannelDescription', + { defaultMessage: 'Exfiltration Over C2 Channel (T1041)' } + ), id: 'T1041', + name: 'Exfiltration Over C2 Channel', reference: 'https://attack.mitre.org/techniques/T1041', tactics: ['exfiltration'], + value: 'exfiltrationOverC2Channel', }, { - name: 'Exfiltration Over Other Network Medium', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverOtherNetworkMediumDescription', + { defaultMessage: 'Exfiltration Over Other Network Medium (T1011)' } + ), id: 'T1011', + name: 'Exfiltration Over Other Network Medium', reference: 'https://attack.mitre.org/techniques/T1011', tactics: ['exfiltration'], + value: 'exfiltrationOverOtherNetworkMedium', }, { - name: 'Exfiltration Over Physical Medium', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverPhysicalMediumDescription', + { defaultMessage: 'Exfiltration Over Physical Medium (T1052)' } + ), id: 'T1052', + name: 'Exfiltration Over Physical Medium', reference: 'https://attack.mitre.org/techniques/T1052', tactics: ['exfiltration'], + value: 'exfiltrationOverPhysicalMedium', }, { - name: 'Exfiltration Over Web Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverWebServiceDescription', + { defaultMessage: 'Exfiltration Over Web Service (T1567)' } + ), id: 'T1567', + name: 'Exfiltration Over Web Service', reference: 'https://attack.mitre.org/techniques/T1567', tactics: ['exfiltration'], + value: 'exfiltrationOverWebService', }, { - name: 'Exploit Public-Facing Application', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitPublicFacingApplicationDescription', + { defaultMessage: 'Exploit Public-Facing Application (T1190)' } + ), id: 'T1190', + name: 'Exploit Public-Facing Application', reference: 'https://attack.mitre.org/techniques/T1190', tactics: ['initial-access'], + value: 'exploitPublicFacingApplication', }, { - name: 'Exploitation for Client Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForClientExecutionDescription', + { defaultMessage: 'Exploitation for Client Execution (T1203)' } + ), id: 'T1203', + name: 'Exploitation for Client Execution', reference: 'https://attack.mitre.org/techniques/T1203', tactics: ['execution'], + value: 'exploitationForClientExecution', }, { - name: 'Exploitation for Credential Access', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForCredentialAccessDescription', + { defaultMessage: 'Exploitation for Credential Access (T1212)' } + ), id: 'T1212', + name: 'Exploitation for Credential Access', reference: 'https://attack.mitre.org/techniques/T1212', tactics: ['credential-access'], + value: 'exploitationForCredentialAccess', }, { - name: 'Exploitation for Defense Evasion', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForDefenseEvasionDescription', + { defaultMessage: 'Exploitation for Defense Evasion (T1211)' } + ), id: 'T1211', + name: 'Exploitation for Defense Evasion', reference: 'https://attack.mitre.org/techniques/T1211', tactics: ['defense-evasion'], + value: 'exploitationForDefenseEvasion', }, { - name: 'Exploitation for Privilege Escalation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForPrivilegeEscalationDescription', + { defaultMessage: 'Exploitation for Privilege Escalation (T1068)' } + ), id: 'T1068', + name: 'Exploitation for Privilege Escalation', reference: 'https://attack.mitre.org/techniques/T1068', tactics: ['privilege-escalation'], + value: 'exploitationForPrivilegeEscalation', }, { - name: 'Exploitation of Remote Services', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationOfRemoteServicesDescription', + { defaultMessage: 'Exploitation of Remote Services (T1210)' } + ), id: 'T1210', + name: 'Exploitation of Remote Services', reference: 'https://attack.mitre.org/techniques/T1210', tactics: ['lateral-movement'], + value: 'exploitationOfRemoteServices', }, { - name: 'External Remote Services', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.externalRemoteServicesDescription', + { defaultMessage: 'External Remote Services (T1133)' } + ), id: 'T1133', + name: 'External Remote Services', reference: 'https://attack.mitre.org/techniques/T1133', tactics: ['persistence', 'initial-access'], + value: 'externalRemoteServices', }, { - name: 'Extra Window Memory Injection', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.extraWindowMemoryInjectionDescription', + { defaultMessage: 'Extra Window Memory Injection (T1181)' } + ), id: 'T1181', + name: 'Extra Window Memory Injection', reference: 'https://attack.mitre.org/techniques/T1181', tactics: ['defense-evasion', 'privilege-escalation'], + value: 'extraWindowMemoryInjection', }, { - name: 'Fallback Channels', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fallbackChannelsDescription', + { defaultMessage: 'Fallback Channels (T1008)' } + ), id: 'T1008', + name: 'Fallback Channels', reference: 'https://attack.mitre.org/techniques/T1008', tactics: ['command-and-control'], + value: 'fallbackChannels', }, { - name: 'File Deletion', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileDeletionDescription', + { defaultMessage: 'File Deletion (T1107)' } + ), id: 'T1107', + name: 'File Deletion', reference: 'https://attack.mitre.org/techniques/T1107', tactics: ['defense-evasion'], + value: 'fileDeletion', }, { - name: 'File System Permissions Weakness', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileSystemPermissionsWeaknessDescription', + { defaultMessage: 'File System Permissions Weakness (T1044)' } + ), id: 'T1044', + name: 'File System Permissions Weakness', reference: 'https://attack.mitre.org/techniques/T1044', tactics: ['persistence', 'privilege-escalation'], + value: 'fileSystemPermissionsWeakness', }, { - name: 'File and Directory Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileAndDirectoryDiscoveryDescription', + { defaultMessage: 'File and Directory Discovery (T1083)' } + ), id: 'T1083', + name: 'File and Directory Discovery', reference: 'https://attack.mitre.org/techniques/T1083', tactics: ['discovery'], + value: 'fileAndDirectoryDiscovery', }, { - name: 'File and Directory Permissions Modification', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileAndDirectoryPermissionsModificationDescription', + { defaultMessage: 'File and Directory Permissions Modification (T1222)' } + ), id: 'T1222', + name: 'File and Directory Permissions Modification', reference: 'https://attack.mitre.org/techniques/T1222', tactics: ['defense-evasion'], + value: 'fileAndDirectoryPermissionsModification', }, { - name: 'Firmware Corruption', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.firmwareCorruptionDescription', + { defaultMessage: 'Firmware Corruption (T1495)' } + ), id: 'T1495', + name: 'Firmware Corruption', reference: 'https://attack.mitre.org/techniques/T1495', tactics: ['impact'], + value: 'firmwareCorruption', }, { - name: 'Forced Authentication', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.forcedAuthenticationDescription', + { defaultMessage: 'Forced Authentication (T1187)' } + ), id: 'T1187', + name: 'Forced Authentication', reference: 'https://attack.mitre.org/techniques/T1187', tactics: ['credential-access'], + value: 'forcedAuthentication', }, { - name: 'Forge Web Credentials', - id: 'T1606', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.forgeWebCredentialsDescription', + { defaultMessage: 'Forge Web Credentials (T1606)' } + ), + id: 'T1606', + name: 'Forge Web Credentials', reference: 'https://attack.mitre.org/techniques/T1606', tactics: ['credential-access'], + value: 'forgeWebCredentials', }, { - name: 'Gatekeeper Bypass', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatekeeperBypassDescription', + { defaultMessage: 'Gatekeeper Bypass (T1144)' } + ), id: 'T1144', + name: 'Gatekeeper Bypass', reference: 'https://attack.mitre.org/techniques/T1144', tactics: ['defense-evasion'], + value: 'gatekeeperBypass', }, { - name: 'Gather Victim Host Information', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimHostInformationDescription', + { defaultMessage: 'Gather Victim Host Information (T1592)' } + ), id: 'T1592', + name: 'Gather Victim Host Information', reference: 'https://attack.mitre.org/techniques/T1592', tactics: ['reconnaissance'], + value: 'gatherVictimHostInformation', }, { - name: 'Gather Victim Identity Information', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimIdentityInformationDescription', + { defaultMessage: 'Gather Victim Identity Information (T1589)' } + ), id: 'T1589', + name: 'Gather Victim Identity Information', reference: 'https://attack.mitre.org/techniques/T1589', tactics: ['reconnaissance'], + value: 'gatherVictimIdentityInformation', }, { - name: 'Gather Victim Network Information', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimNetworkInformationDescription', + { defaultMessage: 'Gather Victim Network Information (T1590)' } + ), id: 'T1590', + name: 'Gather Victim Network Information', reference: 'https://attack.mitre.org/techniques/T1590', tactics: ['reconnaissance'], + value: 'gatherVictimNetworkInformation', }, { - name: 'Gather Victim Org Information', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimOrgInformationDescription', + { defaultMessage: 'Gather Victim Org Information (T1591)' } + ), id: 'T1591', + name: 'Gather Victim Org Information', reference: 'https://attack.mitre.org/techniques/T1591', tactics: ['reconnaissance'], + value: 'gatherVictimOrgInformation', }, { - name: 'Graphical User Interface', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription', + { defaultMessage: 'Graphical User Interface (T1061)' } + ), id: 'T1061', + name: 'Graphical User Interface', reference: 'https://attack.mitre.org/techniques/T1061', tactics: ['execution'], + value: 'graphicalUserInterface', }, { - name: 'Group Policy Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.groupPolicyDiscoveryDescription', + { defaultMessage: 'Group Policy Discovery (T1615)' } + ), id: 'T1615', + name: 'Group Policy Discovery', reference: 'https://attack.mitre.org/techniques/T1615', tactics: ['discovery'], + value: 'groupPolicyDiscovery', }, { - name: 'HISTCONTROL', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.histcontrolDescription', + { defaultMessage: 'HISTCONTROL (T1148)' } + ), id: 'T1148', + name: 'HISTCONTROL', reference: 'https://attack.mitre.org/techniques/T1148', tactics: ['defense-evasion'], + value: 'histcontrol', }, { - name: 'Hardware Additions', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription', + { defaultMessage: 'Hardware Additions (T1200)' } + ), id: 'T1200', + name: 'Hardware Additions', reference: 'https://attack.mitre.org/techniques/T1200', tactics: ['initial-access'], + value: 'hardwareAdditions', }, { - name: 'Hidden Files and Directories', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenFilesAndDirectoriesDescription', + { defaultMessage: 'Hidden Files and Directories (T1158)' } + ), id: 'T1158', + name: 'Hidden Files and Directories', reference: 'https://attack.mitre.org/techniques/T1158', tactics: ['defense-evasion', 'persistence'], + value: 'hiddenFilesAndDirectories', }, { - name: 'Hidden Users', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenUsersDescription', + { defaultMessage: 'Hidden Users (T1147)' } + ), id: 'T1147', + name: 'Hidden Users', reference: 'https://attack.mitre.org/techniques/T1147', tactics: ['defense-evasion'], + value: 'hiddenUsers', }, { - name: 'Hidden Window', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenWindowDescription', + { defaultMessage: 'Hidden Window (T1143)' } + ), id: 'T1143', + name: 'Hidden Window', reference: 'https://attack.mitre.org/techniques/T1143', tactics: ['defense-evasion'], + value: 'hiddenWindow', }, { - name: 'Hide Artifacts', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hideArtifactsDescription', + { defaultMessage: 'Hide Artifacts (T1564)' } + ), id: 'T1564', + name: 'Hide Artifacts', reference: 'https://attack.mitre.org/techniques/T1564', tactics: ['defense-evasion'], + value: 'hideArtifacts', }, { - name: 'Hijack Execution Flow', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hijackExecutionFlowDescription', + { defaultMessage: 'Hijack Execution Flow (T1574)' } + ), id: 'T1574', + name: 'Hijack Execution Flow', reference: 'https://attack.mitre.org/techniques/T1574', tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], + value: 'hijackExecutionFlow', }, { - name: 'Hooking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hookingDescription', + { defaultMessage: 'Hooking (T1179)' } + ), id: 'T1179', + name: 'Hooking', reference: 'https://attack.mitre.org/techniques/T1179', tactics: ['persistence', 'privilege-escalation', 'credential-access'], + value: 'hooking', }, { - name: 'Hypervisor', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hypervisorDescription', + { defaultMessage: 'Hypervisor (T1062)' } + ), id: 'T1062', + name: 'Hypervisor', reference: 'https://attack.mitre.org/techniques/T1062', tactics: ['persistence'], + value: 'hypervisor', }, { - name: 'Image File Execution Options Injection', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.imageFileExecutionOptionsInjectionDescription', + { defaultMessage: 'Image File Execution Options Injection (T1183)' } + ), id: 'T1183', + name: 'Image File Execution Options Injection', reference: 'https://attack.mitre.org/techniques/T1183', tactics: ['privilege-escalation', 'persistence', 'defense-evasion'], + value: 'imageFileExecutionOptionsInjection', }, { - name: 'Impair Defenses', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.impairDefensesDescription', + { defaultMessage: 'Impair Defenses (T1562)' } + ), id: 'T1562', + name: 'Impair Defenses', reference: 'https://attack.mitre.org/techniques/T1562', tactics: ['defense-evasion'], + value: 'impairDefenses', }, { - name: 'Implant Internal Image', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.implantInternalImageDescription', + { defaultMessage: 'Implant Internal Image (T1525)' } + ), id: 'T1525', + name: 'Implant Internal Image', reference: 'https://attack.mitre.org/techniques/T1525', tactics: ['persistence'], + value: 'implantInternalImage', }, { - name: 'Indicator Blocking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorBlockingDescription', + { defaultMessage: 'Indicator Blocking (T1054)' } + ), id: 'T1054', + name: 'Indicator Blocking', reference: 'https://attack.mitre.org/techniques/T1054', tactics: ['defense-evasion'], + value: 'indicatorBlocking', }, { - name: 'Indicator Removal', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorRemovalDescription', + { defaultMessage: 'Indicator Removal (T1070)' } + ), id: 'T1070', + name: 'Indicator Removal', reference: 'https://attack.mitre.org/techniques/T1070', tactics: ['defense-evasion'], + value: 'indicatorRemoval', }, { - name: 'Indicator Removal from Tools', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorRemovalFromToolsDescription', + { defaultMessage: 'Indicator Removal from Tools (T1066)' } + ), id: 'T1066', + name: 'Indicator Removal from Tools', reference: 'https://attack.mitre.org/techniques/T1066', tactics: ['defense-evasion'], + value: 'indicatorRemovalFromTools', }, { - name: 'Indirect Command Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indirectCommandExecutionDescription', + { defaultMessage: 'Indirect Command Execution (T1202)' } + ), id: 'T1202', + name: 'Indirect Command Execution', reference: 'https://attack.mitre.org/techniques/T1202', tactics: ['defense-evasion'], + value: 'indirectCommandExecution', }, { - name: 'Ingress Tool Transfer', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.ingressToolTransferDescription', + { defaultMessage: 'Ingress Tool Transfer (T1105)' } + ), id: 'T1105', + name: 'Ingress Tool Transfer', reference: 'https://attack.mitre.org/techniques/T1105', tactics: ['command-and-control'], + value: 'ingressToolTransfer', }, { - name: 'Inhibit System Recovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inhibitSystemRecoveryDescription', + { defaultMessage: 'Inhibit System Recovery (T1490)' } + ), id: 'T1490', + name: 'Inhibit System Recovery', reference: 'https://attack.mitre.org/techniques/T1490', tactics: ['impact'], + value: 'inhibitSystemRecovery', }, { - name: 'Input Capture', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inputCaptureDescription', + { defaultMessage: 'Input Capture (T1056)' } + ), id: 'T1056', + name: 'Input Capture', reference: 'https://attack.mitre.org/techniques/T1056', tactics: ['collection', 'credential-access'], + value: 'inputCapture', }, { - name: 'Input Prompt', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inputPromptDescription', + { defaultMessage: 'Input Prompt (T1141)' } + ), id: 'T1141', + name: 'Input Prompt', reference: 'https://attack.mitre.org/techniques/T1141', tactics: ['credential-access'], + value: 'inputPrompt', }, { - name: 'Install Root Certificate', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.installRootCertificateDescription', + { defaultMessage: 'Install Root Certificate (T1130)' } + ), id: 'T1130', + name: 'Install Root Certificate', reference: 'https://attack.mitre.org/techniques/T1130', tactics: ['defense-evasion'], + value: 'installRootCertificate', }, { - name: 'InstallUtil', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.installUtilDescription', + { defaultMessage: 'InstallUtil (T1118)' } + ), id: 'T1118', + name: 'InstallUtil', reference: 'https://attack.mitre.org/techniques/T1118', tactics: ['defense-evasion', 'execution'], + value: 'installUtil', }, { - name: 'Inter-Process Communication', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.interProcessCommunicationDescription', + { defaultMessage: 'Inter-Process Communication (T1559)' } + ), id: 'T1559', + name: 'Inter-Process Communication', reference: 'https://attack.mitre.org/techniques/T1559', tactics: ['execution'], + value: 'interProcessCommunication', }, { - name: 'Internal Spearphishing', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.internalSpearphishingDescription', + { defaultMessage: 'Internal Spearphishing (T1534)' } + ), id: 'T1534', + name: 'Internal Spearphishing', reference: 'https://attack.mitre.org/techniques/T1534', tactics: ['lateral-movement'], + value: 'internalSpearphishing', }, { - name: 'Kerberoasting', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.kerberoastingDescription', + { defaultMessage: 'Kerberoasting (T1208)' } + ), id: 'T1208', + name: 'Kerberoasting', reference: 'https://attack.mitre.org/techniques/T1208', tactics: ['credential-access'], + value: 'kerberoasting', }, { - name: 'Kernel Modules and Extensions', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.kernelModulesAndExtensionsDescription', + { defaultMessage: 'Kernel Modules and Extensions (T1215)' } + ), id: 'T1215', + name: 'Kernel Modules and Extensions', reference: 'https://attack.mitre.org/techniques/T1215', tactics: ['persistence'], + value: 'kernelModulesAndExtensions', }, { - name: 'Keychain', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.keychainDescription', + { defaultMessage: 'Keychain (T1142)' } + ), id: 'T1142', + name: 'Keychain', reference: 'https://attack.mitre.org/techniques/T1142', tactics: ['credential-access'], + value: 'keychain', }, { - name: 'LC_LOAD_DYLIB Addition', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lcLoadDylibAdditionDescription', + { defaultMessage: 'LC_LOAD_DYLIB Addition (T1161)' } + ), id: 'T1161', + name: 'LC_LOAD_DYLIB Addition', reference: 'https://attack.mitre.org/techniques/T1161', tactics: ['persistence'], + value: 'lcLoadDylibAddition', }, { - name: 'LC_MAIN Hijacking', - id: 'T1149', - reference: 'https://attack.mitre.org/techniques/T1149', - tactics: ['defense-evasion'], + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lcMainHijackingDescription', + { defaultMessage: 'LC_MAIN Hijacking (T1149)' } + ), + id: 'T1149', + name: 'LC_MAIN Hijacking', + reference: 'https://attack.mitre.org/techniques/T1149', + tactics: ['defense-evasion'], + value: 'lcMainHijacking', }, { - name: 'LLMNR/NBT-NS Poisoning and Relay', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.llmnrNbtNsPoisoningAndRelayDescription', + { defaultMessage: 'LLMNR/NBT-NS Poisoning and Relay (T1171)' } + ), id: 'T1171', + name: 'LLMNR/NBT-NS Poisoning and Relay', reference: 'https://attack.mitre.org/techniques/T1171', tactics: ['credential-access'], + value: 'llmnrNbtNsPoisoningAndRelay', }, { - name: 'LSASS Driver', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lsassDriverDescription', + { defaultMessage: 'LSASS Driver (T1177)' } + ), id: 'T1177', + name: 'LSASS Driver', reference: 'https://attack.mitre.org/techniques/T1177', tactics: ['execution', 'persistence'], + value: 'lsassDriver', }, { - name: 'Lateral Tool Transfer', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lateralToolTransferDescription', + { defaultMessage: 'Lateral Tool Transfer (T1570)' } + ), id: 'T1570', + name: 'Lateral Tool Transfer', reference: 'https://attack.mitre.org/techniques/T1570', tactics: ['lateral-movement'], + value: 'lateralToolTransfer', }, { - name: 'Launch Agent', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchAgentDescription', + { defaultMessage: 'Launch Agent (T1159)' } + ), id: 'T1159', + name: 'Launch Agent', reference: 'https://attack.mitre.org/techniques/T1159', tactics: ['persistence'], + value: 'launchAgent', }, { - name: 'Launch Daemon', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchDaemonDescription', + { defaultMessage: 'Launch Daemon (T1160)' } + ), id: 'T1160', + name: 'Launch Daemon', reference: 'https://attack.mitre.org/techniques/T1160', tactics: ['persistence', 'privilege-escalation'], + value: 'launchDaemon', }, { - name: 'Launchctl', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchctlDescription', + { defaultMessage: 'Launchctl (T1152)' } + ), id: 'T1152', + name: 'Launchctl', reference: 'https://attack.mitre.org/techniques/T1152', tactics: ['defense-evasion', 'execution', 'persistence'], + value: 'launchctl', }, { - name: 'Local Job Scheduling', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.localJobSchedulingDescription', + { defaultMessage: 'Local Job Scheduling (T1168)' } + ), id: 'T1168', + name: 'Local Job Scheduling', reference: 'https://attack.mitre.org/techniques/T1168', tactics: ['persistence', 'execution'], + value: 'localJobScheduling', }, { - name: 'Login Item', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.loginItemDescription', + { defaultMessage: 'Login Item (T1162)' } + ), id: 'T1162', + name: 'Login Item', reference: 'https://attack.mitre.org/techniques/T1162', tactics: ['persistence'], + value: 'loginItem', }, { - name: 'Malicious Shell Modification', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.maliciousShellModificationDescription', + { defaultMessage: 'Malicious Shell Modification (T1156)' } + ), id: 'T1156', + name: 'Malicious Shell Modification', reference: 'https://attack.mitre.org/techniques/T1156', tactics: ['persistence'], + value: 'maliciousShellModification', }, { - name: 'Masquerading', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.masqueradingDescription', + { defaultMessage: 'Masquerading (T1036)' } + ), id: 'T1036', + name: 'Masquerading', reference: 'https://attack.mitre.org/techniques/T1036', tactics: ['defense-evasion'], + value: 'masquerading', }, { - name: 'Modify Authentication Process', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyAuthenticationProcessDescription', + { defaultMessage: 'Modify Authentication Process (T1556)' } + ), id: 'T1556', + name: 'Modify Authentication Process', reference: 'https://attack.mitre.org/techniques/T1556', tactics: ['credential-access', 'defense-evasion', 'persistence'], + value: 'modifyAuthenticationProcess', }, { - name: 'Modify Cloud Compute Infrastructure', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyCloudComputeInfrastructureDescription', + { defaultMessage: 'Modify Cloud Compute Infrastructure (T1578)' } + ), id: 'T1578', + name: 'Modify Cloud Compute Infrastructure', reference: 'https://attack.mitre.org/techniques/T1578', tactics: ['defense-evasion'], + value: 'modifyCloudComputeInfrastructure', }, { - name: 'Modify Existing Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyExistingServiceDescription', + { defaultMessage: 'Modify Existing Service (T1031)' } + ), id: 'T1031', + name: 'Modify Existing Service', reference: 'https://attack.mitre.org/techniques/T1031', tactics: ['persistence'], + value: 'modifyExistingService', }, { - name: 'Modify Registry', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyRegistryDescription', + { defaultMessage: 'Modify Registry (T1112)' } + ), id: 'T1112', + name: 'Modify Registry', reference: 'https://attack.mitre.org/techniques/T1112', tactics: ['defense-evasion'], + value: 'modifyRegistry', }, { - name: 'Modify System Image', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifySystemImageDescription', + { defaultMessage: 'Modify System Image (T1601)' } + ), id: 'T1601', + name: 'Modify System Image', reference: 'https://attack.mitre.org/techniques/T1601', tactics: ['defense-evasion'], + value: 'modifySystemImage', }, { - name: 'Mshta', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.mshtaDescription', + { defaultMessage: 'Mshta (T1170)' } + ), id: 'T1170', + name: 'Mshta', reference: 'https://attack.mitre.org/techniques/T1170', tactics: ['defense-evasion', 'execution'], + value: 'mshta', }, { - name: 'Multi-Factor Authentication Interception', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiFactorAuthenticationInterceptionDescription', + { defaultMessage: 'Multi-Factor Authentication Interception (T1111)' } + ), id: 'T1111', + name: 'Multi-Factor Authentication Interception', reference: 'https://attack.mitre.org/techniques/T1111', tactics: ['credential-access'], + value: 'multiFactorAuthenticationInterception', }, { - name: 'Multi-Factor Authentication Request Generation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiFactorAuthenticationRequestGenerationDescription', + { defaultMessage: 'Multi-Factor Authentication Request Generation (T1621)' } + ), id: 'T1621', + name: 'Multi-Factor Authentication Request Generation', reference: 'https://attack.mitre.org/techniques/T1621', tactics: ['credential-access'], + value: 'multiFactorAuthenticationRequestGeneration', }, { - name: 'Multi-Stage Channels', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiStageChannelsDescription', + { defaultMessage: 'Multi-Stage Channels (T1104)' } + ), id: 'T1104', + name: 'Multi-Stage Channels', reference: 'https://attack.mitre.org/techniques/T1104', tactics: ['command-and-control'], + value: 'multiStageChannels', }, { - name: 'Multi-hop Proxy', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiHopProxyDescription', + { defaultMessage: 'Multi-hop Proxy (T1188)' } + ), id: 'T1188', + name: 'Multi-hop Proxy', reference: 'https://attack.mitre.org/techniques/T1188', tactics: ['command-and-control'], + value: 'multiHopProxy', }, { - name: 'Multiband Communication', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multibandCommunicationDescription', + { defaultMessage: 'Multiband Communication (T1026)' } + ), id: 'T1026', + name: 'Multiband Communication', reference: 'https://attack.mitre.org/techniques/T1026', tactics: ['command-and-control'], + value: 'multibandCommunication', }, { - name: 'Multilayer Encryption', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multilayerEncryptionDescription', + { defaultMessage: 'Multilayer Encryption (T1079)' } + ), id: 'T1079', + name: 'Multilayer Encryption', reference: 'https://attack.mitre.org/techniques/T1079', tactics: ['command-and-control'], + value: 'multilayerEncryption', }, { - name: 'NTFS File Attributes', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.ntfsFileAttributesDescription', + { defaultMessage: 'NTFS File Attributes (T1096)' } + ), id: 'T1096', + name: 'NTFS File Attributes', reference: 'https://attack.mitre.org/techniques/T1096', tactics: ['defense-evasion'], + value: 'ntfsFileAttributes', }, { - name: 'Native API', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.nativeApiDescription', + { defaultMessage: 'Native API (T1106)' } + ), id: 'T1106', + name: 'Native API', reference: 'https://attack.mitre.org/techniques/T1106', tactics: ['execution'], + value: 'nativeApi', }, { - name: 'Netsh Helper DLL', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.netshHelperDllDescription', + { defaultMessage: 'Netsh Helper DLL (T1128)' } + ), id: 'T1128', + name: 'Netsh Helper DLL', reference: 'https://attack.mitre.org/techniques/T1128', tactics: ['persistence'], + value: 'netshHelperDll', }, { - name: 'Network Boundary Bridging', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkBoundaryBridgingDescription', + { defaultMessage: 'Network Boundary Bridging (T1599)' } + ), id: 'T1599', + name: 'Network Boundary Bridging', reference: 'https://attack.mitre.org/techniques/T1599', tactics: ['defense-evasion'], + value: 'networkBoundaryBridging', }, { - name: 'Network Denial of Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkDenialOfServiceDescription', + { defaultMessage: 'Network Denial of Service (T1498)' } + ), id: 'T1498', + name: 'Network Denial of Service', reference: 'https://attack.mitre.org/techniques/T1498', tactics: ['impact'], + value: 'networkDenialOfService', }, { - name: 'Network Service Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkServiceDiscoveryDescription', + { defaultMessage: 'Network Service Discovery (T1046)' } + ), id: 'T1046', + name: 'Network Service Discovery', reference: 'https://attack.mitre.org/techniques/T1046', tactics: ['discovery'], + value: 'networkServiceDiscovery', }, { - name: 'Network Share Connection Removal', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkShareConnectionRemovalDescription', + { defaultMessage: 'Network Share Connection Removal (T1126)' } + ), id: 'T1126', + name: 'Network Share Connection Removal', reference: 'https://attack.mitre.org/techniques/T1126', tactics: ['defense-evasion'], + value: 'networkShareConnectionRemoval', }, { - name: 'Network Share Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkShareDiscoveryDescription', + { defaultMessage: 'Network Share Discovery (T1135)' } + ), id: 'T1135', + name: 'Network Share Discovery', reference: 'https://attack.mitre.org/techniques/T1135', tactics: ['discovery'], + value: 'networkShareDiscovery', }, { - name: 'Network Sniffing', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkSniffingDescription', + { defaultMessage: 'Network Sniffing (T1040)' } + ), id: 'T1040', + name: 'Network Sniffing', reference: 'https://attack.mitre.org/techniques/T1040', tactics: ['credential-access', 'discovery'], + value: 'networkSniffing', }, { - name: 'New Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.newServiceDescription', + { defaultMessage: 'New Service (T1050)' } + ), id: 'T1050', + name: 'New Service', reference: 'https://attack.mitre.org/techniques/T1050', tactics: ['persistence', 'privilege-escalation'], + value: 'newService', }, { - name: 'Non-Application Layer Protocol', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.nonApplicationLayerProtocolDescription', + { defaultMessage: 'Non-Application Layer Protocol (T1095)' } + ), id: 'T1095', + name: 'Non-Application Layer Protocol', reference: 'https://attack.mitre.org/techniques/T1095', tactics: ['command-and-control'], + value: 'nonApplicationLayerProtocol', }, { - name: 'Non-Standard Port', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.nonStandardPortDescription', + { defaultMessage: 'Non-Standard Port (T1571)' } + ), id: 'T1571', + name: 'Non-Standard Port', reference: 'https://attack.mitre.org/techniques/T1571', tactics: ['command-and-control'], + value: 'nonStandardPort', }, { - name: 'OS Credential Dumping', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.osCredentialDumpingDescription', + { defaultMessage: 'OS Credential Dumping (T1003)' } + ), id: 'T1003', + name: 'OS Credential Dumping', reference: 'https://attack.mitre.org/techniques/T1003', tactics: ['credential-access'], + value: 'osCredentialDumping', }, { - name: 'Obfuscated Files or Information', - id: 'T1027', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.obfuscatedFilesOrInformationDescription', + { defaultMessage: 'Obfuscated Files or Information (T1027)' } + ), + id: 'T1027', + name: 'Obfuscated Files or Information', reference: 'https://attack.mitre.org/techniques/T1027', tactics: ['defense-evasion'], + value: 'obfuscatedFilesOrInformation', }, { - name: 'Obtain Capabilities', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.obtainCapabilitiesDescription', + { defaultMessage: 'Obtain Capabilities (T1588)' } + ), id: 'T1588', + name: 'Obtain Capabilities', reference: 'https://attack.mitre.org/techniques/T1588', tactics: ['resource-development'], + value: 'obtainCapabilities', }, { - name: 'Office Application Startup', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.officeApplicationStartupDescription', + { defaultMessage: 'Office Application Startup (T1137)' } + ), id: 'T1137', + name: 'Office Application Startup', reference: 'https://attack.mitre.org/techniques/T1137', tactics: ['persistence'], + value: 'officeApplicationStartup', }, { - name: 'Parent PID Spoofing', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.parentPidSpoofingDescription', + { defaultMessage: 'Parent PID Spoofing (T1502)' } + ), id: 'T1502', + name: 'Parent PID Spoofing', reference: 'https://attack.mitre.org/techniques/T1502', tactics: ['defense-evasion', 'privilege-escalation'], + value: 'parentPidSpoofing', }, { - name: 'Pass the Hash', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passTheHashDescription', + { defaultMessage: 'Pass the Hash (T1075)' } + ), id: 'T1075', + name: 'Pass the Hash', reference: 'https://attack.mitre.org/techniques/T1075', tactics: ['lateral-movement'], + value: 'passTheHash', }, { - name: 'Pass the Ticket', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passTheTicketDescription', + { defaultMessage: 'Pass the Ticket (T1097)' } + ), id: 'T1097', + name: 'Pass the Ticket', reference: 'https://attack.mitre.org/techniques/T1097', tactics: ['lateral-movement'], + value: 'passTheTicket', }, { - name: 'Password Filter DLL', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passwordFilterDllDescription', + { defaultMessage: 'Password Filter DLL (T1174)' } + ), id: 'T1174', + name: 'Password Filter DLL', reference: 'https://attack.mitre.org/techniques/T1174', tactics: ['credential-access'], + value: 'passwordFilterDll', }, { - name: 'Password Policy Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passwordPolicyDiscoveryDescription', + { defaultMessage: 'Password Policy Discovery (T1201)' } + ), id: 'T1201', + name: 'Password Policy Discovery', reference: 'https://attack.mitre.org/techniques/T1201', tactics: ['discovery'], + value: 'passwordPolicyDiscovery', }, { - name: 'Path Interception', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.pathInterceptionDescription', + { defaultMessage: 'Path Interception (T1034)' } + ), id: 'T1034', + name: 'Path Interception', reference: 'https://attack.mitre.org/techniques/T1034', tactics: ['persistence', 'privilege-escalation'], + value: 'pathInterception', }, { - name: 'Peripheral Device Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.peripheralDeviceDiscoveryDescription', + { defaultMessage: 'Peripheral Device Discovery (T1120)' } + ), id: 'T1120', + name: 'Peripheral Device Discovery', reference: 'https://attack.mitre.org/techniques/T1120', tactics: ['discovery'], + value: 'peripheralDeviceDiscovery', }, { - name: 'Permission Groups Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.permissionGroupsDiscoveryDescription', + { defaultMessage: 'Permission Groups Discovery (T1069)' } + ), id: 'T1069', + name: 'Permission Groups Discovery', reference: 'https://attack.mitre.org/techniques/T1069', tactics: ['discovery'], + value: 'permissionGroupsDiscovery', }, { - name: 'Phishing', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.phishingDescription', + { defaultMessage: 'Phishing (T1566)' } + ), id: 'T1566', + name: 'Phishing', reference: 'https://attack.mitre.org/techniques/T1566', tactics: ['initial-access'], + value: 'phishing', }, { - name: 'Phishing for Information', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.phishingForInformationDescription', + { defaultMessage: 'Phishing for Information (T1598)' } + ), id: 'T1598', + name: 'Phishing for Information', reference: 'https://attack.mitre.org/techniques/T1598', tactics: ['reconnaissance'], + value: 'phishingForInformation', }, { - name: 'Plist File Modification', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.plistFileModificationDescription', + { defaultMessage: 'Plist File Modification (T1647)' } + ), id: 'T1647', + name: 'Plist File Modification', reference: 'https://attack.mitre.org/techniques/T1647', tactics: ['defense-evasion'], + value: 'plistFileModification', }, { - name: 'Plist Modification', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.plistModificationDescription', + { defaultMessage: 'Plist Modification (T1150)' } + ), id: 'T1150', + name: 'Plist Modification', reference: 'https://attack.mitre.org/techniques/T1150', tactics: ['defense-evasion', 'persistence', 'privilege-escalation'], + value: 'plistModification', }, { - name: 'Port Monitors', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.portMonitorsDescription', + { defaultMessage: 'Port Monitors (T1013)' } + ), id: 'T1013', + name: 'Port Monitors', reference: 'https://attack.mitre.org/techniques/T1013', tactics: ['persistence', 'privilege-escalation'], + value: 'portMonitors', }, { - name: 'PowerShell', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.powerShellDescription', + { defaultMessage: 'PowerShell (T1086)' } + ), id: 'T1086', + name: 'PowerShell', reference: 'https://attack.mitre.org/techniques/T1086', tactics: ['execution'], + value: 'powerShell', }, { - name: 'PowerShell Profile', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.powerShellProfileDescription', + { defaultMessage: 'PowerShell Profile (T1504)' } + ), id: 'T1504', + name: 'PowerShell Profile', reference: 'https://attack.mitre.org/techniques/T1504', tactics: ['persistence', 'privilege-escalation'], + value: 'powerShellProfile', }, { - name: 'Pre-OS Boot', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.preOsBootDescription', + { defaultMessage: 'Pre-OS Boot (T1542)' } + ), id: 'T1542', + name: 'Pre-OS Boot', reference: 'https://attack.mitre.org/techniques/T1542', tactics: ['defense-evasion', 'persistence'], + value: 'preOsBoot', }, { - name: 'Private Keys', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.privateKeysDescription', + { defaultMessage: 'Private Keys (T1145)' } + ), id: 'T1145', + name: 'Private Keys', reference: 'https://attack.mitre.org/techniques/T1145', tactics: ['credential-access'], + value: 'privateKeys', }, { - name: 'Process Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processDiscoveryDescription', + { defaultMessage: 'Process Discovery (T1057)' } + ), id: 'T1057', + name: 'Process Discovery', reference: 'https://attack.mitre.org/techniques/T1057', tactics: ['discovery'], + value: 'processDiscovery', }, { - name: 'Process Doppelgänging', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processDoppelgangingDescription', + { defaultMessage: 'Process Doppelgänging (T1186)' } + ), id: 'T1186', + name: 'Process Doppelgänging', reference: 'https://attack.mitre.org/techniques/T1186', tactics: ['defense-evasion'], + value: 'processDoppelganging', }, { - name: 'Process Hollowing', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processHollowingDescription', + { defaultMessage: 'Process Hollowing (T1093)' } + ), id: 'T1093', + name: 'Process Hollowing', reference: 'https://attack.mitre.org/techniques/T1093', tactics: ['defense-evasion'], + value: 'processHollowing', }, { - name: 'Process Injection', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processInjectionDescription', + { defaultMessage: 'Process Injection (T1055)' } + ), id: 'T1055', + name: 'Process Injection', reference: 'https://attack.mitre.org/techniques/T1055', tactics: ['defense-evasion', 'privilege-escalation'], + value: 'processInjection', }, { - name: 'Protocol Tunneling', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.protocolTunnelingDescription', + { defaultMessage: 'Protocol Tunneling (T1572)' } + ), id: 'T1572', + name: 'Protocol Tunneling', reference: 'https://attack.mitre.org/techniques/T1572', tactics: ['command-and-control'], + value: 'protocolTunneling', }, { - name: 'Proxy', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.proxyDescription', + { defaultMessage: 'Proxy (T1090)' } + ), id: 'T1090', + name: 'Proxy', reference: 'https://attack.mitre.org/techniques/T1090', tactics: ['command-and-control'], + value: 'proxy', }, { - name: 'Query Registry', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.queryRegistryDescription', + { defaultMessage: 'Query Registry (T1012)' } + ), id: 'T1012', + name: 'Query Registry', reference: 'https://attack.mitre.org/techniques/T1012', tactics: ['discovery'], + value: 'queryRegistry', }, { - name: 'Rc.common', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rcCommonDescription', + { defaultMessage: 'Rc.common (T1163)' } + ), id: 'T1163', + name: 'Rc.common', reference: 'https://attack.mitre.org/techniques/T1163', tactics: ['persistence'], + value: 'rcCommon', }, { - name: 'Re-opened Applications', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.reOpenedApplicationsDescription', + { defaultMessage: 'Re-opened Applications (T1164)' } + ), id: 'T1164', + name: 'Re-opened Applications', reference: 'https://attack.mitre.org/techniques/T1164', tactics: ['persistence'], + value: 'reOpenedApplications', }, { - name: 'Redundant Access', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.redundantAccessDescription', + { defaultMessage: 'Redundant Access (T1108)' } + ), id: 'T1108', + name: 'Redundant Access', reference: 'https://attack.mitre.org/techniques/T1108', tactics: ['defense-evasion', 'persistence'], + value: 'redundantAccess', }, { - name: 'Reflective Code Loading', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.reflectiveCodeLoadingDescription', + { defaultMessage: 'Reflective Code Loading (T1620)' } + ), id: 'T1620', + name: 'Reflective Code Loading', reference: 'https://attack.mitre.org/techniques/T1620', tactics: ['defense-evasion'], + value: 'reflectiveCodeLoading', }, { - name: 'Registry Run Keys / Startup Folder', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.registryRunKeysStartupFolderDescription', + { defaultMessage: 'Registry Run Keys / Startup Folder (T1060)' } + ), id: 'T1060', + name: 'Registry Run Keys / Startup Folder', reference: 'https://attack.mitre.org/techniques/T1060', tactics: ['persistence'], + value: 'registryRunKeysStartupFolder', }, { - name: 'Regsvcs/Regasm', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.regsvcsRegasmDescription', + { defaultMessage: 'Regsvcs/Regasm (T1121)' } + ), id: 'T1121', + name: 'Regsvcs/Regasm', reference: 'https://attack.mitre.org/techniques/T1121', tactics: ['defense-evasion', 'execution'], + value: 'regsvcsRegasm', }, { - name: 'Regsvr32', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.regsvr32Description', + { defaultMessage: 'Regsvr32 (T1117)' } + ), id: 'T1117', + name: 'Regsvr32', reference: 'https://attack.mitre.org/techniques/T1117', tactics: ['defense-evasion', 'execution'], + value: 'regsvr32', }, { - name: 'Remote Access Software', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteAccessSoftwareDescription', + { defaultMessage: 'Remote Access Software (T1219)' } + ), id: 'T1219', + name: 'Remote Access Software', reference: 'https://attack.mitre.org/techniques/T1219', tactics: ['command-and-control'], + value: 'remoteAccessSoftware', }, { - name: 'Remote Desktop Protocol', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteDesktopProtocolDescription', + { defaultMessage: 'Remote Desktop Protocol (T1076)' } + ), id: 'T1076', + name: 'Remote Desktop Protocol', reference: 'https://attack.mitre.org/techniques/T1076', tactics: ['lateral-movement'], + value: 'remoteDesktopProtocol', }, { - name: 'Remote Service Session Hijacking', - id: 'T1563', - reference: 'https://attack.mitre.org/techniques/T1563', - tactics: ['lateral-movement'], + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteServiceSessionHijackingDescription', + { defaultMessage: 'Remote Service Session Hijacking (T1563)' } + ), + id: 'T1563', + name: 'Remote Service Session Hijacking', + reference: 'https://attack.mitre.org/techniques/T1563', + tactics: ['lateral-movement'], + value: 'remoteServiceSessionHijacking', }, { - name: 'Remote Services', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteServicesDescription', + { defaultMessage: 'Remote Services (T1021)' } + ), id: 'T1021', + name: 'Remote Services', reference: 'https://attack.mitre.org/techniques/T1021', tactics: ['lateral-movement'], + value: 'remoteServices', }, { - name: 'Remote System Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteSystemDiscoveryDescription', + { defaultMessage: 'Remote System Discovery (T1018)' } + ), id: 'T1018', + name: 'Remote System Discovery', reference: 'https://attack.mitre.org/techniques/T1018', tactics: ['discovery'], + value: 'remoteSystemDiscovery', }, { - name: 'Replication Through Removable Media', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.replicationThroughRemovableMediaDescription', + { defaultMessage: 'Replication Through Removable Media (T1091)' } + ), id: 'T1091', + name: 'Replication Through Removable Media', reference: 'https://attack.mitre.org/techniques/T1091', tactics: ['lateral-movement', 'initial-access'], + value: 'replicationThroughRemovableMedia', }, { - name: 'Resource Hijacking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.resourceHijackingDescription', + { defaultMessage: 'Resource Hijacking (T1496)' } + ), id: 'T1496', + name: 'Resource Hijacking', reference: 'https://attack.mitre.org/techniques/T1496', tactics: ['impact'], + value: 'resourceHijacking', }, { - name: 'Revert Cloud Instance', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.revertCloudInstanceDescription', + { defaultMessage: 'Revert Cloud Instance (T1536)' } + ), id: 'T1536', + name: 'Revert Cloud Instance', reference: 'https://attack.mitre.org/techniques/T1536', tactics: ['defense-evasion'], + value: 'revertCloudInstance', }, { - name: 'Rogue Domain Controller', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rogueDomainControllerDescription', + { defaultMessage: 'Rogue Domain Controller (T1207)' } + ), id: 'T1207', + name: 'Rogue Domain Controller', reference: 'https://attack.mitre.org/techniques/T1207', tactics: ['defense-evasion'], + value: 'rogueDomainController', }, { - name: 'Rootkit', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rootkitDescription', + { defaultMessage: 'Rootkit (T1014)' } + ), id: 'T1014', + name: 'Rootkit', reference: 'https://attack.mitre.org/techniques/T1014', tactics: ['defense-evasion'], + value: 'rootkit', }, { - name: 'Rundll32', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rundll32Description', + { defaultMessage: 'Rundll32 (T1085)' } + ), id: 'T1085', + name: 'Rundll32', reference: 'https://attack.mitre.org/techniques/T1085', tactics: ['defense-evasion', 'execution'], + value: 'rundll32', }, { - name: 'Runtime Data Manipulation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.runtimeDataManipulationDescription', + { defaultMessage: 'Runtime Data Manipulation (T1494)' } + ), id: 'T1494', + name: 'Runtime Data Manipulation', reference: 'https://attack.mitre.org/techniques/T1494', tactics: ['impact'], + value: 'runtimeDataManipulation', }, { - name: 'SID-History Injection', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sidHistoryInjectionDescription', + { defaultMessage: 'SID-History Injection (T1178)' } + ), id: 'T1178', + name: 'SID-History Injection', reference: 'https://attack.mitre.org/techniques/T1178', tactics: ['privilege-escalation'], + value: 'sidHistoryInjection', }, { - name: 'SIP and Trust Provider Hijacking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sipAndTrustProviderHijackingDescription', + { defaultMessage: 'SIP and Trust Provider Hijacking (T1198)' } + ), id: 'T1198', + name: 'SIP and Trust Provider Hijacking', reference: 'https://attack.mitre.org/techniques/T1198', tactics: ['defense-evasion', 'persistence'], + value: 'sipAndTrustProviderHijacking', }, { - name: 'SSH Hijacking', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sshHijackingDescription', + { defaultMessage: 'SSH Hijacking (T1184)' } + ), id: 'T1184', + name: 'SSH Hijacking', reference: 'https://attack.mitre.org/techniques/T1184', tactics: ['lateral-movement'], + value: 'sshHijacking', }, { - name: 'Scheduled Task/Job', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scheduledTaskJobDescription', + { defaultMessage: 'Scheduled Task/Job (T1053)' } + ), id: 'T1053', + name: 'Scheduled Task/Job', reference: 'https://attack.mitre.org/techniques/T1053', tactics: ['execution', 'persistence', 'privilege-escalation'], + value: 'scheduledTaskJob', }, { - name: 'Scheduled Transfer', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scheduledTransferDescription', + { defaultMessage: 'Scheduled Transfer (T1029)' } + ), id: 'T1029', + name: 'Scheduled Transfer', reference: 'https://attack.mitre.org/techniques/T1029', tactics: ['exfiltration'], + value: 'scheduledTransfer', }, { - name: 'Screen Capture', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.screenCaptureDescription', + { defaultMessage: 'Screen Capture (T1113)' } + ), id: 'T1113', + name: 'Screen Capture', reference: 'https://attack.mitre.org/techniques/T1113', tactics: ['collection'], + value: 'screenCapture', }, { - name: 'Screensaver', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.screensaverDescription', + { defaultMessage: 'Screensaver (T1180)' } + ), id: 'T1180', + name: 'Screensaver', reference: 'https://attack.mitre.org/techniques/T1180', tactics: ['persistence'], + value: 'screensaver', }, { - name: 'Scripting', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scriptingDescription', + { defaultMessage: 'Scripting (T1064)' } + ), id: 'T1064', + name: 'Scripting', reference: 'https://attack.mitre.org/techniques/T1064', tactics: ['defense-evasion', 'execution'], + value: 'scripting', }, { - name: 'Search Closed Sources', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchClosedSourcesDescription', + { defaultMessage: 'Search Closed Sources (T1597)' } + ), id: 'T1597', + name: 'Search Closed Sources', reference: 'https://attack.mitre.org/techniques/T1597', tactics: ['reconnaissance'], + value: 'searchClosedSources', }, { - name: 'Search Open Technical Databases', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchOpenTechnicalDatabasesDescription', + { defaultMessage: 'Search Open Technical Databases (T1596)' } + ), id: 'T1596', + name: 'Search Open Technical Databases', reference: 'https://attack.mitre.org/techniques/T1596', tactics: ['reconnaissance'], + value: 'searchOpenTechnicalDatabases', }, { - name: 'Search Open Websites/Domains', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchOpenWebsitesDomainsDescription', + { defaultMessage: 'Search Open Websites/Domains (T1593)' } + ), id: 'T1593', + name: 'Search Open Websites/Domains', reference: 'https://attack.mitre.org/techniques/T1593', tactics: ['reconnaissance'], + value: 'searchOpenWebsitesDomains', }, { - name: 'Search Victim-Owned Websites', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchVictimOwnedWebsitesDescription', + { defaultMessage: 'Search Victim-Owned Websites (T1594)' } + ), id: 'T1594', + name: 'Search Victim-Owned Websites', reference: 'https://attack.mitre.org/techniques/T1594', tactics: ['reconnaissance'], + value: 'searchVictimOwnedWebsites', }, { - name: 'Security Software Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitySoftwareDiscoveryDescription', + { defaultMessage: 'Security Software Discovery (T1063)' } + ), id: 'T1063', + name: 'Security Software Discovery', reference: 'https://attack.mitre.org/techniques/T1063', tactics: ['discovery'], + value: 'securitySoftwareDiscovery', }, { - name: 'Security Support Provider', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitySupportProviderDescription', + { defaultMessage: 'Security Support Provider (T1101)' } + ), id: 'T1101', + name: 'Security Support Provider', reference: 'https://attack.mitre.org/techniques/T1101', tactics: ['persistence'], + value: 'securitySupportProvider', }, { - name: 'Securityd Memory', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitydMemoryDescription', + { defaultMessage: 'Securityd Memory (T1167)' } + ), id: 'T1167', + name: 'Securityd Memory', reference: 'https://attack.mitre.org/techniques/T1167', tactics: ['credential-access'], + value: 'securitydMemory', }, { - name: 'Server Software Component', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serverSoftwareComponentDescription', + { defaultMessage: 'Server Software Component (T1505)' } + ), id: 'T1505', + name: 'Server Software Component', reference: 'https://attack.mitre.org/techniques/T1505', tactics: ['persistence'], + value: 'serverSoftwareComponent', }, { - name: 'Serverless Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serverlessExecutionDescription', + { defaultMessage: 'Serverless Execution (T1648)' } + ), id: 'T1648', + name: 'Serverless Execution', reference: 'https://attack.mitre.org/techniques/T1648', tactics: ['execution'], + value: 'serverlessExecution', }, { - name: 'Service Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceExecutionDescription', + { defaultMessage: 'Service Execution (T1035)' } + ), id: 'T1035', + name: 'Service Execution', reference: 'https://attack.mitre.org/techniques/T1035', tactics: ['execution'], + value: 'serviceExecution', }, { - name: 'Service Registry Permissions Weakness', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceRegistryPermissionsWeaknessDescription', + { defaultMessage: 'Service Registry Permissions Weakness (T1058)' } + ), id: 'T1058', + name: 'Service Registry Permissions Weakness', reference: 'https://attack.mitre.org/techniques/T1058', tactics: ['persistence', 'privilege-escalation'], + value: 'serviceRegistryPermissionsWeakness', }, { - name: 'Service Stop', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceStopDescription', + { defaultMessage: 'Service Stop (T1489)' } + ), id: 'T1489', + name: 'Service Stop', reference: 'https://attack.mitre.org/techniques/T1489', tactics: ['impact'], + value: 'serviceStop', }, { - name: 'Setuid and Setgid', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.setuidAndSetgidDescription', + { defaultMessage: 'Setuid and Setgid (T1166)' } + ), id: 'T1166', + name: 'Setuid and Setgid', reference: 'https://attack.mitre.org/techniques/T1166', tactics: ['privilege-escalation', 'persistence'], + value: 'setuidAndSetgid', }, { - name: 'Shared Modules', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sharedModulesDescription', + { defaultMessage: 'Shared Modules (T1129)' } + ), id: 'T1129', + name: 'Shared Modules', reference: 'https://attack.mitre.org/techniques/T1129', tactics: ['execution'], + value: 'sharedModules', }, { - name: 'Shared Webroot', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sharedWebrootDescription', + { defaultMessage: 'Shared Webroot (T1051)' } + ), id: 'T1051', + name: 'Shared Webroot', reference: 'https://attack.mitre.org/techniques/T1051', tactics: ['lateral-movement'], + value: 'sharedWebroot', }, { - name: 'Shortcut Modification', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.shortcutModificationDescription', + { defaultMessage: 'Shortcut Modification (T1023)' } + ), id: 'T1023', + name: 'Shortcut Modification', reference: 'https://attack.mitre.org/techniques/T1023', tactics: ['persistence'], + value: 'shortcutModification', }, { - name: 'Software Deployment Tools', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwareDeploymentToolsDescription', + { defaultMessage: 'Software Deployment Tools (T1072)' } + ), id: 'T1072', + name: 'Software Deployment Tools', reference: 'https://attack.mitre.org/techniques/T1072', tactics: ['execution', 'lateral-movement'], + value: 'softwareDeploymentTools', }, { - name: 'Software Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwareDiscoveryDescription', + { defaultMessage: 'Software Discovery (T1518)' } + ), id: 'T1518', + name: 'Software Discovery', reference: 'https://attack.mitre.org/techniques/T1518', tactics: ['discovery'], + value: 'softwareDiscovery', }, { - name: 'Software Packing', - id: 'T1045', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwarePackingDescription', + { defaultMessage: 'Software Packing (T1045)' } + ), + id: 'T1045', + name: 'Software Packing', reference: 'https://attack.mitre.org/techniques/T1045', tactics: ['defense-evasion'], + value: 'softwarePacking', }, { - name: 'Source', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sourceDescription', + { defaultMessage: 'Source (T1153)' } + ), id: 'T1153', + name: 'Source', reference: 'https://attack.mitre.org/techniques/T1153', tactics: ['execution'], + value: 'source', }, { - name: 'Space after Filename', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spaceAfterFilenameDescription', + { defaultMessage: 'Space after Filename (T1151)' } + ), id: 'T1151', + name: 'Space after Filename', reference: 'https://attack.mitre.org/techniques/T1151', tactics: ['defense-evasion', 'execution'], + value: 'spaceAfterFilename', }, { - name: 'Spearphishing Attachment', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingAttachmentDescription', + { defaultMessage: 'Spearphishing Attachment (T1193)' } + ), id: 'T1193', + name: 'Spearphishing Attachment', reference: 'https://attack.mitre.org/techniques/T1193', tactics: ['initial-access'], + value: 'spearphishingAttachment', }, { - name: 'Spearphishing Link', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingLinkDescription', + { defaultMessage: 'Spearphishing Link (T1192)' } + ), id: 'T1192', + name: 'Spearphishing Link', reference: 'https://attack.mitre.org/techniques/T1192', tactics: ['initial-access'], + value: 'spearphishingLink', }, { - name: 'Spearphishing via Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingViaServiceDescription', + { defaultMessage: 'Spearphishing via Service (T1194)' } + ), id: 'T1194', + name: 'Spearphishing via Service', reference: 'https://attack.mitre.org/techniques/T1194', tactics: ['initial-access'], + value: 'spearphishingViaService', }, { - name: 'Stage Capabilities', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stageCapabilitiesDescription', + { defaultMessage: 'Stage Capabilities (T1608)' } + ), id: 'T1608', + name: 'Stage Capabilities', reference: 'https://attack.mitre.org/techniques/T1608', tactics: ['resource-development'], + value: 'stageCapabilities', }, { - name: 'Standard Cryptographic Protocol', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.standardCryptographicProtocolDescription', + { defaultMessage: 'Standard Cryptographic Protocol (T1032)' } + ), id: 'T1032', + name: 'Standard Cryptographic Protocol', reference: 'https://attack.mitre.org/techniques/T1032', tactics: ['command-and-control'], + value: 'standardCryptographicProtocol', }, { - name: 'Startup Items', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.startupItemsDescription', + { defaultMessage: 'Startup Items (T1165)' } + ), id: 'T1165', + name: 'Startup Items', reference: 'https://attack.mitre.org/techniques/T1165', tactics: ['persistence', 'privilege-escalation'], + value: 'startupItems', }, { - name: 'Steal Application Access Token', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealApplicationAccessTokenDescription', + { defaultMessage: 'Steal Application Access Token (T1528)' } + ), id: 'T1528', + name: 'Steal Application Access Token', reference: 'https://attack.mitre.org/techniques/T1528', tactics: ['credential-access'], + value: 'stealApplicationAccessToken', }, { - name: 'Steal Web Session Cookie', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealWebSessionCookieDescription', + { defaultMessage: 'Steal Web Session Cookie (T1539)' } + ), id: 'T1539', + name: 'Steal Web Session Cookie', reference: 'https://attack.mitre.org/techniques/T1539', tactics: ['credential-access'], + value: 'stealWebSessionCookie', }, { - name: 'Steal or Forge Authentication Certificates', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealOrForgeAuthenticationCertificatesDescription', + { defaultMessage: 'Steal or Forge Authentication Certificates (T1649)' } + ), id: 'T1649', + name: 'Steal or Forge Authentication Certificates', reference: 'https://attack.mitre.org/techniques/T1649', tactics: ['credential-access'], + value: 'stealOrForgeAuthenticationCertificates', }, { - name: 'Steal or Forge Kerberos Tickets', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealOrForgeKerberosTicketsDescription', + { defaultMessage: 'Steal or Forge Kerberos Tickets (T1558)' } + ), id: 'T1558', + name: 'Steal or Forge Kerberos Tickets', reference: 'https://attack.mitre.org/techniques/T1558', tactics: ['credential-access'], + value: 'stealOrForgeKerberosTickets', }, { - name: 'Stored Data Manipulation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.storedDataManipulationDescription', + { defaultMessage: 'Stored Data Manipulation (T1492)' } + ), id: 'T1492', + name: 'Stored Data Manipulation', reference: 'https://attack.mitre.org/techniques/T1492', tactics: ['impact'], + value: 'storedDataManipulation', }, { - name: 'Subvert Trust Controls', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.subvertTrustControlsDescription', + { defaultMessage: 'Subvert Trust Controls (T1553)' } + ), id: 'T1553', + name: 'Subvert Trust Controls', reference: 'https://attack.mitre.org/techniques/T1553', tactics: ['defense-evasion'], + value: 'subvertTrustControls', }, { - name: 'Sudo', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sudoDescription', + { defaultMessage: 'Sudo (T1169)' } + ), id: 'T1169', + name: 'Sudo', reference: 'https://attack.mitre.org/techniques/T1169', tactics: ['privilege-escalation'], + value: 'sudo', }, { - name: 'Sudo Caching', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sudoCachingDescription', + { defaultMessage: 'Sudo Caching (T1206)' } + ), id: 'T1206', + name: 'Sudo Caching', reference: 'https://attack.mitre.org/techniques/T1206', tactics: ['privilege-escalation'], + value: 'sudoCaching', }, { - name: 'Supply Chain Compromise', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.supplyChainCompromiseDescription', + { defaultMessage: 'Supply Chain Compromise (T1195)' } + ), id: 'T1195', + name: 'Supply Chain Compromise', reference: 'https://attack.mitre.org/techniques/T1195', tactics: ['initial-access'], + value: 'supplyChainCompromise', }, { - name: 'System Binary Proxy Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemBinaryProxyExecutionDescription', + { defaultMessage: 'System Binary Proxy Execution (T1218)' } + ), id: 'T1218', + name: 'System Binary Proxy Execution', reference: 'https://attack.mitre.org/techniques/T1218', tactics: ['defense-evasion'], + value: 'systemBinaryProxyExecution', }, { - name: 'System Firmware', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemFirmwareDescription', + { defaultMessage: 'System Firmware (T1019)' } + ), id: 'T1019', + name: 'System Firmware', reference: 'https://attack.mitre.org/techniques/T1019', tactics: ['persistence'], + value: 'systemFirmware', }, { - name: 'System Information Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemInformationDiscoveryDescription', + { defaultMessage: 'System Information Discovery (T1082)' } + ), id: 'T1082', + name: 'System Information Discovery', reference: 'https://attack.mitre.org/techniques/T1082', tactics: ['discovery'], + value: 'systemInformationDiscovery', }, { - name: 'System Location Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemLocationDiscoveryDescription', + { defaultMessage: 'System Location Discovery (T1614)' } + ), id: 'T1614', + name: 'System Location Discovery', reference: 'https://attack.mitre.org/techniques/T1614', tactics: ['discovery'], + value: 'systemLocationDiscovery', }, { - name: 'System Network Configuration Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemNetworkConfigurationDiscoveryDescription', + { defaultMessage: 'System Network Configuration Discovery (T1016)' } + ), id: 'T1016', + name: 'System Network Configuration Discovery', reference: 'https://attack.mitre.org/techniques/T1016', tactics: ['discovery'], + value: 'systemNetworkConfigurationDiscovery', }, { - name: 'System Network Connections Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemNetworkConnectionsDiscoveryDescription', + { defaultMessage: 'System Network Connections Discovery (T1049)' } + ), id: 'T1049', + name: 'System Network Connections Discovery', reference: 'https://attack.mitre.org/techniques/T1049', tactics: ['discovery'], + value: 'systemNetworkConnectionsDiscovery', }, { - name: 'System Owner/User Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemOwnerUserDiscoveryDescription', + { defaultMessage: 'System Owner/User Discovery (T1033)' } + ), id: 'T1033', + name: 'System Owner/User Discovery', reference: 'https://attack.mitre.org/techniques/T1033', tactics: ['discovery'], + value: 'systemOwnerUserDiscovery', }, { - name: 'System Script Proxy Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemScriptProxyExecutionDescription', + { defaultMessage: 'System Script Proxy Execution (T1216)' } + ), id: 'T1216', + name: 'System Script Proxy Execution', reference: 'https://attack.mitre.org/techniques/T1216', tactics: ['defense-evasion'], + value: 'systemScriptProxyExecution', }, { - name: 'System Service Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemServiceDiscoveryDescription', + { defaultMessage: 'System Service Discovery (T1007)' } + ), id: 'T1007', + name: 'System Service Discovery', reference: 'https://attack.mitre.org/techniques/T1007', tactics: ['discovery'], + value: 'systemServiceDiscovery', }, { - name: 'System Services', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemServicesDescription', + { defaultMessage: 'System Services (T1569)' } + ), id: 'T1569', + name: 'System Services', reference: 'https://attack.mitre.org/techniques/T1569', tactics: ['execution'], + value: 'systemServices', }, { - name: 'System Shutdown/Reboot', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemShutdownRebootDescription', + { defaultMessage: 'System Shutdown/Reboot (T1529)' } + ), id: 'T1529', + name: 'System Shutdown/Reboot', reference: 'https://attack.mitre.org/techniques/T1529', tactics: ['impact'], + value: 'systemShutdownReboot', }, { - name: 'System Time Discovery', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemTimeDiscoveryDescription', + { defaultMessage: 'System Time Discovery (T1124)' } + ), id: 'T1124', + name: 'System Time Discovery', reference: 'https://attack.mitre.org/techniques/T1124', tactics: ['discovery'], + value: 'systemTimeDiscovery', }, { - name: 'Systemd Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemdServiceDescription', + { defaultMessage: 'Systemd Service (T1501)' } + ), id: 'T1501', + name: 'Systemd Service', reference: 'https://attack.mitre.org/techniques/T1501', tactics: ['persistence'], + value: 'systemdService', }, { - name: 'Taint Shared Content', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.taintSharedContentDescription', + { defaultMessage: 'Taint Shared Content (T1080)' } + ), id: 'T1080', + name: 'Taint Shared Content', reference: 'https://attack.mitre.org/techniques/T1080', tactics: ['lateral-movement'], + value: 'taintSharedContent', }, { - name: 'Template Injection', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.templateInjectionDescription', + { defaultMessage: 'Template Injection (T1221)' } + ), id: 'T1221', + name: 'Template Injection', reference: 'https://attack.mitre.org/techniques/T1221', tactics: ['defense-evasion'], + value: 'templateInjection', }, { - name: 'Time Providers', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.timeProvidersDescription', + { defaultMessage: 'Time Providers (T1209)' } + ), id: 'T1209', + name: 'Time Providers', reference: 'https://attack.mitre.org/techniques/T1209', tactics: ['persistence'], + value: 'timeProviders', }, { - name: 'Timestomp', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.timestompDescription', + { defaultMessage: 'Timestomp (T1099)' } + ), id: 'T1099', + name: 'Timestomp', reference: 'https://attack.mitre.org/techniques/T1099', tactics: ['defense-evasion'], + value: 'timestomp', }, { - name: 'Traffic Signaling', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trafficSignalingDescription', + { defaultMessage: 'Traffic Signaling (T1205)' } + ), id: 'T1205', + name: 'Traffic Signaling', reference: 'https://attack.mitre.org/techniques/T1205', tactics: ['defense-evasion', 'persistence', 'command-and-control'], + value: 'trafficSignaling', }, { - name: 'Transfer Data to Cloud Account', - id: 'T1537', - reference: 'https://attack.mitre.org/techniques/T1537', - tactics: ['exfiltration'], + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.transferDataToCloudAccountDescription', + { defaultMessage: 'Transfer Data to Cloud Account (T1537)' } + ), + id: 'T1537', + name: 'Transfer Data to Cloud Account', + reference: 'https://attack.mitre.org/techniques/T1537', + tactics: ['exfiltration'], + value: 'transferDataToCloudAccount', }, { - name: 'Transmitted Data Manipulation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.transmittedDataManipulationDescription', + { defaultMessage: 'Transmitted Data Manipulation (T1493)' } + ), id: 'T1493', + name: 'Transmitted Data Manipulation', reference: 'https://attack.mitre.org/techniques/T1493', tactics: ['impact'], + value: 'transmittedDataManipulation', }, { - name: 'Trap', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trapDescription', + { defaultMessage: 'Trap (T1154)' } + ), id: 'T1154', + name: 'Trap', reference: 'https://attack.mitre.org/techniques/T1154', tactics: ['execution', 'persistence'], + value: 'trap', }, { - name: 'Trusted Developer Utilities Proxy Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trustedDeveloperUtilitiesProxyExecutionDescription', + { defaultMessage: 'Trusted Developer Utilities Proxy Execution (T1127)' } + ), id: 'T1127', + name: 'Trusted Developer Utilities Proxy Execution', reference: 'https://attack.mitre.org/techniques/T1127', tactics: ['defense-evasion'], + value: 'trustedDeveloperUtilitiesProxyExecution', }, { - name: 'Trusted Relationship', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trustedRelationshipDescription', + { defaultMessage: 'Trusted Relationship (T1199)' } + ), id: 'T1199', + name: 'Trusted Relationship', reference: 'https://attack.mitre.org/techniques/T1199', tactics: ['initial-access'], + value: 'trustedRelationship', }, { - name: 'Uncommonly Used Port', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.uncommonlyUsedPortDescription', + { defaultMessage: 'Uncommonly Used Port (T1065)' } + ), id: 'T1065', + name: 'Uncommonly Used Port', reference: 'https://attack.mitre.org/techniques/T1065', tactics: ['command-and-control'], + value: 'uncommonlyUsedPort', }, { - name: 'Unsecured Credentials', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.unsecuredCredentialsDescription', + { defaultMessage: 'Unsecured Credentials (T1552)' } + ), id: 'T1552', + name: 'Unsecured Credentials', reference: 'https://attack.mitre.org/techniques/T1552', tactics: ['credential-access'], + value: 'unsecuredCredentials', }, { - name: 'Unused/Unsupported Cloud Regions', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.unusedUnsupportedCloudRegionsDescription', + { defaultMessage: 'Unused/Unsupported Cloud Regions (T1535)' } + ), id: 'T1535', + name: 'Unused/Unsupported Cloud Regions', reference: 'https://attack.mitre.org/techniques/T1535', tactics: ['defense-evasion'], + value: 'unusedUnsupportedCloudRegions', }, { - name: 'Use Alternate Authentication Material', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.useAlternateAuthenticationMaterialDescription', + { defaultMessage: 'Use Alternate Authentication Material (T1550)' } + ), id: 'T1550', + name: 'Use Alternate Authentication Material', reference: 'https://attack.mitre.org/techniques/T1550', tactics: ['defense-evasion', 'lateral-movement'], + value: 'useAlternateAuthenticationMaterial', }, { - name: 'User Execution', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.userExecutionDescription', + { defaultMessage: 'User Execution (T1204)' } + ), id: 'T1204', + name: 'User Execution', reference: 'https://attack.mitre.org/techniques/T1204', tactics: ['execution'], + value: 'userExecution', }, { - name: 'Valid Accounts', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.validAccountsDescription', + { defaultMessage: 'Valid Accounts (T1078)' } + ), id: 'T1078', + name: 'Valid Accounts', reference: 'https://attack.mitre.org/techniques/T1078', tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], + value: 'validAccounts', }, { - name: 'Video Capture', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.videoCaptureDescription', + { defaultMessage: 'Video Capture (T1125)' } + ), id: 'T1125', + name: 'Video Capture', reference: 'https://attack.mitre.org/techniques/T1125', tactics: ['collection'], + value: 'videoCapture', }, { - name: 'Virtualization/Sandbox Evasion', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.virtualizationSandboxEvasionDescription', + { defaultMessage: 'Virtualization/Sandbox Evasion (T1497)' } + ), id: 'T1497', + name: 'Virtualization/Sandbox Evasion', reference: 'https://attack.mitre.org/techniques/T1497', tactics: ['defense-evasion', 'discovery'], + value: 'virtualizationSandboxEvasion', }, { - name: 'Weaken Encryption', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.weakenEncryptionDescription', + { defaultMessage: 'Weaken Encryption (T1600)' } + ), id: 'T1600', + name: 'Weaken Encryption', reference: 'https://attack.mitre.org/techniques/T1600', tactics: ['defense-evasion'], + value: 'weakenEncryption', }, { - name: 'Web Service', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webServiceDescription', + { defaultMessage: 'Web Service (T1102)' } + ), id: 'T1102', + name: 'Web Service', reference: 'https://attack.mitre.org/techniques/T1102', tactics: ['command-and-control'], + value: 'webService', }, { - name: 'Web Session Cookie', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webSessionCookieDescription', + { defaultMessage: 'Web Session Cookie (T1506)' } + ), id: 'T1506', + name: 'Web Session Cookie', reference: 'https://attack.mitre.org/techniques/T1506', tactics: ['defense-evasion', 'lateral-movement'], + value: 'webSessionCookie', }, { - name: 'Web Shell', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webShellDescription', + { defaultMessage: 'Web Shell (T1100)' } + ), id: 'T1100', + name: 'Web Shell', reference: 'https://attack.mitre.org/techniques/T1100', tactics: ['persistence', 'privilege-escalation'], + value: 'webShell', }, { - name: 'Windows Admin Shares', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsAdminSharesDescription', + { defaultMessage: 'Windows Admin Shares (T1077)' } + ), id: 'T1077', + name: 'Windows Admin Shares', reference: 'https://attack.mitre.org/techniques/T1077', tactics: ['lateral-movement'], + value: 'windowsAdminShares', }, { - name: 'Windows Management Instrumentation', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationDescription', + { defaultMessage: 'Windows Management Instrumentation (T1047)' } + ), id: 'T1047', + name: 'Windows Management Instrumentation', reference: 'https://attack.mitre.org/techniques/T1047', tactics: ['execution'], - }, - { - name: 'Windows Management Instrumentation Event Subscription', - id: 'T1084', - reference: 'https://attack.mitre.org/techniques/T1084', - tactics: ['persistence'], - }, - { - name: 'Windows Remote Management', - id: 'T1028', - reference: 'https://attack.mitre.org/techniques/T1028', - tactics: ['execution', 'lateral-movement'], - }, - { - name: 'Winlogon Helper DLL', - id: 'T1004', - reference: 'https://attack.mitre.org/techniques/T1004', - tactics: ['persistence'], - }, - { - name: 'XSL Script Processing', - id: 'T1220', - reference: 'https://attack.mitre.org/techniques/T1220', - tactics: ['defense-evasion'], - }, -]; - -export const techniquesOptions: MitreTechniquesOptions[] = [ - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.abuseElevationControlMechanismDescription', - { defaultMessage: 'Abuse Elevation Control Mechanism (T1548)' } - ), - id: 'T1548', - name: 'Abuse Elevation Control Mechanism', - reference: 'https://attack.mitre.org/techniques/T1548', - tactics: 'privilege-escalation,defense-evasion', - value: 'abuseElevationControlMechanism', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accessTokenManipulationDescription', - { defaultMessage: 'Access Token Manipulation (T1134)' } - ), - id: 'T1134', - name: 'Access Token Manipulation', - reference: 'https://attack.mitre.org/techniques/T1134', - tactics: 'defense-evasion,privilege-escalation', - value: 'accessTokenManipulation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accessibilityFeaturesDescription', - { defaultMessage: 'Accessibility Features (T1015)' } - ), - id: 'T1015', - name: 'Accessibility Features', - reference: 'https://attack.mitre.org/techniques/T1015', - tactics: 'persistence,privilege-escalation', - value: 'accessibilityFeatures', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountAccessRemovalDescription', - { defaultMessage: 'Account Access Removal (T1531)' } - ), - id: 'T1531', - name: 'Account Access Removal', - reference: 'https://attack.mitre.org/techniques/T1531', - tactics: 'impact', - value: 'accountAccessRemoval', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountDiscoveryDescription', - { defaultMessage: 'Account Discovery (T1087)' } - ), - id: 'T1087', - name: 'Account Discovery', - reference: 'https://attack.mitre.org/techniques/T1087', - tactics: 'discovery', - value: 'accountDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.accountManipulationDescription', - { defaultMessage: 'Account Manipulation (T1098)' } - ), - id: 'T1098', - name: 'Account Manipulation', - reference: 'https://attack.mitre.org/techniques/T1098', - tactics: 'persistence', - value: 'accountManipulation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.acquireInfrastructureDescription', - { defaultMessage: 'Acquire Infrastructure (T1583)' } - ), - id: 'T1583', - name: 'Acquire Infrastructure', - reference: 'https://attack.mitre.org/techniques/T1583', - tactics: 'resource-development', - value: 'acquireInfrastructure', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.activeScanningDescription', - { defaultMessage: 'Active Scanning (T1595)' } - ), - id: 'T1595', - name: 'Active Scanning', - reference: 'https://attack.mitre.org/techniques/T1595', - tactics: 'reconnaissance', - value: 'activeScanning', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.adversaryInTheMiddleDescription', - { defaultMessage: 'Adversary-in-the-Middle (T1557)' } - ), - id: 'T1557', - name: 'Adversary-in-the-Middle', - reference: 'https://attack.mitre.org/techniques/T1557', - tactics: 'credential-access,collection', - value: 'adversaryInTheMiddle', + value: 'windowsManagementInstrumentation', }, { label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appCertDlLsDescription', - { defaultMessage: 'AppCert DLLs (T1182)' } - ), - id: 'T1182', - name: 'AppCert DLLs', - reference: 'https://attack.mitre.org/techniques/T1182', - tactics: 'persistence,privilege-escalation', - value: 'appCertDlLs', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appInitDlLsDescription', - { defaultMessage: 'AppInit DLLs (T1103)' } - ), - id: 'T1103', - name: 'AppInit DLLs', - reference: 'https://attack.mitre.org/techniques/T1103', - tactics: 'persistence,privilege-escalation', - value: 'appInitDlLs', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.appleScriptDescription', - { defaultMessage: 'AppleScript (T1155)' } - ), - id: 'T1155', - name: 'AppleScript', - reference: 'https://attack.mitre.org/techniques/T1155', - tactics: 'execution', - value: 'appleScript', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationAccessTokenDescription', - { defaultMessage: 'Application Access Token (T1527)' } - ), - id: 'T1527', - name: 'Application Access Token', - reference: 'https://attack.mitre.org/techniques/T1527', - tactics: 'defense-evasion,lateral-movement', - value: 'applicationAccessToken', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationDeploymentSoftwareDescription', - { defaultMessage: 'Application Deployment Software (T1017)' } - ), - id: 'T1017', - name: 'Application Deployment Software', - reference: 'https://attack.mitre.org/techniques/T1017', - tactics: 'lateral-movement', - value: 'applicationDeploymentSoftware', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationLayerProtocolDescription', - { defaultMessage: 'Application Layer Protocol (T1071)' } - ), - id: 'T1071', - name: 'Application Layer Protocol', - reference: 'https://attack.mitre.org/techniques/T1071', - tactics: 'command-and-control', - value: 'applicationLayerProtocol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationShimmingDescription', - { defaultMessage: 'Application Shimming (T1138)' } - ), - id: 'T1138', - name: 'Application Shimming', - reference: 'https://attack.mitre.org/techniques/T1138', - tactics: 'persistence,privilege-escalation', - value: 'applicationShimming', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.applicationWindowDiscoveryDescription', - { defaultMessage: 'Application Window Discovery (T1010)' } - ), - id: 'T1010', - name: 'Application Window Discovery', - reference: 'https://attack.mitre.org/techniques/T1010', - tactics: 'discovery', - value: 'applicationWindowDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.archiveCollectedDataDescription', - { defaultMessage: 'Archive Collected Data (T1560)' } - ), - id: 'T1560', - name: 'Archive Collected Data', - reference: 'https://attack.mitre.org/techniques/T1560', - tactics: 'collection', - value: 'archiveCollectedData', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.audioCaptureDescription', - { defaultMessage: 'Audio Capture (T1123)' } - ), - id: 'T1123', - name: 'Audio Capture', - reference: 'https://attack.mitre.org/techniques/T1123', - tactics: 'collection', - value: 'audioCapture', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.authenticationPackageDescription', - { defaultMessage: 'Authentication Package (T1131)' } - ), - id: 'T1131', - name: 'Authentication Package', - reference: 'https://attack.mitre.org/techniques/T1131', - tactics: 'persistence', - value: 'authenticationPackage', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.automatedCollectionDescription', - { defaultMessage: 'Automated Collection (T1119)' } - ), - id: 'T1119', - name: 'Automated Collection', - reference: 'https://attack.mitre.org/techniques/T1119', - tactics: 'collection', - value: 'automatedCollection', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.automatedExfiltrationDescription', - { defaultMessage: 'Automated Exfiltration (T1020)' } - ), - id: 'T1020', - name: 'Automated Exfiltration', - reference: 'https://attack.mitre.org/techniques/T1020', - tactics: 'exfiltration', - value: 'automatedExfiltration', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bitsJobsDescription', - { defaultMessage: 'BITS Jobs (T1197)' } - ), - id: 'T1197', - name: 'BITS Jobs', - reference: 'https://attack.mitre.org/techniques/T1197', - tactics: 'defense-evasion,persistence', - value: 'bitsJobs', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bashHistoryDescription', - { defaultMessage: 'Bash History (T1139)' } - ), - id: 'T1139', - name: 'Bash History', - reference: 'https://attack.mitre.org/techniques/T1139', - tactics: 'credential-access', - value: 'bashHistory', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.binaryPaddingDescription', - { defaultMessage: 'Binary Padding (T1009)' } - ), - id: 'T1009', - name: 'Binary Padding', - reference: 'https://attack.mitre.org/techniques/T1009', - tactics: 'defense-evasion', - value: 'binaryPadding', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonAutostartExecutionDescription', - { defaultMessage: 'Boot or Logon Autostart Execution (T1547)' } - ), - id: 'T1547', - name: 'Boot or Logon Autostart Execution', - reference: 'https://attack.mitre.org/techniques/T1547', - tactics: 'persistence,privilege-escalation', - value: 'bootOrLogonAutostartExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonInitializationScriptsDescription', - { defaultMessage: 'Boot or Logon Initialization Scripts (T1037)' } - ), - id: 'T1037', - name: 'Boot or Logon Initialization Scripts', - reference: 'https://attack.mitre.org/techniques/T1037', - tactics: 'persistence,privilege-escalation', - value: 'bootOrLogonInitializationScripts', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootkitDescription', - { defaultMessage: 'Bootkit (T1067)' } - ), - id: 'T1067', - name: 'Bootkit', - reference: 'https://attack.mitre.org/techniques/T1067', - tactics: 'persistence', - value: 'bootkit', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription', - { defaultMessage: 'Browser Bookmark Discovery (T1217)' } - ), - id: 'T1217', - name: 'Browser Bookmark Discovery', - reference: 'https://attack.mitre.org/techniques/T1217', - tactics: 'discovery', - value: 'browserBookmarkDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserExtensionsDescription', - { defaultMessage: 'Browser Extensions (T1176)' } - ), - id: 'T1176', - name: 'Browser Extensions', - reference: 'https://attack.mitre.org/techniques/T1176', - tactics: 'persistence', - value: 'browserExtensions', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserSessionHijackingDescription', - { defaultMessage: 'Browser Session Hijacking (T1185)' } - ), - id: 'T1185', - name: 'Browser Session Hijacking', - reference: 'https://attack.mitre.org/techniques/T1185', - tactics: 'collection', - value: 'browserSessionHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bruteForceDescription', - { defaultMessage: 'Brute Force (T1110)' } - ), - id: 'T1110', - name: 'Brute Force', - reference: 'https://attack.mitre.org/techniques/T1110', - tactics: 'credential-access', - value: 'bruteForce', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.buildImageOnHostDescription', - { defaultMessage: 'Build Image on Host (T1612)' } - ), - id: 'T1612', - name: 'Build Image on Host', - reference: 'https://attack.mitre.org/techniques/T1612', - tactics: 'defense-evasion', - value: 'buildImageOnHost', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bypassUserAccountControlDescription', - { defaultMessage: 'Bypass User Account Control (T1088)' } - ), - id: 'T1088', - name: 'Bypass User Account Control', - reference: 'https://attack.mitre.org/techniques/T1088', - tactics: 'defense-evasion,privilege-escalation', - value: 'bypassUserAccountControl', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cmstpDescription', - { defaultMessage: 'CMSTP (T1191)' } - ), - id: 'T1191', - name: 'CMSTP', - reference: 'https://attack.mitre.org/techniques/T1191', - tactics: 'defense-evasion,execution', - value: 'cmstp', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.changeDefaultFileAssociationDescription', - { defaultMessage: 'Change Default File Association (T1042)' } - ), - id: 'T1042', - name: 'Change Default File Association', - reference: 'https://attack.mitre.org/techniques/T1042', - tactics: 'persistence', - value: 'changeDefaultFileAssociation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.clearCommandHistoryDescription', - { defaultMessage: 'Clear Command History (T1146)' } - ), - id: 'T1146', - name: 'Clear Command History', - reference: 'https://attack.mitre.org/techniques/T1146', - tactics: 'defense-evasion', - value: 'clearCommandHistory', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.clipboardDataDescription', - { defaultMessage: 'Clipboard Data (T1115)' } - ), - id: 'T1115', - name: 'Clipboard Data', - reference: 'https://attack.mitre.org/techniques/T1115', - tactics: 'collection', - value: 'clipboardData', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudInfrastructureDiscoveryDescription', - { defaultMessage: 'Cloud Infrastructure Discovery (T1580)' } - ), - id: 'T1580', - name: 'Cloud Infrastructure Discovery', - reference: 'https://attack.mitre.org/techniques/T1580', - tactics: 'discovery', - value: 'cloudInfrastructureDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudInstanceMetadataApiDescription', - { defaultMessage: 'Cloud Instance Metadata API (T1522)' } - ), - id: 'T1522', - name: 'Cloud Instance Metadata API', - reference: 'https://attack.mitre.org/techniques/T1522', - tactics: 'credential-access', - value: 'cloudInstanceMetadataApi', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudServiceDashboardDescription', - { defaultMessage: 'Cloud Service Dashboard (T1538)' } - ), - id: 'T1538', - name: 'Cloud Service Dashboard', - reference: 'https://attack.mitre.org/techniques/T1538', - tactics: 'discovery', - value: 'cloudServiceDashboard', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudServiceDiscoveryDescription', - { defaultMessage: 'Cloud Service Discovery (T1526)' } - ), - id: 'T1526', - name: 'Cloud Service Discovery', - reference: 'https://attack.mitre.org/techniques/T1526', - tactics: 'discovery', - value: 'cloudServiceDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cloudStorageObjectDiscoveryDescription', - { defaultMessage: 'Cloud Storage Object Discovery (T1619)' } - ), - id: 'T1619', - name: 'Cloud Storage Object Discovery', - reference: 'https://attack.mitre.org/techniques/T1619', - tactics: 'discovery', - value: 'cloudStorageObjectDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.codeSigningDescription', - { defaultMessage: 'Code Signing (T1116)' } - ), - id: 'T1116', - name: 'Code Signing', - reference: 'https://attack.mitre.org/techniques/T1116', - tactics: 'defense-evasion', - value: 'codeSigning', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.commandAndScriptingInterpreterDescription', - { defaultMessage: 'Command and Scripting Interpreter (T1059)' } - ), - id: 'T1059', - name: 'Command and Scripting Interpreter', - reference: 'https://attack.mitre.org/techniques/T1059', - tactics: 'execution', - value: 'commandAndScriptingInterpreter', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.commonlyUsedPortDescription', - { defaultMessage: 'Commonly Used Port (T1043)' } - ), - id: 'T1043', - name: 'Commonly Used Port', - reference: 'https://attack.mitre.org/techniques/T1043', - tactics: 'command-and-control', - value: 'commonlyUsedPort', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.communicationThroughRemovableMediaDescription', - { defaultMessage: 'Communication Through Removable Media (T1092)' } - ), - id: 'T1092', - name: 'Communication Through Removable Media', - reference: 'https://attack.mitre.org/techniques/T1092', - tactics: 'command-and-control', - value: 'communicationThroughRemovableMedia', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compileAfterDeliveryDescription', - { defaultMessage: 'Compile After Delivery (T1500)' } - ), - id: 'T1500', - name: 'Compile After Delivery', - reference: 'https://attack.mitre.org/techniques/T1500', - tactics: 'defense-evasion', - value: 'compileAfterDelivery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compiledHtmlFileDescription', - { defaultMessage: 'Compiled HTML File (T1223)' } - ), - id: 'T1223', - name: 'Compiled HTML File', - reference: 'https://attack.mitre.org/techniques/T1223', - tactics: 'defense-evasion,execution', - value: 'compiledHtmlFile', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentFirmwareDescription', - { defaultMessage: 'Component Firmware (T1109)' } - ), - id: 'T1109', - name: 'Component Firmware', - reference: 'https://attack.mitre.org/techniques/T1109', - tactics: 'defense-evasion,persistence', - value: 'componentFirmware', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentObjectModelHijackingDescription', - { defaultMessage: 'Component Object Model Hijacking (T1122)' } - ), - id: 'T1122', - name: 'Component Object Model Hijacking', - reference: 'https://attack.mitre.org/techniques/T1122', - tactics: 'defense-evasion,persistence', - value: 'componentObjectModelHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.componentObjectModelAndDistributedComDescription', - { defaultMessage: 'Component Object Model and Distributed COM (T1175)' } - ), - id: 'T1175', - name: 'Component Object Model and Distributed COM', - reference: 'https://attack.mitre.org/techniques/T1175', - tactics: 'lateral-movement,execution', - value: 'componentObjectModelAndDistributedCom', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compromiseAccountsDescription', - { defaultMessage: 'Compromise Accounts (T1586)' } - ), - id: 'T1586', - name: 'Compromise Accounts', - reference: 'https://attack.mitre.org/techniques/T1586', - tactics: 'resource-development', - value: 'compromiseAccounts', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compromiseClientSoftwareBinaryDescription', - { defaultMessage: 'Compromise Client Software Binary (T1554)' } - ), - id: 'T1554', - name: 'Compromise Client Software Binary', - reference: 'https://attack.mitre.org/techniques/T1554', - tactics: 'persistence', - value: 'compromiseClientSoftwareBinary', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.compromiseInfrastructureDescription', - { defaultMessage: 'Compromise Infrastructure (T1584)' } - ), - id: 'T1584', - name: 'Compromise Infrastructure', - reference: 'https://attack.mitre.org/techniques/T1584', - tactics: 'resource-development', - value: 'compromiseInfrastructure', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.containerAdministrationCommandDescription', - { defaultMessage: 'Container Administration Command (T1609)' } - ), - id: 'T1609', - name: 'Container Administration Command', - reference: 'https://attack.mitre.org/techniques/T1609', - tactics: 'execution', - value: 'containerAdministrationCommand', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.containerAndResourceDiscoveryDescription', - { defaultMessage: 'Container and Resource Discovery (T1613)' } - ), - id: 'T1613', - name: 'Container and Resource Discovery', - reference: 'https://attack.mitre.org/techniques/T1613', - tactics: 'discovery', - value: 'containerAndResourceDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.controlPanelItemsDescription', - { defaultMessage: 'Control Panel Items (T1196)' } - ), - id: 'T1196', - name: 'Control Panel Items', - reference: 'https://attack.mitre.org/techniques/T1196', - tactics: 'defense-evasion,execution', - value: 'controlPanelItems', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.createAccountDescription', - { defaultMessage: 'Create Account (T1136)' } - ), - id: 'T1136', - name: 'Create Account', - reference: 'https://attack.mitre.org/techniques/T1136', - tactics: 'persistence', - value: 'createAccount', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.createOrModifySystemProcessDescription', - { defaultMessage: 'Create or Modify System Process (T1543)' } - ), - id: 'T1543', - name: 'Create or Modify System Process', - reference: 'https://attack.mitre.org/techniques/T1543', - tactics: 'persistence,privilege-escalation', - value: 'createOrModifySystemProcess', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsFromPasswordStoresDescription', - { defaultMessage: 'Credentials from Password Stores (T1555)' } - ), - id: 'T1555', - name: 'Credentials from Password Stores', - reference: 'https://attack.mitre.org/techniques/T1555', - tactics: 'credential-access', - value: 'credentialsFromPasswordStores', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsFromWebBrowsersDescription', - { defaultMessage: 'Credentials from Web Browsers (T1503)' } - ), - id: 'T1503', - name: 'Credentials from Web Browsers', - reference: 'https://attack.mitre.org/techniques/T1503', - tactics: 'credential-access', - value: 'credentialsFromWebBrowsers', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsInFilesDescription', - { defaultMessage: 'Credentials in Files (T1081)' } - ), - id: 'T1081', - name: 'Credentials in Files', - reference: 'https://attack.mitre.org/techniques/T1081', - tactics: 'credential-access', - value: 'credentialsInFiles', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.credentialsInRegistryDescription', - { defaultMessage: 'Credentials in Registry (T1214)' } - ), - id: 'T1214', - name: 'Credentials in Registry', - reference: 'https://attack.mitre.org/techniques/T1214', - tactics: 'credential-access', - value: 'credentialsInRegistry', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.customCommandAndControlProtocolDescription', - { defaultMessage: 'Custom Command and Control Protocol (T1094)' } - ), - id: 'T1094', - name: 'Custom Command and Control Protocol', - reference: 'https://attack.mitre.org/techniques/T1094', - tactics: 'command-and-control', - value: 'customCommandAndControlProtocol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.customCryptographicProtocolDescription', - { defaultMessage: 'Custom Cryptographic Protocol (T1024)' } - ), - id: 'T1024', - name: 'Custom Cryptographic Protocol', - reference: 'https://attack.mitre.org/techniques/T1024', - tactics: 'command-and-control', - value: 'customCryptographicProtocol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dllSearchOrderHijackingDescription', - { defaultMessage: 'DLL Search Order Hijacking (T1038)' } - ), - id: 'T1038', - name: 'DLL Search Order Hijacking', - reference: 'https://attack.mitre.org/techniques/T1038', - tactics: 'persistence,privilege-escalation,defense-evasion', - value: 'dllSearchOrderHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dllSideLoadingDescription', - { defaultMessage: 'DLL Side-Loading (T1073)' } - ), - id: 'T1073', - name: 'DLL Side-Loading', - reference: 'https://attack.mitre.org/techniques/T1073', - tactics: 'defense-evasion', - value: 'dllSideLoading', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataCompressedDescription', - { defaultMessage: 'Data Compressed (T1002)' } - ), - id: 'T1002', - name: 'Data Compressed', - reference: 'https://attack.mitre.org/techniques/T1002', - tactics: 'exfiltration', - value: 'dataCompressed', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataDestructionDescription', - { defaultMessage: 'Data Destruction (T1485)' } - ), - id: 'T1485', - name: 'Data Destruction', - reference: 'https://attack.mitre.org/techniques/T1485', - tactics: 'impact', - value: 'dataDestruction', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncodingDescription', - { defaultMessage: 'Data Encoding (T1132)' } - ), - id: 'T1132', - name: 'Data Encoding', - reference: 'https://attack.mitre.org/techniques/T1132', - tactics: 'command-and-control', - value: 'dataEncoding', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncryptedDescription', - { defaultMessage: 'Data Encrypted (T1022)' } - ), - id: 'T1022', - name: 'Data Encrypted', - reference: 'https://attack.mitre.org/techniques/T1022', - tactics: 'exfiltration', - value: 'dataEncrypted', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataEncryptedForImpactDescription', - { defaultMessage: 'Data Encrypted for Impact (T1486)' } - ), - id: 'T1486', - name: 'Data Encrypted for Impact', - reference: 'https://attack.mitre.org/techniques/T1486', - tactics: 'impact', - value: 'dataEncryptedForImpact', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataManipulationDescription', - { defaultMessage: 'Data Manipulation (T1565)' } - ), - id: 'T1565', - name: 'Data Manipulation', - reference: 'https://attack.mitre.org/techniques/T1565', - tactics: 'impact', - value: 'dataManipulation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataObfuscationDescription', - { defaultMessage: 'Data Obfuscation (T1001)' } - ), - id: 'T1001', - name: 'Data Obfuscation', - reference: 'https://attack.mitre.org/techniques/T1001', - tactics: 'command-and-control', - value: 'dataObfuscation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataStagedDescription', - { defaultMessage: 'Data Staged (T1074)' } - ), - id: 'T1074', - name: 'Data Staged', - reference: 'https://attack.mitre.org/techniques/T1074', - tactics: 'collection', - value: 'dataStaged', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataTransferSizeLimitsDescription', - { defaultMessage: 'Data Transfer Size Limits (T1030)' } - ), - id: 'T1030', - name: 'Data Transfer Size Limits', - reference: 'https://attack.mitre.org/techniques/T1030', - tactics: 'exfiltration', - value: 'dataTransferSizeLimits', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromCloudStorageDescription', - { defaultMessage: 'Data from Cloud Storage (T1530)' } - ), - id: 'T1530', - name: 'Data from Cloud Storage', - reference: 'https://attack.mitre.org/techniques/T1530', - tactics: 'collection', - value: 'dataFromCloudStorage', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromConfigurationRepositoryDescription', - { defaultMessage: 'Data from Configuration Repository (T1602)' } - ), - id: 'T1602', - name: 'Data from Configuration Repository', - reference: 'https://attack.mitre.org/techniques/T1602', - tactics: 'collection', - value: 'dataFromConfigurationRepository', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromInformationRepositoriesDescription', - { defaultMessage: 'Data from Information Repositories (T1213)' } - ), - id: 'T1213', - name: 'Data from Information Repositories', - reference: 'https://attack.mitre.org/techniques/T1213', - tactics: 'collection', - value: 'dataFromInformationRepositories', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromLocalSystemDescription', - { defaultMessage: 'Data from Local System (T1005)' } - ), - id: 'T1005', - name: 'Data from Local System', - reference: 'https://attack.mitre.org/techniques/T1005', - tactics: 'collection', - value: 'dataFromLocalSystem', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromNetworkSharedDriveDescription', - { defaultMessage: 'Data from Network Shared Drive (T1039)' } - ), - id: 'T1039', - name: 'Data from Network Shared Drive', - reference: 'https://attack.mitre.org/techniques/T1039', - tactics: 'collection', - value: 'dataFromNetworkSharedDrive', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dataFromRemovableMediaDescription', - { defaultMessage: 'Data from Removable Media (T1025)' } - ), - id: 'T1025', - name: 'Data from Removable Media', - reference: 'https://attack.mitre.org/techniques/T1025', - tactics: 'collection', - value: 'dataFromRemovableMedia', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.debuggerEvasionDescription', - { defaultMessage: 'Debugger Evasion (T1622)' } - ), - id: 'T1622', - name: 'Debugger Evasion', - reference: 'https://attack.mitre.org/techniques/T1622', - tactics: 'defense-evasion,discovery', - value: 'debuggerEvasion', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.defacementDescription', - { defaultMessage: 'Defacement (T1491)' } - ), - id: 'T1491', - name: 'Defacement', - reference: 'https://attack.mitre.org/techniques/T1491', - tactics: 'impact', - value: 'defacement', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.deobfuscateDecodeFilesOrInformationDescription', - { defaultMessage: 'Deobfuscate/Decode Files or Information (T1140)' } - ), - id: 'T1140', - name: 'Deobfuscate/Decode Files or Information', - reference: 'https://attack.mitre.org/techniques/T1140', - tactics: 'defense-evasion', - value: 'deobfuscateDecodeFilesOrInformation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.deployContainerDescription', - { defaultMessage: 'Deploy Container (T1610)' } - ), - id: 'T1610', - name: 'Deploy Container', - reference: 'https://attack.mitre.org/techniques/T1610', - tactics: 'defense-evasion,execution', - value: 'deployContainer', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.developCapabilitiesDescription', - { defaultMessage: 'Develop Capabilities (T1587)' } - ), - id: 'T1587', - name: 'Develop Capabilities', - reference: 'https://attack.mitre.org/techniques/T1587', - tactics: 'resource-development', - value: 'developCapabilities', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.directVolumeAccessDescription', - { defaultMessage: 'Direct Volume Access (T1006)' } - ), - id: 'T1006', - name: 'Direct Volume Access', - reference: 'https://attack.mitre.org/techniques/T1006', - tactics: 'defense-evasion', - value: 'directVolumeAccess', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.disablingSecurityToolsDescription', - { defaultMessage: 'Disabling Security Tools (T1089)' } - ), - id: 'T1089', - name: 'Disabling Security Tools', - reference: 'https://attack.mitre.org/techniques/T1089', - tactics: 'defense-evasion', - value: 'disablingSecurityTools', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskContentWipeDescription', - { defaultMessage: 'Disk Content Wipe (T1488)' } - ), - id: 'T1488', - name: 'Disk Content Wipe', - reference: 'https://attack.mitre.org/techniques/T1488', - tactics: 'impact', - value: 'diskContentWipe', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskStructureWipeDescription', - { defaultMessage: 'Disk Structure Wipe (T1487)' } - ), - id: 'T1487', - name: 'Disk Structure Wipe', - reference: 'https://attack.mitre.org/techniques/T1487', - tactics: 'impact', - value: 'diskStructureWipe', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.diskWipeDescription', - { defaultMessage: 'Disk Wipe (T1561)' } - ), - id: 'T1561', - name: 'Disk Wipe', - reference: 'https://attack.mitre.org/techniques/T1561', - tactics: 'impact', - value: 'diskWipe', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainFrontingDescription', - { defaultMessage: 'Domain Fronting (T1172)' } - ), - id: 'T1172', - name: 'Domain Fronting', - reference: 'https://attack.mitre.org/techniques/T1172', - tactics: 'command-and-control', - value: 'domainFronting', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainGenerationAlgorithmsDescription', - { defaultMessage: 'Domain Generation Algorithms (T1483)' } - ), - id: 'T1483', - name: 'Domain Generation Algorithms', - reference: 'https://attack.mitre.org/techniques/T1483', - tactics: 'command-and-control', - value: 'domainGenerationAlgorithms', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainPolicyModificationDescription', - { defaultMessage: 'Domain Policy Modification (T1484)' } - ), - id: 'T1484', - name: 'Domain Policy Modification', - reference: 'https://attack.mitre.org/techniques/T1484', - tactics: 'defense-evasion,privilege-escalation', - value: 'domainPolicyModification', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainTrustDiscoveryDescription', - { defaultMessage: 'Domain Trust Discovery (T1482)' } - ), - id: 'T1482', - name: 'Domain Trust Discovery', - reference: 'https://attack.mitre.org/techniques/T1482', - tactics: 'discovery', - value: 'domainTrustDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.driveByCompromiseDescription', - { defaultMessage: 'Drive-by Compromise (T1189)' } - ), - id: 'T1189', - name: 'Drive-by Compromise', - reference: 'https://attack.mitre.org/techniques/T1189', - tactics: 'initial-access', - value: 'driveByCompromise', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dylibHijackingDescription', - { defaultMessage: 'Dylib Hijacking (T1157)' } - ), - id: 'T1157', - name: 'Dylib Hijacking', - reference: 'https://attack.mitre.org/techniques/T1157', - tactics: 'persistence,privilege-escalation', - value: 'dylibHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dynamicDataExchangeDescription', - { defaultMessage: 'Dynamic Data Exchange (T1173)' } - ), - id: 'T1173', - name: 'Dynamic Data Exchange', - reference: 'https://attack.mitre.org/techniques/T1173', - tactics: 'execution', - value: 'dynamicDataExchange', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dynamicResolutionDescription', - { defaultMessage: 'Dynamic Resolution (T1568)' } - ), - id: 'T1568', - name: 'Dynamic Resolution', - reference: 'https://attack.mitre.org/techniques/T1568', - tactics: 'command-and-control', - value: 'dynamicResolution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.elevatedExecutionWithPromptDescription', - { defaultMessage: 'Elevated Execution with Prompt (T1514)' } - ), - id: 'T1514', - name: 'Elevated Execution with Prompt', - reference: 'https://attack.mitre.org/techniques/T1514', - tactics: 'privilege-escalation', - value: 'elevatedExecutionWithPrompt', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.emailCollectionDescription', - { defaultMessage: 'Email Collection (T1114)' } - ), - id: 'T1114', - name: 'Email Collection', - reference: 'https://attack.mitre.org/techniques/T1114', - tactics: 'collection', - value: 'emailCollection', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.emondDescription', - { defaultMessage: 'Emond (T1519)' } - ), - id: 'T1519', - name: 'Emond', - reference: 'https://attack.mitre.org/techniques/T1519', - tactics: 'persistence,privilege-escalation', - value: 'emond', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.encryptedChannelDescription', - { defaultMessage: 'Encrypted Channel (T1573)' } - ), - id: 'T1573', - name: 'Encrypted Channel', - reference: 'https://attack.mitre.org/techniques/T1573', - tactics: 'command-and-control', - value: 'encryptedChannel', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.endpointDenialOfServiceDescription', - { defaultMessage: 'Endpoint Denial of Service (T1499)' } - ), - id: 'T1499', - name: 'Endpoint Denial of Service', - reference: 'https://attack.mitre.org/techniques/T1499', - tactics: 'impact', - value: 'endpointDenialOfService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.escapeToHostDescription', - { defaultMessage: 'Escape to Host (T1611)' } - ), - id: 'T1611', - name: 'Escape to Host', - reference: 'https://attack.mitre.org/techniques/T1611', - tactics: 'privilege-escalation', - value: 'escapeToHost', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.establishAccountsDescription', - { defaultMessage: 'Establish Accounts (T1585)' } - ), - id: 'T1585', - name: 'Establish Accounts', - reference: 'https://attack.mitre.org/techniques/T1585', - tactics: 'resource-development', - value: 'establishAccounts', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.eventTriggeredExecutionDescription', - { defaultMessage: 'Event Triggered Execution (T1546)' } - ), - id: 'T1546', - name: 'Event Triggered Execution', - reference: 'https://attack.mitre.org/techniques/T1546', - tactics: 'privilege-escalation,persistence', - value: 'eventTriggeredExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.executionGuardrailsDescription', - { defaultMessage: 'Execution Guardrails (T1480)' } - ), - id: 'T1480', - name: 'Execution Guardrails', - reference: 'https://attack.mitre.org/techniques/T1480', - tactics: 'defense-evasion', - value: 'executionGuardrails', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverAlternativeProtocolDescription', - { defaultMessage: 'Exfiltration Over Alternative Protocol (T1048)' } - ), - id: 'T1048', - name: 'Exfiltration Over Alternative Protocol', - reference: 'https://attack.mitre.org/techniques/T1048', - tactics: 'exfiltration', - value: 'exfiltrationOverAlternativeProtocol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverC2ChannelDescription', - { defaultMessage: 'Exfiltration Over C2 Channel (T1041)' } - ), - id: 'T1041', - name: 'Exfiltration Over C2 Channel', - reference: 'https://attack.mitre.org/techniques/T1041', - tactics: 'exfiltration', - value: 'exfiltrationOverC2Channel', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverOtherNetworkMediumDescription', - { defaultMessage: 'Exfiltration Over Other Network Medium (T1011)' } - ), - id: 'T1011', - name: 'Exfiltration Over Other Network Medium', - reference: 'https://attack.mitre.org/techniques/T1011', - tactics: 'exfiltration', - value: 'exfiltrationOverOtherNetworkMedium', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverPhysicalMediumDescription', - { defaultMessage: 'Exfiltration Over Physical Medium (T1052)' } - ), - id: 'T1052', - name: 'Exfiltration Over Physical Medium', - reference: 'https://attack.mitre.org/techniques/T1052', - tactics: 'exfiltration', - value: 'exfiltrationOverPhysicalMedium', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exfiltrationOverWebServiceDescription', - { defaultMessage: 'Exfiltration Over Web Service (T1567)' } - ), - id: 'T1567', - name: 'Exfiltration Over Web Service', - reference: 'https://attack.mitre.org/techniques/T1567', - tactics: 'exfiltration', - value: 'exfiltrationOverWebService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitPublicFacingApplicationDescription', - { defaultMessage: 'Exploit Public-Facing Application (T1190)' } - ), - id: 'T1190', - name: 'Exploit Public-Facing Application', - reference: 'https://attack.mitre.org/techniques/T1190', - tactics: 'initial-access', - value: 'exploitPublicFacingApplication', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForClientExecutionDescription', - { defaultMessage: 'Exploitation for Client Execution (T1203)' } - ), - id: 'T1203', - name: 'Exploitation for Client Execution', - reference: 'https://attack.mitre.org/techniques/T1203', - tactics: 'execution', - value: 'exploitationForClientExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForCredentialAccessDescription', - { defaultMessage: 'Exploitation for Credential Access (T1212)' } - ), - id: 'T1212', - name: 'Exploitation for Credential Access', - reference: 'https://attack.mitre.org/techniques/T1212', - tactics: 'credential-access', - value: 'exploitationForCredentialAccess', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForDefenseEvasionDescription', - { defaultMessage: 'Exploitation for Defense Evasion (T1211)' } - ), - id: 'T1211', - name: 'Exploitation for Defense Evasion', - reference: 'https://attack.mitre.org/techniques/T1211', - tactics: 'defense-evasion', - value: 'exploitationForDefenseEvasion', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationForPrivilegeEscalationDescription', - { defaultMessage: 'Exploitation for Privilege Escalation (T1068)' } - ), - id: 'T1068', - name: 'Exploitation for Privilege Escalation', - reference: 'https://attack.mitre.org/techniques/T1068', - tactics: 'privilege-escalation', - value: 'exploitationForPrivilegeEscalation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.exploitationOfRemoteServicesDescription', - { defaultMessage: 'Exploitation of Remote Services (T1210)' } - ), - id: 'T1210', - name: 'Exploitation of Remote Services', - reference: 'https://attack.mitre.org/techniques/T1210', - tactics: 'lateral-movement', - value: 'exploitationOfRemoteServices', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.externalRemoteServicesDescription', - { defaultMessage: 'External Remote Services (T1133)' } - ), - id: 'T1133', - name: 'External Remote Services', - reference: 'https://attack.mitre.org/techniques/T1133', - tactics: 'persistence,initial-access', - value: 'externalRemoteServices', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.extraWindowMemoryInjectionDescription', - { defaultMessage: 'Extra Window Memory Injection (T1181)' } - ), - id: 'T1181', - name: 'Extra Window Memory Injection', - reference: 'https://attack.mitre.org/techniques/T1181', - tactics: 'defense-evasion,privilege-escalation', - value: 'extraWindowMemoryInjection', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fallbackChannelsDescription', - { defaultMessage: 'Fallback Channels (T1008)' } - ), - id: 'T1008', - name: 'Fallback Channels', - reference: 'https://attack.mitre.org/techniques/T1008', - tactics: 'command-and-control', - value: 'fallbackChannels', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileDeletionDescription', - { defaultMessage: 'File Deletion (T1107)' } - ), - id: 'T1107', - name: 'File Deletion', - reference: 'https://attack.mitre.org/techniques/T1107', - tactics: 'defense-evasion', - value: 'fileDeletion', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileSystemPermissionsWeaknessDescription', - { defaultMessage: 'File System Permissions Weakness (T1044)' } - ), - id: 'T1044', - name: 'File System Permissions Weakness', - reference: 'https://attack.mitre.org/techniques/T1044', - tactics: 'persistence,privilege-escalation', - value: 'fileSystemPermissionsWeakness', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileAndDirectoryDiscoveryDescription', - { defaultMessage: 'File and Directory Discovery (T1083)' } - ), - id: 'T1083', - name: 'File and Directory Discovery', - reference: 'https://attack.mitre.org/techniques/T1083', - tactics: 'discovery', - value: 'fileAndDirectoryDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.fileAndDirectoryPermissionsModificationDescription', - { defaultMessage: 'File and Directory Permissions Modification (T1222)' } - ), - id: 'T1222', - name: 'File and Directory Permissions Modification', - reference: 'https://attack.mitre.org/techniques/T1222', - tactics: 'defense-evasion', - value: 'fileAndDirectoryPermissionsModification', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.firmwareCorruptionDescription', - { defaultMessage: 'Firmware Corruption (T1495)' } - ), - id: 'T1495', - name: 'Firmware Corruption', - reference: 'https://attack.mitre.org/techniques/T1495', - tactics: 'impact', - value: 'firmwareCorruption', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.forcedAuthenticationDescription', - { defaultMessage: 'Forced Authentication (T1187)' } - ), - id: 'T1187', - name: 'Forced Authentication', - reference: 'https://attack.mitre.org/techniques/T1187', - tactics: 'credential-access', - value: 'forcedAuthentication', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.forgeWebCredentialsDescription', - { defaultMessage: 'Forge Web Credentials (T1606)' } - ), - id: 'T1606', - name: 'Forge Web Credentials', - reference: 'https://attack.mitre.org/techniques/T1606', - tactics: 'credential-access', - value: 'forgeWebCredentials', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatekeeperBypassDescription', - { defaultMessage: 'Gatekeeper Bypass (T1144)' } - ), - id: 'T1144', - name: 'Gatekeeper Bypass', - reference: 'https://attack.mitre.org/techniques/T1144', - tactics: 'defense-evasion', - value: 'gatekeeperBypass', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimHostInformationDescription', - { defaultMessage: 'Gather Victim Host Information (T1592)' } - ), - id: 'T1592', - name: 'Gather Victim Host Information', - reference: 'https://attack.mitre.org/techniques/T1592', - tactics: 'reconnaissance', - value: 'gatherVictimHostInformation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimIdentityInformationDescription', - { defaultMessage: 'Gather Victim Identity Information (T1589)' } - ), - id: 'T1589', - name: 'Gather Victim Identity Information', - reference: 'https://attack.mitre.org/techniques/T1589', - tactics: 'reconnaissance', - value: 'gatherVictimIdentityInformation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimNetworkInformationDescription', - { defaultMessage: 'Gather Victim Network Information (T1590)' } - ), - id: 'T1590', - name: 'Gather Victim Network Information', - reference: 'https://attack.mitre.org/techniques/T1590', - tactics: 'reconnaissance', - value: 'gatherVictimNetworkInformation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimOrgInformationDescription', - { defaultMessage: 'Gather Victim Org Information (T1591)' } - ), - id: 'T1591', - name: 'Gather Victim Org Information', - reference: 'https://attack.mitre.org/techniques/T1591', - tactics: 'reconnaissance', - value: 'gatherVictimOrgInformation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription', - { defaultMessage: 'Graphical User Interface (T1061)' } - ), - id: 'T1061', - name: 'Graphical User Interface', - reference: 'https://attack.mitre.org/techniques/T1061', - tactics: 'execution', - value: 'graphicalUserInterface', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.groupPolicyDiscoveryDescription', - { defaultMessage: 'Group Policy Discovery (T1615)' } - ), - id: 'T1615', - name: 'Group Policy Discovery', - reference: 'https://attack.mitre.org/techniques/T1615', - tactics: 'discovery', - value: 'groupPolicyDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.histcontrolDescription', - { defaultMessage: 'HISTCONTROL (T1148)' } - ), - id: 'T1148', - name: 'HISTCONTROL', - reference: 'https://attack.mitre.org/techniques/T1148', - tactics: 'defense-evasion', - value: 'histcontrol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription', - { defaultMessage: 'Hardware Additions (T1200)' } - ), - id: 'T1200', - name: 'Hardware Additions', - reference: 'https://attack.mitre.org/techniques/T1200', - tactics: 'initial-access', - value: 'hardwareAdditions', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenFilesAndDirectoriesDescription', - { defaultMessage: 'Hidden Files and Directories (T1158)' } - ), - id: 'T1158', - name: 'Hidden Files and Directories', - reference: 'https://attack.mitre.org/techniques/T1158', - tactics: 'defense-evasion,persistence', - value: 'hiddenFilesAndDirectories', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenUsersDescription', - { defaultMessage: 'Hidden Users (T1147)' } - ), - id: 'T1147', - name: 'Hidden Users', - reference: 'https://attack.mitre.org/techniques/T1147', - tactics: 'defense-evasion', - value: 'hiddenUsers', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hiddenWindowDescription', - { defaultMessage: 'Hidden Window (T1143)' } - ), - id: 'T1143', - name: 'Hidden Window', - reference: 'https://attack.mitre.org/techniques/T1143', - tactics: 'defense-evasion', - value: 'hiddenWindow', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hideArtifactsDescription', - { defaultMessage: 'Hide Artifacts (T1564)' } - ), - id: 'T1564', - name: 'Hide Artifacts', - reference: 'https://attack.mitre.org/techniques/T1564', - tactics: 'defense-evasion', - value: 'hideArtifacts', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hijackExecutionFlowDescription', - { defaultMessage: 'Hijack Execution Flow (T1574)' } - ), - id: 'T1574', - name: 'Hijack Execution Flow', - reference: 'https://attack.mitre.org/techniques/T1574', - tactics: 'persistence,privilege-escalation,defense-evasion', - value: 'hijackExecutionFlow', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hookingDescription', - { defaultMessage: 'Hooking (T1179)' } - ), - id: 'T1179', - name: 'Hooking', - reference: 'https://attack.mitre.org/techniques/T1179', - tactics: 'persistence,privilege-escalation,credential-access', - value: 'hooking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hypervisorDescription', - { defaultMessage: 'Hypervisor (T1062)' } - ), - id: 'T1062', - name: 'Hypervisor', - reference: 'https://attack.mitre.org/techniques/T1062', - tactics: 'persistence', - value: 'hypervisor', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.imageFileExecutionOptionsInjectionDescription', - { defaultMessage: 'Image File Execution Options Injection (T1183)' } - ), - id: 'T1183', - name: 'Image File Execution Options Injection', - reference: 'https://attack.mitre.org/techniques/T1183', - tactics: 'privilege-escalation,persistence,defense-evasion', - value: 'imageFileExecutionOptionsInjection', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.impairDefensesDescription', - { defaultMessage: 'Impair Defenses (T1562)' } - ), - id: 'T1562', - name: 'Impair Defenses', - reference: 'https://attack.mitre.org/techniques/T1562', - tactics: 'defense-evasion', - value: 'impairDefenses', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.implantInternalImageDescription', - { defaultMessage: 'Implant Internal Image (T1525)' } - ), - id: 'T1525', - name: 'Implant Internal Image', - reference: 'https://attack.mitre.org/techniques/T1525', - tactics: 'persistence', - value: 'implantInternalImage', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorBlockingDescription', - { defaultMessage: 'Indicator Blocking (T1054)' } - ), - id: 'T1054', - name: 'Indicator Blocking', - reference: 'https://attack.mitre.org/techniques/T1054', - tactics: 'defense-evasion', - value: 'indicatorBlocking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorRemovalDescription', - { defaultMessage: 'Indicator Removal (T1070)' } - ), - id: 'T1070', - name: 'Indicator Removal', - reference: 'https://attack.mitre.org/techniques/T1070', - tactics: 'defense-evasion', - value: 'indicatorRemoval', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indicatorRemovalFromToolsDescription', - { defaultMessage: 'Indicator Removal from Tools (T1066)' } - ), - id: 'T1066', - name: 'Indicator Removal from Tools', - reference: 'https://attack.mitre.org/techniques/T1066', - tactics: 'defense-evasion', - value: 'indicatorRemovalFromTools', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.indirectCommandExecutionDescription', - { defaultMessage: 'Indirect Command Execution (T1202)' } - ), - id: 'T1202', - name: 'Indirect Command Execution', - reference: 'https://attack.mitre.org/techniques/T1202', - tactics: 'defense-evasion', - value: 'indirectCommandExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.ingressToolTransferDescription', - { defaultMessage: 'Ingress Tool Transfer (T1105)' } - ), - id: 'T1105', - name: 'Ingress Tool Transfer', - reference: 'https://attack.mitre.org/techniques/T1105', - tactics: 'command-and-control', - value: 'ingressToolTransfer', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inhibitSystemRecoveryDescription', - { defaultMessage: 'Inhibit System Recovery (T1490)' } - ), - id: 'T1490', - name: 'Inhibit System Recovery', - reference: 'https://attack.mitre.org/techniques/T1490', - tactics: 'impact', - value: 'inhibitSystemRecovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inputCaptureDescription', - { defaultMessage: 'Input Capture (T1056)' } - ), - id: 'T1056', - name: 'Input Capture', - reference: 'https://attack.mitre.org/techniques/T1056', - tactics: 'collection,credential-access', - value: 'inputCapture', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.inputPromptDescription', - { defaultMessage: 'Input Prompt (T1141)' } - ), - id: 'T1141', - name: 'Input Prompt', - reference: 'https://attack.mitre.org/techniques/T1141', - tactics: 'credential-access', - value: 'inputPrompt', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.installRootCertificateDescription', - { defaultMessage: 'Install Root Certificate (T1130)' } - ), - id: 'T1130', - name: 'Install Root Certificate', - reference: 'https://attack.mitre.org/techniques/T1130', - tactics: 'defense-evasion', - value: 'installRootCertificate', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.installUtilDescription', - { defaultMessage: 'InstallUtil (T1118)' } - ), - id: 'T1118', - name: 'InstallUtil', - reference: 'https://attack.mitre.org/techniques/T1118', - tactics: 'defense-evasion,execution', - value: 'installUtil', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.interProcessCommunicationDescription', - { defaultMessage: 'Inter-Process Communication (T1559)' } - ), - id: 'T1559', - name: 'Inter-Process Communication', - reference: 'https://attack.mitre.org/techniques/T1559', - tactics: 'execution', - value: 'interProcessCommunication', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.internalSpearphishingDescription', - { defaultMessage: 'Internal Spearphishing (T1534)' } - ), - id: 'T1534', - name: 'Internal Spearphishing', - reference: 'https://attack.mitre.org/techniques/T1534', - tactics: 'lateral-movement', - value: 'internalSpearphishing', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.kerberoastingDescription', - { defaultMessage: 'Kerberoasting (T1208)' } - ), - id: 'T1208', - name: 'Kerberoasting', - reference: 'https://attack.mitre.org/techniques/T1208', - tactics: 'credential-access', - value: 'kerberoasting', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.kernelModulesAndExtensionsDescription', - { defaultMessage: 'Kernel Modules and Extensions (T1215)' } - ), - id: 'T1215', - name: 'Kernel Modules and Extensions', - reference: 'https://attack.mitre.org/techniques/T1215', - tactics: 'persistence', - value: 'kernelModulesAndExtensions', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.keychainDescription', - { defaultMessage: 'Keychain (T1142)' } - ), - id: 'T1142', - name: 'Keychain', - reference: 'https://attack.mitre.org/techniques/T1142', - tactics: 'credential-access', - value: 'keychain', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lcLoadDylibAdditionDescription', - { defaultMessage: 'LC_LOAD_DYLIB Addition (T1161)' } - ), - id: 'T1161', - name: 'LC_LOAD_DYLIB Addition', - reference: 'https://attack.mitre.org/techniques/T1161', - tactics: 'persistence', - value: 'lcLoadDylibAddition', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lcMainHijackingDescription', - { defaultMessage: 'LC_MAIN Hijacking (T1149)' } - ), - id: 'T1149', - name: 'LC_MAIN Hijacking', - reference: 'https://attack.mitre.org/techniques/T1149', - tactics: 'defense-evasion', - value: 'lcMainHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.llmnrNbtNsPoisoningAndRelayDescription', - { defaultMessage: 'LLMNR/NBT-NS Poisoning and Relay (T1171)' } - ), - id: 'T1171', - name: 'LLMNR/NBT-NS Poisoning and Relay', - reference: 'https://attack.mitre.org/techniques/T1171', - tactics: 'credential-access', - value: 'llmnrNbtNsPoisoningAndRelay', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lsassDriverDescription', - { defaultMessage: 'LSASS Driver (T1177)' } - ), - id: 'T1177', - name: 'LSASS Driver', - reference: 'https://attack.mitre.org/techniques/T1177', - tactics: 'execution,persistence', - value: 'lsassDriver', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.lateralToolTransferDescription', - { defaultMessage: 'Lateral Tool Transfer (T1570)' } - ), - id: 'T1570', - name: 'Lateral Tool Transfer', - reference: 'https://attack.mitre.org/techniques/T1570', - tactics: 'lateral-movement', - value: 'lateralToolTransfer', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchAgentDescription', - { defaultMessage: 'Launch Agent (T1159)' } - ), - id: 'T1159', - name: 'Launch Agent', - reference: 'https://attack.mitre.org/techniques/T1159', - tactics: 'persistence', - value: 'launchAgent', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchDaemonDescription', - { defaultMessage: 'Launch Daemon (T1160)' } - ), - id: 'T1160', - name: 'Launch Daemon', - reference: 'https://attack.mitre.org/techniques/T1160', - tactics: 'persistence,privilege-escalation', - value: 'launchDaemon', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchctlDescription', - { defaultMessage: 'Launchctl (T1152)' } - ), - id: 'T1152', - name: 'Launchctl', - reference: 'https://attack.mitre.org/techniques/T1152', - tactics: 'defense-evasion,execution,persistence', - value: 'launchctl', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.localJobSchedulingDescription', - { defaultMessage: 'Local Job Scheduling (T1168)' } - ), - id: 'T1168', - name: 'Local Job Scheduling', - reference: 'https://attack.mitre.org/techniques/T1168', - tactics: 'persistence,execution', - value: 'localJobScheduling', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.loginItemDescription', - { defaultMessage: 'Login Item (T1162)' } - ), - id: 'T1162', - name: 'Login Item', - reference: 'https://attack.mitre.org/techniques/T1162', - tactics: 'persistence', - value: 'loginItem', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.maliciousShellModificationDescription', - { defaultMessage: 'Malicious Shell Modification (T1156)' } - ), - id: 'T1156', - name: 'Malicious Shell Modification', - reference: 'https://attack.mitre.org/techniques/T1156', - tactics: 'persistence', - value: 'maliciousShellModification', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.masqueradingDescription', - { defaultMessage: 'Masquerading (T1036)' } - ), - id: 'T1036', - name: 'Masquerading', - reference: 'https://attack.mitre.org/techniques/T1036', - tactics: 'defense-evasion', - value: 'masquerading', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyAuthenticationProcessDescription', - { defaultMessage: 'Modify Authentication Process (T1556)' } - ), - id: 'T1556', - name: 'Modify Authentication Process', - reference: 'https://attack.mitre.org/techniques/T1556', - tactics: 'credential-access,defense-evasion,persistence', - value: 'modifyAuthenticationProcess', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyCloudComputeInfrastructureDescription', - { defaultMessage: 'Modify Cloud Compute Infrastructure (T1578)' } - ), - id: 'T1578', - name: 'Modify Cloud Compute Infrastructure', - reference: 'https://attack.mitre.org/techniques/T1578', - tactics: 'defense-evasion', - value: 'modifyCloudComputeInfrastructure', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyExistingServiceDescription', - { defaultMessage: 'Modify Existing Service (T1031)' } - ), - id: 'T1031', - name: 'Modify Existing Service', - reference: 'https://attack.mitre.org/techniques/T1031', - tactics: 'persistence', - value: 'modifyExistingService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifyRegistryDescription', - { defaultMessage: 'Modify Registry (T1112)' } - ), - id: 'T1112', - name: 'Modify Registry', - reference: 'https://attack.mitre.org/techniques/T1112', - tactics: 'defense-evasion', - value: 'modifyRegistry', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.modifySystemImageDescription', - { defaultMessage: 'Modify System Image (T1601)' } - ), - id: 'T1601', - name: 'Modify System Image', - reference: 'https://attack.mitre.org/techniques/T1601', - tactics: 'defense-evasion', - value: 'modifySystemImage', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.mshtaDescription', - { defaultMessage: 'Mshta (T1170)' } - ), - id: 'T1170', - name: 'Mshta', - reference: 'https://attack.mitre.org/techniques/T1170', - tactics: 'defense-evasion,execution', - value: 'mshta', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiFactorAuthenticationInterceptionDescription', - { defaultMessage: 'Multi-Factor Authentication Interception (T1111)' } - ), - id: 'T1111', - name: 'Multi-Factor Authentication Interception', - reference: 'https://attack.mitre.org/techniques/T1111', - tactics: 'credential-access', - value: 'multiFactorAuthenticationInterception', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiFactorAuthenticationRequestGenerationDescription', - { defaultMessage: 'Multi-Factor Authentication Request Generation (T1621)' } - ), - id: 'T1621', - name: 'Multi-Factor Authentication Request Generation', - reference: 'https://attack.mitre.org/techniques/T1621', - tactics: 'credential-access', - value: 'multiFactorAuthenticationRequestGeneration', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiStageChannelsDescription', - { defaultMessage: 'Multi-Stage Channels (T1104)' } - ), - id: 'T1104', - name: 'Multi-Stage Channels', - reference: 'https://attack.mitre.org/techniques/T1104', - tactics: 'command-and-control', - value: 'multiStageChannels', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multiHopProxyDescription', - { defaultMessage: 'Multi-hop Proxy (T1188)' } - ), - id: 'T1188', - name: 'Multi-hop Proxy', - reference: 'https://attack.mitre.org/techniques/T1188', - tactics: 'command-and-control', - value: 'multiHopProxy', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multibandCommunicationDescription', - { defaultMessage: 'Multiband Communication (T1026)' } - ), - id: 'T1026', - name: 'Multiband Communication', - reference: 'https://attack.mitre.org/techniques/T1026', - tactics: 'command-and-control', - value: 'multibandCommunication', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.multilayerEncryptionDescription', - { defaultMessage: 'Multilayer Encryption (T1079)' } - ), - id: 'T1079', - name: 'Multilayer Encryption', - reference: 'https://attack.mitre.org/techniques/T1079', - tactics: 'command-and-control', - value: 'multilayerEncryption', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.ntfsFileAttributesDescription', - { defaultMessage: 'NTFS File Attributes (T1096)' } - ), - id: 'T1096', - name: 'NTFS File Attributes', - reference: 'https://attack.mitre.org/techniques/T1096', - tactics: 'defense-evasion', - value: 'ntfsFileAttributes', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.nativeApiDescription', - { defaultMessage: 'Native API (T1106)' } - ), - id: 'T1106', - name: 'Native API', - reference: 'https://attack.mitre.org/techniques/T1106', - tactics: 'execution', - value: 'nativeApi', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.netshHelperDllDescription', - { defaultMessage: 'Netsh Helper DLL (T1128)' } - ), - id: 'T1128', - name: 'Netsh Helper DLL', - reference: 'https://attack.mitre.org/techniques/T1128', - tactics: 'persistence', - value: 'netshHelperDll', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkBoundaryBridgingDescription', - { defaultMessage: 'Network Boundary Bridging (T1599)' } - ), - id: 'T1599', - name: 'Network Boundary Bridging', - reference: 'https://attack.mitre.org/techniques/T1599', - tactics: 'defense-evasion', - value: 'networkBoundaryBridging', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkDenialOfServiceDescription', - { defaultMessage: 'Network Denial of Service (T1498)' } - ), - id: 'T1498', - name: 'Network Denial of Service', - reference: 'https://attack.mitre.org/techniques/T1498', - tactics: 'impact', - value: 'networkDenialOfService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkServiceDiscoveryDescription', - { defaultMessage: 'Network Service Discovery (T1046)' } - ), - id: 'T1046', - name: 'Network Service Discovery', - reference: 'https://attack.mitre.org/techniques/T1046', - tactics: 'discovery', - value: 'networkServiceDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkShareConnectionRemovalDescription', - { defaultMessage: 'Network Share Connection Removal (T1126)' } - ), - id: 'T1126', - name: 'Network Share Connection Removal', - reference: 'https://attack.mitre.org/techniques/T1126', - tactics: 'defense-evasion', - value: 'networkShareConnectionRemoval', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkShareDiscoveryDescription', - { defaultMessage: 'Network Share Discovery (T1135)' } - ), - id: 'T1135', - name: 'Network Share Discovery', - reference: 'https://attack.mitre.org/techniques/T1135', - tactics: 'discovery', - value: 'networkShareDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.networkSniffingDescription', - { defaultMessage: 'Network Sniffing (T1040)' } - ), - id: 'T1040', - name: 'Network Sniffing', - reference: 'https://attack.mitre.org/techniques/T1040', - tactics: 'credential-access,discovery', - value: 'networkSniffing', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.newServiceDescription', - { defaultMessage: 'New Service (T1050)' } - ), - id: 'T1050', - name: 'New Service', - reference: 'https://attack.mitre.org/techniques/T1050', - tactics: 'persistence,privilege-escalation', - value: 'newService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.nonApplicationLayerProtocolDescription', - { defaultMessage: 'Non-Application Layer Protocol (T1095)' } - ), - id: 'T1095', - name: 'Non-Application Layer Protocol', - reference: 'https://attack.mitre.org/techniques/T1095', - tactics: 'command-and-control', - value: 'nonApplicationLayerProtocol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.nonStandardPortDescription', - { defaultMessage: 'Non-Standard Port (T1571)' } - ), - id: 'T1571', - name: 'Non-Standard Port', - reference: 'https://attack.mitre.org/techniques/T1571', - tactics: 'command-and-control', - value: 'nonStandardPort', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.osCredentialDumpingDescription', - { defaultMessage: 'OS Credential Dumping (T1003)' } - ), - id: 'T1003', - name: 'OS Credential Dumping', - reference: 'https://attack.mitre.org/techniques/T1003', - tactics: 'credential-access', - value: 'osCredentialDumping', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.obfuscatedFilesOrInformationDescription', - { defaultMessage: 'Obfuscated Files or Information (T1027)' } - ), - id: 'T1027', - name: 'Obfuscated Files or Information', - reference: 'https://attack.mitre.org/techniques/T1027', - tactics: 'defense-evasion', - value: 'obfuscatedFilesOrInformation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.obtainCapabilitiesDescription', - { defaultMessage: 'Obtain Capabilities (T1588)' } - ), - id: 'T1588', - name: 'Obtain Capabilities', - reference: 'https://attack.mitre.org/techniques/T1588', - tactics: 'resource-development', - value: 'obtainCapabilities', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.officeApplicationStartupDescription', - { defaultMessage: 'Office Application Startup (T1137)' } - ), - id: 'T1137', - name: 'Office Application Startup', - reference: 'https://attack.mitre.org/techniques/T1137', - tactics: 'persistence', - value: 'officeApplicationStartup', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.parentPidSpoofingDescription', - { defaultMessage: 'Parent PID Spoofing (T1502)' } - ), - id: 'T1502', - name: 'Parent PID Spoofing', - reference: 'https://attack.mitre.org/techniques/T1502', - tactics: 'defense-evasion,privilege-escalation', - value: 'parentPidSpoofing', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passTheHashDescription', - { defaultMessage: 'Pass the Hash (T1075)' } - ), - id: 'T1075', - name: 'Pass the Hash', - reference: 'https://attack.mitre.org/techniques/T1075', - tactics: 'lateral-movement', - value: 'passTheHash', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passTheTicketDescription', - { defaultMessage: 'Pass the Ticket (T1097)' } - ), - id: 'T1097', - name: 'Pass the Ticket', - reference: 'https://attack.mitre.org/techniques/T1097', - tactics: 'lateral-movement', - value: 'passTheTicket', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passwordFilterDllDescription', - { defaultMessage: 'Password Filter DLL (T1174)' } - ), - id: 'T1174', - name: 'Password Filter DLL', - reference: 'https://attack.mitre.org/techniques/T1174', - tactics: 'credential-access', - value: 'passwordFilterDll', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.passwordPolicyDiscoveryDescription', - { defaultMessage: 'Password Policy Discovery (T1201)' } - ), - id: 'T1201', - name: 'Password Policy Discovery', - reference: 'https://attack.mitre.org/techniques/T1201', - tactics: 'discovery', - value: 'passwordPolicyDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.pathInterceptionDescription', - { defaultMessage: 'Path Interception (T1034)' } - ), - id: 'T1034', - name: 'Path Interception', - reference: 'https://attack.mitre.org/techniques/T1034', - tactics: 'persistence,privilege-escalation', - value: 'pathInterception', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.peripheralDeviceDiscoveryDescription', - { defaultMessage: 'Peripheral Device Discovery (T1120)' } - ), - id: 'T1120', - name: 'Peripheral Device Discovery', - reference: 'https://attack.mitre.org/techniques/T1120', - tactics: 'discovery', - value: 'peripheralDeviceDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.permissionGroupsDiscoveryDescription', - { defaultMessage: 'Permission Groups Discovery (T1069)' } - ), - id: 'T1069', - name: 'Permission Groups Discovery', - reference: 'https://attack.mitre.org/techniques/T1069', - tactics: 'discovery', - value: 'permissionGroupsDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.phishingDescription', - { defaultMessage: 'Phishing (T1566)' } - ), - id: 'T1566', - name: 'Phishing', - reference: 'https://attack.mitre.org/techniques/T1566', - tactics: 'initial-access', - value: 'phishing', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.phishingForInformationDescription', - { defaultMessage: 'Phishing for Information (T1598)' } - ), - id: 'T1598', - name: 'Phishing for Information', - reference: 'https://attack.mitre.org/techniques/T1598', - tactics: 'reconnaissance', - value: 'phishingForInformation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.plistFileModificationDescription', - { defaultMessage: 'Plist File Modification (T1647)' } - ), - id: 'T1647', - name: 'Plist File Modification', - reference: 'https://attack.mitre.org/techniques/T1647', - tactics: 'defense-evasion', - value: 'plistFileModification', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.plistModificationDescription', - { defaultMessage: 'Plist Modification (T1150)' } - ), - id: 'T1150', - name: 'Plist Modification', - reference: 'https://attack.mitre.org/techniques/T1150', - tactics: 'defense-evasion,persistence,privilege-escalation', - value: 'plistModification', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.portMonitorsDescription', - { defaultMessage: 'Port Monitors (T1013)' } - ), - id: 'T1013', - name: 'Port Monitors', - reference: 'https://attack.mitre.org/techniques/T1013', - tactics: 'persistence,privilege-escalation', - value: 'portMonitors', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.powerShellDescription', - { defaultMessage: 'PowerShell (T1086)' } - ), - id: 'T1086', - name: 'PowerShell', - reference: 'https://attack.mitre.org/techniques/T1086', - tactics: 'execution', - value: 'powerShell', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.powerShellProfileDescription', - { defaultMessage: 'PowerShell Profile (T1504)' } - ), - id: 'T1504', - name: 'PowerShell Profile', - reference: 'https://attack.mitre.org/techniques/T1504', - tactics: 'persistence,privilege-escalation', - value: 'powerShellProfile', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.preOsBootDescription', - { defaultMessage: 'Pre-OS Boot (T1542)' } - ), - id: 'T1542', - name: 'Pre-OS Boot', - reference: 'https://attack.mitre.org/techniques/T1542', - tactics: 'defense-evasion,persistence', - value: 'preOsBoot', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.privateKeysDescription', - { defaultMessage: 'Private Keys (T1145)' } - ), - id: 'T1145', - name: 'Private Keys', - reference: 'https://attack.mitre.org/techniques/T1145', - tactics: 'credential-access', - value: 'privateKeys', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processDiscoveryDescription', - { defaultMessage: 'Process Discovery (T1057)' } - ), - id: 'T1057', - name: 'Process Discovery', - reference: 'https://attack.mitre.org/techniques/T1057', - tactics: 'discovery', - value: 'processDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processDoppelgangingDescription', - { defaultMessage: 'Process Doppelgänging (T1186)' } - ), - id: 'T1186', - name: 'Process Doppelgänging', - reference: 'https://attack.mitre.org/techniques/T1186', - tactics: 'defense-evasion', - value: 'processDoppelganging', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processHollowingDescription', - { defaultMessage: 'Process Hollowing (T1093)' } - ), - id: 'T1093', - name: 'Process Hollowing', - reference: 'https://attack.mitre.org/techniques/T1093', - tactics: 'defense-evasion', - value: 'processHollowing', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.processInjectionDescription', - { defaultMessage: 'Process Injection (T1055)' } - ), - id: 'T1055', - name: 'Process Injection', - reference: 'https://attack.mitre.org/techniques/T1055', - tactics: 'defense-evasion,privilege-escalation', - value: 'processInjection', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.protocolTunnelingDescription', - { defaultMessage: 'Protocol Tunneling (T1572)' } - ), - id: 'T1572', - name: 'Protocol Tunneling', - reference: 'https://attack.mitre.org/techniques/T1572', - tactics: 'command-and-control', - value: 'protocolTunneling', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.proxyDescription', - { defaultMessage: 'Proxy (T1090)' } - ), - id: 'T1090', - name: 'Proxy', - reference: 'https://attack.mitre.org/techniques/T1090', - tactics: 'command-and-control', - value: 'proxy', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.queryRegistryDescription', - { defaultMessage: 'Query Registry (T1012)' } - ), - id: 'T1012', - name: 'Query Registry', - reference: 'https://attack.mitre.org/techniques/T1012', - tactics: 'discovery', - value: 'queryRegistry', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rcCommonDescription', - { defaultMessage: 'Rc.common (T1163)' } - ), - id: 'T1163', - name: 'Rc.common', - reference: 'https://attack.mitre.org/techniques/T1163', - tactics: 'persistence', - value: 'rcCommon', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.reOpenedApplicationsDescription', - { defaultMessage: 'Re-opened Applications (T1164)' } - ), - id: 'T1164', - name: 'Re-opened Applications', - reference: 'https://attack.mitre.org/techniques/T1164', - tactics: 'persistence', - value: 'reOpenedApplications', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.redundantAccessDescription', - { defaultMessage: 'Redundant Access (T1108)' } - ), - id: 'T1108', - name: 'Redundant Access', - reference: 'https://attack.mitre.org/techniques/T1108', - tactics: 'defense-evasion,persistence', - value: 'redundantAccess', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.reflectiveCodeLoadingDescription', - { defaultMessage: 'Reflective Code Loading (T1620)' } - ), - id: 'T1620', - name: 'Reflective Code Loading', - reference: 'https://attack.mitre.org/techniques/T1620', - tactics: 'defense-evasion', - value: 'reflectiveCodeLoading', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.registryRunKeysStartupFolderDescription', - { defaultMessage: 'Registry Run Keys / Startup Folder (T1060)' } - ), - id: 'T1060', - name: 'Registry Run Keys / Startup Folder', - reference: 'https://attack.mitre.org/techniques/T1060', - tactics: 'persistence', - value: 'registryRunKeysStartupFolder', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.regsvcsRegasmDescription', - { defaultMessage: 'Regsvcs/Regasm (T1121)' } - ), - id: 'T1121', - name: 'Regsvcs/Regasm', - reference: 'https://attack.mitre.org/techniques/T1121', - tactics: 'defense-evasion,execution', - value: 'regsvcsRegasm', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.regsvr32Description', - { defaultMessage: 'Regsvr32 (T1117)' } - ), - id: 'T1117', - name: 'Regsvr32', - reference: 'https://attack.mitre.org/techniques/T1117', - tactics: 'defense-evasion,execution', - value: 'regsvr32', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteAccessSoftwareDescription', - { defaultMessage: 'Remote Access Software (T1219)' } - ), - id: 'T1219', - name: 'Remote Access Software', - reference: 'https://attack.mitre.org/techniques/T1219', - tactics: 'command-and-control', - value: 'remoteAccessSoftware', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteDesktopProtocolDescription', - { defaultMessage: 'Remote Desktop Protocol (T1076)' } - ), - id: 'T1076', - name: 'Remote Desktop Protocol', - reference: 'https://attack.mitre.org/techniques/T1076', - tactics: 'lateral-movement', - value: 'remoteDesktopProtocol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteServiceSessionHijackingDescription', - { defaultMessage: 'Remote Service Session Hijacking (T1563)' } - ), - id: 'T1563', - name: 'Remote Service Session Hijacking', - reference: 'https://attack.mitre.org/techniques/T1563', - tactics: 'lateral-movement', - value: 'remoteServiceSessionHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteServicesDescription', - { defaultMessage: 'Remote Services (T1021)' } - ), - id: 'T1021', - name: 'Remote Services', - reference: 'https://attack.mitre.org/techniques/T1021', - tactics: 'lateral-movement', - value: 'remoteServices', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.remoteSystemDiscoveryDescription', - { defaultMessage: 'Remote System Discovery (T1018)' } - ), - id: 'T1018', - name: 'Remote System Discovery', - reference: 'https://attack.mitre.org/techniques/T1018', - tactics: 'discovery', - value: 'remoteSystemDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.replicationThroughRemovableMediaDescription', - { defaultMessage: 'Replication Through Removable Media (T1091)' } - ), - id: 'T1091', - name: 'Replication Through Removable Media', - reference: 'https://attack.mitre.org/techniques/T1091', - tactics: 'lateral-movement,initial-access', - value: 'replicationThroughRemovableMedia', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.resourceHijackingDescription', - { defaultMessage: 'Resource Hijacking (T1496)' } - ), - id: 'T1496', - name: 'Resource Hijacking', - reference: 'https://attack.mitre.org/techniques/T1496', - tactics: 'impact', - value: 'resourceHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.revertCloudInstanceDescription', - { defaultMessage: 'Revert Cloud Instance (T1536)' } - ), - id: 'T1536', - name: 'Revert Cloud Instance', - reference: 'https://attack.mitre.org/techniques/T1536', - tactics: 'defense-evasion', - value: 'revertCloudInstance', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rogueDomainControllerDescription', - { defaultMessage: 'Rogue Domain Controller (T1207)' } - ), - id: 'T1207', - name: 'Rogue Domain Controller', - reference: 'https://attack.mitre.org/techniques/T1207', - tactics: 'defense-evasion', - value: 'rogueDomainController', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rootkitDescription', - { defaultMessage: 'Rootkit (T1014)' } - ), - id: 'T1014', - name: 'Rootkit', - reference: 'https://attack.mitre.org/techniques/T1014', - tactics: 'defense-evasion', - value: 'rootkit', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rundll32Description', - { defaultMessage: 'Rundll32 (T1085)' } - ), - id: 'T1085', - name: 'Rundll32', - reference: 'https://attack.mitre.org/techniques/T1085', - tactics: 'defense-evasion,execution', - value: 'rundll32', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.runtimeDataManipulationDescription', - { defaultMessage: 'Runtime Data Manipulation (T1494)' } - ), - id: 'T1494', - name: 'Runtime Data Manipulation', - reference: 'https://attack.mitre.org/techniques/T1494', - tactics: 'impact', - value: 'runtimeDataManipulation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sidHistoryInjectionDescription', - { defaultMessage: 'SID-History Injection (T1178)' } - ), - id: 'T1178', - name: 'SID-History Injection', - reference: 'https://attack.mitre.org/techniques/T1178', - tactics: 'privilege-escalation', - value: 'sidHistoryInjection', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sipAndTrustProviderHijackingDescription', - { defaultMessage: 'SIP and Trust Provider Hijacking (T1198)' } - ), - id: 'T1198', - name: 'SIP and Trust Provider Hijacking', - reference: 'https://attack.mitre.org/techniques/T1198', - tactics: 'defense-evasion,persistence', - value: 'sipAndTrustProviderHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sshHijackingDescription', - { defaultMessage: 'SSH Hijacking (T1184)' } - ), - id: 'T1184', - name: 'SSH Hijacking', - reference: 'https://attack.mitre.org/techniques/T1184', - tactics: 'lateral-movement', - value: 'sshHijacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scheduledTaskJobDescription', - { defaultMessage: 'Scheduled Task/Job (T1053)' } - ), - id: 'T1053', - name: 'Scheduled Task/Job', - reference: 'https://attack.mitre.org/techniques/T1053', - tactics: 'execution,persistence,privilege-escalation', - value: 'scheduledTaskJob', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scheduledTransferDescription', - { defaultMessage: 'Scheduled Transfer (T1029)' } - ), - id: 'T1029', - name: 'Scheduled Transfer', - reference: 'https://attack.mitre.org/techniques/T1029', - tactics: 'exfiltration', - value: 'scheduledTransfer', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.screenCaptureDescription', - { defaultMessage: 'Screen Capture (T1113)' } - ), - id: 'T1113', - name: 'Screen Capture', - reference: 'https://attack.mitre.org/techniques/T1113', - tactics: 'collection', - value: 'screenCapture', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.screensaverDescription', - { defaultMessage: 'Screensaver (T1180)' } - ), - id: 'T1180', - name: 'Screensaver', - reference: 'https://attack.mitre.org/techniques/T1180', - tactics: 'persistence', - value: 'screensaver', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scriptingDescription', - { defaultMessage: 'Scripting (T1064)' } - ), - id: 'T1064', - name: 'Scripting', - reference: 'https://attack.mitre.org/techniques/T1064', - tactics: 'defense-evasion,execution', - value: 'scripting', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchClosedSourcesDescription', - { defaultMessage: 'Search Closed Sources (T1597)' } - ), - id: 'T1597', - name: 'Search Closed Sources', - reference: 'https://attack.mitre.org/techniques/T1597', - tactics: 'reconnaissance', - value: 'searchClosedSources', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchOpenTechnicalDatabasesDescription', - { defaultMessage: 'Search Open Technical Databases (T1596)' } - ), - id: 'T1596', - name: 'Search Open Technical Databases', - reference: 'https://attack.mitre.org/techniques/T1596', - tactics: 'reconnaissance', - value: 'searchOpenTechnicalDatabases', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchOpenWebsitesDomainsDescription', - { defaultMessage: 'Search Open Websites/Domains (T1593)' } - ), - id: 'T1593', - name: 'Search Open Websites/Domains', - reference: 'https://attack.mitre.org/techniques/T1593', - tactics: 'reconnaissance', - value: 'searchOpenWebsitesDomains', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.searchVictimOwnedWebsitesDescription', - { defaultMessage: 'Search Victim-Owned Websites (T1594)' } - ), - id: 'T1594', - name: 'Search Victim-Owned Websites', - reference: 'https://attack.mitre.org/techniques/T1594', - tactics: 'reconnaissance', - value: 'searchVictimOwnedWebsites', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitySoftwareDiscoveryDescription', - { defaultMessage: 'Security Software Discovery (T1063)' } - ), - id: 'T1063', - name: 'Security Software Discovery', - reference: 'https://attack.mitre.org/techniques/T1063', - tactics: 'discovery', - value: 'securitySoftwareDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitySupportProviderDescription', - { defaultMessage: 'Security Support Provider (T1101)' } - ), - id: 'T1101', - name: 'Security Support Provider', - reference: 'https://attack.mitre.org/techniques/T1101', - tactics: 'persistence', - value: 'securitySupportProvider', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.securitydMemoryDescription', - { defaultMessage: 'Securityd Memory (T1167)' } - ), - id: 'T1167', - name: 'Securityd Memory', - reference: 'https://attack.mitre.org/techniques/T1167', - tactics: 'credential-access', - value: 'securitydMemory', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serverSoftwareComponentDescription', - { defaultMessage: 'Server Software Component (T1505)' } - ), - id: 'T1505', - name: 'Server Software Component', - reference: 'https://attack.mitre.org/techniques/T1505', - tactics: 'persistence', - value: 'serverSoftwareComponent', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serverlessExecutionDescription', - { defaultMessage: 'Serverless Execution (T1648)' } - ), - id: 'T1648', - name: 'Serverless Execution', - reference: 'https://attack.mitre.org/techniques/T1648', - tactics: 'execution', - value: 'serverlessExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceExecutionDescription', - { defaultMessage: 'Service Execution (T1035)' } - ), - id: 'T1035', - name: 'Service Execution', - reference: 'https://attack.mitre.org/techniques/T1035', - tactics: 'execution', - value: 'serviceExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceRegistryPermissionsWeaknessDescription', - { defaultMessage: 'Service Registry Permissions Weakness (T1058)' } - ), - id: 'T1058', - name: 'Service Registry Permissions Weakness', - reference: 'https://attack.mitre.org/techniques/T1058', - tactics: 'persistence,privilege-escalation', - value: 'serviceRegistryPermissionsWeakness', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.serviceStopDescription', - { defaultMessage: 'Service Stop (T1489)' } - ), - id: 'T1489', - name: 'Service Stop', - reference: 'https://attack.mitre.org/techniques/T1489', - tactics: 'impact', - value: 'serviceStop', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.setuidAndSetgidDescription', - { defaultMessage: 'Setuid and Setgid (T1166)' } - ), - id: 'T1166', - name: 'Setuid and Setgid', - reference: 'https://attack.mitre.org/techniques/T1166', - tactics: 'privilege-escalation,persistence', - value: 'setuidAndSetgid', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sharedModulesDescription', - { defaultMessage: 'Shared Modules (T1129)' } - ), - id: 'T1129', - name: 'Shared Modules', - reference: 'https://attack.mitre.org/techniques/T1129', - tactics: 'execution', - value: 'sharedModules', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sharedWebrootDescription', - { defaultMessage: 'Shared Webroot (T1051)' } - ), - id: 'T1051', - name: 'Shared Webroot', - reference: 'https://attack.mitre.org/techniques/T1051', - tactics: 'lateral-movement', - value: 'sharedWebroot', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.shortcutModificationDescription', - { defaultMessage: 'Shortcut Modification (T1023)' } - ), - id: 'T1023', - name: 'Shortcut Modification', - reference: 'https://attack.mitre.org/techniques/T1023', - tactics: 'persistence', - value: 'shortcutModification', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwareDeploymentToolsDescription', - { defaultMessage: 'Software Deployment Tools (T1072)' } - ), - id: 'T1072', - name: 'Software Deployment Tools', - reference: 'https://attack.mitre.org/techniques/T1072', - tactics: 'execution,lateral-movement', - value: 'softwareDeploymentTools', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwareDiscoveryDescription', - { defaultMessage: 'Software Discovery (T1518)' } - ), - id: 'T1518', - name: 'Software Discovery', - reference: 'https://attack.mitre.org/techniques/T1518', - tactics: 'discovery', - value: 'softwareDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.softwarePackingDescription', - { defaultMessage: 'Software Packing (T1045)' } - ), - id: 'T1045', - name: 'Software Packing', - reference: 'https://attack.mitre.org/techniques/T1045', - tactics: 'defense-evasion', - value: 'softwarePacking', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sourceDescription', - { defaultMessage: 'Source (T1153)' } - ), - id: 'T1153', - name: 'Source', - reference: 'https://attack.mitre.org/techniques/T1153', - tactics: 'execution', - value: 'source', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spaceAfterFilenameDescription', - { defaultMessage: 'Space after Filename (T1151)' } - ), - id: 'T1151', - name: 'Space after Filename', - reference: 'https://attack.mitre.org/techniques/T1151', - tactics: 'defense-evasion,execution', - value: 'spaceAfterFilename', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingAttachmentDescription', - { defaultMessage: 'Spearphishing Attachment (T1193)' } - ), - id: 'T1193', - name: 'Spearphishing Attachment', - reference: 'https://attack.mitre.org/techniques/T1193', - tactics: 'initial-access', - value: 'spearphishingAttachment', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingLinkDescription', - { defaultMessage: 'Spearphishing Link (T1192)' } - ), - id: 'T1192', - name: 'Spearphishing Link', - reference: 'https://attack.mitre.org/techniques/T1192', - tactics: 'initial-access', - value: 'spearphishingLink', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.spearphishingViaServiceDescription', - { defaultMessage: 'Spearphishing via Service (T1194)' } - ), - id: 'T1194', - name: 'Spearphishing via Service', - reference: 'https://attack.mitre.org/techniques/T1194', - tactics: 'initial-access', - value: 'spearphishingViaService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stageCapabilitiesDescription', - { defaultMessage: 'Stage Capabilities (T1608)' } - ), - id: 'T1608', - name: 'Stage Capabilities', - reference: 'https://attack.mitre.org/techniques/T1608', - tactics: 'resource-development', - value: 'stageCapabilities', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.standardCryptographicProtocolDescription', - { defaultMessage: 'Standard Cryptographic Protocol (T1032)' } - ), - id: 'T1032', - name: 'Standard Cryptographic Protocol', - reference: 'https://attack.mitre.org/techniques/T1032', - tactics: 'command-and-control', - value: 'standardCryptographicProtocol', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.startupItemsDescription', - { defaultMessage: 'Startup Items (T1165)' } - ), - id: 'T1165', - name: 'Startup Items', - reference: 'https://attack.mitre.org/techniques/T1165', - tactics: 'persistence,privilege-escalation', - value: 'startupItems', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealApplicationAccessTokenDescription', - { defaultMessage: 'Steal Application Access Token (T1528)' } - ), - id: 'T1528', - name: 'Steal Application Access Token', - reference: 'https://attack.mitre.org/techniques/T1528', - tactics: 'credential-access', - value: 'stealApplicationAccessToken', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealWebSessionCookieDescription', - { defaultMessage: 'Steal Web Session Cookie (T1539)' } - ), - id: 'T1539', - name: 'Steal Web Session Cookie', - reference: 'https://attack.mitre.org/techniques/T1539', - tactics: 'credential-access', - value: 'stealWebSessionCookie', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealOrForgeAuthenticationCertificatesDescription', - { defaultMessage: 'Steal or Forge Authentication Certificates (T1649)' } - ), - id: 'T1649', - name: 'Steal or Forge Authentication Certificates', - reference: 'https://attack.mitre.org/techniques/T1649', - tactics: 'credential-access', - value: 'stealOrForgeAuthenticationCertificates', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.stealOrForgeKerberosTicketsDescription', - { defaultMessage: 'Steal or Forge Kerberos Tickets (T1558)' } - ), - id: 'T1558', - name: 'Steal or Forge Kerberos Tickets', - reference: 'https://attack.mitre.org/techniques/T1558', - tactics: 'credential-access', - value: 'stealOrForgeKerberosTickets', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.storedDataManipulationDescription', - { defaultMessage: 'Stored Data Manipulation (T1492)' } - ), - id: 'T1492', - name: 'Stored Data Manipulation', - reference: 'https://attack.mitre.org/techniques/T1492', - tactics: 'impact', - value: 'storedDataManipulation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.subvertTrustControlsDescription', - { defaultMessage: 'Subvert Trust Controls (T1553)' } - ), - id: 'T1553', - name: 'Subvert Trust Controls', - reference: 'https://attack.mitre.org/techniques/T1553', - tactics: 'defense-evasion', - value: 'subvertTrustControls', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sudoDescription', - { defaultMessage: 'Sudo (T1169)' } - ), - id: 'T1169', - name: 'Sudo', - reference: 'https://attack.mitre.org/techniques/T1169', - tactics: 'privilege-escalation', - value: 'sudo', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sudoCachingDescription', - { defaultMessage: 'Sudo Caching (T1206)' } - ), - id: 'T1206', - name: 'Sudo Caching', - reference: 'https://attack.mitre.org/techniques/T1206', - tactics: 'privilege-escalation', - value: 'sudoCaching', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.supplyChainCompromiseDescription', - { defaultMessage: 'Supply Chain Compromise (T1195)' } - ), - id: 'T1195', - name: 'Supply Chain Compromise', - reference: 'https://attack.mitre.org/techniques/T1195', - tactics: 'initial-access', - value: 'supplyChainCompromise', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemBinaryProxyExecutionDescription', - { defaultMessage: 'System Binary Proxy Execution (T1218)' } - ), - id: 'T1218', - name: 'System Binary Proxy Execution', - reference: 'https://attack.mitre.org/techniques/T1218', - tactics: 'defense-evasion', - value: 'systemBinaryProxyExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemFirmwareDescription', - { defaultMessage: 'System Firmware (T1019)' } - ), - id: 'T1019', - name: 'System Firmware', - reference: 'https://attack.mitre.org/techniques/T1019', - tactics: 'persistence', - value: 'systemFirmware', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemInformationDiscoveryDescription', - { defaultMessage: 'System Information Discovery (T1082)' } - ), - id: 'T1082', - name: 'System Information Discovery', - reference: 'https://attack.mitre.org/techniques/T1082', - tactics: 'discovery', - value: 'systemInformationDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemLocationDiscoveryDescription', - { defaultMessage: 'System Location Discovery (T1614)' } - ), - id: 'T1614', - name: 'System Location Discovery', - reference: 'https://attack.mitre.org/techniques/T1614', - tactics: 'discovery', - value: 'systemLocationDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemNetworkConfigurationDiscoveryDescription', - { defaultMessage: 'System Network Configuration Discovery (T1016)' } - ), - id: 'T1016', - name: 'System Network Configuration Discovery', - reference: 'https://attack.mitre.org/techniques/T1016', - tactics: 'discovery', - value: 'systemNetworkConfigurationDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemNetworkConnectionsDiscoveryDescription', - { defaultMessage: 'System Network Connections Discovery (T1049)' } - ), - id: 'T1049', - name: 'System Network Connections Discovery', - reference: 'https://attack.mitre.org/techniques/T1049', - tactics: 'discovery', - value: 'systemNetworkConnectionsDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemOwnerUserDiscoveryDescription', - { defaultMessage: 'System Owner/User Discovery (T1033)' } - ), - id: 'T1033', - name: 'System Owner/User Discovery', - reference: 'https://attack.mitre.org/techniques/T1033', - tactics: 'discovery', - value: 'systemOwnerUserDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemScriptProxyExecutionDescription', - { defaultMessage: 'System Script Proxy Execution (T1216)' } - ), - id: 'T1216', - name: 'System Script Proxy Execution', - reference: 'https://attack.mitre.org/techniques/T1216', - tactics: 'defense-evasion', - value: 'systemScriptProxyExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemServiceDiscoveryDescription', - { defaultMessage: 'System Service Discovery (T1007)' } - ), - id: 'T1007', - name: 'System Service Discovery', - reference: 'https://attack.mitre.org/techniques/T1007', - tactics: 'discovery', - value: 'systemServiceDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemServicesDescription', - { defaultMessage: 'System Services (T1569)' } - ), - id: 'T1569', - name: 'System Services', - reference: 'https://attack.mitre.org/techniques/T1569', - tactics: 'execution', - value: 'systemServices', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemShutdownRebootDescription', - { defaultMessage: 'System Shutdown/Reboot (T1529)' } - ), - id: 'T1529', - name: 'System Shutdown/Reboot', - reference: 'https://attack.mitre.org/techniques/T1529', - tactics: 'impact', - value: 'systemShutdownReboot', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemTimeDiscoveryDescription', - { defaultMessage: 'System Time Discovery (T1124)' } - ), - id: 'T1124', - name: 'System Time Discovery', - reference: 'https://attack.mitre.org/techniques/T1124', - tactics: 'discovery', - value: 'systemTimeDiscovery', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.systemdServiceDescription', - { defaultMessage: 'Systemd Service (T1501)' } - ), - id: 'T1501', - name: 'Systemd Service', - reference: 'https://attack.mitre.org/techniques/T1501', - tactics: 'persistence', - value: 'systemdService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.taintSharedContentDescription', - { defaultMessage: 'Taint Shared Content (T1080)' } - ), - id: 'T1080', - name: 'Taint Shared Content', - reference: 'https://attack.mitre.org/techniques/T1080', - tactics: 'lateral-movement', - value: 'taintSharedContent', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.templateInjectionDescription', - { defaultMessage: 'Template Injection (T1221)' } - ), - id: 'T1221', - name: 'Template Injection', - reference: 'https://attack.mitre.org/techniques/T1221', - tactics: 'defense-evasion', - value: 'templateInjection', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.timeProvidersDescription', - { defaultMessage: 'Time Providers (T1209)' } - ), - id: 'T1209', - name: 'Time Providers', - reference: 'https://attack.mitre.org/techniques/T1209', - tactics: 'persistence', - value: 'timeProviders', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.timestompDescription', - { defaultMessage: 'Timestomp (T1099)' } - ), - id: 'T1099', - name: 'Timestomp', - reference: 'https://attack.mitre.org/techniques/T1099', - tactics: 'defense-evasion', - value: 'timestomp', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trafficSignalingDescription', - { defaultMessage: 'Traffic Signaling (T1205)' } - ), - id: 'T1205', - name: 'Traffic Signaling', - reference: 'https://attack.mitre.org/techniques/T1205', - tactics: 'defense-evasion,persistence,command-and-control', - value: 'trafficSignaling', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.transferDataToCloudAccountDescription', - { defaultMessage: 'Transfer Data to Cloud Account (T1537)' } - ), - id: 'T1537', - name: 'Transfer Data to Cloud Account', - reference: 'https://attack.mitre.org/techniques/T1537', - tactics: 'exfiltration', - value: 'transferDataToCloudAccount', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.transmittedDataManipulationDescription', - { defaultMessage: 'Transmitted Data Manipulation (T1493)' } - ), - id: 'T1493', - name: 'Transmitted Data Manipulation', - reference: 'https://attack.mitre.org/techniques/T1493', - tactics: 'impact', - value: 'transmittedDataManipulation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trapDescription', - { defaultMessage: 'Trap (T1154)' } - ), - id: 'T1154', - name: 'Trap', - reference: 'https://attack.mitre.org/techniques/T1154', - tactics: 'execution,persistence', - value: 'trap', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trustedDeveloperUtilitiesProxyExecutionDescription', - { defaultMessage: 'Trusted Developer Utilities Proxy Execution (T1127)' } - ), - id: 'T1127', - name: 'Trusted Developer Utilities Proxy Execution', - reference: 'https://attack.mitre.org/techniques/T1127', - tactics: 'defense-evasion', - value: 'trustedDeveloperUtilitiesProxyExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trustedRelationshipDescription', - { defaultMessage: 'Trusted Relationship (T1199)' } - ), - id: 'T1199', - name: 'Trusted Relationship', - reference: 'https://attack.mitre.org/techniques/T1199', - tactics: 'initial-access', - value: 'trustedRelationship', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.uncommonlyUsedPortDescription', - { defaultMessage: 'Uncommonly Used Port (T1065)' } - ), - id: 'T1065', - name: 'Uncommonly Used Port', - reference: 'https://attack.mitre.org/techniques/T1065', - tactics: 'command-and-control', - value: 'uncommonlyUsedPort', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.unsecuredCredentialsDescription', - { defaultMessage: 'Unsecured Credentials (T1552)' } - ), - id: 'T1552', - name: 'Unsecured Credentials', - reference: 'https://attack.mitre.org/techniques/T1552', - tactics: 'credential-access', - value: 'unsecuredCredentials', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.unusedUnsupportedCloudRegionsDescription', - { defaultMessage: 'Unused/Unsupported Cloud Regions (T1535)' } - ), - id: 'T1535', - name: 'Unused/Unsupported Cloud Regions', - reference: 'https://attack.mitre.org/techniques/T1535', - tactics: 'defense-evasion', - value: 'unusedUnsupportedCloudRegions', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.useAlternateAuthenticationMaterialDescription', - { defaultMessage: 'Use Alternate Authentication Material (T1550)' } - ), - id: 'T1550', - name: 'Use Alternate Authentication Material', - reference: 'https://attack.mitre.org/techniques/T1550', - tactics: 'defense-evasion,lateral-movement', - value: 'useAlternateAuthenticationMaterial', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.userExecutionDescription', - { defaultMessage: 'User Execution (T1204)' } - ), - id: 'T1204', - name: 'User Execution', - reference: 'https://attack.mitre.org/techniques/T1204', - tactics: 'execution', - value: 'userExecution', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.validAccountsDescription', - { defaultMessage: 'Valid Accounts (T1078)' } - ), - id: 'T1078', - name: 'Valid Accounts', - reference: 'https://attack.mitre.org/techniques/T1078', - tactics: 'defense-evasion,persistence,privilege-escalation,initial-access', - value: 'validAccounts', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.videoCaptureDescription', - { defaultMessage: 'Video Capture (T1125)' } - ), - id: 'T1125', - name: 'Video Capture', - reference: 'https://attack.mitre.org/techniques/T1125', - tactics: 'collection', - value: 'videoCapture', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.virtualizationSandboxEvasionDescription', - { defaultMessage: 'Virtualization/Sandbox Evasion (T1497)' } - ), - id: 'T1497', - name: 'Virtualization/Sandbox Evasion', - reference: 'https://attack.mitre.org/techniques/T1497', - tactics: 'defense-evasion,discovery', - value: 'virtualizationSandboxEvasion', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.weakenEncryptionDescription', - { defaultMessage: 'Weaken Encryption (T1600)' } - ), - id: 'T1600', - name: 'Weaken Encryption', - reference: 'https://attack.mitre.org/techniques/T1600', - tactics: 'defense-evasion', - value: 'weakenEncryption', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webServiceDescription', - { defaultMessage: 'Web Service (T1102)' } - ), - id: 'T1102', - name: 'Web Service', - reference: 'https://attack.mitre.org/techniques/T1102', - tactics: 'command-and-control', - value: 'webService', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webSessionCookieDescription', - { defaultMessage: 'Web Session Cookie (T1506)' } - ), - id: 'T1506', - name: 'Web Session Cookie', - reference: 'https://attack.mitre.org/techniques/T1506', - tactics: 'defense-evasion,lateral-movement', - value: 'webSessionCookie', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webShellDescription', - { defaultMessage: 'Web Shell (T1100)' } - ), - id: 'T1100', - name: 'Web Shell', - reference: 'https://attack.mitre.org/techniques/T1100', - tactics: 'persistence,privilege-escalation', - value: 'webShell', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsAdminSharesDescription', - { defaultMessage: 'Windows Admin Shares (T1077)' } - ), - id: 'T1077', - name: 'Windows Admin Shares', - reference: 'https://attack.mitre.org/techniques/T1077', - tactics: 'lateral-movement', - value: 'windowsAdminShares', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationDescription', - { defaultMessage: 'Windows Management Instrumentation (T1047)' } - ), - id: 'T1047', - name: 'Windows Management Instrumentation', - reference: 'https://attack.mitre.org/techniques/T1047', - tactics: 'execution', - value: 'windowsManagementInstrumentation', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationEventSubscriptionDescription', - { defaultMessage: 'Windows Management Instrumentation Event Subscription (T1084)' } - ), - id: 'T1084', - name: 'Windows Management Instrumentation Event Subscription', - reference: 'https://attack.mitre.org/techniques/T1084', - tactics: 'persistence', - value: 'windowsManagementInstrumentationEventSubscription', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsRemoteManagementDescription', - { defaultMessage: 'Windows Remote Management (T1028)' } - ), - id: 'T1028', - name: 'Windows Remote Management', - reference: 'https://attack.mitre.org/techniques/T1028', - tactics: 'execution,lateral-movement', - value: 'windowsRemoteManagement', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.winlogonHelperDllDescription', - { defaultMessage: 'Winlogon Helper DLL (T1004)' } - ), - id: 'T1004', - name: 'Winlogon Helper DLL', - reference: 'https://attack.mitre.org/techniques/T1004', - tactics: 'persistence', - value: 'winlogonHelperDll', - }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.xslScriptProcessingDescription', - { defaultMessage: 'XSL Script Processing (T1220)' } - ), - id: 'T1220', - name: 'XSL Script Processing', - reference: 'https://attack.mitre.org/techniques/T1220', - tactics: 'defense-evasion', - value: 'xslScriptProcessing', - }, -]; - -export const subtechniques = [ - { - name: '/etc/passwd and /etc/shadow', - id: 'T1003.008', - reference: 'https://attack.mitre.org/techniques/T1003/008', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'ARP Cache Poisoning', - id: 'T1557.002', - reference: 'https://attack.mitre.org/techniques/T1557/002', - tactics: ['credential-access', 'collection'], - techniqueId: 'T1557', - }, - { - name: 'AS-REP Roasting', - id: 'T1558.004', - reference: 'https://attack.mitre.org/techniques/T1558/004', - tactics: ['credential-access'], - techniqueId: 'T1558', - }, - { - name: 'Accessibility Features', - id: 'T1546.008', - reference: 'https://attack.mitre.org/techniques/T1546/008', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Active Setup', - id: 'T1547.014', - reference: 'https://attack.mitre.org/techniques/T1547/014', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Add-ins', - id: 'T1137.006', - reference: 'https://attack.mitre.org/techniques/T1137/006', - tactics: ['persistence'], - techniqueId: 'T1137', - }, - { - name: 'Additional Cloud Credentials', - id: 'T1098.001', - reference: 'https://attack.mitre.org/techniques/T1098/001', - tactics: ['persistence'], - techniqueId: 'T1098', - }, - { - name: 'Additional Cloud Roles', - id: 'T1098.003', - reference: 'https://attack.mitre.org/techniques/T1098/003', - tactics: ['persistence'], - techniqueId: 'T1098', - }, - { - name: 'Additional Email Delegate Permissions', - id: 'T1098.002', - reference: 'https://attack.mitre.org/techniques/T1098/002', - tactics: ['persistence'], - techniqueId: 'T1098', - }, - { - name: 'AppCert DLLs', - id: 'T1546.009', - reference: 'https://attack.mitre.org/techniques/T1546/009', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'AppInit DLLs', - id: 'T1546.010', - reference: 'https://attack.mitre.org/techniques/T1546/010', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'AppleScript', - id: 'T1059.002', - reference: 'https://attack.mitre.org/techniques/T1059/002', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'Application Access Token', - id: 'T1550.001', - reference: 'https://attack.mitre.org/techniques/T1550/001', - tactics: ['defense-evasion', 'lateral-movement'], - techniqueId: 'T1550', - }, - { - name: 'Application Exhaustion Flood', - id: 'T1499.003', - reference: 'https://attack.mitre.org/techniques/T1499/003', - tactics: ['impact'], - techniqueId: 'T1499', - }, - { - name: 'Application Shimming', - id: 'T1546.011', - reference: 'https://attack.mitre.org/techniques/T1546/011', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Application or System Exploitation', - id: 'T1499.004', - reference: 'https://attack.mitre.org/techniques/T1499/004', - tactics: ['impact'], - techniqueId: 'T1499', - }, - { - name: 'Archive via Custom Method', - id: 'T1560.003', - reference: 'https://attack.mitre.org/techniques/T1560/003', - tactics: ['collection'], - techniqueId: 'T1560', - }, - { - name: 'Archive via Library', - id: 'T1560.002', - reference: 'https://attack.mitre.org/techniques/T1560/002', - tactics: ['collection'], - techniqueId: 'T1560', - }, - { - name: 'Archive via Utility', - id: 'T1560.001', - reference: 'https://attack.mitre.org/techniques/T1560/001', - tactics: ['collection'], - techniqueId: 'T1560', - }, - { - name: 'Asymmetric Cryptography', - id: 'T1573.002', - reference: 'https://attack.mitre.org/techniques/T1573/002', - tactics: ['command-and-control'], - techniqueId: 'T1573', - }, - { - name: 'Asynchronous Procedure Call', - id: 'T1055.004', - reference: 'https://attack.mitre.org/techniques/T1055/004', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'At', - id: 'T1053.002', - reference: 'https://attack.mitre.org/techniques/T1053/002', - tactics: ['execution', 'persistence', 'privilege-escalation'], - techniqueId: 'T1053', - }, - { - name: 'At (Linux)', - id: 'T1053.001', - reference: 'https://attack.mitre.org/techniques/T1053/001', - tactics: ['execution', 'persistence', 'privilege-escalation'], - techniqueId: 'T1053', - }, - { - name: 'Authentication Package', - id: 'T1547.002', - reference: 'https://attack.mitre.org/techniques/T1547/002', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Bash History', - id: 'T1552.003', - reference: 'https://attack.mitre.org/techniques/T1552/003', - tactics: ['credential-access'], - techniqueId: 'T1552', - }, - { - name: 'Bidirectional Communication', - id: 'T1102.002', - reference: 'https://attack.mitre.org/techniques/T1102/002', - tactics: ['command-and-control'], - techniqueId: 'T1102', - }, - { - name: 'Binary Padding', - id: 'T1027.001', - reference: 'https://attack.mitre.org/techniques/T1027/001', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Bootkit', - id: 'T1542.003', - reference: 'https://attack.mitre.org/techniques/T1542/003', - tactics: ['persistence', 'defense-evasion'], - techniqueId: 'T1542', - }, - { - name: 'Botnet', - id: 'T1583.005', - reference: 'https://attack.mitre.org/techniques/T1583/005', - tactics: ['resource-development'], - techniqueId: 'T1583', - }, - { - name: 'Botnet', - id: 'T1584.005', - reference: 'https://attack.mitre.org/techniques/T1584/005', - tactics: ['resource-development'], - techniqueId: 'T1584', - }, - { - name: 'Business Relationships', - id: 'T1591.002', - reference: 'https://attack.mitre.org/techniques/T1591/002', - tactics: ['reconnaissance'], - techniqueId: 'T1591', - }, - { - name: 'Bypass User Account Control', - id: 'T1548.002', - reference: 'https://attack.mitre.org/techniques/T1548/002', - tactics: ['privilege-escalation', 'defense-evasion'], - techniqueId: 'T1548', - }, - { - name: 'CDNs', - id: 'T1596.004', - reference: 'https://attack.mitre.org/techniques/T1596/004', - tactics: ['reconnaissance'], - techniqueId: 'T1596', - }, - { - name: 'CMSTP', - id: 'T1218.003', - reference: 'https://attack.mitre.org/techniques/T1218/003', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'COR_PROFILER', - id: 'T1574.012', - reference: 'https://attack.mitre.org/techniques/T1574/012', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Cached Domain Credentials', - id: 'T1003.005', - reference: 'https://attack.mitre.org/techniques/T1003/005', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'Change Default File Association', - id: 'T1546.001', - reference: 'https://attack.mitre.org/techniques/T1546/001', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Clear Command History', - id: 'T1070.003', - reference: 'https://attack.mitre.org/techniques/T1070/003', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Clear Linux or Mac System Logs', - id: 'T1070.002', - reference: 'https://attack.mitre.org/techniques/T1070/002', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Clear Mailbox Data', - id: 'T1070.008', - reference: 'https://attack.mitre.org/techniques/T1070/008', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Clear Network Connection History and Configurations', - id: 'T1070.007', - reference: 'https://attack.mitre.org/techniques/T1070/007', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Clear Persistence', - id: 'T1070.009', - reference: 'https://attack.mitre.org/techniques/T1070/009', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Clear Windows Event Logs', - id: 'T1070.001', - reference: 'https://attack.mitre.org/techniques/T1070/001', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Client Configurations', - id: 'T1592.004', - reference: 'https://attack.mitre.org/techniques/T1592/004', - tactics: ['reconnaissance'], - techniqueId: 'T1592', - }, - { - name: 'Cloud Account', - id: 'T1087.004', - reference: 'https://attack.mitre.org/techniques/T1087/004', - tactics: ['discovery'], - techniqueId: 'T1087', - }, - { - name: 'Cloud Account', - id: 'T1136.003', - reference: 'https://attack.mitre.org/techniques/T1136/003', - tactics: ['persistence'], - techniqueId: 'T1136', - }, - { - name: 'Cloud Accounts', - id: 'T1586.003', - reference: 'https://attack.mitre.org/techniques/T1586/003', - tactics: ['resource-development'], - techniqueId: 'T1586', - }, - { - name: 'Cloud Accounts', - id: 'T1585.003', - reference: 'https://attack.mitre.org/techniques/T1585/003', - tactics: ['resource-development'], - techniqueId: 'T1585', - }, - { - name: 'Cloud Accounts', - id: 'T1078.004', - reference: 'https://attack.mitre.org/techniques/T1078/004', - tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], - techniqueId: 'T1078', - }, - { - name: 'Cloud Groups', - id: 'T1069.003', - reference: 'https://attack.mitre.org/techniques/T1069/003', - tactics: ['discovery'], - techniqueId: 'T1069', - }, - { - name: 'Cloud Instance Metadata API', - id: 'T1552.005', - reference: 'https://attack.mitre.org/techniques/T1552/005', - tactics: ['credential-access'], - techniqueId: 'T1552', - }, - { - name: 'Code Repositories', - id: 'T1593.003', - reference: 'https://attack.mitre.org/techniques/T1593/003', - tactics: ['reconnaissance'], - techniqueId: 'T1593', - }, - { - name: 'Code Repositories', - id: 'T1213.003', - reference: 'https://attack.mitre.org/techniques/T1213/003', - tactics: ['collection'], - techniqueId: 'T1213', - }, - { - name: 'Code Signing', - id: 'T1553.002', - reference: 'https://attack.mitre.org/techniques/T1553/002', - tactics: ['defense-evasion'], - techniqueId: 'T1553', - }, - { - name: 'Code Signing Certificates', - id: 'T1587.002', - reference: 'https://attack.mitre.org/techniques/T1587/002', - tactics: ['resource-development'], - techniqueId: 'T1587', - }, - { - name: 'Code Signing Certificates', - id: 'T1588.003', - reference: 'https://attack.mitre.org/techniques/T1588/003', - tactics: ['resource-development'], - techniqueId: 'T1588', - }, - { - name: 'Code Signing Policy Modification', - id: 'T1553.006', - reference: 'https://attack.mitre.org/techniques/T1553/006', - tactics: ['defense-evasion'], - techniqueId: 'T1553', - }, - { - name: 'Compile After Delivery', - id: 'T1027.004', - reference: 'https://attack.mitre.org/techniques/T1027/004', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Compiled HTML File', - id: 'T1218.001', - reference: 'https://attack.mitre.org/techniques/T1218/001', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Component Firmware', - id: 'T1542.002', - reference: 'https://attack.mitre.org/techniques/T1542/002', - tactics: ['persistence', 'defense-evasion'], - techniqueId: 'T1542', - }, - { - name: 'Component Object Model', - id: 'T1559.001', - reference: 'https://attack.mitre.org/techniques/T1559/001', - tactics: ['execution'], - techniqueId: 'T1559', - }, - { - name: 'Component Object Model Hijacking', - id: 'T1546.015', - reference: 'https://attack.mitre.org/techniques/T1546/015', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Compromise Hardware Supply Chain', - id: 'T1195.003', - reference: 'https://attack.mitre.org/techniques/T1195/003', - tactics: ['initial-access'], - techniqueId: 'T1195', - }, - { - name: 'Compromise Software Dependencies and Development Tools', - id: 'T1195.001', - reference: 'https://attack.mitre.org/techniques/T1195/001', - tactics: ['initial-access'], - techniqueId: 'T1195', - }, - { - name: 'Compromise Software Supply Chain', - id: 'T1195.002', - reference: 'https://attack.mitre.org/techniques/T1195/002', - tactics: ['initial-access'], - techniqueId: 'T1195', - }, - { - name: 'Confluence', - id: 'T1213.001', - reference: 'https://attack.mitre.org/techniques/T1213/001', - tactics: ['collection'], - techniqueId: 'T1213', - }, - { - name: 'Container API', - id: 'T1552.007', - reference: 'https://attack.mitre.org/techniques/T1552/007', - tactics: ['credential-access'], - techniqueId: 'T1552', - }, - { - name: 'Container Orchestration Job', - id: 'T1053.007', - reference: 'https://attack.mitre.org/techniques/T1053/007', - tactics: ['execution', 'persistence', 'privilege-escalation'], - techniqueId: 'T1053', - }, - { - name: 'Control Panel', - id: 'T1218.002', - reference: 'https://attack.mitre.org/techniques/T1218/002', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Create Cloud Instance', - id: 'T1578.002', - reference: 'https://attack.mitre.org/techniques/T1578/002', - tactics: ['defense-evasion'], - techniqueId: 'T1578', - }, - { - name: 'Create Process with Token', - id: 'T1134.002', - reference: 'https://attack.mitre.org/techniques/T1134/002', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1134', - }, - { - name: 'Create Snapshot', - id: 'T1578.001', - reference: 'https://attack.mitre.org/techniques/T1578/001', - tactics: ['defense-evasion'], - techniqueId: 'T1578', - }, - { - name: 'Credential API Hooking', - id: 'T1056.004', - reference: 'https://attack.mitre.org/techniques/T1056/004', - tactics: ['collection', 'credential-access'], - techniqueId: 'T1056', - }, - { - name: 'Credential Stuffing', - id: 'T1110.004', - reference: 'https://attack.mitre.org/techniques/T1110/004', - tactics: ['credential-access'], - techniqueId: 'T1110', - }, - { - name: 'Credentials', - id: 'T1589.001', - reference: 'https://attack.mitre.org/techniques/T1589/001', - tactics: ['reconnaissance'], - techniqueId: 'T1589', - }, - { - name: 'Credentials In Files', - id: 'T1552.001', - reference: 'https://attack.mitre.org/techniques/T1552/001', - tactics: ['credential-access'], - techniqueId: 'T1552', - }, - { - name: 'Credentials from Web Browsers', - id: 'T1555.003', - reference: 'https://attack.mitre.org/techniques/T1555/003', - tactics: ['credential-access'], - techniqueId: 'T1555', - }, - { - name: 'Credentials in Registry', - id: 'T1552.002', - reference: 'https://attack.mitre.org/techniques/T1552/002', - tactics: ['credential-access'], - techniqueId: 'T1552', - }, - { - name: 'Cron', - id: 'T1053.003', - reference: 'https://attack.mitre.org/techniques/T1053/003', - tactics: ['execution', 'persistence', 'privilege-escalation'], - techniqueId: 'T1053', - }, - { - name: 'DCSync', - id: 'T1003.006', - reference: 'https://attack.mitre.org/techniques/T1003/006', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'DHCP Spoofing', - id: 'T1557.003', - reference: 'https://attack.mitre.org/techniques/T1557/003', - tactics: ['credential-access', 'collection'], - techniqueId: 'T1557', - }, - { - name: 'DLL Search Order Hijacking', - id: 'T1574.001', - reference: 'https://attack.mitre.org/techniques/T1574/001', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'DLL Side-Loading', - id: 'T1574.002', - reference: 'https://attack.mitre.org/techniques/T1574/002', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'DNS', - id: 'T1590.002', - reference: 'https://attack.mitre.org/techniques/T1590/002', - tactics: ['reconnaissance'], - techniqueId: 'T1590', - }, - { - name: 'DNS', - id: 'T1071.004', - reference: 'https://attack.mitre.org/techniques/T1071/004', - tactics: ['command-and-control'], - techniqueId: 'T1071', - }, - { - name: 'DNS Calculation', - id: 'T1568.003', - reference: 'https://attack.mitre.org/techniques/T1568/003', - tactics: ['command-and-control'], - techniqueId: 'T1568', - }, - { - name: 'DNS Server', - id: 'T1583.002', - reference: 'https://attack.mitre.org/techniques/T1583/002', - tactics: ['resource-development'], - techniqueId: 'T1583', - }, - { - name: 'DNS Server', - id: 'T1584.002', - reference: 'https://attack.mitre.org/techniques/T1584/002', - tactics: ['resource-development'], - techniqueId: 'T1584', - }, - { - name: 'DNS/Passive DNS', - id: 'T1596.001', - reference: 'https://attack.mitre.org/techniques/T1596/001', - tactics: ['reconnaissance'], - techniqueId: 'T1596', - }, - { - name: 'Dead Drop Resolver', - id: 'T1102.001', - reference: 'https://attack.mitre.org/techniques/T1102/001', - tactics: ['command-and-control'], - techniqueId: 'T1102', - }, - { - name: 'Default Accounts', - id: 'T1078.001', - reference: 'https://attack.mitre.org/techniques/T1078/001', - tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], - techniqueId: 'T1078', - }, - { - name: 'Delete Cloud Instance', - id: 'T1578.003', - reference: 'https://attack.mitre.org/techniques/T1578/003', - tactics: ['defense-evasion'], - techniqueId: 'T1578', - }, - { - name: 'Determine Physical Locations', - id: 'T1591.001', - reference: 'https://attack.mitre.org/techniques/T1591/001', - tactics: ['reconnaissance'], - techniqueId: 'T1591', - }, - { - name: 'Device Registration', - id: 'T1098.005', - reference: 'https://attack.mitre.org/techniques/T1098/005', - tactics: ['persistence'], - techniqueId: 'T1098', - }, - { - name: 'Digital Certificates', - id: 'T1596.003', - reference: 'https://attack.mitre.org/techniques/T1596/003', - tactics: ['reconnaissance'], - techniqueId: 'T1596', - }, - { - name: 'Digital Certificates', - id: 'T1588.004', - reference: 'https://attack.mitre.org/techniques/T1588/004', - tactics: ['resource-development'], - techniqueId: 'T1588', - }, - { - name: 'Digital Certificates', - id: 'T1587.003', - reference: 'https://attack.mitre.org/techniques/T1587/003', - tactics: ['resource-development'], - techniqueId: 'T1587', - }, - { - name: 'Direct Network Flood', - id: 'T1498.001', - reference: 'https://attack.mitre.org/techniques/T1498/001', - tactics: ['impact'], - techniqueId: 'T1498', - }, - { - name: 'Disable Cloud Logs', - id: 'T1562.008', - reference: 'https://attack.mitre.org/techniques/T1562/008', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Disable Crypto Hardware', - id: 'T1600.002', - reference: 'https://attack.mitre.org/techniques/T1600/002', - tactics: ['defense-evasion'], - techniqueId: 'T1600', - }, - { - name: 'Disable Windows Event Logging', - id: 'T1562.002', - reference: 'https://attack.mitre.org/techniques/T1562/002', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Disable or Modify Cloud Firewall', - id: 'T1562.007', - reference: 'https://attack.mitre.org/techniques/T1562/007', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Disable or Modify System Firewall', - id: 'T1562.004', - reference: 'https://attack.mitre.org/techniques/T1562/004', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Disable or Modify Tools', - id: 'T1562.001', - reference: 'https://attack.mitre.org/techniques/T1562/001', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Disk Content Wipe', - id: 'T1561.001', - reference: 'https://attack.mitre.org/techniques/T1561/001', - tactics: ['impact'], - techniqueId: 'T1561', - }, - { - name: 'Disk Structure Wipe', - id: 'T1561.002', - reference: 'https://attack.mitre.org/techniques/T1561/002', - tactics: ['impact'], - techniqueId: 'T1561', - }, - { - name: 'Distributed Component Object Model', - id: 'T1021.003', - reference: 'https://attack.mitre.org/techniques/T1021/003', - tactics: ['lateral-movement'], - techniqueId: 'T1021', - }, - { - name: 'Domain Account', - id: 'T1087.002', - reference: 'https://attack.mitre.org/techniques/T1087/002', - tactics: ['discovery'], - techniqueId: 'T1087', - }, - { - name: 'Domain Account', - id: 'T1136.002', - reference: 'https://attack.mitre.org/techniques/T1136/002', - tactics: ['persistence'], - techniqueId: 'T1136', - }, - { - name: 'Domain Accounts', - id: 'T1078.002', - reference: 'https://attack.mitre.org/techniques/T1078/002', - tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], - techniqueId: 'T1078', - }, - { - name: 'Domain Controller Authentication', - id: 'T1556.001', - reference: 'https://attack.mitre.org/techniques/T1556/001', - tactics: ['credential-access', 'defense-evasion', 'persistence'], - techniqueId: 'T1556', - }, - { - name: 'Domain Fronting', - id: 'T1090.004', - reference: 'https://attack.mitre.org/techniques/T1090/004', - tactics: ['command-and-control'], - techniqueId: 'T1090', - }, - { - name: 'Domain Generation Algorithms', - id: 'T1568.002', - reference: 'https://attack.mitre.org/techniques/T1568/002', - tactics: ['command-and-control'], - techniqueId: 'T1568', - }, - { - name: 'Domain Groups', - id: 'T1069.002', - reference: 'https://attack.mitre.org/techniques/T1069/002', - tactics: ['discovery'], - techniqueId: 'T1069', - }, - { - name: 'Domain Properties', - id: 'T1590.001', - reference: 'https://attack.mitre.org/techniques/T1590/001', - tactics: ['reconnaissance'], - techniqueId: 'T1590', - }, - { - name: 'Domain Trust Modification', - id: 'T1484.002', - reference: 'https://attack.mitre.org/techniques/T1484/002', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1484', - }, - { - name: 'Domains', - id: 'T1583.001', - reference: 'https://attack.mitre.org/techniques/T1583/001', - tactics: ['resource-development'], - techniqueId: 'T1583', - }, - { - name: 'Domains', - id: 'T1584.001', - reference: 'https://attack.mitre.org/techniques/T1584/001', - tactics: ['resource-development'], - techniqueId: 'T1584', - }, - { - name: 'Double File Extension', - id: 'T1036.007', - reference: 'https://attack.mitre.org/techniques/T1036/007', - tactics: ['defense-evasion'], - techniqueId: 'T1036', - }, - { - name: 'Downgrade Attack', - id: 'T1562.010', - reference: 'https://attack.mitre.org/techniques/T1562/010', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Downgrade System Image', - id: 'T1601.002', - reference: 'https://attack.mitre.org/techniques/T1601/002', - tactics: ['defense-evasion'], - techniqueId: 'T1601', - }, - { - name: 'Drive-by Target', - id: 'T1608.004', - reference: 'https://attack.mitre.org/techniques/T1608/004', - tactics: ['resource-development'], - techniqueId: 'T1608', - }, - { - name: 'Dylib Hijacking', - id: 'T1574.004', - reference: 'https://attack.mitre.org/techniques/T1574/004', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Dynamic API Resolution', - id: 'T1027.007', - reference: 'https://attack.mitre.org/techniques/T1027/007', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Dynamic Data Exchange', - id: 'T1559.002', - reference: 'https://attack.mitre.org/techniques/T1559/002', - tactics: ['execution'], - techniqueId: 'T1559', - }, - { - name: 'Dynamic Linker Hijacking', - id: 'T1574.006', - reference: 'https://attack.mitre.org/techniques/T1574/006', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Dynamic-link Library Injection', - id: 'T1055.001', - reference: 'https://attack.mitre.org/techniques/T1055/001', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Elevated Execution with Prompt', - id: 'T1548.004', - reference: 'https://attack.mitre.org/techniques/T1548/004', - tactics: ['privilege-escalation', 'defense-evasion'], - techniqueId: 'T1548', - }, - { - name: 'Email Account', - id: 'T1087.003', - reference: 'https://attack.mitre.org/techniques/T1087/003', - tactics: ['discovery'], - techniqueId: 'T1087', - }, - { - name: 'Email Accounts', - id: 'T1586.002', - reference: 'https://attack.mitre.org/techniques/T1586/002', - tactics: ['resource-development'], - techniqueId: 'T1586', - }, - { - name: 'Email Accounts', - id: 'T1585.002', - reference: 'https://attack.mitre.org/techniques/T1585/002', - tactics: ['resource-development'], - techniqueId: 'T1585', - }, - { - name: 'Email Addresses', - id: 'T1589.002', - reference: 'https://attack.mitre.org/techniques/T1589/002', - tactics: ['reconnaissance'], - techniqueId: 'T1589', - }, - { - name: 'Email Forwarding Rule', - id: 'T1114.003', - reference: 'https://attack.mitre.org/techniques/T1114/003', - tactics: ['collection'], - techniqueId: 'T1114', - }, - { - name: 'Email Hiding Rules', - id: 'T1564.008', - reference: 'https://attack.mitre.org/techniques/T1564/008', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Embedded Payloads', - id: 'T1027.009', - reference: 'https://attack.mitre.org/techniques/T1027/009', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Emond', - id: 'T1546.014', - reference: 'https://attack.mitre.org/techniques/T1546/014', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Employee Names', - id: 'T1589.003', - reference: 'https://attack.mitre.org/techniques/T1589/003', - tactics: ['reconnaissance'], - techniqueId: 'T1589', - }, - { - name: 'Environmental Keying', - id: 'T1480.001', - reference: 'https://attack.mitre.org/techniques/T1480/001', - tactics: ['defense-evasion'], - techniqueId: 'T1480', - }, - { - name: 'Executable Installer File Permissions Weakness', - id: 'T1574.005', - reference: 'https://attack.mitre.org/techniques/T1574/005', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Exfiltration Over Asymmetric Encrypted Non-C2 Protocol', - id: 'T1048.002', - reference: 'https://attack.mitre.org/techniques/T1048/002', - tactics: ['exfiltration'], - techniqueId: 'T1048', - }, - { - name: 'Exfiltration Over Bluetooth', - id: 'T1011.001', - reference: 'https://attack.mitre.org/techniques/T1011/001', - tactics: ['exfiltration'], - techniqueId: 'T1011', - }, - { - name: 'Exfiltration Over Symmetric Encrypted Non-C2 Protocol', - id: 'T1048.001', - reference: 'https://attack.mitre.org/techniques/T1048/001', - tactics: ['exfiltration'], - techniqueId: 'T1048', - }, - { - name: 'Exfiltration Over Unencrypted Non-C2 Protocol', - id: 'T1048.003', - reference: 'https://attack.mitre.org/techniques/T1048/003', - tactics: ['exfiltration'], - techniqueId: 'T1048', - }, - { - name: 'Exfiltration over USB', - id: 'T1052.001', - reference: 'https://attack.mitre.org/techniques/T1052/001', - tactics: ['exfiltration'], - techniqueId: 'T1052', - }, - { - name: 'Exfiltration to Cloud Storage', - id: 'T1567.002', - reference: 'https://attack.mitre.org/techniques/T1567/002', - tactics: ['exfiltration'], - techniqueId: 'T1567', - }, - { - name: 'Exfiltration to Code Repository', - id: 'T1567.001', - reference: 'https://attack.mitre.org/techniques/T1567/001', - tactics: ['exfiltration'], - techniqueId: 'T1567', - }, - { - name: 'Exploits', - id: 'T1587.004', - reference: 'https://attack.mitre.org/techniques/T1587/004', - tactics: ['resource-development'], - techniqueId: 'T1587', - }, - { - name: 'Exploits', - id: 'T1588.005', - reference: 'https://attack.mitre.org/techniques/T1588/005', - tactics: ['resource-development'], - techniqueId: 'T1588', - }, - { - name: 'External Defacement', - id: 'T1491.002', - reference: 'https://attack.mitre.org/techniques/T1491/002', - tactics: ['impact'], - techniqueId: 'T1491', - }, - { - name: 'External Proxy', - id: 'T1090.002', - reference: 'https://attack.mitre.org/techniques/T1090/002', - tactics: ['command-and-control'], - techniqueId: 'T1090', - }, - { - name: 'Extra Window Memory Injection', - id: 'T1055.011', - reference: 'https://attack.mitre.org/techniques/T1055/011', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Fast Flux DNS', - id: 'T1568.001', - reference: 'https://attack.mitre.org/techniques/T1568/001', - tactics: ['command-and-control'], - techniqueId: 'T1568', - }, - { - name: 'File Deletion', - id: 'T1070.004', - reference: 'https://attack.mitre.org/techniques/T1070/004', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'File Transfer Protocols', - id: 'T1071.002', - reference: 'https://attack.mitre.org/techniques/T1071/002', - tactics: ['command-and-control'], - techniqueId: 'T1071', - }, - { - name: 'Firmware', - id: 'T1592.003', - reference: 'https://attack.mitre.org/techniques/T1592/003', - tactics: ['reconnaissance'], - techniqueId: 'T1592', - }, - { - name: 'GUI Input Capture', - id: 'T1056.002', - reference: 'https://attack.mitre.org/techniques/T1056/002', - tactics: ['collection', 'credential-access'], - techniqueId: 'T1056', - }, - { - name: 'Gatekeeper Bypass', - id: 'T1553.001', - reference: 'https://attack.mitre.org/techniques/T1553/001', - tactics: ['defense-evasion'], - techniqueId: 'T1553', - }, - { - name: 'Golden Ticket', - id: 'T1558.001', - reference: 'https://attack.mitre.org/techniques/T1558/001', - tactics: ['credential-access'], - techniqueId: 'T1558', - }, - { - name: 'Group Policy Modification', - id: 'T1484.001', - reference: 'https://attack.mitre.org/techniques/T1484/001', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1484', - }, - { - name: 'Group Policy Preferences', - id: 'T1552.006', - reference: 'https://attack.mitre.org/techniques/T1552/006', - tactics: ['credential-access'], - techniqueId: 'T1552', - }, - { - name: 'HTML Smuggling', - id: 'T1027.006', - reference: 'https://attack.mitre.org/techniques/T1027/006', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Hardware', - id: 'T1592.001', - reference: 'https://attack.mitre.org/techniques/T1592/001', - tactics: ['reconnaissance'], - techniqueId: 'T1592', - }, - { - name: 'Hidden File System', - id: 'T1564.005', - reference: 'https://attack.mitre.org/techniques/T1564/005', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Hidden Files and Directories', - id: 'T1564.001', - reference: 'https://attack.mitre.org/techniques/T1564/001', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Hidden Users', - id: 'T1564.002', - reference: 'https://attack.mitre.org/techniques/T1564/002', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Hidden Window', - id: 'T1564.003', - reference: 'https://attack.mitre.org/techniques/T1564/003', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Hybrid Identity', - id: 'T1556.007', - reference: 'https://attack.mitre.org/techniques/T1556/007', - tactics: ['credential-access', 'defense-evasion', 'persistence'], - techniqueId: 'T1556', - }, - { - name: 'IIS Components', - id: 'T1505.004', - reference: 'https://attack.mitre.org/techniques/T1505/004', - tactics: ['persistence'], - techniqueId: 'T1505', - }, - { - name: 'IP Addresses', - id: 'T1590.005', - reference: 'https://attack.mitre.org/techniques/T1590/005', - tactics: ['reconnaissance'], - techniqueId: 'T1590', - }, - { - name: 'Identify Business Tempo', - id: 'T1591.003', - reference: 'https://attack.mitre.org/techniques/T1591/003', - tactics: ['reconnaissance'], - techniqueId: 'T1591', - }, - { - name: 'Identify Roles', - id: 'T1591.004', - reference: 'https://attack.mitre.org/techniques/T1591/004', - tactics: ['reconnaissance'], - techniqueId: 'T1591', - }, - { - name: 'Image File Execution Options Injection', - id: 'T1546.012', - reference: 'https://attack.mitre.org/techniques/T1546/012', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Impair Command History Logging', - id: 'T1562.003', - reference: 'https://attack.mitre.org/techniques/T1562/003', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Indicator Blocking', - id: 'T1562.006', - reference: 'https://attack.mitre.org/techniques/T1562/006', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Indicator Removal from Tools', - id: 'T1027.005', - reference: 'https://attack.mitre.org/techniques/T1027/005', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Install Digital Certificate', - id: 'T1608.003', - reference: 'https://attack.mitre.org/techniques/T1608/003', - tactics: ['resource-development'], - techniqueId: 'T1608', - }, - { - name: 'Install Root Certificate', - id: 'T1553.004', - reference: 'https://attack.mitre.org/techniques/T1553/004', - tactics: ['defense-evasion'], - techniqueId: 'T1553', - }, - { - name: 'InstallUtil', - id: 'T1218.004', - reference: 'https://attack.mitre.org/techniques/T1218/004', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Installer Packages', - id: 'T1546.016', - reference: 'https://attack.mitre.org/techniques/T1546/016', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Internal Defacement', - id: 'T1491.001', - reference: 'https://attack.mitre.org/techniques/T1491/001', - tactics: ['impact'], - techniqueId: 'T1491', - }, - { - name: 'Internal Proxy', - id: 'T1090.001', - reference: 'https://attack.mitre.org/techniques/T1090/001', - tactics: ['command-and-control'], - techniqueId: 'T1090', - }, - { - name: 'Internet Connection Discovery', - id: 'T1016.001', - reference: 'https://attack.mitre.org/techniques/T1016/001', - tactics: ['discovery'], - techniqueId: 'T1016', - }, - { - name: 'Invalid Code Signature', - id: 'T1036.001', - reference: 'https://attack.mitre.org/techniques/T1036/001', - tactics: ['defense-evasion'], - techniqueId: 'T1036', - }, - { - name: 'JavaScript', - id: 'T1059.007', - reference: 'https://attack.mitre.org/techniques/T1059/007', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'Junk Data', - id: 'T1001.001', - reference: 'https://attack.mitre.org/techniques/T1001/001', - tactics: ['command-and-control'], - techniqueId: 'T1001', - }, - { - name: 'Kerberoasting', - id: 'T1558.003', - reference: 'https://attack.mitre.org/techniques/T1558/003', - tactics: ['credential-access'], - techniqueId: 'T1558', - }, - { - name: 'Kernel Modules and Extensions', - id: 'T1547.006', - reference: 'https://attack.mitre.org/techniques/T1547/006', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'KernelCallbackTable', - id: 'T1574.013', - reference: 'https://attack.mitre.org/techniques/T1574/013', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Keychain', - id: 'T1555.001', - reference: 'https://attack.mitre.org/techniques/T1555/001', - tactics: ['credential-access'], - techniqueId: 'T1555', - }, - { - name: 'Keylogging', - id: 'T1056.001', - reference: 'https://attack.mitre.org/techniques/T1056/001', - tactics: ['collection', 'credential-access'], - techniqueId: 'T1056', - }, - { - name: 'LC_LOAD_DYLIB Addition', - id: 'T1546.006', - reference: 'https://attack.mitre.org/techniques/T1546/006', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'LLMNR/NBT-NS Poisoning and SMB Relay', - id: 'T1557.001', - reference: 'https://attack.mitre.org/techniques/T1557/001', - tactics: ['credential-access', 'collection'], - techniqueId: 'T1557', - }, - { - name: 'LSA Secrets', - id: 'T1003.004', - reference: 'https://attack.mitre.org/techniques/T1003/004', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'LSASS Driver', - id: 'T1547.008', - reference: 'https://attack.mitre.org/techniques/T1547/008', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'LSASS Memory', - id: 'T1003.001', - reference: 'https://attack.mitre.org/techniques/T1003/001', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'Launch Agent', - id: 'T1543.001', - reference: 'https://attack.mitre.org/techniques/T1543/001', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1543', - }, - { - name: 'Launch Daemon', - id: 'T1543.004', - reference: 'https://attack.mitre.org/techniques/T1543/004', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1543', - }, - { - name: 'Launchctl', - id: 'T1569.001', - reference: 'https://attack.mitre.org/techniques/T1569/001', - tactics: ['execution'], - techniqueId: 'T1569', - }, - { - name: 'Launchd', - id: 'T1053.004', - reference: 'https://attack.mitre.org/techniques/T1053/004', - tactics: ['execution', 'persistence', 'privilege-escalation'], - techniqueId: 'T1053', - }, - { - name: 'Link Target', - id: 'T1608.005', - reference: 'https://attack.mitre.org/techniques/T1608/005', - tactics: ['resource-development'], - techniqueId: 'T1608', - }, - { - name: 'Linux and Mac File and Directory Permissions Modification', - id: 'T1222.002', - reference: 'https://attack.mitre.org/techniques/T1222/002', - tactics: ['defense-evasion'], - techniqueId: 'T1222', - }, - { - name: 'ListPlanting', - id: 'T1055.015', - reference: 'https://attack.mitre.org/techniques/T1055/015', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Local Account', - id: 'T1087.001', - reference: 'https://attack.mitre.org/techniques/T1087/001', - tactics: ['discovery'], - techniqueId: 'T1087', - }, - { - name: 'Local Account', - id: 'T1136.001', - reference: 'https://attack.mitre.org/techniques/T1136/001', - tactics: ['persistence'], - techniqueId: 'T1136', - }, - { - name: 'Local Accounts', - id: 'T1078.003', - reference: 'https://attack.mitre.org/techniques/T1078/003', - tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], - techniqueId: 'T1078', - }, - { - name: 'Local Data Staging', - id: 'T1074.001', - reference: 'https://attack.mitre.org/techniques/T1074/001', - tactics: ['collection'], - techniqueId: 'T1074', - }, - { - name: 'Local Email Collection', - id: 'T1114.001', - reference: 'https://attack.mitre.org/techniques/T1114/001', - tactics: ['collection'], - techniqueId: 'T1114', - }, - { - name: 'Local Groups', - id: 'T1069.001', - reference: 'https://attack.mitre.org/techniques/T1069/001', - tactics: ['discovery'], - techniqueId: 'T1069', - }, - { - name: 'Login Hook', - id: 'T1037.002', - reference: 'https://attack.mitre.org/techniques/T1037/002', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1037', - }, - { - name: 'Login Items', - id: 'T1547.015', - reference: 'https://attack.mitre.org/techniques/T1547/015', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Logon Script (Windows)', - id: 'T1037.001', - reference: 'https://attack.mitre.org/techniques/T1037/001', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1037', - }, - { - name: 'MMC', - id: 'T1218.014', - reference: 'https://attack.mitre.org/techniques/T1218/014', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'MSBuild', - id: 'T1127.001', - reference: 'https://attack.mitre.org/techniques/T1127/001', - tactics: ['defense-evasion'], - techniqueId: 'T1127', - }, - { - name: 'Mail Protocols', - id: 'T1071.003', - reference: 'https://attack.mitre.org/techniques/T1071/003', - tactics: ['command-and-control'], - techniqueId: 'T1071', - }, - { - name: 'Make and Impersonate Token', - id: 'T1134.003', - reference: 'https://attack.mitre.org/techniques/T1134/003', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1134', - }, - { - name: 'Malicious File', - id: 'T1204.002', - reference: 'https://attack.mitre.org/techniques/T1204/002', - tactics: ['execution'], - techniqueId: 'T1204', - }, - { - name: 'Malicious Image', - id: 'T1204.003', - reference: 'https://attack.mitre.org/techniques/T1204/003', - tactics: ['execution'], - techniqueId: 'T1204', - }, - { - name: 'Malicious Link', - id: 'T1204.001', - reference: 'https://attack.mitre.org/techniques/T1204/001', - tactics: ['execution'], - techniqueId: 'T1204', - }, - { - name: 'Malware', - id: 'T1587.001', - reference: 'https://attack.mitre.org/techniques/T1587/001', - tactics: ['resource-development'], - techniqueId: 'T1587', - }, - { - name: 'Malware', - id: 'T1588.001', - reference: 'https://attack.mitre.org/techniques/T1588/001', - tactics: ['resource-development'], - techniqueId: 'T1588', - }, - { - name: 'Mark-of-the-Web Bypass', - id: 'T1553.005', - reference: 'https://attack.mitre.org/techniques/T1553/005', - tactics: ['defense-evasion'], - techniqueId: 'T1553', - }, - { - name: 'Masquerade Task or Service', - id: 'T1036.004', - reference: 'https://attack.mitre.org/techniques/T1036/004', - tactics: ['defense-evasion'], - techniqueId: 'T1036', - }, - { - name: 'Match Legitimate Name or Location', - id: 'T1036.005', - reference: 'https://attack.mitre.org/techniques/T1036/005', - tactics: ['defense-evasion'], - techniqueId: 'T1036', - }, - { - name: 'Mavinject', - id: 'T1218.013', - reference: 'https://attack.mitre.org/techniques/T1218/013', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Mshta', - id: 'T1218.005', - reference: 'https://attack.mitre.org/techniques/T1218/005', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Msiexec', - id: 'T1218.007', - reference: 'https://attack.mitre.org/techniques/T1218/007', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Multi-Factor Authentication', - id: 'T1556.006', - reference: 'https://attack.mitre.org/techniques/T1556/006', - tactics: ['credential-access', 'defense-evasion', 'persistence'], - techniqueId: 'T1556', - }, - { - name: 'Multi-hop Proxy', - id: 'T1090.003', - reference: 'https://attack.mitre.org/techniques/T1090/003', - tactics: ['command-and-control'], - techniqueId: 'T1090', - }, - { - name: 'NTDS', - id: 'T1003.003', - reference: 'https://attack.mitre.org/techniques/T1003/003', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'NTFS File Attributes', - id: 'T1564.004', - reference: 'https://attack.mitre.org/techniques/T1564/004', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Netsh Helper DLL', - id: 'T1546.007', - reference: 'https://attack.mitre.org/techniques/T1546/007', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Network Address Translation Traversal', - id: 'T1599.001', - reference: 'https://attack.mitre.org/techniques/T1599/001', - tactics: ['defense-evasion'], - techniqueId: 'T1599', - }, - { - name: 'Network Device Authentication', - id: 'T1556.004', - reference: 'https://attack.mitre.org/techniques/T1556/004', - tactics: ['credential-access', 'defense-evasion', 'persistence'], - techniqueId: 'T1556', - }, - { - name: 'Network Device CLI', - id: 'T1059.008', - reference: 'https://attack.mitre.org/techniques/T1059/008', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'Network Device Configuration Dump', - id: 'T1602.002', - reference: 'https://attack.mitre.org/techniques/T1602/002', - tactics: ['collection'], - techniqueId: 'T1602', - }, - { - name: 'Network Logon Script', - id: 'T1037.003', - reference: 'https://attack.mitre.org/techniques/T1037/003', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1037', - }, - { - name: 'Network Security Appliances', - id: 'T1590.006', - reference: 'https://attack.mitre.org/techniques/T1590/006', - tactics: ['reconnaissance'], - techniqueId: 'T1590', - }, - { - name: 'Network Share Connection Removal', - id: 'T1070.005', - reference: 'https://attack.mitre.org/techniques/T1070/005', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Network Topology', - id: 'T1590.004', - reference: 'https://attack.mitre.org/techniques/T1590/004', - tactics: ['reconnaissance'], - techniqueId: 'T1590', - }, - { - name: 'Network Trust Dependencies', - id: 'T1590.003', - reference: 'https://attack.mitre.org/techniques/T1590/003', - tactics: ['reconnaissance'], - techniqueId: 'T1590', - }, - { - name: 'Non-Standard Encoding', - id: 'T1132.002', - reference: 'https://attack.mitre.org/techniques/T1132/002', - tactics: ['command-and-control'], - techniqueId: 'T1132', - }, - { - name: 'OS Exhaustion Flood', - id: 'T1499.001', - reference: 'https://attack.mitre.org/techniques/T1499/001', - tactics: ['impact'], - techniqueId: 'T1499', - }, - { - name: 'Odbcconf', - id: 'T1218.008', - reference: 'https://attack.mitre.org/techniques/T1218/008', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Office Template Macros', - id: 'T1137.001', - reference: 'https://attack.mitre.org/techniques/T1137/001', - tactics: ['persistence'], - techniqueId: 'T1137', - }, - { - name: 'Office Test', - id: 'T1137.002', - reference: 'https://attack.mitre.org/techniques/T1137/002', - tactics: ['persistence'], - techniqueId: 'T1137', - }, - { - name: 'One-Way Communication', - id: 'T1102.003', - reference: 'https://attack.mitre.org/techniques/T1102/003', - tactics: ['command-and-control'], - techniqueId: 'T1102', - }, - { - name: 'Outlook Forms', - id: 'T1137.003', - reference: 'https://attack.mitre.org/techniques/T1137/003', - tactics: ['persistence'], - techniqueId: 'T1137', - }, - { - name: 'Outlook Home Page', - id: 'T1137.004', - reference: 'https://attack.mitre.org/techniques/T1137/004', - tactics: ['persistence'], - techniqueId: 'T1137', - }, - { - name: 'Outlook Rules', - id: 'T1137.005', - reference: 'https://attack.mitre.org/techniques/T1137/005', - tactics: ['persistence'], - techniqueId: 'T1137', - }, - { - name: 'Parent PID Spoofing', - id: 'T1134.004', - reference: 'https://attack.mitre.org/techniques/T1134/004', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1134', - }, - { - name: 'Pass the Hash', - id: 'T1550.002', - reference: 'https://attack.mitre.org/techniques/T1550/002', - tactics: ['defense-evasion', 'lateral-movement'], - techniqueId: 'T1550', - }, - { - name: 'Pass the Ticket', - id: 'T1550.003', - reference: 'https://attack.mitre.org/techniques/T1550/003', - tactics: ['defense-evasion', 'lateral-movement'], - techniqueId: 'T1550', - }, - { - name: 'Password Cracking', - id: 'T1110.002', - reference: 'https://attack.mitre.org/techniques/T1110/002', - tactics: ['credential-access'], - techniqueId: 'T1110', - }, - { - name: 'Password Filter DLL', - id: 'T1556.002', - reference: 'https://attack.mitre.org/techniques/T1556/002', - tactics: ['credential-access', 'defense-evasion', 'persistence'], - techniqueId: 'T1556', - }, - { - name: 'Password Guessing', - id: 'T1110.001', - reference: 'https://attack.mitre.org/techniques/T1110/001', - tactics: ['credential-access'], - techniqueId: 'T1110', - }, - { - name: 'Password Managers', - id: 'T1555.005', - reference: 'https://attack.mitre.org/techniques/T1555/005', - tactics: ['credential-access'], - techniqueId: 'T1555', - }, - { - name: 'Password Spraying', - id: 'T1110.003', - reference: 'https://attack.mitre.org/techniques/T1110/003', - tactics: ['credential-access'], - techniqueId: 'T1110', - }, - { - name: 'Patch System Image', - id: 'T1601.001', - reference: 'https://attack.mitre.org/techniques/T1601/001', - tactics: ['defense-evasion'], - techniqueId: 'T1601', - }, - { - name: 'Path Interception by PATH Environment Variable', - id: 'T1574.007', - reference: 'https://attack.mitre.org/techniques/T1574/007', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Path Interception by Search Order Hijacking', - id: 'T1574.008', - reference: 'https://attack.mitre.org/techniques/T1574/008', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Path Interception by Unquoted Path', - id: 'T1574.009', - reference: 'https://attack.mitre.org/techniques/T1574/009', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Plist Modification', - id: 'T1547.011', - reference: 'https://attack.mitre.org/techniques/T1547/011', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Pluggable Authentication Modules', - id: 'T1556.003', - reference: 'https://attack.mitre.org/techniques/T1556/003', - tactics: ['credential-access', 'defense-evasion', 'persistence'], - techniqueId: 'T1556', - }, - { - name: 'Port Knocking', - id: 'T1205.001', - reference: 'https://attack.mitre.org/techniques/T1205/001', - tactics: ['defense-evasion', 'persistence', 'command-and-control'], - techniqueId: 'T1205', - }, - { - name: 'Port Monitors', - id: 'T1547.010', - reference: 'https://attack.mitre.org/techniques/T1547/010', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Portable Executable Injection', - id: 'T1055.002', - reference: 'https://attack.mitre.org/techniques/T1055/002', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'PowerShell', - id: 'T1059.001', - reference: 'https://attack.mitre.org/techniques/T1059/001', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'PowerShell Profile', - id: 'T1546.013', - reference: 'https://attack.mitre.org/techniques/T1546/013', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Print Processors', - id: 'T1547.012', - reference: 'https://attack.mitre.org/techniques/T1547/012', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Private Keys', - id: 'T1552.004', - reference: 'https://attack.mitre.org/techniques/T1552/004', - tactics: ['credential-access'], - techniqueId: 'T1552', - }, - { - name: 'Proc Filesystem', - id: 'T1003.007', - reference: 'https://attack.mitre.org/techniques/T1003/007', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'Proc Memory', - id: 'T1055.009', - reference: 'https://attack.mitre.org/techniques/T1055/009', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Process Argument Spoofing', - id: 'T1564.010', - reference: 'https://attack.mitre.org/techniques/T1564/010', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Process Doppelgänging', - id: 'T1055.013', - reference: 'https://attack.mitre.org/techniques/T1055/013', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Process Hollowing', - id: 'T1055.012', - reference: 'https://attack.mitre.org/techniques/T1055/012', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Protocol Impersonation', - id: 'T1001.003', - reference: 'https://attack.mitre.org/techniques/T1001/003', - tactics: ['command-and-control'], - techniqueId: 'T1001', - }, - { - name: 'Ptrace System Calls', - id: 'T1055.008', - reference: 'https://attack.mitre.org/techniques/T1055/008', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'PubPrn', - id: 'T1216.001', - reference: 'https://attack.mitre.org/techniques/T1216/001', - tactics: ['defense-evasion'], - techniqueId: 'T1216', - }, - { - name: 'Purchase Technical Data', - id: 'T1597.002', - reference: 'https://attack.mitre.org/techniques/T1597/002', - tactics: ['reconnaissance'], - techniqueId: 'T1597', - }, - { - name: 'Python', - id: 'T1059.006', - reference: 'https://attack.mitre.org/techniques/T1059/006', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'RC Scripts', - id: 'T1037.004', - reference: 'https://attack.mitre.org/techniques/T1037/004', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1037', - }, - { - name: 'RDP Hijacking', - id: 'T1563.002', - reference: 'https://attack.mitre.org/techniques/T1563/002', - tactics: ['lateral-movement'], - techniqueId: 'T1563', - }, - { - name: 'ROMMONkit', - id: 'T1542.004', - reference: 'https://attack.mitre.org/techniques/T1542/004', - tactics: ['defense-evasion', 'persistence'], - techniqueId: 'T1542', - }, - { - name: 'Re-opened Applications', - id: 'T1547.007', - reference: 'https://attack.mitre.org/techniques/T1547/007', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Reduce Key Space', - id: 'T1600.001', - reference: 'https://attack.mitre.org/techniques/T1600/001', - tactics: ['defense-evasion'], - techniqueId: 'T1600', - }, - { - name: 'Reflection Amplification', - id: 'T1498.002', - reference: 'https://attack.mitre.org/techniques/T1498/002', - tactics: ['impact'], - techniqueId: 'T1498', - }, - { - name: 'Registry Run Keys / Startup Folder', - id: 'T1547.001', - reference: 'https://attack.mitre.org/techniques/T1547/001', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Regsvcs/Regasm', - id: 'T1218.009', - reference: 'https://attack.mitre.org/techniques/T1218/009', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Regsvr32', - id: 'T1218.010', - reference: 'https://attack.mitre.org/techniques/T1218/010', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Remote Data Staging', - id: 'T1074.002', - reference: 'https://attack.mitre.org/techniques/T1074/002', - tactics: ['collection'], - techniqueId: 'T1074', - }, - { - name: 'Remote Desktop Protocol', - id: 'T1021.001', - reference: 'https://attack.mitre.org/techniques/T1021/001', - tactics: ['lateral-movement'], - techniqueId: 'T1021', - }, - { - name: 'Remote Email Collection', - id: 'T1114.002', - reference: 'https://attack.mitre.org/techniques/T1114/002', - tactics: ['collection'], - techniqueId: 'T1114', - }, - { - name: 'Rename System Utilities', - id: 'T1036.003', - reference: 'https://attack.mitre.org/techniques/T1036/003', - tactics: ['defense-evasion'], - techniqueId: 'T1036', - }, - { - name: 'Resource Forking', - id: 'T1564.009', - reference: 'https://attack.mitre.org/techniques/T1564/009', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Reversible Encryption', - id: 'T1556.005', - reference: 'https://attack.mitre.org/techniques/T1556/005', - tactics: ['credential-access', 'defense-evasion', 'persistence'], - techniqueId: 'T1556', - }, - { - name: 'Revert Cloud Instance', - id: 'T1578.004', - reference: 'https://attack.mitre.org/techniques/T1578/004', - tactics: ['defense-evasion'], - techniqueId: 'T1578', - }, - { - name: 'Right-to-Left Override', - id: 'T1036.002', - reference: 'https://attack.mitre.org/techniques/T1036/002', - tactics: ['defense-evasion'], - techniqueId: 'T1036', - }, - { - name: 'Run Virtual Instance', - id: 'T1564.006', - reference: 'https://attack.mitre.org/techniques/T1564/006', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'Rundll32', - id: 'T1218.011', - reference: 'https://attack.mitre.org/techniques/T1218/011', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Runtime Data Manipulation', - id: 'T1565.003', - reference: 'https://attack.mitre.org/techniques/T1565/003', - tactics: ['impact'], - techniqueId: 'T1565', - }, - { - name: 'SAML Tokens', - id: 'T1606.002', - reference: 'https://attack.mitre.org/techniques/T1606/002', - tactics: ['credential-access'], - techniqueId: 'T1606', - }, - { - name: 'SEO Poisoning', - id: 'T1608.006', - reference: 'https://attack.mitre.org/techniques/T1608/006', - tactics: ['resource-development'], - techniqueId: 'T1608', - }, - { - name: 'SID-History Injection', - id: 'T1134.005', - reference: 'https://attack.mitre.org/techniques/T1134/005', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1134', - }, - { - name: 'SIP and Trust Provider Hijacking', - id: 'T1553.003', - reference: 'https://attack.mitre.org/techniques/T1553/003', - tactics: ['defense-evasion'], - techniqueId: 'T1553', - }, - { - name: 'SMB/Windows Admin Shares', - id: 'T1021.002', - reference: 'https://attack.mitre.org/techniques/T1021/002', - tactics: ['lateral-movement'], - techniqueId: 'T1021', - }, - { - name: 'SNMP (MIB Dump)', - id: 'T1602.001', - reference: 'https://attack.mitre.org/techniques/T1602/001', - tactics: ['collection'], - techniqueId: 'T1602', - }, - { - name: 'SQL Stored Procedures', - id: 'T1505.001', - reference: 'https://attack.mitre.org/techniques/T1505/001', - tactics: ['persistence'], - techniqueId: 'T1505', - }, - { - name: 'SSH', - id: 'T1021.004', - reference: 'https://attack.mitre.org/techniques/T1021/004', - tactics: ['lateral-movement'], - techniqueId: 'T1021', - }, - { - name: 'SSH Authorized Keys', - id: 'T1098.004', - reference: 'https://attack.mitre.org/techniques/T1098/004', - tactics: ['persistence'], - techniqueId: 'T1098', - }, - { - name: 'SSH Hijacking', - id: 'T1563.001', - reference: 'https://attack.mitre.org/techniques/T1563/001', - tactics: ['lateral-movement'], - techniqueId: 'T1563', - }, - { - name: 'Safe Mode Boot', - id: 'T1562.009', - reference: 'https://attack.mitre.org/techniques/T1562/009', - tactics: ['defense-evasion'], - techniqueId: 'T1562', - }, - { - name: 'Scan Databases', - id: 'T1596.005', - reference: 'https://attack.mitre.org/techniques/T1596/005', - tactics: ['reconnaissance'], - techniqueId: 'T1596', - }, - { - name: 'Scanning IP Blocks', - id: 'T1595.001', - reference: 'https://attack.mitre.org/techniques/T1595/001', - tactics: ['reconnaissance'], - techniqueId: 'T1595', - }, - { - name: 'Scheduled Task', - id: 'T1053.005', - reference: 'https://attack.mitre.org/techniques/T1053/005', - tactics: ['execution', 'persistence', 'privilege-escalation'], - techniqueId: 'T1053', - }, - { - name: 'Screensaver', - id: 'T1546.002', - reference: 'https://attack.mitre.org/techniques/T1546/002', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Search Engines', - id: 'T1593.002', - reference: 'https://attack.mitre.org/techniques/T1593/002', - tactics: ['reconnaissance'], - techniqueId: 'T1593', - }, - { - name: 'Security Account Manager', - id: 'T1003.002', - reference: 'https://attack.mitre.org/techniques/T1003/002', - tactics: ['credential-access'], - techniqueId: 'T1003', - }, - { - name: 'Security Software Discovery', - id: 'T1518.001', - reference: 'https://attack.mitre.org/techniques/T1518/001', - tactics: ['discovery'], - techniqueId: 'T1518', - }, - { - name: 'Security Support Provider', - id: 'T1547.005', - reference: 'https://attack.mitre.org/techniques/T1547/005', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Securityd Memory', - id: 'T1555.002', - reference: 'https://attack.mitre.org/techniques/T1555/002', - tactics: ['credential-access'], - techniqueId: 'T1555', - }, - { - name: 'Server', - id: 'T1583.004', - reference: 'https://attack.mitre.org/techniques/T1583/004', - tactics: ['resource-development'], - techniqueId: 'T1583', - }, - { - name: 'Server', - id: 'T1584.004', - reference: 'https://attack.mitre.org/techniques/T1584/004', - tactics: ['resource-development'], - techniqueId: 'T1584', - }, - { - name: 'Serverless', - id: 'T1583.007', - reference: 'https://attack.mitre.org/techniques/T1583/007', - tactics: ['resource-development'], - techniqueId: 'T1583', - }, - { - name: 'Serverless', - id: 'T1584.007', - reference: 'https://attack.mitre.org/techniques/T1584/007', - tactics: ['resource-development'], - techniqueId: 'T1584', - }, - { - name: 'Service Execution', - id: 'T1569.002', - reference: 'https://attack.mitre.org/techniques/T1569/002', - tactics: ['execution'], - techniqueId: 'T1569', - }, - { - name: 'Service Exhaustion Flood', - id: 'T1499.002', - reference: 'https://attack.mitre.org/techniques/T1499/002', - tactics: ['impact'], - techniqueId: 'T1499', - }, - { - name: 'Services File Permissions Weakness', - id: 'T1574.010', - reference: 'https://attack.mitre.org/techniques/T1574/010', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Services Registry Permissions Weakness', - id: 'T1574.011', - reference: 'https://attack.mitre.org/techniques/T1574/011', - tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], - techniqueId: 'T1574', - }, - { - name: 'Setuid and Setgid', - id: 'T1548.001', - reference: 'https://attack.mitre.org/techniques/T1548/001', - tactics: ['privilege-escalation', 'defense-evasion'], - techniqueId: 'T1548', - }, - { - name: 'Sharepoint', - id: 'T1213.002', - reference: 'https://attack.mitre.org/techniques/T1213/002', - tactics: ['collection'], - techniqueId: 'T1213', - }, - { - name: 'Shortcut Modification', - id: 'T1547.009', - reference: 'https://attack.mitre.org/techniques/T1547/009', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Silver Ticket', - id: 'T1558.002', - reference: 'https://attack.mitre.org/techniques/T1558/002', - tactics: ['credential-access'], - techniqueId: 'T1558', - }, - { - name: 'Social Media', - id: 'T1593.001', - reference: 'https://attack.mitre.org/techniques/T1593/001', - tactics: ['reconnaissance'], - techniqueId: 'T1593', - }, - { - name: 'Social Media Accounts', - id: 'T1586.001', - reference: 'https://attack.mitre.org/techniques/T1586/001', - tactics: ['resource-development'], - techniqueId: 'T1586', - }, - { - name: 'Social Media Accounts', - id: 'T1585.001', - reference: 'https://attack.mitre.org/techniques/T1585/001', - tactics: ['resource-development'], - techniqueId: 'T1585', - }, - { - name: 'Socket Filters', - id: 'T1205.002', - reference: 'https://attack.mitre.org/techniques/T1205/002', - tactics: ['defense-evasion', 'persistence', 'command-and-control'], - techniqueId: 'T1205', - }, - { - name: 'Software', - id: 'T1592.002', - reference: 'https://attack.mitre.org/techniques/T1592/002', - tactics: ['reconnaissance'], - techniqueId: 'T1592', - }, - { - name: 'Software Packing', - id: 'T1027.002', - reference: 'https://attack.mitre.org/techniques/T1027/002', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Space after Filename', - id: 'T1036.006', - reference: 'https://attack.mitre.org/techniques/T1036/006', - tactics: ['defense-evasion'], - techniqueId: 'T1036', - }, - { - name: 'Spearphishing Attachment', - id: 'T1566.001', - reference: 'https://attack.mitre.org/techniques/T1566/001', - tactics: ['initial-access'], - techniqueId: 'T1566', - }, - { - name: 'Spearphishing Attachment', - id: 'T1598.002', - reference: 'https://attack.mitre.org/techniques/T1598/002', - tactics: ['reconnaissance'], - techniqueId: 'T1598', - }, - { - name: 'Spearphishing Link', - id: 'T1566.002', - reference: 'https://attack.mitre.org/techniques/T1566/002', - tactics: ['initial-access'], - techniqueId: 'T1566', - }, - { - name: 'Spearphishing Link', - id: 'T1598.003', - reference: 'https://attack.mitre.org/techniques/T1598/003', - tactics: ['reconnaissance'], - techniqueId: 'T1598', - }, - { - name: 'Spearphishing Service', - id: 'T1598.001', - reference: 'https://attack.mitre.org/techniques/T1598/001', - tactics: ['reconnaissance'], - techniqueId: 'T1598', - }, - { - name: 'Spearphishing via Service', - id: 'T1566.003', - reference: 'https://attack.mitre.org/techniques/T1566/003', - tactics: ['initial-access'], - techniqueId: 'T1566', - }, - { - name: 'Standard Encoding', - id: 'T1132.001', - reference: 'https://attack.mitre.org/techniques/T1132/001', - tactics: ['command-and-control'], - techniqueId: 'T1132', - }, - { - name: 'Startup Items', - id: 'T1037.005', - reference: 'https://attack.mitre.org/techniques/T1037/005', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1037', - }, - { - name: 'Steganography', - id: 'T1027.003', - reference: 'https://attack.mitre.org/techniques/T1027/003', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Steganography', - id: 'T1001.002', - reference: 'https://attack.mitre.org/techniques/T1001/002', - tactics: ['command-and-control'], - techniqueId: 'T1001', - }, - { - name: 'Stored Data Manipulation', - id: 'T1565.001', - reference: 'https://attack.mitre.org/techniques/T1565/001', - tactics: ['impact'], - techniqueId: 'T1565', - }, - { - name: 'Stripped Payloads', - id: 'T1027.008', - reference: 'https://attack.mitre.org/techniques/T1027/008', - tactics: ['defense-evasion'], - techniqueId: 'T1027', - }, - { - name: 'Sudo and Sudo Caching', - id: 'T1548.003', - reference: 'https://attack.mitre.org/techniques/T1548/003', - tactics: ['privilege-escalation', 'defense-evasion'], - techniqueId: 'T1548', - }, - { - name: 'Symmetric Cryptography', - id: 'T1573.001', - reference: 'https://attack.mitre.org/techniques/T1573/001', - tactics: ['command-and-control'], - techniqueId: 'T1573', - }, - { - name: 'System Checks', - id: 'T1497.001', - reference: 'https://attack.mitre.org/techniques/T1497/001', - tactics: ['defense-evasion', 'discovery'], - techniqueId: 'T1497', - }, - { - name: 'System Firmware', - id: 'T1542.001', - reference: 'https://attack.mitre.org/techniques/T1542/001', - tactics: ['persistence', 'defense-evasion'], - techniqueId: 'T1542', - }, - { - name: 'System Language Discovery', - id: 'T1614.001', - reference: 'https://attack.mitre.org/techniques/T1614/001', - tactics: ['discovery'], - techniqueId: 'T1614', - }, - { - name: 'Systemd Service', - id: 'T1543.002', - reference: 'https://attack.mitre.org/techniques/T1543/002', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1543', - }, - { - name: 'Systemd Timers', - id: 'T1053.006', - reference: 'https://attack.mitre.org/techniques/T1053/006', - tactics: ['execution', 'persistence', 'privilege-escalation'], - techniqueId: 'T1053', - }, - { - name: 'TFTP Boot', - id: 'T1542.005', - reference: 'https://attack.mitre.org/techniques/T1542/005', - tactics: ['defense-evasion', 'persistence'], - techniqueId: 'T1542', - }, - { - name: 'Terminal Services DLL', - id: 'T1505.005', - reference: 'https://attack.mitre.org/techniques/T1505/005', - tactics: ['persistence'], - techniqueId: 'T1505', - }, - { - name: 'Thread Execution Hijacking', - id: 'T1055.003', - reference: 'https://attack.mitre.org/techniques/T1055/003', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Thread Local Storage', - id: 'T1055.005', - reference: 'https://attack.mitre.org/techniques/T1055/005', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'Threat Intel Vendors', - id: 'T1597.001', - reference: 'https://attack.mitre.org/techniques/T1597/001', - tactics: ['reconnaissance'], - techniqueId: 'T1597', - }, - { - name: 'Time Based Evasion', - id: 'T1497.003', - reference: 'https://attack.mitre.org/techniques/T1497/003', - tactics: ['defense-evasion', 'discovery'], - techniqueId: 'T1497', - }, - { - name: 'Time Providers', - id: 'T1547.003', - reference: 'https://attack.mitre.org/techniques/T1547/003', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', - }, - { - name: 'Timestomp', - id: 'T1070.006', - reference: 'https://attack.mitre.org/techniques/T1070/006', - tactics: ['defense-evasion'], - techniqueId: 'T1070', - }, - { - name: 'Token Impersonation/Theft', - id: 'T1134.001', - reference: 'https://attack.mitre.org/techniques/T1134/001', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1134', - }, - { - name: 'Tool', - id: 'T1588.002', - reference: 'https://attack.mitre.org/techniques/T1588/002', - tactics: ['resource-development'], - techniqueId: 'T1588', - }, - { - name: 'Traffic Duplication', - id: 'T1020.001', - reference: 'https://attack.mitre.org/techniques/T1020/001', - tactics: ['exfiltration'], - techniqueId: 'T1020', - }, - { - name: 'Transmitted Data Manipulation', - id: 'T1565.002', - reference: 'https://attack.mitre.org/techniques/T1565/002', - tactics: ['impact'], - techniqueId: 'T1565', - }, - { - name: 'Transport Agent', - id: 'T1505.002', - reference: 'https://attack.mitre.org/techniques/T1505/002', - tactics: ['persistence'], - techniqueId: 'T1505', - }, - { - name: 'Trap', - id: 'T1546.005', - reference: 'https://attack.mitre.org/techniques/T1546/005', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Unix Shell', - id: 'T1059.004', - reference: 'https://attack.mitre.org/techniques/T1059/004', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'Unix Shell Configuration Modification', - id: 'T1546.004', - reference: 'https://attack.mitre.org/techniques/T1546/004', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Upload Malware', - id: 'T1608.001', - reference: 'https://attack.mitre.org/techniques/T1608/001', - tactics: ['resource-development'], - techniqueId: 'T1608', - }, - { - name: 'Upload Tool', - id: 'T1608.002', - reference: 'https://attack.mitre.org/techniques/T1608/002', - tactics: ['resource-development'], - techniqueId: 'T1608', - }, - { - name: 'User Activity Based Checks', - id: 'T1497.002', - reference: 'https://attack.mitre.org/techniques/T1497/002', - tactics: ['defense-evasion', 'discovery'], - techniqueId: 'T1497', - }, - { - name: 'VBA Stomping', - id: 'T1564.007', - reference: 'https://attack.mitre.org/techniques/T1564/007', - tactics: ['defense-evasion'], - techniqueId: 'T1564', - }, - { - name: 'VDSO Hijacking', - id: 'T1055.014', - reference: 'https://attack.mitre.org/techniques/T1055/014', - tactics: ['defense-evasion', 'privilege-escalation'], - techniqueId: 'T1055', - }, - { - name: 'VNC', - id: 'T1021.005', - reference: 'https://attack.mitre.org/techniques/T1021/005', - tactics: ['lateral-movement'], - techniqueId: 'T1021', - }, - { - name: 'Verclsid', - id: 'T1218.012', - reference: 'https://attack.mitre.org/techniques/T1218/012', - tactics: ['defense-evasion'], - techniqueId: 'T1218', - }, - { - name: 'Virtual Private Server', - id: 'T1584.003', - reference: 'https://attack.mitre.org/techniques/T1584/003', - tactics: ['resource-development'], - techniqueId: 'T1584', - }, - { - name: 'Virtual Private Server', - id: 'T1583.003', - reference: 'https://attack.mitre.org/techniques/T1583/003', - tactics: ['resource-development'], - techniqueId: 'T1583', - }, - { - name: 'Visual Basic', - id: 'T1059.005', - reference: 'https://attack.mitre.org/techniques/T1059/005', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'Vulnerabilities', - id: 'T1588.006', - reference: 'https://attack.mitre.org/techniques/T1588/006', - tactics: ['resource-development'], - techniqueId: 'T1588', - }, - { - name: 'Vulnerability Scanning', - id: 'T1595.002', - reference: 'https://attack.mitre.org/techniques/T1595/002', - tactics: ['reconnaissance'], - techniqueId: 'T1595', - }, - { - name: 'WHOIS', - id: 'T1596.002', - reference: 'https://attack.mitre.org/techniques/T1596/002', - tactics: ['reconnaissance'], - techniqueId: 'T1596', - }, - { - name: 'Web Cookies', - id: 'T1606.001', - reference: 'https://attack.mitre.org/techniques/T1606/001', - tactics: ['credential-access'], - techniqueId: 'T1606', - }, - { - name: 'Web Portal Capture', - id: 'T1056.003', - reference: 'https://attack.mitre.org/techniques/T1056/003', - tactics: ['collection', 'credential-access'], - techniqueId: 'T1056', - }, - { - name: 'Web Protocols', - id: 'T1071.001', - reference: 'https://attack.mitre.org/techniques/T1071/001', - tactics: ['command-and-control'], - techniqueId: 'T1071', - }, - { - name: 'Web Services', - id: 'T1583.006', - reference: 'https://attack.mitre.org/techniques/T1583/006', - tactics: ['resource-development'], - techniqueId: 'T1583', - }, - { - name: 'Web Services', - id: 'T1584.006', - reference: 'https://attack.mitre.org/techniques/T1584/006', - tactics: ['resource-development'], - techniqueId: 'T1584', - }, - { - name: 'Web Session Cookie', - id: 'T1550.004', - reference: 'https://attack.mitre.org/techniques/T1550/004', - tactics: ['defense-evasion', 'lateral-movement'], - techniqueId: 'T1550', - }, - { - name: 'Web Shell', - id: 'T1505.003', - reference: 'https://attack.mitre.org/techniques/T1505/003', - tactics: ['persistence'], - techniqueId: 'T1505', - }, - { - name: 'Windows Command Shell', - id: 'T1059.003', - reference: 'https://attack.mitre.org/techniques/T1059/003', - tactics: ['execution'], - techniqueId: 'T1059', - }, - { - name: 'Windows Credential Manager', - id: 'T1555.004', - reference: 'https://attack.mitre.org/techniques/T1555/004', - tactics: ['credential-access'], - techniqueId: 'T1555', - }, - { - name: 'Windows File and Directory Permissions Modification', - id: 'T1222.001', - reference: 'https://attack.mitre.org/techniques/T1222/001', - tactics: ['defense-evasion'], - techniqueId: 'T1222', - }, - { - name: 'Windows Management Instrumentation Event Subscription', - id: 'T1546.003', - reference: 'https://attack.mitre.org/techniques/T1546/003', - tactics: ['privilege-escalation', 'persistence'], - techniqueId: 'T1546', - }, - { - name: 'Windows Remote Management', - id: 'T1021.006', - reference: 'https://attack.mitre.org/techniques/T1021/006', - tactics: ['lateral-movement'], - techniqueId: 'T1021', - }, - { - name: 'Windows Service', - id: 'T1543.003', - reference: 'https://attack.mitre.org/techniques/T1543/003', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1543', - }, - { - name: 'Winlogon Helper DLL', - id: 'T1547.004', - reference: 'https://attack.mitre.org/techniques/T1547/004', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationEventSubscriptionDescription', + { defaultMessage: 'Windows Management Instrumentation Event Subscription (T1084)' } + ), + id: 'T1084', + name: 'Windows Management Instrumentation Event Subscription', + reference: 'https://attack.mitre.org/techniques/T1084', + tactics: ['persistence'], + value: 'windowsManagementInstrumentationEventSubscription', }, { - name: 'Wordlist Scanning', - id: 'T1595.003', - reference: 'https://attack.mitre.org/techniques/T1595/003', - tactics: ['reconnaissance'], - techniqueId: 'T1595', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.windowsRemoteManagementDescription', + { defaultMessage: 'Windows Remote Management (T1028)' } + ), + id: 'T1028', + name: 'Windows Remote Management', + reference: 'https://attack.mitre.org/techniques/T1028', + tactics: ['execution', 'lateral-movement'], + value: 'windowsRemoteManagement', }, { - name: 'XDG Autostart Entries', - id: 'T1547.013', - reference: 'https://attack.mitre.org/techniques/T1547/013', - tactics: ['persistence', 'privilege-escalation'], - techniqueId: 'T1547', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.winlogonHelperDllDescription', + { defaultMessage: 'Winlogon Helper DLL (T1004)' } + ), + id: 'T1004', + name: 'Winlogon Helper DLL', + reference: 'https://attack.mitre.org/techniques/T1004', + tactics: ['persistence'], + value: 'winlogonHelperDll', }, { - name: 'XPC Services', - id: 'T1559.003', - reference: 'https://attack.mitre.org/techniques/T1559/003', - tactics: ['execution'], - techniqueId: 'T1559', + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.xslScriptProcessingDescription', + { defaultMessage: 'XSL Script Processing (T1220)' } + ), + id: 'T1220', + name: 'XSL Script Processing', + reference: 'https://attack.mitre.org/techniques/T1220', + tactics: ['defense-evasion'], + value: 'xslScriptProcessing', }, ]; -export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ +export const subtechniques: MitreSubTechnique[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.etcPasswdAndEtcShadowT1003Description', @@ -8740,7 +3864,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.008', name: '/etc/passwd and /etc/shadow', reference: 'https://attack.mitre.org/techniques/T1003/008', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'etcPasswdAndEtcShadow', }, @@ -8752,7 +3876,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1557.002', name: 'ARP Cache Poisoning', reference: 'https://attack.mitre.org/techniques/T1557/002', - tactics: 'credential-access,collection', + tactics: ['credential-access', 'collection'], techniqueId: 'T1557', value: 'arpCachePoisoning', }, @@ -8764,7 +3888,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1558.004', name: 'AS-REP Roasting', reference: 'https://attack.mitre.org/techniques/T1558/004', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1558', value: 'asRepRoasting', }, @@ -8776,7 +3900,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.008', name: 'Accessibility Features', reference: 'https://attack.mitre.org/techniques/T1546/008', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'accessibilityFeatures', }, @@ -8788,7 +3912,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.014', name: 'Active Setup', reference: 'https://attack.mitre.org/techniques/T1547/014', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'activeSetup', }, @@ -8800,7 +3924,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1137.006', name: 'Add-ins', reference: 'https://attack.mitre.org/techniques/T1137/006', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1137', value: 'addIns', }, @@ -8812,7 +3936,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1098.001', name: 'Additional Cloud Credentials', reference: 'https://attack.mitre.org/techniques/T1098/001', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1098', value: 'additionalCloudCredentials', }, @@ -8824,7 +3948,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1098.003', name: 'Additional Cloud Roles', reference: 'https://attack.mitre.org/techniques/T1098/003', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1098', value: 'additionalCloudRoles', }, @@ -8836,7 +3960,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1098.002', name: 'Additional Email Delegate Permissions', reference: 'https://attack.mitre.org/techniques/T1098/002', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1098', value: 'additionalEmailDelegatePermissions', }, @@ -8848,7 +3972,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.009', name: 'AppCert DLLs', reference: 'https://attack.mitre.org/techniques/T1546/009', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'appCertDlLs', }, @@ -8860,7 +3984,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.010', name: 'AppInit DLLs', reference: 'https://attack.mitre.org/techniques/T1546/010', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'appInitDlLs', }, @@ -8872,7 +3996,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.002', name: 'AppleScript', reference: 'https://attack.mitre.org/techniques/T1059/002', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'appleScript', }, @@ -8884,7 +4008,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1550.001', name: 'Application Access Token', reference: 'https://attack.mitre.org/techniques/T1550/001', - tactics: 'defense-evasion,lateral-movement', + tactics: ['defense-evasion', 'lateral-movement'], techniqueId: 'T1550', value: 'applicationAccessToken', }, @@ -8896,7 +4020,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1499.003', name: 'Application Exhaustion Flood', reference: 'https://attack.mitre.org/techniques/T1499/003', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1499', value: 'applicationExhaustionFlood', }, @@ -8908,7 +4032,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.011', name: 'Application Shimming', reference: 'https://attack.mitre.org/techniques/T1546/011', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'applicationShimming', }, @@ -8920,7 +4044,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1499.004', name: 'Application or System Exploitation', reference: 'https://attack.mitre.org/techniques/T1499/004', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1499', value: 'applicationOrSystemExploitation', }, @@ -8932,7 +4056,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1560.003', name: 'Archive via Custom Method', reference: 'https://attack.mitre.org/techniques/T1560/003', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1560', value: 'archiveViaCustomMethod', }, @@ -8944,7 +4068,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1560.002', name: 'Archive via Library', reference: 'https://attack.mitre.org/techniques/T1560/002', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1560', value: 'archiveViaLibrary', }, @@ -8956,7 +4080,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1560.001', name: 'Archive via Utility', reference: 'https://attack.mitre.org/techniques/T1560/001', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1560', value: 'archiveViaUtility', }, @@ -8968,7 +4092,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1573.002', name: 'Asymmetric Cryptography', reference: 'https://attack.mitre.org/techniques/T1573/002', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1573', value: 'asymmetricCryptography', }, @@ -8980,7 +4104,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.004', name: 'Asynchronous Procedure Call', reference: 'https://attack.mitre.org/techniques/T1055/004', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'asynchronousProcedureCall', }, @@ -8992,7 +4116,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1053.002', name: 'At', reference: 'https://attack.mitre.org/techniques/T1053/002', - tactics: 'execution,persistence,privilege-escalation', + tactics: ['execution', 'persistence', 'privilege-escalation'], techniqueId: 'T1053', value: 'at', }, @@ -9004,7 +4128,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1053.001', name: 'At (Linux)', reference: 'https://attack.mitre.org/techniques/T1053/001', - tactics: 'execution,persistence,privilege-escalation', + tactics: ['execution', 'persistence', 'privilege-escalation'], techniqueId: 'T1053', value: 'atLinux', }, @@ -9016,7 +4140,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.002', name: 'Authentication Package', reference: 'https://attack.mitre.org/techniques/T1547/002', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'authenticationPackage', }, @@ -9028,7 +4152,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1552.003', name: 'Bash History', reference: 'https://attack.mitre.org/techniques/T1552/003', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1552', value: 'bashHistory', }, @@ -9040,7 +4164,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1102.002', name: 'Bidirectional Communication', reference: 'https://attack.mitre.org/techniques/T1102/002', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1102', value: 'bidirectionalCommunication', }, @@ -9052,7 +4176,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.001', name: 'Binary Padding', reference: 'https://attack.mitre.org/techniques/T1027/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'binaryPadding', }, @@ -9064,7 +4188,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1542.003', name: 'Bootkit', reference: 'https://attack.mitre.org/techniques/T1542/003', - tactics: 'persistence,defense-evasion', + tactics: ['persistence', 'defense-evasion'], techniqueId: 'T1542', value: 'bootkit', }, @@ -9076,7 +4200,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1583.005', name: 'Botnet', reference: 'https://attack.mitre.org/techniques/T1583/005', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1583', value: 'botnet', }, @@ -9088,7 +4212,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1584.005', name: 'Botnet', reference: 'https://attack.mitre.org/techniques/T1584/005', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1584', value: 'botnet', }, @@ -9100,7 +4224,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1591.002', name: 'Business Relationships', reference: 'https://attack.mitre.org/techniques/T1591/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1591', value: 'businessRelationships', }, @@ -9112,7 +4236,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1548.002', name: 'Bypass User Account Control', reference: 'https://attack.mitre.org/techniques/T1548/002', - tactics: 'privilege-escalation,defense-evasion', + tactics: ['privilege-escalation', 'defense-evasion'], techniqueId: 'T1548', value: 'bypassUserAccountControl', }, @@ -9124,7 +4248,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1596.004', name: 'CDNs', reference: 'https://attack.mitre.org/techniques/T1596/004', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1596', value: 'cdNs', }, @@ -9136,7 +4260,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.003', name: 'CMSTP', reference: 'https://attack.mitre.org/techniques/T1218/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'cmstp', }, @@ -9148,7 +4272,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.012', name: 'COR_PROFILER', reference: 'https://attack.mitre.org/techniques/T1574/012', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'corProfiler', }, @@ -9160,7 +4284,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.005', name: 'Cached Domain Credentials', reference: 'https://attack.mitre.org/techniques/T1003/005', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'cachedDomainCredentials', }, @@ -9172,10 +4296,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.001', name: 'Change Default File Association', reference: 'https://attack.mitre.org/techniques/T1546/001', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'changeDefaultFileAssociation', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.chatMessagesT1552Description', + { defaultMessage: 'Chat Messages (T1552.008)' } + ), + id: 'T1552.008', + name: 'Chat Messages', + reference: 'https://attack.mitre.org/techniques/T1552/008', + tactics: ['credential-access'], + techniqueId: 'T1552', + value: 'chatMessages', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.clearCommandHistoryT1070Description', @@ -9184,7 +4320,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.003', name: 'Clear Command History', reference: 'https://attack.mitre.org/techniques/T1070/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'clearCommandHistory', }, @@ -9196,7 +4332,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.002', name: 'Clear Linux or Mac System Logs', reference: 'https://attack.mitre.org/techniques/T1070/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'clearLinuxOrMacSystemLogs', }, @@ -9208,7 +4344,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.008', name: 'Clear Mailbox Data', reference: 'https://attack.mitre.org/techniques/T1070/008', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'clearMailboxData', }, @@ -9220,7 +4356,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.007', name: 'Clear Network Connection History and Configurations', reference: 'https://attack.mitre.org/techniques/T1070/007', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'clearNetworkConnectionHistoryAndConfigurations', }, @@ -9232,7 +4368,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.009', name: 'Clear Persistence', reference: 'https://attack.mitre.org/techniques/T1070/009', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'clearPersistence', }, @@ -9244,7 +4380,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.001', name: 'Clear Windows Event Logs', reference: 'https://attack.mitre.org/techniques/T1070/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'clearWindowsEventLogs', }, @@ -9256,10 +4392,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1592.004', name: 'Client Configurations', reference: 'https://attack.mitre.org/techniques/T1592/004', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1592', value: 'clientConfigurations', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.cloudApiT1059Description', + { defaultMessage: 'Cloud API (T1059.009)' } + ), + id: 'T1059.009', + name: 'Cloud API', + reference: 'https://attack.mitre.org/techniques/T1059/009', + tactics: ['execution'], + techniqueId: 'T1059', + value: 'cloudApi', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.cloudAccountT1087Description', @@ -9268,7 +4416,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1087.004', name: 'Cloud Account', reference: 'https://attack.mitre.org/techniques/T1087/004', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1087', value: 'cloudAccount', }, @@ -9280,7 +4428,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1136.003', name: 'Cloud Account', reference: 'https://attack.mitre.org/techniques/T1136/003', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1136', value: 'cloudAccount', }, @@ -9292,7 +4440,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1586.003', name: 'Cloud Accounts', reference: 'https://attack.mitre.org/techniques/T1586/003', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1586', value: 'cloudAccounts', }, @@ -9304,7 +4452,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1585.003', name: 'Cloud Accounts', reference: 'https://attack.mitre.org/techniques/T1585/003', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1585', value: 'cloudAccounts', }, @@ -9316,7 +4464,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1078.004', name: 'Cloud Accounts', reference: 'https://attack.mitre.org/techniques/T1078/004', - tactics: 'defense-evasion,persistence,privilege-escalation,initial-access', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], techniqueId: 'T1078', value: 'cloudAccounts', }, @@ -9328,7 +4476,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1069.003', name: 'Cloud Groups', reference: 'https://attack.mitre.org/techniques/T1069/003', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1069', value: 'cloudGroups', }, @@ -9340,10 +4488,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1552.005', name: 'Cloud Instance Metadata API', reference: 'https://attack.mitre.org/techniques/T1552/005', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1552', value: 'cloudInstanceMetadataApi', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.cloudServicesT1021Description', + { defaultMessage: 'Cloud Services (T1021.007)' } + ), + id: 'T1021.007', + name: 'Cloud Services', + reference: 'https://attack.mitre.org/techniques/T1021/007', + tactics: ['lateral-movement'], + techniqueId: 'T1021', + value: 'cloudServices', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.codeRepositoriesT1593Description', @@ -9352,7 +4512,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1593.003', name: 'Code Repositories', reference: 'https://attack.mitre.org/techniques/T1593/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1593', value: 'codeRepositories', }, @@ -9364,7 +4524,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1213.003', name: 'Code Repositories', reference: 'https://attack.mitre.org/techniques/T1213/003', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1213', value: 'codeRepositories', }, @@ -9376,7 +4536,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1553.002', name: 'Code Signing', reference: 'https://attack.mitre.org/techniques/T1553/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1553', value: 'codeSigning', }, @@ -9388,7 +4548,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1587.002', name: 'Code Signing Certificates', reference: 'https://attack.mitre.org/techniques/T1587/002', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1587', value: 'codeSigningCertificates', }, @@ -9400,7 +4560,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1588.003', name: 'Code Signing Certificates', reference: 'https://attack.mitre.org/techniques/T1588/003', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1588', value: 'codeSigningCertificates', }, @@ -9412,10 +4572,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1553.006', name: 'Code Signing Policy Modification', reference: 'https://attack.mitre.org/techniques/T1553/006', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1553', value: 'codeSigningPolicyModification', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.commandObfuscationT1027Description', + { defaultMessage: 'Command Obfuscation (T1027.010)' } + ), + id: 'T1027.010', + name: 'Command Obfuscation', + reference: 'https://attack.mitre.org/techniques/T1027/010', + tactics: ['defense-evasion'], + techniqueId: 'T1027', + value: 'commandObfuscation', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.compileAfterDeliveryT1027Description', @@ -9424,7 +4596,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.004', name: 'Compile After Delivery', reference: 'https://attack.mitre.org/techniques/T1027/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'compileAfterDelivery', }, @@ -9436,7 +4608,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.001', name: 'Compiled HTML File', reference: 'https://attack.mitre.org/techniques/T1218/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'compiledHtmlFile', }, @@ -9448,7 +4620,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1542.002', name: 'Component Firmware', reference: 'https://attack.mitre.org/techniques/T1542/002', - tactics: 'persistence,defense-evasion', + tactics: ['persistence', 'defense-evasion'], techniqueId: 'T1542', value: 'componentFirmware', }, @@ -9460,7 +4632,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1559.001', name: 'Component Object Model', reference: 'https://attack.mitre.org/techniques/T1559/001', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1559', value: 'componentObjectModel', }, @@ -9472,7 +4644,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.015', name: 'Component Object Model Hijacking', reference: 'https://attack.mitre.org/techniques/T1546/015', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'componentObjectModelHijacking', }, @@ -9484,7 +4656,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1195.003', name: 'Compromise Hardware Supply Chain', reference: 'https://attack.mitre.org/techniques/T1195/003', - tactics: 'initial-access', + tactics: ['initial-access'], techniqueId: 'T1195', value: 'compromiseHardwareSupplyChain', }, @@ -9496,7 +4668,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1195.001', name: 'Compromise Software Dependencies and Development Tools', reference: 'https://attack.mitre.org/techniques/T1195/001', - tactics: 'initial-access', + tactics: ['initial-access'], techniqueId: 'T1195', value: 'compromiseSoftwareDependenciesAndDevelopmentTools', }, @@ -9508,7 +4680,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1195.002', name: 'Compromise Software Supply Chain', reference: 'https://attack.mitre.org/techniques/T1195/002', - tactics: 'initial-access', + tactics: ['initial-access'], techniqueId: 'T1195', value: 'compromiseSoftwareSupplyChain', }, @@ -9520,7 +4692,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1213.001', name: 'Confluence', reference: 'https://attack.mitre.org/techniques/T1213/001', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1213', value: 'confluence', }, @@ -9532,7 +4704,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1552.007', name: 'Container API', reference: 'https://attack.mitre.org/techniques/T1552/007', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1552', value: 'containerApi', }, @@ -9544,7 +4716,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1053.007', name: 'Container Orchestration Job', reference: 'https://attack.mitre.org/techniques/T1053/007', - tactics: 'execution,persistence,privilege-escalation', + tactics: ['execution', 'persistence', 'privilege-escalation'], techniqueId: 'T1053', value: 'containerOrchestrationJob', }, @@ -9556,7 +4728,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.002', name: 'Control Panel', reference: 'https://attack.mitre.org/techniques/T1218/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'controlPanel', }, @@ -9568,7 +4740,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1578.002', name: 'Create Cloud Instance', reference: 'https://attack.mitre.org/techniques/T1578/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1578', value: 'createCloudInstance', }, @@ -9580,7 +4752,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1134.002', name: 'Create Process with Token', reference: 'https://attack.mitre.org/techniques/T1134/002', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1134', value: 'createProcessWithToken', }, @@ -9592,7 +4764,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1578.001', name: 'Create Snapshot', reference: 'https://attack.mitre.org/techniques/T1578/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1578', value: 'createSnapshot', }, @@ -9604,7 +4776,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1056.004', name: 'Credential API Hooking', reference: 'https://attack.mitre.org/techniques/T1056/004', - tactics: 'collection,credential-access', + tactics: ['collection', 'credential-access'], techniqueId: 'T1056', value: 'credentialApiHooking', }, @@ -9616,7 +4788,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1110.004', name: 'Credential Stuffing', reference: 'https://attack.mitre.org/techniques/T1110/004', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1110', value: 'credentialStuffing', }, @@ -9628,7 +4800,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1589.001', name: 'Credentials', reference: 'https://attack.mitre.org/techniques/T1589/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1589', value: 'credentials', }, @@ -9640,7 +4812,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1552.001', name: 'Credentials In Files', reference: 'https://attack.mitre.org/techniques/T1552/001', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1552', value: 'credentialsInFiles', }, @@ -9652,7 +4824,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1555.003', name: 'Credentials from Web Browsers', reference: 'https://attack.mitre.org/techniques/T1555/003', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1555', value: 'credentialsFromWebBrowsers', }, @@ -9664,7 +4836,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1552.002', name: 'Credentials in Registry', reference: 'https://attack.mitre.org/techniques/T1552/002', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1552', value: 'credentialsInRegistry', }, @@ -9676,7 +4848,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1053.003', name: 'Cron', reference: 'https://attack.mitre.org/techniques/T1053/003', - tactics: 'execution,persistence,privilege-escalation', + tactics: ['execution', 'persistence', 'privilege-escalation'], techniqueId: 'T1053', value: 'cron', }, @@ -9688,7 +4860,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.006', name: 'DCSync', reference: 'https://attack.mitre.org/techniques/T1003/006', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'dcSync', }, @@ -9700,7 +4872,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1557.003', name: 'DHCP Spoofing', reference: 'https://attack.mitre.org/techniques/T1557/003', - tactics: 'credential-access,collection', + tactics: ['credential-access', 'collection'], techniqueId: 'T1557', value: 'dhcpSpoofing', }, @@ -9712,7 +4884,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.001', name: 'DLL Search Order Hijacking', reference: 'https://attack.mitre.org/techniques/T1574/001', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'dllSearchOrderHijacking', }, @@ -9724,7 +4896,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.002', name: 'DLL Side-Loading', reference: 'https://attack.mitre.org/techniques/T1574/002', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'dllSideLoading', }, @@ -9736,7 +4908,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1590.002', name: 'DNS', reference: 'https://attack.mitre.org/techniques/T1590/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1590', value: 'dns', }, @@ -9748,7 +4920,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1071.004', name: 'DNS', reference: 'https://attack.mitre.org/techniques/T1071/004', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1071', value: 'dns', }, @@ -9760,7 +4932,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1568.003', name: 'DNS Calculation', reference: 'https://attack.mitre.org/techniques/T1568/003', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1568', value: 'dnsCalculation', }, @@ -9772,7 +4944,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1583.002', name: 'DNS Server', reference: 'https://attack.mitre.org/techniques/T1583/002', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1583', value: 'dnsServer', }, @@ -9784,7 +4956,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1584.002', name: 'DNS Server', reference: 'https://attack.mitre.org/techniques/T1584/002', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1584', value: 'dnsServer', }, @@ -9796,7 +4968,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1596.001', name: 'DNS/Passive DNS', reference: 'https://attack.mitre.org/techniques/T1596/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1596', value: 'dnsPassiveDns', }, @@ -9808,7 +4980,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1102.001', name: 'Dead Drop Resolver', reference: 'https://attack.mitre.org/techniques/T1102/001', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1102', value: 'deadDropResolver', }, @@ -9820,7 +4992,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1078.001', name: 'Default Accounts', reference: 'https://attack.mitre.org/techniques/T1078/001', - tactics: 'defense-evasion,persistence,privilege-escalation,initial-access', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], techniqueId: 'T1078', value: 'defaultAccounts', }, @@ -9832,7 +5004,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1578.003', name: 'Delete Cloud Instance', reference: 'https://attack.mitre.org/techniques/T1578/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1578', value: 'deleteCloudInstance', }, @@ -9844,7 +5016,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1591.001', name: 'Determine Physical Locations', reference: 'https://attack.mitre.org/techniques/T1591/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1591', value: 'determinePhysicalLocations', }, @@ -9856,7 +5028,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1098.005', name: 'Device Registration', reference: 'https://attack.mitre.org/techniques/T1098/005', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1098', value: 'deviceRegistration', }, @@ -9868,7 +5040,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1596.003', name: 'Digital Certificates', reference: 'https://attack.mitre.org/techniques/T1596/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1596', value: 'digitalCertificates', }, @@ -9880,7 +5052,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1588.004', name: 'Digital Certificates', reference: 'https://attack.mitre.org/techniques/T1588/004', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1588', value: 'digitalCertificates', }, @@ -9892,7 +5064,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1587.003', name: 'Digital Certificates', reference: 'https://attack.mitre.org/techniques/T1587/003', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1587', value: 'digitalCertificates', }, @@ -9904,7 +5076,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1498.001', name: 'Direct Network Flood', reference: 'https://attack.mitre.org/techniques/T1498/001', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1498', value: 'directNetworkFlood', }, @@ -9916,7 +5088,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.008', name: 'Disable Cloud Logs', reference: 'https://attack.mitre.org/techniques/T1562/008', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'disableCloudLogs', }, @@ -9928,7 +5100,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1600.002', name: 'Disable Crypto Hardware', reference: 'https://attack.mitre.org/techniques/T1600/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1600', value: 'disableCryptoHardware', }, @@ -9940,7 +5112,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.002', name: 'Disable Windows Event Logging', reference: 'https://attack.mitre.org/techniques/T1562/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'disableWindowsEventLogging', }, @@ -9952,7 +5124,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.007', name: 'Disable or Modify Cloud Firewall', reference: 'https://attack.mitre.org/techniques/T1562/007', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'disableOrModifyCloudFirewall', }, @@ -9964,7 +5136,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.004', name: 'Disable or Modify System Firewall', reference: 'https://attack.mitre.org/techniques/T1562/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'disableOrModifySystemFirewall', }, @@ -9976,7 +5148,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.001', name: 'Disable or Modify Tools', reference: 'https://attack.mitre.org/techniques/T1562/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'disableOrModifyTools', }, @@ -9988,7 +5160,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1561.001', name: 'Disk Content Wipe', reference: 'https://attack.mitre.org/techniques/T1561/001', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1561', value: 'diskContentWipe', }, @@ -10000,7 +5172,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1561.002', name: 'Disk Structure Wipe', reference: 'https://attack.mitre.org/techniques/T1561/002', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1561', value: 'diskStructureWipe', }, @@ -10012,7 +5184,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1021.003', name: 'Distributed Component Object Model', reference: 'https://attack.mitre.org/techniques/T1021/003', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1021', value: 'distributedComponentObjectModel', }, @@ -10024,7 +5196,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1087.002', name: 'Domain Account', reference: 'https://attack.mitre.org/techniques/T1087/002', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1087', value: 'domainAccount', }, @@ -10036,7 +5208,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1136.002', name: 'Domain Account', reference: 'https://attack.mitre.org/techniques/T1136/002', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1136', value: 'domainAccount', }, @@ -10048,7 +5220,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1078.002', name: 'Domain Accounts', reference: 'https://attack.mitre.org/techniques/T1078/002', - tactics: 'defense-evasion,persistence,privilege-escalation,initial-access', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], techniqueId: 'T1078', value: 'domainAccounts', }, @@ -10060,7 +5232,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1556.001', name: 'Domain Controller Authentication', reference: 'https://attack.mitre.org/techniques/T1556/001', - tactics: 'credential-access,defense-evasion,persistence', + tactics: ['credential-access', 'defense-evasion', 'persistence'], techniqueId: 'T1556', value: 'domainControllerAuthentication', }, @@ -10072,7 +5244,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1090.004', name: 'Domain Fronting', reference: 'https://attack.mitre.org/techniques/T1090/004', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1090', value: 'domainFronting', }, @@ -10084,7 +5256,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1568.002', name: 'Domain Generation Algorithms', reference: 'https://attack.mitre.org/techniques/T1568/002', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1568', value: 'domainGenerationAlgorithms', }, @@ -10096,7 +5268,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1069.002', name: 'Domain Groups', reference: 'https://attack.mitre.org/techniques/T1069/002', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1069', value: 'domainGroups', }, @@ -10108,7 +5280,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1590.001', name: 'Domain Properties', reference: 'https://attack.mitre.org/techniques/T1590/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1590', value: 'domainProperties', }, @@ -10120,7 +5292,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1484.002', name: 'Domain Trust Modification', reference: 'https://attack.mitre.org/techniques/T1484/002', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1484', value: 'domainTrustModification', }, @@ -10132,7 +5304,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1583.001', name: 'Domains', reference: 'https://attack.mitre.org/techniques/T1583/001', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1583', value: 'domains', }, @@ -10144,7 +5316,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1584.001', name: 'Domains', reference: 'https://attack.mitre.org/techniques/T1584/001', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1584', value: 'domains', }, @@ -10156,7 +5328,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1036.007', name: 'Double File Extension', reference: 'https://attack.mitre.org/techniques/T1036/007', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1036', value: 'doubleFileExtension', }, @@ -10168,7 +5340,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.010', name: 'Downgrade Attack', reference: 'https://attack.mitre.org/techniques/T1562/010', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'downgradeAttack', }, @@ -10180,7 +5352,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1601.002', name: 'Downgrade System Image', reference: 'https://attack.mitre.org/techniques/T1601/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1601', value: 'downgradeSystemImage', }, @@ -10192,7 +5364,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1608.004', name: 'Drive-by Target', reference: 'https://attack.mitre.org/techniques/T1608/004', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1608', value: 'driveByTarget', }, @@ -10204,7 +5376,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.004', name: 'Dylib Hijacking', reference: 'https://attack.mitre.org/techniques/T1574/004', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'dylibHijacking', }, @@ -10216,7 +5388,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.007', name: 'Dynamic API Resolution', reference: 'https://attack.mitre.org/techniques/T1027/007', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'dynamicApiResolution', }, @@ -10228,7 +5400,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1559.002', name: 'Dynamic Data Exchange', reference: 'https://attack.mitre.org/techniques/T1559/002', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1559', value: 'dynamicDataExchange', }, @@ -10240,7 +5412,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.006', name: 'Dynamic Linker Hijacking', reference: 'https://attack.mitre.org/techniques/T1574/006', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'dynamicLinkerHijacking', }, @@ -10252,7 +5424,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.001', name: 'Dynamic-link Library Injection', reference: 'https://attack.mitre.org/techniques/T1055/001', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'dynamicLinkLibraryInjection', }, @@ -10264,7 +5436,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1548.004', name: 'Elevated Execution with Prompt', reference: 'https://attack.mitre.org/techniques/T1548/004', - tactics: 'privilege-escalation,defense-evasion', + tactics: ['privilege-escalation', 'defense-evasion'], techniqueId: 'T1548', value: 'elevatedExecutionWithPrompt', }, @@ -10276,7 +5448,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1087.003', name: 'Email Account', reference: 'https://attack.mitre.org/techniques/T1087/003', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1087', value: 'emailAccount', }, @@ -10288,7 +5460,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1586.002', name: 'Email Accounts', reference: 'https://attack.mitre.org/techniques/T1586/002', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1586', value: 'emailAccounts', }, @@ -10300,7 +5472,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1585.002', name: 'Email Accounts', reference: 'https://attack.mitre.org/techniques/T1585/002', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1585', value: 'emailAccounts', }, @@ -10312,7 +5484,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1589.002', name: 'Email Addresses', reference: 'https://attack.mitre.org/techniques/T1589/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1589', value: 'emailAddresses', }, @@ -10324,7 +5496,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1114.003', name: 'Email Forwarding Rule', reference: 'https://attack.mitre.org/techniques/T1114/003', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1114', value: 'emailForwardingRule', }, @@ -10336,7 +5508,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.008', name: 'Email Hiding Rules', reference: 'https://attack.mitre.org/techniques/T1564/008', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'emailHidingRules', }, @@ -10348,7 +5520,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.009', name: 'Embedded Payloads', reference: 'https://attack.mitre.org/techniques/T1027/009', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'embeddedPayloads', }, @@ -10360,7 +5532,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.014', name: 'Emond', reference: 'https://attack.mitre.org/techniques/T1546/014', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'emond', }, @@ -10372,7 +5544,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1589.003', name: 'Employee Names', reference: 'https://attack.mitre.org/techniques/T1589/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1589', value: 'employeeNames', }, @@ -10384,7 +5556,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1480.001', name: 'Environmental Keying', reference: 'https://attack.mitre.org/techniques/T1480/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1480', value: 'environmentalKeying', }, @@ -10396,7 +5568,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.005', name: 'Executable Installer File Permissions Weakness', reference: 'https://attack.mitre.org/techniques/T1574/005', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'executableInstallerFilePermissionsWeakness', }, @@ -10408,7 +5580,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1048.002', name: 'Exfiltration Over Asymmetric Encrypted Non-C2 Protocol', reference: 'https://attack.mitre.org/techniques/T1048/002', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1048', value: 'exfiltrationOverAsymmetricEncryptedNonC2Protocol', }, @@ -10420,7 +5592,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1011.001', name: 'Exfiltration Over Bluetooth', reference: 'https://attack.mitre.org/techniques/T1011/001', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1011', value: 'exfiltrationOverBluetooth', }, @@ -10432,7 +5604,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1048.001', name: 'Exfiltration Over Symmetric Encrypted Non-C2 Protocol', reference: 'https://attack.mitre.org/techniques/T1048/001', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1048', value: 'exfiltrationOverSymmetricEncryptedNonC2Protocol', }, @@ -10444,7 +5616,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1048.003', name: 'Exfiltration Over Unencrypted Non-C2 Protocol', reference: 'https://attack.mitre.org/techniques/T1048/003', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1048', value: 'exfiltrationOverUnencryptedNonC2Protocol', }, @@ -10456,7 +5628,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1052.001', name: 'Exfiltration over USB', reference: 'https://attack.mitre.org/techniques/T1052/001', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1052', value: 'exfiltrationOverUsb', }, @@ -10468,7 +5640,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1567.002', name: 'Exfiltration to Cloud Storage', reference: 'https://attack.mitre.org/techniques/T1567/002', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1567', value: 'exfiltrationToCloudStorage', }, @@ -10480,10 +5652,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1567.001', name: 'Exfiltration to Code Repository', reference: 'https://attack.mitre.org/techniques/T1567/001', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1567', value: 'exfiltrationToCodeRepository', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.exfiltrationToTextStorageSitesT1567Description', + { defaultMessage: 'Exfiltration to Text Storage Sites (T1567.003)' } + ), + id: 'T1567.003', + name: 'Exfiltration to Text Storage Sites', + reference: 'https://attack.mitre.org/techniques/T1567/003', + tactics: ['exfiltration'], + techniqueId: 'T1567', + value: 'exfiltrationToTextStorageSites', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.exploitsT1587Description', @@ -10492,7 +5676,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1587.004', name: 'Exploits', reference: 'https://attack.mitre.org/techniques/T1587/004', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1587', value: 'exploits', }, @@ -10504,7 +5688,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1588.005', name: 'Exploits', reference: 'https://attack.mitre.org/techniques/T1588/005', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1588', value: 'exploits', }, @@ -10516,7 +5700,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1491.002', name: 'External Defacement', reference: 'https://attack.mitre.org/techniques/T1491/002', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1491', value: 'externalDefacement', }, @@ -10528,7 +5712,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1090.002', name: 'External Proxy', reference: 'https://attack.mitre.org/techniques/T1090/002', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1090', value: 'externalProxy', }, @@ -10540,7 +5724,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.011', name: 'Extra Window Memory Injection', reference: 'https://attack.mitre.org/techniques/T1055/011', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'extraWindowMemoryInjection', }, @@ -10552,7 +5736,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1568.001', name: 'Fast Flux DNS', reference: 'https://attack.mitre.org/techniques/T1568/001', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1568', value: 'fastFluxDns', }, @@ -10564,7 +5748,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.004', name: 'File Deletion', reference: 'https://attack.mitre.org/techniques/T1070/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'fileDeletion', }, @@ -10576,10 +5760,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1071.002', name: 'File Transfer Protocols', reference: 'https://attack.mitre.org/techniques/T1071/002', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1071', value: 'fileTransferProtocols', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.filelessStorageT1027Description', + { defaultMessage: 'Fileless Storage (T1027.011)' } + ), + id: 'T1027.011', + name: 'Fileless Storage', + reference: 'https://attack.mitre.org/techniques/T1027/011', + tactics: ['defense-evasion'], + techniqueId: 'T1027', + value: 'filelessStorage', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.firmwareT1592Description', @@ -10588,7 +5784,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1592.003', name: 'Firmware', reference: 'https://attack.mitre.org/techniques/T1592/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1592', value: 'firmware', }, @@ -10600,7 +5796,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1056.002', name: 'GUI Input Capture', reference: 'https://attack.mitre.org/techniques/T1056/002', - tactics: 'collection,credential-access', + tactics: ['collection', 'credential-access'], techniqueId: 'T1056', value: 'guiInputCapture', }, @@ -10612,7 +5808,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1553.001', name: 'Gatekeeper Bypass', reference: 'https://attack.mitre.org/techniques/T1553/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1553', value: 'gatekeeperBypass', }, @@ -10624,7 +5820,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1558.001', name: 'Golden Ticket', reference: 'https://attack.mitre.org/techniques/T1558/001', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1558', value: 'goldenTicket', }, @@ -10636,7 +5832,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1484.001', name: 'Group Policy Modification', reference: 'https://attack.mitre.org/techniques/T1484/001', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1484', value: 'groupPolicyModification', }, @@ -10648,7 +5844,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1552.006', name: 'Group Policy Preferences', reference: 'https://attack.mitre.org/techniques/T1552/006', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1552', value: 'groupPolicyPreferences', }, @@ -10660,7 +5856,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.006', name: 'HTML Smuggling', reference: 'https://attack.mitre.org/techniques/T1027/006', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'htmlSmuggling', }, @@ -10672,7 +5868,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1592.001', name: 'Hardware', reference: 'https://attack.mitre.org/techniques/T1592/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1592', value: 'hardware', }, @@ -10684,7 +5880,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.005', name: 'Hidden File System', reference: 'https://attack.mitre.org/techniques/T1564/005', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'hiddenFileSystem', }, @@ -10696,7 +5892,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.001', name: 'Hidden Files and Directories', reference: 'https://attack.mitre.org/techniques/T1564/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'hiddenFilesAndDirectories', }, @@ -10708,7 +5904,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.002', name: 'Hidden Users', reference: 'https://attack.mitre.org/techniques/T1564/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'hiddenUsers', }, @@ -10720,7 +5916,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.003', name: 'Hidden Window', reference: 'https://attack.mitre.org/techniques/T1564/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'hiddenWindow', }, @@ -10732,7 +5928,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1556.007', name: 'Hybrid Identity', reference: 'https://attack.mitre.org/techniques/T1556/007', - tactics: 'credential-access,defense-evasion,persistence', + tactics: ['credential-access', 'defense-evasion', 'persistence'], techniqueId: 'T1556', value: 'hybridIdentity', }, @@ -10744,7 +5940,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1505.004', name: 'IIS Components', reference: 'https://attack.mitre.org/techniques/T1505/004', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1505', value: 'iisComponents', }, @@ -10756,7 +5952,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1590.005', name: 'IP Addresses', reference: 'https://attack.mitre.org/techniques/T1590/005', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1590', value: 'ipAddresses', }, @@ -10768,7 +5964,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1591.003', name: 'Identify Business Tempo', reference: 'https://attack.mitre.org/techniques/T1591/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1591', value: 'identifyBusinessTempo', }, @@ -10780,7 +5976,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1591.004', name: 'Identify Roles', reference: 'https://attack.mitre.org/techniques/T1591/004', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1591', value: 'identifyRoles', }, @@ -10792,7 +5988,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.012', name: 'Image File Execution Options Injection', reference: 'https://attack.mitre.org/techniques/T1546/012', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'imageFileExecutionOptionsInjection', }, @@ -10804,7 +6000,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.003', name: 'Impair Command History Logging', reference: 'https://attack.mitre.org/techniques/T1562/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'impairCommandHistoryLogging', }, @@ -10816,7 +6012,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.006', name: 'Indicator Blocking', reference: 'https://attack.mitre.org/techniques/T1562/006', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'indicatorBlocking', }, @@ -10828,7 +6024,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.005', name: 'Indicator Removal from Tools', reference: 'https://attack.mitre.org/techniques/T1027/005', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'indicatorRemovalFromTools', }, @@ -10840,7 +6036,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1608.003', name: 'Install Digital Certificate', reference: 'https://attack.mitre.org/techniques/T1608/003', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1608', value: 'installDigitalCertificate', }, @@ -10852,7 +6048,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1553.004', name: 'Install Root Certificate', reference: 'https://attack.mitre.org/techniques/T1553/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1553', value: 'installRootCertificate', }, @@ -10864,7 +6060,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.004', name: 'InstallUtil', reference: 'https://attack.mitre.org/techniques/T1218/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'installUtil', }, @@ -10876,7 +6072,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.016', name: 'Installer Packages', reference: 'https://attack.mitre.org/techniques/T1546/016', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'installerPackages', }, @@ -10888,7 +6084,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1491.001', name: 'Internal Defacement', reference: 'https://attack.mitre.org/techniques/T1491/001', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1491', value: 'internalDefacement', }, @@ -10900,7 +6096,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1090.001', name: 'Internal Proxy', reference: 'https://attack.mitre.org/techniques/T1090/001', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1090', value: 'internalProxy', }, @@ -10912,7 +6108,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1016.001', name: 'Internet Connection Discovery', reference: 'https://attack.mitre.org/techniques/T1016/001', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1016', value: 'internetConnectionDiscovery', }, @@ -10924,7 +6120,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1036.001', name: 'Invalid Code Signature', reference: 'https://attack.mitre.org/techniques/T1036/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1036', value: 'invalidCodeSignature', }, @@ -10936,7 +6132,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.007', name: 'JavaScript', reference: 'https://attack.mitre.org/techniques/T1059/007', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'javaScript', }, @@ -10948,7 +6144,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1001.001', name: 'Junk Data', reference: 'https://attack.mitre.org/techniques/T1001/001', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1001', value: 'junkData', }, @@ -10960,7 +6156,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1558.003', name: 'Kerberoasting', reference: 'https://attack.mitre.org/techniques/T1558/003', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1558', value: 'kerberoasting', }, @@ -10972,7 +6168,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.006', name: 'Kernel Modules and Extensions', reference: 'https://attack.mitre.org/techniques/T1547/006', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'kernelModulesAndExtensions', }, @@ -10984,7 +6180,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.013', name: 'KernelCallbackTable', reference: 'https://attack.mitre.org/techniques/T1574/013', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'kernelCallbackTable', }, @@ -10996,7 +6192,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1555.001', name: 'Keychain', reference: 'https://attack.mitre.org/techniques/T1555/001', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1555', value: 'keychain', }, @@ -11008,7 +6204,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1056.001', name: 'Keylogging', reference: 'https://attack.mitre.org/techniques/T1056/001', - tactics: 'collection,credential-access', + tactics: ['collection', 'credential-access'], techniqueId: 'T1056', value: 'keylogging', }, @@ -11020,7 +6216,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.006', name: 'LC_LOAD_DYLIB Addition', reference: 'https://attack.mitre.org/techniques/T1546/006', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'lcLoadDylibAddition', }, @@ -11032,7 +6228,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1557.001', name: 'LLMNR/NBT-NS Poisoning and SMB Relay', reference: 'https://attack.mitre.org/techniques/T1557/001', - tactics: 'credential-access,collection', + tactics: ['credential-access', 'collection'], techniqueId: 'T1557', value: 'llmnrNbtNsPoisoningAndSmbRelay', }, @@ -11044,7 +6240,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.004', name: 'LSA Secrets', reference: 'https://attack.mitre.org/techniques/T1003/004', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'lsaSecrets', }, @@ -11056,7 +6252,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.008', name: 'LSASS Driver', reference: 'https://attack.mitre.org/techniques/T1547/008', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'lsassDriver', }, @@ -11068,7 +6264,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.001', name: 'LSASS Memory', reference: 'https://attack.mitre.org/techniques/T1003/001', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'lsassMemory', }, @@ -11080,7 +6276,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1543.001', name: 'Launch Agent', reference: 'https://attack.mitre.org/techniques/T1543/001', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1543', value: 'launchAgent', }, @@ -11092,7 +6288,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1543.004', name: 'Launch Daemon', reference: 'https://attack.mitre.org/techniques/T1543/004', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1543', value: 'launchDaemon', }, @@ -11104,7 +6300,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1569.001', name: 'Launchctl', reference: 'https://attack.mitre.org/techniques/T1569/001', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1569', value: 'launchctl', }, @@ -11116,7 +6312,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1053.004', name: 'Launchd', reference: 'https://attack.mitre.org/techniques/T1053/004', - tactics: 'execution,persistence,privilege-escalation', + tactics: ['execution', 'persistence', 'privilege-escalation'], techniqueId: 'T1053', value: 'launchd', }, @@ -11128,7 +6324,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1608.005', name: 'Link Target', reference: 'https://attack.mitre.org/techniques/T1608/005', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1608', value: 'linkTarget', }, @@ -11140,7 +6336,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1222.002', name: 'Linux and Mac File and Directory Permissions Modification', reference: 'https://attack.mitre.org/techniques/T1222/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1222', value: 'linuxAndMacFileAndDirectoryPermissionsModification', }, @@ -11152,7 +6348,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.015', name: 'ListPlanting', reference: 'https://attack.mitre.org/techniques/T1055/015', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'listPlanting', }, @@ -11164,7 +6360,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1087.001', name: 'Local Account', reference: 'https://attack.mitre.org/techniques/T1087/001', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1087', value: 'localAccount', }, @@ -11176,7 +6372,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1136.001', name: 'Local Account', reference: 'https://attack.mitre.org/techniques/T1136/001', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1136', value: 'localAccount', }, @@ -11188,7 +6384,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1078.003', name: 'Local Accounts', reference: 'https://attack.mitre.org/techniques/T1078/003', - tactics: 'defense-evasion,persistence,privilege-escalation,initial-access', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], techniqueId: 'T1078', value: 'localAccounts', }, @@ -11200,7 +6396,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1074.001', name: 'Local Data Staging', reference: 'https://attack.mitre.org/techniques/T1074/001', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1074', value: 'localDataStaging', }, @@ -11212,7 +6408,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1114.001', name: 'Local Email Collection', reference: 'https://attack.mitre.org/techniques/T1114/001', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1114', value: 'localEmailCollection', }, @@ -11224,7 +6420,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1069.001', name: 'Local Groups', reference: 'https://attack.mitre.org/techniques/T1069/001', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1069', value: 'localGroups', }, @@ -11236,7 +6432,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1037.002', name: 'Login Hook', reference: 'https://attack.mitre.org/techniques/T1037/002', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1037', value: 'loginHook', }, @@ -11248,7 +6444,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.015', name: 'Login Items', reference: 'https://attack.mitre.org/techniques/T1547/015', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'loginItems', }, @@ -11260,7 +6456,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1037.001', name: 'Logon Script (Windows)', reference: 'https://attack.mitre.org/techniques/T1037/001', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1037', value: 'logonScriptWindows', }, @@ -11272,7 +6468,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.014', name: 'MMC', reference: 'https://attack.mitre.org/techniques/T1218/014', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'mmc', }, @@ -11284,7 +6480,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1127.001', name: 'MSBuild', reference: 'https://attack.mitre.org/techniques/T1127/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1127', value: 'msBuild', }, @@ -11296,7 +6492,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1071.003', name: 'Mail Protocols', reference: 'https://attack.mitre.org/techniques/T1071/003', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1071', value: 'mailProtocols', }, @@ -11308,7 +6504,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1134.003', name: 'Make and Impersonate Token', reference: 'https://attack.mitre.org/techniques/T1134/003', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1134', value: 'makeAndImpersonateToken', }, @@ -11320,7 +6516,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1204.002', name: 'Malicious File', reference: 'https://attack.mitre.org/techniques/T1204/002', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1204', value: 'maliciousFile', }, @@ -11332,7 +6528,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1204.003', name: 'Malicious Image', reference: 'https://attack.mitre.org/techniques/T1204/003', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1204', value: 'maliciousImage', }, @@ -11344,10 +6540,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1204.001', name: 'Malicious Link', reference: 'https://attack.mitre.org/techniques/T1204/001', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1204', value: 'maliciousLink', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.malvertisingT1583Description', + { defaultMessage: 'Malvertising (T1583.008)' } + ), + id: 'T1583.008', + name: 'Malvertising', + reference: 'https://attack.mitre.org/techniques/T1583/008', + tactics: ['resource-development'], + techniqueId: 'T1583', + value: 'malvertising', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.malwareT1587Description', @@ -11356,7 +6564,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1587.001', name: 'Malware', reference: 'https://attack.mitre.org/techniques/T1587/001', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1587', value: 'malware', }, @@ -11368,7 +6576,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1588.001', name: 'Malware', reference: 'https://attack.mitre.org/techniques/T1588/001', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1588', value: 'malware', }, @@ -11380,10 +6588,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1553.005', name: 'Mark-of-the-Web Bypass', reference: 'https://attack.mitre.org/techniques/T1553/005', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1553', value: 'markOfTheWebBypass', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.masqueradeFileTypeT1036Description', + { defaultMessage: 'Masquerade File Type (T1036.008)' } + ), + id: 'T1036.008', + name: 'Masquerade File Type', + reference: 'https://attack.mitre.org/techniques/T1036/008', + tactics: ['defense-evasion'], + techniqueId: 'T1036', + value: 'masqueradeFileType', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.masqueradeTaskOrServiceT1036Description', @@ -11392,7 +6612,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1036.004', name: 'Masquerade Task or Service', reference: 'https://attack.mitre.org/techniques/T1036/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1036', value: 'masqueradeTaskOrService', }, @@ -11404,7 +6624,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1036.005', name: 'Match Legitimate Name or Location', reference: 'https://attack.mitre.org/techniques/T1036/005', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1036', value: 'matchLegitimateNameOrLocation', }, @@ -11416,7 +6636,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.013', name: 'Mavinject', reference: 'https://attack.mitre.org/techniques/T1218/013', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'mavinject', }, @@ -11428,7 +6648,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.005', name: 'Mshta', reference: 'https://attack.mitre.org/techniques/T1218/005', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'mshta', }, @@ -11440,7 +6660,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.007', name: 'Msiexec', reference: 'https://attack.mitre.org/techniques/T1218/007', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'msiexec', }, @@ -11452,7 +6672,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1556.006', name: 'Multi-Factor Authentication', reference: 'https://attack.mitre.org/techniques/T1556/006', - tactics: 'credential-access,defense-evasion,persistence', + tactics: ['credential-access', 'defense-evasion', 'persistence'], techniqueId: 'T1556', value: 'multiFactorAuthentication', }, @@ -11464,7 +6684,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1090.003', name: 'Multi-hop Proxy', reference: 'https://attack.mitre.org/techniques/T1090/003', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1090', value: 'multiHopProxy', }, @@ -11476,7 +6696,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.003', name: 'NTDS', reference: 'https://attack.mitre.org/techniques/T1003/003', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'ntds', }, @@ -11488,7 +6708,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.004', name: 'NTFS File Attributes', reference: 'https://attack.mitre.org/techniques/T1564/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'ntfsFileAttributes', }, @@ -11500,7 +6720,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.007', name: 'Netsh Helper DLL', reference: 'https://attack.mitre.org/techniques/T1546/007', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'netshHelperDll', }, @@ -11512,7 +6732,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1599.001', name: 'Network Address Translation Traversal', reference: 'https://attack.mitre.org/techniques/T1599/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1599', value: 'networkAddressTranslationTraversal', }, @@ -11524,7 +6744,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1556.004', name: 'Network Device Authentication', reference: 'https://attack.mitre.org/techniques/T1556/004', - tactics: 'credential-access,defense-evasion,persistence', + tactics: ['credential-access', 'defense-evasion', 'persistence'], techniqueId: 'T1556', value: 'networkDeviceAuthentication', }, @@ -11536,7 +6756,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.008', name: 'Network Device CLI', reference: 'https://attack.mitre.org/techniques/T1059/008', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'networkDeviceCli', }, @@ -11548,7 +6768,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1602.002', name: 'Network Device Configuration Dump', reference: 'https://attack.mitre.org/techniques/T1602/002', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1602', value: 'networkDeviceConfigurationDump', }, @@ -11560,10 +6780,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1037.003', name: 'Network Logon Script', reference: 'https://attack.mitre.org/techniques/T1037/003', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1037', value: 'networkLogonScript', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.networkProviderDllT1556Description', + { defaultMessage: 'Network Provider DLL (T1556.008)' } + ), + id: 'T1556.008', + name: 'Network Provider DLL', + reference: 'https://attack.mitre.org/techniques/T1556/008', + tactics: ['credential-access', 'defense-evasion', 'persistence'], + techniqueId: 'T1556', + value: 'networkProviderDll', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.networkSecurityAppliancesT1590Description', @@ -11572,7 +6804,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1590.006', name: 'Network Security Appliances', reference: 'https://attack.mitre.org/techniques/T1590/006', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1590', value: 'networkSecurityAppliances', }, @@ -11584,7 +6816,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.005', name: 'Network Share Connection Removal', reference: 'https://attack.mitre.org/techniques/T1070/005', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'networkShareConnectionRemoval', }, @@ -11596,7 +6828,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1590.004', name: 'Network Topology', reference: 'https://attack.mitre.org/techniques/T1590/004', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1590', value: 'networkTopology', }, @@ -11608,7 +6840,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1590.003', name: 'Network Trust Dependencies', reference: 'https://attack.mitre.org/techniques/T1590/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1590', value: 'networkTrustDependencies', }, @@ -11620,7 +6852,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1132.002', name: 'Non-Standard Encoding', reference: 'https://attack.mitre.org/techniques/T1132/002', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1132', value: 'nonStandardEncoding', }, @@ -11632,7 +6864,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1499.001', name: 'OS Exhaustion Flood', reference: 'https://attack.mitre.org/techniques/T1499/001', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1499', value: 'osExhaustionFlood', }, @@ -11644,7 +6876,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.008', name: 'Odbcconf', reference: 'https://attack.mitre.org/techniques/T1218/008', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'odbcconf', }, @@ -11656,7 +6888,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1137.001', name: 'Office Template Macros', reference: 'https://attack.mitre.org/techniques/T1137/001', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1137', value: 'officeTemplateMacros', }, @@ -11668,7 +6900,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1137.002', name: 'Office Test', reference: 'https://attack.mitre.org/techniques/T1137/002', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1137', value: 'officeTest', }, @@ -11680,7 +6912,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1102.003', name: 'One-Way Communication', reference: 'https://attack.mitre.org/techniques/T1102/003', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1102', value: 'oneWayCommunication', }, @@ -11692,7 +6924,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1137.003', name: 'Outlook Forms', reference: 'https://attack.mitre.org/techniques/T1137/003', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1137', value: 'outlookForms', }, @@ -11704,7 +6936,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1137.004', name: 'Outlook Home Page', reference: 'https://attack.mitre.org/techniques/T1137/004', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1137', value: 'outlookHomePage', }, @@ -11716,7 +6948,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1137.005', name: 'Outlook Rules', reference: 'https://attack.mitre.org/techniques/T1137/005', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1137', value: 'outlookRules', }, @@ -11728,7 +6960,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1134.004', name: 'Parent PID Spoofing', reference: 'https://attack.mitre.org/techniques/T1134/004', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1134', value: 'parentPidSpoofing', }, @@ -11740,7 +6972,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1550.002', name: 'Pass the Hash', reference: 'https://attack.mitre.org/techniques/T1550/002', - tactics: 'defense-evasion,lateral-movement', + tactics: ['defense-evasion', 'lateral-movement'], techniqueId: 'T1550', value: 'passTheHash', }, @@ -11752,7 +6984,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1550.003', name: 'Pass the Ticket', reference: 'https://attack.mitre.org/techniques/T1550/003', - tactics: 'defense-evasion,lateral-movement', + tactics: ['defense-evasion', 'lateral-movement'], techniqueId: 'T1550', value: 'passTheTicket', }, @@ -11764,7 +6996,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1110.002', name: 'Password Cracking', reference: 'https://attack.mitre.org/techniques/T1110/002', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1110', value: 'passwordCracking', }, @@ -11776,7 +7008,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1556.002', name: 'Password Filter DLL', reference: 'https://attack.mitre.org/techniques/T1556/002', - tactics: 'credential-access,defense-evasion,persistence', + tactics: ['credential-access', 'defense-evasion', 'persistence'], techniqueId: 'T1556', value: 'passwordFilterDll', }, @@ -11788,7 +7020,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1110.001', name: 'Password Guessing', reference: 'https://attack.mitre.org/techniques/T1110/001', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1110', value: 'passwordGuessing', }, @@ -11800,7 +7032,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1555.005', name: 'Password Managers', reference: 'https://attack.mitre.org/techniques/T1555/005', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1555', value: 'passwordManagers', }, @@ -11812,7 +7044,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1110.003', name: 'Password Spraying', reference: 'https://attack.mitre.org/techniques/T1110/003', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1110', value: 'passwordSpraying', }, @@ -11824,7 +7056,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1601.001', name: 'Patch System Image', reference: 'https://attack.mitre.org/techniques/T1601/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1601', value: 'patchSystemImage', }, @@ -11836,7 +7068,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.007', name: 'Path Interception by PATH Environment Variable', reference: 'https://attack.mitre.org/techniques/T1574/007', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'pathInterceptionByPathEnvironmentVariable', }, @@ -11848,7 +7080,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.008', name: 'Path Interception by Search Order Hijacking', reference: 'https://attack.mitre.org/techniques/T1574/008', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'pathInterceptionBySearchOrderHijacking', }, @@ -11860,7 +7092,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.009', name: 'Path Interception by Unquoted Path', reference: 'https://attack.mitre.org/techniques/T1574/009', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'pathInterceptionByUnquotedPath', }, @@ -11872,7 +7104,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.011', name: 'Plist Modification', reference: 'https://attack.mitre.org/techniques/T1547/011', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'plistModification', }, @@ -11884,7 +7116,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1556.003', name: 'Pluggable Authentication Modules', reference: 'https://attack.mitre.org/techniques/T1556/003', - tactics: 'credential-access,defense-evasion,persistence', + tactics: ['credential-access', 'defense-evasion', 'persistence'], techniqueId: 'T1556', value: 'pluggableAuthenticationModules', }, @@ -11896,7 +7128,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1205.001', name: 'Port Knocking', reference: 'https://attack.mitre.org/techniques/T1205/001', - tactics: 'defense-evasion,persistence,command-and-control', + tactics: ['defense-evasion', 'persistence', 'command-and-control'], techniqueId: 'T1205', value: 'portKnocking', }, @@ -11908,7 +7140,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.010', name: 'Port Monitors', reference: 'https://attack.mitre.org/techniques/T1547/010', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'portMonitors', }, @@ -11920,7 +7152,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.002', name: 'Portable Executable Injection', reference: 'https://attack.mitre.org/techniques/T1055/002', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'portableExecutableInjection', }, @@ -11932,7 +7164,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.001', name: 'PowerShell', reference: 'https://attack.mitre.org/techniques/T1059/001', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'powerShell', }, @@ -11944,7 +7176,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.013', name: 'PowerShell Profile', reference: 'https://attack.mitre.org/techniques/T1546/013', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'powerShellProfile', }, @@ -11956,7 +7188,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.012', name: 'Print Processors', reference: 'https://attack.mitre.org/techniques/T1547/012', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'printProcessors', }, @@ -11968,7 +7200,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1552.004', name: 'Private Keys', reference: 'https://attack.mitre.org/techniques/T1552/004', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1552', value: 'privateKeys', }, @@ -11980,7 +7212,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.007', name: 'Proc Filesystem', reference: 'https://attack.mitre.org/techniques/T1003/007', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'procFilesystem', }, @@ -11992,7 +7224,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.009', name: 'Proc Memory', reference: 'https://attack.mitre.org/techniques/T1055/009', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'procMemory', }, @@ -12004,7 +7236,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.010', name: 'Process Argument Spoofing', reference: 'https://attack.mitre.org/techniques/T1564/010', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'processArgumentSpoofing', }, @@ -12016,7 +7248,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.013', name: 'Process Doppelgänging', reference: 'https://attack.mitre.org/techniques/T1055/013', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'processDoppelganging', }, @@ -12028,7 +7260,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.012', name: 'Process Hollowing', reference: 'https://attack.mitre.org/techniques/T1055/012', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'processHollowing', }, @@ -12040,7 +7272,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1001.003', name: 'Protocol Impersonation', reference: 'https://attack.mitre.org/techniques/T1001/003', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1001', value: 'protocolImpersonation', }, @@ -12052,7 +7284,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.008', name: 'Ptrace System Calls', reference: 'https://attack.mitre.org/techniques/T1055/008', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'ptraceSystemCalls', }, @@ -12064,7 +7296,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1216.001', name: 'PubPrn', reference: 'https://attack.mitre.org/techniques/T1216/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1216', value: 'pubPrn', }, @@ -12076,7 +7308,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1597.002', name: 'Purchase Technical Data', reference: 'https://attack.mitre.org/techniques/T1597/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1597', value: 'purchaseTechnicalData', }, @@ -12088,7 +7320,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.006', name: 'Python', reference: 'https://attack.mitre.org/techniques/T1059/006', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'python', }, @@ -12100,7 +7332,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1037.004', name: 'RC Scripts', reference: 'https://attack.mitre.org/techniques/T1037/004', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1037', value: 'rcScripts', }, @@ -12112,7 +7344,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1563.002', name: 'RDP Hijacking', reference: 'https://attack.mitre.org/techniques/T1563/002', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1563', value: 'rdpHijacking', }, @@ -12124,7 +7356,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1542.004', name: 'ROMMONkit', reference: 'https://attack.mitre.org/techniques/T1542/004', - tactics: 'defense-evasion,persistence', + tactics: ['defense-evasion', 'persistence'], techniqueId: 'T1542', value: 'rommoNkit', }, @@ -12136,7 +7368,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.007', name: 'Re-opened Applications', reference: 'https://attack.mitre.org/techniques/T1547/007', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'reOpenedApplications', }, @@ -12148,7 +7380,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1600.001', name: 'Reduce Key Space', reference: 'https://attack.mitre.org/techniques/T1600/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1600', value: 'reduceKeySpace', }, @@ -12160,7 +7392,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1498.002', name: 'Reflection Amplification', reference: 'https://attack.mitre.org/techniques/T1498/002', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1498', value: 'reflectionAmplification', }, @@ -12172,7 +7404,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.001', name: 'Registry Run Keys / Startup Folder', reference: 'https://attack.mitre.org/techniques/T1547/001', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'registryRunKeysStartupFolder', }, @@ -12184,7 +7416,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.009', name: 'Regsvcs/Regasm', reference: 'https://attack.mitre.org/techniques/T1218/009', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'regsvcsRegasm', }, @@ -12196,7 +7428,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.010', name: 'Regsvr32', reference: 'https://attack.mitre.org/techniques/T1218/010', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'regsvr32', }, @@ -12208,7 +7440,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1074.002', name: 'Remote Data Staging', reference: 'https://attack.mitre.org/techniques/T1074/002', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1074', value: 'remoteDataStaging', }, @@ -12220,7 +7452,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1021.001', name: 'Remote Desktop Protocol', reference: 'https://attack.mitre.org/techniques/T1021/001', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1021', value: 'remoteDesktopProtocol', }, @@ -12232,7 +7464,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1114.002', name: 'Remote Email Collection', reference: 'https://attack.mitre.org/techniques/T1114/002', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1114', value: 'remoteEmailCollection', }, @@ -12244,7 +7476,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1036.003', name: 'Rename System Utilities', reference: 'https://attack.mitre.org/techniques/T1036/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1036', value: 'renameSystemUtilities', }, @@ -12256,7 +7488,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.009', name: 'Resource Forking', reference: 'https://attack.mitre.org/techniques/T1564/009', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'resourceForking', }, @@ -12268,7 +7500,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1556.005', name: 'Reversible Encryption', reference: 'https://attack.mitre.org/techniques/T1556/005', - tactics: 'credential-access,defense-evasion,persistence', + tactics: ['credential-access', 'defense-evasion', 'persistence'], techniqueId: 'T1556', value: 'reversibleEncryption', }, @@ -12280,7 +7512,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1578.004', name: 'Revert Cloud Instance', reference: 'https://attack.mitre.org/techniques/T1578/004', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1578', value: 'revertCloudInstance', }, @@ -12292,7 +7524,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1036.002', name: 'Right-to-Left Override', reference: 'https://attack.mitre.org/techniques/T1036/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1036', value: 'rightToLeftOverride', }, @@ -12304,7 +7536,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.006', name: 'Run Virtual Instance', reference: 'https://attack.mitre.org/techniques/T1564/006', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'runVirtualInstance', }, @@ -12316,7 +7548,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.011', name: 'Rundll32', reference: 'https://attack.mitre.org/techniques/T1218/011', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'rundll32', }, @@ -12328,7 +7560,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1565.003', name: 'Runtime Data Manipulation', reference: 'https://attack.mitre.org/techniques/T1565/003', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1565', value: 'runtimeDataManipulation', }, @@ -12340,7 +7572,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1606.002', name: 'SAML Tokens', reference: 'https://attack.mitre.org/techniques/T1606/002', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1606', value: 'samlTokens', }, @@ -12352,7 +7584,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1608.006', name: 'SEO Poisoning', reference: 'https://attack.mitre.org/techniques/T1608/006', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1608', value: 'seoPoisoning', }, @@ -12364,7 +7596,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1134.005', name: 'SID-History Injection', reference: 'https://attack.mitre.org/techniques/T1134/005', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1134', value: 'sidHistoryInjection', }, @@ -12376,7 +7608,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1553.003', name: 'SIP and Trust Provider Hijacking', reference: 'https://attack.mitre.org/techniques/T1553/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1553', value: 'sipAndTrustProviderHijacking', }, @@ -12388,7 +7620,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1021.002', name: 'SMB/Windows Admin Shares', reference: 'https://attack.mitre.org/techniques/T1021/002', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1021', value: 'smbWindowsAdminShares', }, @@ -12400,7 +7632,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1602.001', name: 'SNMP (MIB Dump)', reference: 'https://attack.mitre.org/techniques/T1602/001', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1602', value: 'snmpMibDump', }, @@ -12412,7 +7644,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1505.001', name: 'SQL Stored Procedures', reference: 'https://attack.mitre.org/techniques/T1505/001', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1505', value: 'sqlStoredProcedures', }, @@ -12424,7 +7656,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1021.004', name: 'SSH', reference: 'https://attack.mitre.org/techniques/T1021/004', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1021', value: 'ssh', }, @@ -12436,7 +7668,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1098.004', name: 'SSH Authorized Keys', reference: 'https://attack.mitre.org/techniques/T1098/004', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1098', value: 'sshAuthorizedKeys', }, @@ -12448,7 +7680,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1563.001', name: 'SSH Hijacking', reference: 'https://attack.mitre.org/techniques/T1563/001', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1563', value: 'sshHijacking', }, @@ -12460,7 +7692,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1562.009', name: 'Safe Mode Boot', reference: 'https://attack.mitre.org/techniques/T1562/009', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1562', value: 'safeModeBoot', }, @@ -12472,7 +7704,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1596.005', name: 'Scan Databases', reference: 'https://attack.mitre.org/techniques/T1596/005', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1596', value: 'scanDatabases', }, @@ -12484,7 +7716,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1595.001', name: 'Scanning IP Blocks', reference: 'https://attack.mitre.org/techniques/T1595/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1595', value: 'scanningIpBlocks', }, @@ -12496,7 +7728,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1053.005', name: 'Scheduled Task', reference: 'https://attack.mitre.org/techniques/T1053/005', - tactics: 'execution,persistence,privilege-escalation', + tactics: ['execution', 'persistence', 'privilege-escalation'], techniqueId: 'T1053', value: 'scheduledTask', }, @@ -12508,7 +7740,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.002', name: 'Screensaver', reference: 'https://attack.mitre.org/techniques/T1546/002', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'screensaver', }, @@ -12520,7 +7752,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1593.002', name: 'Search Engines', reference: 'https://attack.mitre.org/techniques/T1593/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1593', value: 'searchEngines', }, @@ -12532,7 +7764,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1003.002', name: 'Security Account Manager', reference: 'https://attack.mitre.org/techniques/T1003/002', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1003', value: 'securityAccountManager', }, @@ -12544,7 +7776,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1518.001', name: 'Security Software Discovery', reference: 'https://attack.mitre.org/techniques/T1518/001', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1518', value: 'securitySoftwareDiscovery', }, @@ -12556,7 +7788,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.005', name: 'Security Support Provider', reference: 'https://attack.mitre.org/techniques/T1547/005', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'securitySupportProvider', }, @@ -12568,7 +7800,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1555.002', name: 'Securityd Memory', reference: 'https://attack.mitre.org/techniques/T1555/002', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1555', value: 'securitydMemory', }, @@ -12580,7 +7812,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1583.004', name: 'Server', reference: 'https://attack.mitre.org/techniques/T1583/004', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1583', value: 'server', }, @@ -12592,7 +7824,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1584.004', name: 'Server', reference: 'https://attack.mitre.org/techniques/T1584/004', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1584', value: 'server', }, @@ -12604,7 +7836,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1583.007', name: 'Serverless', reference: 'https://attack.mitre.org/techniques/T1583/007', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1583', value: 'serverless', }, @@ -12616,7 +7848,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1584.007', name: 'Serverless', reference: 'https://attack.mitre.org/techniques/T1584/007', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1584', value: 'serverless', }, @@ -12628,7 +7860,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1569.002', name: 'Service Execution', reference: 'https://attack.mitre.org/techniques/T1569/002', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1569', value: 'serviceExecution', }, @@ -12640,7 +7872,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1499.002', name: 'Service Exhaustion Flood', reference: 'https://attack.mitre.org/techniques/T1499/002', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1499', value: 'serviceExhaustionFlood', }, @@ -12652,7 +7884,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.010', name: 'Services File Permissions Weakness', reference: 'https://attack.mitre.org/techniques/T1574/010', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'servicesFilePermissionsWeakness', }, @@ -12664,7 +7896,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1574.011', name: 'Services Registry Permissions Weakness', reference: 'https://attack.mitre.org/techniques/T1574/011', - tactics: 'persistence,privilege-escalation,defense-evasion', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], techniqueId: 'T1574', value: 'servicesRegistryPermissionsWeakness', }, @@ -12676,7 +7908,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1548.001', name: 'Setuid and Setgid', reference: 'https://attack.mitre.org/techniques/T1548/001', - tactics: 'privilege-escalation,defense-evasion', + tactics: ['privilege-escalation', 'defense-evasion'], techniqueId: 'T1548', value: 'setuidAndSetgid', }, @@ -12688,7 +7920,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1213.002', name: 'Sharepoint', reference: 'https://attack.mitre.org/techniques/T1213/002', - tactics: 'collection', + tactics: ['collection'], techniqueId: 'T1213', value: 'sharepoint', }, @@ -12700,7 +7932,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.009', name: 'Shortcut Modification', reference: 'https://attack.mitre.org/techniques/T1547/009', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'shortcutModification', }, @@ -12712,7 +7944,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1558.002', name: 'Silver Ticket', reference: 'https://attack.mitre.org/techniques/T1558/002', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1558', value: 'silverTicket', }, @@ -12724,7 +7956,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1593.001', name: 'Social Media', reference: 'https://attack.mitre.org/techniques/T1593/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1593', value: 'socialMedia', }, @@ -12736,7 +7968,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1586.001', name: 'Social Media Accounts', reference: 'https://attack.mitre.org/techniques/T1586/001', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1586', value: 'socialMediaAccounts', }, @@ -12748,7 +7980,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1585.001', name: 'Social Media Accounts', reference: 'https://attack.mitre.org/techniques/T1585/001', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1585', value: 'socialMediaAccounts', }, @@ -12760,7 +7992,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1205.002', name: 'Socket Filters', reference: 'https://attack.mitre.org/techniques/T1205/002', - tactics: 'defense-evasion,persistence,command-and-control', + tactics: ['defense-evasion', 'persistence', 'command-and-control'], techniqueId: 'T1205', value: 'socketFilters', }, @@ -12772,7 +8004,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1592.002', name: 'Software', reference: 'https://attack.mitre.org/techniques/T1592/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1592', value: 'software', }, @@ -12784,7 +8016,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.002', name: 'Software Packing', reference: 'https://attack.mitre.org/techniques/T1027/002', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'softwarePacking', }, @@ -12796,7 +8028,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1036.006', name: 'Space after Filename', reference: 'https://attack.mitre.org/techniques/T1036/006', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1036', value: 'spaceAfterFilename', }, @@ -12808,7 +8040,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1566.001', name: 'Spearphishing Attachment', reference: 'https://attack.mitre.org/techniques/T1566/001', - tactics: 'initial-access', + tactics: ['initial-access'], techniqueId: 'T1566', value: 'spearphishingAttachment', }, @@ -12820,7 +8052,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1598.002', name: 'Spearphishing Attachment', reference: 'https://attack.mitre.org/techniques/T1598/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1598', value: 'spearphishingAttachment', }, @@ -12832,7 +8064,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1566.002', name: 'Spearphishing Link', reference: 'https://attack.mitre.org/techniques/T1566/002', - tactics: 'initial-access', + tactics: ['initial-access'], techniqueId: 'T1566', value: 'spearphishingLink', }, @@ -12844,7 +8076,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1598.003', name: 'Spearphishing Link', reference: 'https://attack.mitre.org/techniques/T1598/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1598', value: 'spearphishingLink', }, @@ -12856,7 +8088,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1598.001', name: 'Spearphishing Service', reference: 'https://attack.mitre.org/techniques/T1598/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1598', value: 'spearphishingService', }, @@ -12868,10 +8100,22 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1566.003', name: 'Spearphishing via Service', reference: 'https://attack.mitre.org/techniques/T1566/003', - tactics: 'initial-access', + tactics: ['initial-access'], techniqueId: 'T1566', value: 'spearphishingViaService', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.spoofSecurityAlertingT1562Description', + { defaultMessage: 'Spoof Security Alerting (T1562.011)' } + ), + id: 'T1562.011', + name: 'Spoof Security Alerting', + reference: 'https://attack.mitre.org/techniques/T1562/011', + tactics: ['defense-evasion'], + techniqueId: 'T1562', + value: 'spoofSecurityAlerting', + }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.standardEncodingT1132Description', @@ -12880,7 +8124,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1132.001', name: 'Standard Encoding', reference: 'https://attack.mitre.org/techniques/T1132/001', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1132', value: 'standardEncoding', }, @@ -12892,7 +8136,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1037.005', name: 'Startup Items', reference: 'https://attack.mitre.org/techniques/T1037/005', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1037', value: 'startupItems', }, @@ -12904,7 +8148,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.003', name: 'Steganography', reference: 'https://attack.mitre.org/techniques/T1027/003', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'steganography', }, @@ -12916,7 +8160,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1001.002', name: 'Steganography', reference: 'https://attack.mitre.org/techniques/T1001/002', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1001', value: 'steganography', }, @@ -12928,7 +8172,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1565.001', name: 'Stored Data Manipulation', reference: 'https://attack.mitre.org/techniques/T1565/001', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1565', value: 'storedDataManipulation', }, @@ -12940,7 +8184,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1027.008', name: 'Stripped Payloads', reference: 'https://attack.mitre.org/techniques/T1027/008', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1027', value: 'strippedPayloads', }, @@ -12952,7 +8196,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1548.003', name: 'Sudo and Sudo Caching', reference: 'https://attack.mitre.org/techniques/T1548/003', - tactics: 'privilege-escalation,defense-evasion', + tactics: ['privilege-escalation', 'defense-evasion'], techniqueId: 'T1548', value: 'sudoAndSudoCaching', }, @@ -12964,7 +8208,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1573.001', name: 'Symmetric Cryptography', reference: 'https://attack.mitre.org/techniques/T1573/001', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1573', value: 'symmetricCryptography', }, @@ -12976,7 +8220,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1497.001', name: 'System Checks', reference: 'https://attack.mitre.org/techniques/T1497/001', - tactics: 'defense-evasion,discovery', + tactics: ['defense-evasion', 'discovery'], techniqueId: 'T1497', value: 'systemChecks', }, @@ -12988,7 +8232,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1542.001', name: 'System Firmware', reference: 'https://attack.mitre.org/techniques/T1542/001', - tactics: 'persistence,defense-evasion', + tactics: ['persistence', 'defense-evasion'], techniqueId: 'T1542', value: 'systemFirmware', }, @@ -13000,7 +8244,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1614.001', name: 'System Language Discovery', reference: 'https://attack.mitre.org/techniques/T1614/001', - tactics: 'discovery', + tactics: ['discovery'], techniqueId: 'T1614', value: 'systemLanguageDiscovery', }, @@ -13012,7 +8256,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1543.002', name: 'Systemd Service', reference: 'https://attack.mitre.org/techniques/T1543/002', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1543', value: 'systemdService', }, @@ -13024,7 +8268,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1053.006', name: 'Systemd Timers', reference: 'https://attack.mitre.org/techniques/T1053/006', - tactics: 'execution,persistence,privilege-escalation', + tactics: ['execution', 'persistence', 'privilege-escalation'], techniqueId: 'T1053', value: 'systemdTimers', }, @@ -13036,7 +8280,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1542.005', name: 'TFTP Boot', reference: 'https://attack.mitre.org/techniques/T1542/005', - tactics: 'defense-evasion,persistence', + tactics: ['defense-evasion', 'persistence'], techniqueId: 'T1542', value: 'tftpBoot', }, @@ -13048,7 +8292,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1505.005', name: 'Terminal Services DLL', reference: 'https://attack.mitre.org/techniques/T1505/005', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1505', value: 'terminalServicesDll', }, @@ -13060,7 +8304,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.003', name: 'Thread Execution Hijacking', reference: 'https://attack.mitre.org/techniques/T1055/003', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'threadExecutionHijacking', }, @@ -13072,7 +8316,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.005', name: 'Thread Local Storage', reference: 'https://attack.mitre.org/techniques/T1055/005', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'threadLocalStorage', }, @@ -13084,7 +8328,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1597.001', name: 'Threat Intel Vendors', reference: 'https://attack.mitre.org/techniques/T1597/001', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1597', value: 'threatIntelVendors', }, @@ -13096,7 +8340,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1497.003', name: 'Time Based Evasion', reference: 'https://attack.mitre.org/techniques/T1497/003', - tactics: 'defense-evasion,discovery', + tactics: ['defense-evasion', 'discovery'], techniqueId: 'T1497', value: 'timeBasedEvasion', }, @@ -13108,7 +8352,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.003', name: 'Time Providers', reference: 'https://attack.mitre.org/techniques/T1547/003', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'timeProviders', }, @@ -13120,7 +8364,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1070.006', name: 'Timestomp', reference: 'https://attack.mitre.org/techniques/T1070/006', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1070', value: 'timestomp', }, @@ -13132,7 +8376,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1134.001', name: 'Token Impersonation/Theft', reference: 'https://attack.mitre.org/techniques/T1134/001', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1134', value: 'tokenImpersonationTheft', }, @@ -13144,7 +8388,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1588.002', name: 'Tool', reference: 'https://attack.mitre.org/techniques/T1588/002', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1588', value: 'tool', }, @@ -13156,7 +8400,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1020.001', name: 'Traffic Duplication', reference: 'https://attack.mitre.org/techniques/T1020/001', - tactics: 'exfiltration', + tactics: ['exfiltration'], techniqueId: 'T1020', value: 'trafficDuplication', }, @@ -13168,7 +8412,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1565.002', name: 'Transmitted Data Manipulation', reference: 'https://attack.mitre.org/techniques/T1565/002', - tactics: 'impact', + tactics: ['impact'], techniqueId: 'T1565', value: 'transmittedDataManipulation', }, @@ -13180,7 +8424,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1505.002', name: 'Transport Agent', reference: 'https://attack.mitre.org/techniques/T1505/002', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1505', value: 'transportAgent', }, @@ -13192,7 +8436,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.005', name: 'Trap', reference: 'https://attack.mitre.org/techniques/T1546/005', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'trap', }, @@ -13204,7 +8448,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.004', name: 'Unix Shell', reference: 'https://attack.mitre.org/techniques/T1059/004', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'unixShell', }, @@ -13216,7 +8460,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.004', name: 'Unix Shell Configuration Modification', reference: 'https://attack.mitre.org/techniques/T1546/004', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'unixShellConfigurationModification', }, @@ -13228,7 +8472,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1608.001', name: 'Upload Malware', reference: 'https://attack.mitre.org/techniques/T1608/001', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1608', value: 'uploadMalware', }, @@ -13240,7 +8484,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1608.002', name: 'Upload Tool', reference: 'https://attack.mitre.org/techniques/T1608/002', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1608', value: 'uploadTool', }, @@ -13252,7 +8496,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1497.002', name: 'User Activity Based Checks', reference: 'https://attack.mitre.org/techniques/T1497/002', - tactics: 'defense-evasion,discovery', + tactics: ['defense-evasion', 'discovery'], techniqueId: 'T1497', value: 'userActivityBasedChecks', }, @@ -13264,7 +8508,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1564.007', name: 'VBA Stomping', reference: 'https://attack.mitre.org/techniques/T1564/007', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1564', value: 'vbaStomping', }, @@ -13276,7 +8520,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1055.014', name: 'VDSO Hijacking', reference: 'https://attack.mitre.org/techniques/T1055/014', - tactics: 'defense-evasion,privilege-escalation', + tactics: ['defense-evasion', 'privilege-escalation'], techniqueId: 'T1055', value: 'vdsoHijacking', }, @@ -13288,7 +8532,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1021.005', name: 'VNC', reference: 'https://attack.mitre.org/techniques/T1021/005', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1021', value: 'vnc', }, @@ -13300,7 +8544,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1218.012', name: 'Verclsid', reference: 'https://attack.mitre.org/techniques/T1218/012', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1218', value: 'verclsid', }, @@ -13312,7 +8556,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1584.003', name: 'Virtual Private Server', reference: 'https://attack.mitre.org/techniques/T1584/003', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1584', value: 'virtualPrivateServer', }, @@ -13324,7 +8568,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1583.003', name: 'Virtual Private Server', reference: 'https://attack.mitre.org/techniques/T1583/003', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1583', value: 'virtualPrivateServer', }, @@ -13336,7 +8580,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.005', name: 'Visual Basic', reference: 'https://attack.mitre.org/techniques/T1059/005', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'visualBasic', }, @@ -13348,7 +8592,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1588.006', name: 'Vulnerabilities', reference: 'https://attack.mitre.org/techniques/T1588/006', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1588', value: 'vulnerabilities', }, @@ -13360,7 +8604,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1595.002', name: 'Vulnerability Scanning', reference: 'https://attack.mitre.org/techniques/T1595/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1595', value: 'vulnerabilityScanning', }, @@ -13372,7 +8616,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1596.002', name: 'WHOIS', reference: 'https://attack.mitre.org/techniques/T1596/002', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1596', value: 'whois', }, @@ -13384,7 +8628,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1606.001', name: 'Web Cookies', reference: 'https://attack.mitre.org/techniques/T1606/001', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1606', value: 'webCookies', }, @@ -13396,7 +8640,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1056.003', name: 'Web Portal Capture', reference: 'https://attack.mitre.org/techniques/T1056/003', - tactics: 'collection,credential-access', + tactics: ['collection', 'credential-access'], techniqueId: 'T1056', value: 'webPortalCapture', }, @@ -13408,7 +8652,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1071.001', name: 'Web Protocols', reference: 'https://attack.mitre.org/techniques/T1071/001', - tactics: 'command-and-control', + tactics: ['command-and-control'], techniqueId: 'T1071', value: 'webProtocols', }, @@ -13420,7 +8664,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1583.006', name: 'Web Services', reference: 'https://attack.mitre.org/techniques/T1583/006', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1583', value: 'webServices', }, @@ -13432,7 +8676,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1584.006', name: 'Web Services', reference: 'https://attack.mitre.org/techniques/T1584/006', - tactics: 'resource-development', + tactics: ['resource-development'], techniqueId: 'T1584', value: 'webServices', }, @@ -13444,7 +8688,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1550.004', name: 'Web Session Cookie', reference: 'https://attack.mitre.org/techniques/T1550/004', - tactics: 'defense-evasion,lateral-movement', + tactics: ['defense-evasion', 'lateral-movement'], techniqueId: 'T1550', value: 'webSessionCookie', }, @@ -13456,7 +8700,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1505.003', name: 'Web Shell', reference: 'https://attack.mitre.org/techniques/T1505/003', - tactics: 'persistence', + tactics: ['persistence'], techniqueId: 'T1505', value: 'webShell', }, @@ -13468,7 +8712,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1059.003', name: 'Windows Command Shell', reference: 'https://attack.mitre.org/techniques/T1059/003', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1059', value: 'windowsCommandShell', }, @@ -13480,7 +8724,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1555.004', name: 'Windows Credential Manager', reference: 'https://attack.mitre.org/techniques/T1555/004', - tactics: 'credential-access', + tactics: ['credential-access'], techniqueId: 'T1555', value: 'windowsCredentialManager', }, @@ -13492,7 +8736,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1222.001', name: 'Windows File and Directory Permissions Modification', reference: 'https://attack.mitre.org/techniques/T1222/001', - tactics: 'defense-evasion', + tactics: ['defense-evasion'], techniqueId: 'T1222', value: 'windowsFileAndDirectoryPermissionsModification', }, @@ -13504,7 +8748,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1546.003', name: 'Windows Management Instrumentation Event Subscription', reference: 'https://attack.mitre.org/techniques/T1546/003', - tactics: 'privilege-escalation,persistence', + tactics: ['privilege-escalation', 'persistence'], techniqueId: 'T1546', value: 'windowsManagementInstrumentationEventSubscription', }, @@ -13516,7 +8760,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1021.006', name: 'Windows Remote Management', reference: 'https://attack.mitre.org/techniques/T1021/006', - tactics: 'lateral-movement', + tactics: ['lateral-movement'], techniqueId: 'T1021', value: 'windowsRemoteManagement', }, @@ -13528,7 +8772,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1543.003', name: 'Windows Service', reference: 'https://attack.mitre.org/techniques/T1543/003', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1543', value: 'windowsService', }, @@ -13540,7 +8784,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.004', name: 'Winlogon Helper DLL', reference: 'https://attack.mitre.org/techniques/T1547/004', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'winlogonHelperDll', }, @@ -13552,7 +8796,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1595.003', name: 'Wordlist Scanning', reference: 'https://attack.mitre.org/techniques/T1595/003', - tactics: 'reconnaissance', + tactics: ['reconnaissance'], techniqueId: 'T1595', value: 'wordlistScanning', }, @@ -13564,7 +8808,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1547.013', name: 'XDG Autostart Entries', reference: 'https://attack.mitre.org/techniques/T1547/013', - tactics: 'persistence,privilege-escalation', + tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', value: 'xdgAutostartEntries', }, @@ -13576,7 +8820,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ id: 'T1559.003', name: 'XPC Services', reference: 'https://attack.mitre.org/techniques/T1559/003', - tactics: 'execution', + tactics: ['execution'], techniqueId: 'T1559', value: 'xpcServices', }, diff --git a/x-pack/plugins/security_solution/public/detections/mitre/types.ts b/x-pack/plugins/security_solution/public/detections/mitre/types.ts index 4f6de5ad1f6fb..f6e437e5a096d 100644 --- a/x-pack/plugins/security_solution/public/detections/mitre/types.ts +++ b/x-pack/plugins/security_solution/public/detections/mitre/types.ts @@ -5,43 +5,18 @@ * 2.0. */ -export interface MitreOptions { - id: string; - name: string; - reference: string; - value: string; -} - -export interface MitreTacticsOptions extends MitreOptions { - text: string; -} - -export interface MitreTechniquesOptions extends MitreOptions { - label: string; - tactics: string; -} - -export interface MitreSubtechniquesOptions extends MitreTechniquesOptions { - techniqueId: string; -} - export interface MitreTactic { id: string; name: string; reference: string; // A link to the tactic's page + value: string; // A camelCased version of the name we use to reference the tactic + label: string; // An i18n internationalized version of the name we use for rendering } -export interface MitreTechnique { - id: string; - name: string; - reference: string; // A link to the technique's page +export interface MitreTechnique extends MitreTactic { tactics: string[]; // Tactics this technique assigned to (lowercase dash separated) } -export interface MitreSubTechnique { - id: string; - name: string; - reference: string; // A link to the subtechnique's page - tactics: string[]; // Tactics this technique assigned to (lowercase dash separated) +export interface MitreSubTechnique extends MitreTechnique { techniqueId: string; // A technique id this subtechnique assigned to } diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx index df01e7f1b1450..8935ff132f246 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx @@ -11,6 +11,7 @@ import { Routes, Route } from '@kbn/shared-ux-router'; import { ALERT_RULE_NAME, TIMESTAMP } from '@kbn/rule-data-utils'; import { EuiSpacer } from '@elastic/eui'; import { useDispatch } from 'react-redux'; +import type { RunTimeMappings } from '../../../../common/api/search_strategy'; import { timelineActions } from '../../../timelines/store/timeline'; import { TimelineId } from '../../../../common/types/timeline'; import { useGetFieldsData } from '../../../common/hooks/use_get_fields_data'; @@ -42,7 +43,7 @@ export const AlertDetailsPage = memo(() => { const [loading, detailsData, searchHit, dataAsNestedObject] = useTimelineEventsDetails({ indexName, eventId, - runtimeMappings: sourcererDataView.runtimeMappings, + runtimeMappings: sourcererDataView.runtimeMappings as RunTimeMappings, skip: !eventID, }); const dataNotFound = !loading && !detailsData; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/index.ts b/x-pack/plugins/security_solution/public/exceptions/components/index.ts index 8de30af731151..66d203327cc37 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/components/index.ts @@ -8,7 +8,8 @@ export * from './create_shared_exception_list'; export * from './exceptions_list_card'; export * from './exceptions_utility'; export * from './import_exceptions_list_flyout'; -export * from './list_details_link_anchor'; +export * from './link_to_rule_details'; +export * from './link_to_list_details'; export * from './list_exception_items'; export * from './list_with_search'; export * from './manage_rules'; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/link_to_list_details/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/link_to_list_details/index.tsx new file mode 100644 index 0000000000000..20e99dc7de60b --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/components/link_to_list_details/index.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 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 type { FC } from 'react'; + +import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; +import { SecurityPageName } from '../../../../common/constants'; + +interface LinkToListDetailsProps { + linkTitle: string; + listId: string; + external?: boolean; + dataTestSubj?: string; +} +// This component should be removed and moved to @kbn/securitysolution-exception-list-components +// once all the building components get moved + +const LinkToListDetailsComponent: FC = ({ + linkTitle, + listId, + external, + dataTestSubj, +}) => { + return ( + + {linkTitle} + + ); +}; + +LinkToListDetailsComponent.displayName = 'LinkToListDetailsComponent'; + +export const LinkToListDetails = React.memo(LinkToListDetailsComponent); + +LinkToListDetails.displayName = 'LinkToListDetails'; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/link_to_rule_details/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/link_to_rule_details/index.tsx new file mode 100644 index 0000000000000..d2526f6fdef03 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/components/link_to_rule_details/index.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { FC } from 'react'; + +import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; +import { RuleDetailTabs } from '../../../detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs'; +import { SecurityPageName } from '../../../../common/constants'; +import { getRuleDetailsTabUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; + +interface LinkToRuleDetailsProps { + referenceName: string; + referenceId: string; + external?: boolean; + dataTestSubj?: string; +} +// This component should be removed and moved to @kbn/securitysolution-exception-list-components +// once all the building components get moved + +const LinkToRuleDetailsComponent: FC = ({ + referenceName, + referenceId, + external, + dataTestSubj, +}) => { + return ( + + {referenceName} + + ); +}; + +LinkToRuleDetailsComponent.displayName = 'LinkToRuleDetailsComponent'; + +export const LinkToRuleDetails = React.memo(LinkToRuleDetailsComponent); + +LinkToRuleDetails.displayName = 'LinkToRuleDetails'; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/list_details_link_anchor/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/list_details_link_anchor/index.tsx deleted file mode 100644 index 4b54600fc454d..0000000000000 --- a/x-pack/plugins/security_solution/public/exceptions/components/list_details_link_anchor/index.tsx +++ /dev/null @@ -1,40 +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 type { FC } from 'react'; -import { RuleDetailTabs } from '../../../detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs'; -import { SecurityPageName } from '../../../../common/constants'; -import { getRuleDetailsTabUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; -import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; - -interface LinkAnchorProps { - referenceName: string; - referenceId: string; - external?: boolean; -} -// This component should be removed and moved to @kbn/securitysolution-exception-list-components -// once all the building components get moved - -const LinkAnchor: FC = ({ referenceName, referenceId, external }) => { - return ( - - {referenceName} - - ); -}; - -LinkAnchor.displayName = 'LinkAnchor'; - -export const ListDetailsLinkAnchor = React.memo(LinkAnchor); - -ListDetailsLinkAnchor.displayName = 'ListDetailsLinkAnchor'; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx index ee45e7414184f..1b1b6ad36d1ea 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx @@ -19,7 +19,7 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import type { Pagination } from '@elastic/eui'; import { FormattedDate } from '../../../common/components/formatted_date'; import { getFormattedComments } from '../../utils/ui.helpers'; -import { ListDetailsLinkAnchor } from '../list_details_link_anchor'; +import { LinkToRuleDetails } from '../link_to_rule_details'; import { ExceptionsUtility } from '../exceptions_utility'; import * as i18n from '../../translations/list_exception_items'; @@ -89,7 +89,7 @@ const ListExceptionItemsComponent: FC = ({ onEditExceptionItem={onEditExceptionItem} onDeleteException={onDeleteException} getFormattedComments={getFormattedComments} - securityLinkAnchorComponent={ListDetailsLinkAnchor} + securityLinkAnchorComponent={LinkToRuleDetails} formattedDateComponent={FormattedDate} onCreateExceptionListItem={onCreateExceptionListItem} exceptionsUtilityComponent={() => diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_create_shared_list/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_create_shared_list/index.tsx index 4789a762bf71b..4aee4dea525a4 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_create_shared_list/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_create_shared_list/index.tsx @@ -24,6 +24,7 @@ export const createSharedExceptionList = async ({ description: string; }): Promise => { const res: ExceptionListSchema = await http.post(SHARED_EXCEPTION_LIST_URL, { + version: '2023-10-31', body: JSON.stringify({ name, description }), headers: { 'Content-Type': 'application/json' }, method: 'POST', diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx index 4272632d28a10..c153f1e983b0a 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx @@ -22,7 +22,7 @@ import type { Rule } from '../../../detection_engine/rule_management/logic/types import { MissingPrivilegesCallOut } from '../../../detections/components/callouts/missing_privileges_callout'; import { NotFoundPage } from '../../../app/404'; import { AutoDownload } from '../../../common/components/auto_download/auto_download'; -import { ListWithSearch, ManageRules, ListDetailsLinkAnchor } from '../../components'; +import { ListWithSearch, ManageRules, LinkToRuleDetails } from '../../components'; import { useListDetailsView } from '../../hooks'; import * as i18n from '../../translations'; import type { CheckExceptionTtlActionTypes } from '../../components/expired_exceptions_list_items_modal'; @@ -109,7 +109,7 @@ export const ListsDetailViewComponent: FC = () => { isReadonly={isReadOnly} canUserEditList={canUserEditList} backOptions={headerBackOptions} - securityLinkAnchorComponent={ListDetailsLinkAnchor} + securityLinkAnchorComponent={LinkToRuleDetails} onEditListDetails={onEditListDetails} onExportList={handleExportList} onDeleteList={handleDelete} diff --git a/x-pack/plugins/security_solution/public/explore/components/paginated_table/helpers.ts b/x-pack/plugins/security_solution/public/explore/components/paginated_table/helpers.ts index 2623079ba0046..4a80155dcb299 100644 --- a/x-pack/plugins/security_solution/public/explore/components/paginated_table/helpers.ts +++ b/x-pack/plugins/security_solution/public/explore/components/paginated_table/helpers.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { PaginationInputPaginated } from '../../../../common/search_strategy'; +import type { PaginationInputPaginatedInput } from '../../../../common/api/search_strategy'; export const generateTablePaginationOptions = ( activePage: number, limit: number, isBucketSort?: boolean -): PaginationInputPaginated => { +): PaginationInputPaginatedInput => { const cursorStart = activePage * limit; return { activePage, diff --git a/x-pack/plugins/security_solution/public/explore/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/explore/containers/authentications/index.tsx index d3da701b4c6bb..cd2b0185fe83d 100644 --- a/x-pack/plugins/security_solution/public/explore/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/containers/authentications/index.tsx @@ -8,10 +8,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import deepEqual from 'fast-deep-equal'; +import type { UserAuthenticationsRequestOptionsInput } from '../../../../common/api/search_strategy'; import type { AuthenticationsEdges, AuthStackByField, - UserAuthenticationsRequestOptions, } from '../../../../common/search_strategy/security_solution'; import { UsersQueries } from '../../../../common/search_strategy/security_solution'; import type { PageInfoPaginated, SortField } from '../../../../common/search_strategy'; @@ -58,7 +58,7 @@ export const useAuthentications = ({ startDate, }: UseAuthentications): [boolean, AuthenticationArgs] => { const [authenticationsRequest, setAuthenticationsRequest] = - useState(null); + useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -123,7 +123,7 @@ export const useAuthentications = ({ useEffect(() => { setAuthenticationsRequest((prevRequest) => { - const myRequest = { + const myRequest: UserAuthenticationsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: UsersQueries.authentications, diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx index 185dfc687fb4a..3c76bc5dcbf9c 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx @@ -9,6 +9,7 @@ import React, { useMemo, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import type { HostEcs, OsEcs } from '@kbn/securitysolution-ecs'; +import { HostsFields } from '../../../../../common/api/search_strategy/hosts/model/sort'; import type { Columns, Criteria, @@ -25,7 +26,6 @@ import type { HostItem, HostsSortField, } from '../../../../../common/search_strategy/security_solution/hosts'; -import { HostsFields } from '../../../../../common/search_strategy/security_solution/hosts'; import type { Direction, RiskSeverity } from '../../../../../common/search_strategy'; import { SecurityPageName } from '../../../../../common/constants'; import { HostsTableType } from '../../store/model'; @@ -209,6 +209,8 @@ const getNodeField = (field: HostsFields): string => { return 'node.host.name'; case HostsFields.lastSeen: return 'node.lastSeen'; + default: + return ''; } }; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/containers/hosts/index.tsx index 4a385f436519f..bd0b35c4e18fb 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/containers/hosts/index.tsx @@ -8,17 +8,14 @@ import deepEqual from 'fast-deep-equal'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import type { HostsRequestOptionsInput } from '../../../../../common/api/search_strategy'; import type { inputsModel, State } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import type { hostsModel } from '../../store'; import { hostsSelectors } from '../../store'; import { generateTablePaginationOptions } from '../../../components/paginated_table/helpers'; -import type { - HostsEdges, - PageInfoPaginated, - HostsRequestOptions, -} from '../../../../../common/search_strategy'; +import type { HostsEdges, PageInfoPaginated } from '../../../../../common/search_strategy'; import { HostsQueries } from '../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../common/typed_json'; @@ -67,7 +64,7 @@ export const useAllHost = ({ const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled'); - const [hostsRequest, setHostRequest] = useState(null); + const [hostsRequest, setHostRequest] = useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -133,7 +130,7 @@ export const useAllHost = ({ useEffect(() => { setHostRequest((prevRequest) => { - const myRequest = { + const myRequest: HostsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: HostsQueries.hosts, diff --git a/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/hosts/index.tsx index bf78194964f09..d84fa8665b7ab 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/hosts/index.tsx @@ -10,14 +10,12 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import type { KpiHostsRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; -import type { - HostsKpiHostsRequestOptions, - HostsKpiHostsStrategyResponse, -} from '../../../../../../common/search_strategy'; +import type { HostsKpiHostsStrategyResponse } from '../../../../../../common/search_strategy'; import { HostsKpiQueries } from '../../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../../common/typed_json'; @@ -55,7 +53,7 @@ export const useHostsKpiHosts = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = - useState(null); + useState(null); const [hostsKpiHostsResponse, setHostsKpiHostsResponse] = useState({ hosts: 0, @@ -71,7 +69,7 @@ export const useHostsKpiHosts = ({ const { addError, addWarning } = useAppToasts(); const hostsKpiHostsSearch = useCallback( - (request: HostsKpiHostsRequestOptions | null) => { + (request: KpiHostsRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -80,7 +78,7 @@ export const useHostsKpiHosts = ({ setLoading(true); searchSubscription$.current = data.search - .search(request, { + .search(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) @@ -121,7 +119,7 @@ export const useHostsKpiHosts = ({ useEffect(() => { setHostsKpiHostsRequest((prevRequest) => { - const myRequest = { + const myRequest: KpiHostsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: HostsKpiQueries.kpiHosts, diff --git a/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/unique_ips/index.tsx index 730bb48b97a23..740282030286c 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -10,14 +10,12 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import type { KpiUniqueIpsRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; -import type { - HostsKpiUniqueIpsRequestOptions, - HostsKpiUniqueIpsStrategyResponse, -} from '../../../../../../common/search_strategy'; +import type { HostsKpiUniqueIpsStrategyResponse } from '../../../../../../common/search_strategy'; import { HostsKpiQueries } from '../../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../../common/typed_json'; @@ -57,7 +55,7 @@ export const useHostsKpiUniqueIps = ({ const [loading, setLoading] = useState(false); const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = - useState(null); + useState(null); const [hostsKpiUniqueIpsResponse, setHostsKpiUniqueIpsResponse] = useState( { @@ -77,7 +75,7 @@ export const useHostsKpiUniqueIps = ({ const { addError, addWarning } = useAppToasts(); const hostsKpiUniqueIpsSearch = useCallback( - (request: HostsKpiUniqueIpsRequestOptions | null) => { + (request: KpiUniqueIpsRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -86,7 +84,7 @@ export const useHostsKpiUniqueIps = ({ abortCtrl.current = new AbortController(); setLoading(true); searchSubscription$.current = data.search - .search(request, { + .search(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) @@ -129,7 +127,7 @@ export const useHostsKpiUniqueIps = ({ useEffect(() => { setHostsKpiUniqueIpsRequest((prevRequest) => { - const myRequest = { + const myRequest: KpiUniqueIpsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: HostsKpiQueries.kpiUniqueIps, diff --git a/x-pack/plugins/security_solution/public/explore/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/containers/uncommon_processes/index.tsx index 49facc76c33bc..078c2faa49982 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/containers/uncommon_processes/index.tsx @@ -8,6 +8,7 @@ import deepEqual from 'fast-deep-equal'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import type { HostUncommonProcessesRequestOptionsInput } from '../../../../../common/api/search_strategy'; import type { inputsModel, State } from '../../../../common/store'; import { generateTablePaginationOptions } from '../../../components/paginated_table/helpers'; @@ -18,7 +19,6 @@ import type { SortField, PageInfoPaginated, HostsUncommonProcessesEdges, - HostsUncommonProcessesRequestOptions, } from '../../../../../common/search_strategy'; import { HostsQueries } from '../../../../../common/search_strategy'; @@ -66,7 +66,7 @@ export const useUncommonProcesses = ({ getUncommonProcessesSelector(state, type) ); const [uncommonProcessesRequest, setUncommonProcessesRequest] = - useState(null); + useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -121,7 +121,7 @@ export const useUncommonProcesses = ({ useEffect(() => { setUncommonProcessesRequest((prevRequest) => { - const myRequest = { + const myRequest: HostUncommonProcessesRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: HostsQueries.uncommonProcesses, diff --git a/x-pack/plugins/security_solution/public/explore/hosts/store/helpers.test.ts b/x-pack/plugins/security_solution/public/explore/hosts/store/helpers.test.ts index 2c85317a0f4fc..e70267fbdd23a 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/store/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/explore/hosts/store/helpers.test.ts @@ -9,7 +9,8 @@ import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../../../common/ import type { HostsModel } from './model'; import { HostsTableType, HostsType } from './model'; import { setHostsQueriesActivePageToZero } from './helpers'; -import { Direction, HostsFields, RiskScoreFields } from '../../../../common/search_strategy'; +import { Direction, RiskScoreFields } from '../../../../common/search_strategy'; +import { HostsFields } from '../../../../common/api/search_strategy/hosts/model/sort'; export const mockHostsState: HostsModel = { page: { diff --git a/x-pack/plugins/security_solution/public/explore/hosts/store/model.ts b/x-pack/plugins/security_solution/public/explore/hosts/store/model.ts index d0b05ef57ae8d..a83b57ed4b0d8 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/store/model.ts +++ b/x-pack/plugins/security_solution/public/explore/hosts/store/model.ts @@ -5,9 +5,9 @@ * 2.0. */ +import type { HostsFields } from '../../../../common/api/search_strategy/hosts/model/sort'; import type { Direction } from '../../../../common/search_strategy'; import type { - HostsFields, RiskScoreSortField, RiskSeverity, } from '../../../../common/search_strategy/security_solution'; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/explore/hosts/store/reducer.ts index 7208f331b1263..2b4e71502cb6b 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/explore/hosts/store/reducer.ts @@ -6,7 +6,8 @@ */ import { reducerWithInitialState } from 'typescript-fsa-reducers'; -import { Direction, HostsFields, RiskScoreFields } from '../../../../common/search_strategy'; +import { HostsFields } from '../../../../common/api/search_strategy/hosts/model/sort'; +import { Direction, RiskScoreFields } from '../../../../common/search_strategy'; import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../../../common/store/constants'; diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/dns/index.tsx index cf52d2b4ee0eb..ce7fa7e01886e 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/dns/index.tsx @@ -11,14 +11,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { isCompleteResponse } from '@kbn/data-plugin/common'; +import type { NetworkKpiDnsRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; -import type { - NetworkKpiDnsRequestOptions, - NetworkKpiDnsStrategyResponse, -} from '../../../../../../common/search_strategy'; +import type { NetworkKpiDnsStrategyResponse } from '../../../../../../common/search_strategy'; import { NetworkKpiQueries } from '../../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../../common/typed_json'; @@ -57,7 +55,7 @@ export const useNetworkKpiDns = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [networkKpiDnsRequest, setNetworkKpiDnsRequest] = - useState(null); + useState(null); const [networkKpiDnsResponse, setNetworkKpiDnsResponse] = useState({ dnsQueries: 0, @@ -72,7 +70,7 @@ export const useNetworkKpiDns = ({ const { addError } = useAppToasts(); const networkKpiDnsSearch = useCallback( - (request: NetworkKpiDnsRequestOptions | null) => { + (request: NetworkKpiDnsRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -82,7 +80,7 @@ export const useNetworkKpiDns = ({ setLoading(true); searchSubscription$.current = data.search - .search(request, { + .search(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) @@ -118,7 +116,7 @@ export const useNetworkKpiDns = ({ useEffect(() => { setNetworkKpiDnsRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkKpiDnsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.dns, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/network_events/index.tsx index 378120d155e16..40e238e73d56d 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/network_events/index.tsx @@ -11,14 +11,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { isCompleteResponse } from '@kbn/data-plugin/common'; +import type { NetworkKpiEventsRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; -import type { - NetworkKpiNetworkEventsRequestOptions, - NetworkKpiNetworkEventsStrategyResponse, -} from '../../../../../../common/search_strategy'; +import type { NetworkKpiNetworkEventsStrategyResponse } from '../../../../../../common/search_strategy'; import { NetworkKpiQueries } from '../../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../../common/typed_json'; @@ -57,7 +55,7 @@ export const useNetworkKpiNetworkEvents = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [networkKpiNetworkEventsRequest, setNetworkKpiNetworkEventsRequest] = - useState(null); + useState(null); const [networkKpiNetworkEventsResponse, setNetworkKpiNetworkEventsResponse] = useState({ @@ -73,7 +71,7 @@ export const useNetworkKpiNetworkEvents = ({ const { addError } = useAppToasts(); const networkKpiNetworkEventsSearch = useCallback( - (request: NetworkKpiNetworkEventsRequestOptions | null) => { + (request: NetworkKpiEventsRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -83,7 +81,7 @@ export const useNetworkKpiNetworkEvents = ({ setLoading(true); searchSubscription$.current = data.search - .search( + .search( request, { strategy: 'securitySolutionSearchStrategy', @@ -122,7 +120,7 @@ export const useNetworkKpiNetworkEvents = ({ useEffect(() => { setNetworkKpiNetworkEventsRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkKpiEventsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.networkEvents, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/tls_handshakes/index.tsx index b53c07640220e..5bede07b4ce2d 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/tls_handshakes/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/tls_handshakes/index.tsx @@ -11,14 +11,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { isCompleteResponse } from '@kbn/data-plugin/common'; +import type { NetworkKpiTlsHandshakesRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; -import type { - NetworkKpiTlsHandshakesRequestOptions, - NetworkKpiTlsHandshakesStrategyResponse, -} from '../../../../../../common/search_strategy'; +import type { NetworkKpiTlsHandshakesStrategyResponse } from '../../../../../../common/search_strategy'; import { NetworkKpiQueries } from '../../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../../common/typed_json'; @@ -57,7 +55,7 @@ export const useNetworkKpiTlsHandshakes = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [networkKpiTlsHandshakesRequest, setNetworkKpiTlsHandshakesRequest] = - useState(null); + useState(null); const [networkKpiTlsHandshakesResponse, setNetworkKpiTlsHandshakesResponse] = useState({ @@ -73,7 +71,7 @@ export const useNetworkKpiTlsHandshakes = ({ const { addError } = useAppToasts(); const networkKpiTlsHandshakesSearch = useCallback( - (request: NetworkKpiTlsHandshakesRequestOptions | null) => { + (request: NetworkKpiTlsHandshakesRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -82,13 +80,13 @@ export const useNetworkKpiTlsHandshakes = ({ setLoading(true); searchSubscription$.current = data.search - .search( - request, - { - strategy: 'securitySolutionSearchStrategy', - abortSignal: abortCtrl.current.signal, - } - ) + .search< + NetworkKpiTlsHandshakesRequestOptionsInput, + NetworkKpiTlsHandshakesStrategyResponse + >(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) .subscribe({ next: (response) => { if (isCompleteResponse(response)) { @@ -121,7 +119,7 @@ export const useNetworkKpiTlsHandshakes = ({ useEffect(() => { setNetworkKpiTlsHandshakesRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkKpiTlsHandshakesRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.tlsHandshakes, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_flows/index.tsx index 6ff0eb5372a19..8172c700fef12 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_flows/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_flows/index.tsx @@ -11,14 +11,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { isCompleteResponse } from '@kbn/data-plugin/common'; +import type { NetworkKpiUniqueFlowsRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; -import type { - NetworkKpiUniqueFlowsRequestOptions, - NetworkKpiUniqueFlowsStrategyResponse, -} from '../../../../../../common/search_strategy'; +import type { NetworkKpiUniqueFlowsStrategyResponse } from '../../../../../../common/search_strategy'; import { NetworkKpiQueries } from '../../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../../common/typed_json'; @@ -57,7 +55,7 @@ export const useNetworkKpiUniqueFlows = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [networkKpiUniqueFlowsRequest, setNetworkKpiUniqueFlowsRequest] = - useState(null); + useState(null); const [networkKpiUniqueFlowsResponse, setNetworkKpiUniqueFlowsResponse] = useState({ @@ -73,7 +71,7 @@ export const useNetworkKpiUniqueFlows = ({ const { addError } = useAppToasts(); const networkKpiUniqueFlowsSearch = useCallback( - (request: NetworkKpiUniqueFlowsRequestOptions | null) => { + (request: NetworkKpiUniqueFlowsRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -82,7 +80,7 @@ export const useNetworkKpiUniqueFlows = ({ abortCtrl.current = new AbortController(); setLoading(true); searchSubscription$.current = data.search - .search( + .search( request, { strategy: 'securitySolutionSearchStrategy', @@ -121,7 +119,7 @@ export const useNetworkKpiUniqueFlows = ({ useEffect(() => { setNetworkKpiUniqueFlowsRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkKpiUniqueFlowsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.uniqueFlows, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_private_ips/index.tsx index 45435665dba41..d214a8d30bd16 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_private_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/kpi_network/unique_private_ips/index.tsx @@ -11,13 +11,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { isCompleteResponse } from '@kbn/data-plugin/common'; +import type { NetworkKpiUniquePrivateIpsRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; import type { NetworkKpiHistogramData, - NetworkKpiUniquePrivateIpsRequestOptions, NetworkKpiUniquePrivateIpsStrategyResponse, } from '../../../../../../common/search_strategy'; import { NetworkKpiQueries } from '../../../../../../common/search_strategy'; @@ -61,7 +61,7 @@ export const useNetworkKpiUniquePrivateIps = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [networkKpiUniquePrivateIpsRequest, setNetworkKpiUniquePrivateIpsRequest] = - useState(null); + useState(null); const [networkKpiUniquePrivateIpsResponse, setNetworkKpiUniquePrivateIpsResponse] = useState({ @@ -80,7 +80,7 @@ export const useNetworkKpiUniquePrivateIps = ({ const { addError } = useAppToasts(); const networkKpiUniquePrivateIpsSearch = useCallback( - (request: NetworkKpiUniquePrivateIpsRequestOptions | null) => { + (request: NetworkKpiUniquePrivateIpsRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -91,7 +91,7 @@ export const useNetworkKpiUniquePrivateIps = ({ searchSubscription$.current = data.search .search< - NetworkKpiUniquePrivateIpsRequestOptions, + NetworkKpiUniquePrivateIpsRequestOptionsInput, NetworkKpiUniquePrivateIpsStrategyResponse >(request, { strategy: 'securitySolutionSearchStrategy', @@ -133,7 +133,7 @@ export const useNetworkKpiUniquePrivateIps = ({ useEffect(() => { setNetworkKpiUniquePrivateIpsRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkKpiUniquePrivateIpsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.uniquePrivateIps, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/network_dns/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/network_dns/index.tsx index 326d0112e544e..1ad9def6eba32 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/network_dns/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/network_dns/index.tsx @@ -8,17 +8,14 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; +import type { NetworkDnsRequestOptionsInput } from '../../../../../common/api/search_strategy'; import type { ESTermQuery } from '../../../../../common/typed_json'; import type { inputsModel } from '../../../../common/store'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { createFilter } from '../../../../common/containers/helpers'; import { generateTablePaginationOptions } from '../../../components/paginated_table/helpers'; import { networkSelectors } from '../../store'; -import type { - NetworkDnsRequestOptions, - NetworkDnsEdges, - PageInfoPaginated, -} from '../../../../../common/search_strategy'; +import type { NetworkDnsEdges, PageInfoPaginated } from '../../../../../common/search_strategy'; import { NetworkQueries } from '../../../../../common/search_strategy'; import * as i18n from './translations'; import type { InspectResponse } from '../../../../types'; @@ -58,7 +55,9 @@ export const useNetworkDns = ({ const getNetworkDnsSelector = useMemo(() => networkSelectors.dnsSelector(), []); const { activePage, sort, isPtrIncluded, limit } = useDeepEqualSelector(getNetworkDnsSelector); - const [networkDnsRequest, setNetworkDnsRequest] = useState(null); + const [networkDnsRequest, setNetworkDnsRequest] = useState( + null + ); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -113,7 +112,7 @@ export const useNetworkDns = ({ useEffect(() => { setNetworkDnsRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkDnsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, isPtrIncluded, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/network_http/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/network_http/index.tsx index 68d0d97f258b6..af4a86b2cd11b 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/network_http/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/network_http/index.tsx @@ -8,6 +8,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; +import type { NetworkHttpRequestOptionsInput } from '../../../../../common/api/search_strategy'; import type { ESTermQuery } from '../../../../../common/typed_json'; import type { inputsModel } from '../../../../common/store'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -18,7 +19,6 @@ import { networkSelectors } from '../../store'; import type { NetworkHttpEdges, PageInfoPaginated, - NetworkHttpRequestOptions, SortField, } from '../../../../../common/search_strategy'; import { NetworkQueries } from '../../../../../common/search_strategy'; @@ -65,7 +65,9 @@ export const useNetworkHttp = ({ const getHttpSelector = useMemo(() => networkSelectors.httpSelector(), []); const { activePage, limit, sort } = useDeepEqualSelector((state) => getHttpSelector(state, type)); - const [networkHttpRequest, setHostRequest] = useState(null); + const [networkHttpRequest, setHostRequest] = useState( + null + ); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -132,7 +134,7 @@ export const useNetworkHttp = ({ useEffect(() => { setHostRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkHttpRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkQueries.http, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/network_top_countries/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/network_top_countries/index.tsx index 5d6b45b12b422..330af1e174b87 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/network_top_countries/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/network_top_countries/index.tsx @@ -8,6 +8,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; +import type { NetworkTopCountriesRequestOptionsInput } from '../../../../../common/api/search_strategy'; import type { ESTermQuery } from '../../../../../common/typed_json'; import type { inputsModel } from '../../../../common/store'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -18,7 +19,6 @@ import { networkSelectors } from '../../store'; import type { FlowTargetSourceDest, NetworkTopCountriesEdges, - NetworkTopCountriesRequestOptions, PageInfoPaginated, } from '../../../../../common/search_strategy'; import { NetworkQueries } from '../../../../../common/search_strategy'; @@ -68,7 +68,7 @@ export const useNetworkTopCountries = ({ ); const [networkTopCountriesRequest, setNetworkTopCountriesRequest] = - useState(null); + useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -135,7 +135,7 @@ export const useNetworkTopCountries = ({ useEffect(() => { setNetworkTopCountriesRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkTopCountriesRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkQueries.topCountries, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/network_top_n_flow/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/network_top_n_flow/index.tsx index a90ba02f5c17d..07146118f11c9 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/network_top_n_flow/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/network_top_n_flow/index.tsx @@ -8,6 +8,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; +import type { NetworkTopNFlowRequestOptionsInput } from '../../../../../common/api/search_strategy'; import type { ESTermQuery } from '../../../../../common/typed_json'; import type { inputsModel } from '../../../../common/store'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -18,7 +19,6 @@ import { networkSelectors } from '../../store'; import type { FlowTargetSourceDest, NetworkTopNFlowEdges, - NetworkTopNFlowRequestOptions, PageInfoPaginated, } from '../../../../../common/search_strategy'; import { NetworkQueries } from '../../../../../common/search_strategy'; @@ -68,7 +68,7 @@ export const useNetworkTopNFlow = ({ ); const [networkTopNFlowRequest, setTopNFlowRequest] = - useState(null); + useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -135,7 +135,7 @@ export const useNetworkTopNFlow = ({ useEffect(() => { setTopNFlowRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkTopNFlowRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkQueries.topNFlow, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/tls/index.tsx index c3d34c02c69c5..353f14c5e1410 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/tls/index.tsx @@ -8,6 +8,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; +import type { NetworkTlsRequestOptionsInput } from '../../../../../common/api/search_strategy'; import type { ESTermQuery } from '../../../../../common/typed_json'; import type { inputsModel } from '../../../../common/store'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -15,10 +16,7 @@ import { createFilter } from '../../../../common/containers/helpers'; import { generateTablePaginationOptions } from '../../../components/paginated_table/helpers'; import type { networkModel } from '../../store'; import { networkSelectors } from '../../store'; -import type { - NetworkTlsRequestOptions, - NetworkTlsStrategyResponse, -} from '../../../../../common/search_strategy/security_solution/network'; +import type { NetworkTlsStrategyResponse } from '../../../../../common/search_strategy/security_solution/network'; import { NetworkQueries } from '../../../../../common/search_strategy/security_solution/network'; import * as i18n from './translations'; @@ -67,7 +65,9 @@ export const useNetworkTls = ({ const getTlsSelector = useMemo(() => networkSelectors.tlsSelector(), []); const { activePage, limit, sort } = useDeepEqualSelector((state) => getTlsSelector(state, type)); - const [networkTlsRequest, setNetworkTlsRequest] = useState(null); + const [networkTlsRequest, setNetworkTlsRequest] = useState( + null + ); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -134,7 +134,7 @@ export const useNetworkTls = ({ useEffect(() => { setNetworkTlsRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkTlsRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: NetworkQueries.tls, diff --git a/x-pack/plugins/security_solution/public/explore/network/containers/users/index.tsx b/x-pack/plugins/security_solution/public/explore/network/containers/users/index.tsx index 217b4a9baae15..881a59c095995 100644 --- a/x-pack/plugins/security_solution/public/explore/network/containers/users/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/network/containers/users/index.tsx @@ -8,6 +8,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; +import type { NetworkUsersRequestOptionsInput } from '../../../../../common/api/search_strategy'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import type { ESTermQuery } from '../../../../../common/typed_json'; import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; @@ -18,7 +19,6 @@ import { generateTablePaginationOptions } from '../../../components/paginated_ta import { networkSelectors } from '../../store'; import type { FlowTargetSourceDest, - NetworkUsersRequestOptions, NetworkUsersStrategyResponse, } from '../../../../../common/search_strategy/security_solution/network'; import { NetworkQueries } from '../../../../../common/search_strategy/security_solution/network'; @@ -65,9 +65,8 @@ export const useNetworkUsers = ({ const { uiSettings } = useKibana().services; const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); - const [networkUsersRequest, setNetworkUsersRequest] = useState( - null - ); + const [networkUsersRequest, setNetworkUsersRequest] = + useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -134,7 +133,7 @@ export const useNetworkUsers = ({ useEffect(() => { setNetworkUsersRequest((prevRequest) => { - const myRequest = { + const myRequest: NetworkUsersRequestOptionsInput = { ...(prevRequest ?? {}), ip, defaultIndex, diff --git a/x-pack/plugins/security_solution/public/explore/users/containers/users/authentications/index.tsx b/x-pack/plugins/security_solution/public/explore/users/containers/users/authentications/index.tsx index 8263c03bc4fa6..29798d69da399 100644 --- a/x-pack/plugins/security_solution/public/explore/users/containers/users/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/containers/users/authentications/index.tsx @@ -10,14 +10,12 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import type { AuthenticationsKpiRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import type { inputsModel } from '../../../../../common/store'; import { createFilter } from '../../../../../common/containers/helpers'; import { useKibana } from '../../../../../common/lib/kibana'; -import type { - UsersKpiAuthenticationsRequestOptions, - UsersKpiAuthenticationsStrategyResponse, -} from '../../../../../../common/search_strategy'; +import type { UsersKpiAuthenticationsStrategyResponse } from '../../../../../../common/search_strategy'; import { UsersQueries } from '../../../../../../common/search_strategy'; import type { ESTermQuery } from '../../../../../../common/typed_json'; @@ -56,7 +54,7 @@ export const useUsersKpiAuthentications = ({ const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [usersKpiAuthenticationsRequest, setUsersKpiAuthenticationsRequest] = - useState(null); + useState(null); const [usersKpiAuthenticationsResponse, setUsersKpiAuthenticationsResponse] = useState({ @@ -75,7 +73,7 @@ export const useUsersKpiAuthentications = ({ const { addError, addWarning } = useAppToasts(); const usersKpiAuthenticationsSearch = useCallback( - (request: UsersKpiAuthenticationsRequestOptions | null) => { + (request: AuthenticationsKpiRequestOptionsInput | null) => { if (request == null || skip) { return; } @@ -85,7 +83,7 @@ export const useUsersKpiAuthentications = ({ setLoading(true); searchSubscription$.current = data.search - .search( + .search( request, { strategy: 'securitySolutionSearchStrategy', @@ -131,7 +129,7 @@ export const useUsersKpiAuthentications = ({ useEffect(() => { setUsersKpiAuthenticationsRequest((prevRequest) => { - const myRequest = { + const myRequest: AuthenticationsKpiRequestOptionsInput = { ...(prevRequest ?? {}), defaultIndex: indexNames, factoryQueryType: UsersQueries.kpiAuthentications, diff --git a/x-pack/plugins/security_solution/public/flyout/index.tsx b/x-pack/plugins/security_solution/public/flyout/index.tsx index 922c7e55219e0..c5da39105d929 100644 --- a/x-pack/plugins/security_solution/public/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/index.tsx @@ -11,6 +11,9 @@ import { type ExpandableFlyoutProps, ExpandableFlyoutProvider, } from '@kbn/expandable-flyout'; +import type { IsolateHostPanelProps } from './isolate_host'; +import { IsolateHostPanel, IsolateHostPanelKey } from './isolate_host'; +import { IsolateHostPanelProvider } from './isolate_host/context'; import type { RightPanelProps } from './right'; import { RightPanel, RightPanelKey } from './right'; import { RightPanelProvider } from './right/context'; @@ -54,6 +57,14 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels'] ), }, + { + key: IsolateHostPanelKey, + component: (props) => ( + + + + ), + }, ]; const OuterProviders: FC = ({ children }) => { diff --git a/x-pack/plugins/security_solution/public/flyout/isolate_host/content.tsx b/x-pack/plugins/security_solution/public/flyout/isolate_host/content.tsx new file mode 100644 index 0000000000000..7f3671cc60805 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/isolate_host/content.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 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 { FC } from 'react'; +import React, { useCallback } from 'react'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { EuiPanel } from '@elastic/eui'; +import { RightPanelKey } from '../right'; +import { useBasicDataFromDetailsData } from '../../timelines/components/side_panel/event_details/helpers'; +import { EndpointIsolateSuccess } from '../../common/components/endpoint/host_isolation'; +import { useHostIsolationTools } from '../../timelines/components/side_panel/event_details/use_host_isolation_tools'; +import { useIsolateHostPanelContext } from './context'; +import { HostIsolationPanel } from '../../detections/components/host_isolation'; + +/** + * Document details expandable flyout section content for the isolate host component, displaying the form or the success banner + */ +export const PanelContent: FC = () => { + const { openRightPanel } = useExpandableFlyoutContext(); + const { dataFormattedForFieldBrowser, eventId, scopeId, indexName, isolateAction } = + useIsolateHostPanelContext(); + + const { isIsolateActionSuccessBannerVisible, handleIsolationActionSuccess } = + useHostIsolationTools(); + + const { alertId, hostName } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + + const showAlertDetails = useCallback( + () => + openRightPanel({ + id: RightPanelKey, + params: { + id: eventId, + indexName, + scopeId, + }, + }), + [eventId, indexName, scopeId, openRightPanel] + ); + + return ( + + {isIsolateActionSuccessBannerVisible && ( + + )} + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/isolate_host/context.tsx b/x-pack/plugins/security_solution/public/flyout/isolate_host/context.tsx new file mode 100644 index 0000000000000..17d31ebd002af --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/isolate_host/context.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import React, { createContext, memo, useContext, useMemo } from 'react'; + +import { useEventDetails } from '../shared/hooks/use_event_details'; +import { FlyoutError } from '../shared/components/flyout_error'; +import { FlyoutLoading } from '../shared/components/flyout_loading'; +import type { IsolateHostPanelProps } from '.'; + +export interface IsolateHostPanelContext { + /** + * Id of the document + */ + eventId: string; + /** + * Name of the index used in the parent's page + */ + indexName: string; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; + /** + * An array of field objects with category and value + */ + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; + /** + * Isolate action, either 'isolateHost' or 'unisolateHost' + */ + isolateAction: 'isolateHost' | 'unisolateHost'; +} + +export const IsolateHostPanelContext = createContext( + undefined +); + +export type IsolateHostPanelProviderProps = { + /** + * React components to render + */ + children: React.ReactNode; +} & Partial; + +export const IsolateHostPanelProvider = memo( + ({ id, indexName, scopeId, isolateAction, children }: IsolateHostPanelProviderProps) => { + const { dataFormattedForFieldBrowser, loading } = useEventDetails({ eventId: id, indexName }); + + const contextValue = useMemo( + () => + id && indexName && scopeId && isolateAction && dataFormattedForFieldBrowser + ? { + eventId: id, + indexName, + scopeId, + dataFormattedForFieldBrowser, + isolateAction, + } + : undefined, + [id, indexName, scopeId, dataFormattedForFieldBrowser, isolateAction] + ); + + if (loading) { + return ; + } + + if (!contextValue) { + return ; + } + + return ( + + {children} + + ); + } +); + +IsolateHostPanelProvider.displayName = 'IsolateHostPanelProvider'; + +export const useIsolateHostPanelContext = (): IsolateHostPanelContext => { + const contextValue = useContext(IsolateHostPanelContext); + + if (!contextValue) { + throw new Error( + 'IsolateHostPanelContext can only be used within IsolateHostPanelContext provider' + ); + } + + return contextValue; +}; diff --git a/x-pack/plugins/security_solution/public/flyout/isolate_host/header.test.tsx b/x-pack/plugins/security_solution/public/flyout/isolate_host/header.test.tsx new file mode 100644 index 0000000000000..fa4b57a4313fa --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/isolate_host/header.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 React from 'react'; +import { render } from '@testing-library/react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { useIsolateHostPanelContext } from './context'; +import { PanelHeader } from './header'; +import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; + +jest.mock('./context'); + +const renderPanelHeader = () => + render( + + + + ); + +describe('', () => { + (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction: 'isolateHost' }); + + it('should display isolate host message', () => { + const { getByTestId } = renderPanelHeader(); + + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Isolate host'); + }); + + it('should display release host message', () => { + (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction: 'unisolateHost' }); + + const { getByTestId } = renderPanelHeader(); + + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Release host'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/isolate_host/header.tsx b/x-pack/plugins/security_solution/public/flyout/isolate_host/header.tsx new file mode 100644 index 0000000000000..0e5ef2e309b69 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/isolate_host/header.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +import type { FC } from 'react'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useIsolateHostPanelContext } from './context'; +import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; + +/** + * Document details expandable right section header for the isolate host panel + */ +export const PanelHeader: FC = () => { + const { isolateAction } = useIsolateHostPanelContext(); + + const title = + isolateAction === 'isolateHost' ? ( + + ) : ( + + ); + + return ( + + +

    {title}

    +
    +
    + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/isolate_host/index.tsx b/x-pack/plugins/security_solution/public/flyout/isolate_host/index.tsx new file mode 100644 index 0000000000000..ff02d7b78a115 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/isolate_host/index.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FC } from 'react'; +import React from 'react'; +import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; +import { PanelContent } from './content'; +import { PanelHeader } from './header'; + +export const IsolateHostPanelKey: IsolateHostPanelProps['key'] = 'document-details-isolate-host'; + +export interface IsolateHostPanelProps extends FlyoutPanelProps { + key: 'document-details-isolate-host'; + params?: { + id: string; + indexName: string; + scopeId: string; + isolateAction: 'isolateHost' | 'unisolateHost' | undefined; + }; +} + +/** + * Panel to be displayed right section in the document details expandable flyout when isolate host is clicked in the + * take action button + */ +export const IsolateHostPanel: FC> = () => { + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/isolate_host/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/isolate_host/test_ids.ts new file mode 100644 index 0000000000000..24b62d913772d --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/isolate_host/test_ids.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PREFIX } from '../shared/test_ids'; + +export const FLYOUT_HEADER_TITLE_TEST_ID = `${PREFIX}HeaderTitle` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx index 45710eb518428..d0a18279805cd 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx @@ -11,7 +11,7 @@ import '@testing-library/jest-dom'; import { LeftPanelContext } from '../context'; import { TestProviders } from '../../../common/mock'; import { AnalyzeGraph } from './analyze_graph'; -import { ANALYZE_GRAPH_ERROR_TEST_ID, ANALYZER_GRAPH_TEST_ID } from './test_ids'; +import { ANALYZER_GRAPH_TEST_ID } from './test_ids'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -45,21 +45,4 @@ describe('', () => { ); expect(wrapper.getByTestId(ANALYZER_GRAPH_TEST_ID)).toBeInTheDocument(); }); - - it('should render error message on null eventId', () => { - const contextValue = { - eventId: null, - } as unknown as LeftPanelContext; - - const wrapper = render( - - - - - - ); - expect(wrapper.getByTestId(ANALYZE_GRAPH_ERROR_TEST_ID)).toBeInTheDocument(); - expect(wrapper.getByText('Unable to display analyzer')).toBeInTheDocument(); - expect(wrapper.getByText('There was an error displaying analyzer')).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx index 63a75bd163d37..5ce8e47c681dd 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx @@ -7,14 +7,11 @@ import type { FC } from 'react'; import React, { useMemo } from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; -import { ANALYZER_ERROR_MESSAGE } from './translations'; import { useLeftPanelContext } from '../context'; -import { ANALYZE_GRAPH_ERROR_TEST_ID, ANALYZER_GRAPH_TEST_ID } from './test_ids'; +import { ANALYZER_GRAPH_TEST_ID } from './test_ids'; import { Resolver } from '../../../resolver/view'; import { useTimelineDataFilters } from '../../../timelines/containers/use_timeline_data_filters'; -import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations'; import { isActiveTimeline } from '../../../helpers'; export const ANALYZE_GRAPH_ID = 'analyze_graph'; @@ -30,17 +27,8 @@ export const AnalyzeGraph: FC = () => { ); const filters = useMemo(() => ({ from, to }), [from, to]); - if (!eventId) { - return ( - {ERROR_TITLE(ANALYZER_ERROR_MESSAGE)}} - body={

    {ERROR_MESSAGE(ANALYZER_ERROR_MESSAGE)}

    } - data-test-subj={ANALYZE_GRAPH_ERROR_TEST_ID} - /> - ); - } + // TODO as part of https://github.com/elastic/security-team/issues/7032 + // bring back no data message if needed return (
    diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx index dcd8c5884c6fb..2bbfa8f4ab7c5 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx @@ -20,8 +20,8 @@ import { CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID, CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID, CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_NO_DATA_TEST_ID, CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID, - CORRELATIONS_DETAILS_TEST_ID, } from './test_ids'; import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; @@ -102,13 +102,14 @@ describe('CorrelationsDetails', () => { dataCount: 1, }); - const { getByTestId } = renderCorrelationDetails(); + const { getByTestId, queryByTestId } = renderCorrelationDetails(); expect(getByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should render no section and show error message if show values are false', () => { @@ -139,7 +140,10 @@ describe('CorrelationsDetails', () => { expect( queryByTestId(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID) ).not.toBeInTheDocument(); - expect(getByTestId(`${CORRELATIONS_DETAILS_TEST_ID}Error`)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent( + 'No correlations data available.' + ); }); it('should render no section if values are null', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx index 0e6e927779710..f7def1d23ac98 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { CORRELATIONS_ERROR_MESSAGE } from './translations'; -import { CORRELATIONS_DETAILS_TEST_ID } from './test_ids'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CORRELATIONS_DETAILS_NO_DATA_TEST_ID } from './test_ids'; import { RelatedAlertsBySession } from './related_alerts_by_session'; import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event'; import { RelatedCases } from './related_cases'; @@ -98,9 +98,12 @@ export const CorrelationsDetails: React.FC = () => { )} ) : ( -
    - {CORRELATIONS_ERROR_MESSAGE} -
    +

    + +

    )} ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx index 3d36a54e728e2..250889402e455 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx @@ -67,7 +67,7 @@ describe('CorrelationsDetailsAlertsTable', () => { const { getByTestId } = render( {'title'}

    } loading={false} alertIds={alertIds} scopeId={scopeId} diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx index 4099db06d6c5a..0f113efe317aa 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx @@ -5,17 +5,18 @@ * 2.0. */ -import type { ReactNode } from 'react'; +import type { ReactElement, ReactNode } from 'react'; import React, { type FC, useMemo, useCallback } from 'react'; import { type Criteria, EuiBasicTable, formatDate } from '@elastic/eui'; import { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import type { Filter } from '@kbn/es-query'; import { isRight } from 'fp-ts/lib/Either'; import { ALERT_REASON, ALERT_RULE_NAME } from '@kbn/rule-data-utils'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; import type { DataProvider } from '../../../../common/types'; import { SeverityBadge } from '../../../detections/components/rules/severity_badge'; import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; -import * as i18n from './translations'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import { InvestigateInTimelineButton } from '../../../common/components/event_details/table/investigate_in_timeline_button'; import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../detections/components/alerts_table/translations'; @@ -27,24 +28,44 @@ const dataProviderLimit = 5; export const columns = [ { field: '@timestamp', - name: i18n.CORRELATIONS_TIMESTAMP_COLUMN_TITLE, + name: ( + + ), truncateText: true, dataType: 'date' as const, render: (value: string) => formatDate(value, TIMESTAMP_DATE_FORMAT), }, { field: ALERT_RULE_NAME, - name: i18n.CORRELATIONS_RULE_COLUMN_TITLE, + name: ( + + ), truncateText: true, }, { field: ALERT_REASON, - name: i18n.CORRELATIONS_REASON_COLUMN_TITLE, + name: ( + + ), truncateText: true, }, { field: 'kibana.alert.severity', - name: i18n.CORRELATIONS_SEVERITY_COLUMN_TITLE, + name: ( + + ), truncateText: true, render: (value: string) => { const decodedSeverity = Severity.decode(value); @@ -57,7 +78,7 @@ export interface CorrelationsDetailsAlertsTableProps { /** * Text to display in the ExpandablePanel title section */ - title: string; + title: ReactElement; /** * Whether the table is loading */ @@ -188,7 +209,12 @@ const getFilters = (alertIds?: string[]) => { return [ { meta: { - alias: i18n.CORRELATIONS_DETAILS_TABLE_FILTER, + alias: i18n.translate( + 'xpack.securitySolution.flyout.left.insights.correlations.tableFilterLabel', + { + defaultMessage: 'Correlations Details Table Alert IDs.', + } + ), type: 'phrases', key: '_id', params: [...alertIds], diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx index eb56069bb7646..2ecda17c128ba 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx @@ -40,62 +40,58 @@ jest.mock('react-redux', () => { const USER_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(USER_DETAILS_TEST_ID); const HOST_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(HOST_DETAILS_TEST_ID); +const renderEntitiesDetails = (contextValue: LeftPanelContext) => + render( + + + + + + ); + describe('', () => { it('renders entities details correctly', () => { - const { getByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderEntitiesDetails(mockContextValue); expect(getByTestId(ENTITIES_DETAILS_TEST_ID)).toBeInTheDocument(); expect(getByTestId(USER_TEST_ID)).toBeInTheDocument(); expect(getByTestId(HOST_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should render no data message if user name and host name are not available', () => { - const { getByTestId, queryByTestId } = render( - - - fieldName === '@timestamp' ? ['2022-07-25T08:20:18.966Z'] : [], - }} - > - - - - ); + const contextValue = { + ...mockContextValue, + getFieldsData: (fieldName: string) => + fieldName === '@timestamp' ? ['2022-07-25T08:20:18.966Z'] : [], + }; + const { getByTestId, queryByTestId } = renderEntitiesDetails(contextValue); expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent( + 'Host and user information are unavailable for this alert.' + ); expect(queryByTestId(USER_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(HOST_TEST_ID)).not.toBeInTheDocument(); }); it('does not render user and host details if @timestamp is not available', () => { - const { getByTestId, queryByTestId } = render( - - { - switch (fieldName) { - case 'host.name': - return ['host1']; - case 'user.name': - return ['user1']; - default: - return []; - } - }, - }} - > - - - - ); + const contextValue = { + ...mockContextValue, + getFieldsData: (fieldName: string) => { + switch (fieldName) { + case 'host.name': + return ['host1']; + case 'user.name': + return ['user1']; + default: + return []; + } + }, + }; + const { getByTestId, queryByTestId } = renderEntitiesDetails(contextValue); expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent( + 'Host and user information are unavailable for this alert.' + ); expect(queryByTestId(USER_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(HOST_TEST_ID)).not.toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx index ff3678a06e428..78ee648581162 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ENTITIES_NO_DATA_MESSAGE } from './translations'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useLeftPanelContext } from '../context'; import { getField } from '../../shared/utils'; import { UserDetails } from './user_details'; @@ -45,7 +45,12 @@ export const EntitiesDetails: React.FC = () => { )} ) : ( -
    {ENTITIES_NO_DATA_MESSAGE}
    +

    + +

    )} ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx index 825117c01641b..71c54530e1563 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx @@ -121,6 +121,14 @@ const mockRelatedUsersResponse = { relatedUsers: [{ user: 'test user', ip: ['100.XXX.XXX'], risk: RiskSeverity.low }], loading: false, }; + +const renderHostDetails = () => + render( + + + + ); + describe('', () => { beforeEach(() => { jest.clearAllMocks(); @@ -131,21 +139,13 @@ describe('', () => { }); it('should render host details correctly', () => { - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderHostDetails(); expect(getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(HOST_DETAILS_TEST_ID))).toBeInTheDocument(); }); describe('Host overview', () => { it('should render the HostOverview with correct dates and indices', () => { - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderHostDetails(); expect(mockUseHostDetails).toBeCalledWith({ id: 'entities-hosts-details-uuid', startDate: from, @@ -164,32 +164,20 @@ describe('', () => { }); mockUseRiskScore.mockReturnValue({ data: [], isAuthorized: true }); - const { getByText } = render( - - - - ); + const { getByText } = renderHostDetails(); expect(getByText('Host risk score')).toBeInTheDocument(); }); it('should not render host risk score when unauthorized', () => { mockUseRiskScore.mockReturnValue({ data: [], isAuthorized: false }); - const { queryByText } = render( - - - - ); + const { queryByText } = renderHostDetails(); expect(queryByText('Host risk score')).not.toBeInTheDocument(); }); }); describe('Related users', () => { it('should render the related user table with correct dates and indices', () => { - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderHostDetails(); expect(mockUseHostsRelatedUsers).toBeCalledWith({ from: timestamp, hostName: 'test host', @@ -206,11 +194,7 @@ describe('', () => { }); mockUseHasSecurityCapability.mockReturnValue(true); - const { queryAllByRole } = render( - - - - ); + const { queryAllByRole } = renderHostDetails(); expect(queryAllByRole('columnheader').length).toBe(3); expect(queryAllByRole('row')[1].textContent).toContain('test user'); expect(queryAllByRole('row')[1].textContent).toContain('100.XXX.XXX'); @@ -224,20 +208,12 @@ describe('', () => { }); mockUseHasSecurityCapability.mockReturnValue(false); - const { queryAllByRole } = render( - - - - ); + const { queryAllByRole } = renderHostDetails(); expect(queryAllByRole('columnheader').length).toBe(2); }); it('should not render host risk score column when license is not valid', () => { - const { queryAllByRole } = render( - - - - ); + const { queryAllByRole } = renderHostDetails(); expect(queryAllByRole('columnheader').length).toBe(2); }); @@ -248,11 +224,7 @@ describe('', () => { loading: false, }); - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderHostDetails(); expect(getByTestId(HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID).textContent).toContain( 'No users identified' ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/host_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/host_details.tsx index 5941c39cb8c8d..2bccfbc8ac34b 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/host_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/host_details.tsx @@ -20,6 +20,7 @@ import { EuiPanel, } from '@elastic/eui'; import type { EuiBasicTableColumn } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { getSourcererScopeId } from '../../../helpers'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import type { RelatedUser } from '../../../../common/search_strategy/security_solution/related_entities/related_users'; @@ -50,7 +51,6 @@ import { getEmptyTagValue } from '../../../common/components/empty_value'; import { HOST_DETAILS_TEST_ID, HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID } from './test_ids'; import { ENTITY_RISK_CLASSIFICATION } from '../../../explore/components/risk_score/translations'; import { USER_RISK_TOOLTIP } from '../../../explore/users/components/all_users/translations'; -import * as i18n from './translations'; import { useHasSecurityCapability } from '../../../helper_hooks'; const HOST_DETAILS_ID = 'entities-hosts-details'; @@ -128,7 +128,12 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s () => [ { field: 'user', - name: i18n.RELATED_ENTITIES_NAME_COLUMN_TITLE, + name: ( + + ), render: (user: string) => ( = ({ hostName, timestamp, s }, { field: 'ip', - name: i18n.RELATED_ENTITIES_IP_COLUMN_TITLE, + name: ( + + ), render: (ips: string[]) => { return ( = ({ hostName, timestamp, s - {`${i18n.RELATED_USERS_TITLE}: ${totalCount}`} + + + @@ -215,7 +231,12 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s return ( <> -

    {i18n.HOST_TITLE}

    +

    + +

    = ({ hostName, timestamp, s data-test-subj={HOST_DETAILS_TEST_ID} > -
    {i18n.HOSTS_INFO_TITLE}
    +
    + +
    = ({ hostName, timestamp, s -
    {i18n.RELATED_USERS_TITLE}
    +
    + +
    - + + } + > @@ -287,11 +326,21 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s loading={isRelatedUsersLoading} data-test-subj={HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID} pagination={pagination} - message={i18n.RELATED_USERS_TABLE_NO_DATA} + message={ + + } /> + } inspectIndex={0} /> diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx index 4de5f29bd7ea2..37d91600bbe5e 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx @@ -55,6 +55,9 @@ describe('', () => { }); const { getByTestId } = render(renderInvestigationGuide()); expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent( + `There’s no investigation guide for this rule. Edit the rule's settingsExternal link(opens in a new tab or window) to add one.` + ); }); it('should render no data message when there is no rule note', () => { @@ -64,27 +67,17 @@ describe('', () => { }); const { getByTestId } = render(renderInvestigationGuide()); expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent( + `There’s no investigation guide for this rule. Edit the rule's settingsExternal link(opens in a new tab or window) to add one.` + ); }); - it('should render null when dataFormattedForFieldBrowser is null', () => { - const mockContext = { - ...mockContextValue, - dataFormattedForFieldBrowser: null, - }; - (useInvestigationGuide as jest.Mock).mockReturnValue({ - loading: false, - error: false, - }); - const { container } = render(renderInvestigationGuide(mockContext)); - expect(container).toBeEmptyDOMElement(); - }); - - it('should render null useInvestigationGuide errors out', () => { + it('should render no data message when useInvestigationGuide errors out', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: false, error: true, }); - const { container } = render(renderInvestigationGuide()); - expect(container).toBeEmptyDOMElement(); + const { getByTestId } = render(renderInvestigationGuide()); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx index 2d0fe038f3c63..1152e11df6ba6 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx @@ -26,10 +26,6 @@ export const InvestigationGuide: React.FC = () => { dataFormattedForFieldBrowser, }); - if (!dataFormattedForFieldBrowser || error) { - return null; - } - if (loading) { return ( { return ( <> - {basicAlertData.ruleId && ruleNote ? ( + {!error && basicAlertData.ruleId && ruleNote ? ( { showFullView={true} /> ) : ( -
    +

    { target="_blank" > ), }} /> -

    +

    )} ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx index e1512b8b7ada1..c202d7eab9768 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx @@ -11,11 +11,19 @@ import { LeftPanelContext } from '../context'; import { PrevalenceDetails } from './prevalence_details'; import { PREVALENCE_DETAILS_LOADING_TEST_ID, - PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID, + PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID, + PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID, + PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID, + PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID, + PREVALENCE_DETAILS_NO_DATA_TEST_ID, PREVALENCE_DETAILS_TABLE_TEST_ID, + PREVALENCE_DETAILS_UPSELL_TEST_ID, + PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID, + PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID, } from './test_ids'; import { usePrevalence } from '../../shared/hooks/use_prevalence'; import { TestProviders } from '../../../common/mock'; +import { licenseService } from '../../../common/hooks/use_license'; jest.mock('../../shared/hooks/use_prevalence'); @@ -27,6 +35,17 @@ jest.mock('react-redux', () => { useDispatch: () => mockDispatch, }; }); +jest.mock('../../../common/hooks/use_license', () => { + const licenseServiceInstance = { + isPlatinumPlus: jest.fn(), + }; + return { + licenseService: licenseServiceInstance, + useLicense: () => { + return licenseServiceInstance; + }, + }; +}); const panelContextValue = { eventId: 'event id', @@ -35,8 +54,23 @@ const panelContextValue = { dataFormattedForFieldBrowser: [], } as unknown as LeftPanelContext; +const renderPrevalenceDetails = () => + render( + + + + + + ); + describe('PrevalenceDetails', () => { - it('should render the table', () => { + const licenseServiceMock = licenseService as jest.Mocked; + + beforeEach(() => { + licenseServiceMock.isPlatinumPlus.mockReturnValue(true); + }); + + it('should render the table with all columns if license is platinum', () => { const field1 = 'field1'; const field2 = 'field2'; (usePrevalence as jest.Mock).mockReturnValue({ @@ -62,106 +96,154 @@ describe('PrevalenceDetails', () => { ], }); - const { getByTestId } = render( - - - - - - ); + const { getByTestId, getAllByTestId, queryByTestId } = renderPrevalenceDetails(); expect(getByTestId(PREVALENCE_DETAILS_TABLE_TEST_ID)).toBeInTheDocument(); + expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID).length).toBeGreaterThan(1); + expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID).length).toBeGreaterThan(1); + expect( + getAllByTestId(PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID).length + ).toBeGreaterThan(1); + expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID).length).toBeGreaterThan( + 1 + ); + expect( + getAllByTestId(PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID).length + ).toBeGreaterThan(1); + expect( + getAllByTestId(PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID).length + ).toBeGreaterThan(1); + expect(queryByTestId(PREVALENCE_DETAILS_UPSELL_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); - it('should render loading', () => { + it('should render formatted numbers for the alert and document count columns', () => { (usePrevalence as jest.Mock).mockReturnValue({ - loading: true, + loading: false, error: false, - data: [], + data: [ + { + field: 'field1', + value: 'value1', + alertCount: 1000, + docCount: 2000000, + hostPrevalence: 0.05, + userPrevalence: 0.1, + }, + ], }); const { getByTestId } = render( - - - + + + + + ); - expect(getByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID)).toHaveTextContent('field1'); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID)).toHaveTextContent('value1'); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID)).toHaveTextContent('1k'); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID)).toHaveTextContent('2M'); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID)).toHaveTextContent( + '5%' + ); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID)).toHaveTextContent( + '10%' + ); }); - it('should render error if call errors out', () => { + it('should render the table with only basic columns if license is not platinum', () => { + const field1 = 'field1'; + const field2 = 'field2'; (usePrevalence as jest.Mock).mockReturnValue({ loading: false, - error: true, - data: [], + error: false, + data: [ + { + field: field1, + value: 'value1', + alertCount: 1, + docCount: 1, + hostPrevalence: 0.05, + userPrevalence: 0.1, + }, + { + field: field2, + value: 'value2', + alertCount: 1, + docCount: 1, + hostPrevalence: 0.5, + userPrevalence: 0.05, + }, + ], }); + licenseServiceMock.isPlatinumPlus.mockReturnValue(false); - const { getByTestId } = render( - - - - ); + const { getByTestId, getAllByTestId } = renderPrevalenceDetails(); - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_TABLE_TEST_ID)).toBeInTheDocument(); + expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID).length).toBeGreaterThan(1); + expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID).length).toBeGreaterThan(1); + expect( + getAllByTestId(PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID).length + ).toBeGreaterThan(1); + expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID).length).toBeGreaterThan( + 1 + ); + expect( + getAllByTestId(PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID).length + ).toBeGreaterThan(1); + expect( + getAllByTestId(PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID).length + ).toBeGreaterThan(1); + expect(getByTestId(PREVALENCE_DETAILS_UPSELL_TEST_ID)).toBeInTheDocument(); }); - it('should render error if event is null', () => { - const contextValue = { - ...panelContextValue, - eventId: null, - } as unknown as LeftPanelContext; + it('should render loading', () => { (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: true, + loading: true, + error: false, data: [], }); - const { getByTestId } = render( - - - - ); + const { getByTestId, queryByTestId } = renderPrevalenceDetails(); - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(PREVALENCE_DETAILS_UPSELL_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); - it('should render error if dataFormattedForFieldBrowser is null', () => { - const contextValue = { - ...panelContextValue, - dataFormattedForFieldBrowser: null, - }; + it('should render no data message if call errors out', () => { (usePrevalence as jest.Mock).mockReturnValue({ loading: false, error: true, data: [], }); - const { getByTestId } = render( - - - - ); + const { getByTestId, queryByTestId } = renderPrevalenceDetails(); - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent( + 'No prevalence data available.' + ); + expect(queryByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).not.toBeInTheDocument(); }); - it('should render error if browserFields is null', () => { - const contextValue = { - ...panelContextValue, - browserFields: null, - }; + it('should render no data message if no data', () => { (usePrevalence as jest.Mock).mockReturnValue({ loading: false, - error: true, + error: false, data: [], }); - const { getByTestId } = render( - - - - ); + const { getByTestId, queryByTestId } = renderPrevalenceDetails(); - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent( + 'No prevalence data available.' + ); + expect(queryByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx index b11622b4a4560..10244240bfc60 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx @@ -5,51 +5,40 @@ * 2.0. */ -import React, { useState } from 'react'; +import dateMath from '@elastic/datemath'; +import React, { useMemo, useState } from 'react'; import type { EuiBasicTableColumn, OnTimeChangeProps } from '@elastic/eui'; import { - EuiEmptyPrompt, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, + EuiLink, EuiLoadingSpinner, EuiPanel, EuiSpacer, EuiSuperDatePicker, + EuiText, EuiToolTip, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { FormattedCount } from '../../../common/components/formatted_number'; +import { useLicense } from '../../../common/hooks/use_license'; import { InvestigateInTimelineButton } from '../../../common/components/event_details/table/investigate_in_timeline_button'; import type { PrevalenceData } from '../../shared/hooks/use_prevalence'; import { usePrevalence } from '../../shared/hooks/use_prevalence'; -import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations'; -import { - HOST_TITLE, - PREVALENCE_ERROR_MESSAGE, - PREVALENCE_TABLE_ALERT_COUNT_COLUMN_TITLE, - PREVALENCE_TABLE_COUNT_COLUMN_TITLE, - PREVALENCE_TABLE_DOC_COUNT_COLUMN_TITLE, - PREVALENCE_TABLE_VALUE_COLUMN_TITLE, - PREVALENCE_TABLE_PREVALENCE_COLUMN_TITLE, - PREVALENCE_TABLE_FIELD_COLUMN_TITLE, - USER_TITLE, - PREVALENCE_NO_DATA_MESSAGE, - PREVALENCE_TABLE_ALERT_COUNT_COLUMN_TITLE_TOOLTIP, - PREVALENCE_TABLE_DOC_COUNT_COLUMN_TITLE_TOOLTIP, - HOST_PREVALENCE_COLUMN_TITLE_TOOLTIP, - USER_PREVALENCE_COLUMN_TITLE_TOOLTIP, -} from './translations'; import { PREVALENCE_DETAILS_LOADING_TEST_ID, PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID, PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID, - PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID, PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID, PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID, PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID, PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID, - PREVALENCE_DETAILS_TABLE_NO_DATA_TEST_ID, + PREVALENCE_DETAILS_NO_DATA_TEST_ID, PREVALENCE_DETAILS_DATE_PICKER_TEST_ID, PREVALENCE_DETAILS_TABLE_TEST_ID, + PREVALENCE_DETAILS_UPSELL_TEST_ID, } from './test_ids'; import { useLeftPanelContext } from '../context'; import { @@ -63,28 +52,70 @@ export const PREVALENCE_TAB_ID = 'prevalence-details'; const DEFAULT_FROM = 'now-30d'; const DEFAULT_TO = 'now'; -const columns: Array> = [ +interface PrevalenceDetailsRow extends PrevalenceData { + /** + * From datetime selected in the date picker to pass to timeline + */ + from: string; + /** + * To datetime selected in the date picker to pass to timeline + */ + to: string; +} + +const columns: Array> = [ { field: 'field', - name: PREVALENCE_TABLE_FIELD_COLUMN_TITLE, + name: ( + + ), 'data-test-subj': PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID, + render: (field: string) => {field}, + width: '20%', }, { field: 'value', - name: PREVALENCE_TABLE_VALUE_COLUMN_TITLE, + name: ( + + ), 'data-test-subj': PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID, + render: (value: string) => {value}, + width: '20%', }, { name: ( - + + } + > - {PREVALENCE_TABLE_ALERT_COUNT_COLUMN_TITLE} - {PREVALENCE_TABLE_COUNT_COLUMN_TITLE} + + + + + + ), 'data-test-subj': PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID, - render: (data: PrevalenceData) => { + render: (data: PrevalenceDetailsRow) => { const dataProviders = [ getDataProvider(data.field, `timeline-indicator-${data.field}-${data.value}`, data.value), ]; @@ -93,8 +124,9 @@ const columns: Array> = [ asEmptyButton={true} dataProviders={dataProviders} filters={[]} + timeRange={{ kind: 'absolute', from: data.from, to: data.to }} > - <>{data.alertCount} + ) : ( getEmptyTagValue() @@ -104,15 +136,32 @@ const columns: Array> = [ }, { name: ( - + + } + > - {PREVALENCE_TABLE_DOC_COUNT_COLUMN_TITLE} - {PREVALENCE_TABLE_COUNT_COLUMN_TITLE} + + + + + + ), 'data-test-subj': PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID, - render: (data: PrevalenceData) => { + render: (data: PrevalenceDetailsRow) => { const dataProviders = [ { ...getDataProvider( @@ -136,9 +185,10 @@ const columns: Array> = [ asEmptyButton={true} dataProviders={dataProviders} filters={[]} + timeRange={{ kind: 'absolute', from: data.from, to: data.to }} keepDataView // changing dataview from only detections to include non-alerts docs > - <>{data.docCount} + ) : ( getEmptyTagValue() @@ -149,38 +199,66 @@ const columns: Array> = [ { field: 'hostPrevalence', name: ( - + + } + > - {HOST_TITLE} - {PREVALENCE_TABLE_PREVALENCE_COLUMN_TITLE} + + + + + + ), 'data-test-subj': PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID, render: (hostPrevalence: number) => ( - <> - {Math.round(hostPrevalence * 100)} - {'%'} - + {`${Math.round(hostPrevalence * 100)}%`} ), width: '10%', }, { field: 'userPrevalence', name: ( - + + } + > - {USER_TITLE} - {PREVALENCE_TABLE_PREVALENCE_COLUMN_TITLE} + + + + + + ), 'data-test-subj': PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID, render: (userPrevalence: number) => ( - <> - {Math.round(userPrevalence * 100)} - {'%'} - + {`${Math.round(userPrevalence * 100)}%`} ), width: '10%', }, @@ -190,15 +268,40 @@ const columns: Array> = [ * Prevalence table displayed in the document details expandable flyout left section under the Insights tab */ export const PrevalenceDetails: React.FC = () => { - const { browserFields, dataFormattedForFieldBrowser, eventId, investigationFields } = - useLeftPanelContext(); + const { dataFormattedForFieldBrowser, investigationFields } = useLeftPanelContext(); + + const isPlatinumPlus = useLicense().isPlatinumPlus(); + // these two are used by the usePrevalence hook to fetch the data const [start, setStart] = useState(DEFAULT_FROM); const [end, setEnd] = useState(DEFAULT_TO); - const onTimeChange = ({ start: s, end: e }: OnTimeChangeProps) => { + // these two are used to pass to timeline + const [absoluteStart, setAbsoluteStart] = useState( + (dateMath.parse(DEFAULT_FROM) || new Date()).toISOString() + ); + const [absoluteEnd, setAbsoluteEnd] = useState( + (dateMath.parse(DEFAULT_TO) || new Date()).toISOString() + ); + + // TODO update the logic to use a single set of start/end dates + // currently as we're using this InvestigateInTimelineButton component we need to pass the timeRange + // as an AbsoluteTimeRange, which requires from/to values + const onTimeChange = ({ start: s, end: e, isInvalid }: OnTimeChangeProps) => { + if (isInvalid) return; + setStart(s); setEnd(e); + + const from = dateMath.parse(s); + if (from && from.isValid()) { + setAbsoluteStart(from.toISOString()); + } + + const to = dateMath.parse(e); + if (to && to.isValid()) { + setAbsoluteEnd(to.toISOString()); + } }; const { loading, error, data } = usePrevalence({ @@ -210,6 +313,12 @@ export const PrevalenceDetails: React.FC = () => { }, }); + // add timeRange to pass it down to timeline + const items = useMemo( + () => data.map((item) => ({ ...item, from: absoluteStart, to: absoluteEnd })), + [data, absoluteStart, absoluteEnd] + ); + if (loading) { return ( { ); } - if (!eventId || !dataFormattedForFieldBrowser || !browserFields || error) { - return ( - {ERROR_TITLE(PREVALENCE_ERROR_MESSAGE)}} - body={

    {ERROR_MESSAGE(PREVALENCE_ERROR_MESSAGE)}

    } - data-test-subj={PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID} - /> - ); - } + const upsell = ( + <> + + + + + ), + }} + /> + + + + ); return ( <> + {!error && !isPlatinumPlus && upsell} { {data.length > 0 ? ( ) : ( -
    - {PREVALENCE_NO_DATA_MESSAGE} -
    +

    + +

    )}
    diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.test.tsx index c54b76468841b..73e22f2267319 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.test.tsx @@ -39,6 +39,18 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID ); +const renderRelatedAlertsByAncestry = () => + render( + + + + ); + describe('', () => { it('should render many related alerts correctly', () => { (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ @@ -74,16 +86,7 @@ describe('', () => { ], }); - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderRelatedAlertsByAncestry(); expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_TEXT)).toBeInTheDocument(); @@ -99,16 +102,24 @@ describe('', () => { error: true, }); - const { container } = render( - - - - ); + const { container } = renderRelatedAlertsByAncestry(); expect(container).toBeEmptyDOMElement(); }); + + it('should render no data message', () => { + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 0, + }); + (usePaginatedAlerts as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + }); + + const { getByText } = renderRelatedAlertsByAncestry(); + expect(getByText('No alerts related by ancestry.')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.tsx index ac0ac0a351e9a..050d2b4ae1966 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.tsx @@ -6,9 +6,8 @@ */ import React from 'react'; -import { RELATED_ALERTS_BY_ANCESTRY_NO_DATA } from './translations'; +import { FormattedMessage } from '@kbn/i18n-react'; import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table'; -import { CORRELATIONS_ANCESTRY_ALERTS } from '../../shared/translations'; import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; import { CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID } from './test_ids'; @@ -45,7 +44,6 @@ export const RelatedAlertsByAncestry: React.VFC = indices, scopeId, }); - const title = `${dataCount} ${CORRELATIONS_ANCESTRY_ALERTS(dataCount)}`; if (error) { return null; @@ -53,12 +51,23 @@ export const RelatedAlertsByAncestry: React.VFC = return ( + } loading={loading} alertIds={data} scopeId={scopeId} eventId={eventId} - noItemsMessage={RELATED_ALERTS_BY_ANCESTRY_NO_DATA} + noItemsMessage={ + + } data-test-subj={CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.test.tsx index 77dbe5ef21a2c..de46c22eb5199 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.test.tsx @@ -38,6 +38,17 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID ); +const renderRelatedAlertsBySameSourceEvent = () => + render( + + + + ); + describe('', () => { it('should render component correctly', () => { (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ @@ -73,15 +84,7 @@ describe('', () => { ], }); - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderRelatedAlertsBySameSourceEvent(); expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_TEXT)).toBeInTheDocument(); @@ -97,15 +100,24 @@ describe('', () => { error: true, }); - const { container } = render( - - - - ); + const { container } = renderRelatedAlertsBySameSourceEvent(); expect(container).toBeEmptyDOMElement(); }); + + it('should render no data message', () => { + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 0, + }); + (usePaginatedAlerts as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + }); + + const { getByText } = renderRelatedAlertsBySameSourceEvent(); + expect(getByText('No related source events.')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.tsx index 0a0cdeb8fd29c..e3bbf48c5fe15 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { RELATED_ALERTS_BY_SOURCE_EVENT_NO_DATA } from './translations'; -import { CORRELATIONS_SAME_SOURCE_ALERTS } from '../../shared/translations'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; import { CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID } from './test_ids'; import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table'; @@ -39,7 +38,6 @@ export const RelatedAlertsBySameSourceEvent: React.VFC + } loading={loading} alertIds={data} scopeId={scopeId} eventId={eventId} - noItemsMessage={RELATED_ALERTS_BY_SOURCE_EVENT_NO_DATA} + noItemsMessage={ + + } data-test-subj={CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.test.tsx index de1725eef64c9..99ef4c7408555 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.test.tsx @@ -38,6 +38,13 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID ); +const renderRelatedAlertsBySession = () => + render( + + + + ); + describe('', () => { it('should render component correctly', () => { (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ @@ -73,11 +80,7 @@ describe('', () => { ], }); - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderRelatedAlertsBySession(); expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_TEXT)).toBeInTheDocument(); @@ -93,11 +96,24 @@ describe('', () => { error: true, }); - const { container } = render( - - - - ); + const { container } = renderRelatedAlertsBySession(); expect(container).toBeEmptyDOMElement(); }); + + it('should render no data message', () => { + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 0, + }); + (usePaginatedAlerts as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + }); + + const { getByText } = renderRelatedAlertsBySession(); + expect(getByText('No alerts related by session.')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.tsx index 8a55aa3651708..1aa5a2b9dd619 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { RELATED_ALERTS_BY_SESSION_NO_DATA } from './translations'; -import { CORRELATIONS_SESSION_ALERTS } from '../../shared/translations'; +import { FormattedMessage } from '@kbn/i18n-react'; import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table'; import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; import { CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID } from './test_ids'; @@ -39,7 +38,6 @@ export const RelatedAlertsBySession: React.VFC = ({ entityId, scopeId, }); - const title = `${dataCount} ${CORRELATIONS_SESSION_ALERTS(dataCount)}`; if (error) { return null; @@ -47,12 +45,23 @@ export const RelatedAlertsBySession: React.VFC = ({ return ( + } loading={loading} alertIds={data} scopeId={scopeId} eventId={eventId} - noItemsMessage={RELATED_ALERTS_BY_SESSION_NO_DATA} + noItemsMessage={ + + } data-test-subj={CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.test.tsx index ce11b234e8359..264794666234a 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID, @@ -38,6 +39,13 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID ); +const renderRelatedCases = () => + render( + + + + ); + describe('', () => { it('should render many related cases correctly', () => { (useFetchRelatedCases as jest.Mock).mockReturnValue({ @@ -54,7 +62,7 @@ describe('', () => { dataCount: 1, }); - const { getByTestId } = render(); + const { getByTestId } = renderRelatedCases(); expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_TEXT)).toHaveTextContent('1 related case'); @@ -67,7 +75,19 @@ describe('', () => { error: true, }); - const { container } = render(); + const { container } = renderRelatedCases(); expect(container).toBeEmptyDOMElement(); }); + + it('should render no data message', () => { + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 0, + }); + + const { getByText } = renderRelatedCases(); + expect(getByText('No related cases.')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx index 707087d6360e8..5818b9314390c 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx @@ -9,26 +9,26 @@ import React from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; import { EuiInMemoryTable, EuiSkeletonText } from '@elastic/eui'; import type { RelatedCase } from '@kbn/cases-plugin/common'; +import { FormattedMessage } from '@kbn/i18n-react'; import { CaseDetailsLink } from '../../../common/components/links'; -import { CORRELATIONS_RELATED_CASES } from '../../shared/translations'; import { CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID, CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID, } from './test_ids'; import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; -import { - CORRELATIONS_CASE_NAME_COLUMN_TITLE, - CORRELATIONS_CASE_STATUS_COLUMN_TITLE, - RELATED_CASES_NO_DATA, -} from './translations'; const ICON = 'warning'; const columns: Array> = [ { field: 'title', - name: CORRELATIONS_CASE_NAME_COLUMN_TITLE, + name: ( + + ), truncateText: true, render: (value: string, caseData: RelatedCase) => ( @@ -38,7 +38,12 @@ const columns: Array> = [ }, { field: 'status', - name: CORRELATIONS_CASE_STATUS_COLUMN_TITLE, + name: ( + + ), truncateText: true, }, ]; @@ -55,7 +60,6 @@ export interface RelatedCasesProps { */ export const RelatedCases: React.VFC = ({ eventId }) => { const { loading, error, data, dataCount } = useFetchRelatedCases({ eventId }); - const title = `${dataCount} ${CORRELATIONS_RELATED_CASES(dataCount)}`; if (loading) { return ; @@ -68,7 +72,13 @@ export const RelatedCases: React.VFC = ({ eventId }) => { return ( + ), iconType: ICON, }} content={{ error }} @@ -83,7 +93,12 @@ export const RelatedCases: React.VFC = ({ eventId }) => { items={data} columns={columns} pagination={true} - message={RELATED_CASES_NO_DATA} + message={ + + } data-test-subj={CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID} /> diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx index 435df5fb3dcd1..cea5e63003662 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx @@ -10,18 +10,26 @@ import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; import { LeftPanelContext } from '../context'; import { rawEventData, TestProviders } from '../../../common/mock'; -import { RESPONSE_DETAILS_TEST_ID, RESPONSE_EMPTY_TEST_ID } from './test_ids'; +import { RESPONSE_DETAILS_TEST_ID, RESPONSE_NO_DATA_TEST_ID } from './test_ids'; import { ResponseDetails } from './response_details'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; jest.mock('../../../common/hooks/use_experimental_features'); - jest.mock('../../../common/lib/kibana', () => { const originalModule = jest.requireActual('../../../common/lib/kibana'); return { ...originalModule, useKibana: jest.fn().mockReturnValue({ services: { + data: { + search: { + search: () => ({ + subscribe: () => ({ + unsubscribe: jest.fn(), + }), + }), + }, + }, osquery: { OsqueryResults: jest.fn().mockReturnValue(null), fetchAllLiveQueries: jest.fn().mockReturnValue({ @@ -76,7 +84,7 @@ const contextWithResponseActions = { }; // Renders System Under Test -const renderSUT = (contextValue: LeftPanelContext) => +const renderResponseDetails = (contextValue: LeftPanelContext) => render( @@ -98,32 +106,38 @@ describe('', () => { useIsExperimentalFeatureEnabledMock ); }); + it('should render the view with response actions', () => { - const wrapper = renderSUT(contextWithResponseActions); + const wrapper = renderResponseDetails(contextWithResponseActions); expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).toBeInTheDocument(); expect(wrapper.getByTestId('responseActionsViewWrapper')).toBeInTheDocument(); expect(wrapper.queryByTestId('osqueryViewWrapper')).not.toBeInTheDocument(); - expect(wrapper.queryByTestId(RESPONSE_EMPTY_TEST_ID)).not.toBeInTheDocument(); + expect(wrapper.queryByTestId(RESPONSE_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); + it('should render the view with osquery only', () => { featureFlags.responseActionsEnabled = true; featureFlags.endpointResponseActionsEnabled = false; - const wrapper = renderSUT(contextWithResponseActions); + const wrapper = renderResponseDetails(contextWithResponseActions); expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).toBeInTheDocument(); expect(wrapper.queryByTestId('responseActionsViewWrapper')).not.toBeInTheDocument(); expect(wrapper.getByTestId('osqueryViewWrapper')).toBeInTheDocument(); }); + it('should render the empty information', () => { - const wrapper = renderSUT(defaultContextValue); + const wrapper = renderResponseDetails(defaultContextValue); expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).toBeInTheDocument(); expect(wrapper.queryByTestId('responseActionsViewWrapper')).not.toBeInTheDocument(); expect(wrapper.queryByTestId('osqueryViewWrapper')).not.toBeInTheDocument(); - expect(wrapper.getByTestId(RESPONSE_EMPTY_TEST_ID)).toBeInTheDocument(); + expect(wrapper.getByTestId(RESPONSE_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(wrapper.getByTestId(RESPONSE_NO_DATA_TEST_ID)).toHaveTextContent( + 'There are no response actions defined for this event. To add some, edit the rule’s settings and set up response actionsExternal link(opens in a new tab or window).' + ); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx index 07153217262b2..9e813b518a9f6 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; -import { RESPONSE_DETAILS_TEST_ID, RESPONSE_EMPTY_TEST_ID } from './test_ids'; +import { RESPONSE_DETAILS_TEST_ID, RESPONSE_NO_DATA_TEST_ID } from './test_ids'; import { expandDottedObject } from '../../../../common/utils/expand_dotted'; import type { ExpandedEventFieldsObject, @@ -19,7 +19,6 @@ import { useLeftPanelContext } from '../context'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useOsqueryTab } from '../../../common/components/event_details/osquery_tab'; import { useResponseActionsView } from '../../../common/components/event_details/response_actions_view'; -import * as i18n from './translations'; const ExtendedFlyoutWrapper = styled.div` figure { @@ -58,23 +57,28 @@ export const ResponseDetails: React.FC = () => { return (
    -
    {i18n.RESPONSE_TITLE}
    +
    + +
    {!responseActions ? ( - + ), diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx index 8438ab478cfb4..8ca9ac2f480fa 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; import { LeftPanelContext } from '../context'; import { TestProviders } from '../../../common/mock'; -import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids'; +import { SESSION_VIEW_TEST_ID } from './test_ids'; import { SessionView } from './session_view'; import { ANCESTOR_INDEX, @@ -46,6 +46,15 @@ jest.mock('../../../common/lib/kibana', () => { }; }); +const renderSessionView = (contextValue: LeftPanelContext) => + render( + + + + + + ); + describe('', () => { it('renders session view correctly', () => { const contextValue = { @@ -53,13 +62,7 @@ describe('', () => { indexName: '.ds-logs-endpoint.events.process-default', } as unknown as LeftPanelContext; - const wrapper = render( - - - - - - ); + const wrapper = renderSessionView(contextValue); expect(wrapper.getByTestId(SESSION_VIEW_TEST_ID)).toBeInTheDocument(); }); @@ -69,30 +72,7 @@ describe('', () => { indexName: '.alerts-security', // it should prioritize KIBANA_ANCESTOR_INDEX above indexName } as unknown as LeftPanelContext; - const wrapper = render( - - - - - - ); + const wrapper = renderSessionView(contextValue); expect(wrapper.getByTestId(SESSION_VIEW_TEST_ID)).toBeInTheDocument(); }); - - it('should render error message on null eventId', () => { - const contextValue = { - getFieldsData: () => {}, - } as unknown as LeftPanelContext; - - const wrapper = render( - - - - - - ); - expect(wrapper.getByTestId(SESSION_VIEW_ERROR_TEST_ID)).toBeInTheDocument(); - expect(wrapper.getByText('Unable to display session view')).toBeInTheDocument(); - expect(wrapper.getByText('There was an error displaying session view')).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx index 48f1164f75168..e35efacfb3195 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx @@ -7,16 +7,13 @@ import type { FC } from 'react'; import React from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; import { ANCESTOR_INDEX, ENTRY_LEADER_ENTITY_ID, ENTRY_LEADER_START, } from '../../shared/constants/field_names'; import { getField } from '../../shared/utils'; -import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations'; -import { SESSION_VIEW_ERROR_MESSAGE } from './translations'; -import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids'; +import { SESSION_VIEW_TEST_ID } from './test_ids'; import { useKibana } from '../../../common/lib/kibana'; import { useLeftPanelContext } from '../context'; @@ -30,21 +27,12 @@ export const SessionView: FC = () => { const { getFieldsData, indexName } = useLeftPanelContext(); const ancestorIndex = getField(getFieldsData(ANCESTOR_INDEX)); // e.g in case of alert, we want to grab it's origin index - const sessionEntityId = getField(getFieldsData(ENTRY_LEADER_ENTITY_ID)); - const sessionStartTime = getField(getFieldsData(ENTRY_LEADER_START)); + const sessionEntityId = getField(getFieldsData(ENTRY_LEADER_ENTITY_ID)) || ''; + const sessionStartTime = getField(getFieldsData(ENTRY_LEADER_START)) || ''; const index = ancestorIndex || indexName; - if (!index || !sessionEntityId || !sessionStartTime) { - return ( - {ERROR_TITLE(SESSION_VIEW_ERROR_MESSAGE)}} - body={

    {ERROR_MESSAGE(SESSION_VIEW_ERROR_MESSAGE)}

    } - data-test-subj={SESSION_VIEW_ERROR_TEST_ID} - /> - ); - } + // TODO as part of https://github.com/elastic/security-team/issues/7031 + // bring back no data message if needed return (
    diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.test.tsx index 646cbb2fc9bd5..4bc1a8f5fb0d0 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.test.tsx @@ -36,15 +36,21 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( ); const INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID = `${CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID}InvestigateInTimeline`; +const renderSuppressedAlerts = (alertSuppressionCount: number) => + render( + + + + + + ); + describe('', () => { it('should render zero component correctly', () => { - const { getByTestId, queryByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderSuppressedAlerts(0); expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_TEXT)).toHaveTextContent('0 suppressed alert'); @@ -54,13 +60,7 @@ describe('', () => { }); it('should render single component correctly', () => { - const { getByTestId, queryByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderSuppressedAlerts(1); expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_TEXT)).toHaveTextContent('1 suppressed alert'); @@ -70,13 +70,7 @@ describe('', () => { }); it('should render multiple component correctly', () => { - const { getByTestId, queryByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderSuppressedAlerts(2); expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); expect(getByTestId(TITLE_TEXT)).toHaveTextContent('2 suppressed alerts'); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.tsx index 9b543e23f6535..c2123ced63feb 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/suppressed_alerts.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { EuiBetaBadge, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; -import { CORRELATIONS_SUPPRESSED_ALERTS } from '../../shared/translations'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import { CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID, @@ -21,7 +21,7 @@ export interface SuppressedAlertsProps { /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; /** * Value of the kibana.alert.suppression.doc_count field */ @@ -38,7 +38,11 @@ export const SuppressedAlerts: React.VFC = ({ const title = ( - {`${alertSuppressionCount} ${CORRELATIONS_SUPPRESSED_ALERTS(alertSuppressionCount)}`} + +const renderThreatIntelligenceDetails = (contextValue: LeftPanelContext) => render( @@ -59,7 +59,7 @@ describe('', () => { eventFields: {}, }); - const wrapper = renderSUT(defaultContextValue); + const wrapper = renderThreatIntelligenceDetails(defaultContextValue); expect( wrapper.getByTestId(THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID) @@ -79,9 +79,9 @@ describe('', () => { eventFields: {}, }); - const wrapper = renderSUT(defaultContextValue); + const wrapper = renderThreatIntelligenceDetails(defaultContextValue); - expect(wrapper.getByTestId(THREAT_INTELLIGENCE_DETAILS_SPINNER_TEST_ID)).toBeInTheDocument(); + expect(wrapper.getByTestId(THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID)).toBeInTheDocument(); expect(useThreatIntelligenceDetails).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/threat_intelligence_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/threat_intelligence_details.tsx index fff9a607f3501..6a5576ba9aa90 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/threat_intelligence_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/threat_intelligence_details.tsx @@ -11,7 +11,7 @@ import isEmpty from 'lodash/isEmpty'; import { EnrichmentRangePicker } from '../../../common/components/event_details/cti_details/enrichment_range_picker'; import { ThreatDetailsView } from '../../../common/components/event_details/cti_details/threat_details_view'; import { useThreatIntelligenceDetails } from '../hooks/use_threat_intelligence_details'; -import { THREAT_INTELLIGENCE_DETAILS_SPINNER_TEST_ID } from './test_ids'; +import { THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID } from './test_ids'; export const THREAT_INTELLIGENCE_TAB_ID = 'threat-intelligence-details'; @@ -33,7 +33,7 @@ export const ThreatIntelligenceDetails: React.FC = () => { return ( diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/translations.ts b/x-pack/plugins/security_solution/public/flyout/left/components/translations.ts deleted file mode 100644 index 1b37fef66ec71..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/left/components/translations.ts +++ /dev/null @@ -1,279 +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 { i18n } from '@kbn/i18n'; - -export const ENTITIES_NO_DATA_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.entitiesNoDataMessage', - { - defaultMessage: 'Host and user information are unavailable for this alert.', - } -); - -export const ANALYZER_ERROR_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.analyzerErrorMessage', - { - defaultMessage: 'analyzer', - } -); - -export const SESSION_VIEW_ERROR_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.sessionViewErrorMessage', - { - defaultMessage: 'session view', - } -); - -export const CORRELATIONS_ERROR_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.correlationsErrorMessage', - { - defaultMessage: 'No correlations data available', - } -); - -export const USER_TITLE = i18n.translate('xpack.securitySolution.flyout.entities.userTitle', { - defaultMessage: 'User', -}); - -export const USER_PREVALENCE_COLUMN_TITLE_TOOLTIP = i18n.translate( - 'xpack.securitySolution.flyout.entities.userPrevalenceColumTitleTooltip', - { - defaultMessage: 'Percentage of unique users with identical field value pairs', - } -); - -export const USERS_INFO_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.entities.usersInfoTitle', - { - defaultMessage: 'User information', - } -); - -export const RELATED_HOSTS_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.entities.relatedHostsTitle', - { - defaultMessage: 'Related hosts', - } -); - -export const RELATED_HOSTS_TABLE_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.entities.relatedHostsTableNoData', - { - defaultMessage: 'No hosts identified', - } -); - -export const RELATED_HOSTS_TOOL_TIP = (userName: string) => - i18n.translate('xpack.securitySolution.flyout.entities.relatedHostsToolTip', { - defaultMessage: - 'After this alert was generated, {userName} logged into these hosts. Check if this activity is normal.', - values: { userName }, - }); - -export const RELATED_ENTITIES_NAME_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.entities.relatedEntitiesNameColumn', - { - defaultMessage: 'Name', - } -); - -export const RELATED_ENTITIES_IP_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.entities.relatedEntitiesIpColumn', - { - defaultMessage: 'Ip addresses', - } -); - -export const HOST_TITLE = i18n.translate('xpack.securitySolution.flyout.entities.hostTitle', { - defaultMessage: 'Host', -}); - -export const HOST_PREVALENCE_COLUMN_TITLE_TOOLTIP = i18n.translate( - 'xpack.securitySolution.flyout.entities.hostPrevalenceColumTitleTooltip', - { - defaultMessage: 'Percentage of unique hosts with identical field value pairs', - } -); - -export const HOSTS_INFO_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.entities.hostsInfoTitle', - { - defaultMessage: 'Host information', - } -); - -export const RELATED_USERS_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.entities.relatedUsersTitle', - { - defaultMessage: 'Related users', - } -); - -export const RELATED_USERS_TABLE_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.entities.relatedUsersTableNoData', - { - defaultMessage: 'No users identified', - } -); - -export const RELATED_USERS_TOOL_TIP = (hostName: string) => - i18n.translate('xpack.securitySolution.flyout.entities.relatedUsersToolTip', { - defaultMessage: - 'After this alert was generated, these users logged into {hostName}. Check if this activity is normal.', - values: { hostName }, - }); - -export const PREVALENCE_ERROR_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceErrorMessage', - { - defaultMessage: 'prevalence', - } -); - -export const PREVALENCE_NO_DATA_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceNoDataMessage', - { - defaultMessage: 'No prevalence data available', - } -); - -export const PREVALENCE_TABLE_FIELD_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTableFieldColumnTitle', - { - defaultMessage: 'Field', - } -); - -export const PREVALENCE_TABLE_VALUE_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTableValueColumnTitle', - { - defaultMessage: 'Value', - } -); - -export const PREVALENCE_TABLE_ALERT_COUNT_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitle', - { - defaultMessage: 'Alert', - } -); - -export const PREVALENCE_TABLE_ALERT_COUNT_COLUMN_TITLE_TOOLTIP = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitleTooltip', - { - defaultMessage: 'Total number of alerts with identical field value pairs', - } -); - -export const PREVALENCE_TABLE_DOC_COUNT_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitle', - { - defaultMessage: 'Document', - } -); - -export const PREVALENCE_TABLE_DOC_COUNT_COLUMN_TITLE_TOOLTIP = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitleTooltip', - { - defaultMessage: 'Total number of event documents with identical field value pairs', - } -); - -export const PREVALENCE_TABLE_COUNT_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTableCountColumnTitle', - { - defaultMessage: 'count', - } -); - -export const PREVALENCE_TABLE_PREVALENCE_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceTablePrevalenceColumnTitle', - { - defaultMessage: 'prevalence', - } -); - -export const RESPONSE_TITLE = i18n.translate('xpack.securitySolution.flyout.response.title', { - defaultMessage: 'Responses', -}); - -export const CORRELATIONS_TIMESTAMP_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.correlations.timestampColumnTitle', - { - defaultMessage: 'Timestamp', - } -); - -export const CORRELATIONS_RULE_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.correlations.ruleColumnTitle', - { - defaultMessage: 'Rule', - } -); - -export const CORRELATIONS_REASON_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.correlations.reasonColumnTitle', - { - defaultMessage: 'Reason', - } -); - -export const CORRELATIONS_SEVERITY_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.correlations.severityColumnTitle', - { - defaultMessage: 'Severity', - } -); - -export const CORRELATIONS_CASE_STATUS_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.correlations.statusColumnTitle', - { - defaultMessage: 'Status', - } -); - -export const CORRELATIONS_CASE_NAME_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.correlations.caseNameColumnTitle', - { - defaultMessage: 'Name', - } -); - -export const CORRELATIONS_DETAILS_TABLE_FILTER = i18n.translate( - 'xpack.securitySolution.flyout.correlations.correlationsDetailsTableFilter', - { - defaultMessage: 'Correlations Details Table Alert IDs', - } -); - -export const RELATED_ALERTS_BY_ANCESTRY_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.correlations.relatedAlertsByAncestryNoData', - { - defaultMessage: 'No alerts related by ancestry', - } -); - -export const RELATED_ALERTS_BY_SOURCE_EVENT_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.correlations.relatedAlertsBySourceEventNoData', - { - defaultMessage: 'No related source events', - } -); - -export const RELATED_ALERTS_BY_SESSION_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.correlations.relatedAlertsBySessionNoData', - { - defaultMessage: 'No alerts related by session', - } -); - -export const RELATED_CASES_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.correlations.relatedCasesNoData', - { - defaultMessage: 'No related cases', - } -); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx index 845856ab04f3b..6667d7eacd97e 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx @@ -119,7 +119,14 @@ const mockRelatedHostsResponse = { loading: false, }; -describe('', () => { +const renderUserDetails = () => + render( + + + + ); + +describe('', () => { beforeEach(() => { jest.clearAllMocks(); mockUseMlUserPermissions.mockReturnValue({ isPlatinumOrTrialLicense: false, capabilities: {} }); @@ -129,21 +136,13 @@ describe('', () => { }); it('should render host details correctly', () => { - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderUserDetails(); expect(getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(USER_DETAILS_TEST_ID))).toBeInTheDocument(); }); describe('Host overview', () => { it('should render the HostOverview with correct dates and indices', () => { - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderUserDetails(); expect(mockUseObservedUserDetails).toBeCalledWith({ id: 'entities-users-details-uuid', startDate: from, @@ -160,32 +159,20 @@ describe('', () => { isPlatinumOrTrialLicense: true, capabilities: {}, }); - const { getByText } = render( - - - - ); + const { getByText } = renderUserDetails(); expect(getByText('User risk score')).toBeInTheDocument(); }); it('should not render user risk score when license is not valid', () => { mockUseRiskScore.mockReturnValue({ data: [], isAuthorized: false }); - const { queryByText } = render( - - - - ); + const { queryByText } = renderUserDetails(); expect(queryByText('User risk score')).not.toBeInTheDocument(); }); }); describe('Related hosts', () => { it('should render the related host table with correct dates and indices', () => { - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderUserDetails(); expect(mockUseUsersRelatedHosts).toBeCalledWith({ from: timestamp, userName: 'test user', @@ -200,11 +187,7 @@ describe('', () => { isPlatinumOrTrialLicense: true, capabilities: {}, }); - const { queryAllByRole } = render( - - - - ); + const { queryAllByRole } = renderUserDetails(); expect(queryAllByRole('columnheader').length).toBe(3); expect(queryAllByRole('row')[1].textContent).toContain('test host'); expect(queryAllByRole('row')[1].textContent).toContain('100.XXX.XXX'); @@ -212,11 +195,7 @@ describe('', () => { }); it('should not render host risk score column when license is not valid', () => { - const { queryAllByRole } = render( - - - - ); + const { queryAllByRole } = renderUserDetails(); expect(queryAllByRole('columnheader').length).toBe(2); }); @@ -227,11 +206,7 @@ describe('', () => { loading: false, }); - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderUserDetails(); expect(getByTestId(USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID).textContent).toContain( 'No hosts identified' ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/user_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/user_details.tsx index c5a135eb621bb..12ad057284cd6 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/user_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/user_details.tsx @@ -20,6 +20,7 @@ import { EuiPanel, } from '@elastic/eui'; import type { EuiBasicTableColumn } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { getSourcererScopeId } from '../../../helpers'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import type { RelatedHost } from '../../../../common/search_strategy/security_solution/related_entities/related_hosts'; @@ -50,7 +51,6 @@ import { getEmptyTagValue } from '../../../common/components/empty_value'; import { USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID, USER_DETAILS_TEST_ID } from './test_ids'; import { ENTITY_RISK_CLASSIFICATION } from '../../../explore/components/risk_score/translations'; import { HOST_RISK_TOOLTIP } from '../../../explore/hosts/components/hosts_table/translations'; -import * as i18n from './translations'; import { useHasSecurityCapability } from '../../../helper_hooks'; const USER_DETAILS_ID = 'entities-users-details'; @@ -129,7 +129,12 @@ export const UserDetails: React.FC = ({ userName, timestamp, s () => [ { field: 'host', - name: i18n.RELATED_ENTITIES_NAME_COLUMN_TITLE, + name: ( + + ), render: (host: string) => ( = ({ userName, timestamp, s }, { field: 'ip', - name: i18n.RELATED_ENTITIES_IP_COLUMN_TITLE, + name: ( + + ), render: (ips: string[]) => { return ( = ({ userName, timestamp, s - {`${i18n.RELATED_HOSTS_TITLE}: ${totalCount}`} + + + @@ -216,7 +232,12 @@ export const UserDetails: React.FC = ({ userName, timestamp, s return ( <> -

    {i18n.USER_TITLE}

    +

    + +

    = ({ userName, timestamp, s data-test-subj={USER_DETAILS_TEST_ID} > -
    {i18n.USERS_INFO_TITLE}
    +
    + +
    = ({ userName, timestamp, s -
    {i18n.RELATED_HOSTS_TITLE}
    +
    + +
    - + + } + > @@ -290,11 +329,21 @@ export const UserDetails: React.FC = ({ userName, timestamp, s loading={isRelatedHostLoading} data-test-subj={USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID} pagination={pagination} - message={i18n.RELATED_HOSTS_TABLE_NO_DATA} + message={ + + } /> + } inspectIndex={0} /> diff --git a/x-pack/plugins/security_solution/public/flyout/left/context.tsx b/x-pack/plugins/security_solution/public/flyout/left/context.tsx index 15de2dfc9a78d..a791dcbf5fb5a 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/context.tsx @@ -6,24 +6,15 @@ */ import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { css } from '@emotion/react'; -import React, { createContext, useContext, useMemo } from 'react'; -import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import React, { createContext, memo, useContext, useMemo } from 'react'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { useEventDetails } from '../shared/hooks/use_event_details'; +import { FlyoutError } from '../shared/components/flyout_error'; +import { FlyoutLoading } from '../shared/components/flyout_loading'; import type { SearchHit } from '../../../common/search_strategy'; import type { LeftPanelProps } from '.'; import type { GetFieldsData } from '../../common/hooks/use_get_fields_data'; -import { useGetFieldsData } from '../../common/hooks/use_get_fields_data'; -import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { - getAlertIndexAlias, - useBasicDataFromDetailsData, -} from '../../timelines/components/side_panel/event_details/helpers'; -import { useSpaceId } from '../../common/hooks/use_space_id'; -import { useRouteSpy } from '../../common/utils/route/use_route_spy'; -import { SecurityPageName } from '../../../common/constants'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; -import { useSourcererDataView } from '../../common/containers/sourcerer'; +import { useBasicDataFromDetailsData } from '../../timelines/components/side_panel/event_details/helpers'; import { useRuleWithFallback } from '../../detection_engine/rule_management/logic/use_rule_with_fallback'; export interface LeftPanelContext { @@ -42,19 +33,19 @@ export interface LeftPanelContext { /** * An object containing fields by type */ - browserFields: BrowserFields | null; + browserFields: BrowserFields; /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * The actual raw document object */ - searchHit: SearchHit | undefined; + searchHit: SearchHit; /** * User defined fields to highlight (defined on the rule) */ @@ -74,69 +65,66 @@ export type LeftPanelProviderProps = { children: React.ReactNode; } & Partial; -export const LeftPanelProvider = ({ id, indexName, scopeId, children }: LeftPanelProviderProps) => { - const currentSpaceId = useSpaceId(); - const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - const sourcererDataView = useSourcererDataView(sourcererScope); - const [loading, dataFormattedForFieldBrowser, searchHit, dataAsNestedObject] = - useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); - const getFieldsData = useGetFieldsData(searchHit?.fields); - const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); - const { rule: maybeRule } = useRuleWithFallback(ruleId); - - const contextValue = useMemo( - () => - id && indexName && scopeId - ? { - eventId: id, - indexName, - scopeId, - browserFields: sourcererDataView.browserFields, - dataAsNestedObject, - dataFormattedForFieldBrowser, - searchHit, - investigationFields: maybeRule?.investigation_fields?.field_names ?? [], - getFieldsData, - } - : undefined, - [ - id, - indexName, - scopeId, - sourcererDataView.browserFields, +export const LeftPanelProvider = memo( + ({ id, indexName, scopeId, children }: LeftPanelProviderProps) => { + const { + browserFields, dataAsNestedObject, dataFormattedForFieldBrowser, - searchHit, - maybeRule?.investigation_fields, getFieldsData, - ] - ); + loading, + searchHit, + } = useEventDetails({ eventId: id, indexName }); + + const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const { rule: maybeRule } = useRuleWithFallback(ruleId); - if (loading) { - return ( - - - + const contextValue = useMemo( + () => + id && + indexName && + scopeId && + dataAsNestedObject && + dataFormattedForFieldBrowser && + searchHit + ? { + eventId: id, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + investigationFields: maybeRule?.investigation_fields?.field_names ?? [], + getFieldsData, + } + : undefined, + [ + id, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + maybeRule?.investigation_fields, + getFieldsData, + ] ); + + if (loading) { + return ; + } + + if (!contextValue) { + return ; + } + + return {children}; } +); - return {children}; -}; +LeftPanelProvider.displayName = 'LeftPanelProvider'; export const useLeftPanelContext = () => { const contextValue = useContext(LeftPanelContext); diff --git a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_fetch_alerts.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_fetch_alerts.test.tsx index 1641095e2d50f..cc28c004741bd 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_fetch_alerts.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_fetch_alerts.test.tsx @@ -17,7 +17,7 @@ jest.mock('../services/find_alerts'); describe('useFetchAlerts', () => { beforeEach(() => { - jest.mocked(useKibana).mockReturnValue({ + (useKibana as jest.Mock).mockReturnValue({ services: { data: { search: { @@ -25,8 +25,7 @@ describe('useFetchAlerts', () => { }, }, }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); + }); }); it('fetches alerts and handles loading state', async () => { @@ -66,6 +65,9 @@ describe('useFetchAlerts', () => { {children} ); + // hide console error due to the line after + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest .mocked(createFindAlerts) .mockReturnValue(jest.fn().mockRejectedValue(new Error('Fetch failed'))); diff --git a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts index 1e92ff0a6bd45..14319c8fa4404 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts @@ -19,6 +19,7 @@ import { type GetBasicDataFromDetailsData, useBasicDataFromDetailsData, } from '../../../timelines/components/side_panel/event_details/helpers'; +import { mockContextValue } from '../mocks/mock_context'; jest.mock('../../../timelines/containers/details'); jest.mock('../../../common/containers/sourcerer'); @@ -63,20 +64,7 @@ describe('useThreatIntelligenceDetails', () => { () => {}, ]); - jest.mocked(useLeftPanelContext).mockReturnValue({ - indexName: 'test-index', - eventId: 'test-event-id', - getFieldsData: () => null, - dataFormattedForFieldBrowser: null, - scopeId: 'test-scope-id', - browserFields: null, - searchHit: { - _id: 'testId', - _index: 'testIndex', - }, - dataAsNestedObject: null, - investigationFields: [], - }); + jest.mocked(useLeftPanelContext).mockReturnValue(mockContextValue); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.ts b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.ts index 0cdc66a95a99f..c291e2a123c3d 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.ts @@ -6,6 +6,7 @@ */ import { useMemo } from 'react'; +import type { RunTimeMappings } from '../../../../common/api/search_strategy'; import type { CtiEnrichment, EventFields } from '../../../../common/search_strategy'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { @@ -53,7 +54,7 @@ export const useThreatIntelligenceDetails = (): ThreatIntelligenceDetailsValue = const [isEventDataLoading, eventData] = useTimelineEventsDetails({ indexName, eventId, - runtimeMappings: sourcererDataView.runtimeMappings, + runtimeMappings: sourcererDataView.runtimeMappings as RunTimeMappings, skip: !eventId, }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts index abc36f1bf202a..4233a28c1164e 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts @@ -5,31 +5,13 @@ * 2.0. */ -import { ALERT_RISK_SCORE, ALERT_SEVERITY } from '@kbn/rule-data-utils'; +import { mockBrowserFields } from '../../shared/mocks/mock_browser_fields'; +import { mockSearchHit } from '../../shared/mocks/mock_search_hit'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; +import { mockDataAsNestedObject } from '../../shared/mocks/mock_data_as_nested_object'; import type { LeftPanelContext } from '../context'; -/** - * Returns mocked data for field (mock this method: x-pack/plugins/security_solution/public/common/hooks/use_get_fields_data.ts) - * @param field - * @returns string[] - */ -export const mockGetFieldsData = (field: string): string[] => { - switch (field) { - case ALERT_SEVERITY: - return ['low']; - case ALERT_RISK_SCORE: - return ['0']; - case 'host.name': - return ['host1']; - case 'user.name': - return ['user1']; - case '@timestamp': - return ['2022-07-25T08:20:18.966Z']; - default: - return []; - } -}; - /** * Mock contextValue for left panel context */ @@ -37,15 +19,10 @@ export const mockContextValue: LeftPanelContext = { eventId: 'eventId', indexName: 'index', scopeId: 'scopeId', - browserFields: {}, - dataFormattedForFieldBrowser: [], + browserFields: mockBrowserFields, + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, getFieldsData: mockGetFieldsData, - searchHit: { - _id: 'testId', - _index: 'testIndex', - }, - dataAsNestedObject: { - _id: 'testId', - }, + searchHit: mockSearchHit, + dataAsNestedObject: mockDataAsNestedObject, investigationFields: [], }; diff --git a/x-pack/plugins/security_solution/public/flyout/left/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/left/tabs.tsx index 9ac70d515cc45..c2fc6e5fe20db 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/tabs.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/tabs.tsx @@ -5,10 +5,11 @@ * 2.0. */ +import type { ReactElement } from 'react'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { VisualizeTab } from './tabs/visualize_tab'; import { InvestigationTab } from './tabs/investigation_tab'; -import { INSIGHTS_TAB, INVESTIGATIONS_TAB, RESPONSE_TAB, VISUALIZE_TAB } from './translations'; import { InsightsTab } from './tabs/insights_tab'; import type { LeftPanelPaths } from '.'; import { @@ -22,7 +23,7 @@ import { ResponseTab } from './tabs/response_tab'; export type LeftPanelTabsType = Array<{ id: LeftPanelPaths; 'data-test-subj': string; - name: string; + name: ReactElement; content: React.ReactElement; visible: boolean; }>; @@ -31,28 +32,48 @@ export const tabs: LeftPanelTabsType = [ { id: 'visualize', 'data-test-subj': VISUALIZE_TAB_TEST_ID, - name: VISUALIZE_TAB, + name: ( + + ), content: , visible: false, }, { id: 'insights', 'data-test-subj': INSIGHTS_TAB_TEST_ID, - name: INSIGHTS_TAB, + name: ( + + ), content: , visible: true, }, { id: 'investigation', 'data-test-subj': INVESTIGATION_TAB_TEST_ID, - name: INVESTIGATIONS_TAB, + name: ( + + ), content: , visible: true, }, { id: 'response', 'data-test-subj': RESPONSE_TAB_TEST_ID, - name: RESPONSE_TAB, + name: ( + + ), content: , visible: true, }, diff --git a/x-pack/plugins/security_solution/public/flyout/left/tabs/insights_tab.tsx b/x-pack/plugins/security_solution/public/flyout/left/tabs/insights_tab.tsx index a041a980752be..6f35b6b26f3c8 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/tabs/insights_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/tabs/insights_tab.tsx @@ -10,6 +10,8 @@ import React, { memo, useCallback, useState, useEffect } from 'react'; import { EuiButtonGroup, EuiSpacer } from '@elastic/eui'; import type { EuiButtonGroupOptionProps } from '@elastic/eui/src/components/button/button_group/button_group'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { INSIGHTS_TAB_BUTTON_GROUP_TEST_ID, INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID, @@ -19,13 +21,6 @@ import { } from './test_ids'; import { useLeftPanelContext } from '../context'; import { LeftPanelKey, LeftPanelInsightsTab } from '..'; -import { - INSIGHTS_BUTTONGROUP_OPTIONS, - ENTITIES_BUTTON, - THREAT_INTELLIGENCE_BUTTON, - PREVALENCE_BUTTON, - CORRELATIONS_BUTTON, -} from './translations'; import { ENTITIES_TAB_ID, EntitiesDetails } from '../components/entities_details'; import { THREAT_INTELLIGENCE_TAB_ID, @@ -37,22 +32,42 @@ import { CORRELATIONS_TAB_ID, CorrelationsDetails } from '../components/correlat const insightsButtons: EuiButtonGroupOptionProps[] = [ { id: ENTITIES_TAB_ID, - label: ENTITIES_BUTTON, + label: ( + + ), 'data-test-subj': INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID, }, { id: THREAT_INTELLIGENCE_TAB_ID, - label: THREAT_INTELLIGENCE_BUTTON, + label: ( + + ), 'data-test-subj': INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON_TEST_ID, }, { id: PREVALENCE_TAB_ID, - label: PREVALENCE_BUTTON, + label: ( + + ), 'data-test-subj': INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID, }, { id: CORRELATIONS_TAB_ID, - label: CORRELATIONS_BUTTON, + label: ( + + ), 'data-test-subj': INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID, }, ]; @@ -97,7 +112,12 @@ export const InsightsTab: React.FC = memo(() => { { - const { dataFormattedForFieldBrowser } = useLeftPanelContext(); - if (dataFormattedForFieldBrowser == null) { - return null; - } - return ( diff --git a/x-pack/plugins/security_solution/public/flyout/left/tabs/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/left/tabs/test_ids.ts index 7c0e3ca332e8b..bb1dfa035f13a 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/tabs/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/tabs/test_ids.ts @@ -5,23 +5,23 @@ * 2.0. */ -export const VISUALIZE_TAB_BUTTON_GROUP_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutVisualizeTabButtonGroup'; +import { PREFIX } from '../../shared/test_ids'; + +const VISUALIZE_TAB_TEST_ID = `${PREFIX}VisualizeTab` as const; +export const VISUALIZE_TAB_BUTTON_GROUP_TEST_ID = `${VISUALIZE_TAB_TEST_ID}ButtonGroup` as const; export const VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutVisualizeTabSessionViewButton'; + `${VISUALIZE_TAB_TEST_ID}SessionViewButton` as const; export const VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutVisualizeTabGraphAnalyzerButton'; -export const INSIGHTS_TAB_BUTTON_GROUP_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsTabButtonGroup'; + `${VISUALIZE_TAB_TEST_ID}GraphAnalyzerButton` as const; +const INSIGHTS_TAB_TEST_ID = `${PREFIX}InsightsTab` as const; +export const INSIGHTS_TAB_BUTTON_GROUP_TEST_ID = `${INSIGHTS_TAB_TEST_ID}ButtonGroup` as const; export const INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsTabEntitiesButton'; + `${INSIGHTS_TAB_TEST_ID}EntitiesButton` as const; export const INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsTabThreatIntelligenceButton'; + `${INSIGHTS_TAB_TEST_ID}ThreatIntelligenceButton` as const; export const INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsTabPrevalenceButton'; + `${INSIGHTS_TAB_TEST_ID}PrevalenceButton` as const; export const INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsTabCorrelationsButton'; -export const INVESTIGATION_TAB_CONTENT_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInvestigationsTabContent'; -export const RESPONSE_TAB_CONTENT_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutResponseTabContent'; + `${INSIGHTS_TAB_TEST_ID}CorrelationsButton` as const; +export const INVESTIGATION_TAB_CONTENT_TEST_ID = `${PREFIX}InvestigationsTabContent` as const; +export const RESPONSE_TAB_CONTENT_TEST_ID = `${PREFIX}ResponseTabContent` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/left/tabs/translations.ts b/x-pack/plugins/security_solution/public/flyout/left/tabs/translations.ts deleted file mode 100644 index e000ba2feabf8..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/left/tabs/translations.ts +++ /dev/null @@ -1,64 +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 { i18n } from '@kbn/i18n'; - -export const VISUALIZE_BUTTONGROUP_OPTIONS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.visualizeOptions', - { - defaultMessage: 'Visualize options', - } -); - -export const SESSION_VIEW_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.sessionViewButton', - { - defaultMessage: 'Session View', - } -); - -export const ANALYZER_GRAPH_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.analyzerGraphButton', - { - defaultMessage: 'Analyzer Graph', - } -); - -export const INSIGHTS_BUTTONGROUP_OPTIONS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.insightsOptions', - { - defaultMessage: 'Insights options', - } -); - -export const ENTITIES_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.entitiesButton', - { - defaultMessage: 'Entities', - } -); - -export const THREAT_INTELLIGENCE_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.threatIntelligenceButton', - { - defaultMessage: 'Threat intelligence', - } -); - -export const PREVALENCE_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.prevalenceButton', - { - defaultMessage: 'Prevalence', - } -); - -export const CORRELATIONS_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.correlationsButton', - { - defaultMessage: 'Correlations', - } -); diff --git a/x-pack/plugins/security_solution/public/flyout/left/tabs/visualize_tab.tsx b/x-pack/plugins/security_solution/public/flyout/left/tabs/visualize_tab.tsx index baedc5c1cec73..923a2e9aa3ed6 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/tabs/visualize_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/tabs/visualize_tab.tsx @@ -10,6 +10,8 @@ import React, { memo, useState, useCallback, useEffect } from 'react'; import { EuiButtonGroup, EuiSpacer } from '@elastic/eui'; import type { EuiButtonGroupOptionProps } from '@elastic/eui/src/components/button/button_group/button_group'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useLeftPanelContext } from '../context'; import { LeftPanelKey, LeftPanelVisualizeTab } from '..'; import { @@ -18,11 +20,6 @@ import { VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID, } from './test_ids'; import { ANALYZE_GRAPH_ID, AnalyzeGraph } from '../components/analyze_graph'; -import { - ANALYZER_GRAPH_BUTTON, - SESSION_VIEW_BUTTON, - VISUALIZE_BUTTONGROUP_OPTIONS, -} from './translations'; import { SESSION_VIEW_ID, SessionView } from '../components/session_view'; import { ALERTS_ACTIONS } from '../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction'; @@ -30,12 +27,22 @@ import { useStartTransaction } from '../../../common/lib/apm/use_start_transacti const visualizeButtons: EuiButtonGroupOptionProps[] = [ { id: SESSION_VIEW_ID, - label: SESSION_VIEW_BUTTON, + label: ( + + ), 'data-test-subj': VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID, }, { id: ANALYZE_GRAPH_ID, - label: ANALYZER_GRAPH_BUTTON, + label: ( + + ), 'data-test-subj': VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID, }, ]; @@ -83,7 +90,12 @@ export const VisualizeTab: FC = memo(() => { onChangeCompressed(id)} diff --git a/x-pack/plugins/security_solution/public/flyout/left/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/left/test_ids.ts index 65d70f7d674d5..4bcb3808dafe9 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/test_ids.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const VISUALIZE_TAB_TEST_ID = 'securitySolutionDocumentDetailsFlyoutVisualizeTab'; -export const INSIGHTS_TAB_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsTab'; -export const INVESTIGATION_TAB_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInvestigationTab'; -export const RESPONSE_TAB_TEST_ID = 'securitySolutionDocumentDetailsFlyoutResponseTab'; +import { PREFIX } from '../shared/test_ids'; + +export const VISUALIZE_TAB_TEST_ID = `${PREFIX}FlyoutVisualizeTab` as const; +export const INSIGHTS_TAB_TEST_ID = `${PREFIX}FlyoutInsightsTab` as const; +export const INVESTIGATION_TAB_TEST_ID = `${PREFIX}FlyoutInvestigationTab` as const; +export const RESPONSE_TAB_TEST_ID = `${PREFIX}FlyoutResponseTab` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/left/translations.ts b/x-pack/plugins/security_solution/public/flyout/left/translations.ts deleted file mode 100644 index c2dc888ab9aef..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/left/translations.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const VISUALIZE_TAB = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.visualizeTab', - { - defaultMessage: 'Visualize', - } -); - -export const INSIGHTS_TAB = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.insightsTab', - { - defaultMessage: 'Insights', - } -); - -export const INVESTIGATIONS_TAB = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.investigationsTab', - { - defaultMessage: 'Investigation', - } -); - -export const RESPONSE_TAB = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.responseTab', - { - defaultMessage: 'Response', - } -); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx index 30076fd3ca1d2..51feb8ce9d37f 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx @@ -6,9 +6,10 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { PreviewPanelContext } from '../context'; -import { mockContextValue } from '../mocks/mock_preview_panel_context'; +import { mockContextValue } from '../mocks/mock_context'; import { ALERT_REASON_PREVIEW_BODY_TEST_ID } from './test_ids'; import { AlertReasonPreview } from './alert_reason_preview'; import { ThemeProvider } from 'styled-components'; @@ -23,25 +24,15 @@ const panelContextValue = { describe('', () => { it('should render alert reason preview', () => { const { getByTestId } = render( - - - - - + + + + + + + ); expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toBeInTheDocument(); - }); - - it('should render null is dataAsNestedObject is null', () => { - const contextValue = { - ...mockContextValue, - dataAsNestedObject: null, - }; - const { queryByTestId } = render( - - - - ); - expect(queryByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toHaveTextContent('Alert reason'); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx index 985c2bb288fa2..476a1f3551948 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import styled from '@emotion/styled'; import { euiThemeVars } from '@kbn/ui-theme'; -import { ALERT_REASON_TITLE } from './translations'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ALERT_REASON_PREVIEW_BODY_TEST_ID } from './test_ids'; import { usePreviewPanelContext } from '../context'; import { getRowRenderer } from '../../../timelines/components/timeline/body/renderers/get_row_renderer'; @@ -29,16 +29,13 @@ export const AlertReasonPreview: React.FC = () => { const { dataAsNestedObject, scopeId } = usePreviewPanelContext(); const renderer = useMemo( - () => - dataAsNestedObject != null - ? getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }) - : null, + () => getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }), [dataAsNestedObject] ); const rowRenderer = useMemo( () => - renderer && dataAsNestedObject + renderer ? renderer.renderRow({ contextId: 'event-details', data: dataAsNestedObject, @@ -49,14 +46,19 @@ export const AlertReasonPreview: React.FC = () => { [renderer, dataAsNestedObject, scopeId] ); - if (!dataAsNestedObject || !renderer) { + if (!renderer) { return null; } return ( -
    {ALERT_REASON_TITLE}
    +
    + +
    diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx index 13be8994a7d14..bc3f6c5e8f8e3 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { RulePreview } from './rule_preview'; import { PreviewPanelContext } from '../context'; -import { mockContextValue } from '../mocks/mock_preview_panel_context'; +import { mockContextValue } from '../mocks/mock_context'; import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { ThemeProvider } from 'styled-components'; @@ -54,6 +54,19 @@ const contextValue = { ruleId: 'rule id', }; +const renderRulePreview = () => + render( + + + + + + + + + + ); + describe('', () => { beforeEach(() => { // (useAppToasts as jest.Mock).mockReturnValue(useAppToastsValueMock); @@ -63,7 +76,7 @@ describe('', () => { jest.clearAllMocks(); }); - it('should render rule preview and its sub sections', () => { + it('should render rule preview and its sub sections', async () => { mockUseRuleWithFallback.mockReturnValue({ rule: { name: 'rule name', description: 'rule description' }, }); @@ -74,30 +87,27 @@ describe('', () => { ruleActionsData: { actions: ['action'] }, }); mockUseGetSavedQuery.mockReturnValue({ isSavedQueryLoading: false, savedQueryBar: null }); - const { getByTestId } = render( - - - - - - - - - - ); - - expect(getByTestId(RULE_PREVIEW_BODY_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_ABOUT_CONTENT_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_DEFINITION_HEADER_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).toBeInTheDocument(); + + const { getByTestId } = renderRulePreview(); + + await act(async () => { + expect(getByTestId(RULE_PREVIEW_BODY_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toHaveTextContent('About'); + expect(getByTestId(RULE_PREVIEW_ABOUT_CONTENT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_DEFINITION_HEADER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_DEFINITION_HEADER_TEST_ID)).toHaveTextContent('Definition'); + expect(getByTestId(RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID)).toHaveTextContent('Schedule'); + expect(getByTestId(RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).toHaveTextContent('Actions'); + expect(getByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).toBeInTheDocument(); + }); }); - it('should not render actions if action is not available', () => { + it('should not render actions if action is not available', async () => { mockUseRuleWithFallback.mockReturnValue({ rule: { name: 'rule name', description: 'rule description' }, }); @@ -106,51 +116,27 @@ describe('', () => { defineRuleData: mockDefineStepRule(), scheduleRuleData: mockScheduleStepRule(), }); - const { queryByTestId } = render( - - - - - - - - - - ); - - expect(queryByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).not.toBeInTheDocument(); - expect(queryByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).not.toBeInTheDocument(); + const { queryByTestId } = renderRulePreview(); + + await act(async () => { + expect(queryByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).not.toBeInTheDocument(); + }); }); - it('should render loading spinner when rule is loading', () => { + it('should render loading spinner when rule is loading', async () => { mockUseRuleWithFallback.mockReturnValue({ loading: true, rule: null }); - const { getByTestId } = render( - - - - - - - - - - ); - expect(getByTestId(RULE_PREVIEW_LOADING_TEST_ID)).toBeInTheDocument(); + const { getByTestId } = renderRulePreview(); + await act(async () => { + expect(getByTestId(RULE_PREVIEW_LOADING_TEST_ID)).toBeInTheDocument(); + }); }); - it('should not render rule preview when rule is null', () => { + it('should not render rule preview when rule is null', async () => { mockUseRuleWithFallback.mockReturnValue({}); - const { queryByTestId } = render( - - - - - - - - - - ); - expect(queryByTestId(RULE_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument(); + const { queryByTestId } = renderRulePreview(); + await act(async () => { + expect(queryByTestId(RULE_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx index d1595f7419aa0..db87a9a681902 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx @@ -6,6 +6,7 @@ */ import React, { memo, useState, useEffect } from 'react'; import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel, EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useKibana } from '../../../common/lib/kibana'; import { useGetSavedQuery } from '../../../detections/pages/detection_engine/rules/use_get_saved_query'; import type { Rule } from '../../../detection_engine/rule_management/logic'; @@ -26,7 +27,6 @@ import { RULE_PREVIEW_ACTIONS_TEST_ID, RULE_PREVIEW_LOADING_TEST_ID, } from './test_ids'; -import * as i18n from './translations'; /** * Rule summary on a preview panel on top of the right section of expandable flyout @@ -84,7 +84,12 @@ export const RulePreview: React.FC = memo(() => { + } expanded data-test-subj={RULE_PREVIEW_ABOUT_TEST_ID} > @@ -103,7 +108,12 @@ export const RulePreview: React.FC = memo(() => { {defineRuleData && !isSavedQueryLoading && ( <> + } expanded={false} data-test-subj={RULE_PREVIEW_DEFINITION_TEST_ID} > @@ -125,7 +135,12 @@ export const RulePreview: React.FC = memo(() => { {scheduleRuleData && ( <> + } expanded={false} data-test-subj={RULE_PREVIEW_SCHEDULE_TEST_ID} > @@ -141,7 +156,12 @@ export const RulePreview: React.FC = memo(() => { )} {hasActions && ( + } expanded={false} data-test-subj={RULE_PREVIEW_ACTIONS_TEST_ID} > diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.test.tsx index 6a300c42976c9..a6df858cc5d57 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.test.tsx @@ -8,38 +8,37 @@ import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../common/mock'; -import { mockContextValue } from '../mocks/mock_preview_panel_context'; +import { mockContextValue } from '../mocks/mock_context'; import { PreviewPanelContext } from '../context'; import { RULE_PREVIEW_FOOTER_TEST_ID, RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID } from './test_ids'; import { RulePreviewFooter } from './rule_preview_footer'; -const contextValue = { - ...mockContextValue, - ruleId: 'rule id', -}; +const renderRulePreviewFooter = (contextValue: PreviewPanelContext) => + render( + + + + + + ); describe('', () => { it('renders rule details link correctly when ruleId is available', () => { - const { getByTestId } = render( - - - - - - ); + const contextValue = { + ...mockContextValue, + ruleId: 'rule id', + }; + const { getByTestId } = renderRulePreviewFooter(contextValue); expect(getByTestId(RULE_PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID)).toHaveTextContent( + 'Show rule details' + ); }); it('should not render rule details link when ruleId is not available', () => { - const { queryByTestId } = render( - - - - - - ); + const { queryByTestId } = renderRulePreviewFooter(mockContextValue); expect(queryByTestId(RULE_PREVIEW_FOOTER_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID)).not.toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.tsx index 161a28c53137d..e645a08f18197 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_footer.tsx @@ -7,9 +7,9 @@ import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFlyoutFooter } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { usePreviewPanelContext } from '../context'; import { RenderRuleName } from '../../../timelines/components/timeline/body/renderers/formatted_field_helpers'; -import { SHOW_RULE_DETAILS } from './translations'; import { SIGNAL_RULE_NAME_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants'; import { RULE_PREVIEW_FOOTER_TEST_ID } from './test_ids'; @@ -31,7 +31,9 @@ export const RulePreviewFooter: React.FC = memo(() => { isAggregatable={false} isDraggable={false} linkValue={ruleId} - value={SHOW_RULE_DETAILS} + value={i18n.translate('xpack.securitySolution.flyout.preview.rule.viewDetailsLabel', { + defaultMessage: 'Show rule details', + })} openInNewTab />
    diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.test.tsx index 719433051ef0c..a66a64f9b0811 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; +import type { RulePreviewTitleProps } from './rule_preview_title'; import { RulePreviewTitle } from './rule_preview_title'; import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; @@ -24,15 +25,19 @@ const defaultProps = { isSuppressed: false, }; +const renderRulePreviewTitle = (props: RulePreviewTitleProps) => + render( + + + + + + ); + describe('', () => { it('should render title and its components', () => { - const { getByTestId, queryByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderRulePreviewTitle(defaultProps); + expect(getByTestId(RULE_PREVIEW_TITLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_PREVIEW_RULE_CREATED_BY_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID)).toBeInTheDocument(); @@ -44,13 +49,7 @@ describe('', () => { ...defaultProps, isSuppressed: true, }; - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderRulePreviewTitle(props); expect(getByTestId(RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.tsx index ab15265d7dc21..9f3373fa80a3a 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.tsx @@ -17,7 +17,7 @@ import { RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID, } from './test_ids'; -interface RulePreviewTitleProps { +export interface RulePreviewTitleProps { /** * Rule object that represents relevant information about a rule */ diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts index c9894efe905b5..61842f9670415 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts @@ -5,40 +5,34 @@ * 2.0. */ +import { PREFIX } from '../../shared/test_ids'; import { CONTENT_TEST_ID, HEADER_TEST_ID } from '../../right/components/expandable_section'; /* Rule preview */ -export const RULE_PREVIEW_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewTitle'; +const RULE_PREVIEW_TEST_ID = `${PREFIX}RulePreview` as const; +export const RULE_PREVIEW_TITLE_TEST_ID = `${RULE_PREVIEW_TEST_ID}RulePreviewTitle` as const; export const RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutRulePreviewTitleSuppressed'; -export const RULE_PREVIEW_RULE_CREATED_BY_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutRulePreviewCreatedByText'; -export const RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutRulePreviewUpdatedByText'; - -export const RULE_PREVIEW_BODY_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewBody'; -export const RULE_PREVIEW_ABOUT_TEST_ID = `securitySolutionDocumentDetailsFlyoutRulePreviewAboutSection`; + `${RULE_PREVIEW_TITLE_TEST_ID}Suppressed` as const; +export const RULE_PREVIEW_RULE_CREATED_BY_TEST_ID = `${RULE_PREVIEW_TEST_ID}CreatedByText` as const; +export const RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID = `${RULE_PREVIEW_TEST_ID}UpdatedByText` as const; +export const RULE_PREVIEW_BODY_TEST_ID = `${RULE_PREVIEW_TEST_ID}Body` as const; +export const RULE_PREVIEW_ABOUT_TEST_ID = `${RULE_PREVIEW_TEST_ID}AboutSection` as const; export const RULE_PREVIEW_ABOUT_HEADER_TEST_ID = RULE_PREVIEW_ABOUT_TEST_ID + HEADER_TEST_ID; export const RULE_PREVIEW_ABOUT_CONTENT_TEST_ID = RULE_PREVIEW_ABOUT_TEST_ID + CONTENT_TEST_ID; -export const RULE_PREVIEW_DEFINITION_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutRulePreviewDefinitionSection'; +export const RULE_PREVIEW_DEFINITION_TEST_ID = `${RULE_PREVIEW_TEST_ID}DefinitionSection` as const; export const RULE_PREVIEW_DEFINITION_HEADER_TEST_ID = RULE_PREVIEW_DEFINITION_TEST_ID + HEADER_TEST_ID; export const RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID = RULE_PREVIEW_DEFINITION_TEST_ID + CONTENT_TEST_ID; -export const RULE_PREVIEW_SCHEDULE_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutRulePreviewScheduleSection'; +export const RULE_PREVIEW_SCHEDULE_TEST_ID = `${RULE_PREVIEW_TEST_ID}ScheduleSection` as const; export const RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID = RULE_PREVIEW_SCHEDULE_TEST_ID + HEADER_TEST_ID; export const RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID = RULE_PREVIEW_SCHEDULE_TEST_ID + CONTENT_TEST_ID; -export const RULE_PREVIEW_ACTIONS_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutRulePreviewActionsSection'; +export const RULE_PREVIEW_ACTIONS_TEST_ID = `${RULE_PREVIEW_TEST_ID}ActionsSection` as const; export const RULE_PREVIEW_ACTIONS_HEADER_TEST_ID = RULE_PREVIEW_ACTIONS_TEST_ID + HEADER_TEST_ID; export const RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID = RULE_PREVIEW_ACTIONS_TEST_ID + CONTENT_TEST_ID; -export const RULE_PREVIEW_LOADING_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutRulePreviewLoadingSpinner'; -export const RULE_PREVIEW_FOOTER_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewFooter'; -export const RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID = 'goToRuleDetails'; -export const ALERT_REASON_PREVIEW_BODY_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutAlertReasonPreviewBody'; +export const RULE_PREVIEW_LOADING_TEST_ID = `${RULE_PREVIEW_TEST_ID}Loading` as const; +export const RULE_PREVIEW_FOOTER_TEST_ID = `${RULE_PREVIEW_TEST_ID}Footer` as const; +export const RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID = 'goToRuleDetails' as const; +export const ALERT_REASON_PREVIEW_BODY_TEST_ID = `${PREFIX}AlertReasonPreviewBody` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/translations.ts b/x-pack/plugins/security_solution/public/flyout/preview/components/translations.ts deleted file mode 100644 index 36bfdd33ea2ca..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/translations.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const SHOW_RULE_DETAILS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.viewRuleDetailsText', - { defaultMessage: 'Show rule details' } -); - -export const RULE_PREVIEW_ABOUT_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.rulePreviewAboutSectionText', - { defaultMessage: 'About' } -); - -export const RULE_PREVIEW_DEFINITION_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.rulePreviewDefinitionSectionText', - { defaultMessage: 'Definition' } -); - -export const RULE_PREVIEW_SCHEDULE_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.rulePreviewScheduleSectionText', - { defaultMessage: 'Schedule' } -); - -export const RULE_PREVIEW_ACTIONS_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.rulePreviewActionsSectionText', - { defaultMessage: 'Actions' } -); - -export const ALERT_REASON_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.alertReasonTitle', - { defaultMessage: 'Alert reason' } -); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/context.tsx b/x-pack/plugins/security_solution/public/flyout/preview/context.tsx index 005ef1dcdb258..c99fbbd0456b9 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/context.tsx @@ -5,17 +5,13 @@ * 2.0. */ -import React, { createContext, useContext, useMemo } from 'react'; +import React, { createContext, memo, useContext, useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { SecurityPageName } from '@kbn/security-solution-navigation'; +import { useEventDetails } from '../shared/hooks/use_event_details'; +import { FlyoutError } from '../shared/components/flyout_error'; +import { FlyoutLoading } from '../shared/components/flyout_loading'; import type { PreviewPanelProps } from '.'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; -import { useSourcererDataView } from '../../common/containers/sourcerer'; -import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers'; -import { useSpaceId } from '../../common/hooks/use_space_id'; -import { useRouteSpy } from '../../common/utils/route/use_route_spy'; export interface PreviewPanelContext { /** @@ -41,7 +37,7 @@ export interface PreviewPanelContext { /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; } export const PreviewPanelContext = createContext(undefined); @@ -53,47 +49,43 @@ export type PreviewPanelProviderProps = { children: React.ReactNode; } & Partial; -export const PreviewPanelProvider = ({ - id, - indexName, - scopeId, - ruleId, - children, -}: PreviewPanelProviderProps) => { - const currentSpaceId = useSpaceId(); - const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - const sourcererDataView = useSourcererDataView(sourcererScope); - const [_, __, ___, dataAsNestedObject] = useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); +export const PreviewPanelProvider = memo( + ({ id, indexName, scopeId, ruleId, children }: PreviewPanelProviderProps) => { + const { dataAsNestedObject, indexPattern, loading } = useEventDetails({ + eventId: id, + indexName, + }); - const contextValue = useMemo( - () => - id && indexName && scopeId - ? { - eventId: id, - indexName, - scopeId, - ruleId: ruleId ?? '', - indexPattern: sourcererDataView.indexPattern, - dataAsNestedObject, - } - : undefined, - [id, indexName, scopeId, ruleId, sourcererDataView.indexPattern, dataAsNestedObject] - ); + const contextValue = useMemo( + () => + id && indexName && scopeId && dataAsNestedObject + ? { + eventId: id, + indexName, + scopeId, + ruleId: ruleId ?? '', + indexPattern, + dataAsNestedObject, + } + : undefined, + [id, indexName, scopeId, ruleId, indexPattern, dataAsNestedObject] + ); - return ( - {children} - ); -}; + if (loading) { + return ; + } + + if (!contextValue) { + return ; + } + + return ( + {children} + ); + } +); + +PreviewPanelProvider.displayName = 'PreviewPanelProvider'; export const usePreviewPanelContext = (): PreviewPanelContext => { const contextValue = useContext(PreviewPanelContext); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_context.ts new file mode 100644 index 0000000000000..3a12f3d9a580b --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_context.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockDataAsNestedObject } from '../../shared/mocks/mock_data_as_nested_object'; +import type { PreviewPanelContext } from '../context'; + +/** + * Mock contextValue for right panel context + */ +export const mockContextValue: PreviewPanelContext = { + eventId: 'eventId', + indexName: 'index', + scopeId: 'scopeId', + ruleId: '', + indexPattern: { fields: [], title: 'test index' }, + dataAsNestedObject: mockDataAsNestedObject, +}; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts b/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts deleted file mode 100644 index cdfe8ab5307ba..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { mockDataAsNestedObject } from '../../shared/mocks/mock_context'; -import type { PreviewPanelContext } from '../context'; - -/** - * Mock contextValue for right panel context - */ -export const mockContextValue: PreviewPanelContext = { - eventId: 'eventId', - indexName: 'index', - scopeId: 'scopeId', - ruleId: '', - indexPattern: { fields: [], title: 'test index' }, - dataAsNestedObject: mockDataAsNestedObject as unknown as Ecs, -}; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx b/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx index e585c58945d9c..b5e42754a46d3 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { AlertReasonPreview } from './components/alert_reason_preview'; import type { PreviewPanelPaths } from '.'; -import { ALERT_REASON_PREVIEW, RULE_PREVIEW } from './translations'; import { RulePreview } from './components/rule_preview'; import { RulePreviewFooter } from './components/rule_preview_footer'; @@ -17,10 +16,6 @@ export type PreviewPanelType = Array<{ * Id of the preview panel */ id: PreviewPanelPaths; - /** - * Panel name - */ - name: string; /** * Main body component to be rendered in the panel */ @@ -37,13 +32,11 @@ export type PreviewPanelType = Array<{ export const panels: PreviewPanelType = [ { id: 'rule-preview', - name: RULE_PREVIEW, content: , footer: , }, { id: 'alert-reason-preview', - name: ALERT_REASON_PREVIEW, content: , }, ]; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/translations.ts b/x-pack/plugins/security_solution/public/flyout/preview/translations.ts deleted file mode 100644 index cf359e7900cea..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/preview/translations.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const RULE_PREVIEW = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.rulePreviewPanel', - { defaultMessage: 'Rule preview' } -); - -export const ALERT_REASON_PREVIEW = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.alertReasonPreviewPanel', - { defaultMessage: 'Alert reason preview' } -); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/about_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/about_section.test.tsx index c0ee8c64eae81..9b672f5008460 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/about_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/about_section.test.tsx @@ -6,51 +6,45 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { ABOUT_SECTION_CONTENT_TEST_ID, ABOUT_SECTION_HEADER_TEST_ID } from './test_ids'; import { TestProviders } from '../../../common/mock'; import { AboutSection } from './about_section'; import { RightPanelContext } from '../context'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; +import { mockContextValue } from '../mocks/mock_context'; jest.mock('../../../common/components/link_to'); -describe('', () => { - it('should render the component collapsed', () => { - const { getByTestId } = render( - - - - - - ); +const renderAboutSection = (expanded: boolean = false) => + render( + + + + + + ); - expect(getByTestId(ABOUT_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); +describe('', () => { + it('should render the component collapsed', async () => { + const { getByTestId } = renderAboutSection(); + await act(async () => { + expect(getByTestId(ABOUT_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); + }); }); - it('should render the component expanded', () => { - const { getByTestId } = render( - - - - - - ); - - expect(getByTestId(ABOUT_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(ABOUT_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); + it('should render the component expanded', async () => { + const { getByTestId } = renderAboutSection(true); + await act(async () => { + expect(getByTestId(ABOUT_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ABOUT_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); + }); }); - it('should expand the component when clicking on the arrow on header', () => { - const { getByTestId } = render( - - - - - - ); - - getByTestId(ABOUT_SECTION_HEADER_TEST_ID).click(); - expect(getByTestId(ABOUT_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); + it('should expand the component when clicking on the arrow on header', async () => { + const { getByTestId } = renderAboutSection(); + await act(async () => { + getByTestId(ABOUT_SECTION_HEADER_TEST_ID).click(); + expect(getByTestId(ABOUT_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/about_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/about_section.tsx index d1cc1ca3f0a65..0d28b1c13f641 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/about_section.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/about_section.tsx @@ -8,9 +8,9 @@ import { EuiSpacer } from '@elastic/eui'; import type { VFC } from 'react'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ExpandableSection } from './expandable_section'; import { ABOUT_SECTION_TEST_ID } from './test_ids'; -import { ABOUT_TITLE } from './translations'; import { Description } from './description'; import { Reason } from './reason'; import { MitreAttack } from './mitre_attack'; @@ -29,7 +29,12 @@ export const AboutSection: VFC = ({ expanded = true }) => { return ( + } data-test-subj={ABOUT_SECTION_TEST_ID} > diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx index 8d691ad870892..780bea57d1f09 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx @@ -9,8 +9,8 @@ import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../common/mock'; import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockContextValue } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { RightPanelContext } from '../context'; import { AnalyzerPreview } from './analyzer_preview'; import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; @@ -21,66 +21,62 @@ jest.mock('../../../common/containers/alerts/use_alert_prevalence_from_process_t })); const mockUseAlertPrevalenceFromProcessTree = useAlertPrevalenceFromProcessTree as jest.Mock; -const contextValue = { - ...mockContextValue, - dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, -}; - -const contextValueEmpty = { - ...mockContextValue, - dataFormattedForFieldBrowser: [ - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - values: ['rule-uuid'], - originalValue: ['rule-uuid'], - isObjectArray: false, - }, - ], -}; +const renderAnalyzerPreview = (contextValue: RightPanelContext) => + render( + + + + + + ); describe('', () => { beforeEach(() => { jest.resetAllMocks(); }); - it('shows analyzer preview correctly when documentid and index are present', () => { + it('shows analyzer preview correctly when documentId and index are present', () => { mockUseAlertPrevalenceFromProcessTree.mockReturnValue({ loading: false, error: false, alertIds: ['alertid'], statsNodes: mock.mockStatsNodes, }); - const wrapper = render( - - - - - - ); + const contextValue = { + ...mockContextValue, + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, + }; + + const wrapper = renderAnalyzerPreview(contextValue); expect(mockUseAlertPrevalenceFromProcessTree).toHaveBeenCalledWith({ isActiveTimeline: false, documentId: 'ancestors-id', - indices: ['rule-parameters-index'], + indices: ['rule-indices'], }); expect(wrapper.getByTestId(ANALYZER_PREVIEW_TEST_ID)).toBeInTheDocument(); }); - it('does not show analyzer preview when documentid and index are not present', () => { + it('does not show analyzer preview when documentId and index are not present', () => { mockUseAlertPrevalenceFromProcessTree.mockReturnValue({ loading: false, error: false, alertIds: undefined, statsNodes: undefined, }); - const { queryByTestId } = render( - - - - - - ); + const contextValue = { + ...mockContextValue, + dataFormattedForFieldBrowser: [ + { + category: 'kibana', + field: 'kibana.alert.rule.uuid', + values: ['rule-uuid'], + originalValue: ['rule-uuid'], + isObjectArray: false, + }, + ], + }; + const { queryByTestId } = renderAnalyzerPreview(contextValue); expect(mockUseAlertPrevalenceFromProcessTree).toHaveBeenCalledWith({ isActiveTimeline: false, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx index 54fa7d6907bdc..42749285de612 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx @@ -7,10 +7,10 @@ import React, { useEffect, useMemo, useState } from 'react'; import { find } from 'lodash/fp'; import { EuiTreeView } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; import { getTreeNodes } from '../utils/analyzer_helpers'; -import { ANALYZER_PREVIEW_TITLE } from './translations'; -import { ANCESTOR_ID, RULE_PARAMETERS_INDEX } from '../../shared/constants/field_names'; +import { ANCESTOR_ID, RULE_INDICES } from '../../shared/constants/field_names'; import { useRightPanelContext } from '../context'; import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; import type { StatsNode } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; @@ -38,7 +38,7 @@ export const AnalyzerPreview: React.FC = () => { const processDocumentId = documentId && Array.isArray(documentId.values) ? documentId.values[0] : ''; - const index = find({ category: 'kibana', field: RULE_PARAMETERS_INDEX }, data); + const index = find({ category: 'kibana', field: RULE_INDICES }, data); const indices = index?.values ?? []; const { statsNodes } = useAlertPrevalenceFromProcessTree({ @@ -67,7 +67,12 @@ export const AnalyzerPreview: React.FC = () => {
    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx index 9892cfe3adf25..60e17a0bf8433 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { RightPanelContext } from '../context'; import { AnalyzerPreviewContainer } from './analyzer_preview_container'; import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver'; -import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; +import { ANALYZER_PREVIEW_NO_DATA_TEST_ID, ANALYZER_PREVIEW_TEST_ID } from './test_ids'; import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; import * as mock from '../mocks/mock_analyzer_data'; import { @@ -21,7 +21,7 @@ import { EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, } from '../../shared/components/test_ids'; -import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'; jest.mock('../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver'); @@ -33,15 +33,19 @@ jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; }); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => jest.fn(), + }; +}); const panelContextValue = { - dataAsNestedObject: null, dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, } as unknown as RightPanelContext; -const TEST_ID = ANALYZER_PREVIEW_TEST_ID; -const ERROR_TEST_ID = `${ANALYZER_PREVIEW_TEST_ID}Error`; - const renderAnalyzerPreview = () => render( @@ -70,8 +74,8 @@ describe('AnalyzerPreviewContainer', () => { const { getByTestId, queryByTestId } = renderAnalyzerPreview(); - expect(getByTestId(TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(ERROR_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(ANALYZER_PREVIEW_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(ANALYZER_PREVIEW_NO_DATA_TEST_ID)).not.toBeInTheDocument(); expect( getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID)) ).toBeInTheDocument(); @@ -97,8 +101,11 @@ describe('AnalyzerPreviewContainer', () => { const { getByTestId, queryByTestId } = renderAnalyzerPreview(); - expect(queryByTestId(TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(ERROR_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(ANALYZER_PREVIEW_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(ANALYZER_PREVIEW_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ANALYZER_PREVIEW_NO_DATA_TEST_ID)).toHaveTextContent( + 'You can only visualize events triggered by hosts configured with the Elastic Defend integration or any sysmon data from winlogbeat. Refer to Visual event analyzerExternal link(opens in a new tab or window) for more information.' + ); expect( getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID)) ).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx index 632a34e169f71..fd582b37d6fcb 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx @@ -18,8 +18,7 @@ import { setActiveTabTimeline } from '../../../timelines/store/timeline/actions' import { useRightPanelContext } from '../context'; import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver'; import { AnalyzerPreview } from './analyzer_preview'; -import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; -import { ANALYZER_PREVIEW_TITLE } from './translations'; +import { ANALYZER_PREVIEW_NO_DATA_TEST_ID, ANALYZER_PREVIEW_TEST_ID } from './test_ids'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; const timelineId = 'timeline-1'; @@ -31,7 +30,7 @@ export const AnalyzerPreviewContainer: React.FC = () => { const { dataAsNestedObject } = useRightPanelContext(); // decide whether to show the analyzer preview or not - const isEnabled = isInvestigateInResolverActionEnabled(dataAsNestedObject || undefined); + const isEnabled = isInvestigateInResolverActionEnabled(dataAsNestedObject); const dispatch = useDispatch(); const { startTransaction } = useStartTransaction(); @@ -58,7 +57,12 @@ export const AnalyzerPreviewContainer: React.FC = () => { return ( + ), iconType: 'timeline', ...(isEnabled && { callback: goToAnalyzerTab }), }} @@ -67,9 +71,9 @@ export const AnalyzerPreviewContainer: React.FC = () => { {isEnabled ? ( ) : ( -
    +

    {'sysmon'}, @@ -80,14 +84,14 @@ export const AnalyzerPreviewContainer: React.FC = () => { target="_blank" > ), }} /> -

    +

    )}
    ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx index 664b90a54b767..ec94fffd9bbe0 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx @@ -14,12 +14,13 @@ import { CorrelationsOverview } from './correlations_overview'; import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import { - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID, - INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, - INSIGHTS_CORRELATIONS_TEST_ID, + CORRELATIONS_NO_DATA_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, + CORRELATIONS_RELATED_CASES_TEST_ID, + CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, + CORRELATIONS_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID, } from './test_ids'; import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry'; @@ -48,33 +49,22 @@ jest.mock('../../shared/hooks/use_fetch_related_alerts_by_ancestry'); jest.mock('../../shared/hooks/use_fetch_related_alerts_by_same_source_event'); jest.mock('../../shared/hooks/use_fetch_related_cases'); -const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID); -const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(CORRELATIONS_TEST_ID); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(CORRELATIONS_TEST_ID); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(CORRELATIONS_TEST_ID); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_TEST_ID); -const SUPPRESSED_ALERTS_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID -); +const SUPPRESSED_ALERTS_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID); const RELATED_ALERTS_BY_ANCESTRY_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID + CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID ); const RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID + CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID ); const RELATED_ALERTS_BY_SESSION_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID + CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID ); -const RELATED_CASES_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID -); -const CORRELATIONS_ERROR_TEST_ID = `${INSIGHTS_CORRELATIONS_TEST_ID}Error`; +const RELATED_CASES_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID); const panelContextValue = { eventId: 'event id', @@ -141,12 +131,13 @@ describe('', () => { dataCount: 1, }); - const { getByTestId } = render(renderCorrelationsOverview(panelContextValue)); + const { getByTestId, queryByTestId } = render(renderCorrelationsOverview(panelContextValue)); expect(getByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RELATED_CASES_TEST_ID)).toBeInTheDocument(); expect(getByTestId(SUPPRESSED_ALERTS_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should hide rows and show error message if show values are false', () => { @@ -168,7 +159,10 @@ describe('', () => { expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(SUPPRESSED_ALERTS_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(CORRELATIONS_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_NO_DATA_TEST_ID)).toHaveTextContent( + 'No correlations data available.' + ); }); it('should hide rows if values are null', () => { @@ -184,6 +178,7 @@ describe('', () => { expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(SUPPRESSED_ALERTS_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should navigate to the left section Insights tab when clicking on button', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx index 750d260bbf21f..b9dddc7d07ac4 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx @@ -8,6 +8,7 @@ import React, { useCallback } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session'; import { RelatedAlertsBySession } from './related_alerts_by_session'; @@ -19,9 +20,8 @@ import { SuppressedAlerts } from './suppressed_alerts'; import { useShowSuppressedAlerts } from '../../shared/hooks/use_show_suppressed_alerts'; import { RelatedCases } from './related_cases'; import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases'; -import { INSIGHTS_CORRELATIONS_TEST_ID } from './test_ids'; +import { CORRELATIONS_NO_DATA_TEST_ID, CORRELATIONS_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; -import { CORRELATIONS_ERROR, CORRELATIONS_TITLE } from './translations'; import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details'; @@ -84,11 +84,16 @@ export const CorrelationsOverview: React.FC = () => { return ( + ), callback: goToCorrelationsTab, iconType: 'arrowStart', }} - data-test-subj={INSIGHTS_CORRELATIONS_TEST_ID} + data-test-subj={CORRELATIONS_TEST_ID} > {canShowAtLeastOneInsight ? ( @@ -107,7 +112,12 @@ export const CorrelationsOverview: React.FC = () => { )} ) : ( -
    {CORRELATIONS_ERROR}
    +

    + +

    )}
    ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx index f80d7c1939661..3dc09a7ac598f 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx @@ -6,16 +6,12 @@ */ import React from 'react'; +import { FormattedMessage, __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { DESCRIPTION_TITLE_TEST_ID, RULE_SUMMARY_BUTTON_TEST_ID } from './test_ids'; -import { - DOCUMENT_DESCRIPTION_TITLE, - PREVIEW_RULE_DETAILS, - RULE_DESCRIPTION_TITLE, -} from './translations'; import { Description } from './description'; import { RightPanelContext } from '../context'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { PreviewPanelKey } from '../../preview'; @@ -48,7 +44,7 @@ const flyoutContextValue = { openPreviewPanel: jest.fn(), } as unknown as ExpandableFlyoutContext; -const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null) => +const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]) => ({ eventId: 'event id', indexName: 'indexName', @@ -59,11 +55,13 @@ const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsIt const renderDescription = (panelContext: RightPanelContext) => render( - - - - - + + + + + + + ); describe('', () => { @@ -73,7 +71,7 @@ describe('', () => { ); expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(RULE_DESCRIPTION_TITLE); + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent('Rule description'); expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toBeInTheDocument(); }); @@ -83,7 +81,7 @@ describe('', () => { ); expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(RULE_DESCRIPTION_TITLE); + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent('Rule description'); expect(queryByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).not.toBeInTheDocument(); }); @@ -91,18 +89,7 @@ describe('', () => { const { getByTestId } = renderDescription(panelContextValue([ruleDescription])); expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(DOCUMENT_DESCRIPTION_TITLE); - }); - - it('should render null if dataFormattedForFieldBrowser is null', () => { - const panelContext = { - ...panelContextValue([ruleUuid, ruleDescription, ruleName]), - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { container } = renderDescription(panelContext); - - expect(container).toBeEmptyDOMElement(); + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent('Document description'); }); it('should open preview panel when clicking on button', () => { @@ -120,7 +107,12 @@ describe('', () => { indexName: panelContext.indexName, scopeId: panelContext.scopeId, banner: { - title: PREVIEW_RULE_DETAILS, + title: ( + + ), backgroundColor: 'warning', textColor: 'warning', }, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx index 6c4d49d9d6b4c..ac8ec64a0ee34 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx @@ -10,6 +10,7 @@ import type { FC } from 'react'; import React, { useMemo, useCallback } from 'react'; import { isEmpty } from 'lodash'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useRightPanelContext } from '../context'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { @@ -17,12 +18,6 @@ import { DESCRIPTION_TITLE_TEST_ID, RULE_SUMMARY_BUTTON_TEST_ID, } from './test_ids'; -import { - DOCUMENT_DESCRIPTION_TITLE, - RULE_DESCRIPTION_TITLE, - RULE_SUMMARY_TEXT, - PREVIEW_RULE_DETAILS, -} from './translations'; import { PreviewPanelKey, type PreviewPanelProps, RulePreviewPanel } from '../../preview'; /** @@ -45,7 +40,12 @@ export const Description: FC = () => { indexName, scopeId, banner: { - title: PREVIEW_RULE_DETAILS, + title: ( + + ), backgroundColor: 'warning', textColor: 'warning', }, @@ -66,17 +66,16 @@ export const Description: FC = () => { iconSide="right" data-test-subj={RULE_SUMMARY_BUTTON_TEST_ID} > - {RULE_SUMMARY_TEXT} + ), [ruleName, openRulePreview, ruleId] ); - if (!dataFormattedForFieldBrowser) { - return null; - } - const hasRuleDescription = ruleDescription && ruleDescription.length > 0; return ( @@ -86,12 +85,22 @@ export const Description: FC = () => { {isAlert ? ( -
    {RULE_DESCRIPTION_TITLE}
    +
    + +
    {viewRule}
    ) : ( -
    {DOCUMENT_DESCRIPTION_TITLE}
    +
    + +
    )} diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx index 528b839bb218c..ea66a0da11f17 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx @@ -16,7 +16,7 @@ import { } from './test_ids'; import { EntitiesOverview } from './entities_overview'; import { TestProviders } from '../../../common/mock'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, @@ -36,15 +36,18 @@ const mockContextValue = { getFieldsData: mockGetFieldsData, } as unknown as RightPanelContext; +const renderEntitiesOverview = (contextValue: RightPanelContext) => + render( + + + + + + ); + describe('', () => { it('should render wrapper component', () => { - const { getByTestId, queryByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderEntitiesOverview(mockContextValue); expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); @@ -54,15 +57,10 @@ describe('', () => { }); it('should render user and host', () => { - const { getByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderEntitiesOverview(mockContextValue); expect(getByTestId(ENTITIES_USER_OVERVIEW_TEST_ID)).toBeInTheDocument(); expect(getByTestId(ENTITIES_HOST_OVERVIEW_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should only render user when host name is null', () => { @@ -71,16 +69,11 @@ describe('', () => { getFieldsData: (field: string) => (field === 'user.name' ? 'user1' : null), } as unknown as RightPanelContext; - const { queryByTestId, getByTestId } = render( - - - - - - ); + const { queryByTestId, getByTestId } = renderEntitiesOverview(contextValue); expect(getByTestId(ENTITIES_USER_OVERVIEW_TEST_ID)).toBeInTheDocument(); expect(queryByTestId(ENTITIES_HOST_OVERVIEW_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should only render host when user name is null', () => { @@ -89,16 +82,11 @@ describe('', () => { getFieldsData: (field: string) => (field === 'host.name' ? 'host1' : null), } as unknown as RightPanelContext; - const { queryByTestId, getByTestId } = render( - - - - - - ); + const { queryByTestId, getByTestId } = renderEntitiesOverview(contextValue); expect(getByTestId(ENTITIES_HOST_OVERVIEW_TEST_ID)).toBeInTheDocument(); expect(queryByTestId(ENTITIES_USER_OVERVIEW_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should render no data message if both host name and user name are null/blank', () => { @@ -107,65 +95,11 @@ describe('', () => { getFieldsData: (field: string) => {}, } as unknown as RightPanelContext; - const { queryByTestId } = render( - - - - - - ); + const { queryByTestId } = renderEntitiesOverview(contextValue); expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).toBeInTheDocument(); - }); - - it('should not render if eventId is null', () => { - const contextValue = { - ...mockContextValue, - eventId: null, - } as unknown as RightPanelContext; - - const { container } = render( - - - - - + expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).toHaveTextContent( + 'Host and user information are unavailable for this alert.' ); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should not render if indexName is null', () => { - const contextValue = { - ...mockContextValue, - indexName: null, - } as unknown as RightPanelContext; - - const { container } = render( - - - - - - ); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should not render if scopeId is null', () => { - const contextValue = { - ...mockContextValue, - scopeId: null, - } as unknown as RightPanelContext; - - const { container } = render( - - - - - - ); - - expect(container).toBeEmptyDOMElement(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx index efab7fa9f6d03..ff2c314ec76f4 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx @@ -8,10 +8,10 @@ import React, { useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { INSIGHTS_ENTITIES_NO_DATA_TEST_ID, INSIGHTS_ENTITIES_TEST_ID } from './test_ids'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import { useRightPanelContext } from '../context'; -import { ENTITIES_NO_DATA_MESSAGE, ENTITIES_TITLE } from './translations'; import { getField } from '../../shared/utils'; import { HostEntityOverview } from './host_entity_overview'; import { UserEntityOverview } from './user_entity_overview'; @@ -42,15 +42,16 @@ export const EntitiesOverview: React.FC = () => { }); }, [eventId, openLeftPanel, indexName, scopeId]); - if (!eventId || !indexName || !scopeId) { - return null; - } - return ( <> + ), callback: goToEntitiesTab, iconType: 'arrowStart', }} @@ -71,7 +72,12 @@ export const EntitiesOverview: React.FC = () => { )} ) : ( -
    {ENTITIES_NO_DATA_MESSAGE}
    +

    + +

    )}
    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.test.tsx index b1d893b19a153..55a371e4a640c 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { ExpandDetailButton } from './expand_detail_button'; @@ -13,6 +14,20 @@ import { COLLAPSE_DETAILS_BUTTON_TEST_ID, EXPAND_DETAILS_BUTTON_TEST_ID } from ' import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { LeftPanelKey } from '../../left'; +const renderExpandDetailButton = ( + flyoutContextValue: ExpandableFlyoutContext, + panelContextValue: RightPanelContext +) => + render( + + + + + + + + ); + describe('', () => { it('should render expand button', () => { const flyoutContextValue = { @@ -25,15 +40,14 @@ describe('', () => { scopeId: 'scopeId', } as unknown as RightPanelContext; - const { getByTestId } = render( - - - - - + const { getByTestId, queryByTestId } = renderExpandDetailButton( + flyoutContextValue, + panelContextValue ); expect(getByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).toHaveTextContent('Expand details'); + expect(queryByTestId(COLLAPSE_DETAILS_BUTTON_TEST_ID)).not.toBeInTheDocument(); getByTestId(EXPAND_DETAILS_BUTTON_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ @@ -55,15 +69,14 @@ describe('', () => { } as unknown as ExpandableFlyoutContext; const panelContextValue = {} as unknown as RightPanelContext; - const { getByTestId } = render( - - - - - + const { getByTestId, queryByTestId } = renderExpandDetailButton( + flyoutContextValue, + panelContextValue ); expect(getByTestId(COLLAPSE_DETAILS_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(COLLAPSE_DETAILS_BUTTON_TEST_ID)).toHaveTextContent('Collapse details'); + expect(queryByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).not.toBeInTheDocument(); getByTestId(COLLAPSE_DETAILS_BUTTON_TEST_ID).click(); expect(flyoutContextValue.closeLeftPanel).toHaveBeenCalled(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.tsx index 4e02f423d1720..f066e1d411ddf 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expand_detail_button.tsx @@ -9,10 +9,10 @@ import { EuiButtonEmpty } from '@elastic/eui'; import type { FC } from 'react'; import React, { memo, useCallback } from 'react'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { COLLAPSE_DETAILS_BUTTON_TEST_ID, EXPAND_DETAILS_BUTTON_TEST_ID } from './test_ids'; import { LeftPanelKey } from '../../left'; import { useRightPanelContext } from '../context'; -import { COLLAPSE_DETAILS_BUTTON, EXPAND_DETAILS_BUTTON } from './translations'; /** * Button displayed in the top left corner of the panel, to expand the left section of the document details expandable flyout @@ -43,7 +43,10 @@ export const ExpandDetailButton: FC = memo(() => { iconType="arrowEnd" data-test-subj={COLLAPSE_DETAILS_BUTTON_TEST_ID} > - {COLLAPSE_DETAILS_BUTTON} + ) : ( { iconType="arrowStart" data-test-subj={EXPAND_DETAILS_BUTTON_TEST_ID} > - {EXPAND_DETAILS_BUTTON} + ); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.stories.tsx index 64411fbe69513..bb315fe04ac2e 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import type { Story } from '@storybook/react'; import { ExpandableSection } from './expandable_section'; -const title = 'title'; +const title =

    {'title'}

    ; const children =
    {'content'}
    ; export default { diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.test.tsx index e0d08e260f944..687fa89756c06 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.test.tsx @@ -9,40 +9,35 @@ import React from 'react'; import { render } from '@testing-library/react'; import { CONTENT_TEST_ID, ExpandableSection, HEADER_TEST_ID } from './expandable_section'; -const title = 'title'; +const title =

    {'title'}

    ; const children =
    {'content'}
    ; const testId = 'test'; const headerTestId = testId + HEADER_TEST_ID; const contentTestId = testId + CONTENT_TEST_ID; +const renderExpandableSection = (expanded: boolean) => + render( + + {children} + + ); + describe('', () => { it('should render the component collapsed', () => { - const { getByTestId } = render( - - {children} - - ); + const { getByTestId } = renderExpandableSection(false); expect(getByTestId(headerTestId)).toBeInTheDocument(); }); it('should render the component expanded', () => { - const { getByTestId } = render( - - {children} - - ); + const { getByTestId } = renderExpandableSection(true); expect(getByTestId(headerTestId)).toBeInTheDocument(); expect(getByTestId(contentTestId)).toBeInTheDocument(); }); it('should expand the component when clicking on the arrow on header', () => { - const { getByTestId } = render( - - {children} - - ); + const { getByTestId } = renderExpandableSection(false); getByTestId(headerTestId).click(); expect(getByTestId(contentTestId)).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.tsx index 0e59643ae3d27..d3c95661f7fe5 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.tsx @@ -6,6 +6,7 @@ */ import { EuiAccordion, EuiFlexGroup, EuiSpacer, EuiTitle, useGeneratedHtmlId } from '@elastic/eui'; +import type { ReactElement } from 'react'; import React, { type VFC } from 'react'; import { useAccordionState } from '../hooks/use_accordion_state'; @@ -20,7 +21,7 @@ export interface DescriptionSectionProps { /** * Title value to render in the header of the accordion */ - title: string; + title: ReactElement; /** * React component to render in the expandable section of the accordion */ diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx index 3256d3c3895cd..104d838e9499c 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx @@ -10,17 +10,17 @@ import { render } from '@testing-library/react'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { - FLYOUT_HEADER_CHAT_BUTTON_TEST_ID, - FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID, - FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID, - FLYOUT_HEADER_SHARE_BUTTON_TEST_ID, + CHAT_BUTTON_TEST_ID, + RISK_SCORE_VALUE_TEST_ID, + SEVERITY_TITLE_TEST_ID, + SHARE_BUTTON_TEST_ID, FLYOUT_HEADER_TITLE_TEST_ID, } from './test_ids'; import { HeaderTitle } from './header_title'; -import { EVENT_DETAILS } from './translations'; import moment from 'moment-timezone'; import { useDateFormat, useTimeZone } from '../../../common/lib/kibana'; -import { mockDataFormattedForFieldBrowser, mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { useAssistant } from '../hooks/use_assistant'; import { TestProvidersComponent } from '../../../common/mock'; import { useGetAlertDetailsFlyoutLink } from '../../../timelines/components/side_panel/event_details/use_get_alert_details_flyout_link'; @@ -64,8 +64,8 @@ describe('', () => { const { getByTestId } = renderHeader(mockContextValue); expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RISK_SCORE_VALUE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(SEVERITY_TITLE_TEST_ID)).toBeInTheDocument(); }); it('should render rule name in the title if document is an alert', () => { @@ -77,7 +77,7 @@ describe('', () => { it('should render share button in the title', () => { const { getByTestId } = renderHeader(mockContextValue); - expect(getByTestId(FLYOUT_HEADER_SHARE_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(SHARE_BUTTON_TEST_ID)).toBeInTheDocument(); }); it('should not render share button in the title if alert is missing url info', () => { @@ -85,13 +85,13 @@ describe('', () => { const { queryByTestId } = renderHeader(mockContextValue); - expect(queryByTestId(FLYOUT_HEADER_SHARE_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(SHARE_BUTTON_TEST_ID)).not.toBeInTheDocument(); }); it('should render chat button in the title', () => { const { getByTestId } = renderHeader(mockContextValue); - expect(getByTestId(FLYOUT_HEADER_CHAT_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CHAT_BUTTON_TEST_ID)).toBeInTheDocument(); }); it('should not render chat button in the title if should not be shown', () => { @@ -99,7 +99,7 @@ describe('', () => { const { queryByTestId } = renderHeader(mockContextValue); - expect(queryByTestId(FLYOUT_HEADER_CHAT_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CHAT_BUTTON_TEST_ID)).not.toBeInTheDocument(); }); it('should render default document detail title if document is not an alert', () => { @@ -118,6 +118,6 @@ describe('', () => { const { getByTestId } = renderHeader(contextValue); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent(EVENT_DETAILS); + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Event details'); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx index 716db70b60947..d3badd25eaa24 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx @@ -11,6 +11,7 @@ import { NewChatById } from '@kbn/elastic-assistant'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { isEmpty } from 'lodash'; import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useGetAlertDetailsFlyoutLink } from '../../../timelines/components/side_panel/event_details/use_get_alert_details_flyout_link'; import { DocumentStatus } from './status'; import { useAssistant } from '../hooks/use_assistant'; @@ -20,7 +21,6 @@ import { } from '../../../common/components/event_details/translations'; import { DocumentSeverity } from './severity'; import { RiskScore } from './risk_score'; -import { EVENT_DETAILS } from './translations'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { useRightPanelContext } from '../context'; import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; @@ -87,7 +87,14 @@ export const HeaderTitle: VFC = memo(({ flyoutIsExpandable })

    - {isAlert && !isEmpty(ruleName) ? ruleName : EVENT_DETAILS} + {isAlert && !isEmpty(ruleName) ? ( + ruleName + ) : ( + + )}

    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx index 2f21aab420af9..4f4c750c8d39c 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { HIGHLIGHTED_FIELDS_DETAILS_TEST_ID, HIGHLIGHTED_FIELDS_TITLE_TEST_ID } from './test_ids'; import { HighlightedFields } from './highlighted_fields'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { useHighlightedFields } from '../../shared/hooks/use_highlighted_fields'; import { TestProviders } from '../../../common/mock'; import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; @@ -18,13 +18,22 @@ import { useRuleWithFallback } from '../../../detection_engine/rule_management/l jest.mock('../../shared/hooks/use_highlighted_fields'); jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback'); +const renderHighlightedFields = (contextValue: RightPanelContext) => + render( + + + + + + ); + describe('', () => { beforeEach(() => { (useRuleWithFallback as jest.Mock).mockReturnValue({ investigation_fields: undefined }); }); it('should render the component', () => { - const panelContextValue = { + const contextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, scopeId: 'scopeId', } as unknown as RightPanelContext; @@ -34,50 +43,20 @@ describe('', () => { }, }); - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderHighlightedFields(contextValue); expect(getByTestId(HIGHLIGHTED_FIELDS_TITLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument(); }); it(`should render empty component if there aren't any highlighted fields`, () => { - const panelContextValue = { + const contextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, scopeId: 'scopeId', } as unknown as RightPanelContext; (useHighlightedFields as jest.Mock).mockReturnValue({}); - const { container } = render( - - - - ); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should render empty component if dataFormattedForFieldBrowser is null', () => { - const panelContextValue = { - dataFormattedForFieldBrowser: null, - scopeId: 'scopeId', - } as unknown as RightPanelContext; - (useHighlightedFields as jest.Mock).mockReturnValue({ - field: { - values: ['value'], - }, - }); - - const { container } = render( - - - - ); + const { container } = renderHighlightedFields(contextValue); expect(container).toBeEmptyDOMElement(); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx index 2a77f1a1eeddb..d47ab0c4b3c02 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx @@ -9,6 +9,7 @@ import type { FC } from 'react'; import React, { useMemo } from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiPanel, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { getSourcererScopeId } from '../../../helpers'; import { convertHighlightedFieldsToTableRow } from '../../shared/utils/highlighted_fields_helpers'; import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; @@ -20,11 +21,6 @@ import { SecurityCellActionsTrigger, } from '../../../common/components/cell_actions'; import { HIGHLIGHTED_FIELDS_DETAILS_TEST_ID, HIGHLIGHTED_FIELDS_TITLE_TEST_ID } from './test_ids'; -import { - HIGHLIGHTED_FIELDS_FIELD_COLUMN, - HIGHLIGHTED_FIELDS_TITLE, - HIGHLIGHTED_FIELDS_VALUE_COLUMN, -} from './translations'; import { useRightPanelContext } from '../context'; import { useHighlightedFields } from '../../shared/hooks/use_highlighted_fields'; @@ -52,13 +48,25 @@ export interface HighlightedFieldsTableRow { const columns: Array> = [ { field: 'field', - name: HIGHLIGHTED_FIELDS_FIELD_COLUMN, + name: ( + + ), 'data-test-subj': 'fieldCell', + width: '50%', }, { field: 'description', - name: HIGHLIGHTED_FIELDS_VALUE_COLUMN, + name: ( + + ), 'data-test-subj': 'valueCell', + width: '50%', render: (description: { field: string; values: string[] | null | undefined; @@ -98,7 +106,7 @@ export const HighlightedFields: FC = () => { [highlightedFields, scopeId] ); - if (!dataFormattedForFieldBrowser || items.length === 0) { + if (items.length === 0) { return null; } @@ -106,7 +114,12 @@ export const HighlightedFields: FC = () => { -
    {HIGHLIGHTED_FIELDS_TITLE}
    +
    + +
    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields_cell.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields_cell.test.tsx index 3f844c08a5466..2ed33df412a64 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields_cell.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields_cell.test.tsx @@ -18,6 +18,9 @@ import { RightPanelContext } from '../context'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import { TestProviders } from '../../../common/mock'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; +import { useGetEndpointDetails } from '../../../management/hooks'; + +jest.mock('../../../management/hooks'); const flyoutContextValue = { openLeftPanel: jest.fn(), @@ -72,6 +75,7 @@ describe('', () => { }); it('should render agent status cell if field is agent.status', () => { + (useGetEndpointDetails as jest.Mock).mockReturnValue({}); const { getByTestId } = render( diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx index f737cfc006419..8680120dfd251 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx @@ -18,8 +18,8 @@ import { ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID, } from './test_ids'; import { RightPanelContext } from '../context'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockContextValue } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; @@ -66,19 +66,24 @@ jest.mock('../../../explore/containers/risk_score'); const mockUseFirstLastSeen = useFirstLastSeen as jest.Mock; jest.mock('../../../common/containers/use_first_last_seen'); +const renderHostEntityContent = () => + render( + + + + + + + + ); + describe('', () => { describe('license is valid', () => { it('should render os family and host risk classification', () => { mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]); mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true }); - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderHostEntityContent(); expect(getByTestId(ENTITIES_HOST_OVERVIEW_OS_FAMILY_TEST_ID)).toHaveTextContent(osFamily); expect(getByTestId(ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID)).toHaveTextContent('Medium'); @@ -88,13 +93,8 @@ describe('', () => { mockUseHostDetails.mockReturnValue([false, { hostDetails: null }]); mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true }); - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderHostEntityContent(); + expect(getByTestId(ENTITIES_HOST_OVERVIEW_OS_FAMILY_TEST_ID)).toHaveTextContent('—'); expect(getByTestId(ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID)).toHaveTextContent('—'); }); @@ -106,13 +106,7 @@ describe('', () => { mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: false }); mockUseFirstLastSeen.mockReturnValue([false, { lastSeen }]); - const { getByTestId, queryByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderHostEntityContent(); expect(getByTestId(ENTITIES_HOST_OVERVIEW_OS_FAMILY_TEST_ID)).toHaveTextContent(osFamily); expect(getByTestId(ENTITIES_HOST_OVERVIEW_LAST_SEEN_TEST_ID)).toHaveTextContent(lastSeenText); @@ -124,13 +118,7 @@ describe('', () => { mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: false }); mockUseFirstLastSeen.mockReturnValue([false, { lastSeen: null }]); - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderHostEntityContent(); expect(getByTestId(ENTITIES_HOST_OVERVIEW_OS_FAMILY_TEST_ID)).toHaveTextContent('—'); expect(getByTestId(ENTITIES_HOST_OVERVIEW_LAST_SEEN_TEST_ID)).toHaveTextContent('—'); @@ -140,15 +128,7 @@ describe('', () => { mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]); mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true }); - const { getByTestId } = render( - - - - - - - - ); + const { getByTestId } = renderHostEntityContent(); getByTestId(ENTITIES_HOST_OVERVIEW_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx index 88804ffb97d12..60d045006ac49 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx @@ -18,6 +18,7 @@ import { import { css } from '@emotion/css'; import { getOr } from 'lodash/fp'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useRightPanelContext } from '../context'; import type { DescriptionList } from '../../../../common/utility_types'; import { @@ -44,7 +45,6 @@ import { ENTITIES_HOST_OVERVIEW_LINK_TEST_ID, TECHNICAL_PREVIEW_ICON_TEST_ID, } from './test_ids'; -import { TECHNICAL_PREVIEW_TITLE, TECHNICAL_PREVIEW_MESSAGE } from './translations'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; const HOST_ICON = 'storage'; @@ -150,10 +150,20 @@ export const HostEntityOverview: React.FC = ({ hostName <> {i18n.HOST_RISK_CLASSIFICATION} + } size="m" type="iInCircle" - content={TECHNICAL_PREVIEW_MESSAGE} + content={ + + } position="bottom" iconProps={{ className: 'eui-alignTop', diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx index 9f6c5b20b2b07..95bf7598a8197 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx @@ -10,7 +10,8 @@ import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { INSIGHTS_HEADER_TEST_ID } from './test_ids'; import { TestProviders } from '../../../common/mock'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { InsightsSection } from './insights_section'; import { useAlertPrevalence } from '../../../common/containers/alerts/use_alert_prevalence'; @@ -40,6 +41,15 @@ jest.mock('react-router-dom', () => { alertIds: [], }); +const renderInsightsSection = (contextValue: RightPanelContext, expanded: boolean) => + render( + + + + + + ); + describe('', () => { it('should render insights component', () => { const contextValue = { @@ -47,13 +57,7 @@ describe('', () => { getFieldsData: mockGetFieldsData, } as unknown as RightPanelContext; - const wrapper = render( - - - - - - ); + const wrapper = renderInsightsSection(contextValue, false); expect(wrapper.getByTestId(INSIGHTS_HEADER_TEST_ID)).toBeInTheDocument(); expect(wrapper.getAllByRole('button')[0]).toHaveAttribute('aria-expanded', 'false'); @@ -63,16 +67,11 @@ describe('', () => { it('should render insights component as expanded when expanded is true', () => { const contextValue = { eventId: 'some_Id', + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, getFieldsData: mockGetFieldsData, } as unknown as RightPanelContext; - const wrapper = render( - - - - - - ); + const wrapper = renderInsightsSection(contextValue, true); expect(wrapper.getByTestId(INSIGHTS_HEADER_TEST_ID)).toBeInTheDocument(); expect(wrapper.getAllByRole('button')[0]).toHaveAttribute('aria-expanded', 'true'); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.tsx index 86d0447a5a590..a51d5c8fec3fb 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { CorrelationsOverview } from './correlations_overview'; import { PrevalenceOverview } from './prevalence_overview'; import { ThreatIntelligenceOverview } from './threat_intelligence_overview'; import { INSIGHTS_TEST_ID } from './test_ids'; -import { INSIGHTS_TITLE } from './translations'; import { EntitiesOverview } from './entities_overview'; import { ExpandableSection } from './expandable_section'; @@ -27,7 +27,16 @@ export interface InsightsSectionProps { */ export const InsightsSection: React.FC = ({ expanded = false }) => { return ( - + + } + expanded={expanded} + data-test-subj={INSIGHTS_TEST_ID} + > diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/insights_summary_row.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/insights_summary_row.tsx index f3d8e6d25d9d5..8a981d037667f 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/insights_summary_row.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/insights_summary_row.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { VFC } from 'react'; +import type { ReactElement, VFC } from 'react'; import React from 'react'; import { css } from '@emotion/react'; import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiHealth, EuiSkeletonText } from '@elastic/eui'; @@ -31,7 +31,7 @@ export interface InsightsSummaryRowProps { /** * Text corresponding of the number of results/entries */ - text: string; + text: string | ReactElement; /** * Optional parameter for now, will be used to display a dot on the right side * (corresponding to some sort of severity?) diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx index ecac8547c6a51..0dae715262fcc 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { InvestigationGuide } from './investigation_guide'; import { RightPanelContext } from '../context'; @@ -13,21 +14,26 @@ import { INVESTIGATION_GUIDE_BUTTON_TEST_ID, INVESTIGATION_GUIDE_LOADING_TEST_ID, INVESTIGATION_GUIDE_NO_DATA_TEST_ID, + INVESTIGATION_GUIDE_TEST_ID, } from './test_ids'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; +import { mockContextValue } from '../mocks/mock_context'; import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide'; +import { LeftPanelInvestigationTab, LeftPanelKey } from '../../left'; jest.mock('../../shared/hooks/use_investigation_guide'); -const renderInvestigationGuide = (context: RightPanelContext = mockContextValue) => ( - - - - - -); +const renderInvestigationGuide = () => + render( + + + + + + + + ); describe('', () => { it('should render investigation guide button correctly', () => { @@ -37,16 +43,26 @@ describe('', () => { basicAlertData: { ruleId: 'ruleId' }, ruleNote: 'test note', }); - const { getByTestId } = render(renderInvestigationGuide()); + const { getByTestId, queryByTestId } = renderInvestigationGuide(); + expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent('Investigation guide'); expect(getByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).toHaveTextContent( + 'Show investigation guide' + ); + expect(queryByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should render loading', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: true, }); - const { getByTestId } = render(renderInvestigationGuide()); + const { getByTestId, queryByTestId } = renderInvestigationGuide(); expect(getByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(INVESTIGATION_GUIDE_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should render no data message when there is no ruleId', () => { @@ -54,8 +70,12 @@ describe('', () => { basicAlertData: {}, ruleNote: 'test note', }); - const { getByTestId } = render(renderInvestigationGuide()); + const { getByTestId, queryByTestId } = renderInvestigationGuide(); + expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument(); expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent( + 'There’s no investigation guide for this rule.' + ); }); it('should render no data message when there is no rule note', () => { @@ -63,32 +83,45 @@ describe('', () => { basicAlertData: { ruleId: 'ruleId' }, ruleNote: undefined, }); - const { getByTestId } = render(renderInvestigationGuide()); + const { getByTestId, queryByTestId } = renderInvestigationGuide(); + expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument(); expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent( + 'There’s no investigation guide for this rule.' + ); }); - it('should not render investigation guide button when dataFormattedForFieldBrowser is null', () => { + it('should render no data message when useInvestigationGuide errors out', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: false, - error: false, - show: false, + error: true, }); - const mockContext = { - ...mockContextValue, - dataFormattedForFieldBrowser: null, - }; - const { container } = render(renderInvestigationGuide(mockContext)); - expect(container).toBeEmptyDOMElement(); + + const { getByTestId } = renderInvestigationGuide(); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); }); - it('should render null when useInvestigationGuide errors out', () => { + it('should navigate to investigation guide when clicking on button', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: false, - error: true, - show: false, + error: false, + basicAlertData: { ruleId: 'ruleId' }, + ruleNote: 'test note', }); - const { container } = render(renderInvestigationGuide()); - expect(container).toBeEmptyDOMElement(); + const { getByTestId } = renderInvestigationGuide(); + getByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID).click(); + + expect(mockFlyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ + id: LeftPanelKey, + path: { + tab: LeftPanelInvestigationTab, + }, + params: { + id: mockContextValue.eventId, + indexName: mockContextValue.indexName, + scopeId: mockContextValue.scopeId, + }, + }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx index 4816577495c70..a033ee74ddabf 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx @@ -7,6 +7,7 @@ import React, { useCallback } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiTitle } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide'; import { useRightPanelContext } from '../context'; import { LeftPanelKey, LeftPanelInvestigationTab } from '../../left'; @@ -16,11 +17,6 @@ import { INVESTIGATION_GUIDE_NO_DATA_TEST_ID, INVESTIGATION_GUIDE_TEST_ID, } from './test_ids'; -import { - INVESTIGATION_GUIDE_BUTTON, - INVESTIGATION_GUIDE_NO_DATA, - INVESTIGATION_GUIDE_TITLE, -} from './translations'; /** * Render either the investigation guide button that opens Investigation section in the left panel, @@ -48,10 +44,6 @@ export const InvestigationGuide: React.FC = () => { }); }, [eventId, indexName, openLeftPanel, scopeId]); - if (!dataFormattedForFieldBrowser || error) { - return null; - } - if (loading) { return ( { -
    {INVESTIGATION_GUIDE_TITLE}
    +
    + +
    - {basicAlertData.ruleId && ruleNote ? ( + {!error && basicAlertData.ruleId && ruleNote ? ( - {INVESTIGATION_GUIDE_BUTTON} + ) : ( -
    - {INVESTIGATION_GUIDE_NO_DATA} -
    +

    + +

    )}
    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.test.tsx index 1a180c42af714..3a0b0ad4a3b3c 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { INVESTIGATION_SECTION_CONTENT_TEST_ID, @@ -15,51 +16,48 @@ import { RightPanelContext } from '../context'; import { InvestigationSection } from './investigation_section'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; -const mockUseRuleWithFallback = useRuleWithFallback as jest.Mock; jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback'); const flyoutContextValue = {} as unknown as ExpandableFlyoutContext; -const panelContextValue = {} as unknown as RightPanelContext; +const panelContextValue = { + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser.filter( + (d) => d.field !== 'kibana.alert.rule.type' + ), +} as unknown as RightPanelContext; + +const renderInvestigationSection = (expanded: boolean = false) => + render( + + + + + + + + ); describe('', () => { beforeEach(() => { jest.clearAllMocks(); - mockUseRuleWithFallback.mockReturnValue({ rule: { note: 'test note' } }); + (useRuleWithFallback as jest.Mock).mockReturnValue({ rule: { note: 'test note' } }); }); it('should render the component collapsed', () => { - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderInvestigationSection(); expect(getByTestId(INVESTIGATION_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); }); it('should render the component expanded', () => { - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderInvestigationSection(true); expect(getByTestId(INVESTIGATION_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); expect(getByTestId(INVESTIGATION_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); }); it('should expand the component when clicking on the arrow on header', () => { - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderInvestigationSection(); getByTestId(INVESTIGATION_SECTION_HEADER_TEST_ID).click(); expect(getByTestId(INVESTIGATION_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.tsx index 655aa13ea177b..2cf91392ff154 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_section.tsx @@ -8,10 +8,10 @@ import type { VFC } from 'react'; import React from 'react'; import { EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ExpandableSection } from './expandable_section'; import { HighlightedFields } from './highlighted_fields'; import { INVESTIGATION_SECTION_TEST_ID } from './test_ids'; -import { INVESTIGATION_TITLE } from './translations'; import { InvestigationGuide } from './investigation_guide'; export interface DescriptionSectionProps { /** @@ -27,7 +27,12 @@ export const InvestigationSection: VFC = ({ expanded = return ( + } data-test-subj={INVESTIGATION_SECTION_TEST_ID} > diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx index 162c4c8b5888d..2e964ef41ff28 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx @@ -7,7 +7,7 @@ import React from 'react'; import type { Story } from '@storybook/react'; -import { mockSearchHit } from '../mocks/mock_context'; +import { mockSearchHit } from '../../shared/mocks/mock_search_hit'; import { RightPanelContext } from '../context'; import { MitreAttack } from './mitre_attack'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx index 24e52e4ba5915..4768be2ad278a 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx @@ -6,39 +6,42 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { MitreAttack } from './mitre_attack'; import { RightPanelContext } from '../context'; import { MITRE_ATTACK_DETAILS_TEST_ID, MITRE_ATTACK_TITLE_TEST_ID } from './test_ids'; -import { mockSearchHit } from '../mocks/mock_context'; +import { mockSearchHit } from '../../shared/mocks/mock_search_hit'; + +const renderMitreAttack = (contextValue: RightPanelContext) => + render( + + + + ); describe('', () => { - it('should render mitre attack information', () => { + it('should render mitre attack information', async () => { const contextValue = { searchHit: mockSearchHit } as unknown as RightPanelContext; - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderMitreAttack(contextValue); - expect(getByTestId(MITRE_ATTACK_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(MITRE_ATTACK_DETAILS_TEST_ID)).toBeInTheDocument(); + await act(async () => { + expect(getByTestId(MITRE_ATTACK_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(MITRE_ATTACK_DETAILS_TEST_ID)).toBeInTheDocument(); + }); }); - it('should render empty component if missing mitre attack value', () => { + it('should render empty component if missing mitre attack value', async () => { const contextValue = { searchHit: { some_field: 'some_value', }, } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { container } = renderMitreAttack(contextValue); - expect(container).toBeEmptyDOMElement(); + await act(async () => { + expect(container).toBeEmptyDOMElement(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx index 96abc4689771e..0f4d37ab01eb4 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx @@ -9,7 +9,7 @@ import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../common/mock'; import { RightPanelContext } from '../context'; -import { INSIGHTS_PREVALENCE_TEST_ID } from './test_ids'; +import { PREVALENCE_NO_DATA_TEST_ID, PREVALENCE_TEST_ID } from './test_ids'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import React from 'react'; import { PrevalenceOverview } from './prevalence_overview'; @@ -22,28 +22,29 @@ import { EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, } from '../../shared/components/test_ids'; import { usePrevalence } from '../../shared/hooks/use_prevalence'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; +import { mockContextValue } from '../mocks/mock_context'; jest.mock('../../shared/hooks/use_prevalence'); -const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); -const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); -const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); -const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(PREVALENCE_TEST_ID); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(PREVALENCE_TEST_ID); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(PREVALENCE_TEST_ID); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(PREVALENCE_TEST_ID); const flyoutContextValue = { openLeftPanel: jest.fn(), } as unknown as ExpandableFlyoutContext; -const renderPrevalenceOverview = (contextValue: RightPanelContext = mockContextValue) => ( - - - - - - - -); +const renderPrevalenceOverview = (contextValue: RightPanelContext = mockContextValue) => + render( + + + + + + + + ); describe('', () => { it('should render wrapper component', () => { @@ -53,7 +54,7 @@ describe('', () => { data: [], }); - const { getByTestId, queryByTestId } = render(renderPrevalenceOverview()); + const { getByTestId, queryByTestId } = renderPrevalenceOverview(); expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); expect(getByTestId(TITLE_LINK_TEST_ID)).toHaveTextContent('Prevalence'); @@ -68,11 +69,10 @@ describe('', () => { data: [], }); - const { getByTestId } = render(renderPrevalenceOverview()); + const { getByTestId, queryByTestId } = renderPrevalenceOverview(); - expect( - getByTestId(EXPANDABLE_PANEL_LOADING_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID)) - ).toBeInTheDocument(); + expect(getByTestId(EXPANDABLE_PANEL_LOADING_TEST_ID(PREVALENCE_TEST_ID))).toBeInTheDocument(); + expect(queryByTestId(PREVALENCE_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should render no-data message', () => { @@ -82,9 +82,12 @@ describe('', () => { data: [], }); - const { getByTestId } = render(renderPrevalenceOverview()); + const { getByTestId } = renderPrevalenceOverview(); - expect(getByTestId(`${INSIGHTS_PREVALENCE_TEST_ID}Error`)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(PREVALENCE_NO_DATA_TEST_ID)).toHaveTextContent( + 'No prevalence data available.' + ); }); it('should render only data with prevalence less than 10%', () => { @@ -113,68 +116,22 @@ describe('', () => { ], }); - const { queryByTestId, getByTestId } = render(renderPrevalenceOverview()); + const { queryByTestId, getByTestId } = renderPrevalenceOverview(); expect(getByTestId(TITLE_LINK_TEST_ID)).toHaveTextContent('Prevalence'); - const iconDataTestSubj1 = `${INSIGHTS_PREVALENCE_TEST_ID}${field1}Icon`; - const valueDataTestSubj1 = `${INSIGHTS_PREVALENCE_TEST_ID}${field1}Value`; + const iconDataTestSubj1 = `${PREVALENCE_TEST_ID}${field1}Icon`; + const valueDataTestSubj1 = `${PREVALENCE_TEST_ID}${field1}Value`; expect(getByTestId(iconDataTestSubj1)).toBeInTheDocument(); expect(getByTestId(valueDataTestSubj1)).toBeInTheDocument(); expect(getByTestId(valueDataTestSubj1)).toHaveTextContent('field1, value1 is uncommon'); - const iconDataTestSubj2 = `${INSIGHTS_PREVALENCE_TEST_ID}${field2}Icon`; - const valueDataTestSubj2 = `${INSIGHTS_PREVALENCE_TEST_ID}${field2}Value`; + const iconDataTestSubj2 = `${PREVALENCE_TEST_ID}${field2}Icon`; + const valueDataTestSubj2 = `${PREVALENCE_TEST_ID}${field2}Value`; expect(queryByTestId(iconDataTestSubj2)).not.toBeInTheDocument(); expect(queryByTestId(valueDataTestSubj2)).not.toBeInTheDocument(); - }); - - it('should render null if eventId is null', () => { - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [], - }); - const contextValue = { - ...mockContextValue, - eventId: null, - } as unknown as RightPanelContext; - - const { container } = render(renderPrevalenceOverview(contextValue)); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should render null if browserFields is null', () => { - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [], - }); - const contextValue = { - ...mockContextValue, - browserFields: null, - }; - - const { container } = render(renderPrevalenceOverview(contextValue)); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should render null if dataFormattedForFieldBrowser is null', () => { - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [], - }); - const contextValue = { - ...mockContextValue, - dataFormattedForFieldBrowser: null, - }; - - const { container } = render(renderPrevalenceOverview(contextValue)); - expect(container).toBeEmptyDOMElement(); + expect(queryByTestId(PREVALENCE_NO_DATA_TEST_ID)).not.toBeInTheDocument(); }); it('should navigate to left section Insights tab when clicking on button', () => { @@ -193,7 +150,7 @@ describe('', () => { ], }); - const { getByTestId } = render(renderPrevalenceOverview()); + const { getByTestId } = renderPrevalenceOverview(); getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx index 309925c21d98b..5f682462d059e 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx @@ -9,11 +9,11 @@ import type { FC } from 'react'; import React, { useCallback, useMemo } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import { usePrevalence } from '../../shared/hooks/use_prevalence'; -import { INSIGHTS_PREVALENCE_TEST_ID } from './test_ids'; +import { PREVALENCE_NO_DATA_TEST_ID, PREVALENCE_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; -import { PREVALENCE_NO_DATA, PREVALENCE_ROW_UNCOMMON, PREVALENCE_TITLE } from './translations'; import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { PREVALENCE_TAB_ID } from '../../left/components/prevalence_details'; import { InsightsSummaryRow } from './insights_summary_row'; @@ -27,14 +27,8 @@ const DEFAULT_TO = 'now'; * The component fetches the necessary data at once. The loading and error states are handled by the ExpandablePanel component. */ export const PrevalenceOverview: FC = () => { - const { - eventId, - indexName, - browserFields, - dataFormattedForFieldBrowser, - scopeId, - investigationFields, - } = useRightPanelContext(); + const { eventId, indexName, dataFormattedForFieldBrowser, scopeId, investigationFields } = + useRightPanelContext(); const { openLeftPanel } = useExpandableFlyoutContext(); const goToCorrelationsTab = useCallback(() => { @@ -73,31 +67,44 @@ export const PrevalenceOverview: FC = () => { [data] ); - if (!eventId || !browserFields || !dataFormattedForFieldBrowser) { - return null; - } - return ( + ), callback: goToCorrelationsTab, iconType: 'arrowStart', }} content={{ loading, error }} - data-test-subj={INSIGHTS_PREVALENCE_TEST_ID} + data-test-subj={PREVALENCE_TEST_ID} > {uncommonData.length > 0 ? ( uncommonData.map((d) => ( + } + data-test-subj={`${PREVALENCE_TEST_ID}${d.field}`} + key={`${PREVALENCE_TEST_ID}${d.field}`} /> )) ) : ( -
    {PREVALENCE_NO_DATA}
    +

    + +

    )}
    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx index 3ec854cfbd815..200f4cf0536a0 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { FormattedMessage, __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, REASON_DETAILS_TEST_ID, @@ -14,10 +15,10 @@ import { } from './test_ids'; import { Reason } from './reason'; import { RightPanelContext } from '../context'; -import { mockDataFormattedForFieldBrowser, mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { PreviewPanelKey } from '../../preview'; -import { PREVIEW_ALERT_REASON_DETAILS } from './translations'; const flyoutContextValue = { openPreviewPanel: jest.fn(), @@ -33,28 +34,37 @@ const panelContextValue = { const renderReason = (panelContext: RightPanelContext = panelContextValue) => render( - - - - - + + + + + + + ); describe('', () => { - it('should render the component', () => { + it('should render the component for alert', () => { const { getByTestId } = renderReason(); expect(getByTestId(REASON_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(REASON_TITLE_TEST_ID)).toHaveTextContent('Alert reason'); + expect(getByTestId(REASON_DETAILS_PREVIEW_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(REASON_DETAILS_PREVIEW_BUTTON_TEST_ID)).toHaveTextContent( + 'Show full reason' + ); }); - it('should render null if dataFormattedForFieldBrowser is null', () => { + it('should render the component for document', () => { + const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser.filter( + (d) => d.field !== 'kibana.alert.rule.uuid' + ); const panelContext = { ...panelContextValue, - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { container } = renderReason(panelContext); - - expect(container).toBeEmptyDOMElement(); + dataFormattedForFieldBrowser, + }; + const { getByTestId } = renderReason(panelContext); + expect(getByTestId(REASON_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(REASON_TITLE_TEST_ID)).toHaveTextContent('Document reason'); }); it('should render no reason if the field is null', () => { @@ -81,7 +91,12 @@ describe('', () => { indexName: panelContextValue.indexName, scopeId: panelContextValue.scopeId, banner: { - title: PREVIEW_ALERT_REASON_DETAILS, + title: ( + + ), backgroundColor: 'warning', textColor: 'warning', }, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx index b356809917973..a245b0dbfa6e2 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx @@ -10,6 +10,7 @@ import React, { useCallback, useMemo } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { ALERT_REASON } from '@kbn/rule-data-utils'; +import { FormattedMessage } from '@kbn/i18n-react'; import { getField } from '../../shared/utils'; import { AlertReasonPreviewPanel, PreviewPanelKey } from '../../preview'; import { @@ -17,12 +18,6 @@ import { REASON_DETAILS_TEST_ID, REASON_TITLE_TEST_ID, } from './test_ids'; -import { - ALERT_REASON_DETAILS_TEXT, - ALERT_REASON_TITLE, - DOCUMENT_REASON_TITLE, - PREVIEW_ALERT_REASON_DETAILS, -} from './translations'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { useRightPanelContext } from '../context'; @@ -45,7 +40,12 @@ export const Reason: FC = () => { indexName, scopeId, banner: { - title: PREVIEW_ALERT_REASON_DETAILS, + title: ( + + ), backgroundColor: 'warning', textColor: 'warning', }, @@ -63,17 +63,16 @@ export const Reason: FC = () => { iconSide="right" data-test-subj={REASON_DETAILS_PREVIEW_BUTTON_TEST_ID} > - {ALERT_REASON_DETAILS_TEXT} +
    ), [openRulePreview] ); - if (!dataFormattedForFieldBrowser) { - return null; - } - return ( @@ -82,12 +81,22 @@ export const Reason: FC = () => { {isAlert ? ( -
    {ALERT_REASON_TITLE}
    +
    + +
    {viewPreview}
    ) : ( - DOCUMENT_REASON_TITLE +

    + +

    )} diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.test.tsx index 9f6a4ec83a2c2..5aad641c6e400 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.test.tsx @@ -6,12 +6,13 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { SUMMARY_ROW_ICON_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID, SUMMARY_ROW_LOADING_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, } from './test_ids'; import { RelatedAlertsByAncestry } from './related_alerts_by_ancestry'; import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; @@ -22,16 +23,19 @@ const documentId = 'documentId'; const indices = ['indices']; const scopeId = 'scopeId'; -const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID -); -const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID -); +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID); const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID + CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID ); +const renderRelatedAlertsByAncestry = () => + render( + + + + ); + describe('', () => { it('should render many related alerts correctly', () => { (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ @@ -40,9 +44,7 @@ describe('', () => { dataCount: 2, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsByAncestry(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -57,9 +59,7 @@ describe('', () => { dataCount: 1, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsByAncestry(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -72,9 +72,7 @@ describe('', () => { loading: true, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsByAncestry(); expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); }); @@ -84,9 +82,7 @@ describe('', () => { error: true, }); - const { container } = render( - - ); + const { container } = renderRelatedAlertsByAncestry(); expect(container).toBeEmptyDOMElement(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.tsx index 0a48443de7320..1c41fbe0969b3 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; -import { CORRELATIONS_ANCESTRY_ALERTS } from '../../shared/translations'; import { InsightsSummaryRow } from './insights_summary_row'; -import { INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID } from './test_ids'; +import { CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID } from './test_ids'; const ICON = 'warning'; @@ -41,7 +41,13 @@ export const RelatedAlertsByAncestry: React.VFC = indices, scopeId, }); - const text = CORRELATIONS_ANCESTRY_ALERTS(dataCount); + const text = ( + + ); return ( = icon={ICON} value={dataCount} text={text} - data-test-subj={INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID} + data-test-subj={CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID} key={`correlation-row-${text}`} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.test.tsx index afcbaeb8c5c88..59dfb183a1338 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.test.tsx @@ -6,12 +6,13 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { SUMMARY_ROW_ICON_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID, SUMMARY_ROW_LOADING_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, } from './test_ids'; import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event'; @@ -22,15 +23,22 @@ const originalEventId = 'originalEventId'; const scopeId = 'scopeId'; const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID + CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID ); const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID + CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID ); const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID + CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID ); +const renderRelatedAlertsBySameSourceEvent = () => + render( + + + + ); + describe('', () => { it('should render many related alerts correctly', () => { (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ @@ -39,9 +47,7 @@ describe('', () => { dataCount: 2, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsBySameSourceEvent(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -56,9 +62,7 @@ describe('', () => { dataCount: 1, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsBySameSourceEvent(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -71,9 +75,7 @@ describe('', () => { loading: true, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsBySameSourceEvent(); expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); }); @@ -83,9 +85,7 @@ describe('', () => { error: true, }); - const { container } = render( - - ); + const { container } = renderRelatedAlertsBySameSourceEvent(); expect(container).toBeEmptyDOMElement(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.tsx index 1d5df7a326c44..e309a2e4bc93a 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; -import { CORRELATIONS_SAME_SOURCE_ALERTS } from '../../shared/translations'; import { InsightsSummaryRow } from './insights_summary_row'; -import { INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID } from './test_ids'; +import { CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID } from './test_ids'; const ICON = 'warning'; @@ -35,7 +35,13 @@ export const RelatedAlertsBySameSourceEvent: React.VFC + ); return ( ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.test.tsx index 133f00e44b26b..96ab397229420 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.test.tsx @@ -6,12 +6,13 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { SUMMARY_ROW_ICON_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID, SUMMARY_ROW_LOADING_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, } from './test_ids'; import { RelatedAlertsBySession } from './related_alerts_by_session'; import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; @@ -21,15 +22,16 @@ jest.mock('../../shared/hooks/use_fetch_related_alerts_by_session'); const entityId = 'entityId'; const scopeId = 'scopeId'; -const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID -); -const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID -); -const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID( - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID -); +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID); +const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID); + +const renderRelatedAlertsBySession = () => + render( + + + + ); describe('', () => { it('should render many related alerts correctly', () => { @@ -39,9 +41,7 @@ describe('', () => { dataCount: 2, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsBySession(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -56,9 +56,7 @@ describe('', () => { dataCount: 1, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsBySession(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -71,9 +69,7 @@ describe('', () => { loading: true, }); - const { getByTestId } = render( - - ); + const { getByTestId } = renderRelatedAlertsBySession(); expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); }); @@ -83,7 +79,7 @@ describe('', () => { error: true, }); - const { container } = render(); + const { container } = renderRelatedAlertsBySession(); expect(container).toBeEmptyDOMElement(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.tsx index 2a015c0c880d9..4b41389137fad 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; -import { CORRELATIONS_SESSION_ALERTS } from '../../shared/translations'; import { InsightsSummaryRow } from './insights_summary_row'; -import { INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID } from './test_ids'; +import { CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID } from './test_ids'; const ICON = 'warning'; @@ -35,7 +35,13 @@ export const RelatedAlertsBySession: React.VFC = ({ entityId, scopeId, }); - const text = CORRELATIONS_SESSION_ALERTS(dataCount); + const text = ( + + ); return ( = ({ icon={ICON} value={dataCount} text={text} - data-test-subj={INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID} + data-test-subj={CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID} key={`correlation-row-${text}`} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.test.tsx index 7d0d1a88fb2ec..3d20e6399af38 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.test.tsx @@ -6,9 +6,10 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { - INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID, + CORRELATIONS_RELATED_CASES_TEST_ID, SUMMARY_ROW_ICON_TEST_ID, SUMMARY_ROW_LOADING_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID, @@ -20,9 +21,16 @@ jest.mock('../../shared/hooks/use_fetch_related_cases'); const eventId = 'eventId'; -const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID); -const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID); -const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID); +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID); +const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID); + +const renderRelatedCases = () => + render( + + + + ); describe('', () => { it('should render many related cases correctly', () => { @@ -32,7 +40,7 @@ describe('', () => { dataCount: 2, }); - const { getByTestId } = render(); + const { getByTestId } = renderRelatedCases(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -47,7 +55,7 @@ describe('', () => { dataCount: 1, }); - const { getByTestId } = render(); + const { getByTestId } = renderRelatedCases(); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); @@ -60,7 +68,7 @@ describe('', () => { loading: true, }); - const { getByTestId } = render(); + const { getByTestId } = renderRelatedCases(); expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); }); @@ -70,7 +78,7 @@ describe('', () => { error: true, }); - const { container } = render(); + const { container } = renderRelatedCases(); expect(container).toBeEmptyDOMElement(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.tsx index d20e4fc09d56b..d45cc971dc046 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; -import { CORRELATIONS_RELATED_CASES } from '../../shared/translations'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; import { InsightsSummaryRow } from './insights_summary_row'; -import { INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID } from './test_ids'; +import { CORRELATIONS_RELATED_CASES_TEST_ID } from './test_ids'; const ICON = 'warning'; @@ -25,7 +25,13 @@ export interface RelatedCasesProps { */ export const RelatedCases: React.VFC = ({ eventId }) => { const { loading, error, dataCount } = useFetchRelatedCases({ eventId }); - const text = CORRELATIONS_RELATED_CASES(dataCount); + const text = ( + + ); return ( = ({ eventId }) => { icon={ICON} value={dataCount} text={text} - data-test-subj={INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID} + data-test-subj={CORRELATIONS_RELATED_CASES_TEST_ID} key={`correlation-row-${text}`} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/response_button.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/response_button.test.tsx index 3cbed8191f342..489c1e54a60cc 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/response_button.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/response_button.test.tsx @@ -6,10 +6,11 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { RESPONSE_BUTTON_TEST_ID, RESPONSE_EMPTY_TEST_ID } from './test_ids'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; +import { mockContextValue } from '../mocks/mock_context'; import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { ResponseButton } from './response_button'; @@ -34,40 +35,32 @@ const mockInvalidSearchHit = { fields: {}, } as unknown as SearchHit; -describe('', () => { - it('should render response button correctly', () => { - const { getByTestId } = render( +const renderResponseButton = (panelContextValue: RightPanelContext = mockContextValue) => + render( + - + - ); + + ); +describe('', () => { + it('should render response button correctly', () => { + const panelContextValue = { ...mockContextValue, searchHit: mockValidSearchHit }; + const { getByTestId, queryByTestId } = renderResponseButton(panelContextValue); expect(getByTestId(RESPONSE_BUTTON_TEST_ID)).toBeInTheDocument(); - }); - - it('should not render response button when searchHit is undefined', () => { - const { getByTestId } = render( - - - - - - ); - - expect(getByTestId(RESPONSE_EMPTY_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RESPONSE_BUTTON_TEST_ID)).toHaveTextContent('Response'); + expect(queryByTestId(RESPONSE_EMPTY_TEST_ID)).not.toBeInTheDocument(); }); it(`should not render investigation guide button when searchHit doesn't have correct data`, () => { - const { getByTestId } = render( - - - - - - ); + const panelContextValue = { ...mockContextValue, searchHit: mockInvalidSearchHit }; + const { getByTestId, queryByTestId } = renderResponseButton(panelContextValue); expect(getByTestId(RESPONSE_EMPTY_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RESPONSE_EMPTY_TEST_ID)).toHaveTextContent( + 'There are no response actions defined for this event.' + ); + expect(queryByTestId(RESPONSE_BUTTON_TEST_ID)).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/response_button.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/response_button.tsx index b1e4e94296a46..fe6a9dd20d59a 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/response_button.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/response_button.tsx @@ -7,6 +7,7 @@ import React, { useCallback } from 'react'; import { EuiButton } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { expandDottedObject } from '../../../../common/utils/expand_dotted'; import type { ExpandedEventFieldsObject, @@ -15,7 +16,6 @@ import type { import { useRightPanelContext } from '../context'; import { LeftPanelKey, LeftPanelResponseTab } from '../../left'; import { RESPONSE_BUTTON_TEST_ID, RESPONSE_EMPTY_TEST_ID } from './test_ids'; -import { RESPONSE_EMPTY, RESPONSE_TITLE } from './translations'; /** * Response button that opens Response section in the left panel @@ -45,14 +45,22 @@ export const ResponseButton: React.FC = () => { return ( <> {!responseActions ? ( -
    {RESPONSE_EMPTY}
    +

    + +

    ) : ( - {RESPONSE_TITLE} + )} diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/response_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/response_section.test.tsx index d15ad34790ea6..ebff6c0e704fe 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/response_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/response_section.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { RESPONSE_SECTION_CONTENT_TEST_ID, RESPONSE_SECTION_HEADER_TEST_ID } from './test_ids'; import { RightPanelContext } from '../context'; @@ -15,40 +16,33 @@ import { ResponseSection } from './response_section'; const flyoutContextValue = {} as unknown as ExpandableFlyoutContext; const panelContextValue = {} as unknown as RightPanelContext; -describe('', () => { - it('should render the component collapsed', () => { - const { getByTestId } = render( +const renderResponseSection = () => + render( + - ); + + ); + +describe('', () => { + it('should render the component collapsed', () => { + const { getByTestId } = renderResponseSection(); expect(getByTestId(RESPONSE_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); }); it('should render the component expanded', () => { - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderResponseSection(); expect(getByTestId(RESPONSE_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RESPONSE_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); }); it('should expand the component when clicking on the arrow on header', () => { - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderResponseSection(); getByTestId(RESPONSE_SECTION_HEADER_TEST_ID).click(); expect(getByTestId(RESPONSE_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/response_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/response_section.tsx index 18480dde09dde..96a0b070020e2 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/response_section.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/response_section.tsx @@ -7,10 +7,10 @@ import type { VFC } from 'react'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ResponseButton } from './response_button'; import { ExpandableSection } from './expandable_section'; import { RESPONSE_SECTION_TEST_ID } from './test_ids'; -import { RESPONSE_TITLE } from './translations'; export interface ResponseSectionProps { /** * Boolean to allow the component to be expanded or collapsed on first render @@ -25,7 +25,12 @@ export const ResponseSection: VFC = ({ expanded = false }) return ( + } data-test-subj={RESPONSE_SECTION_TEST_ID} > diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.stories.tsx index a50f7d8dff7fe..fe49580d4c7e1 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.stories.tsx @@ -7,7 +7,7 @@ import React from 'react'; import type { Story } from '@storybook/react'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { RiskScore } from './risk_score'; import { RightPanelContext } from '../context'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.test.tsx index 554b6c90db32a..d0b006794ac32 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.test.tsx @@ -6,14 +6,21 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; -import { - FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID, - FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID, -} from './test_ids'; +import { RISK_SCORE_TITLE_TEST_ID, RISK_SCORE_VALUE_TEST_ID } from './test_ids'; import { RiskScore } from './risk_score'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; + +const renderRiskScore = (contextValue: RightPanelContext) => + render( + + + + + + ); describe('', () => { it('should render risk score information', () => { @@ -21,14 +28,10 @@ describe('', () => { getFieldsData: jest.fn().mockImplementation(mockGetFieldsData), } as unknown as RightPanelContext; - const { getByTestId } = render( - - - - ); + const { getByTestId } = renderRiskScore(contextValue); - expect(getByTestId(FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID)).toBeInTheDocument(); - const riskScore = getByTestId(FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID); + expect(getByTestId(RISK_SCORE_TITLE_TEST_ID)).toBeInTheDocument(); + const riskScore = getByTestId(RISK_SCORE_VALUE_TEST_ID); expect(riskScore).toBeInTheDocument(); expect(riskScore).toHaveTextContent('0'); }); @@ -38,11 +41,7 @@ describe('', () => { getFieldsData: jest.fn(), } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { container } = renderRiskScore(contextValue); expect(container).toBeEmptyDOMElement(); }); @@ -52,11 +51,7 @@ describe('', () => { getFieldsData: jest.fn().mockImplementation(() => 123), } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { container } = renderRiskScore(contextValue); expect(container).toBeEmptyDOMElement(); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.tsx index b14aa88f91d0e..15159e13cd2e6 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/risk_score.tsx @@ -9,11 +9,8 @@ import type { FC } from 'react'; import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { ALERT_RISK_SCORE } from '@kbn/rule-data-utils'; -import { - FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID, - FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID, -} from './test_ids'; -import { RISK_SCORE_TITLE } from './translations'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { RISK_SCORE_TITLE_TEST_ID, RISK_SCORE_VALUE_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; /** @@ -39,12 +36,17 @@ export const RiskScore: FC = memo(() => { return ( - -
    {`${RISK_SCORE_TITLE}:`}
    + +
    + +
    - {alertRiskScore} + {alertRiskScore}
    ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.tsx index 9ad7e2bd05d75..1bfca23f84ffa 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.tsx @@ -6,24 +6,20 @@ */ import { EuiCode, EuiIcon, useEuiTheme } from '@elastic/eui'; +import type { ReactElement } from 'react'; import React, { useMemo, type FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { SESSION_PREVIEW_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; import { SIGNAL_RULE_NAME_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants'; import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; import { useProcessData } from '../hooks/use_process_data'; -import { - SESSION_PREVIEW_COMMAND_TEXT, - SESSION_PREVIEW_PROCESS_TEXT, - SESSION_PREVIEW_RULE_TEXT, - SESSION_PREVIEW_TIME_TEXT, -} from './translations'; import { RenderRuleName } from '../../../timelines/components/timeline/body/renderers/formatted_field_helpers'; /** * One-off helper to make sure that inline values are rendered consistently */ -const ValueContainer: FC<{ text?: string }> = ({ text, children }) => ( +const ValueContainer: FC<{ text?: ReactElement }> = ({ text, children }) => ( <> {text && ( <> @@ -53,7 +49,14 @@ export const SessionPreview: FC = () => { const processNameFragment = useMemo(() => { return ( processName && ( - + + } + > {processName} ) @@ -63,7 +66,14 @@ export const SessionPreview: FC = () => { const timeFragment = useMemo(() => { return ( startAt && ( - + + } + > ) @@ -74,7 +84,14 @@ export const SessionPreview: FC = () => { return ( ruleName && ruleId && ( - + + } + > { const commandFragment = useMemo(() => { return ( command && ( - + + } + > {workdir} {command} diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx index 8db5d4a9cacf5..dc572207cd602 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx @@ -12,7 +12,11 @@ import { RightPanelContext } from '../context'; import { SessionPreviewContainer } from './session_preview_container'; import { useSessionPreview } from '../hooks/use_session_preview'; import { useLicense } from '../../../common/hooks/use_license'; -import { SESSION_PREVIEW_TEST_ID } from './test_ids'; +import { + SESSION_PREVIEW_NO_DATA_TEST_ID, + SESSION_PREVIEW_TEST_ID, + SESSION_PREVIEW_UPSELL_TEST_ID, +} from './test_ids'; import { EXPANDABLE_PANEL_CONTENT_TEST_ID, EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, @@ -20,13 +24,12 @@ import { EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, } from '../../shared/components/test_ids'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; jest.mock('../hooks/use_session_preview'); jest.mock('../../../common/hooks/use_license'); const panelContextValue = { - dataAsNestedObject: null, getFieldsData: mockGetFieldsData, } as unknown as RightPanelContext; @@ -36,10 +39,6 @@ const sessionViewConfig = { sessionStartTime: 'sessionStartTime', }; -const TEST_ID = SESSION_PREVIEW_TEST_ID; -const ERROR_TEST_ID = `${SESSION_PREVIEW_TEST_ID}Error`; -const UPSELL_TEST_ID = `${SESSION_PREVIEW_TEST_ID}UpSell`; - const renderSessionPreview = () => render( @@ -60,9 +59,9 @@ describe('SessionPreviewContainer', () => { const { getByTestId, queryByTestId } = renderSessionPreview(); - expect(getByTestId(TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(ERROR_TEST_ID)).not.toBeInTheDocument(); - expect(queryByTestId(UPSELL_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(SESSION_PREVIEW_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).not.toBeInTheDocument(); expect( getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(SESSION_PREVIEW_TEST_ID)) ).toBeInTheDocument(); @@ -86,9 +85,12 @@ describe('SessionPreviewContainer', () => { const { getByTestId, queryByTestId } = renderSessionPreview(); - expect(queryByTestId(TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(ERROR_TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(UPSELL_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(SESSION_PREVIEW_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).toHaveTextContent( + 'You can only view Linux session details if you’ve enabled the Include session data setting in your Elastic Defend integration policy. Refer to Enable Session View dataExternal link(opens in a new tab or window) for more information.' + ); + expect(queryByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).not.toBeInTheDocument(); expect( getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID)) ).toBeInTheDocument(); @@ -100,9 +102,12 @@ describe('SessionPreviewContainer', () => { const { getByTestId, queryByTestId } = renderSessionPreview(); - expect(queryByTestId(TEST_ID)).not.toBeInTheDocument(); - expect(queryByTestId(ERROR_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(UPSELL_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(SESSION_PREVIEW_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).toHaveTextContent( + 'This feature requires an Enterprise subscription' + ); expect( getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID)) ).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx index 49a90921e2998..e3fe9a191ebcb 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx @@ -18,8 +18,11 @@ import { useInvestigateInTimeline } from '../../../detections/components/alerts_ import { useRightPanelContext } from '../context'; import { ALERTS_ACTIONS } from '../../../common/lib/apm/user_actions'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; -import { SESSION_PREVIEW_TEST_ID } from './test_ids'; -import { SESSION_PREVIEW_TITLE } from './translations'; +import { + SESSION_PREVIEW_NO_DATA_TEST_ID, + SESSION_PREVIEW_TEST_ID, + SESSION_PREVIEW_UPSELL_TEST_ID, +} from './test_ids'; import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction'; import { setActiveTabTimeline } from '../../../timelines/store/timeline/actions'; import { getScopedActions } from '../../../helpers'; @@ -67,15 +70,15 @@ export const SessionPreviewContainer: FC = () => { const { euiTheme } = useEuiTheme(); const noSessionMessage = !isEnterprisePlus ? ( -
    +
    @@ -84,9 +87,9 @@ export const SessionPreviewContainer: FC = () => { />
    ) : !sessionViewConfig ? ( -
    +
    { `} > @@ -107,7 +110,7 @@ export const SessionPreviewContainer: FC = () => { target="_blank" > @@ -120,7 +123,12 @@ export const SessionPreviewContainer: FC = () => { return ( + ), iconType: 'timeline', ...(isEnabled && { callback: goToSessionViewTab }), }} diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/severity.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/severity.stories.tsx index ced1cbadec4ae..3c277540596f7 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/severity.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/severity.stories.tsx @@ -7,7 +7,7 @@ import React from 'react'; import type { Story } from '@storybook/react'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { DocumentSeverity } from './severity'; import { RightPanelContext } from '../context'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/severity.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/severity.test.tsx index d599236b4531d..685bcc8cc4e0e 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/severity.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/severity.test.tsx @@ -8,14 +8,20 @@ import React from 'react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; -import { - FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID, - FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID, -} from './test_ids'; +import { SEVERITY_TITLE_TEST_ID, SEVERITY_VALUE_TEST_ID } from './test_ids'; import { DocumentSeverity } from './severity'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { TestProviders } from '../../../common/mock'; +const renderDocumentSeverity = (contextValue: RightPanelContext) => + render( + + + + + + ); + describe('', () => { it('should render severity information', () => { const contextValue = { @@ -23,16 +29,10 @@ describe('', () => { scopeId: 'scopeId', } as unknown as RightPanelContext; - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderDocumentSeverity(contextValue); - expect(getByTestId(FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID)).toBeInTheDocument(); - const severity = getByTestId(FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID); + expect(getByTestId(SEVERITY_TITLE_TEST_ID)).toBeInTheDocument(); + const severity = getByTestId(SEVERITY_VALUE_TEST_ID); expect(severity).toBeInTheDocument(); expect(severity).toHaveTextContent('Low'); }); @@ -43,11 +43,7 @@ describe('', () => { scopeId: 'scopeId', } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { container } = renderDocumentSeverity(contextValue); expect(container).toBeEmptyDOMElement(); }); @@ -58,11 +54,7 @@ describe('', () => { scopeId: 'scopeId', } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { container } = renderDocumentSeverity(contextValue); expect(container).toBeEmptyDOMElement(); }); @@ -73,11 +65,7 @@ describe('', () => { scopeId: 'scopeId', } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { container } = renderDocumentSeverity(contextValue); expect(container).toBeEmptyDOMElement(); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/severity.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/severity.tsx index c5f49400f088c..81896bf770636 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/severity.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/severity.tsx @@ -11,13 +11,13 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { ALERT_SEVERITY } from '@kbn/rule-data-utils'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { CellActionsMode } from '@kbn/cell-actions'; +import { FormattedMessage } from '@kbn/i18n-react'; import { getSourcererScopeId } from '../../../helpers'; import { SecurityCellActions } from '../../../common/components/cell_actions'; import { SecurityCellActionsTrigger } from '../../../actions/constants'; -import { SEVERITY_TITLE } from './translations'; import { useRightPanelContext } from '../context'; import { SeverityBadge } from '../../../detections/components/rules/severity_badge'; -import { FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID } from './test_ids'; +import { SEVERITY_TITLE_TEST_ID } from './test_ids'; const isSeverity = (x: unknown): x is Severity => x === 'low' || x === 'medium' || x === 'high' || x === 'critical'; @@ -45,8 +45,13 @@ export const DocumentSeverity: FC = memo(() => { return ( - -
    {`${SEVERITY_TITLE}:`}
    + +
    + +
    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/share_button.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/share_button.test.tsx index 00b757541d587..dccea9feb0d84 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/share_button.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/share_button.test.tsx @@ -5,11 +5,12 @@ * 2.0. */ +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render, screen, fireEvent } from '@testing-library/react'; import { copyToClipboard } from '@elastic/eui'; import { ShareButton } from './share_button'; import React from 'react'; -import { FLYOUT_HEADER_SHARE_BUTTON_TEST_ID } from './test_ids'; +import { SHARE_BUTTON_TEST_ID } from './test_ids'; import { FLYOUT_URL_PARAM } from '../../shared/hooks/url/use_sync_flyout_state_with_url'; jest.mock('@elastic/eui', () => ({ @@ -18,17 +19,24 @@ jest.mock('@elastic/eui', () => ({ EuiCopy: jest.fn(({ children: functionAsChild }) => functionAsChild(jest.fn())), })); -describe('ShareButton', () => { - const alertUrl = 'https://example.com/alert'; +const alertUrl = 'https://example.com/alert'; + +const renderShareButton = () => + render( + + + + ); +describe('ShareButton', () => { beforeEach(() => { jest.clearAllMocks(); }); it('renders the share button', () => { - render(); + renderShareButton(); - expect(screen.getByTestId(FLYOUT_HEADER_SHARE_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(screen.getByTestId(SHARE_BUTTON_TEST_ID)).toBeInTheDocument(); }); it('copies the alert URL to clipboard', () => { @@ -41,9 +49,9 @@ describe('ShareButton', () => { }, }); - render(); + renderShareButton(); - fireEvent.click(screen.getByTestId(FLYOUT_HEADER_SHARE_BUTTON_TEST_ID)); + fireEvent.click(screen.getByTestId(SHARE_BUTTON_TEST_ID)); expect(copyToClipboard).toHaveBeenCalledWith( `${alertUrl}&${FLYOUT_URL_PARAM}=${syncedFlyoutState}` diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/share_button.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/share_button.tsx index 04405e9b7a31a..e9637e61205da 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/share_button.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/share_button.tsx @@ -8,9 +8,9 @@ import { copyToClipboard, EuiButtonEmpty, EuiCopy } from '@elastic/eui'; import type { FC } from 'react'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { FLYOUT_URL_PARAM } from '../../shared/hooks/url/use_sync_flyout_state_with_url'; -import { FLYOUT_HEADER_SHARE_BUTTON_TEST_ID } from './test_ids'; -import { SHARE } from './translations'; +import { SHARE_BUTTON_TEST_ID } from './test_ids'; interface ShareButtonProps { /** @@ -39,9 +39,12 @@ export const ShareButton: FC = ({ alertUrl }) => { copyToClipboard(alertDetailsLink); }} iconType="share" - data-test-subj={FLYOUT_HEADER_SHARE_BUTTON_TEST_ID} + data-test-subj={SHARE_BUTTON_TEST_ID} > - {SHARE} + )} diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/status.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/status.stories.tsx index 5de5bd7cd3625..12b25774c7196 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/status.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/status.stories.tsx @@ -8,10 +8,11 @@ import React from 'react'; import type { Story } from '@storybook/react'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { mockBrowserFields } from '../../shared/mocks/mock_browser_fields'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { StorybookProviders } from '../../../common/mock/storybook_providers'; import { DocumentStatus } from './status'; import { RightPanelContext } from '../context'; -import { mockBrowserFields, mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; export default { component: DocumentStatus, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/status.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/status.test.tsx index 379181d9691e7..9cb2f871015e6 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/status.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/status.test.tsx @@ -10,10 +10,10 @@ import { render } from '@testing-library/react'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { DocumentStatus } from './status'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { TestProviders } from '../../../common/mock'; import { useAlertsActions } from '../../../detections/components/alerts_table/timeline_actions/use_alerts_actions'; -import { FLYOUT_HEADER_STATUS_BUTTON_TEST_ID } from './test_ids'; +import { STATUS_BUTTON_TEST_ID } from './test_ids'; jest.mock('../../../detections/components/alerts_table/timeline_actions/use_alerts_actions'); @@ -53,10 +53,10 @@ describe('', () => { const { getByTestId, getByText } = renderStatus(contextValue); - expect(getByTestId(FLYOUT_HEADER_STATUS_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(STATUS_BUTTON_TEST_ID)).toBeInTheDocument(); expect(getByText('open')).toBeInTheDocument(); - getByTestId(FLYOUT_HEADER_STATUS_BUTTON_TEST_ID).click(); + getByTestId(STATUS_BUTTON_TEST_ID).click(); expect(getByTestId('data-test-subj')).toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx index fa3a0c6f2ab14..359b889398261 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx @@ -47,7 +47,7 @@ export const DocumentStatus: FC = () => { eventId, contextId: scopeId, scopeId, - browserFields: browserFields || {}, + browserFields, item, }) ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.test.tsx index 585e8f02ace03..73e9441c0641b 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.test.tsx @@ -6,49 +6,63 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { SUMMARY_ROW_ICON_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID, - INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, - SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID, + CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, + CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID, } from './test_ids'; import { SuppressedAlerts } from './suppressed_alerts'; -const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID); -const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID); +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID); + +const renderSuppressedAlerts = (alertSuppressionCount: number) => + render( + + + + ); describe('', () => { it('should render zero suppressed alert correctly', () => { - const { getByTestId } = render(); + const { getByTestId } = renderSuppressedAlerts(0); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); expect(value).toHaveTextContent('0 suppressed alert'); expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument(); + expect( + getByTestId(CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID) + ).toBeInTheDocument(); }); it('should render single suppressed alert correctly', () => { - const { getByTestId } = render(); + const { getByTestId } = renderSuppressedAlerts(1); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); expect(value).toHaveTextContent('1 suppressed alert'); expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument(); + expect( + getByTestId(CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID) + ).toBeInTheDocument(); }); it('should render multiple suppressed alerts row correctly', () => { - const { getByTestId } = render(); + const { getByTestId } = renderSuppressedAlerts(2); expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); const value = getByTestId(VALUE_TEST_ID); expect(value).toBeInTheDocument(); expect(value).toHaveTextContent('2 suppressed alerts'); expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument(); + expect( + getByTestId(CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID) + ).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.tsx index 4732d43cadff9..11c67e60d0fab 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/suppressed_alerts.tsx @@ -7,14 +7,13 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiBetaBadge } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { - INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, - SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID, + CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, + CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID, } from './test_ids'; -import { CORRELATIONS_SUPPRESSED_ALERTS } from '../../shared/translations'; import { InsightsSummaryRow } from './insights_summary_row'; import { SUPPRESSED_ALERTS_COUNT_TECHNICAL_PREVIEW } from '../../../common/components/event_details/insights/translations'; -import { TECHNICAL_PREVIEW_MESSAGE } from './translations'; export interface SuppressedAlertsProps { /** @@ -35,8 +34,14 @@ export const SuppressedAlerts: React.VFC = ({ alertSuppre error={false} icon={'layers'} value={alertSuppressionCount} - text={CORRELATIONS_SUPPRESSED_ALERTS(alertSuppressionCount)} - data-test-subj={INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID} + text={ + + } + data-test-subj={CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID} key={`correlation-row-suppressed-alerts`} /> @@ -45,9 +50,14 @@ export const SuppressedAlerts: React.VFC = ({ alertSuppre label={SUPPRESSED_ALERTS_COUNT_TECHNICAL_PREVIEW} size="s" iconType="beaker" - tooltipContent={TECHNICAL_PREVIEW_MESSAGE} + tooltipContent={ + + } tooltipPosition="bottom" - data-test-subj={SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID} + data-test-subj={CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID} />
    diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts index 6f0ee15d7db01..8d1b13c12c146 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts @@ -5,74 +5,66 @@ * 2.0. */ -import { RESPONSE_BASE_TEST_ID } from '../../left/components/test_ids'; +import { PREFIX } from '../../shared/test_ids'; import { CONTENT_TEST_ID, HEADER_TEST_ID } from './expandable_section'; /* Header */ -export const FLYOUT_HEADER_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutHeaderTitle'; -export const EXPAND_DETAILS_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHeaderExpandDetailButton'; +const FLYOUT_HEADER_TEST_ID = `${PREFIX}Header` as const; +export const FLYOUT_HEADER_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}Title` as const; +export const EXPAND_DETAILS_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}ExpandDetailButton` as const; export const COLLAPSE_DETAILS_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHeaderCollapseDetailButton'; -export const FLYOUT_HEADER_STATUS_BUTTON_TEST_ID = 'rule-status-badge'; -export const FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID = - 'securitySolutionAlertDetailsFlyoutHeaderSeverityTitle'; -export const FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID = 'severity'; -export const FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID = - 'securitySolutionAlertDetailsFlyoutHeaderRiskScoreTitle'; -export const FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID = - 'securitySolutionAlertDetailsFlyoutHeaderRiskScoreValue'; -export const FLYOUT_HEADER_SHARE_BUTTON_TEST_ID = - 'securitySolutionAlertDetailsFlyoutHeaderShareButton'; -export const FLYOUT_HEADER_CHAT_BUTTON_TEST_ID = 'newChatById'; + `${FLYOUT_HEADER_TEST_ID}CollapseDetailButton` as const; +export const STATUS_BUTTON_TEST_ID = 'rule-status-badge' as const; +export const SEVERITY_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}SeverityTitle` as const; +export const SEVERITY_VALUE_TEST_ID = 'severity' as const; +export const RISK_SCORE_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}RiskScoreTitle` as const; +export const RISK_SCORE_VALUE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}RiskScoreValue` as const; +export const SHARE_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}ShareButton` as const; +export const CHAT_BUTTON_TEST_ID = 'newChatById' as const; /* About section */ -export const ABOUT_SECTION_TEST_ID = 'securitySolutionDocumentDetailsFlyoutAboutSection'; +export const ABOUT_SECTION_TEST_ID = `${PREFIX}AboutSection` as const; export const ABOUT_SECTION_HEADER_TEST_ID = ABOUT_SECTION_TEST_ID + HEADER_TEST_ID; export const ABOUT_SECTION_CONTENT_TEST_ID = ABOUT_SECTION_TEST_ID + CONTENT_TEST_ID; -export const RULE_SUMMARY_BUTTON_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRuleSummaryButton'; -export const DESCRIPTION_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutDescriptionTitle'; -export const DESCRIPTION_DETAILS_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutDescriptionDetails'; -export const REASON_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutReasonTitle'; -export const REASON_DETAILS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutReasonDetails'; -export const REASON_DETAILS_PREVIEW_BUTTON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutReasonDetailsPreviewButton'; -export const MITRE_ATTACK_TITLE_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackTitle'; -export const MITRE_ATTACK_DETAILS_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackDetails'; +export const RULE_SUMMARY_BUTTON_TEST_ID = `${PREFIX}RuleSummaryButton` as const; +const DESCRIPTION_TEST_ID = `${PREFIX}Description` as const; +export const DESCRIPTION_TITLE_TEST_ID = `${DESCRIPTION_TEST_ID}Title` as const; +export const DESCRIPTION_DETAILS_TEST_ID = `${DESCRIPTION_TEST_ID}Details` as const; +const REASON_TEST_ID = `${PREFIX}Reason` as const; +export const REASON_TITLE_TEST_ID = `${REASON_TEST_ID}Title` as const; +export const REASON_DETAILS_TEST_ID = `${REASON_TEST_ID}Details` as const; +export const REASON_DETAILS_PREVIEW_BUTTON_TEST_ID = `${REASON_TEST_ID}PreviewButton` as const; +const MITRE_ATTACK_TEST_ID = `${PREFIX}MitreAttack` as const; +export const MITRE_ATTACK_TITLE_TEST_ID = `${MITRE_ATTACK_TEST_ID}Title` as const; +export const MITRE_ATTACK_DETAILS_TEST_ID = `${MITRE_ATTACK_TEST_ID}Details` as const; /* Investigation section */ -export const INVESTIGATION_SECTION_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInvestigationSection'; +export const INVESTIGATION_SECTION_TEST_ID = `${PREFIX}InvestigationSection` as const; export const INVESTIGATION_SECTION_HEADER_TEST_ID = INVESTIGATION_SECTION_TEST_ID + HEADER_TEST_ID; export const INVESTIGATION_SECTION_CONTENT_TEST_ID = INVESTIGATION_SECTION_TEST_ID + CONTENT_TEST_ID; -export const HIGHLIGHTED_FIELDS_TITLE_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHighlightedFieldsTitle'; -export const HIGHLIGHTED_FIELDS_DETAILS_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHighlightedFieldsDetails'; -export const HIGHLIGHTED_FIELDS_CELL_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHighlightedFieldsCell'; +export const INVESTIGATION_GUIDE_TEST_ID = `${PREFIX}InvestigationGuide` as const; +export const INVESTIGATION_GUIDE_BUTTON_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Button` as const; +export const INVESTIGATION_GUIDE_LOADING_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Loading` as const; +export const INVESTIGATION_GUIDE_NO_DATA_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}NoData` as const; +const HIGHLIGHTED_FIELDS_TEST_ID = `${PREFIX}HighlightedFields` as const; +export const HIGHLIGHTED_FIELDS_TITLE_TEST_ID = `${HIGHLIGHTED_FIELDS_TEST_ID}Title` as const; +export const HIGHLIGHTED_FIELDS_DETAILS_TEST_ID = `${HIGHLIGHTED_FIELDS_TEST_ID}Details` as const; +export const HIGHLIGHTED_FIELDS_CELL_TEST_ID = `${HIGHLIGHTED_FIELDS_TEST_ID}Cell` as const; export const HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHighlightedFieldsBasicCell'; + `${HIGHLIGHTED_FIELDS_TEST_ID}BasicCell` as const; export const HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHighlightedFieldsLinkedCell'; + `${HIGHLIGHTED_FIELDS_TEST_ID}LinkedCell` as const; export const HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutHighlightedFieldsAgentStatusCell'; - -export const INVESTIGATION_GUIDE_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInvestigationGuide'; -export const INVESTIGATION_GUIDE_BUTTON_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Button`; -export const INVESTIGATION_GUIDE_LOADING_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Loading`; -export const INVESTIGATION_GUIDE_NO_DATA_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}NoData`; + `${HIGHLIGHTED_FIELDS_TEST_ID}AgentStatusCell` as const; /* Insights section */ -export const INSIGHTS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsights'; -export const INSIGHTS_HEADER_TEST_ID = `${INSIGHTS_TEST_ID}Header`; +export const INSIGHTS_TEST_ID = `${PREFIX}Insights` as const; +export const INSIGHTS_HEADER_TEST_ID = `${INSIGHTS_TEST_ID}Header` as const; /* Summary row */ @@ -80,65 +72,71 @@ export const SUMMARY_ROW_LOADING_TEST_ID = (dataTestSubj: string) => `${dataTest export const SUMMARY_ROW_ICON_TEST_ID = (dataTestSubj: string) => `${dataTestSubj}Icon`; export const SUMMARY_ROW_VALUE_TEST_ID = (dataTestSubj: string) => `${dataTestSubj}Value`; -/* Insights Entities */ +/* Entities */ -export const INSIGHTS_ENTITIES_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsEntities'; +export const INSIGHTS_ENTITIES_TEST_ID = `${PREFIX}InsightsEntities` as const; export const INSIGHTS_ENTITIES_NO_DATA_TEST_ID = `${INSIGHTS_ENTITIES_TEST_ID}NoData` as const; -export const ENTITIES_USER_OVERVIEW_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutEntitiesUserOverview'; -export const ENTITIES_USER_OVERVIEW_LINK_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}Link`; -export const ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}Domain`; -export const ENTITIES_USER_OVERVIEW_LAST_SEEN_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}LastSeen`; -export const ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}RiskLevel`; -export const ENTITIES_HOST_OVERVIEW_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutEntitiesHostOverview'; -export const ENTITIES_HOST_OVERVIEW_LINK_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}Link`; -export const ENTITIES_HOST_OVERVIEW_OS_FAMILY_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}OsFamily`; -export const ENTITIES_HOST_OVERVIEW_LAST_SEEN_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}LastSeen`; -export const ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}RiskLevel`; +export const ENTITIES_USER_OVERVIEW_TEST_ID = `${INSIGHTS_ENTITIES_TEST_ID}UserOverview` as const; +export const ENTITIES_USER_OVERVIEW_LINK_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}Link` as const; +export const ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID = + `${ENTITIES_USER_OVERVIEW_TEST_ID}Domain` as const; +export const ENTITIES_USER_OVERVIEW_LAST_SEEN_TEST_ID = + `${ENTITIES_USER_OVERVIEW_TEST_ID}LastSeen` as const; +export const ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID = + `${ENTITIES_USER_OVERVIEW_TEST_ID}RiskLevel` as const; +export const ENTITIES_HOST_OVERVIEW_TEST_ID = `${INSIGHTS_ENTITIES_TEST_ID}HostOverview` as const; +export const ENTITIES_HOST_OVERVIEW_LINK_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}Link` as const; +export const ENTITIES_HOST_OVERVIEW_OS_FAMILY_TEST_ID = + `${ENTITIES_HOST_OVERVIEW_TEST_ID}OsFamily` as const; +export const ENTITIES_HOST_OVERVIEW_LAST_SEEN_TEST_ID = + `${ENTITIES_HOST_OVERVIEW_TEST_ID}LastSeen` as const; +export const ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID = + `${ENTITIES_HOST_OVERVIEW_TEST_ID}RiskLevel` as const; export const TECHNICAL_PREVIEW_ICON_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutTechnicalPreviewIcon'; - -/* Insights Threat intelligence */ - -export const INSIGHTS_THREAT_INTELLIGENCE_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsThreatIntelligence'; -export const INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID = `${INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}Container`; - -/* Insights Correlations */ - -export const INSIGHTS_CORRELATIONS_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsCorrelations'; -export const INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsSupressedAlerts'; -export const SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutSupressedAlertsTechnicalPreview'; -export const INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedCases'; -export const INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedAlertsBySession'; -export const INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedAlertsBySameSourceEvent'; -export const INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedAlertsByAncestry'; + `${INSIGHTS_ENTITIES_TEST_ID}TechnicalPreviewIcon` as const; + +/* Threat intelligence */ + +export const INSIGHTS_THREAT_INTELLIGENCE_TEST_ID = `${PREFIX}InsightsThreatIntelligence` as const; + +/* Correlations */ + +export const CORRELATIONS_TEST_ID = `${PREFIX}Correlations` as const; +export const CORRELATIONS_NO_DATA_TEST_ID = `${CORRELATIONS_TEST_ID}NoData` as const; +export const CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID = + `${CORRELATIONS_TEST_ID}SuppressedAlerts` as const; +export const CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID = + `${CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID}TechnicalPreview` as const; +export const CORRELATIONS_RELATED_CASES_TEST_ID = `${CORRELATIONS_TEST_ID}RelatedCases` as const; +export const CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID = + `${CORRELATIONS_TEST_ID}RelatedAlertsBySession` as const; +export const CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID = + `${CORRELATIONS_TEST_ID}RelatedAlertsBySameSourceEvent` as const; +export const CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID = + `${CORRELATIONS_TEST_ID}RelatedAlertsByAncestry` as const; /* Insights Prevalence */ -export const INSIGHTS_PREVALENCE_TEST_ID = - 'securitySolutionDocumentDetailsFlyoutInsightsPrevalence'; +export const PREVALENCE_TEST_ID = `${PREFIX}InsightsPrevalence` as const; +export const PREVALENCE_NO_DATA_TEST_ID = `${PREVALENCE_TEST_ID}NoData` as const; /* Visualizations section */ -export const VISUALIZATIONS_SECTION_TEST_ID = 'securitySolutionDocumentDetailsVisualizationsTitle'; +const VISUALIZATIONS_TEST_ID = `${PREFIX}Visualizations` as const; +export const VISUALIZATIONS_SECTION_TEST_ID = `${VISUALIZATIONS_TEST_ID}Title` as const; export const VISUALIZATIONS_SECTION_HEADER_TEST_ID = - 'securitySolutionDocumentDetailsVisualizationsTitleHeader'; -export const ANALYZER_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsAnalyzerPreview'; -export const SESSION_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsSessionPreview'; + `${VISUALIZATIONS_TEST_ID}TitleHeader` as const; +export const ANALYZER_PREVIEW_TEST_ID = `${PREFIX}AnalyzerPreview` as const; +export const ANALYZER_PREVIEW_NO_DATA_TEST_ID = `${ANALYZER_PREVIEW_TEST_ID}NoData` as const; +export const SESSION_PREVIEW_TEST_ID = `${PREFIX}SessionPreview` as const; +export const SESSION_PREVIEW_UPSELL_TEST_ID = `${SESSION_PREVIEW_TEST_ID}UpSell` as const; +export const SESSION_PREVIEW_NO_DATA_TEST_ID = `${SESSION_PREVIEW_TEST_ID}NoData` as const; /* Response section */ -export const RESPONSE_SECTION_TEST_ID = 'securitySolutionDocumentDetailsFlyoutResponseSection'; +const RESPONSE_TEST_ID = `${PREFIX}Response` as const; +export const RESPONSE_SECTION_TEST_ID = `${RESPONSE_TEST_ID}Section` as const; export const RESPONSE_SECTION_HEADER_TEST_ID = RESPONSE_SECTION_TEST_ID + HEADER_TEST_ID; export const RESPONSE_SECTION_CONTENT_TEST_ID = RESPONSE_SECTION_TEST_ID + CONTENT_TEST_ID; -export const RESPONSE_BUTTON_TEST_ID = 'securitySolutionDocumentDetailsFlyoutResponseButton'; -export const RESPONSE_EMPTY_TEST_ID = `${RESPONSE_BASE_TEST_ID}Empty` as const; +export const RESPONSE_BUTTON_TEST_ID = `${RESPONSE_TEST_ID}Button` as const; +export const RESPONSE_EMPTY_TEST_ID = `${RESPONSE_TEST_ID}Empty` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx index 70d8fce2d5c64..a111e5469e614 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx @@ -14,10 +14,7 @@ import { ThreatIntelligenceOverview } from './threat_intelligence_overview'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import { useFetchThreatIntelligence } from '../hooks/use_fetch_threat_intelligence'; import { THREAT_INTELLIGENCE_TAB_ID } from '../../left/components/threat_intelligence_details'; -import { - INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, -} from './test_ids'; +import { INSIGHTS_THREAT_INTELLIGENCE_TEST_ID } from './test_ids'; import { EXPANDABLE_PANEL_CONTENT_TEST_ID, EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, @@ -106,7 +103,7 @@ describe('', () => { ); }); - it('should render 0 field enriched', () => { + it('should render 0 fields enriched', () => { (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ loading: false, threatMatchesCount: 1, @@ -116,11 +113,11 @@ describe('', () => { const { getByTestId } = render(renderThreatIntelligenceOverview(panelContextValue)); expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent( - '0 field enriched with threat intelligence' + '0 fields enriched with threat intelligence' ); }); - it('should render 0 match detected', () => { + it('should render 0 matches detected', () => { (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ loading: false, threatMatchesCount: 0, @@ -129,7 +126,7 @@ describe('', () => { const { getByTestId } = render(renderThreatIntelligenceOverview(panelContextValue)); - expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent('0 threat match detected'); + expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent('0 threat matches detected'); }); it('should render loading', () => { @@ -142,35 +139,6 @@ describe('', () => { expect(getAllByTestId(LOADING_TEST_ID)).toHaveLength(2); }); - it('should render null when eventId is null', () => { - (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ - loading: false, - }); - const contextValue = { - ...panelContextValue, - eventId: null, - } as unknown as RightPanelContext; - - const { getByTestId } = render(renderThreatIntelligenceOverview(contextValue)); - - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID)).toBeEmptyDOMElement(); - }); - - it('should render null when dataFormattedForFieldBrowser is null', () => { - (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ - loading: false, - error: true, - }); - const contextValue = { - ...panelContextValue, - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { getByTestId } = render(renderThreatIntelligenceOverview(contextValue)); - - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID)).toBeEmptyDOMElement(); - }); - it('should navigate to left section Insights tab when clicking on button', () => { (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx index 6e8c1f3c6dded..0175d44e4f4bd 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx @@ -9,18 +9,12 @@ import type { FC } from 'react'; import React, { useCallback } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; import { useFetchThreatIntelligence } from '../hooks/use_fetch_threat_intelligence'; import { InsightsSummaryRow } from './insights_summary_row'; import { useRightPanelContext } from '../context'; import { INSIGHTS_THREAT_INTELLIGENCE_TEST_ID } from './test_ids'; -import { - THREAT_INTELLIGENCE_TITLE, - THREAT_MATCH_DETECTED, - THREAT_ENRICHMENT, - THREAT_MATCHES_DETECTED, - THREAT_ENRICHMENTS, -} from './translations'; import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { THREAT_INTELLIGENCE_TAB_ID } from '../../left/components/threat_intelligence_details'; @@ -48,21 +42,19 @@ export const ThreatIntelligenceOverview: FC = () => { }); }, [eventId, openLeftPanel, indexName, scopeId]); - const { - loading: threatIntelLoading, - error: threatIntelError, - threatMatchesCount, - threatEnrichmentsCount, - } = useFetchThreatIntelligence({ + const { loading, threatMatchesCount, threatEnrichmentsCount } = useFetchThreatIntelligence({ dataFormattedForFieldBrowser, }); - const error: boolean = !eventId || !dataFormattedForFieldBrowser || threatIntelError; - return ( + ), callback: goToThreatIntelligenceTab, iconType: 'arrowStart', }} @@ -74,19 +66,29 @@ export const ThreatIntelligenceOverview: FC = () => { data-test-subj={`${INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}Container`} > + } data-test-subj={INSIGHTS_THREAT_INTELLIGENCE_TEST_ID} /> + } data-test-subj={INSIGHTS_THREAT_INTELLIGENCE_TEST_ID} /> diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts b/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts deleted file mode 100644 index b9c6fd2692ac3..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -/* Header */ - -export const EXPAND_DETAILS_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.expandDetailButton', - { defaultMessage: 'Expand details' } -); - -export const COLLAPSE_DETAILS_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.collapseDetailButton', - { defaultMessage: 'Collapse details' } -); - -export const EVENT_DETAILS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.headerTitle', - { defaultMessage: 'Event details' } -); - -export const SEVERITY_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.severityTitle', - { - defaultMessage: 'Severity', - } -); - -export const RISK_SCORE_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.riskScoreTitle', - { - defaultMessage: 'Risk score', - } -); - -export const RULE_SUMMARY_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.ruleSummaryText', - { - defaultMessage: 'Show rule summary', - } -); - -export const ALERT_REASON_DETAILS_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.alertReasonDetailsText', - { - defaultMessage: 'Show full reason', - } -); - -/* About section */ - -export const ABOUT_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.aboutTitle', - { - defaultMessage: 'About', - } -); - -export const RULE_DESCRIPTION_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.ruleDescriptionTitle', - { - defaultMessage: 'Rule description', - } -); - -export const PREVIEW_RULE_DETAILS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.previewRuleDetailsText', - { defaultMessage: 'Preview rule details' } -); - -export const PREVIEW_ALERT_REASON_DETAILS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.previewAlertReasonDetailsText', - { defaultMessage: 'Preview alert reason' } -); - -export const DOCUMENT_DESCRIPTION_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.documentDescriptionTitle', - { - defaultMessage: 'Document description', - } -); - -export const ALERT_REASON_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.alertReasonTitle', - { - defaultMessage: 'Alert reason', - } -); - -export const DOCUMENT_REASON_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.documentReasonTitle', - { - defaultMessage: 'Document reason', - } -); - -/* Investigation section */ - -export const INVESTIGATION_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.investigationSectionTitle', - { - defaultMessage: 'Investigation', - } -); - -export const HIGHLIGHTED_FIELDS_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.highlightedFieldsTitle', - { defaultMessage: 'Highlighted fields' } -); - -export const HIGHLIGHTED_FIELDS_FIELD_COLUMN = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.highlightedFields.fieldColumn', - { defaultMessage: 'Field' } -); - -export const HIGHLIGHTED_FIELDS_VALUE_COLUMN = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.highlightedFields.valueColumn', - { defaultMessage: 'Value' } -); - -/* Insights section */ - -export const ENTITIES_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.entitiesTitle', - { defaultMessage: 'Entities' } -); - -export const ENTITIES_NO_DATA_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.entitiesNoDataMessage', - { - defaultMessage: 'Host and user information are unavailable for this alert', - } -); - -export const THREAT_INTELLIGENCE_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.threatIntelligenceTitle', - { defaultMessage: 'Threat intelligence' } -); - -export const INSIGHTS_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.insightsTitle', - { defaultMessage: 'Insights' } -); - -export const CORRELATIONS_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.correlationsTitle', - { defaultMessage: 'Correlations' } -); - -export const CORRELATIONS_ERROR = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.correlations.error', - { - defaultMessage: 'No correlations data available', - } -); - -export const PREVALENCE_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.prevalenceTitle', - { defaultMessage: 'Prevalence' } -); - -export const PREVALENCE_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.prevalenceNoData', - { - defaultMessage: - 'Over the last 30 days, the highlighted fields for this alert were observed frequently on other host and user events.', - } -); - -export const THREAT_MATCH_DETECTED = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatch', - { - defaultMessage: `threat match detected`, - } -); - -export const THREAT_MATCHES_DETECTED = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatches', - { - defaultMessage: `threat matches detected`, - } -); - -export const THREAT_ENRICHMENT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichment', - { - defaultMessage: `field enriched with threat intelligence`, - } -); - -export const THREAT_ENRICHMENTS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichments', - { - defaultMessage: `fields enriched with threat intelligence`, - } -); - -export const PREVALENCE_ROW_UNCOMMON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.prevalenceRowText', - { - defaultMessage: 'is uncommon', - } -); - -export const VISUALIZATIONS_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.visualizationsTitle', - { defaultMessage: 'Visualizations' } -); - -export const ANALYZER_PREVIEW_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.analyzerPreviewTitle', - { defaultMessage: 'Analyzer preview' } -); - -export const SHARE = i18n.translate('xpack.securitySolution.flyout.documentDetails.share', { - defaultMessage: 'Share Alert', -}); - -export const INVESTIGATION_GUIDE_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.investigationGuideTitle', - { - defaultMessage: 'Investigation guide', - } -); - -export const INVESTIGATION_GUIDE_BUTTON = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.investigationGuideButton', - { - defaultMessage: 'Show investigation guide', - } -); - -export const INVESTIGATION_GUIDE_NO_DATA = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.investigationGuideNoData', - { - defaultMessage: 'There’s no investigation guide for this rule.', - } -); - -export const SESSION_PREVIEW_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.sessionPreview.title', - { - defaultMessage: 'Session viewer preview', - } -); - -export const SESSION_PREVIEW_PROCESS_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.sessionPreview.processText', - { - defaultMessage: 'started', - } -); - -export const SESSION_PREVIEW_TIME_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.sessionPreview.timeText', - { - defaultMessage: 'at', - } -); - -export const SESSION_PREVIEW_RULE_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.sessionPreview.ruleText', - { - defaultMessage: 'with rule', - } -); - -export const SESSION_PREVIEW_COMMAND_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.sessionPreview.commandText', - { - defaultMessage: 'by', - } -); - -export const RESPONSE_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.responseSectionTitle', - { - defaultMessage: 'Response', - } -); - -export const RESPONSE_EMPTY = i18n.translate('xpack.securitySolution.flyout.response.empty', { - defaultMessage: 'This alert did not generate an external notification.', -}); - -export const TECHNICAL_PREVIEW_TITLE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.technicalPreviewTitle', - { defaultMessage: 'Technical preview' } -); - -export const TECHNICAL_PREVIEW_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.technicalPreviewMessage', - { - defaultMessage: - 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', - } -); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx index 821b0ee5d19f6..4340e7a630717 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx @@ -17,8 +17,8 @@ import { ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID, } from './test_ids'; import { useObservedUserDetails } from '../../../explore/users/containers/users/observed_details'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockContextValue } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; @@ -66,19 +66,22 @@ jest.mock('../../../explore/containers/risk_score'); const mockUseFirstLastSeen = useFirstLastSeen as jest.Mock; jest.mock('../../../common/containers/use_first_last_seen'); +const renderUserEntityOverview = () => + render( + + + + + + ); + describe('', () => { describe('license is valid', () => { it('should render user domain and user risk classification', () => { mockUseUserDetails.mockReturnValue([false, { userDetails: userData }]); mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true }); - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderUserEntityOverview(); expect(getByTestId(ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID)).toHaveTextContent(domain); expect(getByTestId(ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID)).toHaveTextContent('Medium'); @@ -88,13 +91,8 @@ describe('', () => { mockUseUserDetails.mockReturnValue([false, { userDetails: null }]); mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true }); - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderUserEntityOverview(); + expect(getByTestId(ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID)).toHaveTextContent('—'); expect(getByTestId(ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID)).toHaveTextContent('—'); }); @@ -106,13 +104,7 @@ describe('', () => { mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: false }); mockUseFirstLastSeen.mockReturnValue([false, { lastSeen }]); - const { getByTestId, queryByTestId } = render( - - - - - - ); + const { getByTestId, queryByTestId } = renderUserEntityOverview(); expect(getByTestId(ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID)).toHaveTextContent(domain); expect(getByTestId(ENTITIES_USER_OVERVIEW_LAST_SEEN_TEST_ID)).toHaveTextContent(lastSeenText); @@ -124,13 +116,7 @@ describe('', () => { mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: false }); mockUseFirstLastSeen.mockReturnValue([false, { lastSeen: null }]); - const { getByTestId } = render( - - - - - - ); + const { getByTestId } = renderUserEntityOverview(); expect(getByTestId(ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID)).toHaveTextContent('—'); expect(getByTestId(ENTITIES_USER_OVERVIEW_LAST_SEEN_TEST_ID)).toHaveTextContent('—'); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx index 998f6f71b02a7..941a5c299ffbe 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx @@ -18,6 +18,7 @@ import { import { css } from '@emotion/css'; import { getOr } from 'lodash/fp'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useRightPanelContext } from '../context'; @@ -44,7 +45,6 @@ import { ENTITIES_USER_OVERVIEW_LINK_TEST_ID, TECHNICAL_PREVIEW_ICON_TEST_ID, } from './test_ids'; -import { TECHNICAL_PREVIEW_TITLE, TECHNICAL_PREVIEW_MESSAGE } from './translations'; import { useObservedUserDetails } from '../../../explore/users/containers/users/observed_details'; const USER_ICON = 'user'; @@ -149,10 +149,20 @@ export const UserEntityOverview: React.FC = ({ userName <> {i18n.USER_RISK_CLASSIFICATION} + } size="m" type="iInCircle" - content={TECHNICAL_PREVIEW_MESSAGE} + content={ + + } position="bottom" iconProps={{ className: 'eui-alignTop', diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.test.tsx index a427cdf04bce9..3d34a7e02aff3 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.test.tsx @@ -6,12 +6,13 @@ */ import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { VISUALIZATIONS_SECTION_HEADER_TEST_ID } from './test_ids'; import { TestProviders } from '../../../common/mock'; import { VisualizationsSection } from './visualizations_section'; -import { mockContextValue } from '../mocks/mock_right_panel_context'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockContextValue } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { RightPanelContext } from '../context'; import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; @@ -42,11 +43,13 @@ describe('', () => { } as unknown as ExpandableFlyoutContext; const { getByTestId, getAllByRole } = render( - - - - - + + + + + + + ); expect(getByTestId(VISUALIZATIONS_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.tsx index b2cc0d2969c7e..4b53911bea590 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/visualizations_section.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { AnalyzerPreviewContainer } from './analyzer_preview_container'; import { SessionPreviewContainer } from './session_preview_container'; import { ExpandableSection } from './expandable_section'; import { VISUALIZATIONS_SECTION_TEST_ID } from './test_ids'; -import { VISUALIZATIONS_TITLE } from './translations'; export interface VisualizationsSectionProps { /** @@ -29,7 +29,12 @@ export const VisualizationsSection: React.FC = ({ return ( + } data-test-subj={VISUALIZATIONS_SECTION_TEST_ID} > diff --git a/x-pack/plugins/security_solution/public/flyout/right/context.tsx b/x-pack/plugins/security_solution/public/flyout/right/context.tsx index fb9424314deb2..66a5ed3096f03 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/context.tsx @@ -6,25 +6,16 @@ */ import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { css } from '@emotion/react'; -import React, { createContext, useContext, useMemo } from 'react'; -import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import React, { createContext, memo, useContext, useMemo } from 'react'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { useEventDetails } from '../shared/hooks/use_event_details'; +import { FlyoutError } from '../shared/components/flyout_error'; +import { FlyoutLoading } from '../shared/components/flyout_loading'; import type { SearchHit } from '../../../common/search_strategy'; -import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { - getAlertIndexAlias, - useBasicDataFromDetailsData, -} from '../../timelines/components/side_panel/event_details/helpers'; -import { useSpaceId } from '../../common/hooks/use_space_id'; -import { useRouteSpy } from '../../common/utils/route/use_route_spy'; -import { SecurityPageName } from '../../../common/constants'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; -import { useSourcererDataView } from '../../common/containers/sourcerer'; +import { useBasicDataFromDetailsData } from '../../timelines/components/side_panel/event_details/helpers'; import type { RightPanelProps } from '.'; import type { GetFieldsData } from '../../common/hooks/use_get_fields_data'; -import { useGetFieldsData } from '../../common/hooks/use_get_fields_data'; import { useRuleWithFallback } from '../../detection_engine/rule_management/logic/use_rule_with_fallback'; export interface RightPanelContext { @@ -43,19 +34,19 @@ export interface RightPanelContext { /** * An object containing fields by type */ - browserFields: BrowserFields | null; + browserFields: BrowserFields; /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * The actual raw document object */ - searchHit: SearchHit | undefined; + searchHit: SearchHit; /** * User defined fields to highlight (defined on the rule) */ @@ -79,78 +70,69 @@ export type RightPanelProviderProps = { children: React.ReactNode; } & Partial; -export const RightPanelProvider = ({ - id, - indexName, - scopeId, - children, -}: RightPanelProviderProps) => { - const currentSpaceId = useSpaceId(); - // TODO Replace getAlertIndexAlias way to retrieving the eventIndex with the GET /_alias - // https://github.com/elastic/kibana/issues/113063 - const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - const sourcererDataView = useSourcererDataView(sourcererScope); - const [loading, dataFormattedForFieldBrowser, searchHit, dataAsNestedObject, refetchFlyoutData] = - useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); - const getFieldsData = useGetFieldsData(searchHit?.fields); - const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); - const { rule: maybeRule } = useRuleWithFallback(ruleId); - - const contextValue = useMemo( - () => - id && indexName && scopeId - ? { - eventId: id, - indexName, - scopeId, - browserFields: sourcererDataView.browserFields, - dataAsNestedObject, - dataFormattedForFieldBrowser, - searchHit, - investigationFields: maybeRule?.investigation_fields?.field_names ?? [], - refetchFlyoutData, - getFieldsData, - } - : undefined, - [ - id, - maybeRule, - indexName, - scopeId, - sourcererDataView.browserFields, +export const RightPanelProvider = memo( + ({ id, indexName, scopeId, children }: RightPanelProviderProps) => { + const { + browserFields, dataAsNestedObject, dataFormattedForFieldBrowser, - searchHit, - refetchFlyoutData, getFieldsData, - ] - ); + loading, + refetchFlyoutData, + searchHit, + } = useEventDetails({ eventId: id, indexName }); - if (loading) { - return ( - - - + const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const { rule: maybeRule } = useRuleWithFallback(ruleId); + + const contextValue = useMemo( + () => + id && + indexName && + scopeId && + dataAsNestedObject && + dataFormattedForFieldBrowser && + searchHit + ? { + eventId: id, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + investigationFields: maybeRule?.investigation_fields?.field_names ?? [], + refetchFlyoutData, + getFieldsData, + } + : undefined, + [ + id, + maybeRule, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + refetchFlyoutData, + getFieldsData, + ] ); + + if (loading) { + return ; + } + + if (!contextValue) { + return ; + } + + return {children}; } +); - return {children}; -}; +RightPanelProvider.displayName = 'RightPanelProvider'; export const useRightPanelContext = (): RightPanelContext => { const contextValue = useContext(RightPanelContext); diff --git a/x-pack/plugins/security_solution/public/flyout/right/footer.tsx b/x-pack/plugins/security_solution/public/flyout/right/footer.tsx index d0980141ebfcc..b11f1ad1f0013 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/footer.tsx @@ -6,7 +6,7 @@ */ import type { FC } from 'react'; -import React, { memo } from 'react'; +import React, { useCallback } from 'react'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { FlyoutFooter } from '../../timelines/components/side_panel/event_details/flyout'; import { useRightPanelContext } from './context'; @@ -15,16 +15,34 @@ import { useHostIsolationTools } from '../../timelines/components/side_panel/eve /** * */ -export const PanelFooter: FC = memo(() => { - const { closeFlyout } = useExpandableFlyoutContext(); - const { dataFormattedForFieldBrowser, dataAsNestedObject, refetchFlyoutData, scopeId } = - useRightPanelContext(); +export const PanelFooter: FC = () => { + const { closeFlyout, openRightPanel } = useExpandableFlyoutContext(); + const { + eventId, + indexName, + dataFormattedForFieldBrowser, + dataAsNestedObject, + refetchFlyoutData, + scopeId, + } = useRightPanelContext(); const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolationTools(); - if (!dataFormattedForFieldBrowser || !dataAsNestedObject) { - return null; - } + const showHostIsolationPanelCallback = useCallback( + (action: 'isolateHost' | 'unisolateHost' | undefined) => { + showHostIsolationPanel(action); + openRightPanel({ + id: 'document-details-isolate-host', + params: { + id: eventId, + indexName, + scopeId, + isolateAction: action, + }, + }); + }, + [eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel] + ); return ( { isHostIsolationPanelOpen={isHostIsolationPanelOpen} isReadOnly={false} loadingEventDetails={false} - onAddIsolationStatusClick={showHostIsolationPanel} + onAddIsolationStatusClick={showHostIsolationPanelCallback} scopeId={scopeId} refetchFlyoutData={refetchFlyoutData} /> ); -}); - -PanelFooter.displayName = 'PanelFooter'; +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/header.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/header.test.tsx index 6432cbc2d41b6..b226ddc8289b9 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/header.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/header.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { TestProviders } from '../../common/mock'; import { RightPanelContext } from './context'; -import { mockContextValue } from './mocks/mock_right_panel_context'; +import { mockContextValue } from './mocks/mock_context'; import { PanelHeader } from './header'; import { COLLAPSE_DETAILS_BUTTON_TEST_ID, diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.test.tsx index ec4fcd4c18dc4..68b07b45b30ff 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.test.tsx @@ -9,7 +9,7 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks'; import type { UseAssistantParams, UseAssistantResult } from './use_assistant'; import { useAssistant } from './use_assistant'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { useAssistantOverlay } from '@kbn/elastic-assistant'; import { useAssistantAvailability } from '../../../assistant/use_assistant_availability'; @@ -78,10 +78,13 @@ describe('useAssistant', () => { expect(await getPromptContext()).toEqual({ '@timestamp': ['2023-01-01T01:01:01.000Z'], + 'event.category': ['registry'], 'kibana.alert.ancestors.id': ['ancestors-id'], 'kibana.alert.rule.description': ['rule-description'], + 'kibana.alert.rule.indices': ['rule-indices'], 'kibana.alert.rule.name': ['rule-name'], 'kibana.alert.rule.parameters.index': ['rule-parameters-index'], + 'kibana.alert.rule.type': ['query'], 'kibana.alert.rule.uuid': ['rule-uuid'], 'kibana.alert.workflow_status': ['open'], 'process.entity_id': ['process-entity_id'], diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.ts b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.ts index 8d9360fdb0f5e..a53d2e97015ab 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_assistant.ts @@ -31,7 +31,7 @@ export interface UseAssistantParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * Is true if the document is an alert */ diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.test.tsx index d75fe216593d2..075935ad37fd4 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.test.tsx @@ -71,7 +71,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toHaveLength(1); expect(hookResult.result.current.threatMatchesCount).toEqual(1); expect(hookResult.result.current.threatEnrichments).toHaveLength(1); @@ -121,7 +120,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toHaveLength(2); expect(hookResult.result.current.threatMatchesCount).toEqual(2); expect(hookResult.result.current.threatEnrichments).toHaveLength(2); @@ -148,7 +146,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toHaveLength(1); expect(hookResult.result.current.threatMatchesCount).toEqual(1); expect(hookResult.result.current.threatEnrichments).toEqual(undefined); @@ -176,7 +173,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toEqual(undefined); expect(hookResult.result.current.threatMatchesCount).toEqual(0); expect(hookResult.result.current.threatEnrichments).toHaveLength(1); @@ -192,28 +188,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(true); - expect(hookResult.result.current.error).toEqual(false); - expect(hookResult.result.current.threatMatches).toEqual(undefined); - expect(hookResult.result.current.threatMatchesCount).toEqual(0); - expect(hookResult.result.current.threatEnrichments).toEqual(undefined); - expect(hookResult.result.current.threatEnrichmentsCount).toEqual(0); - }); - - it('should return error true', () => { - (useInvestigationTimeEnrichment as jest.Mock).mockReturnValue({ - result: { - enrichments: [], - totalCount: 0, - }, - loading: false, - }); - - hookResult = renderHook(() => - useFetchThreatIntelligence({ dataFormattedForFieldBrowser: null }) - ); - - expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(true); expect(hookResult.result.current.threatMatches).toEqual(undefined); expect(hookResult.result.current.threatMatchesCount).toEqual(0); expect(hookResult.result.current.threatEnrichments).toEqual(undefined); diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts index 37971e755f48b..3b495ad52bc60 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts @@ -23,7 +23,7 @@ export interface UseThreatIntelligenceParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; } export interface UseThreatIntelligenceResult { @@ -31,10 +31,6 @@ export interface UseThreatIntelligenceResult { * Returns true while the threat intelligence data is being queried */ loading: boolean; - /** - * Returns true if the dataFormattedForFieldBrowser property is null - */ - error: boolean; /** * Threat matches (from an indicator match rule) */ @@ -99,7 +95,6 @@ export const useFetchThreatIntelligence = ({ return { loading, - error: !dataFormattedForFieldBrowser, threatMatches, threatMatchesCount: (threatMatches || []).length, threatEnrichments, diff --git a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts index c90a286d1dd97..2bb599a657591 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts @@ -5,176 +5,25 @@ * 2.0. */ -import { - ALERT_REASON, - ALERT_RISK_SCORE, - ALERT_SEVERITY, - ALERT_SUPPRESSION_DOCS_COUNT, -} from '@kbn/rule-data-utils'; +import { mockBrowserFields } from '../../shared/mocks/mock_browser_fields'; +import { mockSearchHit } from '../../shared/mocks/mock_search_hit'; +import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; +import { mockDataAsNestedObject } from '../../shared/mocks/mock_data_as_nested_object'; +import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; +import type { RightPanelContext } from '../context'; /** - * Returns mocked data for field (mock this method: x-pack/plugins/security_solution/public/common/hooks/use_get_fields_data.ts) - * @param field - * @returns string[] + * Mock contextValue for right panel context */ -export const mockGetFieldsData = (field: string): string[] => { - switch (field) { - case ALERT_SEVERITY: - return ['low']; - case ALERT_RISK_SCORE: - return ['0']; - case 'host.name': - return ['host1']; - case 'user.name': - return ['user1']; - case ALERT_REASON: - return ['reason']; - case ALERT_SUPPRESSION_DOCS_COUNT: - return ['1']; - default: - return []; - } -}; - -/** - * Mock an array of fields for an alert - */ -export const mockDataFormattedForFieldBrowser = [ - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - values: ['rule-uuid'], - originalValue: ['rule-uuid'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.name', - values: ['rule-name'], - originalValue: ['rule-name'], - isObjectArray: false, - }, - { - category: 'base', - field: '@timestamp', - values: ['2023-01-01T01:01:01.000Z'], - originalValue: ['2023-01-01T01:01:01.000Z'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.description', - values: ['rule-description'], - originalValue: ['rule-description'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.ancestors.id', - values: ['ancestors-id'], - originalValue: ['ancestors-id'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.index', - values: ['rule-parameters-index'], - originalValue: ['rule-parameters-index'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.entity_id', - values: ['process-entity_id'], - originalValue: ['process-entity_id'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.workflow_status', - values: ['open'], - originalValue: ['open'], - isObjectArray: false, - }, -]; - -/** - * Mock an object of nested properties for an alert - */ -export const mockDataAsNestedObject = { - _id: '123', - '@timestamp': ['2023-01-01T01:01:01.000Z'], - event: { - category: ['malware'], - kind: ['signal'], - }, - host: { - name: ['host-name'], - }, - kibana: { - alert: { - rule: { - name: ['rule-name'], - }, - severity: ['low'], - }, - }, - process: { - name: ['process-name'], - }, -}; - -/** - * Mock the document result of the search for an alert - */ -export const mockSearchHit = { - fields: { - 'kibana.alert.rule.parameters': [ - { - threat: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: '123', - reference: 'https://attack.mitre.org/tactics/123', - name: 'Tactic', - }, - technique: [ - { - id: '456', - reference: 'https://attack.mitre.org/techniques/456', - name: 'Technique', - }, - ], - }, - ], - }, - ], - }, -}; - -/** - * Mock the browserFields object - */ -export const mockBrowserFields = { - kibana: { - fields: { - 'kibana.alert.workflow_status': { - aggregatable: true, - count: 0, - esTypes: [0], - format: { - id: 'string', - params: undefined, - }, - isMapped: true, - name: 'kibana.alert.workflow_status', - readFromDocValues: true, - scripted: false, - searchable: true, - shortDotsEnable: false, - type: 'string', - }, - }, - }, +export const mockContextValue: RightPanelContext = { + eventId: 'eventId', + indexName: 'index', + scopeId: 'scopeId', + getFieldsData: mockGetFieldsData, + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, + browserFields: mockBrowserFields, + dataAsNestedObject: mockDataAsNestedObject, + searchHit: mockSearchHit, + investigationFields: [], + refetchFlyoutData: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_kibana_ui_settings_service.ts b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_kibana_ui_settings_service.ts deleted file mode 100644 index 8d7fee7846753..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_kibana_ui_settings_service.ts +++ /dev/null @@ -1,29 +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 { IUiSettingsClient } from '@kbn/core/public'; - -const DEFAULT_DATE_FORMAT = 'dateFormat' as const; -const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; - -/** - * Creates an object to pass to the uiSettings property when creating a KibanaReactContext (see src/plugins/kibana_react/public/context/context.tsx). - * @param dateFormat defaults to '' - * @param timezone defaults to 'UTC - * @returns the object {@link IUiSettingsClient} - */ -export const mockUiSettingsService = (dateFormat: string = '', timezone: string = 'UTC') => - ({ - get: (key: string) => { - const settings = { - [DEFAULT_DATE_FORMAT]: dateFormat, - [DEFAULT_DATE_FORMAT_TZ]: timezone, - }; - // @ts-expect-error - return settings[key]; - }, - } as unknown as IUiSettingsClient); diff --git a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts deleted file mode 100644 index 38b69703356c0..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RightPanelContext } from '../context'; -import { mockDataFormattedForFieldBrowser, mockGetFieldsData } from './mock_context'; - -/** - * Mock contextValue for right panel context - */ -export const mockContextValue: RightPanelContext = { - eventId: 'eventId', - indexName: 'index', - scopeId: 'scopeId', - getFieldsData: mockGetFieldsData, - dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, - browserFields: {}, - dataAsNestedObject: null, - searchHit: undefined, - investigationFields: [], - refetchFlyoutData: jest.fn(), -}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs.tsx index 89802787283fe..1cdc25f36c01b 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs.tsx @@ -5,17 +5,18 @@ * 2.0. */ +import type { ReactElement } from 'react'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { JSON_TAB_TEST_ID, OVERVIEW_TAB_TEST_ID, TABLE_TAB_TEST_ID } from './test_ids'; import type { RightPanelPaths } from '.'; import { JsonTab } from './tabs/json_tab'; import { OverviewTab } from './tabs/overview_tab'; import { TableTab } from './tabs/table_tab'; -import { JSON_TAB, OVERVIEW_TAB, TABLE_TAB } from './translations'; export type RightPanelTabsType = Array<{ id: RightPanelPaths; - name: string; + name: ReactElement; content: React.ReactElement; 'data-test-subj': string; }>; @@ -27,19 +28,34 @@ export const tabs: RightPanelTabsType = [ { id: 'overview', 'data-test-subj': OVERVIEW_TAB_TEST_ID, - name: OVERVIEW_TAB, + name: ( + + ), content: , }, { id: 'table', 'data-test-subj': TABLE_TAB_TEST_ID, - name: TABLE_TAB, + name: ( + + ), content: , }, { id: 'json', 'data-test-subj': JSON_TAB_TEST_ID, - name: JSON_TAB, + name: ( + + ), content: , }, ]; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx index f714a3c7839e5..dac048913c49b 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { JsonTab } from './json_tab'; -import { JSON_TAB_ERROR_TEST_ID, JSON_TAB_CONTENT_TEST_ID } from './test_ids'; +import { JSON_TAB_CONTENT_TEST_ID } from './test_ids'; describe('', () => { it('should render code block component', () => { @@ -27,22 +27,4 @@ describe('', () => { expect(getByTestId(JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument(); }); - - it('should render error message on invalid searchHit', () => { - const contextValue = { - searchHit: null, - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(JSON_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx index 54322959d5326..b49bbca6f5705 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx @@ -5,12 +5,8 @@ * 2.0. */ -import { EuiEmptyPrompt } from '@elastic/eui'; import type { FC } from 'react'; import React, { memo } from 'react'; -import { DOCUMENT_ERROR_DETAILS, DOCUMENT_ERROR_TITLE } from './translations'; -import { JSON_TAB_ERROR_TEST_ID } from './test_ids'; -import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations'; import { JsonView } from '../../../common/components/event_details/json_view'; import { useRightPanelContext } from '../context'; @@ -20,18 +16,6 @@ import { useRightPanelContext } from '../context'; export const JsonTab: FC = memo(() => { const { searchHit } = useRightPanelContext(); - if (!searchHit) { - return ( - {ERROR_TITLE(DOCUMENT_ERROR_TITLE)}} - body={

    {ERROR_MESSAGE(DOCUMENT_ERROR_DETAILS)}

    } - data-test-subj={JSON_TAB_ERROR_TEST_ID} - /> - ); - } - return ; }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx index 16e0d84cd083a..d93cf67abc620 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; -import { TABLE_TAB_ERROR_TEST_ID, TABLE_TAB_CONTENT_TEST_ID } from './test_ids'; +import { TABLE_TAB_CONTENT_TEST_ID } from './test_ids'; import { TableTab } from './table_tab'; import { TestProviders } from '../../../common/mock'; @@ -40,64 +40,4 @@ describe('', () => { expect(getByTestId(TABLE_TAB_CONTENT_TEST_ID)).toBeInTheDocument(); }); - - it('should render error message on null browserFields', () => { - const contextValue = { - eventId: 'some_Id', - browserFields: null, - dataFormattedForFieldBrowser: [], - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(TABLE_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); - - it('should render error message on null dataFormattedForFieldBrowser', () => { - const contextValue = { - eventId: 'some_Id', - browserFields: {}, - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(TABLE_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); - - it('should render error message on null eventId', () => { - const contextValue = { - eventId: null, - browserFields: {}, - dataFormattedForFieldBrowser: [], - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(TABLE_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx index 7f3d0f45d63af..4f0fac3097679 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx @@ -7,13 +7,9 @@ import type { FC } from 'react'; import React, { memo } from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; -import { DOCUMENT_ERROR_DETAILS, DOCUMENT_ERROR_TITLE } from './translations'; -import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations'; import { TimelineTabs } from '../../../../common/types'; import { EventFieldsBrowser } from '../../../common/components/event_details/event_fields_browser'; import { useRightPanelContext } from '../context'; -import { TABLE_TAB_ERROR_TEST_ID } from './test_ids'; /** * Table view displayed in the document details expandable flyout right section @@ -21,18 +17,6 @@ import { TABLE_TAB_ERROR_TEST_ID } from './test_ids'; export const TableTab: FC = memo(() => { const { browserFields, dataFormattedForFieldBrowser, eventId } = useRightPanelContext(); - if (!browserFields || !eventId || !dataFormattedForFieldBrowser) { - return ( - {ERROR_TITLE(DOCUMENT_ERROR_TITLE)}} - body={

    {ERROR_MESSAGE(DOCUMENT_ERROR_DETAILS)}

    } - data-test-subj={TABLE_TAB_ERROR_TEST_ID} - /> - ); - } - return ( ', () => { + it('should render error title and body', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId(FLYOUT_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(FLYOUT_ERROR_TEST_ID)).toHaveTextContent('Unable to display data'); + expect(getByTestId(FLYOUT_ERROR_TEST_ID)).toHaveTextContent( + 'There was an error displaying data.' + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.tsx new file mode 100644 index 0000000000000..bda4e581e164b --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiEmptyPrompt, EuiFlexItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { FLYOUT_ERROR_TEST_ID } from '../test_ids'; + +/** + * Use this when you need to show an error state in the flyout + */ +export const FlyoutError: React.VFC = () => ( + + + + + } + body={ +

    + +

    + } + data-test-subj={FLYOUT_ERROR_TEST_ID} + /> +
    +); + +FlyoutError.displayName = 'FlyoutError'; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx new file mode 100644 index 0000000000000..e7889d30526c5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { FLYOUT_LOADING_TEST_ID } from '../test_ids'; +import { FlyoutLoading } from './flyout_loading'; + +describe('', () => { + it('should render loading', () => { + const { getByTestId } = render(); + expect(getByTestId(FLYOUT_LOADING_TEST_ID)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx new file mode 100644 index 0000000000000..e1186c6257efd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FLYOUT_LOADING_TEST_ID } from '../test_ids'; + +/** + * Use this when you need to show a loading state in the flyout + */ +export const FlyoutLoading: React.VFC = () => ( + + + +); + +FlyoutLoading.displayName = 'FlyoutLoading'; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/constants/field_names.ts b/x-pack/plugins/security_solution/public/flyout/shared/constants/field_names.ts index e9a896a73dded..b663ea41e2069 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/constants/field_names.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/constants/field_names.ts @@ -7,6 +7,7 @@ export const ANCESTOR_ID = 'kibana.alert.ancestors.id'; export const RULE_PARAMETERS_INDEX = 'kibana.alert.rule.parameters.index'; +export const RULE_INDICES = 'kibana.alert.rule.indices'; export const ORIGINAL_EVENT_ID = 'kibana.alert.original_event.id'; export const ENTRY_LEADER_ENTITY_ID = 'process.entry_leader.entity_id'; export const ENTRY_LEADER_START = 'process.entry_leader.start'; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.test.tsx new file mode 100644 index 0000000000000..0a092271e1ddd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; +import type { UseEventDetailsParams, UseEventDetailsResult } from './use_event_details'; +import { useEventDetails } from './use_event_details'; +import { useSpaceId } from '../../../common/hooks/use_space_id'; +import { useRouteSpy } from '../../../common/utils/route/use_route_spy'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { useTimelineEventsDetails } from '../../../timelines/containers/details'; +import { useGetFieldsData } from '../../../common/hooks/use_get_fields_data'; + +jest.mock('../../../common/hooks/use_space_id'); +jest.mock('../../../common/utils/route/use_route_spy'); +jest.mock('../../../common/containers/sourcerer'); +jest.mock('../../../timelines/containers/details'); +jest.mock('../../../common/hooks/use_get_fields_data'); + +const eventId = 'eventId'; +const indexName = 'indexName'; + +describe('useEventDetails', () => { + let hookResult: RenderHookResult; + + it('should return all properties', () => { + jest.mocked(useSpaceId).mockReturnValue('default'); + (useRouteSpy as jest.Mock).mockReturnValue([{ pageName: 'alerts' }]); + (useSourcererDataView as jest.Mock).mockReturnValue({ + browserFields: {}, + indexPattern: {}, + }); + (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, [], {}, {}, jest.fn()]); + jest.mocked(useGetFieldsData).mockReturnValue((field: string) => field); + + hookResult = renderHook(() => useEventDetails({ eventId, indexName })); + + expect(hookResult.result.current.browserFields).toEqual({}); + expect(hookResult.result.current.dataAsNestedObject).toEqual({}); + expect(hookResult.result.current.dataFormattedForFieldBrowser).toEqual([]); + expect(hookResult.result.current.getFieldsData('test')).toEqual('test'); + expect(hookResult.result.current.indexPattern).toEqual({}); + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.refetchFlyoutData()).toEqual(undefined); + expect(hookResult.result.current.searchHit).toEqual({}); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts new file mode 100644 index 0000000000000..0f43743bcab28 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; +import type { DataViewBase } from '@kbn/es-query'; +import type { RunTimeMappings } from '../../../../common/api/search_strategy'; +import { useSpaceId } from '../../../common/hooks/use_space_id'; +import { getAlertIndexAlias } from '../../../timelines/components/side_panel/event_details/helpers'; +import { useRouteSpy } from '../../../common/utils/route/use_route_spy'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { useTimelineEventsDetails } from '../../../timelines/containers/details'; +import { useGetFieldsData } from '../../../common/hooks/use_get_fields_data'; +import type { SearchHit } from '../../../../common/search_strategy'; +import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data'; + +export interface UseEventDetailsParams { + /** + * Id of the document + */ + eventId: string | undefined; + /** + * Name of the index used in the parent's page + */ + indexName: string | undefined; +} + +export interface UseEventDetailsResult { + /** + * An object containing fields by type + */ + browserFields: BrowserFields; + /** + * An object with top level fields from the ECS object + */ + dataAsNestedObject: Ecs | null; + /** + * An array of field objects with category and value + */ + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + /** + * Retrieves searchHit values for the provided field + */ + getFieldsData: GetFieldsData; + /** + * Index pattern for rule details + */ + indexPattern: DataViewBase; + /** + * Whether the data is loading + */ + loading: boolean; + /** + * Promise to trigger a data refresh + */ + refetchFlyoutData: () => Promise; + /** + * The actual raw document object + */ + searchHit: SearchHit | undefined; +} + +/** + * Hook to retrieve event details for alert details flyout contexts + */ +export const useEventDetails = ({ + eventId, + indexName, +}: UseEventDetailsParams): UseEventDetailsResult => { + const currentSpaceId = useSpaceId(); + // TODO Replace getAlertIndexAlias way to retrieving the eventIndex with the GET /_alias + // https://github.com/elastic/kibana/issues/113063 + const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; + const [{ pageName }] = useRouteSpy(); + const sourcererScope = + pageName === SecurityPageName.detections + ? SourcererScopeName.detections + : SourcererScopeName.default; + const sourcererDataView = useSourcererDataView(sourcererScope); + const [loading, dataFormattedForFieldBrowser, searchHit, dataAsNestedObject, refetchFlyoutData] = + useTimelineEventsDetails({ + indexName: eventIndex, + eventId: eventId ?? '', + runtimeMappings: sourcererDataView.runtimeMappings as RunTimeMappings, + skip: !eventId, + }); + const getFieldsData = useGetFieldsData(searchHit?.fields); + + return { + browserFields: sourcererDataView.browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + getFieldsData, + indexPattern: sourcererDataView.indexPattern, + loading, + refetchFlyoutData, + searchHit, + }; +}; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_prevalence.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_prevalence.ts index 804784728c3f7..3a0f5f824f4b2 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_prevalence.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_prevalence.ts @@ -11,6 +11,9 @@ import { useQuery } from '@tanstack/react-query'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { createFetchData } from '../utils/fetch_data'; import { useKibana } from '../../../common/lib/kibana'; +import { useTimelineDataFilters } from '../../../timelines/containers/use_timeline_data_filters'; +import { isActiveTimeline } from '../../../helpers'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; const QUERY_KEY = 'useFetchFieldValuePairWithAggregation'; @@ -99,7 +102,10 @@ export const useFetchPrevalence = ({ }, } = useKibana(); - const searchRequest = buildSearchRequest(highlightedFieldsFilters, from, to); + // retrieves detections and non-detections indices (for example, the alert security index from the current space and 'logs-*' indices) + const { selectedPatterns } = useTimelineDataFilters(isActiveTimeline(SourcererScopeName.default)); + + const searchRequest = buildSearchRequest(highlightedFieldsFilters, from, to, selectedPatterns); const { data, isLoading, isError } = useQuery( [QUERY_KEY, highlightedFieldsFilters, from, to], @@ -120,7 +126,8 @@ export const useFetchPrevalence = ({ const buildSearchRequest = ( highlightedFieldsFilters: Record, from: string, - to: string + to: string, + selectedPatterns: string[] ): IEsSearchRequest => { const query = buildEsQuery( undefined, @@ -146,14 +153,16 @@ const buildSearchRequest = ( ] ); - return buildAggregationSearchRequest(query, highlightedFieldsFilters); + return buildAggregationSearchRequest(query, highlightedFieldsFilters, selectedPatterns); }; const buildAggregationSearchRequest = ( query: QueryDslQueryContainer, - highlightedFieldsFilters: Record + highlightedFieldsFilters: Record, + selectedPatterns: string[] ): IEsSearchRequest => ({ params: { + index: selectedPatterns, body: { query, aggs: { diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.test.tsx index 77890fbd96f4b..b45af8ea45d17 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.test.tsx @@ -7,7 +7,7 @@ import { renderHook } from '@testing-library/react-hooks'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser'; import { useHighlightedFields } from './use_highlighted_fields'; const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts index ab7de04a021ce..f9fa147c8395a 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts @@ -18,7 +18,7 @@ export interface UseHighlightedFieldsParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * An array of fields user has selected to highlight, defined on rule */ @@ -45,8 +45,6 @@ export const useHighlightedFields = ({ dataFormattedForFieldBrowser, investigationFields, }: UseHighlightedFieldsParams): UseHighlightedFieldsResult => { - if (!dataFormattedForFieldBrowser) return {}; - const eventCategories = getEventCategoriesFromData(dataFormattedForFieldBrowser); const eventCodeField = find( diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.test.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.test.ts index a60fae257a0b9..198129192cdb4 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.test.ts @@ -13,7 +13,7 @@ import type { } from './use_investigation_guide'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser'; import { useInvestigationGuide } from './use_investigation_guide'; jest.mock('../../../timelines/components/side_panel/event_details/helpers'); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts index 66d2183ad64fe..cc546a43241d2 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts @@ -14,7 +14,7 @@ export interface UseInvestigationGuideParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; } export interface UseInvestigationGuideResult { diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx index 5d7bccd74b424..3f7d73923b8fa 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx @@ -8,7 +8,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { usePrevalence } from './use_prevalence'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser'; import { useHighlightedFields } from './use_highlighted_fields'; import { FIELD_NAMES_AGG_KEY, diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts index a7b110f92e740..a78295139d724 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts @@ -38,7 +38,7 @@ export interface UsePrevalenceParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * User defined fields to highlight (defined on the rule) */ diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx index 7500d7c041708..32c2cdaf72675 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx @@ -7,7 +7,6 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { UseShowRelatedAlertsByAncestryParams, UseShowRelatedAlertsByAncestryResult, @@ -15,7 +14,8 @@ import type { import { useShowRelatedAlertsByAncestry } from './use_show_related_alerts_by_ancestry'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { licenseService } from '../../../common/hooks/use_license'; -import { mockDataAsNestedObject, mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser'; +import { mockDataAsNestedObject } from '../mocks/mock_data_as_nested_object'; jest.mock('../../../common/hooks/use_experimental_features'); jest.mock('../../../common/hooks/use_license', () => { @@ -31,7 +31,7 @@ jest.mock('../../../common/hooks/use_license', () => { }); const licenseServiceMock = licenseService as jest.Mocked; -const dataAsNestedObject = mockDataAsNestedObject as unknown as Ecs; +const dataAsNestedObject = mockDataAsNestedObject; const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; describe('useShowRelatedAlertsByAncestry', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts index 85fde8e6732b3..fe2ccb518abe0 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts @@ -24,11 +24,11 @@ export interface UseShowRelatedAlertsByAncestryParams { /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; } export interface UseShowRelatedAlertsByAncestryResult { @@ -57,9 +57,7 @@ export const useShowRelatedAlertsByAncestry = ({ const isRelatedAlertsByProcessAncestryEnabled = useIsExperimentalFeatureEnabled( 'insightsRelatedAlertsByProcessAncestry' ); - const hasProcessEntityInfo = isInvestigateInResolverActionEnabled( - dataAsNestedObject || undefined - ); + const hasProcessEntityInfo = isInvestigateInResolverActionEnabled(dataAsNestedObject); const originalDocumentId = getField(getFieldsData(ANCESTOR_ID)); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_browser_fields.ts b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_browser_fields.ts new file mode 100644 index 0000000000000..760a547794b76 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_browser_fields.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { BrowserFields } from '@kbn/timelines-plugin/common'; + +/** + * Mock the browserFields object + */ +export const mockBrowserFields: BrowserFields = { + kibana: { + fields: { + 'kibana.alert.workflow_status': { + aggregatable: true, + esTypes: ['0'], + format: '', + name: 'kibana.alert.workflow_status', + readFromDocValues: true, + searchable: true, + type: 'string', + }, + }, + }, +}; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_context.ts deleted file mode 100644 index 8280fb64df927..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_context.ts +++ /dev/null @@ -1,105 +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. - */ - -/** - * Mock an object of nested properties for an alert - */ -export const mockDataAsNestedObject = { - _id: '123', - '@timestamp': ['2023-01-01T01:01:01.000Z'], - agent: { - type: ['endpoint'], - }, - event: { - category: ['malware'], - kind: ['signal'], - }, - host: { - name: ['host-name'], - }, - kibana: { - alert: { - rule: { - name: ['rule-name'], - }, - severity: ['low'], - }, - }, - process: { - name: ['process-name'], - entity_id: ['process-entity_id'], - }, -}; - -/** - * Mock an array of fields for an alert - */ -export const mockDataFormattedForFieldBrowser = [ - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - values: ['rule-uuid'], - originalValue: ['rule-uuid'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.name', - values: ['rule-name'], - originalValue: ['rule-name'], - isObjectArray: false, - }, - { - category: 'base', - field: '@timestamp', - values: ['2023-01-01T01:01:01.000Z'], - originalValue: ['2023-01-01T01:01:01.000Z'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.description', - values: ['rule-description'], - originalValue: ['rule-description'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.ancestors.id', - values: ['ancestors-id'], - originalValue: ['ancestors-id'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.index', - values: ['rule-parameters-index'], - originalValue: ['rule-parameters-index'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.entity_id', - values: ['process-entity_id'], - originalValue: ['process-entity_id'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.category', - values: ['registry'], - originalValue: ['registry'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.type', - values: ['query'], - originalValue: ['query'], - isObjectArray: false, - }, -]; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_data_as_nested_object.ts b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_data_as_nested_object.ts new file mode 100644 index 0000000000000..e7b468ac31f34 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_data_as_nested_object.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; + +/** + * Mock an object of nested properties for an alert + */ +export const mockDataAsNestedObject: Ecs = { + _id: 'testId', + timestamp: '2023-01-01T01:01:01.000Z', + agent: { + type: ['endpoint'], + }, + event: { + category: ['malware'], + kind: ['signal'], + }, + host: { + name: ['host-name'], + }, + kibana: { + alert: { + rule: { + name: ['rule-name'], + parameters: {}, + uuid: [], + }, + severity: ['low'], + }, + }, + process: { + name: ['process-name'], + entity_id: ['process-entity_id'], + }, +}; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_data_formatted_for_field_browser.ts b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_data_formatted_for_field_browser.ts new file mode 100644 index 0000000000000..96bdc77817311 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_data_formatted_for_field_browser.ts @@ -0,0 +1,91 @@ +/* + * Copyright 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 { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; + +/** + * Mock an array of fields for an alert + */ +export const mockDataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ + { + category: 'base', + field: '@timestamp', + values: ['2023-01-01T01:01:01.000Z'], + originalValue: ['2023-01-01T01:01:01.000Z'], + isObjectArray: false, + }, + { + category: 'event', + field: 'event.category', + values: ['registry'], + originalValue: ['registry'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.ancestors.id', + values: ['ancestors-id'], + originalValue: ['ancestors-id'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.description', + values: ['rule-description'], + originalValue: ['rule-description'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.indices', + values: ['rule-indices'], + originalValue: ['rule-indices'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.name', + values: ['rule-name'], + originalValue: ['rule-name'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.parameters.index', + values: ['rule-parameters-index'], + originalValue: ['rule-parameters-index'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.type', + values: ['query'], + originalValue: ['query'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.uuid', + values: ['rule-uuid'], + originalValue: ['rule-uuid'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.workflow_status', + values: ['open'], + originalValue: ['open'], + isObjectArray: false, + }, + { + category: 'process', + field: 'process.entity_id', + values: ['process-entity_id'], + originalValue: ['process-entity_id'], + isObjectArray: false, + }, +]; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_get_fields_data.ts b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_get_fields_data.ts new file mode 100644 index 0000000000000..a0cdb76ae05bb --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_get_fields_data.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ALERT_REASON, + ALERT_RISK_SCORE, + ALERT_SEVERITY, + ALERT_SUPPRESSION_DOCS_COUNT, +} from '@kbn/rule-data-utils'; + +const mockFieldData: Record = { + [ALERT_SEVERITY]: ['low'], + [ALERT_RISK_SCORE]: ['0'], + 'host.name': ['host1'], + 'user.name': ['user1'], + [ALERT_REASON]: ['reason'], + [ALERT_SUPPRESSION_DOCS_COUNT]: ['1'], + '@timestamp': ['2023-01-01T00:00:00.000Z'], +}; + +/** + * Returns mocked data for field (mock this method: x-pack/plugins/security_solution/public/common/hooks/use_get_fields_data.ts) + * @param field + * @returns string[] + */ +export const mockGetFieldsData = (field: string): string[] => mockFieldData[field] ?? []; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_search_hit.ts b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_search_hit.ts new file mode 100644 index 0000000000000..f140629dabc80 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_search_hit.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SearchHit } from '../../../../common/search_strategy'; + +/** + * Mock the document result of the search for an alert + */ +export const mockSearchHit: SearchHit = { + _index: 'index', + _id: 'id', + fields: { + 'kibana.alert.rule.parameters': [ + { + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: '123', + reference: 'https://attack.mitre.org/tactics/123', + name: 'Tactic', + }, + technique: [ + { + id: '456', + reference: 'https://attack.mitre.org/techniques/456', + name: 'Technique', + }, + ], + }, + ], + }, + ], + }, +}; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/shared/test_ids.ts new file mode 100644 index 0000000000000..4c0d747afd588 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/test_ids.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const PREFIX = 'securitySolutionFlyout' as const; + +export const FLYOUT_ERROR_TEST_ID = `${PREFIX}Error` as const; +export const FLYOUT_LOADING_TEST_ID = `${PREFIX}Loading` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/translations.ts b/x-pack/plugins/security_solution/public/flyout/shared/translations.ts deleted file mode 100644 index b58e106cb26b1..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/shared/translations.ts +++ /dev/null @@ -1,50 +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 { i18n } from '@kbn/i18n'; - -export const ERROR_TITLE = (title: string) => - i18n.translate('xpack.securitySolution.flyout.errorTitle', { - values: { title }, - defaultMessage: 'Unable to display {title}', - }); - -export const ERROR_MESSAGE = (message: string) => - i18n.translate('xpack.securitySolution.flyout.errorMessage', { - values: { message }, - defaultMessage: 'There was an error displaying {message}', - }); - -export const CORRELATIONS_SUPPRESSED_ALERTS = (count: number) => - i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.suppressedAlerts', { - defaultMessage: 'suppressed {count, plural, =1 {alert} other {alerts}}', - values: { count }, - }); - -export const CORRELATIONS_ANCESTRY_ALERTS = (count: number) => - i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.ancestryAlerts', { - defaultMessage: '{count, plural, one {alert} other {alerts}} related by ancestry', - values: { count }, - }); - -export const CORRELATIONS_SAME_SOURCE_ALERTS = (count: number) => - i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.sourceAlerts', { - defaultMessage: '{count, plural, one {alert} other {alerts}} related by source event', - values: { count }, - }); - -export const CORRELATIONS_SESSION_ALERTS = (count: number) => - i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.sessionAlerts', { - defaultMessage: '{count, plural, one {alert} other {alerts}} related by session', - values: { count }, - }); - -export const CORRELATIONS_RELATED_CASES = (count: number) => - i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.relatedCases', { - defaultMessage: 'related {count, plural, one {case} other {cases}}', - values: { count }, - }); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/utils/fetch_data.ts b/x-pack/plugins/security_solution/public/flyout/shared/utils/fetch_data.ts index 08dfe620aff2b..65f92bc2ec1f7 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/utils/fetch_data.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/utils/fetch_data.ts @@ -15,10 +15,14 @@ export const createFetchData = async ( searchService: ISearchStart, req: IEsSearchRequest ): Promise => { + let rawResponse: TResponse; return new Promise((resolve, reject) => { searchService.search>(req).subscribe({ next: (response) => { - resolve(response.rawResponse); + rawResponse = response.rawResponse; + }, + complete: () => { + resolve(rawResponse); }, error: (error) => { reject(error); diff --git a/x-pack/plugins/security_solution/public/helpers.tsx b/x-pack/plugins/security_solution/public/helpers.tsx index 5a6e2c2c5108d..0dfc0878631cc 100644 --- a/x-pack/plugins/security_solution/public/helpers.tsx +++ b/x-pack/plugins/security_solution/public/helpers.tsx @@ -15,6 +15,7 @@ import type { Capabilities, CoreStart } from '@kbn/core/public'; import type { DocLinks } from '@kbn/doc-links'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { dataTableActions, TableId } from '@kbn/securitysolution-data-table'; +import { isObject } from 'lodash'; import { ALERTS_PATH, APP_UI_ID, @@ -157,7 +158,10 @@ export const getInspectResponse = ( response: StrategyResponseType | TimelineEqlResponse | undefined, prevResponse: InspectResponse ): InspectResponse => ({ - dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], + dsl: + isObject(response?.inspect) && response?.inspect.dsl + ? response.inspect.dsl + : prevResponse?.dsl || [], response: response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, }); diff --git a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx index d8d5abbacf94f..a957ca2f93764 100644 --- a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx @@ -10,8 +10,7 @@ import React, { memo, useMemo } from 'react'; import type { CommonProps } from '@elastic/eui'; import { EuiPageHeader, - EuiPageContent_Deprecated as EuiPageContent, - EuiPageContentBody_Deprecated as EuiPageContentBody, + EuiPageSection, EuiFlexGroup, EuiFlexItem, EuiTitle, @@ -77,16 +76,9 @@ export const AdministrationListPage: FC )} - - - {children} - + + {children} +
    ); } diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx index 66d97b0da5451..7d1895ba7d1b7 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx @@ -38,7 +38,7 @@ export const CommandInputUsage = memo>(({ >(({ (({ commandDef, errorMessage {title}, description }]} descriptionProps={additionalProps} titleProps={additionalProps} @@ -191,7 +191,7 @@ export const CommandUsage = memo(({ commandDef, errorMessage .euiDescriptionList__title { - width: 20%; - margin-top: ${({ theme: { eui } }) => eui.euiSizeS}; - } - - > .euiDescriptionList__description { - width: 80%; - margin-top: ${({ theme: { eui } }) => eui.euiSizeS}; - } - } - } `; export const Console = memo( diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx index ea91afe44afdd..04958e60b4de8 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx @@ -58,8 +58,8 @@ describe('When using the `ExecuteActionHostResponse` component', () => { it('should show execute context accordion as `closed`', async () => { render(); - expect(renderResult.getByTestId('test-executeResponseOutput-context').className).toEqual( - 'euiAccordion' + expect(renderResult.getByTestId('test-executeResponseOutput-context').className).not.toContain( + 'euiAccordion-isOpen' ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx index 357d0e566e328..2de29f621c7ec 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx @@ -252,7 +252,7 @@ export const EndpointStatusActionResult = memo< diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx index 849e9e484b008..7920bae8b22d7 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx @@ -36,7 +36,6 @@ const customDescriptionListCss = css` > .euiDescriptionList__title, > .euiDescriptionList__description { font-weight: ${(props) => props.theme.eui.euiFontWeightRegular}; - margin-top: ${(props) => props.theme.eui.euiSizeS}; } } `; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx index 2504392d5ef10..81e1e6b7843fc 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx @@ -7,19 +7,19 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { - EuiI18nNumber, + type CriteriaWithPagination, EuiAvatar, EuiBasicTable, EuiButtonIcon, EuiFacetButton, EuiHorizontalRule, - RIGHT_ALIGNMENT, + EuiI18nNumber, EuiScreenReaderOnly, + EuiSkeletonText, EuiText, EuiToolTip, type HorizontalAlignment, - type CriteriaWithPagination, - EuiSkeletonText, + RIGHT_ALIGNMENT, } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -30,20 +30,19 @@ import { SecuritySolutionLinkAnchor } from '../../../../common/components/links' import type { ActionListApiResponse } from '../../../../../common/endpoint/types'; import type { EndpointActionListRequestQuery } from '../../../../../common/api/endpoint'; import { FormattedDate } from '../../../../common/components/formatted_date'; -import { TABLE_COLUMN_NAMES, UX_MESSAGES, ARIA_LABELS } from '../translations'; +import { ARIA_LABELS, TABLE_COLUMN_NAMES, UX_MESSAGES } from '../translations'; import { getActionStatus, getUiCommand } from './hooks'; import { getEmptyValue } from '../../../../common/components/empty_value'; -import { StatusBadge } from './status_badge'; +import { ResponseActionStatusBadge } from './response_action_status_badge'; import { ActionsLogExpandedTray } from './action_log_expanded_tray'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants'; -import { useActionHistoryUrlParams } from './use_action_history_url_params'; import { useUrlPagination } from '../../../hooks/use_url_pagination'; const emptyValue = getEmptyValue(); // Truncated usernames -const StyledFacetButton = euiStyled(EuiFacetButton)` +const StyledFacetButton = euiStyled(EuiFacetButton).attrs({ title: undefined })` .euiText { margin-top: 0.38rem; overflow-y: visible !important; @@ -129,6 +128,10 @@ const getResponseActionListTableColumns = ({ - ( }) => { const getTestId = useTestIdGenerator(dataTestSubj); const { pagination: paginationFromUrlParams } = useUrlPagination(); - const { withOutputs: withOutputsFromUrl } = useActionHistoryUrlParams(); const [expandedRowMap, setExpandedRowMap] = useState({}); - const actionIdsWithOpenTrays = useMemo((): string[] => { - // get the list of action ids from URL params on the history page - if (!isFlyout) { - return withOutputsFromUrl ?? []; - } - // get the list of action ids form the query params for flyout view - return queryParams.withOutputs - ? typeof queryParams.withOutputs === 'string' - ? [queryParams.withOutputs] - : queryParams.withOutputs - : []; - }, [isFlyout, queryParams.withOutputs, withOutputsFromUrl]); + const actionIdsWithOpenTrays = useMemo( + (): string[] => + // get the list of action ids from the query params for flyout view + queryParams.withOutputs + ? typeof queryParams.withOutputs === 'string' + ? [queryParams.withOutputs] + : queryParams.withOutputs + : [], + [queryParams.withOutputs] + ); const redoOpenTrays = useCallback(() => { if (actionIdsWithOpenTrays.length && items.length) { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx index ce99e3cec70d8..285006ad31cf3 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useState, useEffect } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import type { DurationRange, OnRefreshChangeProps, @@ -17,14 +17,14 @@ import type { ResponseActionStatus, } from '../../../../../common/endpoint/service/response_actions/constants'; import { - RESPONSE_ACTION_STATUS, RESPONSE_ACTION_API_COMMANDS_NAMES, + RESPONSE_ACTION_STATUS, RESPONSE_ACTION_TYPE, } from '../../../../../common/endpoint/service/response_actions/constants'; import type { DateRangePickerValues } from './actions_log_date_range_picker'; import type { FILTER_NAMES } from '../translations'; import { FILTER_TYPE_OPTIONS, UX_MESSAGES } from '../translations'; -import { StatusBadge } from './status_badge'; +import { ResponseActionStatusBadge } from './response_action_status_badge'; import { useActionHistoryUrlParams } from './use_action_history_url_params'; import { useGetEndpointsList } from '../../../hooks/endpoint/use_get_endpoints_list'; @@ -216,8 +216,8 @@ export const useActionsLogFilter = ({ selectedAgentIds: selectedAgentIdsFromUrl, }); - // track state of selected hosts via URL - // when page is loaded via selected hosts on URL + // track the state of selected hosts via URL + // when the page is loaded via selected hosts on URL const [areHostsSelectedOnMount, setAreHostsSelectedOnMount] = useState(false); useEffect(() => { if (selectedAgentIdsFromUrl && selectedAgentIdsFromUrl.length > 0) { @@ -240,7 +240,7 @@ export const useActionsLogFilter = ({ ? RESPONSE_ACTION_STATUS.map((statusName) => ({ key: statusName, label: ( - { + return ( + // We've a EuiTooltip that wraps this component, + // Thus we don't need to add a title tooltip as well. + + {status} + + ); + } +); + +ResponseActionStatusBadge.displayName = 'ResponseActionStatusBadge'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx deleted file mode 100644 index 046811160c57d..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React, { memo } from 'react'; -import { EuiBadge, type EuiBadgeProps } from '@elastic/eui'; - -export const StatusBadge = memo( - ({ - color, - status, - 'data-test-subj': dataTestSubj, - }: { - color: EuiBadgeProps['color']; - 'data-test-subj'?: string; - status: string; - }) => { - return ( - - {status} - - ); - } -); - -StatusBadge.displayName = 'StatusBadge'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx index 476a86a6561dd..1e835e34ac7ef 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx @@ -7,12 +7,13 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import { - createAppRootMockRenderer, type AppContextTestRender, + createAppRootMockRenderer, } from '../../../../common/mock/endpoint'; import { ResponseActionsLog } from '../response_actions_log'; import type { @@ -29,7 +30,6 @@ import { v4 as uuidv4 } from 'uuid'; import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../common/endpoint/service/response_actions/constants'; import { useUserPrivileges as _useUserPrivileges } from '../../../../common/components/user_privileges'; import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks'; -import { waitFor } from '@testing-library/react'; import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint/service/authz/mocks'; import { useGetEndpointActionList as _useGetEndpointActionList } from '../../../hooks/response_actions/use_get_endpoint_action_list'; import { OUTPUT_MESSAGES } from '../translations'; @@ -470,7 +470,7 @@ describe('Response actions history', () => { ); }); - it('should expand each row to show details', async () => { + it('should expand/collapse each row to show/hide details', async () => { render(); const { getAllByTestId, queryAllByTestId } = renderResult; @@ -961,8 +961,7 @@ describe('Response actions history', () => { const expandButtons = getAllByTestId(`${testPrefix}-expand-button`); expandButtons.map((button) => userEvent.click(button)); - const outputs = getAllByTestId(`${testPrefix}-details-tray-output`); - return outputs; + return getAllByTestId(`${testPrefix}-details-tray-output`); }; it.each(RESPONSE_ACTION_API_COMMANDS_NAMES)( diff --git a/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx b/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx index db25aea95f917..162b8a19be6c4 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx @@ -6,7 +6,7 @@ */ import React, { memo } from 'react'; -import { EuiFlexGroup, EuiPageTemplate_Deprecated as EuiPageTemplate } from '@elastic/eui'; +import { EuiFlexGroup, EuiPanel, EuiPageTemplate } from '@elastic/eui'; import styled from 'styled-components'; export const StyledEuiFlexGroup = styled(EuiFlexGroup)` @@ -22,8 +22,10 @@ export const ManagementEmptyStateWrapper = memo( 'data-test-subj'?: string; }) => { return ( - - {children} + + + {children} + ); } diff --git a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx index 3ea38e5ed2314..77b39c9689a46 100644 --- a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx +++ b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx @@ -24,11 +24,11 @@ const OverlayRootContainer = styled.div` position: fixed; overflow: hidden; - top: calc((${({ theme: { eui } }) => eui.euiHeaderHeightCompensation} * 2)); + top: var(--euiFixedHeadersOffset, 0); bottom: 0; right: 0; - height: calc(100% - ${({ theme: { eui } }) => eui.euiHeaderHeightCompensation} * 2); + height: calc(100% - var(--euiFixedHeadersOffset, 0)); width: 100%; z-index: ${({ theme: { eui } }) => eui.euiZFlyout}; @@ -89,17 +89,6 @@ const PageOverlayGlobalStyles = createGlobalStyle<{ theme: EuiTheme }>` body.${PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME} { ${FULL_SCREEN_CONTENT_OVERRIDES_CSS_STYLESHEET} } - - //------------------------------------------------------------------------------------------- - // Style overrides for when Page Overlay is displayed in serverless project - //------------------------------------------------------------------------------------------- - // With serverless, there is 1 less header displayed, thus the display of the page overlay - // need to be adjusted slightly so that it still display below the header - //------------------------------------------------------------------------------------------- - body.kbnBody.kbnBody--projectLayout:not(.${PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME}) .${PAGE_OVERLAY_CSS_CLASSNAME} { - top: ${({ theme: { eui } }) => eui.euiHeaderHeightCompensation}; - height: calc(100% - (${({ theme: { eui } }) => eui.euiHeaderHeightCompensation})); - } `; const setDocumentBodyOverlayIsVisible = () => { @@ -264,8 +253,6 @@ export const PageOverlay = memo( useEffect(() => { if ( isMounted() && - // @ts-expect-error ts upgrade v4.7.4 - onHide && hideOnUrlPathnameChange && !isHidden && openedOnPathName && diff --git a/x-pack/plugins/security_solution/public/management/cypress.config.ts b/x-pack/plugins/security_solution/public/management/cypress.config.ts deleted file mode 100644 index b02724cb8eec8..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress.config.ts +++ /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 { defineCypressConfig } from '@kbn/cypress-config'; -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { dataLoaders } from './cypress/support/data_loaders'; - -export default defineCypressConfig({ - reporter: '../../../../node_modules/cypress-multi-reporters', - reporterOptions: { - configFile: './public/management/reporter_config.json', - }, - - defaultCommandTimeout: 60000, - execTimeout: 120000, - pageLoadTimeout: 12000, - - retries: { - runMode: 1, - openMode: 0, - }, - - screenshotsFolder: - '../../../target/kibana-security-solution/public/management/cypress/screenshots', - trashAssetsBeforeRuns: false, - video: false, - viewportHeight: 900, - viewportWidth: 1440, - experimentalStudio: true, - - env: { - KIBANA_URL: 'http://localhost:5601', - ELASTICSEARCH_URL: 'http://localhost:9200', - FLEET_SERVER_URL: 'https://localhost:8220', - // Username/password used for both elastic and kibana - KIBANA_USERNAME: 'elastic', - KIBANA_PASSWORD: 'changeme', - ELASTICSEARCH_USERNAME: 'system_indices_superuser', - ELASTICSEARCH_PASSWORD: 'changeme', - }, - - e2e: { - // baseUrl: To override, set Env. variable `CYPRESS_BASE_URL` - baseUrl: 'http://localhost:5601', - supportFile: 'public/management/cypress/support/e2e.ts', - specPattern: 'public/management/cypress/e2e/mocked_data/', - experimentalRunAllSpecs: true, - setupNodeEvents: (on, config) => { - return dataLoaders(on, config); - }, - }, -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/.eslintrc.json b/x-pack/plugins/security_solution/public/management/cypress/.eslintrc.json new file mode 100644 index 0000000000000..22a4d052afdc5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "plugins": ["cypress"], + "extends": [ + "plugin:cypress/recommended" + ], + "env": { + "cypress/globals": true + }, + "rules": { + "cypress/no-force": "warn", + "import/no-extraneous-dependencies": "off" + } +} diff --git a/x-pack/plugins/security_solution/public/management/cypress/README.md b/x-pack/plugins/security_solution/public/management/cypress/README.md index 70db70573a415..65af201662c48 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/README.md +++ b/x-pack/plugins/security_solution/public/management/cypress/README.md @@ -29,6 +29,16 @@ If you also want to run the tests against real endpoints as on the CI pipeline, See [running interactive tests on real endpoint with vagrant](#cypress-interactive-with-real-endpoints-using-vagrant) for more information. +## Adding new tests - tagging for ESS vs Serverless + +Similarly to Security Solution cypress tests, we use tags in order to select which tests we want to execute on which environment: + +- `@serverless` includes a test in the Serverless test suite. You need to explicitly add this tag to any test you want to run against a Serverless environment. +- `@ess` includes a test in the normal, non-Serverless test suite. You need to explicitly add this tag to any test you want to run against a non-Serverless environment. +- `@brokenInServerless` excludes a test from the Serverless test suite (even if it's tagged as `@serverless`). Indicates that a test should run in Serverless, but currently is broken. + +Important: if you don't provide any tag, your test won't be executed. + ## Running the tests There are currently three ways to run the tests, comprised of two execution modes and two target environments, which @@ -102,14 +112,11 @@ failures locally, etc. # bootstrap kibana from the project root and build the plugins/assets that cypress will execute against yarn kbn bootstrap && node scripts/build_kibana_platform_plugins -# launch the cypress test runner -cd x-pack/plugins/security_solution -yarn cypress:dw:run-as-ci - -# or +# launch the cypress test runner against ESS +yarn --cwd x-pack/plugins/security_solution cypress:dw:run -# launch without changing directory from kibana/ -yarn --cwd x-pack/plugins/security_solution cypress:dw:run-as-ci +# or against Serverless +yarn --cwd x-pack/plugins/security_solution cypress:dw:serverless:run ``` #### Cypress @@ -120,66 +127,11 @@ This is the preferred mode for developing new tests against mocked data # bootstrap kibana from the project root and build the plugins/assets that cypress will execute against yarn kbn bootstrap && node scripts/build_kibana_platform_plugins -# launch the cypress test runner -cd x-pack/plugins/security_solution -yarn cypress:dw:open - -# or - -# launch without changing directory from kibana/ +# launch the cypress test runner against ESS yarn --cwd x-pack/plugins/security_solution cypress:dw:open -``` - -For developing/debugging tests against real endpoint please use: - -Endpoint tests require [Multipass](https://multipass.run/) to be installed on your machine. -```shell -# bootstrap kibana from the project root and build the plugins/assets that cypress will execute against -yarn kbn bootstrap && node scripts/build_kibana_platform_plugins - -# launch the cypress test runner with real endpoint -cd x-pack/plugins/security_solution -yarn cypress:dw:endpoint:open - -# or - -# launch without changing directory from kibana/ -yarn --cwd x-pack/plugins/security_solution cypress:dw:endpoint:open -``` - -#### Cypress (interactive) with real Endpoints using Vagrant - -```shell -# bootstrap kibana from the project root and build the plugins/assets that cypress will execute against -yarn kbn bootstrap && node scripts/build_kibana_platform_plugins - -# launch the cypress test runner with real endpoint -cd x-pack/plugins/security_solution -export CI=true -yarn cypress:dw:endpoint:open -```` - -Note that you can select the browser you want to use on the top right side of the interactive runner. - -#### Cypress against REAL Endpoint + Headless (Chrome) - -This requires some additional setup as mentioned in the [pre-requisites](#pre-requisites) section. - -Endpoint tests require [Multipass](https://multipass.run/) to be installed on your machine. - -```shell -# bootstrap kibana from the project root and build the plugins/assets that cypress will execute against -yarn kbn bootstrap && node scripts/build_kibana_platform_plugins - -# launch the cypress test runner with real endpoint -cd x-pack/plugins/security_solution -yarn cypress:dw:endpoint:run - -# or - -# launch without changing directory from kibana/ -yarn --cwd x-pack/plugins/security_solution cypress:dw:endpoint:run +# or against Serverless +yarn --cwd x-pack/plugins/security_solution cypress:dw:serverless:open ``` ## Folder Structure @@ -188,15 +140,6 @@ yarn --cwd x-pack/plugins/security_solution cypress:dw:endpoint:run Contains all the tests. Within it are two sub-folders: -#### cypress/endpoint - -Contains all the tests that are executed against real endpoints. - -#### cypress/mocked_data - -Contains all the tests that are executed against mocked endpoint and run on CI. If you want to add tests that run on CI -then this is where you should add those. - ### integration/ Cypress convention. Contains the specs that are going to be executed. @@ -214,7 +157,6 @@ Directory also holds Cypress Plugins that are then initialized via `setupNodeEve ### screens/ Contains the elements we want to interact with within our tests. - Each file inside the screens folder represents a screen in our application. ### tasks/ diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress.config.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress.config.ts new file mode 100644 index 0000000000000..f3725638111b6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/cypress.config.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 { defineCypressConfig } from '@kbn/cypress-config'; +import { getCypressBaseConfig } from './cypress_base.config'; + +export default defineCypressConfig( + getCypressBaseConfig({ + env: { + grepTags: '@ess', + }, + }) +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress_base.config.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress_base.config.ts new file mode 100644 index 0000000000000..e7624ed670f50 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/cypress_base.config.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { merge } from 'lodash'; +import { dataLoaders, dataLoadersForRealEndpoints } from './support/data_loaders'; +import { responseActionTasks } from './support/response_actions'; + +export const getCypressBaseConfig = ( + overrides: Cypress.ConfigOptions = {} +): Cypress.ConfigOptions => { + return merge( + { + reporter: '../../../../node_modules/cypress-multi-reporters', + reporterOptions: { + configFile: './public/management/reporter_config.json', + }, + + defaultCommandTimeout: 60000, + execTimeout: 120000, + pageLoadTimeout: 12000, + + retries: { + runMode: 1, + openMode: 0, + }, + + screenshotsFolder: + '../../../target/kibana-security-solution/public/management/cypress/screenshots', + trashAssetsBeforeRuns: false, + video: false, + viewportHeight: 900, + viewportWidth: 1440, + experimentalStudio: true, + + env: { + 'cypress-react-selector': { + root: '#security-solution-app', + }, + KIBANA_URL: 'http://localhost:5601', + ELASTICSEARCH_URL: 'http://localhost:9200', + FLEET_SERVER_URL: 'https://localhost:8220', + KIBANA_USERNAME: 'system_indices_superuser', + KIBANA_PASSWORD: 'changeme', + ELASTICSEARCH_USERNAME: 'system_indices_superuser', + ELASTICSEARCH_PASSWORD: 'changeme', + + // grep related configs + grepFilterSpecs: true, + grepOmitFiltered: true, + }, + + e2e: { + // baseUrl: To override, set Env. variable `CYPRESS_BASE_URL` + baseUrl: 'http://localhost:5601', + supportFile: 'public/management/cypress/support/e2e.ts', + specPattern: 'public/management/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', + experimentalRunAllSpecs: true, + setupNodeEvents: (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { + dataLoaders(on, config); + + // skip dataLoadersForRealEndpoints() if running in serverless + // https://github.com/elastic/security-team/issues/7467 + // Once we are able to run Fleet server in serverless mode (see: https://github.com/elastic/kibana/pull/166183) + // this `if()` statement needs to be removed and `dataLoadersForRealEndpoints()` should + // just be called without having any checks around it. + if (!config.env.IS_SERVERLESS) { + // Data loaders specific to "real" Endpoint testing + dataLoadersForRealEndpoints(on, config); + } + + responseActionTasks(on, config); + + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('@cypress/grep/src/plugin')(config); + + return config; + }, + }, + }, + overrides + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress_serverless.config.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress_serverless.config.ts new file mode 100644 index 0000000000000..35dda2bd68501 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/cypress_serverless.config.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { defineCypressConfig } from '@kbn/cypress-config'; +import { getCypressBaseConfig } from './cypress_base.config'; + +// eslint-disable-next-line import/no-default-export +export default defineCypressConfig( + getCypressBaseConfig({ + env: { + IS_SERVERLESS: true, + + grepTags: '@serverless --@brokenInServerless', + }, + }) +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts new file mode 100644 index 0000000000000..39ebae1825365 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts @@ -0,0 +1,228 @@ +/* + * Copyright 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 { getEndpointSecurityPolicyManager } from '../../../../../scripts/endpoint/common/roles_users/endpoint_security_policy_manager'; +import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; +import { visitPolicyDetailsPage } from '../../screens/policy_details'; +import { + createPerPolicyArtifact, + createArtifactList, + removeAllArtifacts, + removeExceptionsList, + yieldFirstPolicyID, +} from '../../tasks/artifacts'; +import { loadEndpointDataForEventFiltersIfNeeded } from '../../tasks/load_endpoint_data'; +import { + getRoleWithArtifactReadPrivilege, + login, + loginWithCustomRole, + loginWithRole, + ROLE, +} from '../../tasks/login'; +import { performUserActions } from '../../tasks/perform_user_actions'; + +const loginWithPrivilegeAll = () => { + loginWithRole(ROLE.endpoint_security_policy_manager); +}; + +const loginWithPrivilegeRead = (privilegePrefix: string) => { + const roleWithArtifactReadPrivilege = getRoleWithArtifactReadPrivilege(privilegePrefix); + loginWithCustomRole('roleWithArtifactReadPrivilege', roleWithArtifactReadPrivilege); +}; + +const loginWithPrivilegeNone = (privilegePrefix: string) => { + const roleWithoutArtifactPrivilege = getRoleWithoutArtifactPrivilege(privilegePrefix); + loginWithCustomRole('roleWithoutArtifactPrivilege', roleWithoutArtifactPrivilege); +}; + +const getRoleWithoutArtifactPrivilege = (privilegePrefix: string) => { + const endpointSecurityPolicyManagerRole = getEndpointSecurityPolicyManager(); + + return { + ...endpointSecurityPolicyManagerRole, + kibana: [ + { + ...endpointSecurityPolicyManagerRole.kibana[0], + feature: { + ...endpointSecurityPolicyManagerRole.kibana[0].feature, + siem: endpointSecurityPolicyManagerRole.kibana[0].feature.siem.filter( + (privilege) => privilege !== `${privilegePrefix}all` + ), + }, + }, + ], + }; +}; + +const visitArtifactTab = (tabId: string) => { + visitPolicyDetailsPage(); + cy.get(`#${tabId}`).click(); +}; + +describe( + 'Artifact tabs in Policy Details page', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, // broken due to disabled Native Role Management + () => { + before(() => { + login(); + loadEndpointDataForEventFiltersIfNeeded(); + }); + + after(() => { + login(); + removeAllArtifacts(); + }); + + for (const testData of getArtifactsListTestsData()) { + describe(`${testData.title} tab`, () => { + beforeEach(() => { + login(); + removeExceptionsList(testData.createRequestBody.list_id); + }); + + it(`[NONE] User cannot see the tab for ${testData.title}`, () => { + loginWithPrivilegeNone(testData.privilegePrefix); + visitPolicyDetailsPage(); + + cy.get(`#${testData.tabId}`).should('not.exist'); + }); + + context(`Given there are no ${testData.title} entries`, () => { + it(`[READ] User CANNOT add ${testData.title} artifact`, () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); + + cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist'); + + cy.getByTestSubj('unexisting-manage-artifacts-button').should('not.exist'); + }); + + it(`[ALL] User can add ${testData.title} artifact`, () => { + loginWithPrivilegeAll(); + visitArtifactTab(testData.tabId); + + cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist'); + + cy.getByTestSubj('unexisting-manage-artifacts-button').should('exist').click(); + + const { formActions, checkResults } = testData.create; + + performUserActions(formActions); + + // Add a per policy artifact - but not assign it to any policy + cy.get('[data-test-subj$="-perPolicy"]').click(); // test-subjects are generated in different formats, but all ends with -perPolicy + cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); + + // Check new artifact is in the list + for (const checkResult of checkResults) { + cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); + } + + cy.getByTestSubj('policyDetailsPage').should('not.exist'); + cy.getByTestSubj('backToOrigin').contains(/^Back to .+ policy$/); + + cy.getByTestSubj('backToOrigin').click(); + cy.getByTestSubj('policyDetailsPage').should('exist'); + }); + }); + + context(`Given there are no assigned ${testData.title} entries`, () => { + beforeEach(() => { + login(); + createArtifactList(testData.createRequestBody.list_id); + createPerPolicyArtifact(testData.artifactName, testData.createRequestBody); + }); + + it(`[READ] User CANNOT Manage or Assign ${testData.title} artifacts`, () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); + + cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist'); + + cy.getByTestSubj('unassigned-manage-artifacts-button').should('not.exist'); + cy.getByTestSubj('unassigned-assign-artifacts-button').should('not.exist'); + }); + + it(`[ALL] User can Manage and Assign ${testData.title} artifacts`, () => { + loginWithPrivilegeAll(); + visitArtifactTab(testData.tabId); + + cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist'); + + // Manage artifacts + cy.getByTestSubj('unassigned-manage-artifacts-button').should('exist').click(); + cy.location('pathname').should( + 'equal', + `/app/security/administration/${testData.urlPath}` + ); + cy.getByTestSubj('backToOrigin').click(); + + // Assign artifacts + cy.getByTestSubj('unassigned-assign-artifacts-button').should('exist').click(); + + cy.getByTestSubj('artifacts-assign-flyout').should('exist'); + cy.getByTestSubj('artifacts-assign-confirm-button').should('be.disabled'); + + cy.getByTestSubj(`${testData.artifactName}_checkbox`).click(); + cy.getByTestSubj('artifacts-assign-confirm-button').click(); + }); + }); + + context(`Given there are assigned ${testData.title} entries`, () => { + beforeEach(() => { + login(); + createArtifactList(testData.createRequestBody.list_id); + yieldFirstPolicyID().then((policyID) => { + createPerPolicyArtifact(testData.artifactName, testData.createRequestBody, policyID); + }); + }); + + it(`[READ] User can see ${testData.title} artifacts but CANNOT assign or remove from policy`, () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); + + // List of artifacts + cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1); + cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains( + testData.artifactName + ); + + // Cannot assign artifacts + cy.getByTestSubj('artifacts-assign-button').should('not.exist'); + + // Cannot remove from policy + cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click(); + cy.getByTestSubj('remove-from-policy-action').should('not.exist'); + }); + + it(`[ALL] User can see ${testData.title} artifacts and can assign or remove artifacts from policy`, () => { + loginWithPrivilegeAll(); + visitArtifactTab(testData.tabId); + + // List of artifacts + cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1); + cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains( + testData.artifactName + ); + + // Assign artifacts + cy.getByTestSubj('artifacts-assign-button').should('exist').click(); + cy.getByTestSubj('artifacts-assign-flyout').should('exist'); + cy.getByTestSubj('artifacts-assign-cancel-button').click(); + + // Remove from policy + cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click(); + cy.getByTestSubj('remove-from-policy-action').click(); + cy.getByTestSubj('confirmModalConfirmButton').click(); + + cy.contains('Successfully removed'); + }); + }); + }); + } + } +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts.cy.ts new file mode 100644 index 0000000000000..e854653e74e0f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts.cy.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { recurse } from 'cypress-recurse'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { HOST_METADATA_LIST_ROUTE } from '../../../../../common/endpoint/constants'; +import type { MetadataListResponse, PolicyData } from '../../../../../common/endpoint/types'; +import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; +import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; +import { removeAllArtifacts } from '../../tasks/artifacts'; +import { login } from '../../tasks/login'; +import { performUserActions } from '../../tasks/perform_user_actions'; +import { request, loadPage } from '../../tasks/common'; +import { + createAgentPolicyTask, + getEndpointIntegrationVersion, + yieldEndpointPolicyRevision, +} from '../../tasks/fleet'; +import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; +import { createEndpointHost } from '../../tasks/create_endpoint_host'; +import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; +import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; + +const yieldAppliedEndpointRevision = (): Cypress.Chainable => + request({ + method: 'GET', + url: HOST_METADATA_LIST_ROUTE, + }).then(({ body }) => { + expect(body.data.length).is.lte(2); // during update it can be temporary zero + return Number(body.data?.[0]?.metadata.Endpoint.policy.applied.endpoint_policy_version) ?? -1; + }); + +const parseRevNumber = (revString: string) => Number(revString.match(/\d+/)?.[0]); + +describe('Artifact pages', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }) + ); + + login(); + removeAllArtifacts(); + + // wait for ManifestManager to pick up artifact changes that happened either here + // or in a previous test suite `after` + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(6000); // packagerTaskInterval + 1s + + yieldEndpointPolicyRevision().then((actualEndpointPolicyRevision) => { + const hasReachedActualRevision = (revision: number) => + revision === actualEndpointPolicyRevision; + + // need to wait until revision is bumped to ensure test success + recurse(yieldAppliedEndpointRevision, hasReachedActualRevision, { delay: 1500 }); + }); + }); + + beforeEach(() => { + login(); + loadPage(APP_ENDPOINTS_PATH); + }); + + after(() => { + removeAllArtifacts(); + + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + for (const testData of getArtifactsListTestsData()) { + describe(`${testData.title}`, () => { + it(`should update Endpoint Policy on Endpoint when adding ${testData.artifactName}`, () => { + cy.getByTestSubj('policyListRevNo') + .first() + .invoke('text') + .then(parseRevNumber) + .then((initialRevisionNumber) => { + loadPage(`/app/security/administration/${testData.urlPath}`); + + cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).click(); + performUserActions(testData.create.formActions); + cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); + + // Check new artifact is in the list + for (const checkResult of testData.create.checkResults) { + cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); + } + + loadPage(APP_ENDPOINTS_PATH); + + // depends on the 10s auto refresh + cy.getByTestSubj('policyListRevNo') + .first() + .should(($div) => { + const revisionNumber = parseRevNumber($div.text()); + expect(revisionNumber).to.eq(initialRevisionNumber + 1); + }); + }); + }); + }); + } +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts new file mode 100644 index 0000000000000..8f575282ad3f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + getRoleWithArtifactReadPrivilege, + login, + loginWithCustomRole, + loginWithRole, + ROLE, +} from '../../tasks/login'; +import { loadPage } from '../../tasks/common'; + +import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; +import { removeAllArtifacts } from '../../tasks/artifacts'; +import { performUserActions } from '../../tasks/perform_user_actions'; +import { loadEndpointDataForEventFiltersIfNeeded } from '../../tasks/load_endpoint_data'; + +const loginWithWriteAccess = (url: string) => { + loginWithRole(ROLE.endpoint_security_policy_manager); + loadPage(url); +}; + +const loginWithReadAccess = (privilegePrefix: string, url: string) => { + const roleWithArtifactReadPrivilege = getRoleWithArtifactReadPrivilege(privilegePrefix); + loginWithCustomRole('roleWithArtifactReadPrivilege', roleWithArtifactReadPrivilege); + loadPage(url); +}; + +const loginWithoutAccess = (url: string) => { + loginWithRole(ROLE.t1_analyst); + loadPage(url); +}; + +describe('Artifacts pages', { tags: '@ess' }, () => { + before(() => { + login(); + loadEndpointDataForEventFiltersIfNeeded(); + // Clean artifacts data + removeAllArtifacts(); + }); + + after(() => { + // Clean artifacts data + removeAllArtifacts(); + }); + + for (const testData of getArtifactsListTestsData()) { + describe(`When on the ${testData.title} entries list`, () => { + it(`no access - should show no privileges callout`, () => { + loginWithoutAccess(`/app/security/administration/${testData.urlPath}`); + cy.getByTestSubj('noPrivilegesPage').should('exist'); + cy.getByTestSubj('empty-page-feature-action').should('exist'); + cy.getByTestSubj(testData.emptyState).should('not.exist'); + cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist'); + }); + + it(`read - should show empty state page if there is no ${testData.title} entry and the add button does not exist`, () => { + loginWithReadAccess( + testData.privilegePrefix, + `/app/security/administration/${testData.urlPath}` + ); + cy.getByTestSubj(testData.emptyState).should('exist'); + cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist'); + }); + + it(`write - should show empty state page if there is no ${testData.title} entry and the add button exists`, () => { + loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); + cy.getByTestSubj(testData.emptyState).should('exist'); + cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('exist'); + }); + + it(`write - should create new ${testData.title} entry`, () => { + loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); + // Opens add flyout + cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).click(); + + performUserActions(testData.create.formActions); + + // Submit create artifact form + cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); + + // Check new artifact is in the list + for (const checkResult of testData.create.checkResults) { + cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); + } + + // Title is shown after adding an item + cy.getByTestSubj('header-page-title').contains(testData.title); + }); + + it(`read - should not be able to update/delete an existing ${testData.title} entry`, () => { + loginWithReadAccess( + testData.privilegePrefix, + `/app/security/administration/${testData.urlPath}` + ); + cy.getByTestSubj('header-page-title').contains(testData.title); + cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).should('not.exist'); + cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).should('not.exist'); + cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).should('not.exist'); + }); + + it(`read - should not be able to create a new ${testData.title} entry`, () => { + loginWithReadAccess( + testData.privilegePrefix, + `/app/security/administration/${testData.urlPath}` + ); + cy.getByTestSubj('header-page-title').contains(testData.title); + cy.getByTestSubj(`${testData.pagePrefix}-pageAddButton`).should('not.exist'); + }); + + it(`write - should be able to update an existing ${testData.title} entry`, () => { + loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); + // Opens edit flyout + cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).click(); + cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).click(); + + performUserActions(testData.update.formActions); + + // Submit edit artifact form + cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); + + for (const checkResult of testData.create.checkResults) { + cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); + } + + // Title still shown after editing an item + cy.getByTestSubj('header-page-title').contains(testData.title); + }); + + it(`write - should be able to delete the existing ${testData.title} entry`, () => { + loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); + // Remove it + cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).click(); + cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).click(); + cy.getByTestSubj(`${testData.pagePrefix}-deleteModal-submitButton`).click(); + // No card visible after removing it + cy.getByTestSubj(testData.delete.card).should('not.exist'); + // Empty state is displayed after removing last item + cy.getByTestSubj(testData.emptyState).should('exist'); + }); + }); + } +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts new file mode 100644 index 0000000000000..a671b1c855f84 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PolicyData } from '../../../../../common/endpoint/types'; +import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; +import { closeAllToasts } from '../../tasks/toasts'; +import { toggleRuleOffAndOn, visitRuleAlerts } from '../../tasks/isolate'; +import { cleanupRule, loadRule } from '../../tasks/api_fixtures'; +import { login } from '../../tasks/login'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; +import { changeAlertsFilter } from '../../tasks/alerts'; +import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; +import { createEndpointHost } from '../../tasks/create_endpoint_host'; +import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; +import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; + +describe( + 'Automated Response Actions', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version, 'automated_response_actions').then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }) + ); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + const hostname = new URL(Cypress.env('FLEET_SERVER_URL')).port; + const fleetHostname = `dev-fleet-server.${hostname}`; + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + describe('From alerts', () => { + let ruleId: string; + let ruleName: string; + + before(() => { + loadRule().then((data) => { + ruleId = data.id; + ruleName = data.name; + }); + }); + + after(() => { + if (ruleId) { + cleanupRule(ruleId); + } + }); + + it.skip('should have generated endpoint and rule', () => { + loadPage(APP_ENDPOINTS_PATH); + cy.contains(createdHost.hostname).should('exist'); + + toggleRuleOffAndOn(ruleName); + + visitRuleAlerts(ruleName); + closeAllToasts(); + + changeAlertsFilter('event.category: "file"'); + cy.getByTestSubj('expand-event').first().click(); + cy.getByTestSubj('responseActionsViewTab').click(); + cy.getByTestSubj('response-actions-notification').should('not.have.text', '0'); + + cy.getByTestSubj(`response-results-${createdHost.hostname}-details-tray`) + .should('contain', 'isolate completed successfully') + .and('contain', createdHost.hostname); + + cy.getByTestSubj(`response-results-${fleetHostname}-details-tray`) + .should('contain', 'The host does not have Elastic Defend integration installed') + .and('contain', 'dev-fleet-server'); + }); + }); + } +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/form.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts similarity index 96% rename from x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/form.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts index 2e82ff002e36b..84f6903c35a9b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/form.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts @@ -13,12 +13,12 @@ import { tryAddingDisabledResponseAction, validateAvailableCommands, visitRuleActions, -} from '../../../tasks/response_actions'; -import { cleanupRule, generateRandomStringName, loadRule } from '../../../tasks/api_fixtures'; -import { RESPONSE_ACTION_TYPES } from '../../../../../../common/api/detection_engine'; -import { loginWithRole, ROLE } from '../../../tasks/login'; +} from '../../tasks/response_actions'; +import { cleanupRule, generateRandomStringName, loadRule } from '../../tasks/api_fixtures'; +import { RESPONSE_ACTION_TYPES } from '../../../../../common/api/detection_engine'; +import { loginWithRole, ROLE } from '../../tasks/login'; -describe('Form', () => { +describe('Form', { tags: '@ess' }, () => { describe('User with no access can not create an endpoint response action', () => { before(() => { loginWithRole(ROLE.endpoint_response_actions_no_access); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/history_log.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/history_log.cy.ts similarity index 88% rename from x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/history_log.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/history_log.cy.ts index 388f6eb81dfcb..8e33a98fa5d79 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/history_log.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/history_log.cy.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { generateRandomStringName } from '../../../tasks/utils'; -import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; -import type { ReturnTypeFromChainable } from '../../../types'; -import { indexEndpointRuleAlerts } from '../../../tasks/index_endpoint_rule_alerts'; +import { generateRandomStringName } from '../../tasks/utils'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import type { ReturnTypeFromChainable } from '../../types'; +import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; -import { login, ROLE } from '../../../tasks/login'; +import { login, ROLE } from '../../tasks/login'; -describe('Response actions history page', () => { +describe('Response actions history page', { tags: '@ess' }, () => { let endpointData: ReturnTypeFromChainable | undefined; let endpointDataWithAutomated: ReturnTypeFromChainable | undefined; let alertData: ReturnTypeFromChainable | undefined; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts similarity index 77% rename from x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts index b046a067e260c..edbaa90d3200c 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts @@ -5,17 +5,17 @@ * 2.0. */ -import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common'; -import { APP_ALERTS_PATH } from '../../../../../../common/constants'; -import { closeAllToasts } from '../../../tasks/toasts'; -import { fillUpNewRule } from '../../../tasks/response_actions'; -import { login, loginWithRole, ROLE } from '../../../tasks/login'; -import { generateRandomStringName } from '../../../tasks/utils'; -import type { ReturnTypeFromChainable } from '../../../types'; -import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; -import { indexEndpointRuleAlerts } from '../../../tasks/index_endpoint_rule_alerts'; +import { disableExpandableFlyoutAdvancedSettings } from '../../tasks/common'; +import { APP_ALERTS_PATH } from '../../../../../common/constants'; +import { closeAllToasts } from '../../tasks/toasts'; +import { fillUpNewRule } from '../../tasks/response_actions'; +import { login, loginWithRole, ROLE } from '../../tasks/login'; +import { generateRandomStringName } from '../../tasks/utils'; +import type { ReturnTypeFromChainable } from '../../types'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; -describe('No License', { env: { ftrConfig: { license: 'basic' } } }, () => { +describe('No License', { tags: '@ess', env: { ftrConfig: { license: 'basic' } } }, () => { describe('User cannot use endpoint action in form', () => { const [ruleName, ruleDescription] = generateRandomStringName(2); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts similarity index 81% rename from x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts index 82fc29edb7b5e..2fe539e8ffe77 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts @@ -5,17 +5,17 @@ * 2.0. */ -import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common'; -import { generateRandomStringName } from '../../../tasks/utils'; -import { APP_ALERTS_PATH } from '../../../../../../common/constants'; -import { closeAllToasts } from '../../../tasks/toasts'; -import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; -import type { ReturnTypeFromChainable } from '../../../types'; -import { indexEndpointRuleAlerts } from '../../../tasks/index_endpoint_rule_alerts'; +import { disableExpandableFlyoutAdvancedSettings } from '../../tasks/common'; +import { generateRandomStringName } from '../../tasks/utils'; +import { APP_ALERTS_PATH } from '../../../../../common/constants'; +import { closeAllToasts } from '../../tasks/toasts'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import type { ReturnTypeFromChainable } from '../../types'; +import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; -import { login, ROLE } from '../../../tasks/login'; +import { login, ROLE } from '../../tasks/login'; -describe('Results', () => { +describe('Results', { tags: '@ess' }, () => { let endpointData: ReturnTypeFromChainable | undefined; let alertData: ReturnTypeFromChainable | undefined; const [endpointAgentId, endpointHostname] = generateRandomStringName(2); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/artifacts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/artifacts.cy.ts deleted file mode 100644 index 151e0f6bf233b..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/artifacts.cy.ts +++ /dev/null @@ -1,128 +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 { recurse } from 'cypress-recurse'; -import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { HOST_METADATA_LIST_ROUTE } from '../../../../../common/endpoint/constants'; -import type { MetadataListResponse, PolicyData } from '../../../../../common/endpoint/types'; -import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; -import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; -import { removeAllArtifacts } from '../../tasks/artifacts'; -import { login } from '../../tasks/login'; -import { performUserActions } from '../../tasks/perform_user_actions'; -import { request, loadPage } from '../../tasks/common'; -import { - createAgentPolicyTask, - getEndpointIntegrationVersion, - yieldEndpointPolicyRevision, -} from '../../tasks/fleet'; -import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; -import { createEndpointHost } from '../../tasks/create_endpoint_host'; -import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; - -const yieldAppliedEndpointRevision = (): Cypress.Chainable => - request({ - method: 'GET', - url: HOST_METADATA_LIST_ROUTE, - }).then(({ body }) => { - expect(body.data.length).is.lte(2); // during update it can be temporary zero - return Number(body.data?.[0]?.metadata.Endpoint.policy.applied.endpoint_policy_version) ?? -1; - }); - -const parseRevNumber = (revString: string) => Number(revString.match(/\d+/)?.[0]); - -describe('Artifact pages', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }) - ); - - login(); - removeAllArtifacts(); - - // wait for ManifestManager to pick up artifact changes that happened either here - // or in a previous test suite `after` - cy.wait(6000); // packagerTaskInterval + 1s - - yieldEndpointPolicyRevision().then((actualEndpointPolicyRevision) => { - const hasReachedActualRevision = (revision: number) => - revision === actualEndpointPolicyRevision; - - // need to wait until revision is bumped to ensure test success - recurse(yieldAppliedEndpointRevision, hasReachedActualRevision, { delay: 1500 }); - }); - }); - - beforeEach(() => { - login(); - loadPage(APP_ENDPOINTS_PATH); - }); - - after(() => { - removeAllArtifacts(); - - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - for (const testData of getArtifactsListTestsData()) { - describe(`${testData.title}`, () => { - it(`should update Endpoint Policy on Endpoint when adding ${testData.artifactName}`, () => { - cy.getByTestSubj('policyListRevNo') - .first() - .invoke('text') - .then(parseRevNumber) - .then((initialRevisionNumber) => { - loadPage(`/app/security/administration/${testData.urlPath}`); - - cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).click(); - performUserActions(testData.create.formActions); - cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); - - // Check new artifact is in the list - for (const checkResult of testData.create.checkResults) { - cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); - } - - loadPage(APP_ENDPOINTS_PATH); - - // depends on the 10s auto refresh - cy.getByTestSubj('policyListRevNo') - .first() - .should(($div) => { - const revisionNumber = parseRevNumber($div.text()); - expect(revisionNumber).to.eq(initialRevisionNumber + 1); - }); - }); - }); - }); - } -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts deleted file mode 100644 index 5bb5021a3229c..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts +++ /dev/null @@ -1,106 +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 { PolicyData } from '../../../../../common/endpoint/types'; -import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; -import { closeAllToasts } from '../../tasks/toasts'; -import { toggleRuleOffAndOn, visitRuleAlerts } from '../../tasks/isolate'; -import { cleanupRule, loadRule } from '../../tasks/api_fixtures'; -import { login } from '../../tasks/login'; -import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; -import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; -import { changeAlertsFilter } from '../../tasks/alerts'; -import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; -import { createEndpointHost } from '../../tasks/create_endpoint_host'; -import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; - -describe('Automated Response Actions', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => - createAgentPolicyTask(version, 'automated_response_actions').then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }) - ); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - const hostname = new URL(Cypress.env('FLEET_SERVER_URL')).port; - const fleetHostname = `dev-fleet-server.${hostname}`; - - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); - - describe('From alerts', () => { - let ruleId: string; - let ruleName: string; - - before(() => { - loadRule().then((data) => { - ruleId = data.id; - ruleName = data.name; - }); - }); - - after(() => { - if (ruleId) { - cleanupRule(ruleId); - } - }); - - it('should have generated endpoint and rule', () => { - loadPage(APP_ENDPOINTS_PATH); - cy.contains(createdHost.hostname).should('exist'); - - toggleRuleOffAndOn(ruleName); - - visitRuleAlerts(ruleName); - closeAllToasts(); - - changeAlertsFilter('event.category: "file"'); - cy.getByTestSubj('expand-event').first().click(); - cy.getByTestSubj('responseActionsViewTab').click(); - cy.getByTestSubj('response-actions-notification').should('not.have.text', '0'); - - cy.getByTestSubj(`response-results-${createdHost.hostname}-details-tray`) - .should('contain', 'isolate completed successfully') - .and('contain', createdHost.hostname); - - cy.getByTestSubj(`response-results-${fleetHostname}-details-tray`) - .should('contain', 'The host does not have Elastic Defend integration installed') - .and('contain', 'dev-fleet-server'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoint_alerts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoint_alerts.cy.ts deleted file mode 100644 index 5a33371754be5..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoint_alerts.cy.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -import { getAlertsTableRows, navigateToAlertsList } from '../../screens/alerts'; -import { waitForEndpointAlerts } from '../../tasks/alerts'; -import { request } from '../../tasks/common'; -import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; -import { createEndpointHost } from '../../tasks/create_endpoint_host'; -import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; -import type { PolicyData, ResponseActionApiResponse } from '../../../../../common/endpoint/types'; -import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; -import { login } from '../../tasks/login'; -import { EXECUTE_ROUTE } from '../../../../../common/endpoint/constants'; -import { waitForActionToComplete } from '../../tasks/response_actions'; - -describe('Endpoint generated alerts', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version, 'alerts test').then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }); - }); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - beforeEach(() => { - login(); - }); - - it('should create a Detection Engine alert from an endpoint alert', () => { - // Triggers a Malicious Behaviour alert on Linux system (`grep *` was added only to identify this specific alert) - const executeMaliciousCommand = `bash -c cat /dev/tcp/foo | grep ${Math.random() - .toString(16) - .substring(2)}`; - - // Send `execute` command that triggers malicious behaviour using the `execute` response action - request({ - method: 'POST', - url: EXECUTE_ROUTE, - body: { - endpoint_ids: [createdHost.agentId], - parameters: { - command: executeMaliciousCommand, - }, - }, - headers: { - 'Elastic-Api-Version': '2023-10-31', - }, - }) - .then((response) => waitForActionToComplete(response.body.data.id)) - .then(() => { - return waitForEndpointAlerts(createdHost.agentId, [ - { - term: { 'process.group_leader.args': executeMaliciousCommand }, - }, - ]); - }) - .then(() => { - return navigateToAlertsList( - `query=(language:kuery,query:'agent.id: "${createdHost.agentId}" ')` - ); - }); - - getAlertsTableRows().should('have.length.greaterThan', 0); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoints.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoints.cy.ts deleted file mode 100644 index d86fabde64a18..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoints.cy.ts +++ /dev/null @@ -1,166 +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 { Agent } from '@kbn/fleet-plugin/common'; -import type { PolicyData } from '../../../../../common/endpoint/types'; -import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; -import { - createAgentPolicyTask, - getAgentByHostName, - getEndpointIntegrationVersion, - reassignAgentPolicy, -} from '../../tasks/fleet'; -import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; -import { - AGENT_HOSTNAME_CELL, - TABLE_ROW_ACTIONS, - TABLE_ROW_ACTIONS_MENU, - AGENT_POLICY_CELL, -} from '../../screens/endpoints'; -import { - FLEET_REASSIGN_POLICY_MODAL, - FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON, -} from '../../screens/fleet'; -import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; -import { createEndpointHost } from '../../tasks/create_endpoint_host'; -import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; - -describe('Endpoints page', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }); - }); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - beforeEach(() => { - login(); - }); - - it('Shows endpoint on the list', () => { - loadPage(APP_ENDPOINTS_PATH); - cy.contains('Hosts running Elastic Defend').should('exist'); - cy.getByTestSubj(AGENT_HOSTNAME_CELL) - .contains(createdHost.hostname) - .should('have.text', createdHost.hostname); - }); - - describe('Endpoint reassignment', () => { - let response: IndexedFleetEndpointPolicyResponse; - let initialAgentData: Agent; - - before(() => { - getAgentByHostName(createdHost.hostname).then((agentData) => { - initialAgentData = agentData; - }); - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - response = data; - }); - }); - }); - - beforeEach(() => { - login(); - }); - - after(() => { - if (initialAgentData?.policy_id) { - reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id); - } - if (response) { - cy.task('deleteIndexedFleetEndpointPolicies', response); - } - }); - - it('User can reassign a single endpoint to a different Agent Configuration', () => { - loadPage(APP_ENDPOINTS_PATH); - const hostname = cy - .getByTestSubj(AGENT_HOSTNAME_CELL) - .filter(`:contains("${createdHost.hostname}")`); - const tableRow = hostname.parents('tr'); - tableRow.findByTestSubj(TABLE_ROW_ACTIONS).click(); - cy.getByTestSubj(TABLE_ROW_ACTIONS_MENU).contains('Reassign agent policy').click(); - cy.getByTestSubj(FLEET_REASSIGN_POLICY_MODAL) - .find('select') - .select(response.agentPolicies[0].name); - cy.getByTestSubj(FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON).click(); - cy.getByTestSubj(AGENT_HOSTNAME_CELL) - .filter(`:contains("${createdHost.hostname}")`) - .should('exist'); - cy.getByTestSubj(AGENT_HOSTNAME_CELL) - .filter(`:contains("${createdHost.hostname}")`) - .parents('tr') - .findByTestSubj(AGENT_POLICY_CELL) - .should('have.text', response.agentPolicies[0].name); - }); - }); - - it('should update endpoint policy on Endpoint', () => { - const parseRevNumber = (revString: string) => Number(revString.match(/\d+/)?.[0]); - - loadPage(APP_ENDPOINTS_PATH); - - cy.getByTestSubj('policyListRevNo') - .first() - .invoke('text') - .then(parseRevNumber) - .then((initialRevisionNumber) => { - // Update policy - cy.getByTestSubj('policyNameCellLink').first().click(); - - cy.getByTestSubj('policyDetailsSaveButton').click(); - cy.getByTestSubj('policyDetailsConfirmModal').should('exist'); - cy.getByTestSubj('confirmModalConfirmButton').click(); - cy.contains(/has been updated/); - - cy.getByTestSubj('policyDetailsBackLink').click(); - - // Assert disappearing 'Out-of-date' indicator, Success Policy Status and increased revision number - cy.getByTestSubj('rowPolicyOutOfDate').should('exist'); - cy.getByTestSubj('rowPolicyOutOfDate').should('not.exist'); // depends on the 10s auto-refresh - - cy.getByTestSubj('policyStatusCellLink').first().should('contain', 'Success'); - - cy.getByTestSubj('policyListRevNo') - .first() - .invoke('text') - .then(parseRevNumber) - .should('equal', initialRevisionNumber + 1); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts deleted file mode 100644 index f39d7fae9f0fe..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts +++ /dev/null @@ -1,261 +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 { PolicyData } from '../../../../../common/endpoint/types'; -import { APP_CASES_PATH, APP_ENDPOINTS_PATH } from '../../../../../common/constants'; -import { closeAllToasts } from '../../tasks/toasts'; -import { - checkEndpointListForOnlyIsolatedHosts, - checkEndpointListForOnlyUnIsolatedHosts, - checkFlyoutEndpointIsolation, - filterOutIsolatedHosts, - isolateHostWithComment, - openAlertDetails, - openCaseAlertDetails, - releaseHostWithComment, - toggleRuleOffAndOn, - visitRuleAlerts, - waitForReleaseOption, -} from '../../tasks/isolate'; -import { cleanupCase, cleanupRule, loadCase, loadRule } from '../../tasks/api_fixtures'; -import { login } from '../../tasks/login'; -import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; -import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; -import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; -import { createEndpointHost } from '../../tasks/create_endpoint_host'; -import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; - -describe.skip('Isolate command', () => { - let isolateComment: string; - let releaseComment: string; - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - isolateComment = `Isolating ${host.hostname}`; - releaseComment = `Releasing ${host.hostname}`; - }); - }); - }); - }); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); - - describe('From manage', () => { - it('should allow filtering endpoint by Isolated status', () => { - loadPage(APP_ENDPOINTS_PATH); - closeAllToasts(); - cy.getByTestSubj('globalLoadingIndicator-hidden').should('exist'); - checkEndpointListForOnlyUnIsolatedHosts(); - - filterOutIsolatedHosts(); - cy.contains('No items found'); - cy.getByTestSubj('adminSearchBar').click().type('{selectall}{backspace}'); - cy.getByTestSubj('querySubmitButton').click(); - cy.getByTestSubj('endpointTableRowActions').click(); - cy.getByTestSubj('isolateLink').click(); - - cy.contains(`Isolate host ${createdHost.hostname} from network.`); - cy.getByTestSubj('endpointHostIsolationForm'); - cy.getByTestSubj('host_isolation_comment').type(isolateComment); - cy.getByTestSubj('hostIsolateConfirmButton').click(); - cy.contains(`Isolation on host ${createdHost.hostname} successfully submitted`); - cy.getByTestSubj('euiFlyoutCloseButton').click(); - cy.getByTestSubj('rowHostStatus-actionStatuses').should('contain.text', 'Isolated'); - filterOutIsolatedHosts(); - - checkEndpointListForOnlyIsolatedHosts(); - - cy.getByTestSubj('endpointTableRowActions').click(); - cy.getByTestSubj('unIsolateLink').click(); - releaseHostWithComment(releaseComment, createdHost.hostname); - cy.contains('Confirm').click(); - cy.getByTestSubj('euiFlyoutCloseButton').click(); - cy.getByTestSubj('adminSearchBar').click().type('{selectall}{backspace}'); - cy.getByTestSubj('querySubmitButton').click(); - checkEndpointListForOnlyUnIsolatedHosts(); - }); - }); - - describe('From alerts', () => { - let ruleId: string; - let ruleName: string; - - before(() => { - loadRule( - { query: `agent.name: ${createdHost.hostname} and agent.type: endpoint` }, - false - ).then((data) => { - ruleId = data.id; - ruleName = data.name; - }); - }); - - after(() => { - if (ruleId) { - cleanupRule(ruleId); - } - }); - - it('should isolate and release host', () => { - loadPage(APP_ENDPOINTS_PATH); - cy.contains(createdHost.hostname).should('exist'); - - toggleRuleOffAndOn(ruleName); - visitRuleAlerts(ruleName); - - closeAllToasts(); - openAlertDetails(); - - isolateHostWithComment(isolateComment, createdHost.hostname); - - cy.getByTestSubj('hostIsolateConfirmButton').click(); - cy.contains(`Isolation on host ${createdHost.hostname} successfully submitted`); - - cy.getByTestSubj('euiFlyoutCloseButton').click(); - openAlertDetails(); - - checkFlyoutEndpointIsolation(); - - releaseHostWithComment(releaseComment, createdHost.hostname); - cy.contains('Confirm').click(); - - cy.contains(`Release on host ${createdHost.hostname} successfully submitted`); - cy.getByTestSubj('euiFlyoutCloseButton').click(); - openAlertDetails(); - cy.getByTestSubj('event-field-agent.status').within(() => { - cy.get('[title="Isolated"]').should('not.exist'); - }); - }); - }); - - describe('From cases', () => { - let ruleId: string; - let ruleName: string; - let caseId: string; - - const caseOwner = 'securitySolution'; - - before(() => { - loadRule( - { query: `agent.name: ${createdHost.hostname} and agent.type: endpoint` }, - false - ).then((data) => { - ruleId = data.id; - ruleName = data.name; - }); - loadCase(caseOwner).then((data) => { - caseId = data.id; - }); - }); - - beforeEach(() => { - login(); - }); - - after(() => { - if (ruleId) { - cleanupRule(ruleId); - } - if (caseId) { - cleanupCase(caseId); - } - }); - - it('should isolate and release host', () => { - loadPage(APP_ENDPOINTS_PATH); - cy.contains(createdHost.hostname).should('exist'); - - toggleRuleOffAndOn(ruleName); - - visitRuleAlerts(ruleName); - closeAllToasts(); - - openAlertDetails(); - - cy.getByTestSubj('add-to-existing-case-action').click(); - cy.getByTestSubj(`cases-table-row-select-${caseId}`).click(); - cy.contains(`An alert was added to \"Test ${caseOwner} case`); - - cy.intercept('GET', `/api/cases/${caseId}/user_actions/_find*`).as('case'); - loadPage(`${APP_CASES_PATH}/${caseId}`); - cy.wait('@case', { timeout: 30000 }).then(({ response: res }) => { - const caseAlertId = res?.body.userActions[1].id; - - closeAllToasts(); - openCaseAlertDetails(caseAlertId); - isolateHostWithComment(isolateComment, createdHost.hostname); - cy.getByTestSubj('hostIsolateConfirmButton').click(); - - cy.getByTestSubj('euiFlyoutCloseButton').click(); - - cy.getByTestSubj('user-actions-list').within(() => { - cy.contains(isolateComment); - cy.get('[aria-label="lock"]').should('exist'); - cy.get('[aria-label="lockOpen"]').should('not.exist'); - }); - - waitForReleaseOption(caseAlertId); - - releaseHostWithComment(releaseComment, createdHost.hostname); - - cy.contains('Confirm').click(); - - cy.contains(`Release on host ${createdHost.hostname} successfully submitted`); - cy.getByTestSubj('euiFlyoutCloseButton').click(); - - cy.getByTestSubj('user-actions-list').within(() => { - cy.contains(releaseComment); - cy.contains(isolateComment); - cy.get('[aria-label="lock"]').should('exist'); - cy.get('[aria-label="lockOpen"]').should('exist'); - }); - - openCaseAlertDetails(caseAlertId); - - cy.getByTestSubj('event-field-agent.status').then(($status) => { - if ($status.find('[title="Isolated"]').length > 0) { - cy.getByTestSubj('euiFlyoutCloseButton').click(); - cy.getByTestSubj(`comment-action-show-alert-${caseAlertId}`).click(); - cy.getByTestSubj('take-action-dropdown-btn').click(); - } - cy.get('[title="Isolated"]').should('not.exist'); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_details.cy.ts deleted file mode 100644 index 6e63264f63e2e..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_details.cy.ts +++ /dev/null @@ -1,169 +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 moment from 'moment/moment'; -import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import type { PolicyData } from '../../../../../common/endpoint/types'; -import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; -import { setCustomProtectionUpdatesManifestVersion } from '../../tasks/endpoint_policy'; -import { login, ROLE } from '../../tasks/login'; -import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; - -describe('Policy Details', () => { - describe('Protection updates', () => { - const loadProtectionUpdatesUrl = (policyId: string) => - loadPage(`/app/security/administration/policy/${policyId}/protectionUpdates`); - - describe('Renders and saves protection updates', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - const today = moment(); - const formattedToday = today.format('MMMM DD, YYYY'); - - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); - - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - }); - }); - }); - - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); - - it('should render the protection updates tab content', () => { - loadProtectionUpdatesUrl(policy.id); - cy.getByTestSubj('protection-updates-automatic-updates-enabled'); - cy.getByTestSubj('protection-updates-manifest-switch'); - cy.getByTestSubj('protection-updates-manifest-name-title'); - cy.getByTestSubj('protection-updates-manifest-name'); - - cy.getByTestSubj('protection-updates-manifest-switch').click(); - - cy.getByTestSubj('protection-updates-manifest-name-deployed-version-title'); - cy.getByTestSubj('protection-updates-deployed-version').contains('latest'); - cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); - cy.getByTestSubj('protection-updates-version-to-deploy-picker').within(() => { - cy.get('input').should('have.value', formattedToday); - }); - cy.getByTestSubj('policyDetailsSaveButton'); - }); - - it('should successfully update the manifest version to custom date', () => { - loadProtectionUpdatesUrl(policy.id); - cy.getByTestSubj('protection-updates-manifest-switch').click(); - cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy'); - cy.getByTestSubj('policyDetailsSaveButton').click(); - cy.wait('@policy').then(({ request, response }) => { - expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( - today.format('YYYY-MM-DD') - ); - expect(response?.statusCode).to.equal(200); - }); - cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); - cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday); - }); - }); - - describe('Renders and saves protection updates with custom version', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - - const twoMonthsAgo = moment().subtract(2, 'months').format('YYYY-MM-DD'); - - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); - - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo); - }); - }); - }); - - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); - - it('should update manifest version to latest when enabling automatic updates', () => { - loadProtectionUpdatesUrl(policy.id); - cy.getByTestSubj('protection-updates-manifest-outdated'); - cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy_latest'); - - cy.getByTestSubj('protection-updates-manifest-switch').click(); - cy.wait('@policy_latest').then(({ request, response }) => { - expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( - 'latest' - ); - expect(response?.statusCode).to.equal(200); - }); - cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); - cy.getByTestSubj('protection-updates-automatic-updates-enabled'); - }); - }); - - describe('Renders read only protection updates for user without write permissions', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - const twoMonthsAgo = moment().subtract(2, 'months'); - - beforeEach(() => { - login(ROLE.endpoint_security_policy_management_read); - disableExpandableFlyoutAdvancedSettings(); - }); - - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo.format('YYYY-MM-DD')); - }); - }); - }); - - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); - - it('should render the protection updates tab content', () => { - loadProtectionUpdatesUrl(policy.id); - cy.getByTestSubj('protection-updates-manifest-switch').should('not.exist'); - cy.getByTestSubj('protection-updates-state-view-mode'); - cy.getByTestSubj('protection-updates-manifest-name-title'); - cy.getByTestSubj('protection-updates-manifest-name'); - - cy.getByTestSubj('protection-updates-manifest-name-deployed-version-title'); - cy.getByTestSubj('protection-updates-deployed-version').contains( - twoMonthsAgo.format('MMMM DD, YYYY') - ); - cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); - cy.getByTestSubj('protection-updates-version-to-deploy-view-mode'); - cy.getByTestSubj('protection-updates-version-to-deploy-picker').should('not.exist'); - cy.getByTestSubj('policyDetailsSaveButton').should('be.disabled'); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts deleted file mode 100644 index 245de3ce5512c..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts +++ /dev/null @@ -1,331 +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 { PolicyData } from '../../../../../common/endpoint/types'; -import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; -import { - inputConsoleCommand, - openResponseConsoleFromEndpointList, - performCommandInputChecks, - submitCommand, - waitForCommandToBeExecuted, - waitForEndpointListPageToBeLoaded, -} from '../../tasks/response_console'; -import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { getEndpointIntegrationVersion, createAgentPolicyTask } from '../../tasks/fleet'; -import { - checkEndpointListForOnlyIsolatedHosts, - checkEndpointListForOnlyUnIsolatedHosts, -} from '../../tasks/isolate'; - -import { login } from '../../tasks/login'; -import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; -import { createEndpointHost } from '../../tasks/create_endpoint_host'; -import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; - -describe('Response console', () => { - beforeEach(() => { - login(); - }); - - describe('User journey for Isolate command: isolate and release an endpoint', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }) - ); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - it('should isolate host from response console', () => { - const command = 'isolate'; - waitForEndpointListPageToBeLoaded(createdHost.hostname); - checkEndpointListForOnlyUnIsolatedHosts(); - openResponseConsoleFromEndpointList(); - performCommandInputChecks(command); - submitCommand(); - waitForCommandToBeExecuted(command); - waitForEndpointListPageToBeLoaded(createdHost.hostname); - checkEndpointListForOnlyIsolatedHosts(); - }); - - it('should release host from response console', () => { - const command = 'release'; - waitForEndpointListPageToBeLoaded(createdHost.hostname); - checkEndpointListForOnlyIsolatedHosts(); - openResponseConsoleFromEndpointList(); - performCommandInputChecks(command); - submitCommand(); - waitForCommandToBeExecuted(command); - waitForEndpointListPageToBeLoaded(createdHost.hostname); - checkEndpointListForOnlyUnIsolatedHosts(); - }); - }); - - describe('User journey for Processes operations: list, kill and suspend process', () => { - let cronPID: string; - let newCronPID: string; - - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }) - ); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - it('"processes" - should obtain a list of processes', () => { - waitForEndpointListPageToBeLoaded(createdHost.hostname); - openResponseConsoleFromEndpointList(); - performCommandInputChecks('processes'); - submitCommand(); - cy.contains('Action pending.').should('exist'); - cy.getByTestSubj('getProcessesSuccessCallout', { timeout: 120000 }).within(() => { - ['USER', 'PID', 'ENTITY ID', 'COMMAND'].forEach((header) => { - cy.contains(header); - }); - - cy.get('tbody > tr').should('have.length.greaterThan', 0); - cy.get('tbody > tr > td').should('contain', '/usr/sbin/cron'); - cy.get('tbody > tr > td') - .contains('/usr/sbin/cron') - .parents('td') - .siblings('td') - .eq(1) - .find('span') - .then((span) => { - cronPID = span.text(); - }); - }); - }); - - it('"kill-process --pid" - should kill a process', () => { - waitForEndpointListPageToBeLoaded(createdHost.hostname); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`kill-process --pid ${cronPID}`); - submitCommand(); - waitForCommandToBeExecuted('kill-process'); - - performCommandInputChecks('processes'); - submitCommand(); - - cy.getByTestSubj('getProcessesSuccessCallout', { timeout: 120000 }).within(() => { - cy.get('tbody > tr > td') - .contains('/usr/sbin/cron') - .parents('td') - .siblings('td') - .eq(1) - .find('span') - .then((span) => { - newCronPID = span.text(); - }); - }); - expect(newCronPID).to.not.equal(cronPID); - }); - - it('"suspend-process --pid" - should suspend a process', () => { - waitForEndpointListPageToBeLoaded(createdHost.hostname); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`suspend-process --pid ${newCronPID}`); - submitCommand(); - waitForCommandToBeExecuted('suspend-process'); - }); - }); - - describe('File operations: get-file and execute', () => { - const homeFilePath = process.env.CI || true ? '/home/vagrant' : `/home/ubuntu`; - - const fileContent = 'This is a test file for the get-file command.'; - const filePath = `${homeFilePath}/test_file.txt`; - - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }) - ); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - it('"get-file --path" - should retrieve a file', () => { - waitForEndpointListPageToBeLoaded(createdHost.hostname); - cy.task('createFileOnEndpoint', { - hostname: createdHost.hostname, - path: filePath, - content: fileContent, - }); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`get-file --path ${filePath}`); - submitCommand(); - cy.getByTestSubj('getFileSuccess', { timeout: 60000 }).within(() => { - cy.contains('File retrieved from the host.'); - cy.contains('(ZIP file passcode: elastic)'); - cy.contains( - 'Files are periodically deleted to clear storage space. Download and save file locally if needed.' - ); - cy.contains('Click here to download').click(); - const downloadsFolder = Cypress.config('downloadsFolder'); - cy.readFile(`${downloadsFolder}/upload.zip`); - - cy.task('uploadFileToEndpoint', { - hostname: createdHost.hostname, - srcPath: `${downloadsFolder}/upload.zip`, - destPath: `${homeFilePath}/upload.zip`, - }); - - cy.task('readZippedFileContentOnEndpoint', { - hostname: createdHost.hostname, - path: `${homeFilePath}/upload.zip`, - password: 'elastic', - }).then((unzippedFileContent) => { - expect(unzippedFileContent).to.equal(fileContent); - }); - }); - }); - - it('"execute --command" - should execute a command', () => { - waitForEndpointListPageToBeLoaded(createdHost.hostname); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`execute --command "ls -al ${homeFilePath}"`); - submitCommand(); - waitForCommandToBeExecuted('execute'); - }); - }); - - describe('document signing', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - let createdHost: CreateAndEnrollEndpointHostResponse; - - before(() => { - getEndpointIntegrationVersion().then((version) => - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - - return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_id).then((host) => { - createdHost = host as CreateAndEnrollEndpointHostResponse; - }); - }); - }) - ); - }); - - after(() => { - if (createdHost) { - cy.task('destroyEndpointHost', createdHost); - } - - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - - if (createdHost) { - deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); - } - }); - - it('should fail if data tampered', () => { - waitForEndpointListPageToBeLoaded(createdHost.hostname); - checkEndpointListForOnlyUnIsolatedHosts(); - openResponseConsoleFromEndpointList(); - performCommandInputChecks('isolate'); - - // stop host so that we ensure tamper happens before endpoint processes the action - cy.task('stopEndpointHost', createdHost.hostname); - // get action doc before we submit command so we know when the new action doc is indexed - cy.task('getLatestActionDoc').then((previousActionDoc) => { - submitCommand(); - cy.task('tamperActionDoc', previousActionDoc); - }); - cy.task('startEndpointHost', createdHost.hostname); - - const actionValidationErrorMsg = - 'Fleet action response error: Failed to validate action signature; check Endpoint logs for details'; - cy.contains(actionValidationErrorMsg, { timeout: 120000 }).should('exist'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts new file mode 100644 index 0000000000000..3daf711eca9cd --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts @@ -0,0 +1,98 @@ +/* + * Copyright 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 { deleteAllLoadedEndpointData } from '../tasks/delete_all_endpoint_data'; +import { getAlertsTableRows, navigateToAlertsList } from '../screens/alerts'; +import { waitForEndpointAlerts } from '../tasks/alerts'; +import { request } from '../tasks/common'; +import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../tasks/fleet'; +import { createEndpointHost } from '../tasks/create_endpoint_host'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { enableAllPolicyProtections } from '../tasks/endpoint_policy'; +import type { PolicyData, ResponseActionApiResponse } from '../../../../common/endpoint/types'; +import type { CreateAndEnrollEndpointHostResponse } from '../../../../scripts/endpoint/common/endpoint_host_services'; +import { login } from '../tasks/login'; +import { EXECUTE_ROUTE } from '../../../../common/endpoint/constants'; +import { waitForActionToComplete } from '../tasks/response_actions'; + +describe( + 'Endpoint generated alerts', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version, 'alerts test').then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }); + }); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + beforeEach(() => { + login(); + }); + + it('should create a Detection Engine alert from an endpoint alert', () => { + // Triggers a Malicious Behaviour alert on Linux system (`grep *` was added only to identify this specific alert) + const executeMaliciousCommand = `bash -c cat /dev/tcp/foo | grep ${Math.random() + .toString(16) + .substring(2)}`; + + // Send `execute` command that triggers malicious behaviour using the `execute` response action + request({ + method: 'POST', + url: EXECUTE_ROUTE, + body: { + endpoint_ids: [createdHost.agentId], + parameters: { + command: executeMaliciousCommand, + }, + }, + }) + .then((response) => waitForActionToComplete(response.body.data.id)) + .then(() => { + return waitForEndpointAlerts(createdHost.agentId, [ + { + term: { 'process.group_leader.args': executeMaliciousCommand }, + }, + ]); + }) + .then(() => { + return navigateToAlertsList( + `query=(language:kuery,query:'agent.id: "${createdHost.agentId}" ')` + ); + }); + + getAlertsTableRows().should('have.length.greaterThan', 0); + }); + } +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts new file mode 100644 index 0000000000000..2664dbcbb76a0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts @@ -0,0 +1,168 @@ +/* + * Copyright 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 { Agent } from '@kbn/fleet-plugin/common'; +import { + AGENT_HOSTNAME_CELL, + AGENT_POLICY_CELL, + TABLE_ROW_ACTIONS, + TABLE_ROW_ACTIONS_MENU, +} from '../../screens'; +import type { PolicyData } from '../../../../../common/endpoint/types'; +import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; +import { + createAgentPolicyTask, + getAgentByHostName, + getEndpointIntegrationVersion, + reassignAgentPolicy, +} from '../../tasks/fleet'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { login } from '../../tasks/login'; +import { loadPage } from '../../tasks/common'; +import { + FLEET_REASSIGN_POLICY_MODAL, + FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON, +} from '../../screens/fleet/agent_details'; +import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; +import { createEndpointHost } from '../../tasks/create_endpoint_host'; +import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; +import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; + +describe('Endpoints page', { tags: '@ess' }, () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }); + }); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + beforeEach(() => { + login(); + }); + + it('Shows endpoint on the list', () => { + loadPage(APP_ENDPOINTS_PATH); + cy.contains('Hosts running Elastic Defend').should('exist'); + cy.getByTestSubj(AGENT_HOSTNAME_CELL) + .contains(createdHost.hostname) + .should('have.text', createdHost.hostname); + }); + + describe('Endpoint reassignment', () => { + let response: IndexedFleetEndpointPolicyResponse; + let initialAgentData: Agent; + + before(() => { + getAgentByHostName(createdHost.hostname).then((agentData) => { + initialAgentData = agentData; + }); + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + response = data; + }); + }); + }); + + beforeEach(() => { + login(); + }); + + after(() => { + if (initialAgentData?.policy_id) { + reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id); + } + if (response) { + cy.task('deleteIndexedFleetEndpointPolicies', response); + } + }); + + it('User can reassign a single endpoint to a different Agent Configuration', () => { + loadPage(APP_ENDPOINTS_PATH); + cy.getByTestSubj(AGENT_HOSTNAME_CELL) + .filter(`:contains("${createdHost.hostname}")`) + .then((hostname) => { + const tableRow = hostname.parents('tr'); + + tableRow.find(`[data-test-subj=${TABLE_ROW_ACTIONS}`).trigger('click'); + cy.getByTestSubj(TABLE_ROW_ACTIONS_MENU).contains('Reassign agent policy').click(); + cy.getByTestSubj(FLEET_REASSIGN_POLICY_MODAL) + .find('select') + .select(response.agentPolicies[0].name); + cy.getByTestSubj(FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON).click(); + cy.getByTestSubj(AGENT_HOSTNAME_CELL) + .filter(`:contains("${createdHost.hostname}")`) + .should('exist'); + cy.getByTestSubj(AGENT_HOSTNAME_CELL) + .filter(`:contains("${createdHost.hostname}")`) + .parents('tr') + .findByTestSubj(AGENT_POLICY_CELL) + .should('have.text', response.agentPolicies[0].name); + }); + }); + }); + + it('should update endpoint policy on Endpoint', () => { + const parseRevNumber = (revString: string) => Number(revString.match(/\d+/)?.[0]); + + loadPage(APP_ENDPOINTS_PATH); + + cy.getByTestSubj('policyListRevNo') + .first() + .invoke('text') + .then(parseRevNumber) + .then((initialRevisionNumber) => { + // Update policy + cy.getByTestSubj('policyNameCellLink').first().click(); + + cy.getByTestSubj('policyDetailsSaveButton').click(); + cy.getByTestSubj('policyDetailsConfirmModal').should('exist'); + cy.getByTestSubj('confirmModalConfirmButton').click(); + cy.contains(/has been updated/); + + cy.getByTestSubj('policyDetailsBackLink').click(); + + // Assert disappearing 'Out-of-date' indicator, Success Policy Status and increased revision number + cy.getByTestSubj('rowPolicyOutOfDate').should('exist'); + cy.getByTestSubj('rowPolicyOutOfDate').should('not.exist'); // depends on the 10s auto-refresh + + cy.getByTestSubj('policyStatusCellLink').first().should('contain', 'Success'); + + cy.getByTestSubj('policyListRevNo') + .first() + .invoke('text') + .then(parseRevNumber) + .should('equal', initialRevisionNumber + 1); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints_mocked_data.cy.ts new file mode 100644 index 0000000000000..5804143f3b19c --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints_mocked_data.cy.ts @@ -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 type { MetadataListResponse } from '../../../../../common/endpoint/types'; +import { EndpointSortableField } from '../../../../../common/endpoint/types'; +import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; +import type { ReturnTypeFromChainable } from '../../types'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { login } from '../../tasks/login'; +import { loadPage } from '../../tasks/common'; + +describe('Endpoints page', { tags: '@ess' }, () => { + let endpointData: ReturnTypeFromChainable; + + before(() => { + indexEndpointHosts({ count: 3 }).then((indexEndpoints) => { + endpointData = indexEndpoints; + }); + }); + + beforeEach(() => { + login(); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('Loads the endpoints page', () => { + loadPage(APP_ENDPOINTS_PATH); + cy.contains('Hosts running Elastic Defend').should('exist'); + }); + + describe('Sorting', () => { + it('Sorts by enrollment date descending order by default', () => { + cy.intercept('api/endpoint/metadata*').as('getEndpointMetadataRequest'); + + loadPage(APP_ENDPOINTS_PATH); + + cy.wait('@getEndpointMetadataRequest').then((subject) => { + const body = subject.response?.body as MetadataListResponse; + + expect(body.sortField).to.equal(EndpointSortableField.ENROLLED_AT); + expect(body.sortDirection).to.equal('desc'); + }); + + // no sorting indicator is present on the screen + cy.get('.euiTableSortIcon').should('not.exist'); + }); + + it('User can sort by any field', () => { + loadPage(APP_ENDPOINTS_PATH); + + const fields = Object.values(EndpointSortableField).filter( + // enrolled_at is not present in the table, it's just the default sorting + (value) => value !== EndpointSortableField.ENROLLED_AT + ); + + for (let i = 0; i < fields.length; i++) { + const field = fields[i]; + cy.intercept(`api/endpoint/metadata*${encodeURIComponent(field)}*`).as(`request.${field}`); + + cy.getByTestSubj(`tableHeaderCell_${field}_${i}`).as('header').click(); + validateSortingInResponse(field, 'asc'); + cy.get('@header').should('have.attr', 'aria-sort', 'ascending'); + cy.get('.euiTableSortIcon').should('exist'); + + cy.get('@header').click(); + validateSortingInResponse(field, 'desc'); + cy.get('@header').should('have.attr', 'aria-sort', 'descending'); + cy.get('.euiTableSortIcon').should('exist'); + } + }); + + it('Sorting can be passed via URL', () => { + cy.intercept('api/endpoint/metadata*').as(`request.host_status`); + + loadPage(`${APP_ENDPOINTS_PATH}?sort_field=host_status&sort_direction=desc`); + + validateSortingInResponse('host_status', 'desc'); + cy.get('[data-test-subj^=tableHeaderCell_host_status').should( + 'have.attr', + 'aria-sort', + 'descending' + ); + }); + + const validateSortingInResponse = (field: string, direction: 'asc' | 'desc') => + cy.wait(`@request.${field}`).then((subject) => { + expect(subject.response?.statusCode).to.equal(200); + + const body = subject.response?.body as MetadataListResponse; + expect(body.total).to.equal(3); + expect(body.sortField).to.equal(field); + expect(body.sortDirection).to.equal(direction); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/policy_response.cy.ts similarity index 92% rename from x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/policy_response.cy.ts index c8949b8c09077..073bb92ea8f43 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/policy_response.cy.ts @@ -5,17 +5,17 @@ * 2.0. */ +import { navigateToEndpointPolicyResponse } from '../../screens'; import type { CyIndexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; -import { navigateToEndpointPolicyResponse } from '../../screens/endpoints'; import type { HostMetadata } from '../../../../../common/endpoint/types'; import type { IndexedEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_endpoint_policy_response'; import { login } from '../../tasks/login'; -import { navigateToFleetAgentDetails } from '../../screens/fleet'; +import { navigateToFleetAgentDetails } from '../../screens/fleet/agent_details'; import { EndpointPolicyResponseGenerator } from '../../../../../common/endpoint/data_generators/endpoint_policy_response_generator'; import { descriptions } from '../../../components/policy_response/policy_response_friendly_names'; -describe.skip('Endpoint Policy Response', () => { +describe.skip('Endpoint Policy Response', { tags: '@ess' }, () => { let loadedEndpoint: CyIndexEndpointHosts; let endpointMetadata: HostMetadata; let loadedPolicyResponse: IndexedEndpointPolicyResponse; @@ -61,8 +61,7 @@ describe.skip('Endpoint Policy Response', () => { login(); }); - // TODO failing test skipped https://github.com/elastic/kibana/issues/162428 - describe.skip('from Fleet Agent Details page', () => { + describe('from Fleet Agent Details page', () => { it('should display policy response with errors', () => { navigateToFleetAgentDetails(endpointMetadata.agent.id); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoint_role_rbac.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.cy.ts similarity index 95% rename from x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoint_role_rbac.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.cy.ts index a3198d894076e..a09f9e8b8273d 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoint_role_rbac.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.cy.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { closeAllToasts } from '../../tasks/toasts'; -import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; +import { closeAllToasts } from '../tasks/toasts'; +import { login } from '../tasks/login'; +import { loadPage } from '../tasks/common'; -describe('When defining a kibana role for Endpoint security access', () => { +describe('When defining a kibana role for Endpoint security access', { tags: '@ess' }, () => { const getAllSubFeatureRows = (): Cypress.Chainable> => { return cy .get('#featurePrivilegeControls_siem') diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/artifact_tabs_in_policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/artifact_tabs_in_policy_details.cy.ts deleted file mode 100644 index 2977db30fadc9..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/artifact_tabs_in_policy_details.cy.ts +++ /dev/null @@ -1,224 +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 { getEndpointSecurityPolicyManager } from '../../../../../scripts/endpoint/common/roles_users/endpoint_security_policy_manager'; -import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; -import { visitPolicyDetailsPage } from '../../screens/policy_details'; -import { - createPerPolicyArtifact, - createArtifactList, - removeAllArtifacts, - removeExceptionsList, - yieldFirstPolicyID, -} from '../../tasks/artifacts'; -import { loadEndpointDataForEventFiltersIfNeeded } from '../../tasks/load_endpoint_data'; -import { - getRoleWithArtifactReadPrivilege, - login, - loginWithCustomRole, - loginWithRole, - ROLE, -} from '../../tasks/login'; -import { performUserActions } from '../../tasks/perform_user_actions'; - -const loginWithPrivilegeAll = () => { - loginWithRole(ROLE.endpoint_security_policy_manager); -}; - -const loginWithPrivilegeRead = (privilegePrefix: string) => { - const roleWithArtifactReadPrivilege = getRoleWithArtifactReadPrivilege(privilegePrefix); - loginWithCustomRole('roleWithArtifactReadPrivilege', roleWithArtifactReadPrivilege); -}; - -const loginWithPrivilegeNone = (privilegePrefix: string) => { - const roleWithoutArtifactPrivilege = getRoleWithoutArtifactPrivilege(privilegePrefix); - loginWithCustomRole('roleWithoutArtifactPrivilege', roleWithoutArtifactPrivilege); -}; - -const getRoleWithoutArtifactPrivilege = (privilegePrefix: string) => { - const endpointSecurityPolicyManagerRole = getEndpointSecurityPolicyManager(); - - return { - ...endpointSecurityPolicyManagerRole, - kibana: [ - { - ...endpointSecurityPolicyManagerRole.kibana[0], - feature: { - ...endpointSecurityPolicyManagerRole.kibana[0].feature, - siem: endpointSecurityPolicyManagerRole.kibana[0].feature.siem.filter( - (privilege) => privilege !== `${privilegePrefix}all` - ), - }, - }, - ], - }; -}; - -const visitArtifactTab = (tabId: string) => { - visitPolicyDetailsPage(); - cy.get(`#${tabId}`).click(); -}; - -describe('Artifact tabs in Policy Details page', () => { - before(() => { - login(); - loadEndpointDataForEventFiltersIfNeeded(); - }); - - after(() => { - login(); - removeAllArtifacts(); - }); - - for (const testData of getArtifactsListTestsData()) { - describe(`${testData.title} tab`, () => { - beforeEach(() => { - login(); - removeExceptionsList(testData.createRequestBody.list_id); - }); - - it(`[NONE] User cannot see the tab for ${testData.title}`, () => { - loginWithPrivilegeNone(testData.privilegePrefix); - visitPolicyDetailsPage(); - - cy.get(`#${testData.tabId}`).should('not.exist'); - }); - - context(`Given there are no ${testData.title} entries`, () => { - it(`[READ] User CANNOT add ${testData.title} artifact`, () => { - loginWithPrivilegeRead(testData.privilegePrefix); - visitArtifactTab(testData.tabId); - - cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist'); - - cy.getByTestSubj('unexisting-manage-artifacts-button').should('not.exist'); - }); - - it(`[ALL] User can add ${testData.title} artifact`, () => { - loginWithPrivilegeAll(); - visitArtifactTab(testData.tabId); - - cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist'); - - cy.getByTestSubj('unexisting-manage-artifacts-button').should('exist').click(); - - const { formActions, checkResults } = testData.create; - - performUserActions(formActions); - - // Add a per policy artifact - but not assign it to any policy - cy.get('[data-test-subj$="-perPolicy"]').click(); // test-subjects are generated in different formats, but all ends with -perPolicy - cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); - - // Check new artifact is in the list - for (const checkResult of checkResults) { - cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); - } - - cy.getByTestSubj('policyDetailsPage').should('not.exist'); - cy.getByTestSubj('backToOrigin').contains(/^Back to .+ policy$/); - - cy.getByTestSubj('backToOrigin').click(); - cy.getByTestSubj('policyDetailsPage').should('exist'); - }); - }); - - context(`Given there are no assigned ${testData.title} entries`, () => { - beforeEach(() => { - login(); - createArtifactList(testData.createRequestBody.list_id); - createPerPolicyArtifact(testData.artifactName, testData.createRequestBody); - }); - - it(`[READ] User CANNOT Manage or Assign ${testData.title} artifacts`, () => { - loginWithPrivilegeRead(testData.privilegePrefix); - visitArtifactTab(testData.tabId); - - cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist'); - - cy.getByTestSubj('unassigned-manage-artifacts-button').should('not.exist'); - cy.getByTestSubj('unassigned-assign-artifacts-button').should('not.exist'); - }); - - it(`[ALL] User can Manage and Assign ${testData.title} artifacts`, () => { - loginWithPrivilegeAll(); - visitArtifactTab(testData.tabId); - - cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist'); - - // Manage artifacts - cy.getByTestSubj('unassigned-manage-artifacts-button').should('exist').click(); - cy.location('pathname').should( - 'equal', - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj('backToOrigin').click(); - - // Assign artifacts - cy.getByTestSubj('unassigned-assign-artifacts-button').should('exist').click(); - - cy.getByTestSubj('artifacts-assign-flyout').should('exist'); - cy.getByTestSubj('artifacts-assign-confirm-button').should('be.disabled'); - - cy.getByTestSubj(`${testData.artifactName}_checkbox`).click(); - cy.getByTestSubj('artifacts-assign-confirm-button').click(); - }); - }); - - context(`Given there are assigned ${testData.title} entries`, () => { - beforeEach(() => { - login(); - createArtifactList(testData.createRequestBody.list_id); - yieldFirstPolicyID().then((policyID) => { - createPerPolicyArtifact(testData.artifactName, testData.createRequestBody, policyID); - }); - }); - - it(`[READ] User can see ${testData.title} artifacts but CANNOT assign or remove from policy`, () => { - loginWithPrivilegeRead(testData.privilegePrefix); - visitArtifactTab(testData.tabId); - - // List of artifacts - cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1); - cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains( - testData.artifactName - ); - - // Cannot assign artifacts - cy.getByTestSubj('artifacts-assign-button').should('not.exist'); - - // Cannot remove from policy - cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click(); - cy.getByTestSubj('remove-from-policy-action').should('not.exist'); - }); - - it(`[ALL] User can see ${testData.title} artifacts and can assign or remove artifacts from policy`, () => { - loginWithPrivilegeAll(); - visitArtifactTab(testData.tabId); - - // List of artifacts - cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1); - cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains( - testData.artifactName - ); - - // Assign artifacts - cy.getByTestSubj('artifacts-assign-button').should('exist').click(); - cy.getByTestSubj('artifacts-assign-flyout').should('exist'); - cy.getByTestSubj('artifacts-assign-cancel-button').click(); - - // Remove from policy - cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click(); - cy.getByTestSubj('remove-from-policy-action').click(); - cy.getByTestSubj('confirmModalConfirmButton').click(); - - cy.contains('Successfully removed'); - }); - }); - }); - } -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/artifacts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/artifacts.cy.ts deleted file mode 100644 index 53693ab40b651..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/artifacts.cy.ts +++ /dev/null @@ -1,147 +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 { - getRoleWithArtifactReadPrivilege, - login, - loginWithCustomRole, - loginWithRole, - ROLE, -} from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; - -import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; -import { removeAllArtifacts } from '../../tasks/artifacts'; -import { performUserActions } from '../../tasks/perform_user_actions'; -import { loadEndpointDataForEventFiltersIfNeeded } from '../../tasks/load_endpoint_data'; - -const loginWithWriteAccess = (url: string) => { - loginWithRole(ROLE.endpoint_security_policy_manager); - loadPage(url); -}; - -const loginWithReadAccess = (privilegePrefix: string, url: string) => { - const roleWithArtifactReadPrivilege = getRoleWithArtifactReadPrivilege(privilegePrefix); - loginWithCustomRole('roleWithArtifactReadPrivilege', roleWithArtifactReadPrivilege); - loadPage(url); -}; - -const loginWithoutAccess = (url: string) => { - loginWithRole(ROLE.t1_analyst); - loadPage(url); -}; - -describe('Artifacts pages', () => { - before(() => { - login(); - loadEndpointDataForEventFiltersIfNeeded(); - // Clean artifacts data - removeAllArtifacts(); - }); - - after(() => { - // Clean artifacts data - removeAllArtifacts(); - }); - - for (const testData of getArtifactsListTestsData()) { - describe(`When on the ${testData.title} entries list`, () => { - it(`no access - should show no privileges callout`, () => { - loginWithoutAccess(`/app/security/administration/${testData.urlPath}`); - cy.getByTestSubj('noPrivilegesPage').should('exist'); - cy.getByTestSubj('empty-page-feature-action').should('exist'); - cy.getByTestSubj(testData.emptyState).should('not.exist'); - cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist'); - }); - - it(`read - should show empty state page if there is no ${testData.title} entry and the add button does not exist`, () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj(testData.emptyState).should('exist'); - cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist'); - }); - - it(`write - should show empty state page if there is no ${testData.title} entry and the add button exists`, () => { - loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); - cy.getByTestSubj(testData.emptyState).should('exist'); - cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('exist'); - }); - - it(`write - should create new ${testData.title} entry`, () => { - loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); - // Opens add flyout - cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).click(); - - performUserActions(testData.create.formActions); - - // Submit create artifact form - cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); - - // Check new artifact is in the list - for (const checkResult of testData.create.checkResults) { - cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); - } - - // Title is shown after adding an item - cy.getByTestSubj('header-page-title').contains(testData.title); - }); - - it(`read - should not be able to update/delete an existing ${testData.title} entry`, () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj('header-page-title').contains(testData.title); - cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).should('not.exist'); - cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).should('not.exist'); - cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).should('not.exist'); - }); - - it(`read - should not be able to create a new ${testData.title} entry`, () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj('header-page-title').contains(testData.title); - cy.getByTestSubj(`${testData.pagePrefix}-pageAddButton`).should('not.exist'); - }); - - it(`write - should be able to update an existing ${testData.title} entry`, () => { - loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); - // Opens edit flyout - cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).click(); - cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).click(); - - performUserActions(testData.update.formActions); - - // Submit edit artifact form - cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); - - for (const checkResult of testData.create.checkResults) { - cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); - } - - // Title still shown after editing an item - cy.getByTestSubj('header-page-title').contains(testData.title); - }); - - it(`write - should be able to delete the existing ${testData.title} entry`, () => { - loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); - // Remove it - cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).click(); - cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).click(); - cy.getByTestSubj(`${testData.pagePrefix}-deleteModal-submitButton`).click(); - // No card visible after removing it - cy.getByTestSubj(testData.delete.card).should('not.exist'); - // Empty state is displayed after removing last item - cy.getByTestSubj(testData.emptyState).should('exist'); - }); - }); - } -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoints.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoints.cy.ts deleted file mode 100644 index ce9533d857677..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoints.cy.ts +++ /dev/null @@ -1,106 +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 { MetadataListResponse } from '../../../../../common/endpoint/types'; -import { EndpointSortableField } from '../../../../../common/endpoint/types'; -import { APP_ENDPOINTS_PATH } from '../../../../../common/constants'; -import type { ReturnTypeFromChainable } from '../../types'; -import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; -import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; - -describe('Endpoints page', () => { - let endpointData: ReturnTypeFromChainable; - - before(() => { - indexEndpointHosts({ count: 3 }).then((indexEndpoints) => { - endpointData = indexEndpoints; - }); - }); - - beforeEach(() => { - login(); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('Loads the endpoints page', () => { - loadPage(APP_ENDPOINTS_PATH); - cy.contains('Hosts running Elastic Defend').should('exist'); - }); - - describe('Sorting', () => { - it('Sorts by enrollment date descending order by default', () => { - cy.intercept('api/endpoint/metadata*').as('getEndpointMetadataRequest'); - - loadPage(APP_ENDPOINTS_PATH); - - cy.wait('@getEndpointMetadataRequest').then((subject) => { - const body = subject.response?.body as MetadataListResponse; - - expect(body.sortField).to.equal(EndpointSortableField.ENROLLED_AT); - expect(body.sortDirection).to.equal('desc'); - }); - - // no sorting indicator is present on the screen - cy.get('.euiTableSortIcon').should('not.exist'); - }); - - it('User can sort by any field', () => { - loadPage(APP_ENDPOINTS_PATH); - - const fields = Object.values(EndpointSortableField).filter( - // enrolled_at is not present in the table, it's just the default sorting - (value) => value !== EndpointSortableField.ENROLLED_AT - ); - - for (let i = 0; i < fields.length; i++) { - const field = fields[i]; - cy.intercept(`api/endpoint/metadata*${encodeURIComponent(field)}*`).as(`request.${field}`); - - cy.getByTestSubj(`tableHeaderCell_${field}_${i}`).as('header').click(); - validateSortingInResponse(field, 'asc'); - cy.get('@header').should('have.attr', 'aria-sort', 'ascending'); - cy.get('.euiTableSortIcon').should('exist'); - - cy.get('@header').click(); - validateSortingInResponse(field, 'desc'); - cy.get('@header').should('have.attr', 'aria-sort', 'descending'); - cy.get('.euiTableSortIcon').should('exist'); - } - }); - - it('Sorting can be passed via URL', () => { - cy.intercept('api/endpoint/metadata*').as(`request.host_status`); - - loadPage(`${APP_ENDPOINTS_PATH}?sort_field=host_status&sort_direction=desc`); - - validateSortingInResponse('host_status', 'desc'); - cy.get('[data-test-subj^=tableHeaderCell_host_status').should( - 'have.attr', - 'aria-sort', - 'descending' - ); - }); - - const validateSortingInResponse = (field: string, direction: 'asc' | 'desc') => - cy.wait(`@request.${field}`).then((subject) => { - expect(subject.response?.statusCode).to.equal(200); - - const body = subject.response?.body as MetadataListResponse; - expect(body.total).to.equal(3); - expect(body.sortField).to.equal(field); - expect(body.sortDirection).to.equal(direction); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts deleted file mode 100644 index 549b1c4e2a328..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts +++ /dev/null @@ -1,322 +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 { getEndpointListPath } from '../../../common/routing'; -import { - checkEndpointIsIsolated, - checkFlyoutEndpointIsolation, - filterOutIsolatedHosts, - interceptActionRequests, - isolateHostWithComment, - openAlertDetails, - openCaseAlertDetails, - releaseHostWithComment, - sendActionResponse, - waitForReleaseOption, -} from '../../tasks/isolate'; -import type { ActionDetails } from '../../../../../common/endpoint/types'; -import { closeAllToasts } from '../../tasks/toasts'; -import type { ReturnTypeFromChainable } from '../../types'; -import { addAlertsToCase } from '../../tasks/add_alerts_to_case'; -import { APP_ALERTS_PATH, APP_CASES_PATH, APP_PATH } from '../../../../../common/constants'; -import { login } from '../../tasks/login'; -import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; -import { indexNewCase } from '../../tasks/index_new_case'; -import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; -import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; - -describe('Isolate command', () => { - describe('from Manage', () => { - let endpointData: ReturnTypeFromChainable | undefined; - let isolatedEndpointData: ReturnTypeFromChainable | undefined; - let isolatedEndpointHostnames: [string, string]; - let endpointHostnames: [string, string]; - - before(() => { - indexEndpointHosts({ - count: 2, - withResponseActions: false, - isolation: false, - }).then((indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostnames = [ - endpointData.data.hosts[0].host.name, - endpointData.data.hosts[1].host.name, - ]; - }); - - indexEndpointHosts({ - count: 2, - withResponseActions: false, - isolation: true, - }).then((indexEndpoints) => { - isolatedEndpointData = indexEndpoints; - isolatedEndpointHostnames = [ - isolatedEndpointData.data.hosts[0].host.name, - isolatedEndpointData.data.hosts[1].host.name, - ]; - }); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - endpointData = undefined; - } - - if (isolatedEndpointData) { - isolatedEndpointData.cleanup(); - isolatedEndpointData = undefined; - } - }); - - beforeEach(() => { - login(); - }); - - it('should allow filtering endpoint by Isolated status', () => { - loadPage(APP_PATH + getEndpointListPath({ name: 'endpointList' })); - closeAllToasts(); - filterOutIsolatedHosts(); - isolatedEndpointHostnames.forEach(checkEndpointIsIsolated); - endpointHostnames.forEach((hostname) => { - cy.contains(hostname).should('not.exist'); - }); - }); - }); - - describe.skip('from Alerts', () => { - let endpointData: ReturnTypeFromChainable | undefined; - let alertData: ReturnTypeFromChainable | undefined; - let hostname: string; - - before(() => { - disableExpandableFlyoutAdvancedSettings(); - indexEndpointHosts({ withResponseActions: false, isolation: false }).then( - (indexEndpoints) => { - endpointData = indexEndpoints; - hostname = endpointData.data.hosts[0].host.name; - - return indexEndpointRuleAlerts({ - endpointAgentId: endpointData.data.hosts[0].agent.id, - endpointHostname: endpointData.data.hosts[0].host.name, - endpointIsolated: false, - }); - } - ); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - endpointData = undefined; - } - - if (alertData) { - alertData.cleanup(); - alertData = undefined; - } - }); - - beforeEach(() => { - login(); - }); - - it('should isolate and release host', () => { - const isolateComment = `Isolating ${hostname}`; - const releaseComment = `Releasing ${hostname}`; - let isolateRequestResponse: ActionDetails; - let releaseRequestResponse: ActionDetails; - - loadPage(APP_ALERTS_PATH); - closeAllToasts(); - - cy.getByTestSubj('alertsTable').within(() => { - cy.getByTestSubj('expand-event') - .first() - .within(() => { - cy.get(`[data-is-loading="true"]`).should('exist'); - }); - cy.getByTestSubj('expand-event') - .first() - .within(() => { - cy.get(`[data-is-loading="true"]`).should('not.exist'); - }); - }); - - openAlertDetails(); - - isolateHostWithComment(isolateComment, hostname); - - interceptActionRequests((responseBody) => { - isolateRequestResponse = responseBody; - }, 'isolate'); - - cy.getByTestSubj('hostIsolateConfirmButton').click(); - - cy.wait('@isolate').then(() => { - sendActionResponse(isolateRequestResponse); - }); - - cy.contains(`Isolation on host ${hostname} successfully submitted`); - - cy.getByTestSubj('euiFlyoutCloseButton').click(); - cy.wait(1000); - openAlertDetails(); - - checkFlyoutEndpointIsolation(); - - releaseHostWithComment(releaseComment, hostname); - - interceptActionRequests((responseBody) => { - releaseRequestResponse = responseBody; - }, 'release'); - - cy.contains('Confirm').click(); - - cy.wait('@release').then(() => { - sendActionResponse(releaseRequestResponse); - }); - - cy.contains(`Release on host ${hostname} successfully submitted`); - cy.getByTestSubj('euiFlyoutCloseButton').click(); - openAlertDetails(); - cy.getByTestSubj('event-field-agent.status').within(() => { - cy.get('[title="Isolated"]').should('not.exist'); - }); - }); - }); - - describe('from Cases', () => { - let endpointData: ReturnTypeFromChainable | undefined; - let caseData: ReturnTypeFromChainable | undefined; - let alertData: ReturnTypeFromChainable | undefined; - let caseAlertActions: ReturnType; - let alertId: string; - let caseUrlPath: string; - let hostname: string; - - before(() => { - indexNewCase().then((indexCase) => { - caseData = indexCase; - caseUrlPath = `${APP_CASES_PATH}/${indexCase.data.id}`; - }); - - indexEndpointHosts({ withResponseActions: false, isolation: false }) - .then((indexEndpoints) => { - endpointData = indexEndpoints; - hostname = endpointData.data.hosts[0].host.name; - - return indexEndpointRuleAlerts({ - endpointAgentId: endpointData.data.hosts[0].agent.id, - endpointHostname: endpointData.data.hosts[0].host.name, - endpointIsolated: false, - }); - }) - .then((indexedAlert) => { - alertData = indexedAlert; - alertId = alertData.alerts[0]._id; - - if (caseData) { - caseAlertActions = addAlertsToCase({ - caseId: caseData.data.id, - alertIds: [alertId], - }); - } - }); - }); - - after(() => { - if (caseData) { - caseData.cleanup(); - caseData = undefined; - } - - if (endpointData) { - endpointData.cleanup(); - endpointData = undefined; - } - - if (alertData) { - alertData.cleanup(); - alertData = undefined; - } - }); - - beforeEach(() => { - login(); - }); - - it('should isolate and release host', () => { - let isolateRequestResponse: ActionDetails; - let releaseRequestResponse: ActionDetails; - const isolateComment = `Isolating ${hostname}`; - const releaseComment = `Releasing ${hostname}`; - const caseAlertId = caseAlertActions.comments[alertId]; - - loadPage(caseUrlPath); - closeAllToasts(); - openCaseAlertDetails(caseAlertId); - - isolateHostWithComment(isolateComment, hostname); - - interceptActionRequests((responseBody) => { - isolateRequestResponse = responseBody; - }, 'isolate'); - - cy.getByTestSubj('hostIsolateConfirmButton').click(); - - cy.wait('@isolate').then(() => { - sendActionResponse(isolateRequestResponse); - }); - - cy.contains(`Isolation on host ${hostname} successfully submitted`); - - cy.getByTestSubj('euiFlyoutCloseButton').click(); - - cy.getByTestSubj('user-actions-list').within(() => { - cy.contains(isolateComment); - cy.get('[aria-label="lock"]').should('exist'); - cy.get('[aria-label="lockOpen"]').should('not.exist'); - }); - - waitForReleaseOption(caseAlertId); - - releaseHostWithComment(releaseComment, hostname); - - interceptActionRequests((responseBody) => { - releaseRequestResponse = responseBody; - }, 'release'); - - cy.contains('Confirm').click(); - - cy.wait('@release').then(() => { - sendActionResponse(releaseRequestResponse); - }); - - cy.contains(`Release on host ${hostname} successfully submitted`); - cy.getByTestSubj('euiFlyoutCloseButton').click(); - - cy.getByTestSubj('user-actions-list').within(() => { - cy.contains(releaseComment); - cy.contains(isolateComment); - cy.get('[aria-label="lock"]').should('exist'); - cy.get('[aria-label="lockOpen"]').should('exist'); - }); - - openCaseAlertDetails(caseAlertId); - cy.getByTestSubj('event-field-agent.status').then(($status) => { - if ($status.find('[title="Isolated"]').length > 0) { - cy.getByTestSubj('euiFlyoutCloseButton').click(); - cy.getByTestSubj(`comment-action-show-alert-${caseAlertId}`).click(); - cy.getByTestSubj('take-action-dropdown-btn').click(); - } - cy.get('[title="Isolated"]').should('not.exist'); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_details.cy.ts deleted file mode 100644 index 543ce48ffb79d..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_details.cy.ts +++ /dev/null @@ -1,225 +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 { getPolicySettingsFormTestSubjects } from '../../../pages/policy/view/policy_settings_form/mocks'; -import { ProtectionModes } from '../../../../../common/endpoint/types'; -import { - PackagePolicyBackupHelper, - savePolicyForm, - visitPolicyDetailsPage, - yieldPolicyConfig, -} from '../../screens/policy_details'; -import type { CyIndexEndpointHosts } from '../../tasks/index_endpoint_hosts'; -import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; -import { login } from '../../tasks/login'; - -describe('Policy Details', () => { - const packagePolicyBackupHelper = new PackagePolicyBackupHelper(); - let indexedHostsData: CyIndexEndpointHosts; - - before(() => { - login(); - indexEndpointHosts().then((results) => { - indexedHostsData = results; - }); - packagePolicyBackupHelper.backup(); - }); - - beforeEach(() => { - login(); - visitPolicyDetailsPage(indexedHostsData.data.integrationPolicies[0].id); - }); - - afterEach(() => { - packagePolicyBackupHelper.restore(); - }); - - after(() => { - indexedHostsData.cleanup(); - }); - - describe('Malware Protection card', () => { - const malwareTestSubj = getPolicySettingsFormTestSubjects().malware; - - it('user should be able to see related rules', () => { - cy.getByTestSubj(malwareTestSubj.card).contains('related detection rules').click(); - - cy.url().should('contain', 'app/security/rules/management'); - }); - - it('changing protection level should enable or disable user notification', () => { - cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); - cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should( - 'have.attr', - 'aria-checked', - 'true' - ); - - // Default: Prevent + Notify user enabled - cy.getByTestSubj(malwareTestSubj.protectionPreventRadio).find('input').should('be.checked'); - cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('be.checked'); - - // Changing to Detect -> Notify user disabled - cy.getByTestSubj(malwareTestSubj.protectionDetectRadio).find('label').click(); - cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('not.be.checked'); - - // Changing back to Prevent -> Notify user enabled - cy.getByTestSubj(malwareTestSubj.protectionPreventRadio).find('label').click(); - cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('be.checked'); - }); - - it('disabling protection should disable notification in yaml for every OS', () => { - // Enable malware protection and user notification - cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); - cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should( - 'have.attr', - 'aria-checked', - 'true' - ); - savePolicyForm(); - - yieldPolicyConfig().then((policyConfig) => { - expect(policyConfig.mac.popup.malware.enabled).to.equal(true); - expect(policyConfig.windows.popup.malware.enabled).to.equal(true); - expect(policyConfig.linux.popup.malware.enabled).to.equal(true); - }); - - // disable malware protection - cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); - cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should( - 'have.attr', - 'aria-checked', - 'false' - ); - savePolicyForm(); - - yieldPolicyConfig().then((policyConfig) => { - expect(policyConfig.mac.popup.malware.enabled).to.equal(false); - expect(policyConfig.windows.popup.malware.enabled).to.equal(false); - expect(policyConfig.linux.popup.malware.enabled).to.equal(false); - }); - }); - - it('user should be able to enable Malware protection for every OS in yaml', () => { - yieldPolicyConfig().then((policyConfig) => { - expect(policyConfig.linux.malware.mode).to.equal(ProtectionModes.off); - expect(policyConfig.mac.malware.mode).to.equal(ProtectionModes.off); - expect(policyConfig.windows.malware.mode).to.equal(ProtectionModes.off); - }); - - cy.getByTestSubj(malwareTestSubj.osValuesContainer).should( - 'contain.text', - 'Windows, Mac, Linux' - ); - - cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); - savePolicyForm(); - - yieldPolicyConfig().then((policyConfig) => { - expect(policyConfig.linux.malware.mode).to.equal(ProtectionModes.prevent); - expect(policyConfig.mac.malware.mode).to.equal(ProtectionModes.prevent); - expect(policyConfig.windows.malware.mode).to.equal(ProtectionModes.prevent); - }); - }); - }); - - describe('Ransomware Protection card', () => { - const ransomwareTestSubj = getPolicySettingsFormTestSubjects().ransomware; - - it('user should be able to see related rules', () => { - cy.getByTestSubj(ransomwareTestSubj.card).contains('related detection rules').click(); - - cy.url().should('contain', 'app/security/rules/management'); - }); - - it('changing protection level should enable or disable user notification', () => { - cy.getByTestSubj(ransomwareTestSubj.enableDisableSwitch).click(); - cy.getByTestSubj(ransomwareTestSubj.enableDisableSwitch).should( - 'have.attr', - 'aria-checked', - 'true' - ); - - // Default: Prevent + Notify user enabled - cy.getByTestSubj(ransomwareTestSubj.protectionPreventRadio) - .find('input') - .should('be.checked'); - cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('be.checked'); - - // Changing to Detect -> Notify user disabled - cy.getByTestSubj(ransomwareTestSubj.protectionDetectRadio).find('label').click(); - cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('not.be.checked'); - - // Changing back to Prevent -> Notify user enabled - cy.getByTestSubj(ransomwareTestSubj.protectionPreventRadio).find('label').click(); - cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('be.checked'); - }); - }); - - describe('Advanced settings', () => { - const testSubjects = getPolicySettingsFormTestSubjects().advancedSection; - - it('should show empty text inputs except for some settings', () => { - const settingsWithDefaultValues = [ - 'mac.advanced.capture_env_vars', - 'linux.advanced.capture_env_vars', - ]; - - cy.getByTestSubj(testSubjects.showHideButton).click(); - - cy.getByTestSubj(testSubjects.settingsContainer) - .children() - .each(($child) => { - const settingName = $child.find('label').text(); - - if (settingsWithDefaultValues.includes(settingName)) { - cy.wrap($child).find('input').should('not.have.value', ''); - } else { - cy.wrap($child).find('input').should('have.value', ''); - } - }); - }); - - it('should add advanced settings to policy yaml only when they are set', () => { - // Initially config should only contain the two default entries - yieldPolicyConfig().then((policyConfig) => { - expect(policyConfig.linux.advanced).to.have.keys(['capture_env_vars']); - expect(policyConfig.mac.advanced).to.have.keys(['capture_env_vars']); - expect(policyConfig.windows.advanced).to.equal(undefined); - }); - - // Set agent.connection_delay entry for every OS - cy.getByTestSubj(testSubjects.showHideButton).click(); - cy.getByTestSubj(testSubjects.settingsContainer) - .children() - .each(($child) => { - const settingName = $child.find('label').text(); - - if (settingName.includes('.agent.connection_delay')) { - cy.wrap($child).find('input').type('66'); - } - }); - savePolicyForm(); - - // Validate yaml - yieldPolicyConfig().then((policyConfig) => { - expect(policyConfig.linux.advanced).to.have.keys(['capture_env_vars', 'agent']); - expect(policyConfig.linux.advanced).to.have.deep.property('agent', { - connection_delay: '66', - }); - expect(policyConfig.mac.advanced).to.have.keys(['capture_env_vars', 'agent']); - expect(policyConfig.mac.advanced).to.have.deep.property('agent', { - connection_delay: '66', - }); - expect(policyConfig.windows.advanced).to.have.keys(['agent']); - expect(policyConfig.windows.advanced).to.have.deep.property('agent', { - connection_delay: '66', - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/reponse_actions_history.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/reponse_actions_history.cy.ts deleted file mode 100644 index cb6c7c3a96eee..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/reponse_actions_history.cy.ts +++ /dev/null @@ -1,53 +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 { ReturnTypeFromChainable } from '../../types'; -import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; -import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; - -describe('Response actions history page', () => { - let endpointData: ReturnTypeFromChainable; - // let actionData: ReturnTypeFromChainable; - - before(() => { - indexEndpointHosts({ numResponseActions: 11 }).then((indexEndpoints) => { - endpointData = indexEndpoints; - }); - }); - - beforeEach(() => { - login(); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('retains expanded action details on page reload', () => { - loadPage(`/app/security/administration/response_actions_history`); - cy.getByTestSubj('response-actions-list-expand-button').eq(3).click(); // 4th row on 1st page - cy.getByTestSubj('response-actions-list-details-tray').should('exist'); - cy.url().should('include', 'withOutputs'); - - // navigate to page 2 - cy.getByTestSubj('pagination-button-1').click(); - cy.getByTestSubj('response-actions-list-details-tray').should('not.exist'); - - // reload with URL params on page 2 with existing URL - cy.reload(); - cy.getByTestSubj('response-actions-list-details-tray').should('not.exist'); - - // navigate to page 1 - cy.getByTestSubj('pagination-button-0').click(); - cy.getByTestSubj('response-actions-list-details-tray').should('exist'); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/response_console.cy.ts deleted file mode 100644 index fa347d77581ae..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/response_console.cy.ts +++ /dev/null @@ -1,319 +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 { ActionDetails } from '../../../../../common/endpoint/types'; -import type { ReturnTypeFromChainable } from '../../types'; -import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; -import { - checkReturnedProcessesTable, - inputConsoleCommand, - openResponseConsoleFromEndpointList, - performCommandInputChecks, - submitCommand, - waitForEndpointListPageToBeLoaded, -} from '../../tasks/response_console'; -import { - checkEndpointIsIsolated, - checkEndpointIsNotIsolated, - interceptActionRequests, - sendActionResponse, -} from '../../tasks/isolate'; -import { login } from '../../tasks/login'; - -describe('Response console', () => { - beforeEach(() => { - login(); - }); - - describe('`isolate` command', () => { - let endpointData: ReturnTypeFromChainable; - let endpointHostname: string; - let isolateRequestResponse: ActionDetails; - - before(() => { - indexEndpointHosts({ withResponseActions: false, isolation: false }).then( - (indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostname = endpointData.data.hosts[0].host.name; - } - ); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('should isolate host from response console', () => { - waitForEndpointListPageToBeLoaded(endpointHostname); - checkEndpointIsNotIsolated(endpointHostname); - openResponseConsoleFromEndpointList(); - performCommandInputChecks('isolate'); - interceptActionRequests((responseBody) => { - isolateRequestResponse = responseBody; - }, 'isolate'); - - submitCommand(); - cy.contains('Action pending.').should('exist'); - cy.wait('@isolate').then(() => { - sendActionResponse(isolateRequestResponse); - }); - cy.contains('Action completed.', { timeout: 120000 }).should('exist'); - waitForEndpointListPageToBeLoaded(endpointHostname); - checkEndpointIsIsolated(endpointHostname); - }); - }); - - describe('`release` command', () => { - let endpointData: ReturnTypeFromChainable; - let endpointHostname: string; - let releaseRequestResponse: ActionDetails; - - before(() => { - indexEndpointHosts({ withResponseActions: false, isolation: true }).then((indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostname = endpointData.data.hosts[0].host.name; - }); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('should release host from response console', () => { - waitForEndpointListPageToBeLoaded(endpointHostname); - checkEndpointIsIsolated(endpointHostname); - openResponseConsoleFromEndpointList(); - performCommandInputChecks('release'); - interceptActionRequests((responseBody) => { - releaseRequestResponse = responseBody; - }, 'release'); - submitCommand(); - cy.contains('Action pending.').should('exist'); - cy.wait('@release').then(() => { - sendActionResponse(releaseRequestResponse); - }); - cy.contains('Action completed.', { timeout: 120000 }).should('exist'); - waitForEndpointListPageToBeLoaded(endpointHostname); - checkEndpointIsNotIsolated(endpointHostname); - }); - }); - - describe('`processes` command', () => { - let endpointData: ReturnTypeFromChainable; - let endpointHostname: string; - let processesRequestResponse: ActionDetails; - - before(() => { - indexEndpointHosts({ withResponseActions: false, isolation: false }).then( - (indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostname = endpointData.data.hosts[0].host.name; - } - ); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('should return processes from response console', () => { - waitForEndpointListPageToBeLoaded(endpointHostname); - openResponseConsoleFromEndpointList(); - performCommandInputChecks('processes'); - interceptActionRequests((responseBody) => { - processesRequestResponse = responseBody; - }, 'processes'); - submitCommand(); - cy.contains('Action pending.').should('exist'); - cy.wait('@processes').then(() => { - sendActionResponse(processesRequestResponse); - }); - cy.getByTestSubj('getProcessesSuccessCallout', { timeout: 120000 }).within(() => { - checkReturnedProcessesTable(); - }); - }); - }); - - describe('`kill-process` command', () => { - let endpointData: ReturnTypeFromChainable; - let endpointHostname: string; - let killProcessRequestResponse: ActionDetails; - - before(() => { - indexEndpointHosts({ withResponseActions: false, isolation: false }).then( - (indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostname = endpointData.data.hosts[0].host.name; - } - ); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('should kill process from response console', () => { - waitForEndpointListPageToBeLoaded(endpointHostname); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`kill-process --pid 1`); - - interceptActionRequests((responseBody) => { - killProcessRequestResponse = responseBody; - }, 'kill-process'); - submitCommand(); - cy.contains('Action pending.').should('exist'); - cy.wait('@kill-process').then(() => { - sendActionResponse(killProcessRequestResponse); - }); - cy.contains('Action completed.', { timeout: 120000 }).should('exist'); - }); - }); - - describe('`suspend-process` command', () => { - let endpointData: ReturnTypeFromChainable; - let endpointHostname: string; - let suspendProcessRequestResponse: ActionDetails; - - before(() => { - indexEndpointHosts({ withResponseActions: false, isolation: false }).then( - (indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostname = endpointData.data.hosts[0].host.name; - } - ); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('should suspend process from response console', () => { - waitForEndpointListPageToBeLoaded(endpointHostname); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`suspend-process --pid 1`); - - interceptActionRequests((responseBody) => { - suspendProcessRequestResponse = responseBody; - }, 'suspend-process'); - submitCommand(); - cy.contains('Action pending.').should('exist'); - cy.wait('@suspend-process').then(() => { - sendActionResponse(suspendProcessRequestResponse); - }); - cy.contains('Action completed.', { timeout: 120000 }).should('exist'); - }); - }); - - // Broken until this is fixed: https://github.com/elastic/kibana/issues/162760 - describe.skip('`get-file` command', () => { - let endpointData: ReturnTypeFromChainable; - let endpointHostname: string; - let getFileRequestResponse: ActionDetails; - - before(() => { - indexEndpointHosts({ withResponseActions: false, isolation: false }).then( - (indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostname = endpointData.data.hosts[0].host.name; - } - ); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('should get file from response console', () => { - waitForEndpointListPageToBeLoaded(endpointHostname); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`get-file --path /test/path/test.txt`); - - interceptActionRequests((responseBody) => { - getFileRequestResponse = responseBody; - }, 'get-file'); - submitCommand(); - cy.contains('Retrieving the file from host.').should('exist'); - cy.wait('@get-file').then(() => { - sendActionResponse(getFileRequestResponse); - }); - cy.getByTestSubj('getFileSuccess').within(() => { - cy.contains('File retrieved from the host.'); - cy.contains('(ZIP file passcode: elastic)'); - cy.contains( - 'Files are periodically deleted to clear storage space. Download and save file locally if needed.' - ); - cy.contains('Click here to download').click(); - }); - - const downloadsFolder = Cypress.config('downloadsFolder'); - cy.readFile(`${downloadsFolder}/upload.zip`); - }); - }); - - describe('`execute` command', () => { - let endpointData: ReturnTypeFromChainable; - let endpointHostname: string; - let executeRequestResponse: ActionDetails; - - before(() => { - indexEndpointHosts({ withResponseActions: false, isolation: false }).then( - (indexEndpoints) => { - endpointData = indexEndpoints; - endpointHostname = endpointData.data.hosts[0].host.name; - } - ); - }); - - after(() => { - if (endpointData) { - endpointData.cleanup(); - // @ts-expect-error ignore setting to undefined - endpointData = undefined; - } - }); - - it('should execute a command from response console', () => { - waitForEndpointListPageToBeLoaded(endpointHostname); - openResponseConsoleFromEndpointList(); - inputConsoleCommand(`execute --command "ls -al"`); - - interceptActionRequests((responseBody) => { - executeRequestResponse = responseBody; - }, 'execute'); - submitCommand(); - cy.contains('Action pending.').should('exist'); - cy.wait('@execute').then(() => { - sendActionResponse(executeRequestResponse); - }); - cy.contains('Command execution was successful', { timeout: 120000 }).should('exist'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts new file mode 100644 index 0000000000000..6e95c1ad73557 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment/moment'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import type { PolicyData } from '../../../../../common/endpoint/types'; +import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; +import { + setCustomProtectionUpdatesManifestVersion, + setCustomProtectionUpdatesNote, +} from '../../tasks/endpoint_policy'; +import { login, ROLE } from '../../tasks/login'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; + +describe( + 'Policy Details', + { + tags: '@ess', + env: { ftrConfig: { enableExperimental: ['protectionUpdatesEnabled'] } }, + }, + () => { + describe('Protection updates', () => { + const loadProtectionUpdatesUrl = (policyId: string) => + loadPage(`/app/security/administration/policy/${policyId}/protectionUpdates`); + const testNote = 'test note'; + const updatedTestNote = 'updated test note'; + + describe('Renders and saves protection updates', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + const today = moment.utc(); + const formattedToday = today.format('MMMM DD, YYYY'); + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should render the protection updates tab content', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-automatic-updates-enabled'); + cy.getByTestSubj('protection-updates-manifest-switch'); + cy.getByTestSubj('protection-updates-manifest-name-title'); + cy.getByTestSubj('protection-updates-manifest-name'); + + cy.getByTestSubj('protection-updates-manifest-switch').click(); + + cy.getByTestSubj('protection-updates-manifest-name-deployed-version-title'); + cy.getByTestSubj('protection-updates-deployed-version').contains('latest'); + cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); + cy.getByTestSubj('protection-updates-version-to-deploy-picker').within(() => { + cy.get('input').should('have.value', formattedToday); + }); + cy.getByTestSubj('protection-updates-manifest-name-note-title'); + cy.getByTestSubj('protection-updates-manifest-note'); + cy.getByTestSubj('policyDetailsSaveButton'); + }); + + it('should successfully update the manifest version to custom date', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-manifest-switch').click(); + cy.getByTestSubj('protection-updates-manifest-note').type(testNote); + + cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy'); + cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note'); + cy.getByTestSubj('policyDetailsSaveButton').click(); + cy.wait('@policy').then(({ request, response }) => { + expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( + today.format('YYYY-MM-DD') + ); + expect(response?.statusCode).to.equal(200); + }); + + cy.wait('@note').then(({ request, response }) => { + expect(request.body.note).to.equal(testNote); + expect(response?.statusCode).to.equal(200); + }); + + cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); + cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday); + cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); + }); + }); + + describe('Renders and saves protection updates with custom version', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + + const twoMonthsAgo = moment.utc().subtract(2, 'months').format('YYYY-MM-DD'); + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo); + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should update manifest version to latest when enabling automatic updates', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-manifest-outdated'); + cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy_latest'); + + cy.getByTestSubj('protection-updates-manifest-switch').click(); + cy.wait('@policy_latest').then(({ request, response }) => { + expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( + 'latest' + ); + expect(response?.statusCode).to.equal(200); + }); + cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); + cy.getByTestSubj('protection-updates-automatic-updates-enabled'); + }); + }); + + describe('Renders and saves protection updates with custom note', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + + const twoMonthsAgo = moment.utc().subtract(2, 'months').format('YYYY-MM-DD'); + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo); + setCustomProtectionUpdatesNote(policy.id, testNote); + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should update note on save', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); + cy.getByTestSubj('protection-updates-manifest-note').clear(); + cy.getByTestSubj('protection-updates-manifest-note').type(updatedTestNote); + + cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note_updated'); + cy.getByTestSubj('policyDetailsSaveButton').click(); + cy.wait('@note_updated').then(({ request, response }) => { + expect(request.body.note).to.equal(updatedTestNote); + expect(response?.statusCode).to.equal(200); + }); + cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); + cy.getByTestSubj('protection-updates-manifest-note').contains(updatedTestNote); + }); + }); + + describe('Renders read only protection updates for user without write permissions', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + const twoMonthsAgo = moment.utc().subtract(2, 'months'); + + beforeEach(() => { + login(ROLE.endpoint_security_policy_management_read); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + setCustomProtectionUpdatesManifestVersion( + policy.id, + twoMonthsAgo.format('YYYY-MM-DD') + ); + setCustomProtectionUpdatesNote(policy.id, testNote); + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should render the protection updates tab content', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-manifest-switch').should('not.exist'); + cy.getByTestSubj('protection-updates-state-view-mode'); + cy.getByTestSubj('protection-updates-manifest-name-title'); + cy.getByTestSubj('protection-updates-manifest-name'); + + cy.getByTestSubj('protection-updates-manifest-name-deployed-version-title'); + cy.getByTestSubj('protection-updates-deployed-version').contains( + twoMonthsAgo.format('MMMM DD, YYYY') + ); + cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); + cy.getByTestSubj('protection-updates-version-to-deploy-view-mode'); + cy.getByTestSubj('protection-updates-version-to-deploy-picker').should('not.exist'); + + cy.getByTestSubj('protection-updates-manifest-name-note-title'); + cy.getByTestSubj('protection-updates-manifest-note').should('not.exist'); + cy.getByTestSubj('protection-updates-manifest-note-view-mode').contains(testNote); + cy.getByTestSubj('policyDetailsSaveButton').should('be.disabled'); + }); + }); + }); + } +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details_mocked_data.cy.ts new file mode 100644 index 0000000000000..11800441fc035 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details_mocked_data.cy.ts @@ -0,0 +1,225 @@ +/* + * Copyright 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 { getPolicySettingsFormTestSubjects } from '../../../pages/policy/view/policy_settings_form/mocks'; +import { ProtectionModes } from '../../../../../common/endpoint/types'; +import { + PackagePolicyBackupHelper, + savePolicyForm, + visitPolicyDetailsPage, + yieldPolicyConfig, +} from '../../screens/policy_details'; +import type { CyIndexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { login } from '../../tasks/login'; + +describe('Policy Details', { tags: ['@ess', '@serverless'] }, () => { + const packagePolicyBackupHelper = new PackagePolicyBackupHelper(); + let indexedHostsData: CyIndexEndpointHosts; + + before(() => { + login(); + indexEndpointHosts().then((results) => { + indexedHostsData = results; + }); + packagePolicyBackupHelper.backup(); + }); + + beforeEach(() => { + login(); + visitPolicyDetailsPage(indexedHostsData.data.integrationPolicies[0].id); + }); + + afterEach(() => { + packagePolicyBackupHelper.restore(); + }); + + after(() => { + indexedHostsData.cleanup(); + }); + + describe('Malware Protection card', () => { + const malwareTestSubj = getPolicySettingsFormTestSubjects().malware; + + it('user should be able to see related rules', () => { + cy.getByTestSubj(malwareTestSubj.card).contains('related detection rules').click(); + + cy.url().should('contain', 'app/security/rules/management'); + }); + + it('changing protection level should enable or disable user notification', () => { + cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); + cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should( + 'have.attr', + 'aria-checked', + 'true' + ); + + // Default: Prevent + Notify user enabled + cy.getByTestSubj(malwareTestSubj.protectionPreventRadio).find('input').should('be.checked'); + cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('be.checked'); + + // Changing to Detect -> Notify user disabled + cy.getByTestSubj(malwareTestSubj.protectionDetectRadio).find('label').click(); + cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('not.be.checked'); + + // Changing back to Prevent -> Notify user enabled + cy.getByTestSubj(malwareTestSubj.protectionPreventRadio).find('label').click(); + cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('be.checked'); + }); + + it('disabling protection should disable notification in yaml for every OS', () => { + // Enable malware protection and user notification + cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); + cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should( + 'have.attr', + 'aria-checked', + 'true' + ); + savePolicyForm(); + + yieldPolicyConfig().then((policyConfig) => { + expect(policyConfig.mac.popup.malware.enabled).to.equal(true); + expect(policyConfig.windows.popup.malware.enabled).to.equal(true); + expect(policyConfig.linux.popup.malware.enabled).to.equal(true); + }); + + // disable malware protection + cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); + cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should( + 'have.attr', + 'aria-checked', + 'false' + ); + savePolicyForm(); + + yieldPolicyConfig().then((policyConfig) => { + expect(policyConfig.mac.popup.malware.enabled).to.equal(false); + expect(policyConfig.windows.popup.malware.enabled).to.equal(false); + expect(policyConfig.linux.popup.malware.enabled).to.equal(false); + }); + }); + + it('user should be able to enable Malware protection for every OS in yaml', () => { + yieldPolicyConfig().then((policyConfig) => { + expect(policyConfig.linux.malware.mode).to.equal(ProtectionModes.off); + expect(policyConfig.mac.malware.mode).to.equal(ProtectionModes.off); + expect(policyConfig.windows.malware.mode).to.equal(ProtectionModes.off); + }); + + cy.getByTestSubj(malwareTestSubj.osValuesContainer).should( + 'contain.text', + 'Windows, Mac, Linux' + ); + + cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click(); + savePolicyForm(); + + yieldPolicyConfig().then((policyConfig) => { + expect(policyConfig.linux.malware.mode).to.equal(ProtectionModes.prevent); + expect(policyConfig.mac.malware.mode).to.equal(ProtectionModes.prevent); + expect(policyConfig.windows.malware.mode).to.equal(ProtectionModes.prevent); + }); + }); + }); + + describe('Ransomware Protection card', () => { + const ransomwareTestSubj = getPolicySettingsFormTestSubjects().ransomware; + + it('user should be able to see related rules', () => { + cy.getByTestSubj(ransomwareTestSubj.card).contains('related detection rules').click(); + + cy.url().should('contain', 'app/security/rules/management'); + }); + + it('changing protection level should enable or disable user notification', () => { + cy.getByTestSubj(ransomwareTestSubj.enableDisableSwitch).click(); + cy.getByTestSubj(ransomwareTestSubj.enableDisableSwitch).should( + 'have.attr', + 'aria-checked', + 'true' + ); + + // Default: Prevent + Notify user enabled + cy.getByTestSubj(ransomwareTestSubj.protectionPreventRadio) + .find('input') + .should('be.checked'); + cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('be.checked'); + + // Changing to Detect -> Notify user disabled + cy.getByTestSubj(ransomwareTestSubj.protectionDetectRadio).find('label').click(); + cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('not.be.checked'); + + // Changing back to Prevent -> Notify user enabled + cy.getByTestSubj(ransomwareTestSubj.protectionPreventRadio).find('label').click(); + cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('be.checked'); + }); + }); + + describe('Advanced settings', () => { + const testSubjects = getPolicySettingsFormTestSubjects().advancedSection; + + it('should show empty text inputs except for some settings', () => { + const settingsWithDefaultValues = [ + 'mac.advanced.capture_env_vars', + 'linux.advanced.capture_env_vars', + ]; + + cy.getByTestSubj(testSubjects.showHideButton).click(); + + cy.getByTestSubj(testSubjects.settingsContainer) + .children() + .each(($child) => { + const settingName = $child.find('label').text(); + + if (settingsWithDefaultValues.includes(settingName)) { + cy.wrap($child).find('input').should('not.have.value', ''); + } else { + cy.wrap($child).find('input').should('have.value', ''); + } + }); + }); + + it('should add advanced settings to policy yaml only when they are set', () => { + // Initially config should only contain the two default entries + yieldPolicyConfig().then((policyConfig) => { + expect(policyConfig.linux.advanced).to.have.keys(['capture_env_vars']); + expect(policyConfig.mac.advanced).to.have.keys(['capture_env_vars']); + expect(policyConfig.windows.advanced).to.equal(undefined); + }); + + // Set agent.connection_delay entry for every OS + cy.getByTestSubj(testSubjects.showHideButton).click(); + cy.getByTestSubj(testSubjects.settingsContainer) + .children() + .each(($child) => { + const settingName = $child.find('label').text(); + + if (settingName.includes('.agent.connection_delay')) { + cy.wrap($child).find('input').type('66'); + } + }); + savePolicyForm(); + + // Validate yaml + yieldPolicyConfig().then((policyConfig) => { + expect(policyConfig.linux.advanced).to.have.keys(['capture_env_vars', 'agent']); + expect(policyConfig.linux.advanced).to.have.deep.property('agent', { + connection_delay: '66', + }); + expect(policyConfig.mac.advanced).to.have.keys(['capture_env_vars', 'agent']); + expect(policyConfig.mac.advanced).to.have.deep.property('agent', { + connection_delay: '66', + }); + expect(policyConfig.windows.advanced).to.have.keys(['agent']); + expect(policyConfig.windows.advanced).to.have.deep.property('agent', { + connection_delay: '66', + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_experimental_features_disabled.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_experimental_features_disabled.cy.ts new file mode 100644 index 0000000000000..1d072bad8a68b --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_experimental_features_disabled.cy.ts @@ -0,0 +1,98 @@ +/* + * Copyright 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 { PolicyData } from '../../../../../common/endpoint/types'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; + +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { login } from '../../tasks/login'; +import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; + +describe('Disabled experimental features on: ', () => { + describe('Policy list', () => { + describe('Renders policy list without protection updates feature flag', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should render the list', () => { + loadPage('/app/security/administration/policy'); + cy.getByTestSubj('tableHeaderCell_Name_0'); + cy.getByTestSubj('tableHeaderCell_Deployed Version_1').should('not.exist'); + cy.getByTestSubj('tableHeaderCell_created_by_1'); + cy.getByTestSubj('tableHeaderCell_created_at_2'); + cy.getByTestSubj('tableHeaderCell_updated_by_3'); + cy.getByTestSubj('tableHeaderCell_updated_at_4'); + cy.getByTestSubj('tableHeaderCell_Endpoints_5'); + cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('not.exist'); + cy.getByTestSubj('policyDeployedVersion').should('not.exist'); + }); + }); + }); + + describe('Policy details', () => { + describe('Renders policy details without protection updates feature flag', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should return 404 on policyUpdates url', () => { + loadPage(`/app/security/administration/policy/${policy.id}/protectionUpdates`); + cy.getByTestSubj('notFoundPage'); + cy.getByTestSubj('protection-updates-automatic-updates-enabled').should('not.exist'); + }); + + it('should render policy details without protection updates tab', () => { + loadPage(`/app/security/administration/policy/${policy.id}`); + cy.get('div[role="tablist"]').within(() => { + cy.contains('Protection updates').should('not.exist'); + cy.get('#settings'); + cy.get('#trustedApps'); + cy.get('#hostIsolationExceptions'); + cy.get('#blocklists'); + cy.get('#protectionUpdates').should('not.exist'); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_list.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_list.cy.ts new file mode 100644 index 0000000000000..b3e5e0477c5be --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_list.cy.ts @@ -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 moment from 'moment'; +import { setCustomProtectionUpdatesManifestVersion } from '../../tasks/endpoint_policy'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; + +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { login } from '../../tasks/login'; +import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; + +describe( + 'Policy List', + { + env: { ftrConfig: { enableExperimental: ['protectionUpdatesEnabled'] } }, + }, + () => { + describe('Renders policy list with outdated policies', () => { + const indexedPolicies: IndexedFleetEndpointPolicyResponse[] = []; + + const monthAgo = moment.utc().subtract(1, 'months').format('YYYY-MM-DD'); + const threeDaysAgo = moment.utc().subtract(3, 'days').format('YYYY-MM-DD'); + const eighteenMonthsAgo = moment + .utc() + .subtract(18, 'months') + .add(1, 'day') + .format('YYYY-MM-DD'); + const dates = [monthAgo, threeDaysAgo, eighteenMonthsAgo]; + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + for (let i = 0; i < 4; i++) { + createAgentPolicyTask(version).then((data) => { + indexedPolicies.push(data); + if (dates[i]) { + setCustomProtectionUpdatesManifestVersion(data.integrationPolicies[0].id, dates[i]); + } + }); + } + }); + }); + + after(() => { + if (indexedPolicies.length) { + indexedPolicies.forEach((policy) => { + cy.task('deleteIndexedFleetEndpointPolicies', policy); + }); + } + }); + + it('should render the policy list', () => { + loadPage('/app/security/administration/policy'); + cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('contain', '2 policies'); + dates.forEach((date) => { + cy.contains(moment.utc(date, 'YYYY-MM-DD').format('MMMM DD, YYYY')); + }); + cy.getByTestSubj('policyDeployedVersion').should('have.length', 4); + }); + }); + + describe('Renders policy list with no outdated policies', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should render the list', () => { + loadPage('/app/security/administration/policy'); + cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('not.exist'); + cy.getByTestSubj('policyDeployedVersion').should('have.length', 1); + cy.getByTestSubj('policyDeployedVersion').should('have.text', 'latest'); + }); + }); + } +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/isolate.cy.ts new file mode 100644 index 0000000000000..f09e0b462a75d --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/isolate.cy.ts @@ -0,0 +1,261 @@ +/* + * Copyright 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 { PolicyData } from '../../../../../common/endpoint/types'; +import { APP_CASES_PATH, APP_ENDPOINTS_PATH } from '../../../../../common/constants'; +import { closeAllToasts } from '../../tasks/toasts'; +import { + checkEndpointListForOnlyIsolatedHosts, + checkEndpointListForOnlyUnIsolatedHosts, + checkFlyoutEndpointIsolation, + filterOutIsolatedHosts, + isolateHostWithComment, + openAlertDetails, + openCaseAlertDetails, + releaseHostWithComment, + toggleRuleOffAndOn, + visitRuleAlerts, + waitForReleaseOption, +} from '../../tasks/isolate'; +import { cleanupCase, cleanupRule, loadCase, loadRule } from '../../tasks/api_fixtures'; +import { login } from '../../tasks/login'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; +import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; +import { createEndpointHost } from '../../tasks/create_endpoint_host'; +import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; +import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; + +describe.skip('Isolate command', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { + let isolateComment: string; + let releaseComment: string; + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + isolateComment = `Isolating ${host.hostname}`; + releaseComment = `Releasing ${host.hostname}`; + }); + }); + }); + }); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + describe('From manage', () => { + it('should allow filtering endpoint by Isolated status', () => { + loadPage(APP_ENDPOINTS_PATH); + closeAllToasts(); + cy.getByTestSubj('globalLoadingIndicator-hidden').should('exist'); + checkEndpointListForOnlyUnIsolatedHosts(); + + filterOutIsolatedHosts(); + cy.contains('No items found'); + cy.getByTestSubj('adminSearchBar').type('{selectall}{backspace}'); + cy.getByTestSubj('querySubmitButton').click(); + cy.getByTestSubj('endpointTableRowActions').click(); + cy.getByTestSubj('isolateLink').click(); + + cy.contains(`Isolate host ${createdHost.hostname} from network.`); + cy.getByTestSubj('endpointHostIsolationForm'); + cy.getByTestSubj('host_isolation_comment').type(isolateComment); + cy.getByTestSubj('hostIsolateConfirmButton').click(); + cy.contains(`Isolation on host ${createdHost.hostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.getByTestSubj('rowHostStatus-actionStatuses').should('contain.text', 'Isolated'); + filterOutIsolatedHosts(); + + checkEndpointListForOnlyIsolatedHosts(); + + cy.getByTestSubj('endpointTableRowActions').click(); + cy.getByTestSubj('unIsolateLink').click(); + releaseHostWithComment(releaseComment, createdHost.hostname); + cy.contains('Confirm').click(); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.getByTestSubj('adminSearchBar').type('{selectall}{backspace}'); + cy.getByTestSubj('querySubmitButton').click(); + checkEndpointListForOnlyUnIsolatedHosts(); + }); + }); + + describe('From alerts', () => { + let ruleId: string; + let ruleName: string; + + before(() => { + loadRule( + { query: `agent.name: ${createdHost.hostname} and agent.type: endpoint` }, + false + ).then((data) => { + ruleId = data.id; + ruleName = data.name; + }); + }); + + after(() => { + if (ruleId) { + cleanupRule(ruleId); + } + }); + + it('should isolate and release host', () => { + loadPage(APP_ENDPOINTS_PATH); + cy.contains(createdHost.hostname).should('exist'); + + toggleRuleOffAndOn(ruleName); + visitRuleAlerts(ruleName); + + closeAllToasts(); + openAlertDetails(); + + isolateHostWithComment(isolateComment, createdHost.hostname); + + cy.getByTestSubj('hostIsolateConfirmButton').click(); + cy.contains(`Isolation on host ${createdHost.hostname} successfully submitted`); + + cy.getByTestSubj('euiFlyoutCloseButton').click(); + openAlertDetails(); + + checkFlyoutEndpointIsolation(); + + releaseHostWithComment(releaseComment, createdHost.hostname); + cy.contains('Confirm').click(); + + cy.contains(`Release on host ${createdHost.hostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + openAlertDetails(); + cy.getByTestSubj('event-field-agent.status').within(() => { + cy.get('[title="Isolated"]').should('not.exist'); + }); + }); + }); + + describe('From cases', () => { + let ruleId: string; + let ruleName: string; + let caseId: string; + + const caseOwner = 'securitySolution'; + + before(() => { + loadRule( + { query: `agent.name: ${createdHost.hostname} and agent.type: endpoint` }, + false + ).then((data) => { + ruleId = data.id; + ruleName = data.name; + }); + loadCase(caseOwner).then((data) => { + caseId = data.id; + }); + }); + + beforeEach(() => { + login(); + }); + + after(() => { + if (ruleId) { + cleanupRule(ruleId); + } + if (caseId) { + cleanupCase(caseId); + } + }); + + it('should isolate and release host', () => { + loadPage(APP_ENDPOINTS_PATH); + cy.contains(createdHost.hostname).should('exist'); + + toggleRuleOffAndOn(ruleName); + + visitRuleAlerts(ruleName); + closeAllToasts(); + + openAlertDetails(); + + cy.getByTestSubj('add-to-existing-case-action').click(); + cy.getByTestSubj(`cases-table-row-select-${caseId}`).click(); + cy.contains(`An alert was added to \"Test ${caseOwner} case`); + + cy.intercept('GET', `/api/cases/${caseId}/user_actions/_find*`).as('case'); + loadPage(`${APP_CASES_PATH}/${caseId}`); + cy.wait('@case', { timeout: 30000 }).then(({ response: res }) => { + const caseAlertId = res?.body.userActions[1].id; + + closeAllToasts(); + openCaseAlertDetails(caseAlertId); + isolateHostWithComment(isolateComment, createdHost.hostname); + cy.getByTestSubj('hostIsolateConfirmButton').click(); + + cy.getByTestSubj('euiFlyoutCloseButton').click(); + + cy.getByTestSubj('user-actions-list').within(() => { + cy.contains(isolateComment); + cy.get('[aria-label="lock"]').should('exist'); + cy.get('[aria-label="lockOpen"]').should('not.exist'); + }); + + waitForReleaseOption(caseAlertId); + + releaseHostWithComment(releaseComment, createdHost.hostname); + + cy.contains('Confirm').click(); + + cy.contains(`Release on host ${createdHost.hostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + + cy.getByTestSubj('user-actions-list').within(() => { + cy.contains(releaseComment); + cy.contains(isolateComment); + cy.get('[aria-label="lock"]').should('exist'); + cy.get('[aria-label="lockOpen"]').should('exist'); + }); + + openCaseAlertDetails(caseAlertId); + + cy.getByTestSubj('event-field-agent.status').then(($status) => { + if ($status.find('[title="Isolated"]').length > 0) { + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.getByTestSubj(`comment-action-show-alert-${caseAlertId}`).click(); + cy.getByTestSubj('take-action-dropdown-btn').click(); + } + cy.get('[title="Isolated"]').should('not.exist'); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/isolate_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/isolate_mocked_data.cy.ts new file mode 100644 index 0000000000000..8998ec3cc32b7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/isolate_mocked_data.cy.ts @@ -0,0 +1,323 @@ +/* + * Copyright 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 { getEndpointListPath } from '../../../common/routing'; +import { + checkEndpointIsIsolated, + checkFlyoutEndpointIsolation, + filterOutIsolatedHosts, + interceptActionRequests, + isolateHostWithComment, + openAlertDetails, + openCaseAlertDetails, + releaseHostWithComment, + sendActionResponse, + waitForReleaseOption, +} from '../../tasks/isolate'; +import type { ActionDetails } from '../../../../../common/endpoint/types'; +import { closeAllToasts } from '../../tasks/toasts'; +import type { ReturnTypeFromChainable } from '../../types'; +import { addAlertsToCase } from '../../tasks/add_alerts_to_case'; +import { APP_ALERTS_PATH, APP_CASES_PATH, APP_PATH } from '../../../../../common/constants'; +import { login } from '../../tasks/login'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; +import { indexNewCase } from '../../tasks/index_new_case'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; + +describe('Isolate command', { tags: '@ess' }, () => { + describe('from Manage', () => { + let endpointData: ReturnTypeFromChainable | undefined; + let isolatedEndpointData: ReturnTypeFromChainable | undefined; + let isolatedEndpointHostnames: [string, string]; + let endpointHostnames: [string, string]; + + before(() => { + indexEndpointHosts({ + count: 2, + withResponseActions: false, + isolation: false, + }).then((indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostnames = [ + endpointData.data.hosts[0].host.name, + endpointData.data.hosts[1].host.name, + ]; + }); + + indexEndpointHosts({ + count: 2, + withResponseActions: false, + isolation: true, + }).then((indexEndpoints) => { + isolatedEndpointData = indexEndpoints; + isolatedEndpointHostnames = [ + isolatedEndpointData.data.hosts[0].host.name, + isolatedEndpointData.data.hosts[1].host.name, + ]; + }); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + endpointData = undefined; + } + + if (isolatedEndpointData) { + isolatedEndpointData.cleanup(); + isolatedEndpointData = undefined; + } + }); + + beforeEach(() => { + login(); + }); + + it('should allow filtering endpoint by Isolated status', () => { + loadPage(APP_PATH + getEndpointListPath({ name: 'endpointList' })); + closeAllToasts(); + filterOutIsolatedHosts(); + isolatedEndpointHostnames.forEach(checkEndpointIsIsolated); + endpointHostnames.forEach((hostname) => { + cy.contains(hostname).should('not.exist'); + }); + }); + }); + + describe.skip('from Alerts', () => { + let endpointData: ReturnTypeFromChainable | undefined; + let alertData: ReturnTypeFromChainable | undefined; + let hostname: string; + + before(() => { + disableExpandableFlyoutAdvancedSettings(); + indexEndpointHosts({ withResponseActions: false, isolation: false }).then( + (indexEndpoints) => { + endpointData = indexEndpoints; + hostname = endpointData.data.hosts[0].host.name; + + return indexEndpointRuleAlerts({ + endpointAgentId: endpointData.data.hosts[0].agent.id, + endpointHostname: endpointData.data.hosts[0].host.name, + endpointIsolated: false, + }); + } + ); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + endpointData = undefined; + } + + if (alertData) { + alertData.cleanup(); + alertData = undefined; + } + }); + + beforeEach(() => { + login(); + }); + + it('should isolate and release host', () => { + const isolateComment = `Isolating ${hostname}`; + const releaseComment = `Releasing ${hostname}`; + let isolateRequestResponse: ActionDetails; + let releaseRequestResponse: ActionDetails; + + loadPage(APP_ALERTS_PATH); + closeAllToasts(); + + cy.getByTestSubj('alertsTable').within(() => { + cy.getByTestSubj('expand-event') + .first() + .within(() => { + cy.get(`[data-is-loading="true"]`).should('exist'); + }); + cy.getByTestSubj('expand-event') + .first() + .within(() => { + cy.get(`[data-is-loading="true"]`).should('not.exist'); + }); + }); + + openAlertDetails(); + + isolateHostWithComment(isolateComment, hostname); + + interceptActionRequests((responseBody) => { + isolateRequestResponse = responseBody; + }, 'isolate'); + + cy.getByTestSubj('hostIsolateConfirmButton').click(); + + cy.wait('@isolate').then(() => { + sendActionResponse(isolateRequestResponse); + }); + + cy.contains(`Isolation on host ${hostname} successfully submitted`); + + cy.getByTestSubj('euiFlyoutCloseButton').click(); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(1000); + openAlertDetails(); + + checkFlyoutEndpointIsolation(); + + releaseHostWithComment(releaseComment, hostname); + + interceptActionRequests((responseBody) => { + releaseRequestResponse = responseBody; + }, 'release'); + + cy.contains('Confirm').click(); + + cy.wait('@release').then(() => { + sendActionResponse(releaseRequestResponse); + }); + + cy.contains(`Release on host ${hostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + openAlertDetails(); + cy.getByTestSubj('event-field-agent.status').within(() => { + cy.get('[title="Isolated"]').should('not.exist'); + }); + }); + }); + + describe('from Cases', () => { + let endpointData: ReturnTypeFromChainable | undefined; + let caseData: ReturnTypeFromChainable | undefined; + let alertData: ReturnTypeFromChainable | undefined; + let caseAlertActions: ReturnType; + let alertId: string; + let caseUrlPath: string; + let hostname: string; + + before(() => { + indexNewCase().then((indexCase) => { + caseData = indexCase; + caseUrlPath = `${APP_CASES_PATH}/${indexCase.data.id}`; + }); + + indexEndpointHosts({ withResponseActions: false, isolation: false }) + .then((indexEndpoints) => { + endpointData = indexEndpoints; + hostname = endpointData.data.hosts[0].host.name; + + return indexEndpointRuleAlerts({ + endpointAgentId: endpointData.data.hosts[0].agent.id, + endpointHostname: endpointData.data.hosts[0].host.name, + endpointIsolated: false, + }); + }) + .then((indexedAlert) => { + alertData = indexedAlert; + alertId = alertData.alerts[0]._id; + + if (caseData) { + caseAlertActions = addAlertsToCase({ + caseId: caseData.data.id, + alertIds: [alertId], + }); + } + }); + }); + + after(() => { + if (caseData) { + caseData.cleanup(); + caseData = undefined; + } + + if (endpointData) { + endpointData.cleanup(); + endpointData = undefined; + } + + if (alertData) { + alertData.cleanup(); + alertData = undefined; + } + }); + + beforeEach(() => { + login(); + }); + + it('should isolate and release host', () => { + let isolateRequestResponse: ActionDetails; + let releaseRequestResponse: ActionDetails; + const isolateComment = `Isolating ${hostname}`; + const releaseComment = `Releasing ${hostname}`; + const caseAlertId = caseAlertActions.comments[alertId]; + + loadPage(caseUrlPath); + closeAllToasts(); + openCaseAlertDetails(caseAlertId); + + isolateHostWithComment(isolateComment, hostname); + + interceptActionRequests((responseBody) => { + isolateRequestResponse = responseBody; + }, 'isolate'); + + cy.getByTestSubj('hostIsolateConfirmButton').click(); + + cy.wait('@isolate').then(() => { + sendActionResponse(isolateRequestResponse); + }); + + cy.contains(`Isolation on host ${hostname} successfully submitted`); + + cy.getByTestSubj('euiFlyoutCloseButton').click(); + + cy.getByTestSubj('user-actions-list').within(() => { + cy.contains(isolateComment); + cy.get('[aria-label="lock"]').should('exist'); + cy.get('[aria-label="lockOpen"]').should('not.exist'); + }); + + waitForReleaseOption(caseAlertId); + + releaseHostWithComment(releaseComment, hostname); + + interceptActionRequests((responseBody) => { + releaseRequestResponse = responseBody; + }, 'release'); + + cy.contains('Confirm').click(); + + cy.wait('@release').then(() => { + sendActionResponse(releaseRequestResponse); + }); + + cy.contains(`Release on host ${hostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + + cy.getByTestSubj('user-actions-list').within(() => { + cy.contains(releaseComment); + cy.contains(isolateComment); + cy.get('[aria-label="lock"]').should('exist'); + cy.get('[aria-label="lockOpen"]').should('exist'); + }); + + openCaseAlertDetails(caseAlertId); + cy.getByTestSubj('event-field-agent.status').then(($status) => { + if ($status.find('[title="Isolated"]').length > 0) { + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.getByTestSubj(`comment-action-show-alert-${caseAlertId}`).click(); + cy.getByTestSubj('take-action-dropdown-btn').click(); + } + cy.get('[title="Isolated"]').should('not.exist'); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/reponse_actions_history.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/reponse_actions_history.cy.ts new file mode 100644 index 0000000000000..7d1fe8b40a51e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/reponse_actions_history.cy.ts @@ -0,0 +1,74 @@ +/* + * Copyright 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 { ReturnTypeFromChainable } from '../../types'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { login } from '../../tasks/login'; +import { loadPage } from '../../tasks/common'; + +describe('Response actions history page', { tags: '@ess' }, () => { + let endpointData: ReturnTypeFromChainable; + // let actionData: ReturnTypeFromChainable; + + before(() => { + indexEndpointHosts({ numResponseActions: 11 }).then((indexEndpoints) => { + endpointData = indexEndpoints; + }); + }); + + beforeEach(() => { + login(); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('retains expanded action details on page reload', () => { + loadPage(`/app/security/administration/response_actions_history`); + cy.getByTestSubj('response-actions-list-expand-button').eq(3).click(); // 4th row on 1st page + cy.getByTestSubj('response-actions-list-details-tray').should('exist'); + cy.url().should('include', 'withOutputs'); + + // navigate to page 2 + cy.getByTestSubj('pagination-button-1').click(); + cy.getByTestSubj('response-actions-list-details-tray').should('not.exist'); + + // reload with URL params on page 2 with existing URL + cy.reload(); + cy.getByTestSubj('response-actions-list-details-tray').should('not.exist'); + + // navigate to page 1 + cy.getByTestSubj('pagination-button-0').click(); + cy.getByTestSubj('response-actions-list-details-tray').should('exist'); + }); + + it('collapses expanded tray with a single click', () => { + loadPage(`/app/security/administration/response_actions_history`); + // 2nd row on 1st page + cy.getByTestSubj('response-actions-list-expand-button').eq(1).as('2nd-row'); + + // expand the row + cy.get('@2nd-row').click(); + cy.getByTestSubj('response-actions-list-details-tray').should('exist'); + cy.url().should('include', 'withOutputs'); + + // collapse the row + cy.intercept('GET', '/api/endpoint/action*').as('getResponses'); + cy.get('@2nd-row').click(); + // wait for the API response to come back + // and then see if the tray is actually closed + cy.wait('@getResponses', { timeout: 500 }).then(() => { + cy.getByTestSubj('response-actions-list-details-tray').should('not.exist'); + cy.url().should('not.include', 'withOutputs'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/responder.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts similarity index 97% rename from x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/responder.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts index 60775450c35e0..1e699300d1dab 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/responder.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts @@ -21,7 +21,7 @@ import { indexNewCase } from '../../tasks/index_new_case'; import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; -describe('When accessing Endpoint Response Console', () => { +describe('When accessing Endpoint Response Console', { tags: '@ess' }, () => { const performResponderSanityChecks = () => { openResponderActionLogFlyout(); // Ensure the popover in the action log date quick select picker is accessible diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console.cy.ts new file mode 100644 index 0000000000000..f1d6af17a3146 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console.cy.ts @@ -0,0 +1,331 @@ +/* + * Copyright 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 { PolicyData } from '../../../../../common/endpoint/types'; +import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; +import { + inputConsoleCommand, + openResponseConsoleFromEndpointList, + performCommandInputChecks, + submitCommand, + waitForCommandToBeExecuted, + waitForEndpointListPageToBeLoaded, +} from '../../tasks/response_console'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { getEndpointIntegrationVersion, createAgentPolicyTask } from '../../tasks/fleet'; +import { + checkEndpointListForOnlyIsolatedHosts, + checkEndpointListForOnlyUnIsolatedHosts, +} from '../../tasks/isolate'; + +import { login } from '../../tasks/login'; +import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; +import { createEndpointHost } from '../../tasks/create_endpoint_host'; +import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; + +describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { + beforeEach(() => { + login(); + }); + + describe('User journey for Isolate command: isolate and release an endpoint', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }) + ); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + it('should isolate host from response console', () => { + const command = 'isolate'; + waitForEndpointListPageToBeLoaded(createdHost.hostname); + checkEndpointListForOnlyUnIsolatedHosts(); + openResponseConsoleFromEndpointList(); + performCommandInputChecks(command); + submitCommand(); + waitForCommandToBeExecuted(command); + waitForEndpointListPageToBeLoaded(createdHost.hostname); + checkEndpointListForOnlyIsolatedHosts(); + }); + + it('should release host from response console', () => { + const command = 'release'; + waitForEndpointListPageToBeLoaded(createdHost.hostname); + checkEndpointListForOnlyIsolatedHosts(); + openResponseConsoleFromEndpointList(); + performCommandInputChecks(command); + submitCommand(); + waitForCommandToBeExecuted(command); + waitForEndpointListPageToBeLoaded(createdHost.hostname); + checkEndpointListForOnlyUnIsolatedHosts(); + }); + }); + + describe('User journey for Processes operations: list, kill and suspend process', () => { + let cronPID: string; + let newCronPID: string; + + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }) + ); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + it('"processes" - should obtain a list of processes', () => { + waitForEndpointListPageToBeLoaded(createdHost.hostname); + openResponseConsoleFromEndpointList(); + performCommandInputChecks('processes'); + submitCommand(); + cy.contains('Action pending.').should('exist'); + cy.getByTestSubj('getProcessesSuccessCallout', { timeout: 120000 }).within(() => { + ['USER', 'PID', 'ENTITY ID', 'COMMAND'].forEach((header) => { + cy.contains(header); + }); + + cy.get('tbody > tr').should('have.length.greaterThan', 0); + cy.get('tbody > tr > td').should('contain', '/usr/sbin/cron'); + cy.get('tbody > tr > td') + .contains('/usr/sbin/cron') + .parents('td') + .siblings('td') + .eq(1) + .find('span') + .then((span) => { + cronPID = span.text(); + }); + }); + }); + + it('"kill-process --pid" - should kill a process', () => { + waitForEndpointListPageToBeLoaded(createdHost.hostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`kill-process --pid ${cronPID}`); + submitCommand(); + waitForCommandToBeExecuted('kill-process'); + + performCommandInputChecks('processes'); + submitCommand(); + + cy.getByTestSubj('getProcessesSuccessCallout', { timeout: 120000 }).within(() => { + cy.get('tbody > tr > td') + .contains('/usr/sbin/cron') + .parents('td') + .siblings('td') + .eq(1) + .find('span') + .then((span) => { + newCronPID = span.text(); + }); + }); + expect(newCronPID).to.not.equal(cronPID); + }); + + it('"suspend-process --pid" - should suspend a process', () => { + waitForEndpointListPageToBeLoaded(createdHost.hostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`suspend-process --pid ${newCronPID}`); + submitCommand(); + waitForCommandToBeExecuted('suspend-process'); + }); + }); + + describe('File operations: get-file and execute', () => { + const homeFilePath = process.env.CI || true ? '/home/vagrant' : `/home/ubuntu`; + + const fileContent = 'This is a test file for the get-file command.'; + const filePath = `${homeFilePath}/test_file.txt`; + + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }) + ); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + it('"get-file --path" - should retrieve a file', () => { + waitForEndpointListPageToBeLoaded(createdHost.hostname); + cy.task('createFileOnEndpoint', { + hostname: createdHost.hostname, + path: filePath, + content: fileContent, + }); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`get-file --path ${filePath}`); + submitCommand(); + cy.getByTestSubj('getFileSuccess', { timeout: 60000 }).within(() => { + cy.contains('File retrieved from the host.'); + cy.contains('(ZIP file passcode: elastic)'); + cy.contains( + 'Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + cy.contains('Click here to download').click(); + const downloadsFolder = Cypress.config('downloadsFolder'); + cy.readFile(`${downloadsFolder}/upload.zip`); + + cy.task('uploadFileToEndpoint', { + hostname: createdHost.hostname, + srcPath: `${downloadsFolder}/upload.zip`, + destPath: `${homeFilePath}/upload.zip`, + }); + + cy.task('readZippedFileContentOnEndpoint', { + hostname: createdHost.hostname, + path: `${homeFilePath}/upload.zip`, + password: 'elastic', + }).then((unzippedFileContent) => { + expect(unzippedFileContent).to.equal(fileContent); + }); + }); + }); + + it('"execute --command" - should execute a command', () => { + waitForEndpointListPageToBeLoaded(createdHost.hostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`execute --command "ls -al ${homeFilePath}"`); + submitCommand(); + waitForCommandToBeExecuted('execute'); + }); + }); + + describe('document signing', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return createEndpointHost(policy.policy_id).then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }) + ); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + it('should fail if data tampered', () => { + waitForEndpointListPageToBeLoaded(createdHost.hostname); + checkEndpointListForOnlyUnIsolatedHosts(); + openResponseConsoleFromEndpointList(); + performCommandInputChecks('isolate'); + + // stop host so that we ensure tamper happens before endpoint processes the action + cy.task('stopEndpointHost', createdHost.hostname); + // get action doc before we submit command so we know when the new action doc is indexed + cy.task('getLatestActionDoc').then((previousActionDoc) => { + submitCommand(); + cy.task('tamperActionDoc', previousActionDoc); + }); + cy.task('startEndpointHost', createdHost.hostname); + + const actionValidationErrorMsg = + 'Fleet action response error: Failed to validate action signature; check Endpoint logs for details'; + cy.contains(actionValidationErrorMsg, { timeout: 120000 }).should('exist'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console_mocked_data.cy.ts new file mode 100644 index 0000000000000..9bfc4ff5fc58d --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console_mocked_data.cy.ts @@ -0,0 +1,319 @@ +/* + * Copyright 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 { ActionDetails } from '../../../../../common/endpoint/types'; +import type { ReturnTypeFromChainable } from '../../types'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { + checkReturnedProcessesTable, + inputConsoleCommand, + openResponseConsoleFromEndpointList, + performCommandInputChecks, + submitCommand, + waitForEndpointListPageToBeLoaded, +} from '../../tasks/response_console'; +import { + checkEndpointIsIsolated, + checkEndpointIsNotIsolated, + interceptActionRequests, + sendActionResponse, +} from '../../tasks/isolate'; +import { login } from '../../tasks/login'; + +describe('Response console', { tags: '@ess' }, () => { + beforeEach(() => { + login(); + }); + + describe('`isolate` command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let isolateRequestResponse: ActionDetails; + + before(() => { + indexEndpointHosts({ withResponseActions: false, isolation: false }).then( + (indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostname = endpointData.data.hosts[0].host.name; + } + ); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('should isolate host from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + checkEndpointIsNotIsolated(endpointHostname); + openResponseConsoleFromEndpointList(); + performCommandInputChecks('isolate'); + interceptActionRequests((responseBody) => { + isolateRequestResponse = responseBody; + }, 'isolate'); + + submitCommand(); + cy.contains('Action pending.').should('exist'); + cy.wait('@isolate').then(() => { + sendActionResponse(isolateRequestResponse); + }); + cy.contains('Action completed.', { timeout: 120000 }).should('exist'); + waitForEndpointListPageToBeLoaded(endpointHostname); + checkEndpointIsIsolated(endpointHostname); + }); + }); + + describe('`release` command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let releaseRequestResponse: ActionDetails; + + before(() => { + indexEndpointHosts({ withResponseActions: false, isolation: true }).then((indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostname = endpointData.data.hosts[0].host.name; + }); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('should release host from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + checkEndpointIsIsolated(endpointHostname); + openResponseConsoleFromEndpointList(); + performCommandInputChecks('release'); + interceptActionRequests((responseBody) => { + releaseRequestResponse = responseBody; + }, 'release'); + submitCommand(); + cy.contains('Action pending.').should('exist'); + cy.wait('@release').then(() => { + sendActionResponse(releaseRequestResponse); + }); + cy.contains('Action completed.', { timeout: 120000 }).should('exist'); + waitForEndpointListPageToBeLoaded(endpointHostname); + checkEndpointIsNotIsolated(endpointHostname); + }); + }); + + describe('`processes` command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let processesRequestResponse: ActionDetails; + + before(() => { + indexEndpointHosts({ withResponseActions: false, isolation: false }).then( + (indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostname = endpointData.data.hosts[0].host.name; + } + ); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('should return processes from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + openResponseConsoleFromEndpointList(); + performCommandInputChecks('processes'); + interceptActionRequests((responseBody) => { + processesRequestResponse = responseBody; + }, 'processes'); + submitCommand(); + cy.contains('Action pending.').should('exist'); + cy.wait('@processes').then(() => { + sendActionResponse(processesRequestResponse); + }); + cy.getByTestSubj('getProcessesSuccessCallout', { timeout: 120000 }).within(() => { + checkReturnedProcessesTable(); + }); + }); + }); + + describe('`kill-process` command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let killProcessRequestResponse: ActionDetails; + + before(() => { + indexEndpointHosts({ withResponseActions: false, isolation: false }).then( + (indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostname = endpointData.data.hosts[0].host.name; + } + ); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('should kill process from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`kill-process --pid 1`); + + interceptActionRequests((responseBody) => { + killProcessRequestResponse = responseBody; + }, 'kill-process'); + submitCommand(); + cy.contains('Action pending.').should('exist'); + cy.wait('@kill-process').then(() => { + sendActionResponse(killProcessRequestResponse); + }); + cy.contains('Action completed.', { timeout: 120000 }).should('exist'); + }); + }); + + describe('`suspend-process` command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let suspendProcessRequestResponse: ActionDetails; + + before(() => { + indexEndpointHosts({ withResponseActions: false, isolation: false }).then( + (indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostname = endpointData.data.hosts[0].host.name; + } + ); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('should suspend process from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`suspend-process --pid 1`); + + interceptActionRequests((responseBody) => { + suspendProcessRequestResponse = responseBody; + }, 'suspend-process'); + submitCommand(); + cy.contains('Action pending.').should('exist'); + cy.wait('@suspend-process').then(() => { + sendActionResponse(suspendProcessRequestResponse); + }); + cy.contains('Action completed.', { timeout: 120000 }).should('exist'); + }); + }); + + // Broken until this is fixed: https://github.com/elastic/kibana/issues/162760 + describe.skip('`get-file` command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let getFileRequestResponse: ActionDetails; + + before(() => { + indexEndpointHosts({ withResponseActions: false, isolation: false }).then( + (indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostname = endpointData.data.hosts[0].host.name; + } + ); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('should get file from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`get-file --path /test/path/test.txt`); + + interceptActionRequests((responseBody) => { + getFileRequestResponse = responseBody; + }, 'get-file'); + submitCommand(); + cy.contains('Retrieving the file from host.').should('exist'); + cy.wait('@get-file').then(() => { + sendActionResponse(getFileRequestResponse); + }); + cy.getByTestSubj('getFileSuccess').within(() => { + cy.contains('File retrieved from the host.'); + cy.contains('(ZIP file passcode: elastic)'); + cy.contains( + 'Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + cy.contains('Click here to download').click(); + }); + + const downloadsFolder = Cypress.config('downloadsFolder'); + cy.readFile(`${downloadsFolder}/upload.zip`); + }); + }); + + describe('`execute` command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let executeRequestResponse: ActionDetails; + + before(() => { + indexEndpointHosts({ withResponseActions: false, isolation: false }).then( + (indexEndpoints) => { + endpointData = indexEndpoints; + endpointHostname = endpointData.data.hosts[0].host.name; + } + ); + }); + + after(() => { + if (endpointData) { + endpointData.cleanup(); + // @ts-expect-error ignore setting to undefined + endpointData = undefined; + } + }); + + it('should execute a command from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`execute --command "ls -al"`); + + interceptActionRequests((responseBody) => { + executeRequestResponse = responseBody; + }, 'execute'); + submitCommand(); + cy.contains('Action pending.').should('exist'); + cy.wait('@execute').then(() => { + sendActionResponse(executeRequestResponse); + }); + cy.contains('Command execution was successful', { timeout: 120000 }).should('exist'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/README.md b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/README.md new file mode 100644 index 0000000000000..cf77c126a3c16 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/README.md @@ -0,0 +1,6 @@ +# Serverless only test + +Directory contains tests that are only applicable to serverless. +Any other type of tests should be placed instead into the `./endpoint` directory or `./mocked_data` directories and tagged appropriately + + diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/endpoint_list_with_security_essentials.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/endpoint_list_with_security_essentials.cy.ts similarity index 80% rename from x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/endpoint_list_with_security_essentials.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/endpoint_list_with_security_essentials.cy.ts index 826a9188ae6bf..58c41539361c5 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/endpoint_list_with_security_essentials.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/endpoint_list_with_security_essentials.cy.ts @@ -5,21 +5,20 @@ * 2.0. */ -import { login } from '../../tasks/login'; +import type { CyIndexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import { loginServerless } from '../../tasks/login_serverless'; import { getConsoleActionMenuItem, getUnIsolateActionMenuItem, openRowActionMenu, visitEndpointList, -} from '../../screens/endpoint_management'; -import { - CyIndexEndpointHosts, - indexEndpointHosts, -} from '../../tasks/endpoint_management/index_endpoint_hosts'; +} from '../../screens'; describe( 'When on the Endpoint List in Security Essentials PLI', { + tags: ['@serverless', '@brokenInServerless'], env: { ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'essentials' }], @@ -43,7 +42,7 @@ describe( }); beforeEach(() => { - login(); + loginServerless(); visitEndpointList(); openRowActionMenu(); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/complete.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete.cy.ts similarity index 79% rename from x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/complete.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete.cy.ts index c76bbe5150e3e..7c4323b2aa689 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/complete.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete.cy.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants'; -import { login } from '../../../tasks/login'; -import { getNoPrivilegesPage } from '../../../screens/endpoint_management/common'; -import { getEndpointManagementPageList } from '../../../screens/endpoint_management'; -import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management'; +import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions'; +import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless'; +import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { getNoPrivilegesPage } from '../../../screens/common'; +import { getEndpointManagementPageList } from '../../../screens'; describe( 'App Features for Security Complete PLI', { + tags: ['@serverless', '@brokenInServerless'], env: { ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'complete' }] }, }, @@ -30,7 +31,7 @@ describe( let password: string; beforeEach(() => { - login('endpoint_operations_analyst').then((response) => { + loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => { username = response.username; password = response.password; }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete_with_endpoint.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete_with_endpoint.cy.ts new file mode 100644 index 0000000000000..f3ae8b85a9f24 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete_with_endpoint.cy.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions'; +import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless'; +import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { + getEndpointManagementPageList, + getFleetAgentListTable, + visitFleetAgentList, +} from '../../../screens'; + +describe( + 'App Features for Security Complete PLI with Endpoint Complete Addon', + { + tags: ['@serverless', '@brokenInServerless'], + env: { + ftrConfig: { + productTypes: [ + { product_line: 'security', product_tier: 'complete' }, + { product_line: 'endpoint', product_tier: 'complete' }, + ], + }, + }, + }, + () => { + const allPages = getEndpointManagementPageList(); + let username: string; + let password: string; + + beforeEach(() => { + loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => { + username = response.username; + password = response.password; + }); + }); + + for (const { url, title, pageTestSubj } of allPages) { + it(`should allow access to ${title}`, () => { + cy.visit(url); + cy.getByTestSubj(pageTestSubj).should('exist'); + }); + } + + for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES) { + it(`should allow access to Response Action: ${actionName}`, () => { + ensureResponseActionAuthzAccess('all', actionName, username, password); + }); + } + + it(`should have access to Fleet`, () => { + visitFleetAgentList(); + getFleetAgentListTable().should('exist'); + }); + } +); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/essentials.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials.cy.ts similarity index 79% rename from x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/essentials.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials.cy.ts index da5da7a4fa5f9..900a5d81a9f46 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/essentials.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials.cy.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants'; -import { login } from '../../../tasks/login'; -import { getNoPrivilegesPage } from '../../../screens/endpoint_management/common'; -import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management'; -import { getEndpointManagementPageList } from '../../../screens/endpoint_management'; +import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions'; +import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless'; +import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { getNoPrivilegesPage } from '../../../screens/common'; +import { getEndpointManagementPageList } from '../../../screens'; describe( 'App Features for Security Essential PLI', { + tags: ['@serverless', '@brokenInServerless'], env: { ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'essentials' }], @@ -32,7 +33,7 @@ describe( let password: string; beforeEach(() => { - login('endpoint_operations_analyst').then((response) => { + loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => { username = response.username; password = response.password; }); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/essentials_with_endpoint.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials_with_endpoint.cy.ts similarity index 79% rename from x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/essentials_with_endpoint.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials_with_endpoint.cy.ts index 86295ef1d28c0..7196b73f6813a 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/essentials_with_endpoint.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials_with_endpoint.cy.ts @@ -5,15 +5,19 @@ * 2.0. */ -import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants'; -import { login } from '../../../tasks/login'; -import { getAgentListTable, visitFleetAgentList } from '../../../screens'; -import { getEndpointManagementPageMap } from '../../../screens/endpoint_management'; -import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management'; +import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions'; +import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless'; +import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { + getEndpointManagementPageMap, + getFleetAgentListTable, + visitFleetAgentList, +} from '../../../screens'; describe( 'App Features for Security Essentials PLI with Endpoint Essentials Addon', { + tags: ['@serverless', '@brokenInServerless'], env: { ftrConfig: { productTypes: [ @@ -37,7 +41,7 @@ describe( let password: string; beforeEach(() => { - login('endpoint_operations_analyst').then((response) => { + loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => { username = response.username; password = response.password; }); @@ -71,7 +75,7 @@ describe( it(`should have access to Fleet`, () => { visitFleetAgentList(); - getAgentListTable().should('exist'); + getFleetAgentListTable().should('exist'); }); } ); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/policy_details_with_security_essentials.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/policy_details_with_security_essentials.cy.ts new file mode 100644 index 0000000000000..faf9aba6237b3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/policy_details_with_security_essentials.cy.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loginServerless } from '../../tasks/login_serverless'; +import { visitPolicyDetailsPage } from '../../screens/policy_details'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; + +describe( + 'When displaying the Policy Details in Security Essentials PLI', + { + tags: ['@serverless', '@brokenInServerless'], + env: { + ftrConfig: { + productTypes: [{ product_line: 'security', product_tier: 'essentials' }], + }, + }, + }, + () => { + let loadedPolicyData: IndexedFleetEndpointPolicyResponse; + + before(() => { + cy.task('indexFleetEndpointPolicy', { policyName: 'tests-serverless' }).then((response) => { + loadedPolicyData = response as IndexedFleetEndpointPolicyResponse; + }); + }); + + after(() => { + if (loadedPolicyData) { + cy.task('deleteIndexedFleetEndpointPolicies', loadedPolicyData); + } + }); + + beforeEach(() => { + loginServerless(); + visitPolicyDetailsPage(loadedPolicyData.integrationPolicies[0].id); + }); + + it('should display upselling section for protections', () => { + cy.getByTestSubj('endpointPolicy-protectionsLockedCard', { timeout: 60000 }) + .should('exist') + .and('be.visible'); + }); + } +); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/roles/complete_with_endpoint_roles.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles.cy.ts similarity index 79% rename from x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/roles/complete_with_endpoint_roles.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles.cy.ts index 2311a803043f2..879948a65c47d 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/roles/complete_with_endpoint_roles.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles.cy.ts @@ -6,10 +6,12 @@ */ import { pick } from 'lodash'; -import { login } from '../../../tasks/login'; -import { ServerlessRoleName } from '../../../../../../../shared/lib'; +import type { CyIndexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; +import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; +import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless'; +import { ensurePolicyDetailsPageAuthzAccess } from '../../../screens/policy_details'; +import type { EndpointArtifactPageId } from '../../../screens'; import { - EndpointArtifactPageId, ensureArtifactPageAuthzAccess, ensureEndpointListPageAuthzAccess, ensurePolicyListPageAuthzAccess, @@ -21,25 +23,17 @@ import { openRowActionMenu, visitEndpointList, visitPolicyList, -} from '../../../screens/endpoint_management'; -import { - ensurePermissionDeniedScreen, - getAgentListTable, + ensureFleetPermissionDeniedScreen, + getFleetAgentListTable, visitFleetAgentList, -} from '../../../screens'; -import { getConsoleHelpPanelResponseActionTestSubj, openConsoleHelpPanel, -} from '../../../screens/endpoint_management/response_console'; -import { ensurePolicyDetailsPageAuthzAccess } from '../../../screens/endpoint_management/policy_details'; -import { - CyIndexEndpointHosts, - indexEndpointHosts, -} from '../../../tasks/endpoint_management/index_endpoint_hosts'; +} from '../../../screens'; describe( 'User Roles for Security Complete PLI with Endpoint Complete addon', { + tags: ['@serverless', '@brokenInServerless'], env: { ftrConfig: { productTypes: [ @@ -69,12 +63,12 @@ describe( }); // roles `t1_analyst` and `t2_analyst` are very similar with exception of one page - (['t1_analyst', `t2_analyst`] as ServerlessRoleName[]).forEach((roleName) => { + (['t1_analyst', `t2_analyst`] as ServerlessUser[]).forEach((roleName) => { describe(`for role: ${roleName}`, () => { const deniedPages = allPages.filter((page) => page.id !== 'endpointList'); beforeEach(() => { - login(roleName); + loginServerless(roleName); }); it('should have READ access to Endpoint list page', () => { @@ -98,7 +92,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); it('should NOT have access to execute response actions', () => { @@ -130,7 +124,7 @@ describe( const deniedResponseActions = pick(consoleHelpPanelResponseActionsTestSubj, 'execute'); beforeEach(() => { - login('t3_analyst'); + loginServerless(ServerlessUser.T3_ANALYST); }); it('should have access to Endpoint list page', () => { @@ -154,7 +148,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); describe('Response Actions access', () => { @@ -182,7 +176,7 @@ describe( const deniedPages = allPages.filter(({ id }) => id !== 'blocklist' && id !== 'endpointList'); beforeEach(() => { - login('threat_intelligence_analyst'); + loginServerless(ServerlessUser.THREAT_INTELLIGENCE_ANALYST); }); it('should have access to Endpoint list page', () => { @@ -205,7 +199,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); it('should have access to Response Actions Log', () => { @@ -227,7 +221,7 @@ describe( ]; beforeEach(() => { - login('rule_author'); + loginServerless(ServerlessUser.RULE_AUTHOR); }); for (const { id, title } of artifactPagesFullAccess) { @@ -254,7 +248,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); it('should have access to Response Actions Log', () => { @@ -278,7 +272,7 @@ describe( const grantedAccessPages = [pageById.endpointList, pageById.policyList]; beforeEach(() => { - login('soc_manager'); + loginServerless(ServerlessUser.SOC_MANAGER); }); for (const { id, title } of artifactPagesFullAccess) { @@ -296,7 +290,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); describe('Response Actions access', () => { @@ -325,7 +319,7 @@ describe( const grantedAccessPages = [pageById.endpointList, pageById.policyList]; beforeEach(() => { - login('endpoint_operations_analyst'); + loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST); }); for (const { id, title } of artifactPagesFullAccess) { @@ -352,59 +346,57 @@ describe( it('should have access to Fleet', () => { visitFleetAgentList(); - getAgentListTable().should('exist'); + getFleetAgentListTable().should('exist'); }); }); - (['platform_engineer', 'endpoint_policy_manager'] as ServerlessRoleName[]).forEach( - (roleName) => { - describe(`for role: ${roleName}`, () => { - const artifactPagesFullAccess = [ - pageById.trustedApps, - pageById.eventFilters, - pageById.blocklist, - pageById.hostIsolationExceptions, - ]; - const grantedAccessPages = [pageById.endpointList, pageById.policyList]; - - beforeEach(() => { - login(roleName); - }); + (['platform_engineer', 'endpoint_policy_manager'] as ServerlessUser[]).forEach((roleName) => { + describe(`for role: ${roleName}`, () => { + const artifactPagesFullAccess = [ + pageById.trustedApps, + pageById.eventFilters, + pageById.blocklist, + pageById.hostIsolationExceptions, + ]; + const grantedAccessPages = [pageById.endpointList, pageById.policyList]; - for (const { id, title } of artifactPagesFullAccess) { - it(`should have CRUD access to: ${title}`, () => { - ensureArtifactPageAuthzAccess('all', id as EndpointArtifactPageId); - }); - } + beforeEach(() => { + loginServerless(roleName); + }); - for (const { url, title } of grantedAccessPages) { - it(`should have access to: ${title}`, () => { - cy.visit(url); - getNoPrivilegesPage().should('not.exist'); - }); - } + for (const { id, title } of artifactPagesFullAccess) { + it(`should have CRUD access to: ${title}`, () => { + ensureArtifactPageAuthzAccess('all', id as EndpointArtifactPageId); + }); + } - it('should have access to Fleet', () => { - visitFleetAgentList(); - getAgentListTable().should('exist'); + for (const { url, title } of grantedAccessPages) { + it(`should have access to: ${title}`, () => { + cy.visit(url); + getNoPrivilegesPage().should('not.exist'); }); + } - it('should have access to Response Actions Log', () => { - cy.visit(pageById.responseActionLog); + it('should have access to Fleet', () => { + visitFleetAgentList(); + getFleetAgentListTable().should('exist'); + }); - if (roleName === 'endpoint_policy_manager') { - getNoPrivilegesPage().should('exist'); - } else { - getNoPrivilegesPage().should('not.exist'); - } - }); + it('should have access to Response Actions Log', () => { + cy.visit(pageById.responseActionLog); - it('should NOT have access to execute response actions', () => { - visitEndpointList(); - openRowActionMenu().findByTestSubj('console').should('not.exist'); - }); + if (roleName === 'endpoint_policy_manager') { + getNoPrivilegesPage().should('exist'); + } else { + getNoPrivilegesPage().should('not.exist'); + } }); - } - ); + + it('should NOT have access to execute response actions', () => { + visitEndpointList(); + openRowActionMenu().findByTestSubj('console').should('not.exist'); + }); + }); + }); } ); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/roles/essentials_with_endpoint.roles.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/essentials_with_endpoint.roles.cy.ts similarity index 86% rename from x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/roles/essentials_with_endpoint.roles.cy.ts rename to x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/essentials_with_endpoint.roles.cy.ts index c60095aa23c06..fec6a0f803afb 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/roles/essentials_with_endpoint.roles.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/essentials_with_endpoint.roles.cy.ts @@ -5,32 +5,28 @@ * 2.0. */ -import { login } from '../../../tasks/login'; +import type { CyIndexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; +import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; +import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless'; +import type { EndpointArtifactPageId } from '../../../screens'; import { getNoPrivilegesPage, getArtifactListEmptyStateAddButton, getEndpointManagementPageMap, getEndpointManagementPageList, - EndpointArtifactPageId, ensureArtifactPageAuthzAccess, ensureEndpointListPageAuthzAccess, ensurePolicyListPageAuthzAccess, -} from '../../../screens/endpoint_management'; -import { - ensurePermissionDeniedScreen, - getAgentListTable, + ensureFleetPermissionDeniedScreen, + getFleetAgentListTable, visitFleetAgentList, + ensurePolicyDetailsPageAuthzAccess, } from '../../../screens'; -import { ServerlessRoleName } from '../../../../../../../shared/lib'; -import { ensurePolicyDetailsPageAuthzAccess } from '../../../screens/endpoint_management/policy_details'; -import { - CyIndexEndpointHosts, - indexEndpointHosts, -} from '../../../tasks/endpoint_management/index_endpoint_hosts'; describe( 'Roles for Security Essential PLI with Endpoint Essentials addon', { + tags: ['@serverless', '@brokenInServerless'], env: { ftrConfig: { productTypes: [ @@ -59,12 +55,12 @@ describe( }); // roles `t1_analyst` and `t2_analyst` are the same as far as endpoint access - (['t1_analyst', `t2_analyst`] as ServerlessRoleName[]).forEach((roleName) => { + (['t1_analyst', `t2_analyst`] as ServerlessUser[]).forEach((roleName) => { describe(`for role: ${roleName}`, () => { const deniedPages = allPages.filter((page) => page.id !== 'endpointList'); beforeEach(() => { - login(roleName); + loginServerless(roleName); }); it('should have READ access to Endpoint list page', () => { @@ -80,7 +76,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); }); }); @@ -93,7 +89,7 @@ describe( ]; beforeEach(() => { - login('t3_analyst'); + loginServerless(ServerlessUser.T3_ANALYST); }); it('should have access to Endpoint list page', () => { @@ -124,7 +120,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); }); @@ -132,7 +128,7 @@ describe( const deniedPages = allPages.filter(({ id }) => id !== 'blocklist' && id !== 'endpointList'); beforeEach(() => { - login('threat_intelligence_analyst'); + loginServerless(ServerlessUser.THREAT_INTELLIGENCE_ANALYST); }); it('should have access to Endpoint list page', () => { @@ -155,7 +151,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); }); @@ -167,7 +163,7 @@ describe( ]; beforeEach(() => { - login('rule_author'); + loginServerless(ServerlessUser.RULE_AUTHOR); }); for (const { id, title } of artifactPagesFullAccess) { @@ -198,7 +194,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); }); @@ -211,7 +207,7 @@ describe( const grantedAccessPages = [pageById.endpointList, pageById.policyList]; beforeEach(() => { - login('soc_manager'); + loginServerless(ServerlessUser.SOC_MANAGER); }); for (const { id, title } of artifactPagesFullAccess) { @@ -236,7 +232,7 @@ describe( it('should NOT have access to Fleet', () => { visitFleetAgentList(); - ensurePermissionDeniedScreen(); + ensureFleetPermissionDeniedScreen(); }); }); @@ -246,7 +242,7 @@ describe( 'platform_engineer', `endpoint_operations_analyst`, 'endpoint_policy_manager', - ] as ServerlessRoleName[] + ] as ServerlessUser[] ).forEach((roleName) => { describe(`for role: ${roleName}`, () => { const artifactPagesFullAccess = [ @@ -257,7 +253,7 @@ describe( const grantedAccessPages = [pageById.endpointList, pageById.policyList]; beforeEach(() => { - login(roleName); + loginServerless(roleName); }); for (const { id, title } of artifactPagesFullAccess) { @@ -282,7 +278,7 @@ describe( it('should have access to Fleet', () => { visitFleetAgentList(); - getAgentListTable().should('exist'); + getFleetAgentListTable().should('exist'); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts index d9077bfb984a9..a1e53cee9b09a 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts @@ -13,7 +13,8 @@ export const navigateToAlertsList = (urlQueryParams: string = '') => { }; export const clickAlertListRefreshButton = (): Cypress.Chainable => { - return cy.getByTestSubj('querySubmitButton').click().should('be.enabled'); + cy.getByTestSubj('querySubmitButton').click(); + return cy.getByTestSubj('querySubmitButton').should('be.enabled'); }; /** diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/artifacts.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/artifacts.ts similarity index 93% rename from x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/artifacts.ts rename to x-pack/plugins/security_solution/public/management/cypress/screens/artifacts.ts index abe0882ac9696..10bd7728197d7 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/artifacts.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/artifacts.ts @@ -5,14 +5,11 @@ * 2.0. */ -import { DeepReadonly } from 'utility-types'; +import type { DeepReadonly } from 'utility-types'; import { subj as testSubjSelector } from '@kbn/test-subj-selector'; -import { - EndpointArtifactPageId, - EndpointManagementPageMap, - getEndpointManagementPageMap, -} from './page_reference'; -import { UserAuthzAccessLevel } from './types'; +import type { EndpointArtifactPageId, EndpointManagementPageMap } from './page_reference'; +import { getEndpointManagementPageMap } from './page_reference'; +import type { UserAuthzAccessLevel } from './types'; const artifactPageTopTestSubjPrefix: Readonly> = { trustedApps: 'trustedAppsListPage', diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/common.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/common.ts similarity index 100% rename from x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/common.ts rename to x-pack/plugins/security_solution/public/management/cypress/screens/common.ts diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/endpoint_list.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/endpoint_list.ts new file mode 100644 index 0000000000000..e435602ebceb4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/endpoint_list.ts @@ -0,0 +1,99 @@ +/* + * Copyright 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 { DeepReadonly } from 'utility-types'; +import type { EndpointManagementPageMap } from './page_reference'; +import { getEndpointManagementPageMap } from './page_reference'; +import type { UserAuthzAccessLevel } from './types'; +import { getNoPrivilegesPage } from './common'; +import { loadPage } from '../tasks/common'; +import { APP_PATH } from '../../../../common'; +import { getEndpointDetailsPath } from '../../common/routing'; + +interface ListRowOptions { + endpointId?: string; + hostName?: string; + /** Zero-based row index */ + rowIndex?: number; +} + +export const TABLE_ROW_ACTIONS_MENU = 'tableRowActionsMenuPanel'; +export const AGENT_HOSTNAME_CELL = 'hostnameCellLink'; +export const AGENT_POLICY_CELL = 'policyNameCellLink'; +export const TABLE_ROW_ACTIONS = 'endpointTableRowActions'; + +const pageById: DeepReadonly = getEndpointManagementPageMap(); + +export const visitEndpointList = (): Cypress.Chainable => { + return cy.visit(pageById.endpointList.url); +}; + +/** + * Validate that the endpoint list has the proper level of authz + * + * @param accessLevel + * @param visitPage if `true`, then the endpoint list page will be visited first + */ +export const ensureEndpointListPageAuthzAccess = ( + accessLevel: UserAuthzAccessLevel, + visitPage: boolean = false +): Cypress.Chainable => { + if (visitPage) { + visitEndpointList(); + } + + if (accessLevel === 'none') { + return getNoPrivilegesPage().should('exist'); + } + + // Read and All are currently the same + return getNoPrivilegesPage().should('not.exist'); +}; + +export const getTableRow = ({ + endpointId, + hostName, + rowIndex = 0, +}: ListRowOptions = {}): Cypress.Chainable => { + if (endpointId) { + return cy.get(`tr[data-endpoint-id="${endpointId}"]`).should('exist'); + } + + if (hostName) { + return cy.getByTestSubj('hostnameCellLink').contains(hostName).closest('tr').should('exist'); + } + + return cy + .getByTestSubj('endpointListTable') + .find(`tbody tr[data-endpoint-id]`) + .eq(rowIndex) + .should('exist'); +}; + +export const openRowActionMenu = (options?: ListRowOptions): Cypress.Chainable => { + getTableRow(options).findByTestSubj('endpointTableRowActions', { log: true }).click(); + return cy.getByTestSubj('tableRowActionsMenuPanel'); +}; + +export const openConsoleFromEndpointList = (options?: ListRowOptions): Cypress.Chainable => { + return openRowActionMenu(options).findByTestSubj('console').click(); +}; + +export const getUnIsolateActionMenuItem = (): Cypress.Chainable => { + return cy.getByTestSubj('tableRowActionsMenuPanel').findByTestSubj('unIsolateLink'); +}; + +export const getConsoleActionMenuItem = (): Cypress.Chainable => { + return cy.getByTestSubj('tableRowActionsMenuPanel').findByTestSubj('console'); +}; + +export const navigateToEndpointPolicyResponse = (endpointAgentId: string): void => { + loadPage( + APP_PATH + + getEndpointDetailsPath({ name: 'endpointPolicyResponse', selected_endpoint: endpointAgentId }) + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/endpoints.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/endpoints.ts deleted file mode 100644 index ef5f7926e94dd..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/endpoints.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { APP_PATH } from '../../../../common/constants'; -import { getEndpointDetailsPath } from '../../common/routing'; -import { loadPage } from '../tasks/common'; - -export const AGENT_HOSTNAME_CELL = 'hostnameCellLink'; -export const AGENT_POLICY_CELL = 'policyNameCellLink'; -export const TABLE_ROW_ACTIONS = 'endpointTableRowActions'; -export const TABLE_ROW_ACTIONS_MENU = 'tableRowActionsMenuPanel'; - -export const navigateToEndpointPolicyResponse = (endpointAgentId: string): void => { - loadPage( - APP_PATH + - getEndpointDetailsPath({ name: 'endpointPolicyResponse', selected_endpoint: endpointAgentId }) - ); -}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/fleet.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet.ts deleted file mode 100644 index f1bf244c7558c..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/fleet.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FLEET_BASE_PATH } from '@kbn/fleet-plugin/public/constants'; -import { loadPage } from '../tasks/common'; - -export const FLEET_REASSIGN_POLICY_MODAL = 'agentReassignPolicyModal'; -export const FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON = 'confirmModalConfirmButton'; - -export const navigateToFleetAgentDetails = (agentId: string): void => { - // FYI: attempted to use fleet's `pagePathGetters()`, but got compile - // errors due to it pulling too many modules - loadPage(`${FLEET_BASE_PATH}/agents/${agentId}`); - - cy.getByTestSubj('agentPolicyNameLink').should('be.visible'); -}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/agent_details.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/agent_details.ts new file mode 100644 index 0000000000000..90b236173446f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/agent_details.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FLEET_BASE_PATH } from '@kbn/fleet-plugin/public/constants'; +import { loadPage } from '../../tasks/common'; + +export const FLEET_REASSIGN_POLICY_MODAL = 'agentReassignPolicyModal'; +export const FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON = 'confirmModalConfirmButton'; + +export const navigateToFleetAgentDetails = (agentId: string): void => { + // FYI: attempted to use fleet's `pagePathGetters()`, but got compile + // errors due to it pulling too many modules + loadPage(`${FLEET_BASE_PATH}/agents/${agentId}`); + + cy.getByTestSubj('agentPolicyNameLink').should('be.visible'); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/agent_list.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/agent_list.ts new file mode 100644 index 0000000000000..aace59c9422e1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/agent_list.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FLEET_BASE_PATH } from '@kbn/fleet-plugin/public/constants'; + +export const visitFleetAgentList = (): Cypress.Chainable => { + // `failOnStatus` below is necesary because the page (when not accessible) will actually return + // a `4xx` error along with an HTML page to display. + return cy.visit(FLEET_BASE_PATH, { failOnStatusCode: false }); +}; + +export const getFleetAgentListTable = (): Cypress.Chainable => { + return cy.getByTestSubj('fleetAgentListTable'); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/index.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/index.ts new file mode 100644 index 0000000000000..939dd8a30baa3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './permission_denied'; +export * from './agent_list'; +export * from './agent_details'; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/permission_denied.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/permission_denied.ts similarity index 87% rename from x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/permission_denied.ts rename to x-pack/plugins/security_solution/public/management/cypress/screens/fleet/permission_denied.ts index e6bf6e4321716..5049ab7e22ad1 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/permission_denied.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/fleet/permission_denied.ts @@ -9,6 +9,6 @@ * The screen normally returned by the API when a user does not have access to a Plugin. * Note that the requested page will likely also receive an HTTP status code of `403` */ -export const ensurePermissionDeniedScreen = (): Cypress.Chainable => { +export const ensureFleetPermissionDeniedScreen = (): Cypress.Chainable => { return cy.contains('You do not have permission to access the requested page').should('exist'); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/index.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/index.ts new file mode 100644 index 0000000000000..e1594d2b59f7e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './common'; +export * from './artifacts'; +export * from './endpoint_list'; +export * from './policy_details'; +export * from './policy_list'; +export * from './page_reference'; +export * from './responder'; +export * from './fleet'; +export * from './types'; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/page_reference.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/page_reference.ts similarity index 97% rename from x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/page_reference.ts rename to x-pack/plugins/security_solution/public/management/cypress/screens/page_reference.ts index a99a070b75b7d..5ebdeda5ddcbb 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/page_reference.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/page_reference.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { keyBy } from 'lodash'; import { APP_BLOCKLIST_PATH, APP_ENDPOINTS_PATH, @@ -13,8 +14,7 @@ import { APP_POLICIES_PATH, APP_RESPONSE_ACTIONS_HISTORY_PATH, APP_TRUSTED_APPS_PATH, -} from '@kbn/security-solution-plugin/common/constants'; -import { keyBy } from 'lodash'; +} from '../../../../common/constants'; export interface EndpointManagementPageMap { endpointList: EndpointManagementPage; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/policy_details.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/policy_details.ts index 30b0b392ff456..863ca2283d887 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/policy_details.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/policy_details.ts @@ -13,11 +13,18 @@ import type { UpdatePackagePolicyResponse, } from '@kbn/fleet-plugin/common'; import { packagePolicyRouteService } from '@kbn/fleet-plugin/common'; +import type { UserAuthzAccessLevel } from './types'; import { APP_POLICIES_PATH } from '../../../../common/constants'; import type { PolicyConfig } from '../../../../common/endpoint/types'; -import { request, loadPage } from '../tasks/common'; +import { loadPage, request } from '../tasks/common'; import { expectAndCloseSuccessToast } from '../tasks/toasts'; +import { getNoPrivilegesPage } from './common'; +/** + * Loads the Policy details page - either for the `policyId` provided on input, or if undefined, + * then the first policy displayed on the Policy List will be opened + * @param policyId + */ export const visitPolicyDetailsPage = (policyId?: string) => { if (policyId) { loadPage(`${APP_POLICIES_PATH}/${policyId}`); @@ -89,3 +96,23 @@ export class PackagePolicyBackupHelper { }); } } + +export const ensurePolicyDetailsPageAuthzAccess = ( + policyId: string, + accessLevel: UserAuthzAccessLevel, + visitPage: boolean = false +): Cypress.Chainable => { + if (visitPage) { + visitPolicyDetailsPage(policyId); + } + + if (accessLevel === 'none') { + return getNoPrivilegesPage().should('exist'); + } + + if (accessLevel === 'read') { + return cy.getByTestSubj('policyDetailsSaveButton').should('not.exist'); + } + + return cy.getByTestSubj('policyDetailsSaveButton').should('exist'); +}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/policy_list.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/policy_list.ts similarity index 79% rename from x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/policy_list.ts rename to x-pack/plugins/security_solution/public/management/cypress/screens/policy_list.ts index 6be7e37f16818..e72d0c3ecf93d 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/policy_list.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/policy_list.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { DeepReadonly } from 'utility-types'; -import { EndpointManagementPageMap, getEndpointManagementPageMap } from './page_reference'; +import type { DeepReadonly } from 'utility-types'; +import type { EndpointManagementPageMap } from './page_reference'; +import { getEndpointManagementPageMap } from './page_reference'; import { getNoPrivilegesPage } from './common'; import { visitEndpointList } from './endpoint_list'; -import { UserAuthzAccessLevel } from './types'; +import type { UserAuthzAccessLevel } from './types'; const pageById: DeepReadonly = getEndpointManagementPageMap(); diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts index ceacd2a5de13f..c9e320728ee23 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts @@ -6,6 +6,7 @@ */ import { subj as testSubjSelector } from '@kbn/test-subj-selector'; +import type { ConsoleResponseActionCommands } from '../../../../common/endpoint/service/response_actions/constants'; import { DATE_RANGE_OPTION_TO_TEST_SUBJ_MAP } from '../../../../common/test'; const TEST_SUBJ = Object.freeze({ @@ -13,6 +14,22 @@ const TEST_SUBJ = Object.freeze({ actionLogFlyout: 'responderActionLogFlyout', }); +export const getConsoleHelpPanelResponseActionTestSubj = (): Record< + ConsoleResponseActionCommands, + string +> => { + return { + isolate: 'endpointResponseActionsConsole-commandList-Responseactions-isolate', + release: 'endpointResponseActionsConsole-commandList-Responseactions-release', + processes: 'endpointResponseActionsConsole-commandList-Responseactions-processes', + 'kill-process': 'endpointResponseActionsConsole-commandList-Responseactions-kill-process', + 'suspend-process': 'endpointResponseActionsConsole-commandList-Responseactions-suspend-process', + 'get-file': 'endpointResponseActionsConsole-commandList-Responseactions-get-file', + execute: 'endpointResponseActionsConsole-commandList-Responseactions-execute', + upload: 'endpointResponseActionsConsole-commandList-Responseactions-upload', + }; +}; + const ensureOnResponder = (): Cypress.Chainable> => { return cy.getByTestSubj(TEST_SUBJ.responderPage).should('exist'); }; @@ -59,3 +76,8 @@ export const setResponderActionLogDateRange = ( cy.getByTestSubj(DATE_RANGE_OPTION_TO_TEST_SUBJ_MAP[range]).click(); cy.getByTestSubj('superDatePickerQuickMenu').should('not.exist'); }; + +export const openConsoleHelpPanel = (): Cypress.Chainable => { + ensureOnResponder(); + return cy.getByTestSubj('endpointResponseActionsConsole-header-helpButton').click(); +}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/types.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/types.ts similarity index 100% rename from x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/types.ts rename to x-pack/plugins/security_solution/public/management/cypress/screens/types.ts diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts b/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts index 0c60549e02850..7c8bf5104524b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts @@ -24,11 +24,13 @@ import { subj as testSubjSelector } from '@kbn/test-subj-selector'; -// force ESM in this module -export {}; - import 'cypress-react-selector'; +// @ts-ignore +import registerCypressGrep from '@cypress/grep'; + +registerCypressGrep(); + Cypress.Commands.addQuery<'getByTestSubj'>( 'getByTestSubj', function getByTestSubj(selector, options) { diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts index 0ff00c181b26d..30679364bd2f8 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts @@ -181,7 +181,7 @@ export const getEndpointDetectionAlertsQueryForAgentId = (endpointAgentId: strin export const changeAlertsFilter = (text: string) => { cy.getByTestSubj('filters-global-container').within(() => { - cy.getByTestSubj('queryInput').click().type(text); + cy.getByTestSubj('queryInput').type(text); cy.getByTestSubj('querySubmitButton').click(); }); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts index 38866ec5b5d29..ceba06f142f35 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts @@ -13,6 +13,7 @@ export const API_AUTH = Object.freeze({ export const COMMON_API_HEADERS = Object.freeze({ 'kbn-xsrf': 'cypress', 'x-elastic-internal-origin': 'security-solution', + 'Elastic-Api-Version': '2023-10-31', }); export const waitForPageToBeLoaded = () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.ts index f597e9ae4e225..a1b5972758742 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.ts @@ -10,7 +10,7 @@ import type { UpdatePackagePolicy, UpdatePackagePolicyResponse, } from '@kbn/fleet-plugin/common'; -import { packagePolicyRouteService } from '@kbn/fleet-plugin/common'; +import { packagePolicyRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; import { request } from './common'; import { ProtectionModes } from '../../../../common/endpoint/types'; @@ -24,6 +24,9 @@ export const enableAllPolicyProtections = ( return request({ method: 'GET', url: packagePolicyRouteService.getInfoPath(endpointPolicyId), + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }).then(({ body: { item: endpointPolicy } }) => { const { created_by: _createdBy, @@ -58,6 +61,9 @@ export const enableAllPolicyProtections = ( method: 'PUT', url: packagePolicyRouteService.getUpdatePath(endpointPolicyId), body: updatedEndpointPolicy, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }); }); }; @@ -69,6 +75,9 @@ export const setCustomProtectionUpdatesManifestVersion = ( return request({ method: 'GET', url: packagePolicyRouteService.getInfoPath(endpointPolicyId), + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }).then(({ body: { item: endpointPolicy } }) => { const { created_by: _createdBy, @@ -91,6 +100,21 @@ export const setCustomProtectionUpdatesManifestVersion = ( method: 'PUT', url: packagePolicyRouteService.getUpdatePath(endpointPolicyId), body: updatedEndpointPolicy, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }); }); }; + +export const setCustomProtectionUpdatesNote = ( + endpointPolicyId: string, + note: string +): Cypress.Chainable> => { + return request<{ note: string }>({ + method: 'POST', + url: `/api/endpoint/protection_updates_note/${endpointPolicyId}`, + body: { note }, + headers: { 'Elastic-Api-Version': API_VERSIONS.public.v1 }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts index 775baa7409f0a..bd6edbea158ce 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts @@ -15,6 +15,7 @@ import { agentRouteService, epmRouteService, packagePolicyRouteService, + API_VERSIONS, } from '@kbn/fleet-plugin/common'; import type { PutAgentReassignResponse } from '@kbn/fleet-plugin/common/types'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; @@ -24,6 +25,9 @@ export const getEndpointIntegrationVersion = (): Cypress.Chainable => request({ url: epmRouteService.getInfoPath('endpoint'), method: 'GET', + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }).then((response) => response.body.item.version); export const getAgentByHostName = (hostname: string): Cypress.Chainable => @@ -33,6 +37,9 @@ export const getAgentByHostName = (hostname: string): Cypress.Chainable = qs: { kuery: `local_metadata.host.hostname: "${hostname}"`, }, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }).then((response) => response.body.items[0]); export const reassignAgentPolicy = ( @@ -45,6 +52,9 @@ export const reassignAgentPolicy = ( body: { policy_id: agentPolicyId, }, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }); export const yieldEndpointPolicyRevision = (): Cypress.Chainable => @@ -54,6 +64,9 @@ export const yieldEndpointPolicyRevision = (): Cypress.Chainable => qs: { kuery: 'ingest-package-policies.package.name: endpoint', }, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }).then(({ body }) => { return body.items?.[0]?.revision ?? -1; }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts index 2c76247b24780..a15a71f1362eb 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable cypress/no-unnecessary-waiting */ + import type { ActionDetails } from '../../../../common/endpoint/types'; import { loadPage } from './common'; @@ -114,7 +116,7 @@ export const filterOutEndpoints = (endpointHostname: string): void => { }; export const filterOutIsolatedHosts = (): void => { - cy.getByTestSubj('adminSearchBar').click().type('united.endpoint.Endpoint.state.isolation: true'); + cy.getByTestSubj('adminSearchBar').type('united.endpoint.Endpoint.state.isolation: true'); cy.getByTestSubj('querySubmitButton').click(); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts index 637f6b157875c..58eba62a72f82 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts @@ -5,13 +5,9 @@ * 2.0. */ -/* eslint-disable import/no-nodejs-modules */ - import * as yaml from 'js-yaml'; -import type { UrlObject } from 'url'; -import Url from 'url'; import type { Role } from '@kbn/security-plugin/common'; -import { isLocalhost } from '../../../../scripts/endpoint/common/is_localhost'; +import type { LoginState } from '@kbn/security-plugin/common/login_state'; import { getWithResponseActionsRole } from '../../../../scripts/endpoint/common/roles_users/with_response_actions_role'; import { getNoResponseActionsRole } from '../../../../scripts/endpoint/common/roles_users/without_response_actions_role'; import { request } from './common'; @@ -90,35 +86,6 @@ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; const KIBANA_USERNAME = 'KIBANA_USERNAME'; const KIBANA_PASSWORD = 'KIBANA_PASSWORD'; -/** - * The Kibana server endpoint used for authentication - */ -const LOGIN_API_ENDPOINT = '/internal/security/login'; - -/** - * cy.visit will default to the baseUrl which uses the default kibana test user - * This function will override that functionality in cy.visit by building the baseUrl - * directly from the environment variables set up in x-pack/test/security_solution_cypress/runner.ts - * - * @param role string role/user to log in with - * @param route string route to visit - */ -export const getUrlWithRoute = (role: string, route: string) => { - const url = Cypress.config().baseUrl; - const kibana = new URL(String(url)); - const theUrl = `${Url.format({ - auth: `${role}:changeme`, - username: role, - password: Cypress.env(ELASTICSEARCH_PASSWORD), - protocol: kibana.protocol.replace(':', ''), - hostname: kibana.hostname, - port: kibana.port, - } as UrlObject)}${route.startsWith('/') ? '' : '/'}${route}`; - cy.log(`origin: ${theUrl}`); - - return theUrl; -}; - export const createCustomRoleAndUser = (role: string, rolePrivileges: Omit) => { // post the role request({ @@ -139,31 +106,44 @@ export const createCustomRoleAndUser = (role: string, rolePrivileges: Omit { +const loginWithUsernameAndPassword = (username: string, password: string) => { + const baseUrl = Cypress.config().baseUrl; + if (!baseUrl) { + throw Error(`Cypress config baseUrl not set!`); + } + + // Programmatically authenticate without interacting with the Kibana login page. + const headers = { 'kbn-xsrf': 'cypress-creds' }; + request({ headers, url: `${baseUrl}/internal/security/login_state` }).then( + (loginState) => { + const basicProvider = loginState.body.selector.providers.find( + (provider) => provider.type === 'basic' + ); + return request({ + url: `${baseUrl}/internal/security/login`, + method: 'POST', + headers, + body: { + providerType: basicProvider.type, + providerName: basicProvider.name, + currentURL: '/', + params: { username, password }, + }, + }); + } + ); +}; + +export const loginWithRole = (role: ROLE) => { loginWithCustomRole(role, rolesMapping[role]); }; -export const loginWithCustomRole = async (role: string, rolePrivileges: Omit) => { +export const loginWithCustomRole = (role: string, rolePrivileges: Omit) => { createCustomRoleAndUser(role, rolePrivileges); - const theUrl = new URL(String(Cypress.config().baseUrl)); - theUrl.username = role; - theUrl.password = Cypress.env(ELASTICSEARCH_PASSWORD); - cy.log(`origin: ${theUrl}`); - request({ - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: '/', - params: { - username: role, - password: Cypress.env(ELASTICSEARCH_PASSWORD), - }, - }, - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'POST', - url: getUrlWithRoute(role, LOGIN_API_ENDPOINT), - }); + cy.log(`origin: ${Cypress.config().baseUrl}`); + + loginWithUsernameAndPassword(role, Cypress.env(ELASTICSEARCH_PASSWORD)); }; /** @@ -199,14 +179,6 @@ const credentialsProvidedByEnvironment = (): boolean => * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). */ const loginViaEnvironmentCredentials = () => { - const url = Cypress.config().baseUrl; - - if (!url) { - throw Error(`Cypress config baseUrl not set!`); - } - - const urlObj = new URL(url); - let username: string; let password: string; let usernameEnvVar: string; @@ -228,21 +200,7 @@ const loginViaEnvironmentCredentials = () => { `Authenticating user [${username}] retrieved via environment credentials from the \`CYPRESS_${usernameEnvVar}\` and \`CYPRESS_${passwordEnvVar}\` environment variables` ); - // programmatically authenticate without interacting with the Kibana login page - request({ - body: { - providerType: 'basic', - providerName: url && !isLocalhost(urlObj.hostname) ? 'cloud-basic' : 'basic', - currentURL: '/', - params: { - username, - password, - }, - }, - headers: { 'kbn-xsrf': 'cypress-creds-via-env' }, - method: 'POST', - url: `${url}${LOGIN_API_ENDPOINT}`, - }); + loginWithUsernameAndPassword(username, password); }; /** @@ -258,22 +216,10 @@ const loginViaConfig = () => { // read the login details from `kibana.dev.yaml` cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => { const config = yaml.safeLoad(kibanaDevYml); - - // programmatically authenticate without interacting with the Kibana login page - request({ - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: '/', - params: { - username: Cypress.env(ELASTICSEARCH_USERNAME), - password: config.elasticsearch.password, - }, - }, - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'POST', - url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, - }); + loginWithUsernameAndPassword( + Cypress.env(ELASTICSEARCH_USERNAME), + config.elasticsearch.password + ); }); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/login_serverless.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/login_serverless.ts new file mode 100644 index 0000000000000..533a17663e16b --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/login_serverless.ts @@ -0,0 +1,103 @@ +/* + * Copyright 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 { LoginState } from '@kbn/security-plugin/common/login_state'; +import { COMMON_API_HEADERS, request } from './common'; + +export enum ServerlessUser { + T1_ANALYST = 't1_analyst', + T2_ANALYST = 't2_analyst', + T3_ANALYST = 't3_analyst', + THREAT_INTELLIGENCE_ANALYST = 'threat_intelligence_analyst', + RULE_AUTHOR = 'rule_author', + SOC_MANAGER = 'soc_manager', + DETECTIONS_ADMIN = 'detections_admin', + PLATFORM_ENGINEER = 'platform_engineer', + ENDPOINT_OPERATIONS_ANALYST = 'endpoint_operations_analyst', + ENDPOINT_POLICY_MANAGER = 'endpoint_policy_manager', +} + +/** + * Send login via API + * @param username + * @param password + * + * @private + */ +const sendApiLoginRequest = ( + username: string, + password: string +): Cypress.Chainable<{ username: string; password: string }> => { + const baseUrl = Cypress.config().baseUrl; + const headers = { ...COMMON_API_HEADERS }; + + cy.log(`Authenticating [${username}] via ${baseUrl}`); + + return request({ headers, url: `${baseUrl}/internal/security/login_state` }) + .then((loginState) => { + const basicProvider = loginState.body.selector.providers.find( + (provider) => provider.type === 'basic' + ); + + return request({ + url: `${baseUrl}/internal/security/login`, + method: 'POST', + headers, + body: { + providerType: basicProvider?.type, + providerName: basicProvider?.name, + currentURL: '/', + params: { username, password }, + }, + }); + }) + .then(() => ({ username, password })); +}; + +interface CyLoginTask { + (user?: ServerlessUser | 'elastic'): ReturnType; + + /** + * Login using any username/password + * @param username + * @param password + */ + with(username: string, password: string): ReturnType; +} + +/** + * Login to Kibana using API (not login page). By default, user will be logged in using + * the username and password defined via `KIBANA_USERNAME` and `KIBANA_PASSWORD` cypress env + * variables. + * @param user Defaults to `soc_manager` + */ +export const loginServerless: CyLoginTask = ( + user: ServerlessUser | 'elastic' = ServerlessUser.SOC_MANAGER +): ReturnType => { + const username = Cypress.env('KIBANA_USERNAME'); + const password = Cypress.env('KIBANA_PASSWORD'); + + if (user && user !== 'elastic') { + throw new Error('Serverless usernames not yet implemented'); + + // return cy.task('loadUserAndRole', { name: user }).then((loadedUser) => { + // username = loadedUser.username; + // password = loadedUser.password; + // + // return sendApiLoginRequest(username, password); + // }); + } else { + return sendApiLoginRequest(username, password); + } +}; + +loginServerless.with = ( + username: string, + password: string +): ReturnType => { + return sendApiLoginRequest(username, password); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts index 911f8a0511c11..47f6da88c6924 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts @@ -5,10 +5,22 @@ * 2.0. */ -import { request, loadPage } from './common'; +import type { UserAuthzAccessLevel } from '../screens'; +import { loadPage, request } from './common'; import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; -import { ACTION_DETAILS_ROUTE } from '../../../../common/endpoint/constants'; +import { + ACTION_DETAILS_ROUTE, + EXECUTE_ROUTE, + GET_FILE_ROUTE, + GET_PROCESSES_ROUTE, + ISOLATE_HOST_ROUTE_V2, + KILL_PROCESS_ROUTE, + SUSPEND_PROCESS_ROUTE, + UNISOLATE_HOST_ROUTE_V2, + UPLOAD_ROUTE, +} from '../../../../common/endpoint/constants'; import type { ActionDetails, ActionDetailsApiResponse } from '../../../../common/endpoint/types'; +import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants'; import { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../common/endpoint/service/response_actions/constants'; export const validateAvailableCommands = () => { @@ -84,9 +96,6 @@ export const waitForActionToComplete = ( return request({ method: 'GET', url: resolvePathVariables(ACTION_DETAILS_ROUTE, { action_id: actionId || 'undefined' }), - headers: { - 'Elastic-Api-Version': '2023-10-31', - }, }).then((response) => { if (response.body.data.isCompleted) { action = response.body.data; @@ -106,3 +115,95 @@ export const waitForActionToComplete = ( return action; }); }; + +/** + * Ensure user has the given `accessLevel` to the type of response action + * @param accessLevel + * @param responseAction + * @param username + * @param password + */ +export const ensureResponseActionAuthzAccess = ( + accessLevel: Exclude, + responseAction: ResponseActionsApiCommandNames, + username: string, + password: string +): Cypress.Chainable => { + let url: string = ''; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let apiPayload: any = { + endpoint_ids: ['some-id'], + }; + + switch (responseAction) { + case 'isolate': + url = ISOLATE_HOST_ROUTE_V2; + break; + + case 'unisolate': + url = UNISOLATE_HOST_ROUTE_V2; + break; + + case 'get-file': + url = GET_FILE_ROUTE; + Object.assign(apiPayload, { parameters: { path: 'one/two' } }); + break; + + case 'execute': + url = EXECUTE_ROUTE; + Object.assign(apiPayload, { parameters: { command: 'foo' } }); + break; + case 'running-processes': + url = GET_PROCESSES_ROUTE; + break; + + case 'kill-process': + url = KILL_PROCESS_ROUTE; + Object.assign(apiPayload, { parameters: { pid: 123 } }); + break; + + case 'suspend-process': + url = SUSPEND_PROCESS_ROUTE; + Object.assign(apiPayload, { parameters: { pid: 123 } }); + break; + + case 'upload': + url = UPLOAD_ROUTE; + { + const file = new File(['foo'], 'foo.txt'); + const formData = new FormData(); + + formData.append('file', file, file.name); + + for (const [key, value] of Object.entries(apiPayload as object)) { + formData.append(key, typeof value !== 'string' ? JSON.stringify(value) : value); + } + + apiPayload = formData; + } + break; + + default: + throw new Error(`Response action [${responseAction}] has no API payload defined`); + } + + const requestOptions: Partial = { + url, + method: 'post', + auth: { + user: username, + pass: password, + }, + headers: { + 'Content-Type': undefined, + }, + failOnStatusCode: false, + body: apiPayload as Cypress.RequestBody, + }; + + if (accessLevel === 'none') { + return request(requestOptions).its('status').should('equal', 403); + } + + return request(requestOptions).its('status').should('not.equal', 403); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_console.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_console.ts index 19ef3c5d68d37..958f16abad5c6 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_console.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_console.ts @@ -22,13 +22,11 @@ export const openResponseConsoleFromEndpointList = (): void => { }; export const inputConsoleCommand = (command: string): void => { - cy.getByTestSubj('endpointResponseActionsConsole-inputCapture').click().type(command); + cy.getByTestSubj('endpointResponseActionsConsole-inputCapture').type(command); }; export const clearConsoleCommandInput = (): void => { - cy.getByTestSubj('endpointResponseActionsConsole-inputCapture') - .click() - .type(`{selectall}{backspace}`); + cy.getByTestSubj('endpointResponseActionsConsole-inputCapture').type(`{selectall}{backspace}`); }; export const selectCommandFromHelpMenu = (command: string): void => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/run_endpoint_loader.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/run_endpoint_loader.ts index 36c06e8aee59b..71d9ec58e80ab 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/run_endpoint_loader.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/run_endpoint_loader.ts @@ -6,8 +6,13 @@ */ export const runEndpointLoaderScript = () => { - const { ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD, ELASTICSEARCH_URL, KIBANA_URL } = - Cypress.env(); + const { + ELASTICSEARCH_USERNAME, + ELASTICSEARCH_PASSWORD, + ELASTICSEARCH_URL, + KIBANA_URL, + IS_SERVERLESS, + } = Cypress.env(); const ES_URL = new URL(ELASTICSEARCH_URL); ES_URL.username = ELASTICSEARCH_USERNAME; @@ -16,8 +21,23 @@ export const runEndpointLoaderScript = () => { const KBN_URL = new URL(KIBANA_URL); KBN_URL.username = ELASTICSEARCH_USERNAME; KBN_URL.password = ELASTICSEARCH_PASSWORD; + // FIXME: remove use of cli script and use instead data loaders - const script = `node scripts/endpoint/resolver_generator.js --node="${ES_URL.toString()}" --kibana="${KBN_URL.toString()}" --delete --numHosts=1 --numDocs=1 --fleet --withNewUser=santaEndpoint:changeme --anc=1 --gen=1 --ch=1 --related=1 --relAlerts=1`; + const script = + `node scripts/endpoint/resolver_generator.js ` + + `--node="${ES_URL.toString()}" ` + + `--kibana="${KBN_URL.toString()}" ` + + `--delete ` + + `--numHosts=1 ` + + `--numDocs=1 ` + + `--fleet ` + + `--withNewUser=santaEndpoint:changeme ` + + `--anc=1 ` + + `--gen=1 ` + + `--ch=1 ` + + `--related=1 ` + + `--relAlerts=1 ` + + `--ssl=${IS_SERVERLESS}`; cy.exec(script, { env: { NODE_TLS_REJECT_UNAUTHORIZED: 1 }, timeout: 180000 }); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json b/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json index dc0b2e1ca4fd4..4d7fe47ec2d23 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json +++ b/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json @@ -2,9 +2,6 @@ "extends": "../../../../../../tsconfig.base.json", "include": [ "**/*", - "../cypress_endpoint.config.ts", - "../cypress.config.ts", - "./cypress.d.ts" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts b/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts deleted file mode 100644 index 73f260d63b4b9..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts +++ /dev/null @@ -1,58 +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 { defineCypressConfig } from '@kbn/cypress-config'; -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { dataLoaders, dataLoadersForRealEndpoints } from './cypress/support/data_loaders'; -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { responseActionTasks } from './cypress/support/response_actions'; - -// eslint-disable-next-line import/no-default-export -export default defineCypressConfig({ - reporter: '../../../../node_modules/cypress-multi-reporters', - reporterOptions: { - configFile: './public/management/reporter_config.json', - }, - - defaultCommandTimeout: 60000, - execTimeout: 120000, - pageLoadTimeout: 12000, - - retries: { - runMode: 1, - openMode: 0, - }, - - screenshotsFolder: - '../../../target/kibana-security-solution/public/management/cypress/screenshots', - trashAssetsBeforeRuns: false, - video: false, - viewportHeight: 900, - viewportWidth: 1440, - experimentalStudio: true, - - env: { - 'cypress-react-selector': { - root: '#security-solution-app', - }, - }, - - e2e: { - experimentalMemoryManagement: true, - experimentalInteractiveRunEvents: true, - baseUrl: 'http://localhost:5620', - supportFile: 'public/management/cypress/support/e2e.ts', - specPattern: 'public/management/cypress/e2e/endpoint/*.cy.{js,jsx,ts,tsx}', - experimentalRunAllSpecs: true, - setupNodeEvents: (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { - dataLoaders(on, config); - // Data loaders specific to "real" Endpoint testing - dataLoadersForRealEndpoints(on, config); - responseActionTasks(on, config); - }, - }, -}); diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx index b4f4e3ffc67d3..403731566ef3f 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx @@ -17,6 +17,8 @@ import { import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; import { act } from '@testing-library/react-hooks'; +const apiVersion = '2023-10-31'; + describe('Bulk delete artifact hook', () => { let result: ReturnType; @@ -56,6 +58,7 @@ describe('Bulk delete artifact hook', () => { expect(onSuccessMock).toHaveBeenCalledTimes(1); expect(fakeHttpServices.delete).toHaveBeenCalledTimes(2); expect(fakeHttpServices.delete).toHaveBeenNthCalledWith(1, '/api/exception_lists/items', { + version: apiVersion, query: { id: 'fakeId-1', item_id: undefined, @@ -63,6 +66,7 @@ describe('Bulk delete artifact hook', () => { }, }); expect(fakeHttpServices.delete).toHaveBeenNthCalledWith(2, '/api/exception_lists/items', { + version: apiVersion, query: { id: undefined, item_id: 'fakeId-2', diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx index 57a1f77243b5d..5db9734c61f9f 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx @@ -17,6 +17,8 @@ import { import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; import { act } from '@testing-library/react-hooks'; +const apiVersion = '2023-10-31'; + describe('Bulk update artifact hook', () => { let result: ReturnType; @@ -57,9 +59,11 @@ describe('Bulk update artifact hook', () => { expect(fakeHttpServices.put).toHaveBeenCalledTimes(2); expect(fakeHttpServices.put).toHaveBeenNthCalledWith(1, '/api/exception_lists/items', { body: JSON.stringify(ExceptionsListApiClient.cleanExceptionsBeforeUpdate(exceptionItem1)), + version: apiVersion, }); expect(fakeHttpServices.put).toHaveBeenNthCalledWith(2, '/api/exception_lists/items', { body: JSON.stringify(ExceptionsListApiClient.cleanExceptionsBeforeUpdate(exceptionItem2)), + version: apiVersion, }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx index a40ad85dff53c..853ea0df96a3d 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx @@ -54,6 +54,7 @@ describe('Create artifact hook', () => { expect(onSuccessMock).toHaveBeenCalledTimes(1); expect(fakeHttpServices.post).toHaveBeenCalledTimes(1); expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/exception_lists/items', { + version: '2023-10-31', body: JSON.stringify(exceptionItem), }); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx index f717546be7ead..9473a50fa8a33 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx @@ -54,6 +54,7 @@ describe('Delete artifact hook', () => { expect(onSuccessMock).toHaveBeenCalledTimes(1); expect(fakeHttpServices.delete).toHaveBeenCalledTimes(1); expect(fakeHttpServices.delete).toHaveBeenCalledWith('/api/exception_lists/items', { + version: '2023-10-31', query: { id: 'fakeId', namespace_type: 'agnostic', diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_artifact.test.tsx index dc444e75816c0..86acf7944b258 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_artifact.test.tsx @@ -49,6 +49,7 @@ describe('Get artifact hook', () => { expect(result.data).toBe(apiResponse); expect(fakeHttpServices.get).toHaveBeenCalledTimes(1); expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items', { + version: '2023-10-31', query: { item_id: 'fakeId', namespace_type: 'agnostic', diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx index d7d68e82c9d3e..8f6e5c4353206 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx @@ -68,6 +68,7 @@ describe('List artifact hook', () => { expect(result.data).toBe(apiResponse); expect(fakeHttpServices.get).toHaveBeenCalledTimes(1); expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + version: '2023-10-31', query: { filter: '((exception-list-agnostic.attributes.tags:"policy:policy-1" OR exception-list-agnostic.attributes.tags:"policy:all")) AND ((exception-list-agnostic.attributes.field-1:(*test*) OR exception-list-agnostic.attributes.field-1.field-2:(*test*) OR exception-list-agnostic.attributes.field-2:(*test*)))', diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.test.tsx index f0e8abd533fce..310e4d0e1830e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.test.tsx @@ -62,6 +62,7 @@ describe('Summary artifact hook', () => { expect(result.data).toBe(apiResponse); expect(fakeHttpServices.get).toHaveBeenCalledTimes(1); expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/summary', { + version: '2023-10-31', query: { filter: '((exception-list-agnostic.attributes.tags:"policy:policy-1" OR exception-list-agnostic.attributes.tags:"policy:all")) AND ((exception-list-agnostic.attributes.field-1:(*test*) OR exception-list-agnostic.attributes.field-1.field-2:(*test*) OR exception-list-agnostic.attributes.field-2:(*test*)))', diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx index 8a718ec6a292e..14607a33f2098 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx @@ -54,6 +54,7 @@ describe('Update artifact hook', () => { expect(onSuccessMock).toHaveBeenCalledTimes(1); expect(fakeHttpServices.put).toHaveBeenCalledTimes(1); expect(fakeHttpServices.put).toHaveBeenCalledWith('/api/exception_lists/items', { + version: '2023-10-31', body: JSON.stringify(ExceptionsListApiClient.cleanExceptionsBeforeUpdate(exceptionItem)), }); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts index 78cb1282f94a8..85647202a755c 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.test.ts @@ -17,6 +17,7 @@ import { DefaultPolicyRuleNotificationMessage, } from '../../../../common/endpoint/models/policy_config'; import { set } from 'lodash'; +import { API_VERSIONS } from '@kbn/fleet-plugin/common'; const useQueryMock = _useQuery as jest.Mock; @@ -60,6 +61,7 @@ describe('When using the `useGetFileInfo()` hook', () => { expect(apiMocks.responseProvider.endpointPackagePolicy).toHaveBeenCalledWith({ path: `/api/fleet/package_policies/${policy.id}`, + version: API_VERSIONS.public.v1, }); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.ts index 35d8a0f2ea255..bb8033ae012bb 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy.ts @@ -8,7 +8,7 @@ import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import { useQuery } from '@tanstack/react-query'; -import { packagePolicyRouteService } from '@kbn/fleet-plugin/common'; +import { packagePolicyRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; import { DefaultPolicyNotificationMessage, DefaultPolicyRuleNotificationMessage, @@ -45,7 +45,8 @@ export const useFetchEndpointPolicy = ( ...options, queryFn: async () => { const apiResponse = await http.get( - packagePolicyRouteService.getInfoPath(policyId) + packagePolicyRouteService.getInfoPath(policyId), + { version: API_VERSIONS.public.v1 } ); applyDefaultsToPolicyIfNeeded(apiResponse.item); diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.test.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.test.ts index 0513fee664dc1..80fd6d41dfd6e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.test.ts @@ -12,7 +12,7 @@ import type { PolicyData } from '../../../../common/endpoint/types'; import { allFleetHttpMocks } from '../../mocks'; import { FleetPackagePolicyGenerator } from '../../../../common/endpoint/data_generators/fleet_package_policy_generator'; import { useFetchAgentByAgentPolicySummary } from './use_fetch_endpoint_policy_agent_summary'; -import { agentRouteService } from '@kbn/fleet-plugin/common'; +import { agentRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; const useQueryMock = _useQuery as jest.Mock; @@ -57,6 +57,7 @@ describe('When using the `useFetchEndpointPolicyAgentSummary()` hook', () => { expect(apiMocks.responseProvider.agentStatus).toHaveBeenCalledWith({ path: agentRouteService.getStatusPath(), query: { policyId: policy.policy_id }, + version: API_VERSIONS.public.v1, }); expect(data).toEqual({ total: 50, diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.ts index cbe69a321d1f5..82cebb6263f96 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_fetch_endpoint_policy_agent_summary.ts @@ -9,7 +9,7 @@ import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { GetAgentStatusResponse } from '@kbn/fleet-plugin/common'; import { useQuery } from '@tanstack/react-query'; -import { agentRouteService } from '@kbn/fleet-plugin/common'; +import { agentRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; import { useHttp } from '../../../common/lib/kibana'; type EndpointPolicyAgentSummary = GetAgentStatusResponse['results']; @@ -30,6 +30,7 @@ export const useFetchAgentByAgentPolicySummary = ( return ( await http.get(agentRouteService.getStatusPath(), { query: { policyId: agentPolicyId }, + version: API_VERSIONS.public.v1, }) ).results; }, diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts index 1f9dc8e300f05..8b6be08b41b46 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts @@ -17,7 +17,7 @@ import type { RenderHookResult } from '@testing-library/react-hooks/src/types'; import { useUpdateEndpointPolicy } from './use_update_endpoint_policy'; import type { PolicyData } from '../../../../common/endpoint/types'; import { FleetPackagePolicyGenerator } from '../../../../common/endpoint/data_generators/fleet_package_policy_generator'; -import { packagePolicyRouteService } from '@kbn/fleet-plugin/common'; +import { API_VERSIONS, packagePolicyRouteService } from '@kbn/fleet-plugin/common'; import { getPolicyDataForUpdate } from '../../../../common/endpoint/service/policy'; const useMutationMock = _useMutation as jest.Mock; @@ -64,6 +64,7 @@ describe('When using the `useFetchEndpointPolicyAgentSummary()` hook', () => { expect(apiMocks.responseProvider.updateEndpointPolicy).toHaveBeenCalledWith({ path: packagePolicyRouteService.getUpdatePath(policy.id), body: JSON.stringify(getPolicyDataForUpdate(policy)), + version: API_VERSIONS.public.v1, }); expect(result).toEqual({ item: expect.any(Object) }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.ts index 2e7f8b83ac19b..5e078ad563481 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.ts @@ -8,7 +8,7 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import { useMutation } from '@tanstack/react-query'; -import { packagePolicyRouteService } from '@kbn/fleet-plugin/common'; +import { packagePolicyRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; import { getPolicyDataForUpdate } from '../../../../common/endpoint/service/policy'; import { useHttp } from '../../../common/lib/kibana'; import type { PolicyData } from '../../../../common/endpoint/types'; @@ -39,6 +39,7 @@ export const useUpdateEndpointPolicy = ( return http.put(packagePolicyRouteService.getUpdatePath(policy.id), { body: JSON.stringify(update), + version: API_VERSIONS.public.v1, }); }, options); }; diff --git a/x-pack/plugins/security_solution/public/management/links.test.ts b/x-pack/plugins/security_solution/public/management/links.test.ts index 17116530e6467..6a8c5525b8d58 100644 --- a/x-pack/plugins/security_solution/public/management/links.test.ts +++ b/x-pack/plugins/security_solution/public/management/links.test.ts @@ -93,7 +93,8 @@ describe('links', () => { SecurityPageName.hostIsolationExceptions, SecurityPageName.policies, SecurityPageName.responseActionsHistory, - SecurityPageName.trustedApps + SecurityPageName.trustedApps, + SecurityPageName.cloudDefendPolicies ) ); }); @@ -114,6 +115,7 @@ describe('links', () => { }); describe('Host Isolation Exception', () => { + const apiVersion = '2023-10-31'; it('should return HIE if user has access permission (licensed)', async () => { (calculateEndpointAuthz as jest.Mock).mockReturnValue( getEndpointAuthzInitialStateMock({ canAccessHostIsolationExceptions: true }) @@ -153,6 +155,7 @@ describe('links', () => { expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.hostIsolationExceptions)); expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + version: apiVersion, query: expect.objectContaining({ list_id: [ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id], }), @@ -173,6 +176,7 @@ describe('links', () => { expect(filteredLinks).toEqual(links); expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + version: apiVersion, query: expect.objectContaining({ list_id: [ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id], }), @@ -234,7 +238,9 @@ describe('links', () => { const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins()); - expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.policies)); + expect(filteredLinks).toEqual( + getLinksWithout(SecurityPageName.policies, SecurityPageName.cloudDefendPolicies) + ); }); }); diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 309e9a093979b..60c4c93d43fa6 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -235,6 +235,7 @@ export const getManagementFilteredLinks = async ( if (!canReadPolicyManagement) { linksToExclude.push(SecurityPageName.policies); + linksToExclude.push(SecurityPageName.cloudDefendPolicies); } if (!canReadActionsLogManagement) { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx index ba0131ffb15f1..7285239b398a6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx @@ -34,14 +34,6 @@ import { EndpointPolicyLink } from '../../../../components/endpoint_policy_link' import { OutOfDate } from '../components/out_of_date'; const EndpointDetailsContentStyled = styled.div` - dl dt { - max-width: 27%; - } - - dl dd { - max-width: 73%; - } - .policyLineText { padding-right: 5px; } @@ -239,7 +231,9 @@ export const EndpointDetailsContent = memo( { }) ), path: '/api/exception_lists/items', + version: '2023-10-31', }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx index 98b9d3bd8404d..37d52c27b8d8c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx @@ -6,12 +6,7 @@ */ import React, { memo, useCallback } from 'react'; -import { - EuiButton, - EuiEmptyPrompt, - EuiPageTemplate_Deprecated as EuiPageTemplate, - EuiLink, -} from '@elastic/eui'; +import { EuiButton, EuiLink, EuiPageTemplate } from '@elastic/eui'; import { usePolicyDetailsArtifactsNavigateCallback } from '../../policy_hooks'; import { useGetLinkTo } from './use_policy_artifacts_empty_hooks'; import { useUserPrivileges } from '../../../../../../common/components/user_privileges'; @@ -54,42 +49,41 @@ export const PolicyArtifactsEmptyUnassigned = memo( [navigateCallback] ); return ( - - {labels.emptyUnassignedTitle}} - body={ - canWriteArtifact - ? labels.emptyUnassignedMessage(policyName) - : labels.emptyUnassignedNoPrivilegesMessage(policyName) - } - actions={[ - ...(canCreateArtifactsByPolicy && canWriteArtifact - ? [ - - {labels.emptyUnassignedPrimaryActionButtonTitle} - , - ] - : []), - canWriteArtifact ? ( - // eslint-disable-next-line @elastic/eui/href-or-on-click - - {labels.emptyUnassignedSecondaryActionButtonTitle} - - ) : null, - ]} - /> - + {labels.emptyUnassignedTitle}} + body={ + canWriteArtifact + ? labels.emptyUnassignedMessage(policyName) + : labels.emptyUnassignedNoPrivilegesMessage(policyName) + } + actions={[ + ...(canCreateArtifactsByPolicy && canWriteArtifact + ? [ + + {labels.emptyUnassignedPrimaryActionButtonTitle} + , + ] + : []), + canWriteArtifact ? ( + // eslint-disable-next-line @elastic/eui/href-or-on-click + + {labels.emptyUnassignedSecondaryActionButtonTitle} + + ) : null, + ]} + /> ); } ); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx index 1b1e52c872465..2ff8a399d3858 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx @@ -6,11 +6,7 @@ */ import React, { memo } from 'react'; -import { - EuiEmptyPrompt, - EuiButton, - EuiPageTemplate_Deprecated as EuiPageTemplate, -} from '@elastic/eui'; +import { EuiButton, EuiPageTemplate } from '@elastic/eui'; import { useGetLinkTo } from './use_policy_artifacts_empty_hooks'; import type { POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS } from './translations'; import type { ArtifactListPageUrlParams } from '../../../../../components/artifact_list_page'; @@ -43,28 +39,27 @@ export const PolicyArtifactsEmptyUnexisting = memo( } ); return ( - - {labels.emptyUnexistingTitle}} - body={labels.emptyUnexistingMessage} - actions={ - canWriteArtifact ? ( - // eslint-disable-next-line @elastic/eui/href-or-on-click - - {labels.emptyUnexistingPrimaryActionButtonTitle} - - ) : null - } - /> - + {labels.emptyUnexistingTitle}} + body={labels.emptyUnexistingMessage} + actions={ + canWriteArtifact ? ( + // eslint-disable-next-line @elastic/eui/href-or-on-click + + {labels.emptyUnexistingPrimaryActionButtonTitle} + + ) : null + } + /> ); } ); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx index c93776ef0abac..48d133cd41f29 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx @@ -28,8 +28,11 @@ import { cleanEventFilterToUpdate } from '../../../../event_filters/service/serv import { EventFiltersApiClient } from '../../../../event_filters/service/api_client'; import { POLICY_ARTIFACT_FLYOUT_LABELS } from './translations'; +const apiVersion = '2023-10-31'; + const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({ path: '/api/exception_lists/items/_find', + version: apiVersion, query: { filter: customFilter, list_id: ['endpoint_event_filters'], @@ -217,6 +220,7 @@ describe('Policy details artifacts flyout', () => { // verify the request with the new tag await waitFor(() => { expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenCalledWith({ + version: apiVersion, body: JSON.stringify( getCleanedExceptionWithNewTags(exceptions.data[0], testTags, policy) ), @@ -244,6 +248,7 @@ describe('Policy details artifacts flyout', () => { await waitFor(() => { // first exception expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenCalledWith({ + version: apiVersion, body: JSON.stringify( getCleanedExceptionWithNewTags(exceptions.data[0], testTags, policy) ), @@ -251,6 +256,7 @@ describe('Policy details artifacts flyout', () => { }); // second exception expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenCalledWith({ + version: apiVersion, body: JSON.stringify( getCleanedExceptionWithNewTags(exceptions.data[0], testTags, policy) ), diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx index 666bb5534d3ec..70ac3ce16aab7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx @@ -15,7 +15,7 @@ import { EuiSpacer, EuiLink, EuiButton, - EuiPageContent_Deprecated as EuiPageContent, + EuiPageSection, } from '@elastic/eui'; import { useAppUrl } from '../../../../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../../../../common/constants'; @@ -218,13 +218,7 @@ export const PolicyArtifactsLayout = React.memo( /> )} - + ( getPolicyArtifactsPath={getPolicyArtifactsPath} getArtifactPath={getArtifactPath} /> - +
    ); } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx index 2d5e74439f0f6..1ad26fd171c28 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx @@ -25,6 +25,7 @@ import { EventFiltersApiClient } from '../../../../event_filters/service/api_cli const endpointGenerator = new EndpointDocGenerator('seed'); const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({ path: '/api/exception_lists/items/_find', + version: '2023-10-31', query: { filter: customFilter, list_id: ['endpoint_event_filters'], diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index f1a0976cbf021..7f72a8d475134 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -8,11 +8,7 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { useLocation } from 'react-router-dom'; -import { - EuiCallOut, - EuiLoadingSpinner, - EuiPageTemplate_Deprecated as EuiPageTemplate, -} from '@elastic/eui'; +import { EuiCallOut, EuiLoadingSpinner, EuiPageTemplate } from '@elastic/eui'; import { usePolicyDetailsSelector } from './policy_hooks'; import { policyDetails, agentStatusSummary, apiError } from '../store/policy_details/selectors'; import { AgentsSummary } from './components/agents_summary'; @@ -78,23 +74,23 @@ export const PolicyDetails = React.memo(() => { const pageBody: React.ReactNode = useMemo(() => { if (policyApiError) { return ( - + {policyApiError?.message} - + ); } if (!policyItem) { return ( - + - + ); } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index 6b77d4a615a72..1a1a76ddcf198 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -6,6 +6,7 @@ */ import React, { memo, useCallback, useMemo } from 'react'; + import type { CriteriaWithPagination } from '@elastic/eui'; import { EuiBasicTable, @@ -15,12 +16,17 @@ import { EuiFlexGroup, EuiFlexItem, EuiToolTip, + EuiIconTip, + EuiCallOut, + useEuiTheme, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { useLocation } from 'react-router-dom'; import type { CreatePackagePolicyRouteState } from '@kbn/fleet-plugin/public'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; +import moment from 'moment'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { AdministrationListPage } from '../../../components/administration_list_page'; import { FormattedDate } from '../../../../common/components/formatted_date'; @@ -28,6 +34,7 @@ import { EndpointPolicyLink } from '../../../components/endpoint_policy_link'; import type { PolicyData, PolicyDetailsRouteState } from '../../../../../common/endpoint/types'; import { useUrlPagination } from '../../../hooks/use_url_pagination'; import { + useEndpointPackagePoliciesStats, useGetEndpointSecurityPackage, useGetEndpointSpecificPolicies, } from '../../../services/policies/hooks'; @@ -41,10 +48,12 @@ import { ManagementEmptyStateWrapper } from '../../../components/management_empt export const PolicyList = memo(() => { const { canReadEndpointList, loading: authLoading } = useUserPrivileges().endpointPrivileges; + const isProtectionUpdatesEnabled = useIsExperimentalFeatureEnabled('protectionUpdatesEnabled'); const { pagination, pageSizeOptions, setPagination } = useUrlPagination(); const { search } = useLocation(); const { getAppUrl } = useAppUrl(); const toasts = useToasts(); + const { euiTheme } = useEuiTheme(); // load the list of policies const { @@ -56,6 +65,9 @@ export const PolicyList = memo(() => { perPage: pagination.pageSize, }); + const { data: outdatedManifestsCountResponse, isLoading: isOutdatedManifestsCountLoading } = + useEndpointPackagePoliciesStats(isProtectionUpdatesEnabled); + // grab endpoint version for empty page const { data: endpointPackageInfo, isFetching: packageIsFetching } = useGetEndpointSecurityPackage({ @@ -112,6 +124,31 @@ export const PolicyList = memo(() => { }, } ); + + const outdatedManifestsCallOut = useMemo(() => { + if ( + !isProtectionUpdatesEnabled || + isOutdatedManifestsCountLoading || + !outdatedManifestsCountResponse || + outdatedManifestsCountResponse.outdatedManifestsCount === 0 + ) { + return null; + } + + return ( + + ); + }, [isOutdatedManifestsCountLoading, isProtectionUpdatesEnabled, outdatedManifestsCountResponse]); + const policyColumns = useMemo(() => { const updatedAtColumnName = i18n.translate('xpack.securitySolution.policy.list.updatedAt', { defaultMessage: 'Last Updated', @@ -121,6 +158,45 @@ export const PolicyList = memo(() => { defaultMessage: 'Date Created', }); + const generateDeployedVersionEntry = (version?: 'latest' | string) => { + if (!version) { + return []; + } + + if (version === 'latest') { + return [ + 'success', + i18n.translate('xpack.securitySolution.policy.list.manifestLatest', { + defaultMessage: 'latest', + }), + ]; + } + + const parsedDate = moment.utc(version, 'YYYY-MM-DD'); + + if (parsedDate < moment.utc().subtract(18, 'months')) { + return [ + 'danger', + parsedDate.format('MMMM DD, YYYY'), + i18n.translate('xpack.securitySolution.policy.list.manifestOver18MonthsOld', { + defaultMessage: 'Manifest is over 18 months old and rollback is not supported', + }), + ]; + } + + if (parsedDate > moment.utc().subtract(1, 'month')) { + return ['success', parsedDate.format('MMMM DD, YYYY')]; + } + + return [ + euiTheme.colors.warning, + parsedDate.format('MMMM DD, YYYY'), + i18n.translate('xpack.securitySolution.policy.list.manifestOver1MonthOld', { + defaultMessage: 'Manifest is over a month old', + }), + ]; + }; + return [ { field: '', @@ -141,6 +217,35 @@ export const PolicyList = memo(() => { ); }, }, + ...(isProtectionUpdatesEnabled + ? [ + { + field: '', + name: i18n.translate('xpack.securitySolution.policy.list.deployedVersion', { + defaultMessage: 'Deployed Version', + }), + truncateText: true, + render: (policy: PolicyData) => { + const [color, displayText, tooltip] = generateDeployedVersionEntry( + policy.inputs[0]?.config?.policy.value.global_manifest_version + ); + + return ( + + + + + + + {displayText} + + + + ); + }, + }, + ] + : []), { field: 'created_by', name: i18n.translate('xpack.securitySolution.policy.list.createdBy', { @@ -233,7 +338,13 @@ export const PolicyList = memo(() => { }, }, ]; - }, [backLink, authLoading, canReadEndpointList]); + }, [ + isProtectionUpdatesEnabled, + euiTheme.colors.warning, + backLink, + authLoading, + canReadEndpointList, + ]); const handleTableOnChange = useCallback( ({ page }: CriteriaWithPagination) => { @@ -280,6 +391,7 @@ export const PolicyList = memo(() => { /> + {outdatedManifestsCallOut} { expect(apiMocks.responseProvider.updateEndpointPolicy).toHaveBeenCalledWith({ path: packagePolicyRouteService.getUpdatePath(policyData.id), body: JSON.stringify(getPolicyDataForUpdate(expectedUpdatedPolicy)), + version: API_VERSIONS.public.v1, }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_get_protection_updates_note.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_get_protection_updates_note.ts new file mode 100644 index 0000000000000..0ac3db09b35a2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_get_protection_updates_note.ts @@ -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 { useQuery } from '@tanstack/react-query'; +import { resolvePathVariables } from '../../../../../../common/utils/resolve_path_variables'; +import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../../../../common/endpoint/constants'; +import { useKibana } from '../../../../../../common/lib/kibana'; + +export const getProtectionUpdatesNoteQueryKey = (packagePolicyId: string) => + `protection-updates-note-${packagePolicyId}`; + +interface UseProtectionUpdatesNote { + packagePolicyId: string; +} + +interface NoteResponse { + note: string; +} + +export const useGetProtectionUpdatesNote = ({ packagePolicyId }: UseProtectionUpdatesNote) => { + const { http } = useKibana().services; + + return useQuery<{ data: NoteResponse }, unknown, NoteResponse>( + [getProtectionUpdatesNoteQueryKey(packagePolicyId)], + () => + http.get( + resolvePathVariables(PROTECTION_UPDATES_NOTE_ROUTE, { package_policy_id: packagePolicyId }), + { + version: '2023-10-31', + } + ), + { + keepPreviousData: true, + enabled: !!packagePolicyId, + retry: false, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_post_protection_updates_note.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_post_protection_updates_note.ts new file mode 100644 index 0000000000000..16b6ee66f07ca --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_post_protection_updates_note.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { getProtectionUpdatesNoteQueryKey } from './use_get_protection_updates_note'; +import { useKibana } from '../../../../../../common/lib/kibana'; +import { resolvePathVariables } from '../../../../../../common/utils/resolve_path_variables'; +import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../../../../common/endpoint/constants'; + +interface ProtectionUpdatesNoteParams { + packagePolicyId: string; +} + +interface NoteResponse { + note: string; +} + +export const useCreateProtectionUpdatesNote = ({ + packagePolicyId, +}: ProtectionUpdatesNoteParams) => { + const { http } = useKibana().services; + const queryClient = useQueryClient(); + + return useMutation< + { data: NoteResponse }, + { body: { error: string; message: string } }, + NoteResponse + >( + (payload) => + http.post( + resolvePathVariables(PROTECTION_UPDATES_NOTE_ROUTE, { policy_id: packagePolicyId }), + { + version: '2023-10-31', + body: JSON.stringify(payload), + } + ), + { + onSuccess: () => { + queryClient.invalidateQueries([getProtectionUpdatesNoteQueryKey(packagePolicyId)]); + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx index 09439918dfde0..eaac2595c931b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx @@ -13,14 +13,16 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, + EuiIconTip, EuiPanel, EuiShowFor, EuiSpacer, EuiSwitch, EuiText, + EuiTextArea, EuiTitle, } from '@elastic/eui'; -import React, { useCallback, useContext, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { ThemeContext } from 'styled-components'; import { i18n } from '@kbn/i18n'; @@ -28,6 +30,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { Moment } from 'moment'; import moment from 'moment'; import { cloneDeep } from 'lodash'; +import { useCreateProtectionUpdatesNote } from './hooks/use_post_protection_updates_note'; +import { useGetProtectionUpdatesNote } from './hooks/use_get_protection_updates_note'; import { useUserPrivileges } from '../../../../../common/components/user_privileges'; import { useToasts } from '../../../../../common/lib/kibana'; import { useUpdateEndpointPolicy } from '../../../../hooks/policy/use_update_endpoint_policy'; @@ -64,14 +68,28 @@ export const ProtectionUpdatesLayout = React.memo( const deployedVersion = policy.inputs[0].config.policy.value.global_manifest_version; const [manifestVersion, setManifestVersion] = useState(deployedVersion); - const today = moment(); + const today = moment.utc(); const [selectedDate, setSelectedDate] = useState(today); + const { data: fetchedNote, isLoading: getNoteInProgress } = useGetProtectionUpdatesNote({ + packagePolicyId: _policy.id, + }); + const { isLoading: createNoteInProgress, mutate: createNote } = useCreateProtectionUpdatesNote({ + packagePolicyId: _policy.id, + }); + const [note, setNote] = useState(''); + + useEffect(() => { + if (fetchedNote && !getNoteInProgress) { + setNote(fetchedNote.note); + } + }, [fetchedNote, getNoteInProgress]); + const automaticUpdatesEnabled = manifestVersion === 'latest'; const internalDateFormat = 'YYYY-MM-DD'; const displayDateFormat = 'MMMM DD, YYYY'; - const formattedDate = moment(deployedVersion, internalDateFormat).format(displayDateFormat); - const cutoffDate = moment().subtract(18, 'months'); // Earliest selectable date + const formattedDate = moment.utc(deployedVersion, internalDateFormat).format(displayDateFormat); + const cutoffDate = moment.utc().subtract(18, 'months').add(1, 'day'); // Earliest selectable date const viewModeSwitchLabel = automaticUpdatesEnabled ? AUTOMATIC_UPDATES_CHECKBOX_LABEL @@ -119,8 +137,27 @@ export const ProtectionUpdatesLayout = React.memo( text: err.message, }); }); + if ((!fetchedNote && note !== '') || (fetchedNote && note !== fetchedNote.note)) { + createNote( + { note }, + { + onError: (error) => { + toasts.addDanger({ + 'data-test-subj': 'protectionUpdatesNoteUpdateFailureMessage', + title: i18n.translate( + 'xpack.securitySolution.endpoint.protectionUpdates.noteUpdateErrorTitle', + { + defaultMessage: 'Note update failed!', + } + ), + text: error.body.message, + }); + }, + } + ); + } }, - [dispatch, policy, sendPolicyUpdate, toasts] + [policy, sendPolicyUpdate, fetchedNote, note, toasts, dispatch, createNote] ); const toggleAutomaticUpdates = useCallback( @@ -189,7 +226,7 @@ export const ProtectionUpdatesLayout = React.memo( return null; } - const deployedVersionDate = moment(deployedVersion).format(internalDateFormat); + const deployedVersionDate = moment.utc(deployedVersion).format(internalDateFormat); const daysSinceLastUpdate = today.diff(deployedVersionDate, 'days'); if (daysSinceLastUpdate < 30) { @@ -260,16 +297,57 @@ export const ProtectionUpdatesLayout = React.memo( )} - + {deployedVersion === 'latest' ? 'latest' : formattedDate} + {renderVersionToDeployPicker()} + + +
    + {i18n.translate('xpack.securitySolution.endpoint.protectionUpdates.note.label', { + defaultMessage: 'Note', + })} +
    +
    + + + + } + /> +
    + + {canWritePolicyManagement ? ( + setNote(e.target.value)} + fullWidth={true} + rows={3} + placeholder={i18n.translate( + 'xpack.securitySolution.endpoint.protectionUpdates.note.placeholder', + { + defaultMessage: 'Add relevant information about update here', + } + )} + data-test-subj={'protection-updates-manifest-note'} + /> + ) : ( + {note} + )} + ( {canWritePolicyManagement ? ( { return { id: `agent-id-${i}`, name: `Host-name-${i}`, - selected: [0, 1, 3, 5].includes(i) ? true : false, + selected: [0, 1, 3, 5].includes(i), }; }), page: 0, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/empty_state.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/empty_state.tsx index c4a0d44d19e25..6dc176b1cb52d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/empty_state.tsx @@ -6,7 +6,7 @@ */ import React, { memo } from 'react'; -import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiButton, EuiPageTemplate } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { ManagementEmptyStateWrapper } from '../../../../components/management_empty_state_wrapper'; @@ -18,7 +18,7 @@ export const EmptyState = memo<{ }>(({ onAdd, isAddDisabled = false, backComponent }) => { return ( - ({ sortOrder: 'asc', }); +const apiVersion = '2023-10-31'; + describe('Exceptions List Api Client', () => { let fakeCoreStart: jest.Mocked; let fakeHttpServices: jest.Mocked; @@ -136,6 +138,7 @@ describe('Exceptions List Api Client', () => { expect(fakeHttpServices.get).toHaveBeenCalledTimes(1); const expectedQueryParams = getQueryParams(); expect(fakeHttpServices.get).toHaveBeenCalledWith(`${EXCEPTION_LIST_ITEM_URL}/_find`, { + version: apiVersion, query: { page: expectedQueryParams.page, per_page: expectedQueryParams.perPage, @@ -156,6 +159,7 @@ describe('Exceptions List Api Client', () => { expect(fakeHttpServices.get).toHaveBeenCalledTimes(1); expect(fakeHttpServices.get).toHaveBeenCalledWith(EXCEPTION_LIST_ITEM_URL, { + version: apiVersion, query: { item_id: fakeItemId, id: undefined, @@ -175,6 +179,7 @@ describe('Exceptions List Api Client', () => { expect(fakeHttpServices.post).toHaveBeenCalledTimes(1); expect(fakeHttpServices.post).toHaveBeenCalledWith(EXCEPTION_LIST_ITEM_URL, { + version: apiVersion, body: JSON.stringify(exceptionItem), }); }); @@ -202,6 +207,7 @@ describe('Exceptions List Api Client', () => { expect(fakeHttpServices.put).toHaveBeenCalledTimes(1); expect(fakeHttpServices.put).toHaveBeenCalledWith(EXCEPTION_LIST_ITEM_URL, { + version: apiVersion, body: JSON.stringify(ExceptionsListApiClient.cleanExceptionsBeforeUpdate(exceptionItem)), }); }); @@ -214,6 +220,7 @@ describe('Exceptions List Api Client', () => { expect(fakeHttpServices.delete).toHaveBeenCalledTimes(1); expect(fakeHttpServices.delete).toHaveBeenCalledWith(EXCEPTION_LIST_ITEM_URL, { + version: apiVersion, query: { item_id: fakeItemId, id: undefined, @@ -230,6 +237,7 @@ describe('Exceptions List Api Client', () => { expect(fakeHttpServices.get).toHaveBeenCalledTimes(1); expect(fakeHttpServices.get).toHaveBeenCalledWith(`${EXCEPTION_LIST_URL}/summary`, { + version: apiVersion, query: { filter: fakeQklFilter, list_id: getFakeListId(), @@ -248,6 +256,7 @@ describe('Exceptions List Api Client', () => { await expect(exceptionsListApiClientInstance.hasData()).resolves.toBe(true); expect(fakeHttpServices.get).toHaveBeenCalledWith(`${EXCEPTION_LIST_ITEM_URL}/_find`, { + version: apiVersion, query: expect.objectContaining({ page: 1, per_page: 1, diff --git a/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts b/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts index 4576fa74e5386..8716f4bee93ab 100644 --- a/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts +++ b/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts @@ -43,9 +43,11 @@ export class ExceptionsListApiClient { T extends CreateExceptionListItemSchema | UpdateExceptionListItemSchema >( item: T - ) => T + ) => T, + public readonly version?: string ) { this.ensureListExists = this.createExceptionList(); + this.version = version ?? '2023-10-31'; } /** @@ -184,6 +186,7 @@ export class ExceptionsListApiClient { const result = await this.http.get( `${EXCEPTION_LIST_ITEM_URL}/_find`, { + version: this.version, query: { page, per_page: perPage, @@ -214,6 +217,7 @@ export class ExceptionsListApiClient { await this.ensureListExists; let result = await this.http.get(EXCEPTION_LIST_ITEM_URL, { + version: this.version, query: { id, item_id: itemId, @@ -243,6 +247,7 @@ export class ExceptionsListApiClient { } return this.http.post(EXCEPTION_LIST_ITEM_URL, { + version: this.version, body: JSON.stringify(transformedException), }); } @@ -260,6 +265,7 @@ export class ExceptionsListApiClient { } return this.http.put(EXCEPTION_LIST_ITEM_URL, { + version: this.version, body: JSON.stringify( ExceptionsListApiClient.cleanExceptionsBeforeUpdate(transformedException) ), @@ -277,6 +283,7 @@ export class ExceptionsListApiClient { await this.ensureListExists; return this.http.delete(EXCEPTION_LIST_ITEM_URL, { + version: this.version, query: { id, item_id: itemId, @@ -292,6 +299,7 @@ export class ExceptionsListApiClient { async summary(filter?: string): Promise { await this.ensureListExists; return this.http.get(`${EXCEPTION_LIST_URL}/summary`, { + version: this.version, query: { filter, list_id: this.listId, diff --git a/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts b/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts index 0c6f4e7db9b89..0e20ce9dcbb1a 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts @@ -8,7 +8,10 @@ import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query import { useQuery } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { GetInfoResponse } from '@kbn/fleet-plugin/common'; -import { useHttp } from '../../../common/lib/kibana'; +import { firstValueFrom } from 'rxjs'; +import type { IKibanaSearchResponse } from '@kbn/data-plugin/common'; +import { ENDPOINT_PACKAGE_POLICIES_STATS_STRATEGY } from '../../../../common/endpoint/constants'; +import { useHttp, useKibana } from '../../../common/lib/kibana'; import { MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../common/constants'; import { sendGetEndpointSecurityPackage } from './ingest'; import type { GetPolicyListResponse } from '../../pages/policy/types'; @@ -47,6 +50,22 @@ export function useGetEndpointSpecificPolicies( ); } +export function useEndpointPackagePoliciesStats(enabled: boolean) { + const { data } = useKibana().services; + return useQuery( + ['endpointPackagePoliciesStatsStrategy'], + async () => { + return firstValueFrom( + data.search.search<{}, IKibanaSearchResponse<{ outdatedManifestsCount: number }>>( + {}, + { strategy: ENDPOINT_PACKAGE_POLICIES_STATS_STRATEGY } + ) + ); + }, + { select: (response) => response.rawResponse, enabled } + ); +} + /** * This hook returns the endpoint security package which contains endpoint version info */ diff --git a/x-pack/plugins/security_solution/public/management/services/policies/ingest.test.ts b/x-pack/plugins/security_solution/public/management/services/policies/ingest.test.ts index a3644ce691478..d498f32bb1cab 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/ingest.test.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/ingest.test.ts @@ -7,7 +7,7 @@ import { sendGetPackagePolicy, sendGetEndpointSecurityPackage } from './ingest'; import { httpServiceMock } from '@kbn/core/public/mocks'; -import { PACKAGE_POLICY_API_ROOT, epmRouteService } from '@kbn/fleet-plugin/common'; +import { PACKAGE_POLICY_API_ROOT, epmRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; import { policyListApiPathHandlers } from '../../pages/policy/store/test_mock_utils'; describe('ingest service', () => { @@ -20,7 +20,9 @@ describe('ingest service', () => { describe('sendGetPackagePolicy()', () => { it('builds correct API path', async () => { await sendGetPackagePolicy(http, '123'); - expect(http.get).toHaveBeenCalledWith(`${PACKAGE_POLICY_API_ROOT}/123`, undefined); + expect(http.get).toHaveBeenCalledWith(`${PACKAGE_POLICY_API_ROOT}/123`, { + version: API_VERSIONS.public.v1, + }); }); it('supports http options', async () => { await sendGetPackagePolicy(http, '123', { query: { page: 1 } }); @@ -28,6 +30,7 @@ describe('ingest service', () => { query: { page: 1, }, + version: API_VERSIONS.public.v1, }); }); }); @@ -37,7 +40,9 @@ describe('ingest service', () => { const path = epmRouteService.getInfoPath('endpoint'); http.get.mockReturnValue(Promise.resolve(policyListApiPathHandlers()[path]())); await sendGetEndpointSecurityPackage(http); - expect(http.get).toHaveBeenCalledWith(path); + expect(http.get).toHaveBeenCalledWith(path, { + version: API_VERSIONS.public.v1, + }); }); it('should throw if package is not found', async () => { diff --git a/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts b/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts index e5347449d4dbe..4a2690a112e24 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts @@ -11,7 +11,7 @@ import type { GetPackagePoliciesResponse, GetInfoResponse, } from '@kbn/fleet-plugin/common'; -import { epmRouteService } from '@kbn/fleet-plugin/common'; +import { epmRouteService, API_VERSIONS } from '@kbn/fleet-plugin/common'; import type { NewPolicyData } from '../../../../common/endpoint/types'; import type { GetPolicyResponse, UpdatePolicyResponse } from '../../pages/policy/types'; @@ -34,7 +34,10 @@ export const sendGetPackagePolicy = ( packagePolicyId: string, options?: HttpFetchOptions ) => { - return http.get(`${INGEST_API_PACKAGE_POLICIES}/${packagePolicyId}`, options); + return http.get(`${INGEST_API_PACKAGE_POLICIES}/${packagePolicyId}`, { + ...options, + version: API_VERSIONS.public.v1, + }); }; /** @@ -50,6 +53,7 @@ export const sendBulkGetPackagePolicies = ( ) => { return http.post(`${INGEST_API_PACKAGE_POLICIES}/_bulk_get`, { ...options, + version: API_VERSIONS.public.v1, body: JSON.stringify({ ids: packagePolicyIds, ignoreMissing: true, @@ -73,6 +77,7 @@ export const sendPutPackagePolicy = ( ): Promise => { return http.put(`${INGEST_API_PACKAGE_POLICIES}/${packagePolicyId}`, { ...options, + version: API_VERSIONS.public.v1, body: JSON.stringify(packagePolicy), }); }; @@ -92,6 +97,7 @@ export const sendGetFleetAgentStatusForPolicy = ( ): Promise => { return http.get(INGEST_API_FLEET_AGENT_STATUS, { ...options, + version: API_VERSIONS.public.v1, query: { policyId, }, @@ -105,7 +111,9 @@ export const sendGetEndpointSecurityPackage = async ( http: HttpStart ): Promise => { const path = epmRouteService.getInfoPath('endpoint'); - const endpointPackageResponse = await http.get(path); + const endpointPackageResponse = await http.get(path, { + version: API_VERSIONS.public.v1, + }); const endpointPackageInfo = endpointPackageResponse.item; if (!endpointPackageInfo) { throw new Error('Endpoint package was not found.'); diff --git a/x-pack/plugins/security_solution/public/management/services/policies/policies.test.ts b/x-pack/plugins/security_solution/public/management/services/policies/policies.test.ts index 86715ebff3f75..a523f94d489c5 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/policies.test.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/policies.test.ts @@ -7,7 +7,7 @@ import { httpServiceMock } from '@kbn/core/public/mocks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; -import { PACKAGE_POLICY_API_ROUTES } from '@kbn/fleet-plugin/common/constants/routes'; +import { API_VERSIONS, PACKAGE_POLICY_API_ROUTES } from '@kbn/fleet-plugin/common/constants'; import { sendGetEndpointSpecificPackagePolicies } from './policies'; describe('ingest service', () => { @@ -24,6 +24,7 @@ describe('ingest service', () => { query: { kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`, }, + version: API_VERSIONS.public.v1, }); }); it('supports additional KQL to be defined on input for query params', async () => { @@ -36,6 +37,7 @@ describe('ingest service', () => { perPage: 10, page: 1, }, + version: API_VERSIONS.public.v1, }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/services/policies/policies.ts b/x-pack/plugins/security_solution/public/management/services/policies/policies.ts index de700e20e8c40..a7a45f832b56d 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/policies.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/policies.ts @@ -7,7 +7,7 @@ import type { HttpFetchOptions, HttpStart } from '@kbn/core/public'; import type { GetPackagePoliciesRequest } from '@kbn/fleet-plugin/common'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, API_VERSIONS } from '@kbn/fleet-plugin/common'; import type { GetPolicyListResponse } from '../../pages/policy/types'; import { INGEST_API_PACKAGE_POLICIES } from './ingest'; @@ -29,5 +29,6 @@ export const sendGetEndpointSpecificPackagePolicies = ( options?.query?.kuery ? `${options.query.kuery} and ` : '' }${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`, }, + version: API_VERSIONS.public.v1, }); }; diff --git a/x-pack/plugins/security_solution/public/mocks.ts b/x-pack/plugins/security_solution/public/mocks.ts index b48da210d6756..9e0757047879a 100644 --- a/x-pack/plugins/security_solution/public/mocks.ts +++ b/x-pack/plugins/security_solution/public/mocks.ts @@ -28,6 +28,7 @@ const startMock = (): PluginStart => ({ ), setExtraRoutes: jest.fn(), getUpselling: () => upselling, + setDashboardsLandingCallout: jest.fn(), }); export const securitySolutionMock = { diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 360c95ec94019..b8592df20b3b5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -14,6 +14,7 @@ import type { DataViewBase, Filter, Query } from '@kbn/es-query'; import styled from 'styled-components'; import { EuiButton } from '@elastic/eui'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; +import type { RunTimeMappings } from '@kbn/timelines-plugin/common/api/search_strategy'; import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/events_viewer/translations'; import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; @@ -195,7 +196,7 @@ const EventsByDatasetComponent: React.FC = ({ headerChildren={headerContent} id={uniqueQueryId} indexNames={indexNames} - runtimeMappings={runtimeMappings} + runtimeMappings={runtimeMappings as RunTimeMappings} onError={toggleTopN} paddingSize={paddingSize} setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget} diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_ti_data_sources.ts b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_ti_data_sources.ts index 96a0c44327909..086ad78ac3c5f 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_ti_data_sources.ts +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_ti_data_sources.ts @@ -10,11 +10,11 @@ import { useEffect, useState } from 'react'; import { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { isCompleteResponse } from '@kbn/data-plugin/public'; +import type { ThreatIntelSourceRequestOptionsInput } from '../../../../common/api/search_strategy'; import { useKibana } from '../../../common/lib/kibana'; import type { Bucket, CtiDataSourceStrategyResponse, - CtiDataSourceRequestOptions, } from '../../../../common/search_strategy/security_solution/cti'; import { CtiQueries } from '../../../../common/search_strategy/security_solution/cti'; import { DEFAULT_THREAT_INDEX_KEY } from '../../../../common/constants'; @@ -22,7 +22,7 @@ import type { GlobalTimeArgs } from '../../../common/containers/use_global_time' import { OTHER_DATA_SOURCE_TITLE } from '../../components/overview_cti_links/translations'; import { OTHER_TI_DATASET_KEY } from '../../../../common/cti/constants'; -type GetThreatIntelSourcProps = CtiDataSourceRequestOptions & { +type GetThreatIntelSourceProps = Omit & { data: DataPublicPluginStart; signal: AbortSignal; }; @@ -33,8 +33,8 @@ export const getTiDataSources = ({ defaultIndex, timerange, signal, -}: GetThreatIntelSourcProps): Observable => - data.search.search( +}: GetThreatIntelSourceProps): Observable => + data.search.search( { defaultIndex, factoryQueryType: CtiQueries.dataSource, @@ -47,7 +47,7 @@ export const getTiDataSources = ({ ); export const getTiDataSourcesComplete = ( - props: GetThreatIntelSourcProps + props: GetThreatIntelSourceProps ): Observable => { return getTiDataSources(props).pipe( filter((response) => { diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index c482c7cfa1616..904ec870e9a2c 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -194,7 +194,7 @@ export class Plugin implements IPlugin SecuritySolutionTemplateWrapper, }, - savedObjectsManagement: startPluginsDeps.savedObjectsManagement, + contentManagement: startPluginsDeps.contentManagement, telemetry: this.telemetry.start(), customDataService, topValuesPopover: new TopValuesPopoverService(), @@ -225,6 +225,11 @@ export class Plugin implements IPlugin; public isSidebarEnabled$: BehaviorSubject; public getStartedComponent$: BehaviorSubject; + public dashboardsLandingCallout$: BehaviorSubject; public upsellingService: UpsellingService; public extraRoutes$: BehaviorSubject; public appLinksSwitcher: AppLinksSwitcher; @@ -26,6 +27,8 @@ export class PluginContract { this.isILMAvailable$ = new BehaviorSubject(true); this.isSidebarEnabled$ = new BehaviorSubject(true); this.getStartedComponent$ = new BehaviorSubject(null); + this.dashboardsLandingCallout$ = new BehaviorSubject(null); + this.upsellingService = new UpsellingService(); this.appLinksSwitcher = (appLinks) => appLinks; } @@ -36,6 +39,7 @@ export class PluginContract { isILMAvailable$: this.isILMAvailable$.asObservable(), isSidebarEnabled$: this.isSidebarEnabled$.asObservable(), getStartedComponent$: this.getStartedComponent$.asObservable(), + dashboardsLandingCalloutComponent$: this.dashboardsLandingCallout$.asObservable(), upselling: this.upsellingService, }; } @@ -59,6 +63,9 @@ export class PluginContract { setGetStartedPage: (getStartedComponent) => { this.getStartedComponent$.next(getStartedComponent); }, + setDashboardsLandingCallout: (dashboardsLandingCallout) => { + this.dashboardsLandingCallout$.next(dashboardsLandingCallout); + }, getBreadcrumbsNav$: () => breadcrumbsNav$, getUpselling: () => this.upsellingService, }; diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx index f085c0a906e39..e5e1f88a36169 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx @@ -216,6 +216,7 @@ function EventDetailFields({ event }: { event: SafeResolverEvent }) { = ({ timelineId }) => { - + - + diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx index d3007d0d6346a..c8156c455d7e2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx @@ -42,12 +42,12 @@ const FlyoutPaneComponent: React.FC = ({
    ( size="l" /> ), - actions: , + actions: , }, ] : [], diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 2a810e67af05a..6fcc467c7626b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -364,7 +364,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r data-type="row" >
    Host ID
    @@ -378,7 +378,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    First seen
    @@ -393,7 +393,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r />
    Last seen
    @@ -417,7 +417,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r data-type="row" >
    IP addresses
    @@ -431,7 +431,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    MAC addresses
    @@ -445,7 +445,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    Platform
    @@ -468,7 +468,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r data-type="row" >
    Operating system
    @@ -482,7 +482,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    Family
    @@ -496,7 +496,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    Version
    @@ -510,7 +510,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    Architecture
    @@ -533,7 +533,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r data-type="row" >
    Cloud provider
    @@ -547,7 +547,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    Region
    @@ -561,7 +561,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    Instance ID
    @@ -575,7 +575,7 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r
    Machine type
    @@ -707,7 +707,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul data-type="row" >
    Location
    @@ -721,7 +721,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul
    Autonomous system
    @@ -744,7 +744,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul data-type="row" >
    First seen
    @@ -759,7 +759,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul />
    Last seen
    @@ -783,7 +783,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul data-type="row" >
    Host ID
    @@ -797,7 +797,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul
    Host name
    @@ -820,7 +820,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul data-type="row" >
    WhoIs
    @@ -853,7 +853,7 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul
    Reputation
    diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts index 6e197ad52e4ff..597091bcfce0b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts @@ -111,7 +111,6 @@ export const useManagedUser = (userName: string) => { if (!isInitializing) { search({ defaultIndex: MANAGED_USER_INDEX, - factoryQueryType: UsersQueries.managedDetails, userName, }); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_button.test.tsx new file mode 100644 index 0000000000000..e43fa5faf5c19 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_button.test.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, fireEvent, waitFor, screen } from '@testing-library/react'; +import type { EditTimelineComponentProps } from './edit_timeline_button'; +import { EditTimelineButton } from './edit_timeline_button'; +import { TestProviders } from '../../../../common/mock'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; + +const TEST_ID = { + EDIT_TIMELINE_MODAL: 'edit-timeline-modal', + EDIT_TIMELINE_BTN: 'edit-timeline-button-icon', + EDIT_TIMELINE_TOOLTIP: 'edit-timeline-tooltip', +}; + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: jest.fn(), + }; +}); + +jest.mock('../../../../common/lib/kibana'); + +jest.mock('../../../../common/components/user_privileges'); + +const props = { + initialFocus: 'title' as const, + timelineId: 'timeline-1', + toolTip: 'tooltip message', +}; + +const TestEditTimelineButton = (_props: EditTimelineComponentProps) => ( + + + +); + +jest.mock('raf', () => { + return jest.fn().mockImplementation((cb) => cb()); +}); + +describe('EditTimelineButton', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('Show tooltip', async () => { + render(); + const editTimelineIcon = screen.queryAllByTestId(TEST_ID.EDIT_TIMELINE_BTN)[0]; + + fireEvent.mouseOver(editTimelineIcon); + + await waitFor(() => { + expect(screen.getByRole('tooltip')).toBeVisible(); + }); + }); + + it('should show a button with pencil icon', () => { + render(); + + expect(screen.getByTestId(TEST_ID.EDIT_TIMELINE_BTN).firstChild).toHaveAttribute( + 'data-euiicon-type', + 'pencil' + ); + }); + + it('should have edit timeline btn disabled with tooltip if user does not have write access', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: false }, + }); + render( + + + + ); + expect(screen.getByTestId(TEST_ID.EDIT_TIMELINE_BTN)).toBeDisabled(); + }); + + it('should not show modal if user does not have write access', async () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: false }, + }); + render(); + + expect(screen.queryByTestId(TEST_ID.EDIT_TIMELINE_MODAL)).not.toBeInTheDocument(); + + const editTimelineIcon = screen.getByTestId(TEST_ID.EDIT_TIMELINE_BTN); + + fireEvent.click(editTimelineIcon); + + await waitFor(() => { + expect(screen.queryAllByTestId(TEST_ID.EDIT_TIMELINE_MODAL)).toHaveLength(0); + }); + }); + + it('should show a modal when user has crud privileges', async () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: true }, + }); + render(); + expect(screen.queryByTestId(TEST_ID.EDIT_TIMELINE_MODAL)).not.toBeInTheDocument(); + + const editTimelineIcon = screen.queryAllByTestId(TEST_ID.EDIT_TIMELINE_BTN)[0]; + + fireEvent.click(editTimelineIcon); + + await waitFor(() => { + expect(screen.queryByTestId(TEST_ID.EDIT_TIMELINE_TOOLTIP)).not.toBeInTheDocument(); + expect(screen.queryAllByTestId(TEST_ID.EDIT_TIMELINE_MODAL)[0]).toBeVisible(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_button.tsx new file mode 100644 index 0000000000000..ad59ffbbd6288 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_button.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 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 { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; + +import { TimelineId } from '../../../../../common/types/timeline'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { timelineActions } from '../../../store/timeline'; +import { getTimelineSaveModalByIdSelector } from './selectors'; +import { EditTimelineModal } from './edit_timeline_modal'; +import * as timelineTranslations from './translations'; + +export interface EditTimelineComponentProps { + initialFocus: 'title' | 'description'; + timelineId: string; + toolTip?: string; +} + +export const EditTimelineButton = React.memo( + ({ initialFocus, timelineId, toolTip }) => { + const dispatch = useDispatch(); + const getTimelineSaveModal = useMemo(() => getTimelineSaveModalByIdSelector(), []); + const show = useDeepEqualSelector((state) => getTimelineSaveModal(state, timelineId)); + const [showEditTimelineOverlay, setShowEditTimelineOverlay] = useState(false); + + const closeEditTimeline = useCallback(() => { + setShowEditTimelineOverlay(false); + if (show) { + dispatch( + timelineActions.toggleModalSaveTimeline({ + id: TimelineId.active, + showModalSaveTimeline: false, + }) + ); + } + }, [dispatch, setShowEditTimelineOverlay, show]); + + const openEditTimeline = useCallback(() => { + setShowEditTimelineOverlay(true); + }, [setShowEditTimelineOverlay]); + + // Case: 1 + // check if user has crud privileges so that user can be allowed to edit the timeline + // Case: 2 + // TODO: User may have Crud privileges but they may not have access to timeline index. + // Do we need to check that? + const { + kibanaSecuritySolutionsPrivileges: { crud: hasKibanaCrud }, + } = useUserPrivileges(); + + const finalTooltipMsg = useMemo( + () => (hasKibanaCrud ? toolTip : timelineTranslations.CALL_OUT_UNAUTHORIZED_MSG), + [toolTip, hasKibanaCrud] + ); + + const editTimelineButtonIcon = useMemo( + () => ( + + ), + [openEditTimeline, hasKibanaCrud] + ); + + return (initialFocus === 'title' && show) || showEditTimelineOverlay ? ( + <> + {editTimelineButtonIcon} + + + ) : ( + + {editTimelineButtonIcon} + + ); + } +); + +EditTimelineButton.displayName = 'EditTimelineButton'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_modal.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_modal.test.tsx new file mode 100644 index 0000000000000..4581b38a2fd56 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_modal.test.tsx @@ -0,0 +1,260 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { TestProviders } from '../../../../common/mock'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { EditTimelineModal } from './edit_timeline_modal'; +import * as i18n from './translations'; + +jest.mock('../../../../common/hooks/use_selector', () => ({ + useDeepEqualSelector: jest.fn(), +})); + +jest.mock('../properties/use_create_timeline', () => ({ + useCreateTimeline: jest.fn(), +})); + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: jest.fn(), + }; +}); + +describe('EditTimelineModal', () => { + describe('save timeline', () => { + const props = { + initialFocus: 'title' as const, + closeEditTimeline: jest.fn(), + timelineId: 'timeline-1', + }; + + const mockGetButton = jest.fn().mockReturnValue(
    ); + + beforeEach(() => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ + description: '', + isSaving: true, + status: TimelineStatus.draft, + title: 'my timeline', + timelineType: TimelineType.default, + }); + }); + + afterEach(() => { + (useDeepEqualSelector as jest.Mock).mockReset(); + mockGetButton.mockClear(); + }); + + test('show process bar while saving', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); + }); + + test('Show correct header for edit timeline modal', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( + i18n.SAVE_TIMELINE + ); + }); + + test('Show correct header for edit timeline template modal', () => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ + description: '', + isSaving: true, + status: TimelineStatus.draft, + title: 'my timeline', + timelineType: TimelineType.template, + }); + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( + i18n.SAVE_TIMELINE_TEMPLATE + ); + }); + + test('Show name field', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="edit-timeline-title"]').exists()).toEqual(true); + }); + + test('Show description field', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="edit-timeline-description"]').exists()).toEqual(true); + }); + + test('Show close button', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="close-button"]').exists()).toEqual(true); + }); + + test('Show saveButton', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="save-button"]').exists()).toEqual(true); + }); + }); + + describe('update timeline', () => { + const props = { + initialFocus: 'title' as const, + closeEditTimeline: jest.fn(), + timelineId: 'timeline-1', + }; + + const mockGetButton = jest.fn().mockReturnValue(
    ); + + beforeEach(() => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ + description: 'xxxx', + isSaving: true, + status: TimelineStatus.active, + title: 'my timeline', + timelineType: TimelineType.default, + }); + }); + + afterEach(() => { + (useDeepEqualSelector as jest.Mock).mockReset(); + mockGetButton.mockClear(); + }); + + test('show process bar while saving', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); + }); + + test('Show correct header for save timeline modal', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( + i18n.NAME_TIMELINE + ); + }); + + test('Show correct header for edit timeline template modal', () => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ + description: 'xxxx', + isSaving: true, + status: TimelineStatus.active, + title: 'my timeline', + timelineType: TimelineType.template, + }); + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( + i18n.NAME_TIMELINE_TEMPLATE + ); + }); + + test('Show name field', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="edit-timeline-title"]').exists()).toEqual(true); + }); + + test('Show description field', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="edit-timeline-description"]').exists()).toEqual(true); + }); + + test('Show saveButton', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="save-button"]').exists()).toEqual(true); + }); + }); + + describe('showWarning', () => { + const props = { + initialFocus: 'title' as const, + closeEditTimeline: jest.fn(), + timelineId: 'timeline-1', + showWarning: true, + }; + + const mockGetButton = jest.fn().mockReturnValue(
    ); + + beforeEach(() => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ + description: '', + isSaving: true, + status: TimelineStatus.draft, + title: 'my timeline', + timelineType: TimelineType.default, + showWarnging: true, + }); + }); + + afterEach(() => { + (useDeepEqualSelector as jest.Mock).mockReset(); + mockGetButton.mockClear(); + }); + + test('Show EuiCallOut', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="edit-timeline-callout"]').exists()).toEqual(true); + }); + + test('Show discardTimelineButton', () => { + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="close-button"]').at(2).text()).toEqual( + 'Discard Timeline' + ); + }); + + test('get discardTimelineTemplateButton with correct props', () => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ + description: 'xxxx', + isSaving: true, + status: TimelineStatus.draft, + title: 'my timeline', + timelineType: TimelineType.template, + }); + const component = mount(, { + wrappingComponent: TestProviders, + }); + expect(component.find('[data-test-subj="close-button"]').at(2).text()).toEqual( + 'Discard Timeline Template' + ); + }); + + test('Show saveButton', () => { + const component = mount(); + expect(component.find('[data-test-subj="save-button"]').at(1).exists()).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_modal.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_modal.tsx new file mode 100644 index 0000000000000..3a09d3b59601f --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/edit_timeline_modal.tsx @@ -0,0 +1,263 @@ +/* + * Copyright 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 { pick } from 'lodash/fp'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiSpacer, + EuiProgress, + EuiCallOut, +} from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import usePrevious from 'react-use/lib/usePrevious'; + +import { getUseField, Field, Form, useForm } from '../../../../shared_imports'; +import { TimelineId } from '../../../../../common/types/timeline'; +import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { timelineActions, timelineSelectors } from '../../../store/timeline'; +import { NOTES_PANEL_WIDTH } from '../properties/notes_size'; +import { useCreateTimeline } from '../properties/use_create_timeline'; +import * as commonI18n from '../properties/translations'; +import * as i18n from './translations'; +import { formSchema } from './schema'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; +import { TIMELINE_ACTIONS } from '../../../../common/lib/apm/user_actions'; + +const CommonUseField = getUseField({ component: Field }); +interface EditTimelineModalProps { + closeEditTimeline: () => void; + initialFocus: 'title' | 'description'; + timelineId: string; + showWarning?: boolean; +} + +// when showWarning equals to true, +// the modal is used as a reminder for users to save / discard +// the unsaved timeline / template +export const EditTimelineModal = React.memo( + ({ closeEditTimeline, initialFocus, timelineId, showWarning }) => { + const { startTransaction } = useStartTransaction(); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const { + isSaving, + description = '', + status, + title = '', + timelineType, + } = useDeepEqualSelector((state) => + pick( + ['isSaving', 'description', 'status', 'title', 'timelineType'], + getTimeline(state, timelineId) + ) + ); + const prevIsSaving = usePrevious(isSaving); + const dispatch = useDispatch(); + const handleCreateNewTimeline = useCreateTimeline({ + timelineId: TimelineId.active, + timelineType: TimelineType.default, + }); + + const handleSubmit = useCallback( + (titleAndDescription, isValid) => { + if (isValid) { + dispatch( + timelineActions.updateTitleAndDescription({ + id: timelineId, + ...titleAndDescription, + }) + ); + } + + return Promise.resolve(); + }, + [dispatch, timelineId] + ); + + const initialState = useMemo( + () => ({ + title, + description, + }), + [title, description] + ); + + const { form } = useForm({ + id: 'timelineTitleAndDescriptionForm', + schema: formSchema, + onSubmit: handleSubmit, + options: { + stripEmptyFields: false, + }, + defaultValue: initialState, + }); + const { isSubmitted, isSubmitting, submit } = form; + + const onSubmit = useCallback(() => { + startTransaction({ name: TIMELINE_ACTIONS.SAVE }); + submit(); + }, [submit, startTransaction]); + + const handleCancel = useCallback(() => { + if (showWarning) { + handleCreateNewTimeline(); + } + closeEditTimeline(); + }, [closeEditTimeline, handleCreateNewTimeline, showWarning]); + + const closeModalText = useMemo(() => { + if (status === TimelineStatus.draft && showWarning) { + return timelineType === TimelineType.template + ? i18n.DISCARD_TIMELINE_TEMPLATE + : i18n.DISCARD_TIMELINE; + } + return i18n.CLOSE_MODAL; + }, [showWarning, status, timelineType]); + + const modalHeader = useMemo( + () => + status === TimelineStatus.draft + ? timelineType === TimelineType.template + ? i18n.SAVE_TIMELINE_TEMPLATE + : i18n.SAVE_TIMELINE + : timelineType === TimelineType.template + ? i18n.NAME_TIMELINE_TEMPLATE + : i18n.NAME_TIMELINE, + [status, timelineType] + ); + + const saveButtonTitle = useMemo( + () => + status === TimelineStatus.draft && showWarning + ? timelineType === TimelineType.template + ? i18n.SAVE_TIMELINE_TEMPLATE + : i18n.SAVE_TIMELINE + : i18n.SAVE, + [showWarning, status, timelineType] + ); + + const calloutMessage = useMemo( + () => i18n.UNSAVED_TIMELINE_WARNING(timelineType), + [timelineType] + ); + + const descriptionLabel = useMemo(() => `${i18n.TIMELINE_DESCRIPTION} (${i18n.OPTIONAL})`, []); + + const titleFieldProps = useMemo( + () => ({ + 'aria-label': i18n.TIMELINE_TITLE, + autoFocus: initialFocus === 'title', + 'data-test-subj': 'edit-timeline-title', + disabled: isSaving, + spellCheck: true, + placeholder: + timelineType === TimelineType.template + ? commonI18n.UNTITLED_TEMPLATE + : commonI18n.UNTITLED_TIMELINE, + }), + [initialFocus, isSaving, timelineType] + ); + + const descriptionFieldProps = useMemo( + () => ({ + 'aria-label': i18n.TIMELINE_DESCRIPTION, + autoFocus: initialFocus === 'description', + 'data-test-subj': 'edit-timeline-description', + disabled: isSaving, + placeholder: commonI18n.DESCRIPTION, + }), + [initialFocus, isSaving] + ); + + useEffect(() => { + if (isSubmitted && !isSaving && prevIsSaving) { + closeEditTimeline(); + } + }, [isSubmitted, isSaving, prevIsSaving, closeEditTimeline]); + + return ( + + {isSaving && ( + + )} + {modalHeader} + + + {showWarning && ( + + + + + )} +
    + + + + + + + + + + + + + {closeModalText} + + + + + {saveButtonTitle} + + + + +
    +
    +
    + ); + } +); + +EditTimelineModal.displayName = 'EditTimelineModal'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx deleted file mode 100644 index ee67b469e9250..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx +++ /dev/null @@ -1,124 +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 { render, fireEvent, waitFor, screen } from '@testing-library/react'; -import type { SaveTimelineComponentProps } from './save_timeline_button'; -import { SaveTimelineButton } from './save_timeline_button'; -import { TestProviders } from '../../../../common/mock'; -import { useUserPrivileges } from '../../../../common/components/user_privileges'; - -const TEST_ID = { - SAVE_TIMELINE_MODAL: 'save-timeline-modal', - SAVE_TIMELINE_BTN: 'save-timeline-button-icon', - SAVE_TIMELINE_TOOLTIP: 'save-timeline-tooltip', -}; - -jest.mock('react-redux', () => { - const actual = jest.requireActual('react-redux'); - return { - ...actual, - useDispatch: jest.fn(), - }; -}); - -jest.mock('../../../../common/lib/kibana'); - -jest.mock('../../../../common/components/user_privileges'); - -const props = { - initialFocus: 'title' as const, - timelineId: 'timeline-1', - toolTip: 'tooltip message', -}; - -const TestSaveTimelineButton = (_props: SaveTimelineComponentProps) => ( - - - -); - -jest.mock('raf', () => { - return jest.fn().mockImplementation((cb) => cb()); -}); - -describe('SaveTimelineButton', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - // skipping this test because popover is not getting visible by RTL gestures. - // - // Raised a bug with eui team: https://github.com/elastic/eui/issues/6065 - xit('Show tooltip', async () => { - render(); - const saveTimelineIcon = screen.queryAllByTestId(TEST_ID.SAVE_TIMELINE_BTN)[0]; - - fireEvent.mouseOver(saveTimelineIcon); - - jest.runAllTimers(); - - await waitFor(() => { - expect(screen.getByRole('tooltip')).toBeVisible(); - }); - }); - - it('should show a button with pencil icon', () => { - render(); - - expect(screen.getByTestId(TEST_ID.SAVE_TIMELINE_BTN).firstChild).toHaveAttribute( - 'data-euiicon-type', - 'pencil' - ); - }); - - it('should have edit timeline btn disabled with tooltip if user does not have write access', () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - kibanaSecuritySolutionsPrivileges: { crud: false }, - }); - render( - - - - ); - expect(screen.getByTestId(TEST_ID.SAVE_TIMELINE_BTN)).toBeDisabled(); - }); - - it('should not show modal if user does not have write access', async () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - kibanaSecuritySolutionsPrivileges: { crud: false }, - }); - render(); - - expect(screen.queryByTestId(TEST_ID.SAVE_TIMELINE_MODAL)).not.toBeInTheDocument(); - - const saveTimelineIcon = screen.getByTestId(TEST_ID.SAVE_TIMELINE_BTN); - - fireEvent.click(saveTimelineIcon); - - await waitFor(() => { - expect(screen.queryAllByTestId(TEST_ID.SAVE_TIMELINE_MODAL)).toHaveLength(0); - }); - }); - - it('should show a modal when user has crud privileges', async () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - kibanaSecuritySolutionsPrivileges: { crud: true }, - }); - render(); - expect(screen.queryByTestId(TEST_ID.SAVE_TIMELINE_MODAL)).not.toBeInTheDocument(); - - const saveTimelineIcon = screen.queryAllByTestId(TEST_ID.SAVE_TIMELINE_BTN)[0]; - - fireEvent.click(saveTimelineIcon); - - await waitFor(() => { - expect(screen.queryByTestId(TEST_ID.SAVE_TIMELINE_TOOLTIP)).not.toBeInTheDocument(); - expect(screen.queryAllByTestId(TEST_ID.SAVE_TIMELINE_MODAL)[0]).toBeVisible(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx deleted file mode 100644 index 20657356063c7..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { useUserPrivileges } from '../../../../common/components/user_privileges'; - -import { TimelineId } from '../../../../../common/types/timeline'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { timelineActions } from '../../../store/timeline'; -import { getTimelineSaveModalByIdSelector } from './selectors'; -import { TimelineTitleAndDescription } from './title_and_description'; -import * as timelineTranslations from './translations'; - -export interface SaveTimelineComponentProps { - initialFocus: 'title' | 'description'; - timelineId: string; - toolTip?: string; -} - -export const SaveTimelineButton = React.memo( - ({ initialFocus, timelineId, toolTip }) => { - const dispatch = useDispatch(); - const getTimelineSaveModal = useMemo(() => getTimelineSaveModalByIdSelector(), []); - const show = useDeepEqualSelector((state) => getTimelineSaveModal(state, timelineId)); - const [showSaveTimelineOverlay, setShowSaveTimelineOverlay] = useState(false); - - const closeSaveTimeline = useCallback(() => { - setShowSaveTimelineOverlay(false); - if (show) { - dispatch( - timelineActions.toggleModalSaveTimeline({ - id: TimelineId.active, - showModalSaveTimeline: false, - }) - ); - } - }, [dispatch, setShowSaveTimelineOverlay, show]); - - const openSaveTimeline = useCallback(() => { - setShowSaveTimelineOverlay(true); - }, [setShowSaveTimelineOverlay]); - - // Case: 1 - // check if user has crud privileges so that user can be allowed to edit the timeline - // Case: 2 - // TODO: User may have Crud privileges but they may not have access to timeline index. - // Do we need to check that? - const { - kibanaSecuritySolutionsPrivileges: { crud: hasKibanaCrud }, - } = useUserPrivileges(); - - const finalTooltipMsg = useMemo( - () => (hasKibanaCrud ? toolTip : timelineTranslations.CALL_OUT_UNAUTHORIZED_MSG), - [toolTip, hasKibanaCrud] - ); - - const saveTimelineButtonIcon = useMemo( - () => ( - - ), - [openSaveTimeline, hasKibanaCrud] - ); - - return (initialFocus === 'title' && show) || showSaveTimelineOverlay ? ( - <> - {saveTimelineButtonIcon} - - - ) : ( - - {saveTimelineButtonIcon} - - ); - } -); - -SaveTimelineButton.displayName = 'SaveTimelineButton'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx deleted file mode 100644 index 39b1cdef0063b..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx +++ /dev/null @@ -1,273 +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 { mount } from 'enzyme'; - -import { TestProviders } from '../../../../common/mock'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; -import { TimelineTitleAndDescription } from './title_and_description'; -import * as i18n from './translations'; - -jest.mock('../../../../common/hooks/use_selector', () => ({ - useDeepEqualSelector: jest.fn(), -})); - -jest.mock('../properties/use_create_timeline', () => ({ - useCreateTimeline: jest.fn(), -})); - -jest.mock('react-redux', () => { - const actual = jest.requireActual('react-redux'); - return { - ...actual, - useDispatch: jest.fn(), - }; -}); - -describe('TimelineTitleAndDescription', () => { - describe('save timeline', () => { - const props = { - initialFocus: 'title' as const, - closeSaveTimeline: jest.fn(), - timelineId: 'timeline-1', - onSaveTimeline: jest.fn(), - updateTitle: jest.fn(), - updateDescription: jest.fn(), - }; - - const mockGetButton = jest.fn().mockReturnValue(
    ); - - beforeEach(() => { - (useDeepEqualSelector as jest.Mock).mockReturnValue({ - description: '', - isSaving: true, - status: TimelineStatus.draft, - title: 'my timeline', - timelineType: TimelineType.default, - }); - }); - - afterEach(() => { - (useDeepEqualSelector as jest.Mock).mockReset(); - mockGetButton.mockClear(); - }); - - test('show process bar while saving', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); - }); - - test('Show correct header for save timeline modal', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( - i18n.SAVE_TIMELINE - ); - }); - - test('Show correct header for save timeline template modal', () => { - (useDeepEqualSelector as jest.Mock).mockReturnValue({ - description: '', - isSaving: true, - status: TimelineStatus.draft, - title: 'my timeline', - timelineType: TimelineType.template, - }); - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( - i18n.SAVE_TIMELINE_TEMPLATE - ); - }); - - test('Show name field', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="save-timeline-title"]').exists()).toEqual(true); - }); - - test('Show description field', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="save-timeline-description"]').exists()).toEqual(true); - }); - - test('Show close button', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="close-button"]').exists()).toEqual(true); - }); - - test('Show saveButton', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="save-button"]').exists()).toEqual(true); - }); - }); - - describe('update timeline', () => { - const props = { - initialFocus: 'title' as const, - closeSaveTimeline: jest.fn(), - openSaveTimeline: jest.fn(), - timelineId: 'timeline-1', - toggleSaveTimeline: jest.fn(), - onSaveTimeline: jest.fn(), - updateTitle: jest.fn(), - updateDescription: jest.fn(), - }; - - const mockGetButton = jest.fn().mockReturnValue(
    ); - - beforeEach(() => { - (useDeepEqualSelector as jest.Mock).mockReturnValue({ - description: 'xxxx', - isSaving: true, - status: TimelineStatus.active, - title: 'my timeline', - timelineType: TimelineType.default, - }); - }); - - afterEach(() => { - (useDeepEqualSelector as jest.Mock).mockReset(); - mockGetButton.mockClear(); - }); - - test('show process bar while saving', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); - }); - - test('Show correct header for save timeline modal', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( - i18n.NAME_TIMELINE - ); - }); - - test('Show correct header for save timeline template modal', () => { - (useDeepEqualSelector as jest.Mock).mockReturnValue({ - description: 'xxxx', - isSaving: true, - status: TimelineStatus.active, - title: 'my timeline', - timelineType: TimelineType.template, - }); - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="modal-header"]').at(1).prop('children')).toEqual( - i18n.NAME_TIMELINE_TEMPLATE - ); - }); - - test('Show name field', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="save-timeline-title"]').exists()).toEqual(true); - }); - - test('Show description field', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="save-timeline-description"]').exists()).toEqual(true); - }); - - test('Show saveButton', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="save-button"]').exists()).toEqual(true); - }); - }); - - describe('showWarning', () => { - const props = { - initialFocus: 'title' as const, - closeSaveTimeline: jest.fn(), - openSaveTimeline: jest.fn(), - timelineId: 'timeline-1', - toggleSaveTimeline: jest.fn(), - onSaveTimeline: jest.fn(), - updateTitle: jest.fn(), - updateDescription: jest.fn(), - showWarning: true, - }; - - const mockGetButton = jest.fn().mockReturnValue(
    ); - - beforeEach(() => { - (useDeepEqualSelector as jest.Mock).mockReturnValue({ - description: '', - isSaving: true, - status: TimelineStatus.draft, - title: 'my timeline', - timelineType: TimelineType.default, - showWarnging: true, - }); - }); - - afterEach(() => { - (useDeepEqualSelector as jest.Mock).mockReset(); - mockGetButton.mockClear(); - }); - - test('Show EuiCallOut', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="save-timeline-callout"]').exists()).toEqual(true); - }); - - test('Show discardTimelineButton', () => { - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="close-button"]').at(2).text()).toEqual( - 'Discard Timeline' - ); - }); - - test('get discardTimelineTemplateButton with correct props', () => { - (useDeepEqualSelector as jest.Mock).mockReturnValue({ - description: 'xxxx', - isSaving: true, - status: TimelineStatus.draft, - title: 'my timeline', - timelineType: TimelineType.template, - }); - const component = mount(, { - wrappingComponent: TestProviders, - }); - expect(component.find('[data-test-subj="close-button"]').at(2).text()).toEqual( - 'Discard Timeline Template' - ); - }); - - test('Show saveButton', () => { - const component = mount(); - expect(component.find('[data-test-subj="save-button"]').at(1).exists()).toEqual(true); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx deleted file mode 100644 index b4118355265ca..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx +++ /dev/null @@ -1,263 +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 { pick } from 'lodash/fp'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiSpacer, - EuiProgress, - EuiCallOut, -} from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; -import usePrevious from 'react-use/lib/usePrevious'; - -import { getUseField, Field, Form, useForm } from '../../../../shared_imports'; -import { TimelineId } from '../../../../../common/types/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { timelineActions, timelineSelectors } from '../../../store/timeline'; -import { NOTES_PANEL_WIDTH } from '../properties/notes_size'; -import { useCreateTimeline } from '../properties/use_create_timeline'; -import * as commonI18n from '../properties/translations'; -import * as i18n from './translations'; -import { formSchema } from './schema'; -import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; -import { TIMELINE_ACTIONS } from '../../../../common/lib/apm/user_actions'; - -const CommonUseField = getUseField({ component: Field }); -interface TimelineTitleAndDescriptionProps { - closeSaveTimeline: () => void; - initialFocus: 'title' | 'description'; - timelineId: string; - showWarning?: boolean; -} - -// when showWarning equals to true, -// the modal is used as a reminder for users to save / discard -// the unsaved timeline / template -export const TimelineTitleAndDescription = React.memo( - ({ closeSaveTimeline, initialFocus, timelineId, showWarning }) => { - const { startTransaction } = useStartTransaction(); - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { - isSaving, - description = '', - status, - title = '', - timelineType, - } = useDeepEqualSelector((state) => - pick( - ['isSaving', 'description', 'status', 'title', 'timelineType'], - getTimeline(state, timelineId) - ) - ); - const prevIsSaving = usePrevious(isSaving); - const dispatch = useDispatch(); - const handleCreateNewTimeline = useCreateTimeline({ - timelineId: TimelineId.active, - timelineType: TimelineType.default, - }); - - const handleSubmit = useCallback( - (titleAndDescription, isValid) => { - if (isValid) { - dispatch( - timelineActions.updateTitleAndDescription({ - id: timelineId, - ...titleAndDescription, - }) - ); - } - - return Promise.resolve(); - }, - [dispatch, timelineId] - ); - - const initialState = useMemo( - () => ({ - title, - description, - }), - [title, description] - ); - - const { form } = useForm({ - id: 'timelineTitleAndDescriptionForm', - schema: formSchema, - onSubmit: handleSubmit, - options: { - stripEmptyFields: false, - }, - defaultValue: initialState, - }); - const { isSubmitted, isSubmitting, submit } = form; - - const onSubmit = useCallback(() => { - startTransaction({ name: TIMELINE_ACTIONS.SAVE }); - submit(); - }, [submit, startTransaction]); - - const handleCancel = useCallback(() => { - if (showWarning) { - handleCreateNewTimeline(); - } - closeSaveTimeline(); - }, [closeSaveTimeline, handleCreateNewTimeline, showWarning]); - - const closeModalText = useMemo(() => { - if (status === TimelineStatus.draft && showWarning) { - return timelineType === TimelineType.template - ? i18n.DISCARD_TIMELINE_TEMPLATE - : i18n.DISCARD_TIMELINE; - } - return i18n.CLOSE_MODAL; - }, [showWarning, status, timelineType]); - - const modalHeader = useMemo( - () => - status === TimelineStatus.draft - ? timelineType === TimelineType.template - ? i18n.SAVE_TIMELINE_TEMPLATE - : i18n.SAVE_TIMELINE - : timelineType === TimelineType.template - ? i18n.NAME_TIMELINE_TEMPLATE - : i18n.NAME_TIMELINE, - [status, timelineType] - ); - - const saveButtonTitle = useMemo( - () => - status === TimelineStatus.draft && showWarning - ? timelineType === TimelineType.template - ? i18n.SAVE_TIMELINE_TEMPLATE - : i18n.SAVE_TIMELINE - : i18n.SAVE, - [showWarning, status, timelineType] - ); - - const calloutMessage = useMemo( - () => i18n.UNSAVED_TIMELINE_WARNING(timelineType), - [timelineType] - ); - - const descriptionLabel = useMemo(() => `${i18n.TIMELINE_DESCRIPTION} (${i18n.OPTIONAL})`, []); - - const titleFieldProps = useMemo( - () => ({ - 'aria-label': i18n.TIMELINE_TITLE, - autoFocus: initialFocus === 'title', - 'data-test-subj': 'save-timeline-title', - disabled: isSaving, - spellCheck: true, - placeholder: - timelineType === TimelineType.template - ? commonI18n.UNTITLED_TEMPLATE - : commonI18n.UNTITLED_TIMELINE, - }), - [initialFocus, isSaving, timelineType] - ); - - const descriptionFieldProps = useMemo( - () => ({ - 'aria-label': i18n.TIMELINE_DESCRIPTION, - autoFocus: initialFocus === 'description', - 'data-test-subj': 'save-timeline-description', - disabled: isSaving, - placeholder: commonI18n.DESCRIPTION, - }), - [initialFocus, isSaving] - ); - - useEffect(() => { - if (isSubmitted && !isSaving && prevIsSaving) { - closeSaveTimeline(); - } - }, [isSubmitted, isSaving, prevIsSaving, closeSaveTimeline]); - - return ( - - {isSaving && ( - - )} - {modalHeader} - - - {showWarning && ( - - - - - )} -
    - - - - - - - - - - - - - {closeModalText} - - - - - {saveButtonTitle} - - - - -
    -
    -
    - ); - } -); - -TimelineTitleAndDescription.displayName = 'TimelineTitleAndDescription'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx index 3d5b1a95d66da..2707bf7b04ebc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx @@ -100,33 +100,19 @@ interface BasicTimelineTab { } const AssistantTab: React.FC<{ - isAssistantEnabled: boolean; - renderCellValue: (props: CellValueElementProps) => React.ReactNode; - rowRenderers: RowRenderer[]; - timelineId: TimelineId; shouldRefocusPrompt: boolean; setConversationId: Dispatch>; -}> = memo( - ({ - isAssistantEnabled, - renderCellValue, - rowRenderers, - timelineId, - shouldRefocusPrompt, - setConversationId, - }) => ( - }> - - - - - ) -); +}> = memo(({ shouldRefocusPrompt, setConversationId }) => ( + }> + + + + +)); AssistantTab.displayName = 'AssistantTab'; @@ -147,7 +133,7 @@ const ActiveTimelineTab = memo( showTimeline, }) => { const isDiscoverInTimelineEnabled = useIsExperimentalFeatureEnabled('discoverInTimeline'); - const { hasAssistantPrivilege, isAssistantEnabled } = useAssistantAvailability(); + const { hasAssistantPrivilege } = useAssistantAvailability(); const getTab = useCallback( (tab: TimelineTabs) => { switch (tab) { @@ -235,10 +221,6 @@ const ActiveTimelineTab = memo( {(activeTimelineTab === TimelineTabs.securityAssistant || hasTimelineConversationStarted) && ( (null); + useState(null); const { addError } = useAppToasts(); const [timelineDetailsResponse, setTimelineDetailsResponse] = @@ -70,7 +69,7 @@ export const useTimelineEventsDetails = ({ const [rawEventData, setRawEventData] = useState(undefined); const timelineDetailsSearch = useCallback( - (request: TimelineEventsDetailsRequestOptions | null) => { + (request: TimelineEventsDetailsRequestOptionsInput | null) => { if (request == null || skip || isEmpty(request.eventId)) { return; } @@ -80,7 +79,7 @@ export const useTimelineEventsDetails = ({ setLoading(true); searchSubscription$.current = data.search - .search( + .search( request, { strategy: 'timelineSearchStrategy', @@ -125,7 +124,7 @@ export const useTimelineEventsDetails = ({ eventId, factoryQueryType: TimelineEventsQueries.details, runtimeMappings, - }; + } as const; if (!deepEqual(prevRequest, myRequest)) { return myRequest; } diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index b65c7c7c51498..8d6e871f8354b 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -13,6 +13,10 @@ import { Subscription } from 'rxjs'; import type { DataView } from '@kbn/data-plugin/common'; import { isCompleteResponse } from '@kbn/data-plugin/common'; +import type { + TimelineEqlRequestOptionsInput, + TimelineEventsAllOptionsInput, +} from '@kbn/timelines-plugin/common/api/search_strategy'; import type { ESQuery } from '../../../common/typed_json'; import type { inputsModel } from '../../common/store'; @@ -25,7 +29,6 @@ import { getInspectResponse } from '../../helpers'; import type { PaginationInputPaginated, TimelineEventsAllStrategyResponse, - TimelineEventsAllRequestOptions, TimelineEdges, TimelineItem, TimelineRequestSortField, @@ -38,7 +41,6 @@ import { useRouteSpy } from '../../common/utils/route/use_route_spy'; import { activeTimeline } from './active_timeline_context'; import type { EqlOptionsSelected, - TimelineEqlRequestOptions, TimelineEqlResponse, } from '../../../common/search_strategy/timeline/events/eql'; import { useTrackHttpRequest } from '../../common/lib/apm/use_track_http_request'; @@ -62,12 +64,12 @@ type TimelineEventsSearchHandler = (onNextResponse?: OnNextResponseHandler) => v type LoadPage = (newActivePage: number) => void; type TimelineRequest = T extends 'kuery' - ? TimelineEventsAllRequestOptions + ? TimelineEventsAllOptionsInput : T extends 'lucene' - ? TimelineEventsAllRequestOptions + ? TimelineEventsAllOptionsInput : T extends 'eql' - ? TimelineEqlRequestOptions - : TimelineEventsAllRequestOptions; + ? TimelineEqlRequestOptionsInput + : TimelineEventsAllOptionsInput; type TimelineResponse = T extends 'kuery' ? TimelineEventsAllStrategyResponse @@ -259,10 +261,9 @@ export const useTimelineEventsHandler = ({ activeTimeline.setExpandedDetail({}); activeTimeline.setPageName(pageName); if (request.language === 'eql') { - activeTimeline.setEqlRequest(request as TimelineEqlRequestOptions); + activeTimeline.setEqlRequest(request as TimelineEqlRequestOptionsInput); activeTimeline.setEqlResponse(newTimelineResponse); } else { - // @ts-expect-error EqlSearchRequest.query is not compatible with QueryDslQueryContainer activeTimeline.setRequest(request); activeTimeline.setResponse(newTimelineResponse); } @@ -335,14 +336,14 @@ export const useTimelineEventsHandler = ({ } setTimelineRequest((prevRequest) => { - const prevEqlRequest = prevRequest as TimelineEqlRequestOptions; + const prevEqlRequest = prevRequest as TimelineEqlRequestOptionsInput; const prevSearchParameters = { defaultIndex: prevRequest?.defaultIndex ?? [], filterQuery: prevRequest?.filterQuery ?? '', - querySize: prevRequest?.pagination.querySize ?? 0, + querySize: prevRequest?.pagination?.querySize ?? 0, sort: prevRequest?.sort ?? initSortDefault, timerange: prevRequest?.timerange ?? {}, - runtimeMappings: (prevRequest?.runtimeMappings ?? {}) as RunTimeMappings, + runtimeMappings: (prevRequest?.runtimeMappings ?? {}) as unknown as RunTimeMappings, ...deStructureEqlOptions(prevEqlRequest), }; @@ -379,7 +380,7 @@ export const useTimelineEventsHandler = ({ sort, ...timerange, ...(eqlOptions ? eqlOptions : {}), - }; + } as const; if (activePage !== newActivePage) { setActivePage(newActivePage); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx index f2a1d81ec0294..a260a3d9f9cdd 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx @@ -11,14 +11,14 @@ import deepEqual from 'fast-deep-equal'; import { Subscription } from 'rxjs'; import { isCompleteResponse } from '@kbn/data-plugin/public'; +import { TimelineEventsQueries } from '@kbn/timelines-plugin/common'; import type { inputsModel } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import type { - TimelineKpiStrategyRequest, + TimelineKpiRequestOptionsInput, TimelineKpiStrategyResponse, TimerangeInput, } from '../../../../common/search_strategy'; -import { TimelineEventsQueries } from '../../../../common/search_strategy'; import type { ESQuery } from '../../../../common/typed_json'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import * as i18n from './translations'; @@ -41,15 +41,14 @@ export const useTimelineKpis = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [timelineKpiRequest, setTimelineKpiRequest] = useState( - null - ); + const [timelineKpiRequest, setTimelineKpiRequest] = + useState(null); const [timelineKpiResponse, setTimelineKpiResponse] = useState(null); const { addError } = useAppToasts(); const timelineKpiSearch = useCallback( - (request: TimelineKpiStrategyRequest | null) => { + (request: TimelineKpiRequestOptionsInput | null) => { if (request == null) { return; } @@ -58,7 +57,7 @@ export const useTimelineKpis = ({ setLoading(true); searchSubscription$.current = data.search - .search(request, { + .search(request, { strategy: 'timelineSearchStrategy', abortSignal: abortCtrl.current.signal, }) @@ -93,7 +92,7 @@ export const useTimelineKpis = ({ timerange, filterQuery, factoryQueryType: TimelineEventsQueries.kpi, - }; + } as const; if (!deepEqual(prevRequest, myRequest)) { return myRequest; } diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index b1fc18359f4f6..6b16958f65808 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -48,7 +48,7 @@ import type { ThreatIntelligencePluginStart } from '@kbn/threat-intelligence-plu import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; -import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import type { RouteProps } from 'react-router-dom'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; @@ -133,7 +133,7 @@ export interface StartPlugins { } export interface StartPluginsDependencies extends StartPlugins { - savedObjectsManagement: SavedObjectsManagementPluginStart; + contentManagement: ContentManagementPublicStart; savedObjectsTaggingOss: SavedObjectTaggingOssPluginStart; } @@ -142,6 +142,7 @@ export interface ContractStartServices { isILMAvailable$: Observable; isSidebarEnabled$: Observable; getStartedComponent$: Observable; + dashboardsLandingCalloutComponent$: Observable; upselling: UpsellingService; } @@ -161,7 +162,7 @@ export type StartServices = CoreStart & securityLayout: { getPluginWrapper: () => typeof SecuritySolutionTemplateWrapper; }; - savedObjectsManagement: SavedObjectsManagementPluginStart; + contentManagement: ContentManagementPublicStart; telemetry: TelemetryClientStart; customDataService: DataPublicPluginStart; topValuesPopover: TopValuesPopoverService; @@ -178,6 +179,7 @@ export interface PluginStart { setIsILMAvailable: (isILMAvailable: boolean) => void; setIsSidebarEnabled: (isSidebarEnabled: boolean) => void; setGetStartedPage: (getStartedComponent: React.ComponentType) => void; + setDashboardsLandingCallout: (dashboardsLandingCallout: React.ComponentType) => void; getBreadcrumbsNav$: () => Observable; getUpselling: () => UpsellingService; } diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts index 0eca2cbf03501..db3fe4b32f1e4 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts @@ -19,6 +19,7 @@ import { agentPolicyRouteService, agentRouteService, AGENTS_INDEX, + API_VERSIONS, } from '@kbn/fleet-plugin/common'; import { ToolingLog } from '@kbn/tooling-log'; import type { KbnClient } from '@kbn/test'; @@ -36,6 +37,11 @@ import type { import nodeFetch from 'node-fetch'; import semver from 'semver'; import axios from 'axios'; +import { + RETRYABLE_TRANSIENT_ERRORS, + retryOnError, +} from '../../../common/endpoint/data_loaders/utils'; +import { fetchKibanaStatus } from './stack_services'; import { catchAxiosErrorFormatAndThrow } from './format_axios_error'; import { FleetAgentGenerator } from '../../../common/endpoint/data_generators/fleet_agent_generator'; @@ -106,6 +112,9 @@ export const fetchFleetAgents = async ( .request({ method: 'GET', path: AGENT_API_ROUTES.LIST_PATTERN, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, query: options, }) .catch(catchAxiosErrorFormatAndThrow) @@ -132,11 +141,15 @@ export const waitForHostToEnroll = async ( let found: Agent | undefined; while (!found && !hasTimedOut()) { - found = await fetchFleetAgents(kbnClient, { - perPage: 1, - kuery: `(local_metadata.host.hostname.keyword : "${hostname}") and (status:online)`, - showInactive: false, - }).then((response) => response.items[0]); + found = await retryOnError( + async () => + fetchFleetAgents(kbnClient, { + perPage: 1, + kuery: `(local_metadata.host.hostname.keyword : "${hostname}") and (status:online)`, + showInactive: false, + }).then((response) => response.items[0]), + RETRYABLE_TRANSIENT_ERRORS + ); if (!found) { // sleep and check again @@ -160,6 +173,9 @@ export const fetchFleetServerUrl = async (kbnClient: KbnClient): Promise({ method: 'GET', path: fleetServerHostsRoutesService.getListPath(), + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, query: { perPage: 100, }, @@ -197,6 +213,9 @@ export const fetchAgentPolicyEnrollmentKey = async ( .request({ method: 'GET', path: enrollmentAPIKeyRouteService.getListPath(), + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, query: { kuery: `policy_id: "${agentPolicyId}"` }, }) .catch(catchAxiosErrorFormatAndThrow) @@ -222,6 +241,9 @@ export const fetchAgentPolicyList = async ( .request({ method: 'GET', path: agentPolicyRouteService.getListPath(), + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, query: options, }) .catch(catchAxiosErrorFormatAndThrow) @@ -236,7 +258,7 @@ export const fetchAgentPolicyList = async ( export const getAgentVersionMatchingCurrentStack = async ( kbnClient: KbnClient ): Promise => { - const kbnStatus = await kbnClient.status.get(); + const kbnStatus = await fetchKibanaStatus(kbnClient); const agentVersions = await axios .get('https://artifacts-api.elastic.co/v1/versions') .then((response) => map(response.data.versions, (version) => version.split('-SNAPSHOT')[0])); @@ -323,6 +345,7 @@ export const getAgentDownloadUrl = async ( * Given a stack version number, function will return the closest Agent download version available * for download. THis could be the actual version passed in or lower. * @param version + * @param log */ export const getLatestAgentDownloadVersion = async ( version: string, @@ -386,6 +409,9 @@ export const unEnrollFleetAgent = async ( method: 'POST', path: agentRouteService.getUnenrollPath(agentId), body: { revoke: force }, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, }) .catch(catchAxiosErrorFormatAndThrow); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/format_axios_error.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/format_axios_error.ts index f1b69c8665fc6..1f0c7da3bbad6 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/format_axios_error.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/format_axios_error.ts @@ -22,7 +22,11 @@ export class FormattedAxiosError extends Error { }; constructor(axiosError: AxiosError) { - super(axiosError.message); + super( + `${axiosError.message}${ + axiosError?.response?.data ? `: ${JSON.stringify(axiosError?.response?.data)}` : '' + }` + ); this.request = { method: axiosError.config?.method ?? '?', diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts index a3ad237fc3bcb..7e5d9a95efe76 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts @@ -7,15 +7,23 @@ import { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; +import type { KbnClientOptions } from '@kbn/test'; import { KbnClient } from '@kbn/test'; import type { StatusResponse } from '@kbn/core-status-common-internal'; import pRetry from 'p-retry'; import nodeFetch from 'node-fetch'; +import type { ReqOptions } from '@kbn/test/src/kbn_client/kbn_client_requester'; +import { type AxiosResponse } from 'axios'; +import type { ClientOptions } from '@elastic/elasticsearch/lib/client'; +import fs from 'fs'; +import { CA_CERT_PATH } from '@kbn/dev-utils'; import { catchAxiosErrorFormatAndThrow } from './format_axios_error'; import { isLocalhost } from './is_localhost'; import { getLocalhostRealIp } from './localhost_services'; import { createSecuritySuperuser } from './security_user_services'; +const CA_CERTIFICATE: Buffer = fs.readFileSync(CA_CERT_PATH); + export interface RuntimeServices { kbnClient: KbnClient; esClient: Client; @@ -24,6 +32,7 @@ export interface RuntimeServices { username: string; password: string; }>; + apiKey: string; localhostRealIp: string; kibana: { url: string; @@ -51,12 +60,44 @@ interface CreateRuntimeServicesOptions { fleetServerUrl?: string; username: string; password: string; + /** If defined, both `username` and `password` will be ignored */ + apiKey?: string; /** If undefined, ES username defaults to `username` */ esUsername?: string; /** If undefined, ES password defaults to `password` */ esPassword?: string; log?: ToolingLog; asSuperuser?: boolean; + /** If true, then a certificate will not be used when creating the Kbn/Es clients when url is `https` */ + noCertForSsl?: boolean; +} + +class KbnClientExtended extends KbnClient { + private readonly apiKey: string | undefined; + + constructor({ apiKey, url, ...options }: KbnClientOptions & { apiKey?: string }) { + super({ + ...options, + url: apiKey ? buildUrlWithCredentials(url, '', '') : url, + }); + + this.apiKey = apiKey; + } + + async request(options: ReqOptions): Promise> { + const headers: ReqOptions['headers'] = { + ...(options.headers ?? {}), + }; + + if (this.apiKey) { + headers.Authorization = `ApiKey ${this.apiKey}`; + } + + return super.request({ + ...options, + headers, + }); + } } export const createRuntimeServices = async ({ @@ -65,30 +106,44 @@ export const createRuntimeServices = async ({ fleetServerUrl = 'https://localhost:8220', username: _username, password: _password, + apiKey, esUsername, esPassword, log = new ToolingLog({ level: 'info', writeTo: process.stdout }), asSuperuser = false, + noCertForSsl, }: CreateRuntimeServicesOptions): Promise => { let username = _username; let password = _password; if (asSuperuser) { await waitForKibana(kibanaUrl); + const tmpEsClient = createEsClient({ + url: elasticsearchUrl, + username, + password, + log, + noCertForSsl, + }); - const superuserResponse = await createSecuritySuperuser( - createEsClient({ - url: elasticsearchUrl, - username, - password, - log, - }) - ); + const isServerlessEs = (await tmpEsClient.info()).version.build_flavor === 'serverless'; + + if (isServerlessEs) { + log?.warning( + 'Creating Security Superuser is not supported in current environment. ES is running in serverless mode. ' + + 'Will use username [system_indices_superuser] instead.' + ); - ({ username, password } = superuserResponse); + username = 'system_indices_superuser'; + password = 'changeme'; + } else { + const superuserResponse = await createSecuritySuperuser(tmpEsClient); - if (superuserResponse.created) { - log.info(`Kibana user [${username}] was crated with password [${password}]`); + ({ username, password } = superuserResponse); + + if (superuserResponse.created) { + log.info(`Kibana user [${username}] was crated with password [${password}]`); + } } } @@ -97,15 +152,18 @@ export const createRuntimeServices = async ({ const fleetURL = new URL(fleetServerUrl); return { - kbnClient: createKbnClient({ log, url: kibanaUrl, username, password }), + kbnClient: createKbnClient({ log, url: kibanaUrl, username, password, apiKey, noCertForSsl }), esClient: createEsClient({ log, url: elasticsearchUrl, username: esUsername ?? username, password: esPassword ?? password, + apiKey, + noCertForSsl, }), log, - localhostRealIp: await getLocalhostRealIp(), + localhostRealIp: getLocalhostRealIp(), + apiKey: apiKey ?? '', user: { username, password, @@ -148,40 +206,76 @@ export const createEsClient = ({ url, username, password, + apiKey, log, + noCertForSsl, }: { url: string; username: string; password: string; + /** If defined, both `username` and `password` will be ignored */ + apiKey?: string; log?: ToolingLog; + noCertForSsl?: boolean; }): Client => { - const esUrl = buildUrlWithCredentials(url, username, password); + const isHttps = new URL(url).protocol.startsWith('https'); + const clientOptions: ClientOptions = { + node: buildUrlWithCredentials(url, apiKey ? '' : username, apiKey ? '' : password), + }; + + if (isHttps && !noCertForSsl) { + clientOptions.tls = { + ca: [CA_CERTIFICATE], + }; + } + + if (apiKey) { + clientOptions.auth = { apiKey }; + } if (log) { - log.verbose(`Creating Elasticsearch client with URL: ${esUrl}`); + log.verbose(`Creating Elasticsearch client options: ${JSON.stringify(clientOptions)}`); } - return new Client({ node: esUrl }); + return new Client(clientOptions); }; export const createKbnClient = ({ url, username, password, + apiKey, log = new ToolingLog(), + noCertForSsl, }: { url: string; username: string; password: string; + /** If defined, both `username` and `password` will be ignored */ + apiKey?: string; log?: ToolingLog; + noCertForSsl?: boolean; }): KbnClient => { - const kbnUrl = buildUrlWithCredentials(url, username, password); + const isHttps = new URL(url).protocol.startsWith('https'); + const clientOptions: ConstructorParameters[0] = { + log, + apiKey, + url: buildUrlWithCredentials(url, username, password), + }; + + if (isHttps && !noCertForSsl) { + clientOptions.certificateAuthorities = [CA_CERTIFICATE]; + } if (log) { - log.verbose(`Creating Kibana client with URL: ${kbnUrl}`); + log.verbose( + `Creating Kibana client with URL: ${clientOptions.url} ${ + apiKey ? ` + ApiKey: ${apiKey}` : '' + }` + ); } - return new KbnClient({ log, url: kbnUrl }); + return new KbnClientExtended(clientOptions); }; /** @@ -189,14 +283,7 @@ export const createKbnClient = ({ * @param kbnClient */ export const fetchStackVersion = async (kbnClient: KbnClient): Promise => { - const status = ( - await kbnClient - .request({ - method: 'GET', - path: '/api/status', - }) - .catch(catchAxiosErrorFormatAndThrow) - ).data; + const status = await fetchKibanaStatus(kbnClient); if (!status?.version?.number) { throw new Error( @@ -207,6 +294,16 @@ export const fetchStackVersion = async (kbnClient: KbnClient): Promise = return status.version.number; }; +export const fetchKibanaStatus = async (kbnClient: KbnClient): Promise => { + return kbnClient + .request({ + method: 'GET', + path: '/api/status', + }) + .catch(catchAxiosErrorFormatAndThrow) + .then((response) => response.data); +}; + /** * Checks to ensure Kibana is up and running * @param kbnUrl @@ -232,3 +329,18 @@ export const waitForKibana = async (kbnUrl: string): Promise => { { maxTimeout: 10000 } ); }; + +export const isServerlessKibanaFlavor = async (kbnClient: KbnClient): Promise => { + const kbnStatus = await fetchKibanaStatus(kbnClient); + + // If we don't have status for plugins, then error + // the Status API will always return something (its an open API), but if auth was successful, + // it will also return more data. + if (!kbnStatus.status.plugins) { + throw new Error( + `Unable to retrieve Kibana plugins status (likely an auth issue with the username being used for kibana)` + ); + } + + return kbnStatus.status.plugins?.serverless?.level === 'available'; +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/fleet_server.ts b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/fleet_server.ts index f9d88382d81c7..60471c5c46864 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/fleet_server.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/fleet_server.ts @@ -17,8 +17,9 @@ import { FLEET_SERVER_PACKAGE, PACKAGE_POLICY_API_ROUTES, PACKAGE_POLICY_SAVED_OBJECT_TYPE, + API_VERSIONS, + APP_API_ROUTES, } from '@kbn/fleet-plugin/common'; -import { APP_API_ROUTES } from '@kbn/fleet-plugin/common/constants'; import type { FleetServerHost, GenerateServiceTokenResponse, @@ -87,6 +88,9 @@ const getFleetServerPackagePolicy = async (): Promise .request({ method: 'GET', path: PACKAGE_POLICY_API_ROUTES.LIST_PATTERN, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, query: { perPage: 1, kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "${FLEET_SERVER_PACKAGE}"`, @@ -121,6 +125,9 @@ const getOrCreateFleetServerAgentPolicyId = async (): Promise => { .request({ method: 'POST', path: AGENT_POLICY_API_ROUTES.CREATE_PATTERN, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, body: { name: `Fleet Server policy (${Math.random().toString(32).substring(2)})`, description: `Created by CLI Tool via: ${__filename}`, @@ -150,6 +157,9 @@ const generateFleetServiceToken = async (): Promise => { .request({ method: 'POST', path: APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, body: {}, }) .then((response) => response.data.value); @@ -279,6 +289,9 @@ const configureFleetIfNeeded = async () => { const fleetOutputs = await kbnClient .request({ method: 'GET', + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, path: outputRoutesService.getListPath(), }) .then((response) => response.data); @@ -318,6 +331,9 @@ const configureFleetIfNeeded = async () => { await kbnClient .request({ method: 'PUT', + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, path: outputRoutesService.getUpdatePath(id), body: update, }) @@ -356,6 +372,9 @@ const addFleetServerHostToFleetSettings = async ( .request({ method: 'POST', path: fleetServerHostsRoutesService.getCreatePath(), + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, body: newFleetHostEntry, }) .catch(catchAxiosErrorFormatAndThrow) diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts index c0be16370ddcc..c1c38dcf8b30a 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts @@ -19,7 +19,7 @@ import { METADATA_DATASTREAM } from '../../common/endpoint/constants'; import { EndpointMetadataGenerator } from '../../common/endpoint/data_generators/endpoint_metadata_generator'; import { indexHostsAndAlerts } from '../../common/endpoint/index_data'; import { ANCESTRY_LIMIT, EndpointDocGenerator } from '../../common/endpoint/generate_data'; -import { fetchStackVersion } from './common/stack_services'; +import { fetchStackVersion, isServerlessKibanaFlavor } from './common/stack_services'; import { ENDPOINT_ALERTS_INDEX, ENDPOINT_EVENTS_INDEX } from './common/constants'; import { getWithResponseActionsRole } from './common/roles_users/with_response_actions_role'; import { getNoResponseActionsRole } from './common/roles_users/without_response_actions_role'; @@ -161,6 +161,8 @@ function updateURL({ } async function main() { + const startTime = new Date().getTime(); + const argv = yargs.help().options({ seed: { alias: 's', @@ -318,17 +320,16 @@ async function main() { default: false, }, }).argv; - let ca: Buffer; + let ca: Buffer; let clientOptions: ClientOptions; let url: string; let node: string; - const toolingLogOptions = { - log: new ToolingLog({ - level: 'info', - writeTo: process.stdout, - }), - }; + const logger = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + const toolingLogOptions = { log: logger }; let kbnClientOptions: KbnClientOptions = { ...toolingLogOptions, @@ -350,38 +351,62 @@ async function main() { clientOptions = { node: argv.node }; } let client = new Client(clientOptions); + let kbnClient = new KbnClient({ ...kbnClientOptions }); let user: UserInfo | undefined; - // if fleet flag is used - if (argv.fleet) { - // add endpoint user if --withNewUser flag has values as username:password - const newUserCreds = - argv.withNewUser.indexOf(':') !== -1 ? argv.withNewUser.split(':') : undefined; - user = await addUser( - client, - newUserCreds - ? { - username: newUserCreds[0], - password: newUserCreds[1], - } - : undefined + const isServerless = await isServerlessKibanaFlavor(kbnClient); + + logger.info(`Build flavor: ${isServerless ? 'serverless' : 'non-serverless'}`); + + if (argv.fleet && !argv.withNewUser && !isServerless) { + // warn and exit when using fleet flag + logger.error( + 'Please use the --withNewUser=username:password flag to add a custom user with required roles when --fleet is enabled!' ); + // eslint-disable-next-line no-process-exit + process.exit(0); + } - // update client and kibana options before instantiating - if (user) { - // use endpoint user for Es and Kibana URLs + // if fleet flag is used + if (argv.fleet) { + if (!isServerless) { + // add endpoint user if --withNewUser flag has values as username:password + const newUserCreds = + argv.withNewUser.indexOf(':') !== -1 ? argv.withNewUser.split(':') : undefined; + user = await addUser( + client, + newUserCreds + ? { + username: newUserCreds[0], + password: newUserCreds[1], + } + : undefined + ); + + // update client and kibana options before instantiating + if (user) { + // use endpoint user for Es and Kibana URLs + + url = updateURL({ url: argv.kibana, user }); + node = updateURL({ url: argv.node, user }); + + kbnClientOptions = { + ...kbnClientOptions, + url, + }; - url = updateURL({ url: argv.kibana, user }); - node = updateURL({ url: argv.node, user }); + client = new Client({ ...clientOptions, node }); + kbnClient = new KbnClient({ ...kbnClientOptions }); - kbnClientOptions = { - ...kbnClientOptions, - url, - }; - client = new Client({ ...clientOptions, node }); + logger.verbose(`ES/KBN clients updated to login using: ${JSON.stringify(user)}`); + } + } else { + logger.warning( + 'Option `--withNewUser` not supported in serverless.\n' + + 'Ensure that `--kibana` and `--node` options are defined with username/password of ' + + '`system_indices_superuser:changeme`' + ); } } - // instantiate kibana client - const kbnClient = new KbnClient({ ...kbnClientOptions }); if (argv.delete) { await deleteIndices( @@ -391,6 +416,12 @@ async function main() { } if (argv.rbacUser) { + if (isServerless) { + // FIXME:PT create users in serverless when that capability is available + + throw new Error(`Can not use '--rbacUser' option against serverless deployment`); + } + // Add roles and users with response actions kibana privileges for (const role of Object.keys(rolesMapping)) { const addedRole = await addRole(kbnClient, { @@ -398,32 +429,15 @@ async function main() { ...rolesMapping[role], }); if (addedRole) { - console.log(`Successfully added ${role} role`); + logger.info(`Successfully added ${role} role`); await addUser(client, { username: role, password: 'changeme', roles: [role] }); } else { - console.log(`Failed to add role, ${role}`); + logger.warning(`Failed to add role, ${role}`); } } } - let seed = argv.seed; - - if (!seed) { - seed = Math.random().toString(); - console.log(`No seed supplied, using random seed: ${seed}`); - } - - const startTime = new Date().getTime(); - - if (argv.fleet && !argv.withNewUser) { - // warn and exit when using fleet flag - console.log( - 'Please use the --withNewUser=username:password flag to add a custom user with required roles when --fleet is enabled!' - ); - // eslint-disable-next-line no-process-exit - process.exit(0); - } - + const seed = argv.seed || Math.random().toString(); let DocGenerator: typeof EndpointDocGenerator = EndpointDocGenerator; // If `--randomVersions` is NOT set, then use custom generator that ensures all data generated @@ -446,6 +460,7 @@ async function main() { }; } + logger.info('Indexing host and alerts...'); await indexHostsAndAlerts( client, kbnClient, @@ -475,11 +490,12 @@ async function main() { ); // delete endpoint_user after - if (user) { + if (user && !isServerless) { const deleted = await deleteUser(client, user.username); if (deleted.found) { - console.log(`User ${user.username} deleted successfully!`); + logger.info(`User ${user.username} deleted successfully!`); } } - console.log(`Creating and indexing documents took: ${new Date().getTime() - startTime}ms`); + + logger.info(`Creating and indexing documents took: ${new Date().getTime() - startTime}ms`); } diff --git a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js index 9960aad7ca5f0..4fe41979154ff 100644 --- a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js +++ b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js @@ -19,7 +19,7 @@ const OUTPUT_DIRECTORY = resolve('public', 'detections', 'mitre'); // Every release we should update the version of MITRE ATT&CK content and regenerate the model in our code. // This version must correspond to the one used for prebuilt rules in https://github.com/elastic/detection-rules. // This version is basically a tag on https://github.com/mitre/cti/tags, or can be a branch name like `master`. -const MITRE_CONTENT_VERSION = 'ATT&CK-v12.1'; // last updated when preparing for 8.7.0 release +const MITRE_CONTENT_VERSION = 'ATT&CK-v13.1'; // last updated when preparing for 8.10.3 release const MITRE_CONTENT_URL = `https://raw.githubusercontent.com/mitre/cti/${MITRE_CONTENT_VERSION}/enterprise-attack/enterprise-attack.json`; const getTacticsOptions = (tactics) => @@ -28,7 +28,7 @@ const getTacticsOptions = (tactics) => id: '${t.id}', name: '${t.name}', reference: '${t.reference}', - text: i18n.translate( + label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.${camelCase(t.name)}Description', { defaultMessage: '${t.name} (${t.id})' }), @@ -48,7 +48,7 @@ const getTechniquesOptions = (techniques) => id: '${t.id}', name: '${t.name}', reference: '${t.reference}', - tactics: '${t.tactics.join()}', + tactics: [${t.tactics.map((tactic) => `'${tactic.trim()}'`)}], value: '${camelCase(t.name)}' }`.replace(/(\r\n|\n|\r)/gm, ' ') ); @@ -65,7 +65,7 @@ const getSubtechniquesOptions = (subtechniques) => id: '${t.id}', name: '${t.name}', reference: '${t.reference}', - tactics: '${t.tactics.join()}', + tactics: [${t.tactics.map((tactic) => `'${tactic.trim()}'`)}], techniqueId: '${t.techniqueId}', value: '${camelCase(t.name)}' }`.replace(/(\r\n|\n|\r)/gm, ' ') @@ -203,25 +203,19 @@ async function main() { import { i18n } from '@kbn/i18n'; - import { MitreTacticsOptions, MitreTechniquesOptions, MitreSubtechniquesOptions } from './types'; - - export const tactics = ${JSON.stringify(tactics, null, 2)}; + import { MitreTactic, MitreTechnique, MitreSubTechnique } from './types'; - export const tacticsOptions: MitreTacticsOptions[] = + export const tactics: MitreTactic[] = ${JSON.stringify(getTacticsOptions(tactics), null, 2) .replace(/}"/g, '}') .replace(/"{/g, '{')}; - export const technique = ${JSON.stringify(techniques, null, 2)}; - - export const techniquesOptions: MitreTechniquesOptions[] = + export const techniques: MitreTechnique[] = ${JSON.stringify(getTechniquesOptions(techniques), null, 2) .replace(/}"/g, '}') .replace(/"{/g, '{')}; - export const subtechniques = ${JSON.stringify(subtechniques, null, 2)}; - - export const subtechniquesOptions: MitreSubtechniquesOptions[] = + export const subtechniques: MitreSubTechnique[] = ${JSON.stringify(getSubtechniquesOptions(subtechniques), null, 2) .replace(/}"/g, '}') .replace(/"{/g, '{')}; diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts index 45b48a5d428e3..d411cdf8e8abd 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts @@ -63,8 +63,14 @@ const retrieveIntegrations = (integrationsPaths: string[]) => { export const cli = () => { run( async () => { + const log = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + const { argv } = yargs(process.argv.slice(2)) - .coerce('spec', (arg) => (_.isArray(arg) ? [_.last(arg)] : [arg])) + .coerce('configFile', (arg) => (_.isArray(arg) ? _.last(arg) : arg)) + .coerce('spec', (arg) => (_.isArray(arg) ? _.last(arg) : arg)) .coerce('env', (arg: string) => arg.split(',').reduce((acc, curr) => { const [key, value] = curr.split('='); @@ -77,22 +83,73 @@ export const cli = () => { }, {} as Record) ); - const isOpen = argv._[0] === 'open'; - const cypressConfigFilePath = require.resolve( - `../../${_.isArray(argv.configFile) ? _.last(argv.configFile) : argv.configFile}` - ) as string; + log.info(` +---------------------------------------------- +Script arguments: +---------------------------------------------- + +${JSON.stringify(argv, null, 2)} + +---------------------------------------------- +`); + + const isOpen = argv._.includes('open'); + + const cypressConfigFilePath = require.resolve(`../../${argv.configFile}`) as string; const cypressConfigFile = await import(cypressConfigFilePath); + + log.info(` +---------------------------------------------- +Cypress config for file: ${cypressConfigFilePath}: +---------------------------------------------- + +${JSON.stringify(cypressConfigFile, null, 2)} + +---------------------------------------------- +`); + + const specConfig = cypressConfigFile.e2e.specPattern; + const specArg = argv.spec; + const specPattern = specArg ?? specConfig; + + log.info('Config spec pattern:', specConfig); + log.info('Arguments spec pattern:', specArg); + log.info('Resulting spec pattern:', specPattern); + + // The grep function will filter Cypress specs by tags: it will include and exclude + // spec files according to the tags configuration. const grepSpecPattern = grep({ ...cypressConfigFile, - specPattern: argv.spec ?? cypressConfigFile.e2e.specPattern, + specPattern, excludeSpecPattern: [], }).specPattern; - let files = retrieveIntegrations( - _.isArray(grepSpecPattern) - ? grepSpecPattern - : globby.sync(argv.spec ?? cypressConfigFile.e2e.specPattern) - ); + log.info('Resolved spec files or pattern after grep:', grepSpecPattern); + + const isGrepReturnedFilePaths = _.isArray(grepSpecPattern); + const isGrepReturnedSpecPattern = !isGrepReturnedFilePaths && grepSpecPattern === specPattern; + const grepFilterSpecs = cypressConfigFile.env?.grepFilterSpecs; + + // IMPORTANT! + // When grep returns the same spec pattern as it gets in its arguments, we treat it as + // it couldn't find any concrete specs to execute (maybe because all of them are skipped). + // In this case, we do an early return - it's important to do that. + // If we don't return early, these specs will start executing, and Cypress will be skipping + // tests at runtime: those that should be excluded according to the tags passed in the config. + // This can take so much time that the job can fail by timeout in CI. + if (grepFilterSpecs && isGrepReturnedSpecPattern) { + log.info('No tests found - all tests could have been skipped via Cypress tags'); + // eslint-disable-next-line no-process-exit + return process.exit(0); + } + + const concreteFilePaths = isGrepReturnedFilePaths + ? grepSpecPattern // use the returned concrete file paths + : globby.sync(specPattern); // convert the glob pattern to concrete file paths + + let files = retrieveIntegrations(concreteFilePaths); + + log.info('Resolved spec files after retrieveIntegrations:', files); if (argv.changedSpecsOnly) { files = (findChangedFiles('main', false) as string[]).reduce((acc, itemPath) => { @@ -108,11 +165,6 @@ export const cli = () => { files = files.slice(0, 3); } - const log = new ToolingLog({ - level: 'info', - writeTo: process.stdout, - }); - if (!files?.length) { log.info('No tests found'); // eslint-disable-next-line no-process-exit diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index c039895eaaef5..917d366354270 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -5,7 +5,12 @@ * 2.0. */ -import type { KibanaRequest, Logger, ElasticsearchClient } from '@kbn/core/server'; +import type { + KibanaRequest, + Logger, + ElasticsearchClient, + SavedObjectsClientContract, +} from '@kbn/core/server'; import type { ExceptionListClient, ListsServerExtensionRegistrar } from '@kbn/lists-plugin/server'; import type { CasesClient, CasesStart } from '@kbn/cases-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; @@ -46,6 +51,7 @@ import type { AppFeaturesService } from '../lib/app_features_service/app_feature export interface EndpointAppContextServiceSetupContract { securitySolutionRequestContextFactory: IRequestContextFactory; + cloud: CloudSetup; } export interface EndpointAppContextServiceStartContract { @@ -68,9 +74,9 @@ export interface EndpointAppContextServiceStartContract { experimentalFeatures: ExperimentalFeatures; messageSigningService: MessageSigningServiceInterface | undefined; actionCreateService: ActionCreateService | undefined; - cloud: CloudSetup; esClient: ElasticsearchClient; appFeaturesService: AppFeaturesService; + savedObjectsClient: SavedObjectsClientContract; } /** @@ -102,13 +108,13 @@ export class EndpointAppContextService { logger, manifestManager, alerting, - cloud, licenseService, exceptionListsClient, featureUsageService, endpointMetadataService, esClient, appFeaturesService, + savedObjectsClient, } = dependencies; registerIngestCallback( @@ -120,7 +126,7 @@ export class EndpointAppContextService { alerting, licenseService, exceptionListsClient, - cloud, + this.setupDependencies.cloud, appFeaturesService ) ); @@ -137,7 +143,7 @@ export class EndpointAppContextService { licenseService, featureUsageService, endpointMetadataService, - cloud, + this.setupDependencies.cloud, esClient, appFeaturesService ) @@ -145,7 +151,7 @@ export class EndpointAppContextService { registerIngestCallback( 'packagePolicyPostDelete', - getPackagePolicyDeleteCallback(exceptionListsClient) + getPackagePolicyDeleteCallback(exceptionListsClient, savedObjectsClient) ); } diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts index 518780bbbf8a6..ca7a73a272192 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts @@ -196,7 +196,7 @@ export class CheckMetadataTransformsTask { if (attempts > MAX_ATTEMPTS) { this.logger.warn( - `transform ${transform.id} has failed to restart ${attempts} times. stopping auto restart attempts.` + `Transform ${transform.id} has failed to restart ${attempts} times. stopping auto restart attempts.` ); return { attempts, diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/protection_updates_note/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/endpoint/lib/protection_updates_note/saved_object_mappings.ts new file mode 100644 index 0000000000000..1d805558e2875 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/lib/protection_updates_note/saved_object_mappings.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; + +export const protectionUpdatesNoteSavedObjectType = 'policy-settings-protection-updates-note'; + +export const protectionUpdatesNoteSavedObjectMappings: SavedObjectsType['mappings'] = { + properties: { + note: { + type: 'text', + index: false, + }, + }, +}; + +export const protectionUpdatesNoteType: SavedObjectsType = { + name: protectionUpdatesNoteSavedObjectType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + hidden: false, + namespaceType: 'single', + mappings: protectionUpdatesNoteSavedObjectMappings, +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 73beb5aea6838..6376bf4554041 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -132,6 +132,7 @@ export const createMockEndpointAppContextServiceSetupContract = (): jest.Mocked => { return { securitySolutionRequestContextFactory: requestContextFactoryMock.create(), + cloud: cloudMock.createSetup(), }; }; @@ -213,7 +214,6 @@ export const createMockEndpointAppContextServiceStartContract = >(), exceptionListsClient: listMock.getExceptionListClient(), cases: casesMock, - cloud: cloudMock.createSetup(), featureUsageService: createFeatureUsageServiceMock(), experimentalFeatures, messageSigningService: createMessageSigningServiceMock(), @@ -221,6 +221,7 @@ export const createMockEndpointAppContextServiceStartContract = createFleetActionsClient: jest.fn((_) => fleetActionsClientMock), esClient: elasticsearchClientMock.createElasticsearchClient(), appFeaturesService, + savedObjectsClient: savedObjectsClientMock.create(), }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.test.ts new file mode 100644 index 0000000000000..029d652953861 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.test.ts @@ -0,0 +1,191 @@ +/* + * Copyright 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 { EndpointAppContextService } from '../../endpoint_app_context_services'; +import type { KibanaResponseFactory, SavedObjectsClientContract } from '@kbn/core/server'; + +import { + createMockEndpointAppContextServiceSetupContract, + createMockEndpointAppContextServiceStartContract, + createRouteHandlerContext, +} from '../../mocks'; +import type { ScopedClusterClientMock } from '@kbn/core/server/mocks'; +import { + elasticsearchServiceMock, + httpServerMock, + savedObjectsClientMock, +} from '@kbn/core/server/mocks'; +import { getProtectionUpdatesNoteHandler, postProtectionUpdatesNoteHandler } from './handlers'; +import { requestContextMock } from '../../../lib/detection_engine/routes/__mocks__'; + +const mockedSOSuccessfulFindResponse = { + total: 1, + saved_objects: [ + { + id: 'id', + type: 'type', + references: [ + { + id: 'id_package_policy', + name: 'package_policy', + type: 'ingest-package-policies', + }, + ], + attributes: { note: 'note' }, + score: 1, + }, + ], + page: 1, + per_page: 10, +}; + +const mockedSOSuccessfulFindResponseEmpty = { + total: 0, + saved_objects: [], + page: 1, + per_page: 10, +}; + +const createMockedSOSuccessfulCreateResponse = (note: string) => ({ + id: 'id', + type: 'type', + references: [], + attributes: { note }, +}); + +const mockedSOSuccessfulUpdateResponse = [ + 'policy-settings-protection-updates-note', + 'id', + { note: 'note2' }, + { + references: [ + { + id: 'id_package_policy', + name: 'package_policy', + type: 'ingest-package-policies', + }, + ], + refresh: 'wait_for', + }, +]; + +describe('test protection updates note handler', () => { + let endpointAppContextService: EndpointAppContextService; + let mockSavedObjectClient: jest.Mocked; + let mockResponse: jest.Mocked; + let mockScopedClient: ScopedClusterClientMock; + + describe('test protection updates note handler', () => { + beforeEach(() => { + mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); + mockSavedObjectClient = savedObjectsClientMock.create(); + mockResponse = httpServerMock.createResponseFactory(); + endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); + endpointAppContextService.start(createMockEndpointAppContextServiceStartContract()); + }); + + afterEach(() => endpointAppContextService.stop()); + + it('should create a new note if one does not exist', async () => { + const protectionUpdatesNoteHandler = postProtectionUpdatesNoteHandler(); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { policyId: 'id' }, + body: { note: 'note' }, + }); + + mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponseEmpty); + + mockSavedObjectClient.create.mockResolvedValueOnce( + createMockedSOSuccessfulCreateResponse('note') + ); + + await protectionUpdatesNoteHandler( + requestContextMock.convertContext( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient) + ), + mockRequest, + mockResponse + ); + + expect(mockResponse.ok).toBeCalled(); + expect(mockSavedObjectClient.create).toBeCalledWith( + 'policy-settings-protection-updates-note', + { note: 'note' }, + { + references: [{ id: undefined, name: 'package_policy', type: 'ingest-package-policies' }], + refresh: 'wait_for', + } + ); + }); + + it('should update an existing note on post if one exists', async () => { + const protectionUpdatesNoteHandler = postProtectionUpdatesNoteHandler(); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { policyId: 'id' }, + body: { note: 'note2' }, + }); + + mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponse); + + mockSavedObjectClient.update.mockResolvedValueOnce( + createMockedSOSuccessfulCreateResponse('note2') + ); + + await protectionUpdatesNoteHandler( + requestContextMock.convertContext( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient) + ), + mockRequest, + mockResponse + ); + + expect(mockResponse.ok).toBeCalled(); + expect(mockSavedObjectClient.update).toBeCalledWith(...mockedSOSuccessfulUpdateResponse); + }); + + it('should return the note if one exists', async () => { + const protectionUpdatesNoteHandler = getProtectionUpdatesNoteHandler(); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { policyId: 'id' }, + }); + + mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponse); + + await protectionUpdatesNoteHandler( + requestContextMock.convertContext( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient) + ), + mockRequest, + mockResponse + ); + + expect(mockResponse.ok).toBeCalled(); + const result = mockResponse.ok.mock.calls[0][0]?.body as { note: string }; + expect(result.note).toEqual('note'); + }); + + it('should return notFound if no note exists', async () => { + const protectionUpdatesNoteHandler = getProtectionUpdatesNoteHandler(); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { policyId: 'id' }, + }); + + mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponseEmpty); + + await protectionUpdatesNoteHandler( + requestContextMock.convertContext( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient) + ), + mockRequest, + mockResponse + ); + + expect(mockResponse.notFound).toBeCalled(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts new file mode 100644 index 0000000000000..e1677451ff577 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + RequestHandler, + SavedObjectReference, + SavedObjectsClientContract, +} from '@kbn/core/server'; +import type { TypeOf } from '@kbn/config-schema'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { protectionUpdatesNoteSavedObjectType } from '../../lib/protection_updates_note/saved_object_mappings'; +import type { + CreateUpdateProtectionUpdatesNoteSchema, + GetProtectionUpdatesNoteSchema, +} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; + +const getProtectionNote = async (SOClient: SavedObjectsClientContract, packagePolicyId: string) => { + return SOClient.find<{ note: string }>({ + type: protectionUpdatesNoteSavedObjectType, + hasReference: { type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, id: packagePolicyId }, + }); +}; + +const updateProtectionNote = async ( + SOClient: SavedObjectsClientContract, + noteId: string, + note: string, + references: SavedObjectReference[] +) => { + return SOClient.update( + protectionUpdatesNoteSavedObjectType, + noteId, + { + note, + }, + { + references, + refresh: 'wait_for', + } + ); +}; + +const createProtectionNote = async ( + SOClient: SavedObjectsClientContract, + note: string, + references: SavedObjectReference[] +) => { + return SOClient.create( + protectionUpdatesNoteSavedObjectType, + { + note, + }, + { + references, + refresh: 'wait_for', + } + ); +}; + +export const postProtectionUpdatesNoteHandler = function (): RequestHandler< + TypeOf, + undefined, + TypeOf +> { + return async (context, request, response) => { + const SOClient = (await context.core).savedObjects.client; + const { package_policy_id: packagePolicyId } = request.params; + const { note } = request.body; + + const soClientResponse = await getProtectionNote(SOClient, packagePolicyId); + + if (soClientResponse.saved_objects[0]) { + const { references } = soClientResponse.saved_objects[0]; + + const updatedNoteSO = await updateProtectionNote( + SOClient, + soClientResponse.saved_objects[0].id, + note, + references + ); + + const { attributes } = updatedNoteSO; + + return response.ok({ body: attributes }); + } + + const references: SavedObjectReference[] = [ + { + id: packagePolicyId, + name: 'package_policy', + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + }, + ]; + + const noteSO = await createProtectionNote(SOClient, note, references); + + const { attributes } = noteSO; + + return response.ok({ body: attributes }); + }; +}; + +export const getProtectionUpdatesNoteHandler = function (): RequestHandler< + TypeOf, + undefined, + undefined +> { + return async (context, request, response) => { + const SOClient = (await context.core).savedObjects.client; + const { package_policy_id: packagePolicyId } = request.params; + + const soClientResponse = await getProtectionNote(SOClient, packagePolicyId); + + if (!soClientResponse.saved_objects[0] || !soClientResponse.saved_objects[0].attributes) { + return response.notFound({ body: { message: 'No note found for this policy' } }); + } + + const { attributes } = soClientResponse.saved_objects[0]; + + return response.ok({ body: attributes }); + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts new file mode 100644 index 0000000000000..4d398bbe14e6e --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IRouter } from '@kbn/core/server'; +import { getProtectionUpdatesNoteHandler, postProtectionUpdatesNoteHandler } from './handlers'; +import { + GetProtectionUpdatesNoteSchema, + CreateUpdateProtectionUpdatesNoteSchema, +} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; +import { withEndpointAuthz } from '../with_endpoint_authz'; +import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../common/endpoint/constants'; +import type { EndpointAppContext } from '../../types'; + +export function registerProtectionUpdatesNoteRoutes( + router: IRouter, + endpointAppContext: EndpointAppContext +) { + const logger = endpointAppContext.logFactory.get('protectionUpdatesNote'); + + router.versioned + .post({ + access: 'public', + path: PROTECTION_UPDATES_NOTE_ROUTE, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: CreateUpdateProtectionUpdatesNoteSchema, + }, + }, + withEndpointAuthz( + { all: ['canWritePolicyManagement'] }, + logger, + postProtectionUpdatesNoteHandler() + ) + ); + + router.versioned + .get({ + access: 'public', + path: PROTECTION_UPDATES_NOTE_ROUTE, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: GetProtectionUpdatesNoteSchema, + }, + }, + withEndpointAuthz( + { all: ['canReadPolicyManagement'] }, + logger, + getProtectionUpdatesNoteHandler() + ) + ); +} diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts index 748e2c1058036..918d29c21f95f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts @@ -236,7 +236,7 @@ export class EndpointMetadataService { fleetAgent = await this.getFleetAgent(fleetServices.agent, fleetAgentId); } catch (error) { if (error instanceof FleetAgentNotFoundError) { - this.logger?.warn(`agent with id ${fleetAgentId} not found`); + this.logger?.warn(`Agent with id ${fleetAgentId} not found`); } else { throw error; } diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index e2ce386337a85..b91bfae4eb405 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -437,6 +437,66 @@ describe('ingest_integration tests ', () => { licenseEmitter.next(Platinum); // set license level to platinum }); + it.each([ + { + date: 'invalid', + message: 'Invalid date format. Use "latest" or "YYYY-MM-DD" format. UTC time.', + }, + { + date: '2023-10-1', + message: 'Invalid date format. Use "latest" or "YYYY-MM-DD" format. UTC time.', + }, + { + date: '2020-10-31', + message: + 'Global manifest version is too far in the past. Use "latest" or a date within the last 18 months. UTC time.', + }, + { + date: '2100-10-01', + message: 'Global manifest version cannot be in the future. UTC time.', + }, + { + date: 'latest', + }, + ])( + 'should return bad request for invalid endpoint package policy global manifest values', + async ({ date, message }) => { + const mockPolicy = policyFactory(); // defaults with paid features on + const logger = loggingSystemMock.create().get('ingest_integration.test'); + const callback = getPackagePolicyUpdateCallback( + logger, + licenseService, + endpointAppContextMock.featureUsageService, + endpointAppContextMock.endpointMetadataService, + cloudService, + esClient, + appFeaturesService + ); + const policyConfig = generator.generatePolicyPackagePolicy(); + policyConfig.inputs[0]!.config!.policy.value = { + ...mockPolicy, + global_manifest_version: date, + }; + if (!message) { + const updatedPolicyConfig = await callback( + policyConfig, + soClient, + esClient, + requestContextMock.convertContext(ctx), + req + ); + expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual({ + ...mockPolicy, + global_manifest_version: date, + }); + } else { + await expect(() => + callback(policyConfig, soClient, esClient, requestContextMock.convertContext(ctx), req) + ).rejects.toThrow(message); + } + } + ); + it('updates successfully when paid features are turned on', async () => { const mockPolicy = policyFactory(); mockPolicy.windows.popup.malware.message = 'paid feature'; @@ -615,7 +675,7 @@ describe('ingest_integration tests ', () => { const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const invokeDeleteCallback = async (): Promise => { - const callback = getPackagePolicyDeleteCallback(exceptionListClient); + const callback = getPackagePolicyDeleteCallback(exceptionListClient, soClient); await callback(deletePackagePolicyMock(), soClient, esClient); }; @@ -640,6 +700,27 @@ describe('ingest_integration tests ', () => { }); it('removes policy from artifact', async () => { + soClient.find.mockResolvedValueOnce({ + total: 1, + saved_objects: [ + { + id: 'id', + type: 'type', + references: [ + { + id: 'id_package_policy', + name: 'package_policy', + type: 'ingest-package-policies', + }, + ], + attributes: { note: 'note' }, + score: 1, + }, + ], + page: 1, + per_page: 10, + }); + await invokeDeleteCallback(); expect(exceptionListClient.findExceptionListsItem).toHaveBeenCalledWith({ @@ -660,6 +741,8 @@ describe('ingest_integration tests ', () => { osTypes: fakeArtifact.os_types, tags: [], }); + + expect(soClient.delete).toBeCalledWith('policy-settings-protection-updates-note', 'id'); }); }); }); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index 554417eee480d..ce6ff450a6869 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { Logger, ElasticsearchClient } from '@kbn/core/server'; +import type { Logger, ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { PluginStartContract as AlertsStartContract } from '@kbn/alerting-plugin/server'; import type { @@ -23,6 +23,7 @@ import type { import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; +import { validateEndpointPackagePolicy } from './handlers/validate_endpoint_package_policy'; import { isPolicySetToEventCollectionOnly, ensureOnlyEventCollectionIsAllowed, @@ -44,6 +45,7 @@ import type { AnyPolicyCreateConfig } from './types'; import { ENDPOINT_INTEGRATION_CONFIG_KEY } from './constants'; import { createEventFilters } from './handlers/create_event_filters'; import type { AppFeaturesService } from '../lib/app_features_service/app_features_service'; +import { removeProtectionUpdatesNote } from './handlers/remove_protection_updates_note'; const isEndpointPackagePolicy = ( packagePolicy: T @@ -101,6 +103,9 @@ export const getPackagePolicyCreateCallback = ( return newPackagePolicy; } + if (newPackagePolicy?.inputs) { + validateEndpointPackagePolicy(newPackagePolicy.inputs); + } // Optional endpoint integration configuration let endpointIntegrationConfig; @@ -204,6 +209,8 @@ export const getPackagePolicyUpdateCallback = ( logger ); + validateEndpointPackagePolicy(endpointIntegrationData.inputs); + notifyProtectionFeatureUsage( endpointIntegrationData, featureUsageService, @@ -280,7 +287,8 @@ export const getPackagePolicyPostCreateCallback = ( }; export const getPackagePolicyDeleteCallback = ( - exceptionsClient: ExceptionListClient | undefined + exceptionsClient: ExceptionListClient | undefined, + savedObjectsClient: SavedObjectsClientContract | undefined ): PostPackagePolicyPostDeleteCallback => { return async (deletePackagePolicy): Promise => { if (!exceptionsClient) { @@ -290,8 +298,12 @@ export const getPackagePolicyDeleteCallback = ( for (const policy of deletePackagePolicy) { if (isEndpointPackagePolicy(policy)) { policiesToRemove.push(removePolicyFromArtifacts(exceptionsClient, policy)); + if (savedObjectsClient) { + policiesToRemove.push(removeProtectionUpdatesNote(savedObjectsClient, policy)); + } } } + await Promise.all(policiesToRemove); }; }; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_protection_updates_note.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_protection_updates_note.ts new file mode 100644 index 0000000000000..9106eba06e780 --- /dev/null +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_protection_updates_note.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PostPackagePolicyPostDeleteCallback } from '@kbn/fleet-plugin/server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import pMap from 'p-map'; +import { protectionUpdatesNoteSavedObjectType } from '../../endpoint/lib/protection_updates_note/saved_object_mappings'; + +export const removeProtectionUpdatesNote = async ( + soClient: SavedObjectsClientContract, + policy: Parameters[0][0] +) => { + if (policy.id) { + const foundProtectionUpdatesNotes = await soClient.find({ + type: protectionUpdatesNoteSavedObjectType, + hasReference: { + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + id: policy.id, + }, + }); + await pMap( + foundProtectionUpdatesNotes.saved_objects, + (protectionUpdatesNote: { id: string }) => { + soClient.delete(protectionUpdatesNoteSavedObjectType, protectionUpdatesNote.id); + } + ); + } +}; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts new file mode 100644 index 0000000000000..11033f6cc6989 --- /dev/null +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; + +import type { NewPackagePolicyInput } from '@kbn/fleet-plugin/common'; + +export const validateEndpointPackagePolicy = (inputs: NewPackagePolicyInput[]) => { + const input = inputs.find((i) => i.type === 'endpoint'); + if (input?.config?.policy?.value?.global_manifest_version) { + const globalManifestVersion = input.config.policy.value.global_manifest_version; + + if (globalManifestVersion !== 'latest') { + const parsedDate = moment.utc(globalManifestVersion, 'YYYY-MM-DD', true); + if (!parsedDate.isValid()) { + throw createManifestVersionError( + 'Invalid date format. Use "latest" or "YYYY-MM-DD" format. UTC time.' + ); + } + + const maxAllowedDate = moment.utc().subtract(18, 'months'); + if (parsedDate.isBefore(maxAllowedDate)) { + throw createManifestVersionError( + 'Global manifest version is too far in the past. Use "latest" or a date within the last 18 months. UTC time.' + ); + } + if (parsedDate.isAfter(moment.utc())) { + throw createManifestVersionError( + 'Global manifest version cannot be in the future. UTC time.' + ); + } + } + } +}; + +const createManifestVersionError = ( + message: string +): Error & { statusCode?: number; apiPassThrough?: boolean } => { + const manifestVersionError: Error & { statusCode?: number; apiPassThrough?: boolean } = new Error( + message + ); + manifestVersionError.statusCode = 400; + manifestVersionError.apiPassThrough = true; + return manifestVersionError; +}; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts index 711b46babbc54..d572b15bb4d2d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts @@ -32,8 +32,8 @@ const validateEndpointIntegrationConfig = ( logger: Logger ): void => { if (!config?.endpointConfig?.preset) { - logger.warn('missing endpointConfig preset'); - throwError('invalid endpointConfig preset'); + logger.warn('Missing endpointConfig preset'); + throwError('Invalid endpointConfig preset'); } if ( ![ @@ -43,8 +43,8 @@ const validateEndpointIntegrationConfig = ( ENDPOINT_CONFIG_PRESET_DATA_COLLECTION, ].includes(config.endpointConfig.preset) ) { - logger.warn(`invalid endpointConfig preset: ${config.endpointConfig.preset}`); - throwError('invalid endpointConfig preset'); + logger.warn(`Invalid endpointConfig preset: ${config.endpointConfig.preset}`); + throwError('Invalid endpointConfig preset'); } }; const validateCloudIntegrationConfig = (config: PolicyCreateCloudConfig, logger: Logger): void => { @@ -56,7 +56,7 @@ const validateCloudIntegrationConfig = (config: PolicyCreateCloudConfig, logger: } if (typeof config.eventFilters?.nonInteractiveSession !== 'boolean') { logger.warn( - `missing or invalid value for eventFilters nonInteractiveSession: ${config.eventFilters?.nonInteractiveSession}` + `Missing or invalid value for eventFilters nonInteractiveSession: ${config.eventFilters?.nonInteractiveSession}` ); throwError('invalid value for eventFilters nonInteractiveSession'); } diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_policy_against_license.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_policy_against_license.ts index 4b19654b93ad4..35e8fea91346e 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_policy_against_license.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_policy_against_license.ts @@ -18,8 +18,12 @@ export const validatePolicyAgainstLicense = ( if (!isEndpointPolicyValidForLicense(policyConfig, licenseService.getLicenseInformation())) { logger.warn('Incorrect license tier for paid policy fields'); // The `statusCode` below is used by Fleet API handler to ensure that the proper HTTP code is used in the API response - const licenseError: Error & { statusCode?: number } = new Error('Requires Platinum license'); + const licenseError: Error & { statusCode?: number; passThroughApi?: boolean } = new Error( + 'Requires Platinum license' + ); licenseError.statusCode = 403; + licenseError.passThroughApi = true; + throw licenseError; } }; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_dashboards_by_tags.ts b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_dashboards_by_tags.ts index 356e4c1996e24..ef4695aea6ea5 100644 --- a/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_dashboards_by_tags.ts +++ b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_dashboards_by_tags.ts @@ -22,43 +22,48 @@ export const getDashboardsByTagsRoute = ( logger: Logger, security: SetupPlugins['security'] ) => { - router.post( - { + router.versioned + .post({ path: INTERNAL_DASHBOARDS_URL, - validate: { body: buildRouteValidationWithExcess(getDashboardsRequest) }, + access: 'internal', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const frameworkRequest = await buildFrameworkRequest(context, security, request); - const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; - const { tagIds } = request.body; + }) + .addVersion( + { + version: '1', + validate: { request: { body: buildRouteValidationWithExcess(getDashboardsRequest) } }, + }, + async (context, request, response) => { + const frameworkRequest = await buildFrameworkRequest(context, security, request); + const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; + const { tagIds } = request.body; - try { - const dashboardsResponse = await savedObjectsClient.find({ - type: 'dashboard', - hasReference: tagIds.map((id) => ({ id, type: 'tag' })), - }); - const dashboards = dashboardsResponse.saved_objects ?? []; + try { + const dashboardsResponse = await savedObjectsClient.find({ + type: 'dashboard', + hasReference: tagIds.map((id) => ({ id, type: 'tag' })), + }); + const dashboards = dashboardsResponse.saved_objects ?? []; - return response.ok({ body: dashboards }); - } catch (err) { - const error = transformError(err); - logger.error(`Failed to find dashboards tags - ${JSON.stringify(error.message)}`); + return response.ok({ body: dashboards }); + } catch (err) { + const error = transformError(err); + logger.error(`Failed to find dashboards tags - ${JSON.stringify(error.message)}`); - const siemResponse = buildSiemResponse(response); - return siemResponse.error({ - statusCode: error.statusCode ?? 500, - body: i18n.translate( - 'xpack.securitySolution.dashboards.getSecuritySolutionDashboardsErrorTitle', - { - values: { message: error.message }, - defaultMessage: `Failed to find dashboards - {message}`, - } - ), - }); + const siemResponse = buildSiemResponse(response); + return siemResponse.error({ + statusCode: error.statusCode ?? 500, + body: i18n.translate( + 'xpack.securitySolution.dashboards.getSecuritySolutionDashboardsErrorTitle', + { + values: { message: error.message }, + defaultMessage: `Failed to find dashboards - {message}`, + } + ), + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts index e4e4e2f4a6f78..bf13e1f49134e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts @@ -21,48 +21,56 @@ export const getInstalledIntegrationsRoute = ( router: SecuritySolutionPluginRouter, logger: Logger ) => { - router.get( - { + router.versioned + .get({ + access: 'internal', path: GET_INSTALLED_INTEGRATIONS_URL, - validate: {}, options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '1', + validate: false, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); - try { - const ctx = await context.resolve(['core', 'securitySolution']); - const fleet = ctx.securitySolution.getInternalFleetServices(); - const set = createInstalledIntegrationSet(); + try { + const ctx = await context.resolve(['core', 'securitySolution']); + const fleet = ctx.securitySolution.getInternalFleetServices(); + const set = createInstalledIntegrationSet(); - // Pulls all packages into memory just like the main fleet landing page - // No pagination support currently, so cannot batch this call - const allThePackages = await fleet.packages.getPackages(); - allThePackages.forEach((fleetPackage) => { - set.addPackage(fleetPackage); - }); + // Pulls all packages into memory just like the main fleet landing page + // No pagination support currently, so cannot batch this call + const allThePackages = await fleet.packages.getPackages(); + allThePackages.forEach((fleetPackage) => { + set.addPackage(fleetPackage); + }); - const packagePolicies = await fleet.packagePolicy.list(fleet.internalReadonlySoClient, {}); - packagePolicies.items.forEach((policy) => { - set.addPackagePolicy(policy); - }); + const packagePolicies = await fleet.packagePolicy.list( + fleet.internalReadonlySoClient, + {} + ); + packagePolicies.items.forEach((policy) => { + set.addPackagePolicy(policy); + }); - const installedIntegrations = set.getIntegrations(); + const installedIntegrations = set.getIntegrations(); - const body: GetInstalledIntegrationsResponse = { - installed_integrations: installedIntegrations, - }; + const body: GetInstalledIntegrationsResponse = { + installed_integrations: installedIntegrations, + }; - return response.ok({ body }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + return response.ok({ body }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts index bc6d2a6247f34..7053c913e4a3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts @@ -36,34 +36,39 @@ import { getIndexVersion } from './get_index_version'; import { isOutdated } from '../../migrations/helpers'; export const createIndexRoute = (router: SecuritySolutionPluginRouter) => { - router.post( - { + router.versioned + .post({ path: DETECTION_ENGINE_INDEX_URL, - validate: false, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, _, response): Promise> => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '2023-10-31', + validate: false, + }, + async (context, _, response): Promise> => { + const siemResponse = buildSiemResponse(response); - try { - const securitySolution = await context.securitySolution; - const siemClient = securitySolution?.getAppClient(); - if (!siemClient) { - return siemResponse.error({ statusCode: 404 }); + try { + const securitySolution = await context.securitySolution; + const siemClient = securitySolution?.getAppClient(); + if (!siemClient) { + return siemResponse.error({ statusCode: 404 }); + } + await createDetectionIndex(securitySolution); + return response.ok({ body: { acknowledged: true } }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); } - await createDetectionIndex(securitySolution); - return response.ok({ body: { acknowledged: true } }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); } - } - ); + ); }; export const createDetectionIndex = async ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts index e0f73108bf437..4bad93c04edc0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts @@ -31,57 +31,62 @@ import type { DeleteIndexResponse } from '../../../../../common/api/detection_en * And ensuring they're all gone */ export const deleteIndexRoute = (router: SecuritySolutionPluginRouter) => { - router.delete( - { + router.versioned + .delete({ path: DETECTION_ENGINE_INDEX_URL, - validate: false, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, _, response): Promise> => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '2023-10-31', + validate: false, + }, + async (context, _, response): Promise> => { + const siemResponse = buildSiemResponse(response); - try { - const esClient = (await context.core).elasticsearch.client.asCurrentUser; + try { + const esClient = (await context.core).elasticsearch.client.asCurrentUser; - const siemClient = (await context.securitySolution)?.getAppClient(); + const siemClient = (await context.securitySolution)?.getAppClient(); - if (!siemClient) { - return siemResponse.error({ statusCode: 404 }); - } + if (!siemClient) { + return siemResponse.error({ statusCode: 404 }); + } - const index = siemClient.getSignalsIndex(); - const indexExists = await getIndexExists(esClient, index); + const index = siemClient.getSignalsIndex(); + const indexExists = await getIndexExists(esClient, index); - if (!indexExists) { + if (!indexExists) { + return siemResponse.error({ + statusCode: 404, + body: `index: "${index}" does not exist`, + }); + } else { + await deleteAllIndex(esClient, index, true); + const policyExists = await getPolicyExists(esClient, index); + if (policyExists) { + await deletePolicy(esClient, index); + } + const templateExists = await esClient.indices.existsIndexTemplate({ name: index }); + if (templateExists) { + await esClient.indices.deleteIndexTemplate({ name: index }); + } + const legacyTemplateExists = await esClient.indices.existsTemplate({ name: index }); + if (legacyTemplateExists) { + await esClient.indices.deleteTemplate({ name: index }); + } + return response.ok({ body: { acknowledged: true } }); + } + } catch (err) { + const error = transformError(err); return siemResponse.error({ - statusCode: 404, - body: `index: "${index}" does not exist`, + body: error.message, + statusCode: error.statusCode, }); - } else { - await deleteAllIndex(esClient, index, true); - const policyExists = await getPolicyExists(esClient, index); - if (policyExists) { - await deletePolicy(esClient, index); - } - const templateExists = await esClient.indices.existsIndexTemplate({ name: index }); - if (templateExists) { - await esClient.indices.deleteIndexTemplate({ name: index }); - } - const legacyTemplateExists = await esClient.indices.existsTemplate({ name: index }); - if (legacyTemplateExists) { - await esClient.indices.deleteTemplate({ name: index }); - } - return response.ok({ body: { acknowledged: true } }); } - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts index 7f33c2bad1e20..56f37df726823 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts @@ -14,41 +14,46 @@ import { buildSiemResponse } from '../utils'; import type { ReadAlertsIndexExistsResponse } from '../../../../../common/api/detection_engine'; export const readAlertsIndexExistsRoute = (router: SecuritySolutionPluginRouter) => { - router.get( - { + router.versioned + .get({ path: DETECTION_ENGINE_ALERTS_INDEX_URL, - validate: false, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, _, response): Promise> => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '2023-10-31', + validate: false, + }, + async (context, _, response): Promise> => { + const siemResponse = buildSiemResponse(response); - try { - const core = await context.core; - const securitySolution = await context.securitySolution; - const siemClient = securitySolution?.getAppClient(); + try { + const core = await context.core; + const securitySolution = await context.securitySolution; + const siemClient = securitySolution?.getAppClient(); - if (!siemClient) { - return siemResponse.error({ statusCode: 404 }); - } + if (!siemClient) { + return siemResponse.error({ statusCode: 404 }); + } - const index = siemClient.getSignalsIndex(); + const index = siemClient.getSignalsIndex(); - const indexExists = await getIndexExists(core.elasticsearch.client.asInternalUser, index); - return response.ok({ - body: { - indexExists, - }, - }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + const indexExists = await getIndexExists(core.elasticsearch.client.asInternalUser, index); + return response.ok({ + body: { + indexExists, + }, + }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts index 34ccd59f95946..27adbd71be823 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts @@ -22,79 +22,84 @@ export const readIndexRoute = ( router: SecuritySolutionPluginRouter, ruleDataService: RuleDataPluginService ) => { - router.get( - { + router.versioned + .get({ path: DETECTION_ENGINE_INDEX_URL, - validate: false, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, _, response): Promise> => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '2023-10-31', + validate: false, + }, + async (context, _, response): Promise> => { + const siemResponse = buildSiemResponse(response); - try { - const core = await context.core; - const securitySolution = await context.securitySolution; + try { + const core = await context.core; + const securitySolution = await context.securitySolution; - const siemClient = securitySolution?.getAppClient(); - const esClient = core.elasticsearch.client.asCurrentUser; + const siemClient = securitySolution?.getAppClient(); + const esClient = core.elasticsearch.client.asCurrentUser; - if (!siemClient) { - return siemResponse.error({ statusCode: 404 }); - } + if (!siemClient) { + return siemResponse.error({ statusCode: 404 }); + } - const spaceId = securitySolution.getSpaceId(); - const indexName = ruleDataService.getResourceName(`security.alerts-${spaceId}`); + const spaceId = securitySolution.getSpaceId(); + const indexName = ruleDataService.getResourceName(`security.alerts-${spaceId}`); - const index = siemClient.getSignalsIndex(); - const indexExists = await getBootstrapIndexExists( - core.elasticsearch.client.asInternalUser, - index - ); + const index = siemClient.getSignalsIndex(); + const indexExists = await getBootstrapIndexExists( + core.elasticsearch.client.asInternalUser, + index + ); - if (indexExists) { - let mappingOutdated: boolean | null = null; - let aliasesOutdated: boolean | null = null; - try { - const indexVersion = await getIndexVersion(esClient, index); - mappingOutdated = isOutdated({ - current: indexVersion, - target: SIGNALS_TEMPLATE_VERSION, - }); - aliasesOutdated = await fieldAliasesOutdated(esClient, index); - } catch (err) { - const error = transformError(err); - // Some users may not have the view_index_metadata permission necessary to check the index mapping version - // so just continue and return null for index_mapping_outdated if the error is a 403 - if (error.statusCode !== 403) { - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, + if (indexExists) { + let mappingOutdated: boolean | null = null; + let aliasesOutdated: boolean | null = null; + try { + const indexVersion = await getIndexVersion(esClient, index); + mappingOutdated = isOutdated({ + current: indexVersion, + target: SIGNALS_TEMPLATE_VERSION, }); + aliasesOutdated = await fieldAliasesOutdated(esClient, index); + } catch (err) { + const error = transformError(err); + // Some users may not have the view_index_metadata permission necessary to check the index mapping version + // so just continue and return null for index_mapping_outdated if the error is a 403 + if (error.statusCode !== 403) { + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } + return response.ok({ + body: { + name: indexName, + index_mapping_outdated: mappingOutdated || aliasesOutdated, + }, + }); + } else { + return response.ok({ + body: { + name: indexName, + index_mapping_outdated: false, + }, + }); } - return response.ok({ - body: { - name: indexName, - index_mapping_outdated: mappingOutdated || aliasesOutdated, - }, - }); - } else { - return response.ok({ - body: { - name: indexName, - index_mapping_outdated: false, - }, + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, }); } - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts index ebd06e62aab1d..314d2c273b04a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -18,45 +18,50 @@ export const readPrivilegesRoute = ( router: SecuritySolutionPluginRouter, hasEncryptionKey: boolean ) => { - router.get( - { + router.versioned + .get({ path: DETECTION_ENGINE_PRIVILEGES_URL, - validate: false, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response): Promise> => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '2023-10-31', + validate: false, + }, + async (context, request, response): Promise> => { + const siemResponse = buildSiemResponse(response); - try { - const core = await context.core; - const securitySolution = await context.securitySolution; - const esClient = core.elasticsearch.client.asCurrentUser; - const siemClient = securitySolution?.getAppClient(); + try { + const core = await context.core; + const securitySolution = await context.securitySolution; + const esClient = core.elasticsearch.client.asCurrentUser; + const siemClient = securitySolution?.getAppClient(); - if (!siemClient) { - return siemResponse.error({ statusCode: 404 }); - } + if (!siemClient) { + return siemResponse.error({ statusCode: 404 }); + } - const spaceId = securitySolution.getSpaceId(); - const index = securitySolution - .getRuleDataService() - .getResourceName(`security.alerts-${spaceId}`); - const clusterPrivileges = await readPrivileges(esClient, index); - const privileges = merge(clusterPrivileges, { - is_authenticated: request.auth.isAuthenticated ?? false, - has_encryption_key: hasEncryptionKey, - }); + const spaceId = securitySolution.getSpaceId(); + const index = securitySolution + .getRuleDataService() + .getResourceName(`security.alerts-${spaceId}`); + const clusterPrivileges = await readPrivileges(esClient, index); + const privileges = merge(clusterPrivileges, { + is_authenticated: request.auth.isAuthenticated ?? false, + has_encryption_key: hasEncryptionKey, + }); - return response.ok({ body: privileges }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + return response.ok({ body: privileges }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts index 25360a12d34b5..198a1992058fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts @@ -24,123 +24,129 @@ export const createSignalsMigrationRoute = ( router: SecuritySolutionPluginRouter, security: SetupPlugins['security'] ) => { - router.post( - { + router.versioned + .post({ path: DETECTION_ENGINE_SIGNALS_MIGRATION_URL, - validate: { - body: buildRouteValidation(createSignalsMigrationSchema), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - const { index: indices, ...reindexOptions } = request.body; + }) + .addVersion( + { + version: '2023-10-31', + validate: { request: { body: buildRouteValidation(createSignalsMigrationSchema) } }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const { index: indices, ...reindexOptions } = request.body; - try { - const core = await context.core; - const securitySolution = await context.securitySolution; + try { + const core = await context.core; + const securitySolution = await context.securitySolution; - const esClient = core.elasticsearch.client.asCurrentUser; - const soClient = core.savedObjects.client; - const appClient = securitySolution?.getAppClient(); - if (!appClient) { - return siemResponse.error({ statusCode: 404 }); - } - const user = await security?.authc.getCurrentUser(request); - const migrationService = signalsMigrationService({ - esClient, - soClient, - username: user?.username ?? 'elastic', - }); + const esClient = core.elasticsearch.client.asCurrentUser; + const soClient = core.savedObjects.client; + const appClient = securitySolution?.getAppClient(); + if (!appClient) { + return siemResponse.error({ statusCode: 404 }); + } + const user = await security?.authc.getCurrentUser(request); + const migrationService = signalsMigrationService({ + esClient, + soClient, + username: user?.username ?? 'elastic', + }); - const signalsAlias = appClient.getSignalsIndex(); - const currentVersion = await getTemplateVersion({ - alias: signalsAlias, - esClient, - }); + const signalsAlias = appClient.getSignalsIndex(); + const currentVersion = await getTemplateVersion({ + alias: signalsAlias, + esClient, + }); - if (isOutdated({ current: currentVersion, target: SIGNALS_TEMPLATE_VERSION })) { - throw new BadRequestError( - `Cannot migrate due to the signals template being out of date. Latest version: [${SIGNALS_TEMPLATE_VERSION}], template version: [${currentVersion}]. Please visit Detections to automatically update your template, then try again.` - ); - } + if (isOutdated({ current: currentVersion, target: SIGNALS_TEMPLATE_VERSION })) { + throw new BadRequestError( + `Cannot migrate due to the signals template being out of date. Latest version: [${SIGNALS_TEMPLATE_VERSION}], template version: [${currentVersion}]. Please visit Detections to automatically update your template, then try again.` + ); + } - const signalsIndexAliases = await getIndexAliases({ esClient, alias: signalsAlias }); + const signalsIndexAliases = await getIndexAliases({ esClient, alias: signalsAlias }); - const nonSignalsIndices = indices.filter( - (index) => !signalsIndexAliases.some((alias) => alias.index === index) - ); - if (nonSignalsIndices.length > 0) { - throw new BadRequestError( - `The following indices are not signals indices and cannot be migrated: [${nonSignalsIndices.join()}].` + const nonSignalsIndices = indices.filter( + (index) => !signalsIndexAliases.some((alias) => alias.index === index) ); - } + if (nonSignalsIndices.length > 0) { + throw new BadRequestError( + `The following indices are not signals indices and cannot be migrated: [${nonSignalsIndices.join()}].` + ); + } - const indexVersionsByIndex = await getIndexVersionsByIndex({ esClient, index: indices }); - const signalVersionsByIndex = await getSignalVersionsByIndex({ esClient, index: indices }); + const indexVersionsByIndex = await getIndexVersionsByIndex({ esClient, index: indices }); + const signalVersionsByIndex = await getSignalVersionsByIndex({ + esClient, + index: indices, + }); - const migrationResults = await Promise.all( - indices.map(async (index) => { - const indexVersion = indexVersionsByIndex[index] ?? 0; - const signalVersions = signalVersionsByIndex[index] ?? []; + const migrationResults = await Promise.all( + indices.map(async (index) => { + const indexVersion = indexVersionsByIndex[index] ?? 0; + const signalVersions = signalVersionsByIndex[index] ?? []; - if ( - isOutdated({ current: indexVersion, target: currentVersion }) || - signalsAreOutdated({ signalVersions, target: currentVersion }) - ) { - try { - const isWriteIndex = signalsIndexAliases.some( - (alias) => alias.isWriteIndex && alias.index === index - ); - if (isWriteIndex) { - throw new BadRequestError( - 'The specified index is a write index and cannot be migrated.' + if ( + isOutdated({ current: indexVersion, target: currentVersion }) || + signalsAreOutdated({ signalVersions, target: currentVersion }) + ) { + try { + const isWriteIndex = signalsIndexAliases.some( + (alias) => alias.isWriteIndex && alias.index === index ); - } + if (isWriteIndex) { + throw new BadRequestError( + 'The specified index is a write index and cannot be migrated.' + ); + } - const migration = await migrationService.create({ - index, - reindexOptions, - version: currentVersion, - }); + const migration = await migrationService.create({ + index, + reindexOptions, + version: currentVersion, + }); - return { - index: migration.attributes.sourceIndex, - migration_id: migration.id, - migration_index: migration.attributes.destinationIndex, - }; - } catch (err) { - const error = transformError(err); + return { + index: migration.attributes.sourceIndex, + migration_id: migration.id, + migration_index: migration.attributes.destinationIndex, + }; + } catch (err) { + const error = transformError(err); + return { + index, + error: { + message: error.message, + status_code: error.statusCode, + }, + migration_id: null, + migration_index: null, + }; + } + } else { return { index, - error: { - message: error.message, - status_code: error.statusCode, - }, migration_id: null, migration_index: null, }; } - } else { - return { - index, - migration_id: null, - migration_index: null, - }; - } - }) - ); + }) + ); - return response.ok({ body: { indices: migrationResults } }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + return response.ok({ body: { indices: migrationResults } }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts index da49f0a8ace3a..4a4a38e074c0b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts @@ -20,86 +20,89 @@ export const deleteSignalsMigrationRoute = ( router: SecuritySolutionPluginRouter, security: SetupPlugins['security'] ) => { - router.delete( - { + router.versioned + .delete({ path: DETECTION_ENGINE_SIGNALS_MIGRATION_URL, - validate: { - body: buildRouteValidation(deleteSignalsMigrationSchema), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - const { migration_ids: migrationIds } = request.body; + }) + .addVersion( + { + version: '2023-10-31', + validate: { request: { body: buildRouteValidation(deleteSignalsMigrationSchema) } }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const { migration_ids: migrationIds } = request.body; - try { - const core = await context.core; - const securitySolution = await context.securitySolution; + try { + const core = await context.core; + const securitySolution = await context.securitySolution; - const esClient = core.elasticsearch.client.asCurrentUser; - const soClient = core.savedObjects.client; - const appClient = securitySolution?.getAppClient(); - if (!appClient) { - return siemResponse.error({ statusCode: 404 }); - } + const esClient = core.elasticsearch.client.asCurrentUser; + const soClient = core.savedObjects.client; + const appClient = securitySolution?.getAppClient(); + if (!appClient) { + return siemResponse.error({ statusCode: 404 }); + } - const user = await security?.authc.getCurrentUser(request); - const migrationService = signalsMigrationService({ - esClient, - soClient, - username: user?.username ?? 'elastic', - }); + const user = await security?.authc.getCurrentUser(request); + const migrationService = signalsMigrationService({ + esClient, + soClient, + username: user?.username ?? 'elastic', + }); - const signalsAlias = appClient.getSignalsIndex(); - const migrations = await getMigrationSavedObjectsById({ - ids: migrationIds, - soClient, - }); + const signalsAlias = appClient.getSignalsIndex(); + const migrations = await getMigrationSavedObjectsById({ + ids: migrationIds, + soClient, + }); - const deletionResults = await Promise.all( - migrations.map(async (migration) => { - try { - const deletedMigration = await migrationService.delete({ - migration, - signalsAlias, - }); + const deletionResults = await Promise.all( + migrations.map(async (migration) => { + try { + const deletedMigration = await migrationService.delete({ + migration, + signalsAlias, + }); - return { - id: deletedMigration.id, - destinationIndex: deletedMigration.attributes.destinationIndex, - status: deletedMigration.attributes.status, - sourceIndex: deletedMigration.attributes.sourceIndex, - version: deletedMigration.attributes.version, - updated: deletedMigration.attributes.updated, - }; - } catch (err) { - const error = transformError(err); - return { - id: migration.id, - destinationIndex: migration.attributes.destinationIndex, - error: { - message: error.message, - status_code: error.statusCode, - }, - status: migration.attributes.status, - sourceIndex: migration.attributes.sourceIndex, - version: migration.attributes.version, - updated: migration.attributes.updated, - }; - } - }) - ); + return { + id: deletedMigration.id, + destinationIndex: deletedMigration.attributes.destinationIndex, + status: deletedMigration.attributes.status, + sourceIndex: deletedMigration.attributes.sourceIndex, + version: deletedMigration.attributes.version, + updated: deletedMigration.attributes.updated, + }; + } catch (err) { + const error = transformError(err); + return { + id: migration.id, + destinationIndex: migration.attributes.destinationIndex, + error: { + message: error.message, + status_code: error.statusCode, + }, + status: migration.attributes.status, + sourceIndex: migration.attributes.sourceIndex, + version: migration.attributes.version, + updated: migration.attributes.updated, + }; + } + }) + ); - return response.ok({ body: { migrations: deletionResults } }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + return response.ok({ body: { migrations: deletionResults } }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts index 0cdcef4e987d6..12d6520fa52d1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts @@ -23,95 +23,98 @@ export const finalizeSignalsMigrationRoute = ( ruleDataService: RuleDataPluginService, security: SetupPlugins['security'] ) => { - router.post( - { + router.versioned + .post({ path: DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL, - validate: { - body: buildRouteValidation(finalizeSignalsMigrationSchema), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '2023-10-31', + validate: { request: { body: buildRouteValidation(finalizeSignalsMigrationSchema) } }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); - const core = await context.core; - const securitySolution = await context.securitySolution; + const core = await context.core; + const securitySolution = await context.securitySolution; - const esClient = core.elasticsearch.client.asCurrentUser; - const soClient = core.savedObjects.client; - const { migration_ids: migrationIds } = request.body; + const esClient = core.elasticsearch.client.asCurrentUser; + const soClient = core.savedObjects.client; + const { migration_ids: migrationIds } = request.body; - try { - const appClient = securitySolution?.getAppClient(); - if (!appClient) { - return siemResponse.error({ statusCode: 404 }); - } - const user = await security?.authc.getCurrentUser(request); - const migrationService = signalsMigrationService({ - esClient, - soClient, - username: user?.username ?? 'elastic', - }); - const migrations = await getMigrationSavedObjectsById({ - ids: migrationIds, - soClient, - }); + try { + const appClient = securitySolution?.getAppClient(); + if (!appClient) { + return siemResponse.error({ statusCode: 404 }); + } + const user = await security?.authc.getCurrentUser(request); + const migrationService = signalsMigrationService({ + esClient, + soClient, + username: user?.username ?? 'elastic', + }); + const migrations = await getMigrationSavedObjectsById({ + ids: migrationIds, + soClient, + }); - const spaceId = securitySolution.getSpaceId(); - const signalsAlias = ruleDataService.getResourceName(`security.alerts-${spaceId}`); - const finalizeResults = await Promise.all( - migrations.map(async (migration) => { - try { - const finalizedMigration = await migrationService.finalize({ - migration, - signalsAlias, - }); + const spaceId = securitySolution.getSpaceId(); + const signalsAlias = ruleDataService.getResourceName(`security.alerts-${spaceId}`); + const finalizeResults = await Promise.all( + migrations.map(async (migration) => { + try { + const finalizedMigration = await migrationService.finalize({ + migration, + signalsAlias, + }); - if (isMigrationFailed(finalizedMigration)) { - throw new BadRequestError( - finalizedMigration.attributes.error ?? 'The migration was not successful.' - ); - } + if (isMigrationFailed(finalizedMigration)) { + throw new BadRequestError( + finalizedMigration.attributes.error ?? 'The migration was not successful.' + ); + } - return { - id: finalizedMigration.id, - completed: !isMigrationPending(finalizedMigration), - destinationIndex: finalizedMigration.attributes.destinationIndex, - status: finalizedMigration.attributes.status, - sourceIndex: finalizedMigration.attributes.sourceIndex, - version: finalizedMigration.attributes.version, - updated: finalizedMigration.attributes.updated, - }; - } catch (err) { - const error = transformError(err); - return { - id: migration.id, - destinationIndex: migration.attributes.destinationIndex, - error: { - message: error.message, - status_code: error.statusCode, - }, - status: migration.attributes.status, - sourceIndex: migration.attributes.sourceIndex, - version: migration.attributes.version, - updated: migration.attributes.updated, - }; - } - }) - ); + return { + id: finalizedMigration.id, + completed: !isMigrationPending(finalizedMigration), + destinationIndex: finalizedMigration.attributes.destinationIndex, + status: finalizedMigration.attributes.status, + sourceIndex: finalizedMigration.attributes.sourceIndex, + version: finalizedMigration.attributes.version, + updated: finalizedMigration.attributes.updated, + }; + } catch (err) { + const error = transformError(err); + return { + id: migration.id, + destinationIndex: migration.attributes.destinationIndex, + error: { + message: error.message, + status_code: error.statusCode, + }, + status: migration.attributes.status, + sourceIndex: migration.attributes.sourceIndex, + version: migration.attributes.version, + updated: migration.attributes.updated, + }; + } + }) + ); - return response.ok({ - body: { migrations: finalizeResults }, - }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + return response.ok({ + body: { migrations: finalizeResults }, + }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts index 7ceecf83268cf..dbf6d97d4b72b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts @@ -19,83 +19,86 @@ import { getTemplateVersion } from '../index/check_template_version'; import { buildSiemResponse } from '../utils'; export const getSignalsMigrationStatusRoute = (router: SecuritySolutionPluginRouter) => { - router.get( - { + router.versioned + .get({ path: DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL, - validate: { - query: buildRouteValidation(getSignalsMigrationStatusSchema), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '2023-10-31', + validate: { request: { query: buildRouteValidation(getSignalsMigrationStatusSchema) } }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); - const core = await context.core; - const securitySolution = await context.securitySolution; + const core = await context.core; + const securitySolution = await context.securitySolution; - const esClient = core.elasticsearch.client.asCurrentUser; - const soClient = core.savedObjects.client; + const esClient = core.elasticsearch.client.asCurrentUser; + const soClient = core.savedObjects.client; - try { - const appClient = securitySolution?.getAppClient(); - if (!appClient) { - return siemResponse.error({ statusCode: 404 }); - } - const { from } = request.query; + try { + const appClient = securitySolution?.getAppClient(); + if (!appClient) { + return siemResponse.error({ statusCode: 404 }); + } + const { from } = request.query; - const signalsAlias = appClient.getSignalsIndex(); - const currentVersion = await getTemplateVersion({ alias: signalsAlias, esClient }); - const indexAliases = await getIndexAliases({ alias: signalsAlias, esClient }); - const signalsIndices = indexAliases.map((indexAlias) => indexAlias.index); - const indicesInRange = await getSignalsIndicesInRange({ - esClient, - index: signalsIndices, - from, - }); - const migrationsByIndex = await getMigrationSavedObjectsByIndex({ - index: indicesInRange, - soClient, - }); - const indexVersionsByIndex = await getIndexVersionsByIndex({ - esClient, - index: indicesInRange, - }); - const signalVersionsByIndex = await getSignalVersionsByIndex({ - esClient, - index: indicesInRange, - }); + const signalsAlias = appClient.getSignalsIndex(); + const currentVersion = await getTemplateVersion({ alias: signalsAlias, esClient }); + const indexAliases = await getIndexAliases({ alias: signalsAlias, esClient }); + const signalsIndices = indexAliases.map((indexAlias) => indexAlias.index); + const indicesInRange = await getSignalsIndicesInRange({ + esClient, + index: signalsIndices, + from, + }); + const migrationsByIndex = await getMigrationSavedObjectsByIndex({ + index: indicesInRange, + soClient, + }); + const indexVersionsByIndex = await getIndexVersionsByIndex({ + esClient, + index: indicesInRange, + }); + const signalVersionsByIndex = await getSignalVersionsByIndex({ + esClient, + index: indicesInRange, + }); - const indexStatuses = indicesInRange.map((index) => { - const version = indexVersionsByIndex[index] ?? 0; - const signalVersions = signalVersionsByIndex[index] ?? []; - const migrations = migrationsByIndex[index] ?? []; + const indexStatuses = indicesInRange.map((index) => { + const version = indexVersionsByIndex[index] ?? 0; + const signalVersions = signalVersionsByIndex[index] ?? []; + const migrations = migrationsByIndex[index] ?? []; - return { - index, - version, - signal_versions: signalVersions, - migrations: migrations.map((m) => ({ - id: m.id, - status: m.attributes.status, - version: m.attributes.version, - updated: m.attributes.updated, - })), - is_outdated: - isOutdated({ current: version, target: currentVersion }) || - signalsAreOutdated({ signalVersions, target: currentVersion }), - }; - }); + return { + index, + version, + signal_versions: signalVersions, + migrations: migrations.map((m) => ({ + id: m.id, + status: m.attributes.status, + version: m.attributes.version, + updated: m.attributes.updated, + })), + is_outdated: + isOutdated({ current: version, target: currentVersion }) || + signalsAreOutdated({ signalVersions, target: currentVersion }), + }; + }); - return response.ok({ body: { indices: indexStatuses } }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + return response.ok({ body: { indices: indexStatuses } }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 42d4b81ffa703..c3060fcc93b88 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -35,84 +35,92 @@ export const setSignalsStatusRoute = ( security: SetupPlugins['security'], sender: ITelemetryEventsSender ) => { - router.post( - { + router.versioned + .post({ path: DETECTION_ENGINE_SIGNALS_STATUS_URL, - validate: { - body: buildRouteValidation( - setSignalsStatusSchema - ), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const { conflicts, signal_ids: signalIds, query, status } = request.body; - const core = await context.core; - const securitySolution = await context.securitySolution; - const esClient = core.elasticsearch.client.asCurrentUser; - const siemClient = securitySolution?.getAppClient(); - const siemResponse = buildSiemResponse(response); - const validationErrors = setSignalStatusValidateTypeDependents(request.body); - const spaceId = securitySolution?.getSpaceId() ?? 'default'; + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + body: buildRouteValidation< + typeof setSignalsStatusSchema, + SetSignalsStatusSchemaDecoded + >(setSignalsStatusSchema), + }, + }, + }, + async (context, request, response) => { + const { conflicts, signal_ids: signalIds, query, status } = request.body; + const core = await context.core; + const securitySolution = await context.securitySolution; + const esClient = core.elasticsearch.client.asCurrentUser; + const siemClient = securitySolution?.getAppClient(); + const siemResponse = buildSiemResponse(response); + const validationErrors = setSignalStatusValidateTypeDependents(request.body); + const spaceId = securitySolution?.getSpaceId() ?? 'default'; - if (validationErrors.length) { - return siemResponse.error({ statusCode: 400, body: validationErrors }); - } + if (validationErrors.length) { + return siemResponse.error({ statusCode: 400, body: validationErrors }); + } - if (!siemClient) { - return siemResponse.error({ statusCode: 404 }); - } + if (!siemClient) { + return siemResponse.error({ statusCode: 404 }); + } - const clusterId = sender.getClusterID(); - const [isTelemetryOptedIn, username] = await Promise.all([ - sender.isTelemetryOptedIn(), - security?.authc.getCurrentUser(request)?.username, - ]); - if (isTelemetryOptedIn && clusterId) { - // Sometimes the ids are in the query not passed in the request? - const toSendAlertIds = get(query, 'bool.filter.terms._id') || signalIds; - // Get Context for Insights Payloads - const sessionId = getSessionIDfromKibanaRequest(clusterId, request); - if (username && toSendAlertIds && sessionId && status) { - const insightsPayloads = createAlertStatusPayloads( - clusterId, - toSendAlertIds, - sessionId, - username, - DETECTION_ENGINE_SIGNALS_STATUS_URL, - status - ); - logger.debug(`Sending Insights Payloads ${JSON.stringify(insightsPayloads)}`); - await sender.sendOnDemand(INSIGHTS_CHANNEL, insightsPayloads); + const clusterId = sender.getClusterID(); + const [isTelemetryOptedIn, username] = await Promise.all([ + sender.isTelemetryOptedIn(), + security?.authc.getCurrentUser(request)?.username, + ]); + if (isTelemetryOptedIn && clusterId) { + // Sometimes the ids are in the query not passed in the request? + const toSendAlertIds = get(query, 'bool.filter.terms._id') || signalIds; + // Get Context for Insights Payloads + const sessionId = getSessionIDfromKibanaRequest(clusterId, request); + if (username && toSendAlertIds && sessionId && status) { + const insightsPayloads = createAlertStatusPayloads( + clusterId, + toSendAlertIds, + sessionId, + username, + DETECTION_ENGINE_SIGNALS_STATUS_URL, + status + ); + logger.debug(`Sending Insights Payloads ${JSON.stringify(insightsPayloads)}`); + await sender.sendOnDemand(INSIGHTS_CHANNEL, insightsPayloads); + } } - } - try { - if (signalIds) { - const body = await updateSignalsStatusByIds(status, signalIds, spaceId, esClient); - return response.ok({ body }); - } else { - const body = await updateSignalsStatusByQuery( - status, - query, - { conflicts: conflicts ?? 'abort' }, - spaceId, - esClient - ); - return response.ok({ body }); + try { + if (signalIds) { + const body = await updateSignalsStatusByIds(status, signalIds, spaceId, esClient); + return response.ok({ body }); + } else { + const body = await updateSignalsStatusByQuery( + status, + query, + { conflicts: conflicts ?? 'abort' }, + spaceId, + esClient + ); + return response.ok({ body }); + } + } catch (err) { + // error while getting or updating signal with id: id in signal index .siem-signals + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); } - } catch (err) { - // error while getting or updating signal with id: id in signal index .siem-signals - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); } - } - ); + ); }; const updateSignalsStatusByIds = async ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts index 922a032ffc78f..673b6c2d85818 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -20,63 +20,70 @@ export const querySignalsRoute = ( router: SecuritySolutionPluginRouter, ruleDataClient: IRuleDataClient | null ) => { - router.post( - { + router.versioned + .post({ path: DETECTION_ENGINE_QUERY_SIGNALS_URL, - validate: { - body: buildRouteValidation( - querySignalsSchema - ), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { query, aggs, _source, fields, track_total_hits, size, runtime_mappings, sort } = - request.body; - const siemResponse = buildSiemResponse(response); - if ( - query == null && - aggs == null && - _source == null && - fields == null && - track_total_hits == null && - size == null && - sort == null - ) { - return siemResponse.error({ - statusCode: 400, - body: '"value" must have at least 1 children', - }); - } - - try { - const spaceId = (await context.securitySolution).getSpaceId(); - const result = await ruleDataClient?.getReader({ namespace: spaceId }).search({ - body: { - query, - // Note: I use a spread operator to please TypeScript with aggs: { ...aggs } - aggs: { ...aggs }, - _source, - fields, - track_total_hits, - size, - runtime_mappings: runtime_mappings as MappingRuntimeFields, - sort: sort as Sort, + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + body: buildRouteValidation( + querySignalsSchema + ), }, - ignore_unavailable: true, - }); - return response.ok({ body: result }); - } catch (err) { - // error while getting or updating signal with id: id in signal index .siem-signals - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + }, + }, + async (context, request, response) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { query, aggs, _source, fields, track_total_hits, size, runtime_mappings, sort } = + request.body; + const siemResponse = buildSiemResponse(response); + if ( + query == null && + aggs == null && + _source == null && + fields == null && + track_total_hits == null && + size == null && + sort == null + ) { + return siemResponse.error({ + statusCode: 400, + body: '"value" must have at least 1 children', + }); + } + + try { + const spaceId = (await context.securitySolution).getSpaceId(); + const result = await ruleDataClient?.getReader({ namespace: spaceId }).search({ + body: { + query, + // Note: I use a spread operator to please TypeScript with aggs: { ...aggs } + aggs: { ...aggs }, + _source, + fields, + track_total_hits, + size, + runtime_mappings: runtime_mappings as MappingRuntimeFields, + sort: sort as Sort, + }, + ignore_unavailable: true, + }); + return response.ok({ body: result }); + } catch (err) { + // error while getting or updating signal with id: id in signal index .siem-signals + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts index 1fc13037e1f9a..36d3e57169cce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts @@ -19,42 +19,50 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v import { validateAlertTagsArrays } from './helpers'; export const setAlertTagsRoute = (router: SecuritySolutionPluginRouter) => { - router.post( - { + router.versioned + .post({ path: DETECTION_ENGINE_ALERT_TAGS_URL, - validate: { - body: buildRouteValidation( - setAlertTagsRequestBody - ), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const { tags, ids } = request.body; - const core = await context.core; - const securitySolution = await context.securitySolution; - const esClient = core.elasticsearch.client.asCurrentUser; - const siemClient = securitySolution?.getAppClient(); - const siemResponse = buildSiemResponse(response); - const validationErrors = validateAlertTagsArrays(tags, ids); - const spaceId = securitySolution?.getSpaceId() ?? 'default'; + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + body: buildRouteValidation< + typeof setAlertTagsRequestBody, + SetAlertTagsRequestBodyDecoded + >(setAlertTagsRequestBody), + }, + }, + }, + async (context, request, response) => { + const { tags, ids } = request.body; + const core = await context.core; + const securitySolution = await context.securitySolution; + const esClient = core.elasticsearch.client.asCurrentUser; + const siemClient = securitySolution?.getAppClient(); + const siemResponse = buildSiemResponse(response); + const validationErrors = validateAlertTagsArrays(tags, ids); + const spaceId = securitySolution?.getSpaceId() ?? 'default'; - if (validationErrors.length) { - return siemResponse.error({ statusCode: 400, body: validationErrors }); - } + if (validationErrors.length) { + return siemResponse.error({ statusCode: 400, body: validationErrors }); + } - if (!siemClient) { - return siemResponse.error({ statusCode: 404 }); - } + if (!siemClient) { + return siemResponse.error({ statusCode: 404 }); + } - const tagsToAdd = uniq(tags.tags_to_add); - const tagsToRemove = uniq(tags.tags_to_remove); + const tagsToAdd = uniq(tags.tags_to_add); + const tagsToRemove = uniq(tags.tags_to_remove); - const painlessScript = { - params: { tagsToAdd, tagsToRemove }, - source: `List newTagsArray = []; + const painlessScript = { + params: { tagsToAdd, tagsToRemove }, + source: `List newTagsArray = []; if (ctx._source["kibana.alert.workflow_tags"] != null) { for (tag in ctx._source["kibana.alert.workflow_tags"]) { if (!params.tagsToRemove.contains(tag)) { @@ -71,45 +79,45 @@ export const setAlertTagsRoute = (router: SecuritySolutionPluginRouter) => { ctx._source["kibana.alert.workflow_tags"] = params.tagsToAdd; } `, - lang: 'painless', - }; + lang: 'painless', + }; - const bulkUpdateRequest = []; - for (const id of ids) { - bulkUpdateRequest.push( - { - update: { - _index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, - _id: id, + const bulkUpdateRequest = []; + for (const id of ids) { + bulkUpdateRequest.push( + { + update: { + _index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, + _id: id, + }, }, - }, - { - script: painlessScript, - } - ); - } + { + script: painlessScript, + } + ); + } - try { - const body = await esClient.updateByQuery({ - index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, - refresh: false, - body: { - script: painlessScript, - query: { - bool: { - filter: { terms: { _id: ids } }, + try { + const body = await esClient.updateByQuery({ + index: `${DEFAULT_ALERTS_INDEX}-${spaceId}`, + refresh: false, + body: { + script: painlessScript, + query: { + bool: { + filter: { terms: { _id: ids } }, + }, }, }, - }, - }); - return response.ok({ body }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); + }); + return response.ok({ body }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts index 077aa561ef82d..271e6e7d27749 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route.ts @@ -22,47 +22,52 @@ export const telemetryDetectionRulesPreviewRoute = ( telemetryReceiver: ITelemetryReceiver, telemetrySender: ITelemetryEventsSender ) => { - router.get( - { + router.versioned + .get({ path: SECURITY_TELEMETRY_URL, - validate: false, + access: 'internal', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const detectionRules = await getDetectionRulesPreview({ - logger, - telemetryReceiver, - telemetrySender, - }); + }) + .addVersion( + { + version: '1', + validate: false, + }, + async (context, request, response) => { + const detectionRules = await getDetectionRulesPreview({ + logger, + telemetryReceiver, + telemetrySender, + }); - const securityLists = await getSecurityListsPreview({ - logger, - telemetryReceiver, - telemetrySender, - }); + const securityLists = await getSecurityListsPreview({ + logger, + telemetryReceiver, + telemetrySender, + }); - const endpoints = await getEndpointPreview({ - logger, - telemetryReceiver, - telemetrySender, - }); + const endpoints = await getEndpointPreview({ + logger, + telemetryReceiver, + telemetrySender, + }); - const diagnostics = await getDiagnosticsPreview({ - logger, - telemetryReceiver, - telemetrySender, - }); + const diagnostics = await getDiagnosticsPreview({ + logger, + telemetryReceiver, + telemetrySender, + }); - return response.ok({ - body: { - detection_rules: detectionRules, - security_lists: securityLists, - endpoints, - diagnostics, - }, - }); - } - ); + return response.ok({ + body: { + detection_rules: detectionRules, + security_lists: securityLists, + endpoints, + diagnostics, + }, + }); + } + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts index 5b20d410db8ce..df2c4bb3366b9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts @@ -17,6 +17,7 @@ import { legacyReadNotifications } from '../../logic/notifications/legacy_read_n import type { LegacyRuleNotificationAlertTypeParams } from '../../logic/notifications/legacy_types'; // eslint-disable-next-line no-restricted-imports import { legacyCreateNotifications } from '../../logic/notifications/legacy_create_notifications'; +import { UPDATE_OR_CREATE_LEGACY_ACTIONS } from '../../../../../../common/constants'; /** * Given an "alert_id" and a valid "action_id" this will create a legacy notification. This is for testing @@ -29,86 +30,93 @@ export const legacyCreateLegacyNotificationRoute = ( router: SecuritySolutionPluginRouter, logger: Logger ): void => { - router.post( - { - path: '/internal/api/detection/legacy/notifications', - validate: { - query: schema.object({ alert_id: schema.string() }), - body: schema.object({ - name: schema.string(), - interval: schema.string(), - actions: schema.arrayOf( - schema.object({ - id: schema.string(), - group: schema.string(), - params: schema.object({ - message: schema.string(), - }), - actionTypeId: schema.string(), - }) - ), - }), - }, + router.versioned + .post({ + path: UPDATE_OR_CREATE_LEGACY_ACTIONS, + access: 'internal', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const rulesClient = (await context.alerting).getRulesClient(); - const savedObjectsClient = (await context.core).savedObjects.client; - const { alert_id: ruleAlertId } = request.query; - const { actions, interval, name } = request.body; - try { - // This is to ensure it exists before continuing. - await rulesClient.get({ id: ruleAlertId }); - const notification = await legacyReadNotifications({ - rulesClient, - id: undefined, - ruleAlertId, - }); - if (notification != null) { - await rulesClient.update({ - id: notification.id, - data: { - tags: [], - name, - schedule: { - interval, + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: schema.object({ alert_id: schema.string() }), + body: schema.object({ + name: schema.string(), + interval: schema.string(), + actions: schema.arrayOf( + schema.object({ + id: schema.string(), + group: schema.string(), + params: schema.object({ + message: schema.string(), + }), + actionTypeId: schema.string(), + }) + ), + }), + }, + }, + }, + async (context, request, response) => { + const rulesClient = (await context.alerting).getRulesClient(); + const savedObjectsClient = (await context.core).savedObjects.client; + const { alert_id: ruleAlertId } = request.query; + const { actions, interval, name } = request.body; + try { + // This is to ensure it exists before continuing. + await rulesClient.get({ id: ruleAlertId }); + const notification = await legacyReadNotifications({ + rulesClient, + id: undefined, + ruleAlertId, + }); + if (notification != null) { + await rulesClient.update({ + id: notification.id, + data: { + tags: [], + name, + schedule: { + interval, + }, + actions, + params: { + ruleAlertId, + }, + throttle: null, + notifyWhen: null, }, + }); + } else { + await legacyCreateNotifications({ + rulesClient, actions, - params: { - ruleAlertId, - }, - throttle: null, - notifyWhen: null, - }, - }); - } else { - await legacyCreateNotifications({ - rulesClient, - actions, - enabled: true, + enabled: true, + ruleAlertId, + interval, + name, + }); + } + await legacyUpdateOrCreateRuleActionsSavedObject({ ruleAlertId, - interval, - name, + savedObjectsClient, + actions, + throttle: interval, + logger, }); + } catch (error) { + const message = error instanceof Error ? error.message : 'unknown'; + return response.badRequest({ body: message }); } - await legacyUpdateOrCreateRuleActionsSavedObject({ - ruleAlertId, - savedObjectsClient, - actions, - throttle: interval, - logger, + return response.ok({ + body: { + ok: 'acknowledged', + }, }); - } catch (error) { - const message = error instanceof Error ? error.message : 'unknown'; - return response.badRequest({ body: message }); } - return response.ok({ - body: { - ok: 'acknowledged', - }, - }); - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts index e962b48485020..5f5b29abdf38e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts @@ -47,71 +47,83 @@ import { buildSiemResponse } from '../../../routes/utils'; import { buildRouteValidation } from '../../../../../utils/build_validation/route_validation'; export const createRuleExceptionsRoute = (router: SecuritySolutionPluginRouter) => { - router.post( - { + router.versioned + .post({ path: CREATE_RULE_EXCEPTIONS_URL, - validate: { - params: buildRouteValidation< - typeof CreateRuleExceptionsRequestParams, - CreateRuleExceptionsRequestParamsDecoded - >(CreateRuleExceptionsRequestParams), - body: buildRouteValidation< - typeof CreateRuleExceptionsRequestBody, - CreateRuleExceptionsRequestBodyDecoded - >(CreateRuleExceptionsRequestBody), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - - try { - const ctx = await context.resolve([ - 'core', - 'securitySolution', - 'alerting', - 'licensing', - 'lists', - ]); - const rulesClient = ctx.alerting.getRulesClient(); - const listsClient = ctx.securitySolution.getExceptionListClient(); + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + params: buildRouteValidation< + typeof CreateRuleExceptionsRequestParams, + CreateRuleExceptionsRequestParamsDecoded + >(CreateRuleExceptionsRequestParams), + body: buildRouteValidation< + typeof CreateRuleExceptionsRequestBody, + CreateRuleExceptionsRequestBodyDecoded + >(CreateRuleExceptionsRequestBody), + }, + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); - const { items } = request.body; - const { id: ruleId } = request.params; + try { + const ctx = await context.resolve([ + 'core', + 'securitySolution', + 'alerting', + 'licensing', + 'lists', + ]); + const rulesClient = ctx.alerting.getRulesClient(); + const listsClient = ctx.securitySolution.getExceptionListClient(); - // Check that the rule they're trying to add an exception list to exists - const rule = await readRules({ - rulesClient, - ruleId: undefined, - id: ruleId, - }); + const { items } = request.body; + const { id: ruleId } = request.params; - if (rule == null) { - return siemResponse.error({ - statusCode: 500, - body: `Unable to add exception to rule - rule with id:"${ruleId}" not found`, + // Check that the rule they're trying to add an exception list to exists + const rule = await readRules({ + rulesClient, + ruleId: undefined, + id: ruleId, }); - } - const createdItems = await createRuleExceptions({ items, rule, listsClient, rulesClient }); + if (rule == null) { + return siemResponse.error({ + statusCode: 500, + body: `Unable to add exception to rule - rule with id:"${ruleId}" not found`, + }); + } - const [validated, errors] = validate(createdItems, t.array(exceptionListItemSchema)); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); + const createdItems = await createRuleExceptions({ + items, + rule, + listsClient, + rulesClient, + }); + + const [validated, errors] = validate(createdItems, t.array(exceptionListItemSchema)); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); } - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); } - } - ); + ); }; export const createRuleExceptions = async ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts index 05e8a58d3e972..be7fbc83aa1ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts @@ -27,105 +27,115 @@ import { enrichFilterWithRuleTypeMapping } from '../../../rule_management/logic/ import type { RuleParams } from '../../../rule_schema'; export const findRuleExceptionReferencesRoute = (router: SecuritySolutionPluginRouter) => { - router.get( - { + router.versioned + .get({ path: DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL, - validate: { - query: buildRouteValidation< - typeof findExceptionReferencesOnRuleSchema, - FindExceptionReferencesOnRuleSchemaDecoded - >(findExceptionReferencesOnRuleSchema), - }, + access: 'internal', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: buildRouteValidation< + typeof findExceptionReferencesOnRuleSchema, + FindExceptionReferencesOnRuleSchemaDecoded + >(findExceptionReferencesOnRuleSchema), + }, + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); - try { - const { ids, namespace_types: namespaceTypes, list_ids: listIds } = request.query; + try { + const { ids, namespace_types: namespaceTypes, list_ids: listIds } = request.query; - const ctx = await context.resolve(['core', 'securitySolution', 'alerting']); - const rulesClient = ctx.alerting.getRulesClient(); - const listsClient = ctx.securitySolution.getExceptionListClient(); + const ctx = await context.resolve(['core', 'securitySolution', 'alerting']); + const rulesClient = ctx.alerting.getRulesClient(); + const listsClient = ctx.securitySolution.getExceptionListClient(); - if ( - ids != null && - listIds != null && - (ids.length !== namespaceTypes.length || ids.length !== listIds.length) - ) { - return siemResponse.error({ - body: `"ids", "list_ids" and "namespace_types" need to have the same comma separated number of values. Expected "ids" length: ${ids.length} to equal "namespace_types" length: ${namespaceTypes.length} and "list_ids" length: ${listIds.length}.`, - statusCode: 400, - }); - } + if ( + ids != null && + listIds != null && + (ids.length !== namespaceTypes.length || ids.length !== listIds.length) + ) { + return siemResponse.error({ + body: `"ids", "list_ids" and "namespace_types" need to have the same comma separated number of values. Expected "ids" length: ${ids.length} to equal "namespace_types" length: ${namespaceTypes.length} and "list_ids" length: ${listIds.length}.`, + statusCode: 400, + }); + } - const fetchExact = ids != null && listIds != null; - const foundExceptionLists = await listsClient?.findExceptionList({ - filter: fetchExact - ? `(${listIds - .map( - (listId, index) => - `${getSavedObjectType({ - namespaceType: namespaceTypes[index], - })}.attributes.list_id:${listId}` - ) - .join(' OR ')})` - : undefined, - namespaceType: namespaceTypes, - page: 1, - perPage: 10000, - sortField: undefined, - sortOrder: undefined, - }); + const fetchExact = ids != null && listIds != null; + const foundExceptionLists = await listsClient?.findExceptionList({ + filter: fetchExact + ? `(${listIds + .map( + (listId, index) => + `${getSavedObjectType({ + namespaceType: namespaceTypes[index], + })}.attributes.list_id:${listId}` + ) + .join(' OR ')})` + : undefined, + namespaceType: namespaceTypes, + page: 1, + perPage: 10000, + sortField: undefined, + sortOrder: undefined, + }); - if (foundExceptionLists == null) { - return response.ok({ body: { references: [] } }); - } - const references: RuleReferencesSchema[] = await Promise.all( - foundExceptionLists.data.map(async (list, index) => { - const foundRules = await rulesClient.find({ - options: { - perPage: 10000, - filter: enrichFilterWithRuleTypeMapping(null), - hasReference: { - id: list.id, - type: getSavedObjectType({ namespaceType: list.namespace_type }), + if (foundExceptionLists == null) { + return response.ok({ body: { references: [] } }); + } + const references: RuleReferencesSchema[] = await Promise.all( + foundExceptionLists.data.map(async (list, index) => { + const foundRules = await rulesClient.find({ + options: { + perPage: 10000, + filter: enrichFilterWithRuleTypeMapping(null), + hasReference: { + id: list.id, + type: getSavedObjectType({ namespaceType: list.namespace_type }), + }, }, - }, - }); + }); - const ruleData = foundRules.data.map(({ name, id, params }) => ({ - name, - id, - rule_id: params.ruleId, - exception_lists: params.exceptionsList, - })); + const ruleData = foundRules.data.map(({ name, id, params }) => ({ + name, + id, + rule_id: params.ruleId, + exception_lists: params.exceptionsList, + })); - return { - [list.list_id]: { - ...list, - referenced_rules: ruleData, - }, - }; - }) - ); + return { + [list.list_id]: { + ...list, + referenced_rules: ruleData, + }, + }; + }) + ); - const [validated, errors] = validate({ references }, rulesReferencedByExceptionListsSchema); + const [validated, errors] = validate( + { references }, + rulesReferencedByExceptionListsSchema + ); - if (errors != null) { - return siemResponse.error({ statusCode: 500, body: errors }); - } else { - return response.ok({ body: validated ?? { references: [] } }); + if (errors != null) { + return siemResponse.error({ statusCode: 500, body: errors }); + } else { + return response.ok({ body: validated ?? { references: [] } }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); } - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts index 10c8cc065b996..1049cbb5c89e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts @@ -54,8 +54,11 @@ export const getRuleExecutionEventsRoute = (router: SecuritySolutionPluginRouter const executionLog = ctx.securitySolution.getRuleExecutionLog(); const executionEventsResponse = await executionLog.getExecutionEvents({ ruleId: params.ruleId, + searchTerm: query.search_term, eventTypes: query.event_types, logLevels: query.log_levels, + dateStart: query.date_start, + dateEnd: query.date_end, sortOrder: query.sort_order, page: query.page, perPage: query.per_page, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts index 4e66e93f54507..06e631dece638 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts @@ -10,6 +10,7 @@ export const TIMESTAMP = `@timestamp` as const; +export const MESSAGE = 'message' as const; export const EVENT_PROVIDER = 'event.provider' as const; export const EVENT_ACTION = 'event.action' as const; export const EVENT_SEQUENCE = 'event.sequence' as const; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts index 436ddd91bd595..6f3c867ee3ed4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts @@ -39,11 +39,20 @@ export interface GetExecutionEventsArgs { /** Saved object id of the rule (`rule.id`). */ ruleId: RuleObjectId; - /** Include events of the specified types. If empty, all types of events will be included. */ - eventTypes: RuleExecutionEventType[]; + /** Include events of matching the search term. If omitted, all events will be included. */ + searchTerm?: string; - /** Include events having these log levels. If empty, events of all levels will be included. */ - logLevels: LogLevel[]; + /** Include events of the specified types. If omitted, all types of events will be included. */ + eventTypes?: RuleExecutionEventType[]; + + /** Include events having these log levels. If omitted, events of all levels will be included. */ + logLevels?: LogLevel[]; + + /** Include events recorded starting from the specified moment. If omitted, all events will be included. */ + dateStart?: string; + + /** Include events recorded till the specified moment. If omitted, all events will be included. */ + dateEnd?: string; /** What order to sort by (e.g. `asc` or `desc`). */ sortOrder: SortOrder; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts index 03871248ff5be..fae8b6cfe9f5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts @@ -9,6 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEventLogClient, IValidatedEvent } from '@kbn/event-log-plugin/server'; import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules'; +import { prepareKQLStringParam } from '../../../../../../../common/utils/kql'; import type { GetRuleExecutionEventsResponse, GetRuleExecutionResultsResponse, @@ -53,20 +54,30 @@ export const createEventLogReader = (eventLog: IEventLogClient): IEventLogReader async getExecutionEvents( args: GetExecutionEventsArgs ): Promise { - const { ruleId, eventTypes, logLevels, sortOrder, page, perPage } = args; + const { + ruleId, + searchTerm, + eventTypes, + logLevels, + dateStart, + dateEnd, + sortOrder, + page, + perPage, + } = args; const soType = RULE_SAVED_OBJECT_TYPE; const soIds = [ruleId]; - // TODO: include Framework events - const kqlFilter = kqlAnd([ - `${f.EVENT_PROVIDER}:${RULE_EXECUTION_LOG_PROVIDER}`, - eventTypes.length > 0 ? `${f.EVENT_ACTION}:(${kqlOr(eventTypes)})` : '', - logLevels.length > 0 ? `${f.LOG_LEVEL}:(${kqlOr(logLevels)})` : '', - ]); - const findResult = await withSecuritySpan('findEventsBySavedObjectIds', () => { return eventLog.findEventsBySavedObjectIds(soType, soIds, { - filter: kqlFilter, + // TODO: include Framework events + filter: buildEventLogKqlFilter({ + searchTerm, + eventTypes, + logLevels, + dateStart, + dateEnd, + }), sort: [ { sort_field: f.TIMESTAMP, sort_order: sortOrder }, { sort_field: f.EVENT_SEQUENCE, sort_order: sortOrder }, @@ -173,9 +184,10 @@ const normalizeEvent = (rawEvent: IValidatedEvent): RuleExecutionEvent => { const sequence = normalizeEventSequence(rawEvent); const level = normalizeLogLevel(rawEvent); const type = normalizeEventType(rawEvent); + const executionId = normalizeExecutionId(rawEvent); const message = normalizeEventMessage(rawEvent, type); - return { timestamp, sequence, level, type, message }; + return { timestamp, sequence, level, type, message, execution_id: executionId }; }; type RawEvent = NonNullable; @@ -230,9 +242,64 @@ const normalizeEventMessage = (event: RawEvent, type: RuleExecutionEventType): s } if (type === RuleExecutionEventType['execution-metrics']) { - return ''; + invariant( + event.kibana?.alert?.rule?.execution?.metrics, + 'Required "kibana.alert.rule.execution.metrics" field is not found' + ); + + return JSON.stringify(event.kibana.alert.rule.execution.metrics); } assertUnreachable(type); return ''; }; + +const normalizeExecutionId = (event: RawEvent): string => { + invariant( + event.kibana?.alert?.rule?.execution?.uuid, + 'Required "kibana.alert.rule.execution.uuid" field is not found' + ); + + return event.kibana.alert.rule.execution.uuid; +}; + +const buildEventLogKqlFilter = ({ + searchTerm, + eventTypes, + logLevels, + dateStart, + dateEnd, +}: Pick< + GetExecutionEventsArgs, + 'searchTerm' | 'eventTypes' | 'logLevels' | 'dateStart' | 'dateEnd' +>) => { + const filters = [`${f.EVENT_PROVIDER}:${RULE_EXECUTION_LOG_PROVIDER}`]; + + if (searchTerm?.length) { + filters.push(`${f.MESSAGE}:${prepareKQLStringParam(searchTerm)}`); + } + + if (eventTypes?.length) { + filters.push(`${f.EVENT_ACTION}:(${kqlOr(eventTypes)})`); + } + + if (logLevels?.length) { + filters.push(`${f.LOG_LEVEL}:(${kqlOr(logLevels)})`); + } + + const dateRangeFilter: string[] = []; + + if (dateStart) { + dateRangeFilter.push(`${f.TIMESTAMP} >= ${prepareKQLStringParam(dateStart)}`); + } + + if (dateEnd) { + dateRangeFilter.push(`${f.TIMESTAMP} <= ${prepareKQLStringParam(dateEnd)}`); + } + + if (dateRangeFilter.length) { + filters.push(kqlAnd(dateRangeFilter)); + } + + return kqlAnd(filters); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts index dd283b57d9d6b..21cac11b558ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts @@ -78,407 +78,413 @@ export const previewRulesRoute = async ( getStartServices: StartServicesAccessor, logger: Logger ) => { - router.post( - { + router.versioned + .post({ path: DETECTION_ENGINE_RULES_PREVIEW, - validate: { - body: buildRouteValidation(previewRulesSchema), - }, + access: 'public', options: { tags: ['access:securitySolution', routeLimitedConcurrencyTag(MAX_ROUTE_CONCURRENCY)], }, - }, - async (context, request, response): Promise> => { - const siemResponse = buildSiemResponse(response); - const validationErrors = validateCreateRuleProps(request.body); - const coreContext = await context.core; - if (validationErrors.length) { - return siemResponse.error({ statusCode: 400, body: validationErrors }); - } - try { - const [, { data, security: securityService, share, dataViews }] = await getStartServices(); - const searchSourceClient = await data.search.searchSource.asScoped(request); - const savedObjectsClient = coreContext.savedObjects.client; - const siemClient = (await context.securitySolution).getAppClient(); - - const timeframeEnd = request.body.timeframeEnd; - let invocationCount = request.body.invocationCount; - if (invocationCount < 1) { - return response.ok({ - body: { - logs: [{ errors: ['Invalid invocation count'], warnings: [], duration: 0 }], - previewId: undefined, - isAborted: undefined, - }, - }); + }) + .addVersion( + { + version: '2023-10-31', + validate: { request: { body: buildRouteValidation(previewRulesSchema) } }, + }, + async (context, request, response): Promise> => { + const siemResponse = buildSiemResponse(response); + const validationErrors = validateCreateRuleProps(request.body); + const coreContext = await context.core; + if (validationErrors.length) { + return siemResponse.error({ statusCode: 400, body: validationErrors }); } - - const internalRule = convertCreateAPIToInternalSchema(request.body); - const previewRuleParams = internalRule.params; - - const mlAuthz = buildMlAuthz({ - license: (await context.licensing).license, - ml, - request, - savedObjectsClient, - }); - throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); - - const listsContext = await context.lists; - await listsContext?.getExceptionListClient().createEndpointList(); - - const spaceId = siemClient.getSpaceId(); - const previewId = uuidv4(); - const username = security?.authc.getCurrentUser(request)?.username; - const loggedStatusChanges: Array = []; - const previewRuleExecutionLogger = createPreviewRuleExecutionLogger(loggedStatusChanges); - const runState: Record = {}; - const logs: RulePreviewLogs[] = []; - let isAborted = false; - - const { hasAllRequested } = await securityService.authz - .checkPrivilegesWithRequest(request) - .atSpace(spaceId, { - elasticsearch: { - index: { - [`${DEFAULT_PREVIEW_INDEX}-${spaceId}`]: ['read'], - [`.internal${DEFAULT_PREVIEW_INDEX}-${spaceId}-*`]: ['read'], + try { + const [, { data, security: securityService, share, dataViews }] = + await getStartServices(); + const searchSourceClient = await data.search.searchSource.asScoped(request); + const savedObjectsClient = coreContext.savedObjects.client; + const siemClient = (await context.securitySolution).getAppClient(); + + const timeframeEnd = request.body.timeframeEnd; + let invocationCount = request.body.invocationCount; + if (invocationCount < 1) { + return response.ok({ + body: { + logs: [{ errors: ['Invalid invocation count'], warnings: [], duration: 0 }], + previewId: undefined, + isAborted: undefined, }, - cluster: [], - }, - }); - - if (!hasAllRequested) { - return response.ok({ - body: { - logs: [ - { - errors: [ - 'Missing "read" privileges for the ".preview.alerts-security.alerts" or ".internal.preview.alerts-security.alerts" indices. Without these privileges you cannot use the Rule Preview feature.', - ], - warnings: [], - duration: 0, - }, - ], - previewId: undefined, - isAborted: undefined, - }, - }); - } - - const previewRuleTypeWrapper = createSecurityRuleTypeWrapper({ - ...securityRuleTypeOptions, - ruleDataClient: previewRuleDataClient, - ruleExecutionLoggerFactory: previewRuleExecutionLogger.factory, - isPreview: true, - }); - - const runExecutors = async < - TParams extends RuleParams, - TState extends RuleTypeState, - TInstanceState extends AlertInstanceState, - TInstanceContext extends AlertInstanceContext, - TActionGroupIds extends string = '' - >( - executor: ExecutorType< - TParams, - TState, - TInstanceState, - TInstanceContext, - TActionGroupIds - >, - ruleTypeId: string, - ruleTypeName: string, - params: TParams, - shouldWriteAlerts: () => boolean, - alertFactory: { - create: ( - id: string - ) => Pick< - Alert, - | 'getState' - | 'replaceState' - | 'scheduleActions' - | 'setContext' - | 'getContext' - | 'hasContext' - | 'getUuid' - | 'getStart' - >; - alertLimit: { - getValue: () => number; - setLimitReached: () => void; - }; - done: () => { getRecoveredAlerts: () => [] }; + }); } - ) => { - let statePreview = runState as TState; - - const abortController = new AbortController(); - setTimeout(() => { - abortController.abort(); - isAborted = true; - }, PREVIEW_TIMEOUT_SECONDS * 1000); - - const startedAt = moment(timeframeEnd); - const parsedDuration = parseDuration(internalRule.schedule.interval) ?? 0; - startedAt.subtract(moment.duration(parsedDuration * (invocationCount - 1))); - - let previousStartedAt = null; - - const rule = { - ...internalRule, - id: previewId, - createdAt: new Date(), - createdBy: username ?? 'preview-created-by', - producer: 'preview-producer', - revision: 0, - ruleTypeId, - ruleTypeName, - updatedAt: new Date(), - updatedBy: username ?? 'preview-updated-by', - muteAll: false, - snoozeSchedule: [], - }; - let invocationStartTime; + const internalRule = convertCreateAPIToInternalSchema(request.body); + const previewRuleParams = internalRule.params; - const dataViewsService = await dataViews.dataViewsServiceFactory( + const mlAuthz = buildMlAuthz({ + license: (await context.licensing).license, + ml, + request, savedObjectsClient, - coreContext.elasticsearch.client.asInternalUser - ); + }); + throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); + + const listsContext = await context.lists; + await listsContext?.getExceptionListClient().createEndpointList(); + + const spaceId = siemClient.getSpaceId(); + const previewId = uuidv4(); + const username = security?.authc.getCurrentUser(request)?.username; + const loggedStatusChanges: Array = []; + const previewRuleExecutionLogger = createPreviewRuleExecutionLogger(loggedStatusChanges); + const runState: Record = {}; + const logs: RulePreviewLogs[] = []; + let isAborted = false; + + const { hasAllRequested } = await securityService.authz + .checkPrivilegesWithRequest(request) + .atSpace(spaceId, { + elasticsearch: { + index: { + [`${DEFAULT_PREVIEW_INDEX}-${spaceId}`]: ['read'], + [`.internal${DEFAULT_PREVIEW_INDEX}-${spaceId}-*`]: ['read'], + }, + cluster: [], + }, + }); - while (invocationCount > 0 && !isAborted) { - invocationStartTime = moment(); - - ({ state: statePreview } = (await executor({ - executionId: uuidv4(), - params, - previousStartedAt, - rule, - services: { - shouldWriteAlerts, - shouldStopExecution: () => false, - alertsClient: null, - alertFactory, - savedObjectsClient: coreContext.savedObjects.client, - scopedClusterClient: wrapScopedClusterClient({ - abortController, - scopedClusterClient: coreContext.elasticsearch.client, - }), - searchSourceClient: wrapSearchSourceClient({ - abortController, - searchSourceClient, - }), - uiSettingsClient: coreContext.uiSettings.client, - dataViews: dataViewsService, - share, + if (!hasAllRequested) { + return response.ok({ + body: { + logs: [ + { + errors: [ + 'Missing "read" privileges for the ".preview.alerts-security.alerts" or ".internal.preview.alerts-security.alerts" indices. Without these privileges you cannot use the Rule Preview feature.', + ], + warnings: [], + duration: 0, + }, + ], + previewId: undefined, + isAborted: undefined, }, - spaceId, - startedAt: startedAt.toDate(), - state: statePreview, - logger, - flappingSettings: DISABLE_FLAPPING_SETTINGS, - })) as { state: TState }); - - const errors = loggedStatusChanges - .filter((item) => item.newStatus === RuleExecutionStatus.failed) - .map((item) => item.message ?? 'Unknown Error'); - - const warnings = loggedStatusChanges - .filter((item) => item.newStatus === RuleExecutionStatus['partial failure']) - .map((item) => item.message ?? 'Unknown Warning'); - - logs.push({ - errors, - warnings, - startedAt: startedAt.toDate().toISOString(), - duration: moment().diff(invocationStartTime, 'milliseconds'), }); + } - loggedStatusChanges.length = 0; + const previewRuleTypeWrapper = createSecurityRuleTypeWrapper({ + ...securityRuleTypeOptions, + ruleDataClient: previewRuleDataClient, + ruleExecutionLoggerFactory: previewRuleExecutionLogger.factory, + isPreview: true, + }); - if (errors.length) { - break; + const runExecutors = async < + TParams extends RuleParams, + TState extends RuleTypeState, + TInstanceState extends AlertInstanceState, + TInstanceContext extends AlertInstanceContext, + TActionGroupIds extends string = '' + >( + executor: ExecutorType< + TParams, + TState, + TInstanceState, + TInstanceContext, + TActionGroupIds + >, + ruleTypeId: string, + ruleTypeName: string, + params: TParams, + shouldWriteAlerts: () => boolean, + alertFactory: { + create: ( + id: string + ) => Pick< + Alert, + | 'getState' + | 'replaceState' + | 'scheduleActions' + | 'setContext' + | 'getContext' + | 'hasContext' + | 'getUuid' + | 'getStart' + >; + alertLimit: { + getValue: () => number; + setLimitReached: () => void; + }; + done: () => { getRecoveredAlerts: () => [] }; } + ) => { + let statePreview = runState as TState; + + const abortController = new AbortController(); + setTimeout(() => { + abortController.abort(); + isAborted = true; + }, PREVIEW_TIMEOUT_SECONDS * 1000); + + const startedAt = moment(timeframeEnd); + const parsedDuration = parseDuration(internalRule.schedule.interval) ?? 0; + startedAt.subtract(moment.duration(parsedDuration * (invocationCount - 1))); + + let previousStartedAt = null; + + const rule = { + ...internalRule, + id: previewId, + createdAt: new Date(), + createdBy: username ?? 'preview-created-by', + producer: 'preview-producer', + revision: 0, + ruleTypeId, + ruleTypeName, + updatedAt: new Date(), + updatedBy: username ?? 'preview-updated-by', + muteAll: false, + snoozeSchedule: [], + }; - previousStartedAt = startedAt.toDate(); - startedAt.add(parseInterval(internalRule.schedule.interval)); - invocationCount--; - } - }; - - switch (previewRuleParams.type) { - case 'query': - const queryAlertType = previewRuleTypeWrapper( - createQueryAlertType({ - ...ruleOptions, - id: QUERY_RULE_TYPE_ID, - name: 'Custom Query Rule', - }) - ); - await runExecutors( - queryAlertType.executor, - queryAlertType.id, - queryAlertType.name, - previewRuleParams, - () => true, - { - create: alertInstanceFactoryStub, - alertLimit: { - getValue: () => 1000, - setLimitReached: () => {}, - }, - done: () => ({ getRecoveredAlerts: () => [] }), - } - ); - break; - case 'saved_query': - const savedQueryAlertType = previewRuleTypeWrapper( - createQueryAlertType({ - ...ruleOptions, - id: SAVED_QUERY_RULE_TYPE_ID, - name: 'Saved Query Rule', - }) - ); - await runExecutors( - savedQueryAlertType.executor, - savedQueryAlertType.id, - savedQueryAlertType.name, - previewRuleParams, - () => true, - { - create: alertInstanceFactoryStub, - alertLimit: { - getValue: () => 1000, - setLimitReached: () => {}, - }, - done: () => ({ getRecoveredAlerts: () => [] }), - } - ); - break; - case 'threshold': - const thresholdAlertType = previewRuleTypeWrapper( - createThresholdAlertType(ruleOptions) - ); - await runExecutors( - thresholdAlertType.executor, - thresholdAlertType.id, - thresholdAlertType.name, - previewRuleParams, - () => true, - { - create: alertInstanceFactoryStub, - alertLimit: { - getValue: () => 1000, - setLimitReached: () => {}, - }, - done: () => ({ getRecoveredAlerts: () => [] }), - } - ); - break; - case 'threat_match': - const threatMatchAlertType = previewRuleTypeWrapper( - createIndicatorMatchAlertType(ruleOptions) - ); - await runExecutors( - threatMatchAlertType.executor, - threatMatchAlertType.id, - threatMatchAlertType.name, - previewRuleParams, - () => true, - { - create: alertInstanceFactoryStub, - alertLimit: { - getValue: () => 1000, - setLimitReached: () => {}, - }, - done: () => ({ getRecoveredAlerts: () => [] }), - } - ); - break; - case 'eql': - const eqlAlertType = previewRuleTypeWrapper(createEqlAlertType(ruleOptions)); - await runExecutors( - eqlAlertType.executor, - eqlAlertType.id, - eqlAlertType.name, - previewRuleParams, - () => true, - { - create: alertInstanceFactoryStub, - alertLimit: { - getValue: () => 1000, - setLimitReached: () => {}, - }, - done: () => ({ getRecoveredAlerts: () => [] }), - } - ); - break; - case 'machine_learning': - const mlAlertType = previewRuleTypeWrapper(createMlAlertType(ruleOptions)); - await runExecutors( - mlAlertType.executor, - mlAlertType.id, - mlAlertType.name, - previewRuleParams, - () => true, - { - create: alertInstanceFactoryStub, - alertLimit: { - getValue: () => 1000, - setLimitReached: () => {}, - }, - done: () => ({ getRecoveredAlerts: () => [] }), - } + let invocationStartTime; + + const dataViewsService = await dataViews.dataViewsServiceFactory( + savedObjectsClient, + coreContext.elasticsearch.client.asInternalUser ); - break; - case 'new_terms': - const newTermsAlertType = previewRuleTypeWrapper(createNewTermsAlertType(ruleOptions)); - await runExecutors( - newTermsAlertType.executor, - newTermsAlertType.id, - newTermsAlertType.name, - previewRuleParams, - () => true, - { - create: alertInstanceFactoryStub, - alertLimit: { - getValue: () => 1000, - setLimitReached: () => {}, + + while (invocationCount > 0 && !isAborted) { + invocationStartTime = moment(); + + ({ state: statePreview } = (await executor({ + executionId: uuidv4(), + params, + previousStartedAt, + rule, + services: { + shouldWriteAlerts, + shouldStopExecution: () => false, + alertsClient: null, + alertFactory, + savedObjectsClient: coreContext.savedObjects.client, + scopedClusterClient: wrapScopedClusterClient({ + abortController, + scopedClusterClient: coreContext.elasticsearch.client, + }), + searchSourceClient: wrapSearchSourceClient({ + abortController, + searchSourceClient, + }), + uiSettingsClient: coreContext.uiSettings.client, + dataViews: dataViewsService, + share, }, - done: () => ({ getRecoveredAlerts: () => [] }), + spaceId, + startedAt: startedAt.toDate(), + state: statePreview, + logger, + flappingSettings: DISABLE_FLAPPING_SETTINGS, + })) as { state: TState }); + + const errors = loggedStatusChanges + .filter((item) => item.newStatus === RuleExecutionStatus.failed) + .map((item) => item.message ?? 'Unknown Error'); + + const warnings = loggedStatusChanges + .filter((item) => item.newStatus === RuleExecutionStatus['partial failure']) + .map((item) => item.message ?? 'Unknown Warning'); + + logs.push({ + errors, + warnings, + startedAt: startedAt.toDate().toISOString(), + duration: moment().diff(invocationStartTime, 'milliseconds'), + }); + + loggedStatusChanges.length = 0; + + if (errors.length) { + break; } - ); - break; - default: - assertUnreachable(previewRuleParams); - } - // Refreshes alias to ensure index is able to be read before returning - await coreContext.elasticsearch.client.asInternalUser.indices.refresh( - { - index: previewRuleDataClient.indexNameWithNamespace(spaceId), - }, - { ignore: [404] } - ); - - return response.ok({ - body: { - previewId, - logs, - isAborted, - }, - }); - } catch (err) { - const error = transformError(err as Error); - return siemResponse.error({ - body: { - errors: [error.message], - }, - statusCode: error.statusCode, - }); + previousStartedAt = startedAt.toDate(); + startedAt.add(parseInterval(internalRule.schedule.interval)); + invocationCount--; + } + }; + + switch (previewRuleParams.type) { + case 'query': + const queryAlertType = previewRuleTypeWrapper( + createQueryAlertType({ + ...ruleOptions, + id: QUERY_RULE_TYPE_ID, + name: 'Custom Query Rule', + }) + ); + await runExecutors( + queryAlertType.executor, + queryAlertType.id, + queryAlertType.name, + previewRuleParams, + () => true, + { + create: alertInstanceFactoryStub, + alertLimit: { + getValue: () => 1000, + setLimitReached: () => {}, + }, + done: () => ({ getRecoveredAlerts: () => [] }), + } + ); + break; + case 'saved_query': + const savedQueryAlertType = previewRuleTypeWrapper( + createQueryAlertType({ + ...ruleOptions, + id: SAVED_QUERY_RULE_TYPE_ID, + name: 'Saved Query Rule', + }) + ); + await runExecutors( + savedQueryAlertType.executor, + savedQueryAlertType.id, + savedQueryAlertType.name, + previewRuleParams, + () => true, + { + create: alertInstanceFactoryStub, + alertLimit: { + getValue: () => 1000, + setLimitReached: () => {}, + }, + done: () => ({ getRecoveredAlerts: () => [] }), + } + ); + break; + case 'threshold': + const thresholdAlertType = previewRuleTypeWrapper( + createThresholdAlertType(ruleOptions) + ); + await runExecutors( + thresholdAlertType.executor, + thresholdAlertType.id, + thresholdAlertType.name, + previewRuleParams, + () => true, + { + create: alertInstanceFactoryStub, + alertLimit: { + getValue: () => 1000, + setLimitReached: () => {}, + }, + done: () => ({ getRecoveredAlerts: () => [] }), + } + ); + break; + case 'threat_match': + const threatMatchAlertType = previewRuleTypeWrapper( + createIndicatorMatchAlertType(ruleOptions) + ); + await runExecutors( + threatMatchAlertType.executor, + threatMatchAlertType.id, + threatMatchAlertType.name, + previewRuleParams, + () => true, + { + create: alertInstanceFactoryStub, + alertLimit: { + getValue: () => 1000, + setLimitReached: () => {}, + }, + done: () => ({ getRecoveredAlerts: () => [] }), + } + ); + break; + case 'eql': + const eqlAlertType = previewRuleTypeWrapper(createEqlAlertType(ruleOptions)); + await runExecutors( + eqlAlertType.executor, + eqlAlertType.id, + eqlAlertType.name, + previewRuleParams, + () => true, + { + create: alertInstanceFactoryStub, + alertLimit: { + getValue: () => 1000, + setLimitReached: () => {}, + }, + done: () => ({ getRecoveredAlerts: () => [] }), + } + ); + break; + case 'machine_learning': + const mlAlertType = previewRuleTypeWrapper(createMlAlertType(ruleOptions)); + await runExecutors( + mlAlertType.executor, + mlAlertType.id, + mlAlertType.name, + previewRuleParams, + () => true, + { + create: alertInstanceFactoryStub, + alertLimit: { + getValue: () => 1000, + setLimitReached: () => {}, + }, + done: () => ({ getRecoveredAlerts: () => [] }), + } + ); + break; + case 'new_terms': + const newTermsAlertType = previewRuleTypeWrapper( + createNewTermsAlertType(ruleOptions) + ); + await runExecutors( + newTermsAlertType.executor, + newTermsAlertType.id, + newTermsAlertType.name, + previewRuleParams, + () => true, + { + create: alertInstanceFactoryStub, + alertLimit: { + getValue: () => 1000, + setLimitReached: () => {}, + }, + done: () => ({ getRecoveredAlerts: () => [] }), + } + ); + break; + default: + assertUnreachable(previewRuleParams); + } + + // Refreshes alias to ensure index is able to be read before returning + await coreContext.elasticsearch.client.asInternalUser.indices.refresh( + { + index: previewRuleDataClient.indexNameWithNamespace(spaceId), + }, + { ignore: [404] } + ); + + return response.ok({ + body: { + previewId, + logs, + isAborted, + }, + }); + } catch (err) { + const error = transformError(err as Error); + return siemResponse.error({ + body: { + errors: [error.message], + }, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 8480b1de57b0e..c3fb701458238 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -175,8 +175,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const refresh = actions.length ? 'wait_for' : false; - ruleExecutionLogger.debug('[+] Starting Signal Rule execution'); - ruleExecutionLogger.debug(`interval: ${interval}`); + ruleExecutionLogger.debug(`Starting Security Rule execution (interval: ${interval})`); await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatus.running, @@ -455,11 +454,15 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const createdSignalsCount = result.createdSignals.length; if (result.success) { - ruleExecutionLogger.debug('[+] Signal Rule execution completed.'); + ruleExecutionLogger.debug('Security Rule execution completed'); ruleExecutionLogger.debug( - `[+] Finished indexing ${createdSignalsCount} signals into ${ruleDataClient.indexNameWithNamespace( + `Finished indexing ${createdSignalsCount} alerts into ${ruleDataClient.indexNameWithNamespace( spaceId - )}` + )} ${ + !isEmpty(tuples) + ? `searched between date ranges ${JSON.stringify(tuples, null, 2)}` + : '' + }` ); if (!hasError && !wroteWarningStatus && !result.warning) { @@ -473,18 +476,10 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = }, }); } - - ruleExecutionLogger.debug( - `[+] Finished indexing ${createdSignalsCount} ${ - !isEmpty(tuples) - ? `signals searched between date ranges ${JSON.stringify(tuples, null, 2)}` - : '' - }` - ); } else { await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatus.failed, - message: `Bulk Indexing of signals failed: ${truncateList(result.errors).join()}`, + message: `Bulk Indexing of alerts failed: ${truncateList(result.errors).join()}`, metrics: { searchDurations: result.searchAfterTimes, indexingDurations: result.bulkCreateTimes, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts index b3cf8f3ed1675..84349e9142e22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts @@ -34,10 +34,6 @@ describe('buildAlert', () => { jest.clearAllMocks(); }); - test('it builds an alert composed of a sequence', () => { - expect(true).toEqual(true); - }); - test('it builds an alert as expected without original_event if event does not exist', () => { const completeRule = getCompleteRuleMock(getQueryRuleParams()); const eqlSequence = { @@ -146,6 +142,64 @@ describe('buildAlert', () => { }); describe('recursive intersection between objects', () => { + describe('objectPairIntersection', () => { + test('returns the intersection of fields with identically-valued arrays', () => { + const a = { + field1: [1], + }; + const b = { + field1: [1], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [1], + }; + expect(intersection).toEqual(expected); + }); + + test('returns the intersection of arrays with differing lengths', () => { + const a = { + field1: 1, + }; + const b = { + field1: [1, 2, 3], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [1], + }; + expect(intersection).toEqual(expected); + }); + + test('should work with arrays with same lengths but only one intersecting element', () => { + const a = { + field1: [3, 4, 5], + }; + const b = { + field1: [1, 2, 3], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [3], + }; + expect(intersection).toEqual(expected); + }); + + test('should work with arrays with differing lengths and two intersecting elements', () => { + const a = { + field1: [3, 4, 5], + }; + const b = { + field1: [1, 2, 3, 4], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [3, 4], + }; + expect(intersection).toEqual(expected); + }); + }); + test('should treat numbers and strings as unequal', () => { const a = { field1: 1, @@ -217,7 +271,7 @@ describe('buildAlert', () => { expect(intersection).toEqual(expected); }); - test('should strip arrays out regardless of whether they are equal', () => { + test('returns the intersection of values for fields containing arrays', () => { const a = { array_field1: [1, 2], array_field2: [1, 2], @@ -227,7 +281,7 @@ describe('buildAlert', () => { array_field2: [3, 4], }; const intersection = objectPairIntersection(a, b); - const expected = undefined; + const expected = { array_field1: [1, 2], array_field2: [] }; expect(intersection).toEqual(expected); }); @@ -287,6 +341,7 @@ describe('buildAlert', () => { const intersection = objectPairIntersection(a, b); const expected = { container_field: { + array_field: [1, 2], field1: 1, field6: null, nested_container_field: { @@ -332,6 +387,7 @@ describe('buildAlert', () => { }; const intersection = objectPairIntersection(a, b); const expected = { + array_field: [1, 2], field1: 1, field6: null, container_field: { @@ -419,6 +475,7 @@ describe('buildAlert', () => { }; const intersection = objectArrayIntersection([a, b]); const expected = { + array_field: [1, 2], field1: 1, field6: null, container_field: { @@ -427,7 +484,6 @@ describe('buildAlert', () => { }; expect(intersection).toEqual(expected); }); - test('should work with 3 or more objects', () => { const a = { field1: 1, @@ -477,6 +533,7 @@ describe('buildAlert', () => { }; const intersection = objectArrayIntersection([a, b, c]); const expected = { + array_field: [1, 2], field1: 1, }; expect(intersection).toEqual(expected); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts index 8db01c11d4de5..2675c3996e865 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts @@ -6,6 +6,7 @@ */ import { ALERT_URL, ALERT_UUID } from '@kbn/rule-data-utils'; +import { intersection as lodashIntersection, isArray } from 'lodash'; import { getAlertDetailsUrl } from '../../../../../common/utils/alert_detail_path'; import { DEFAULT_ALERTS_INDEX } from '../../../../../common/constants'; @@ -180,6 +181,11 @@ export const buildAlertRoot = ( }; }; +/** + * Merges array of alert sources with the first item in the array + * @param objects array of alert _source objects + * @returns singular object + */ export const objectArrayIntersection = (objects: object[]) => { if (objects.length === 0) { return undefined; @@ -195,6 +201,16 @@ export const objectArrayIntersection = (objects: object[]) => { } }; +/** + * Finds the intersection of two objects by recursively + * finding the "intersection" of each of of their common keys' + * values. If an intersection cannot be found between a key's + * values, the value will be undefined in the returned object. + * + * @param a object + * @param b object + * @returns intersection of the two objects + */ export const objectPairIntersection = (a: object | undefined, b: object | undefined) => { if (a === undefined || b === undefined) { return undefined; @@ -214,6 +230,12 @@ export const objectPairIntersection = (a: object | undefined, b: object | undefi intersection[key] = objectPairIntersection(aVal, bVal); } else if (aVal === bVal) { intersection[key] = aVal; + } else if (isArray(aVal) && isArray(bVal)) { + intersection[key] = lodashIntersection(aVal, bVal); + } else if (isArray(aVal) && !isArray(bVal)) { + intersection[key] = lodashIntersection(aVal, [bVal]); + } else if (!isArray(aVal) && isArray(bVal)) { + intersection[key] = lodashIntersection([aVal], bVal); } } }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts index a1d19931032f5..603ff74cf2704 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts @@ -69,7 +69,7 @@ export const bulkCreateFactory = const enrichedAlerts = await enrichAlerts(alerts, params, experimentalFeatures); return enrichedAlerts; } catch (error) { - ruleExecutionLogger.error(`Enrichments failed ${error}`); + ruleExecutionLogger.error(`Alerts enrichment failed: ${error}`); throw error; } finally { enrichmentsTimeFinish = performance.now(); @@ -90,13 +90,11 @@ export const bulkCreateFactory = const end = performance.now(); - ruleExecutionLogger.debug( - `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` - ); + ruleExecutionLogger.debug(`Alerts bulk process took ${makeFloatString(end - start)} ms`); if (!isEmpty(errors)) { - ruleExecutionLogger.debug( - `[-] bulkResponse had errors with responses of: ${JSON.stringify(errors)}` + ruleExecutionLogger.warn( + `Alerts bulk process finished with errors: ${JSON.stringify(errors)}` ); return { errors: Object.keys(errors), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts index 110f9848c6d83..231387f9c004a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts @@ -74,7 +74,7 @@ export const bulkCreateWithSuppression = async < const enrichedAlerts = await enrichAlerts(alerts, params); return enrichedAlerts; } catch (error) { - ruleExecutionLogger.error(`Enrichments failed ${error}`); + ruleExecutionLogger.error(`Alerts enrichment failed: ${error}`); throw error; } finally { enrichmentsTimeFinish = performance.now(); @@ -94,14 +94,10 @@ export const bulkCreateWithSuppression = async < const end = performance.now(); - ruleExecutionLogger.debug( - `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` - ); + ruleExecutionLogger.debug(`Alerts bulk process took ${makeFloatString(end - start)} ms`); if (!isEmpty(errors)) { - ruleExecutionLogger.debug( - `[-] bulkResponse had errors with responses of: ${JSON.stringify(errors)}` - ); + ruleExecutionLogger.warn(`Alerts bulk process finished with errors: ${JSON.stringify(errors)}`); return { errors: Object.keys(errors), success: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts index 5971c7685a443..982de01b8bae7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts @@ -89,7 +89,7 @@ export const createSingleFieldMatchEnrichment: CreateFieldsMatchEnrichment = asy ); return eventsMapById; } catch (error) { - logger.error(`Enrichment ${name}: throw error ${error}`); + logger.error(`Enrichment ${name} failed: ${error}`); return {}; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts index a50b33a0eee34..d5c566383ebba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts @@ -62,7 +62,7 @@ describe('filterEventsAgainstList', () => { expect(included.length).toEqual(4); expect(excluded.length).toEqual(0); expect(ruleExecutionLogger.debug.mock.calls[0][0]).toContain( - 'no exception items of type list found - returning original search result' + 'No exception items of type list found - return unfiltered events' ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts index 1093742d76d6e..8a8f7d34aa3a8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts @@ -47,9 +47,7 @@ export const filterEventsAgainstList = async ({ ); if (!atLeastOneLargeValueList) { - ruleExecutionLogger.debug( - 'no exception items of type list found - returning original search result' - ); + ruleExecutionLogger.debug('No exception items of type list found - return unfiltered events'); return [events, []]; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts index c64bb1cc463a2..8e084bd8e35dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts @@ -49,13 +49,18 @@ export const searchAfterAndBulkCreate = async ({ }: SearchAfterAndBulkCreateParams): Promise => { return withSecuritySpan('searchAfterAndBulkCreate', async () => { let toReturn = createSearchAfterReturnType(); + let searchingIteration = 0; // sortId tells us where to start our next consecutive search_after query let sortIds: estypes.SortResults | undefined; let hasSortId = true; // default to true so we execute the search on initial run if (tuple == null || tuple.to == null || tuple.from == null) { - ruleExecutionLogger.error(`[-] malformed date tuple`); + ruleExecutionLogger.error( + `missing run options fields: ${!tuple.to ? '"tuple.to"' : ''}, ${ + !tuple.from ? '"tuple.from"' : '' + }` + ); return createSearchAfterReturnType({ success: false, errors: ['malformed date tuple'], @@ -63,9 +68,14 @@ export const searchAfterAndBulkCreate = async ({ } while (toReturn.createdSignalsCount <= tuple.maxSignals) { + const cycleNum = `cycle ${searchingIteration++}`; try { let mergedSearchResults = createSearchResultReturnType(); - ruleExecutionLogger.debug(`sortIds: ${sortIds}`); + ruleExecutionLogger.debug( + `[${cycleNum}] Searching events${ + sortIds ? ` after cursor ${JSON.stringify(sortIds)}` : '' + } in index pattern "${inputIndexPattern}"` + ); if (hasSortId) { const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ @@ -97,9 +107,29 @@ export const searchAfterAndBulkCreate = async ({ }), ]); + // determine if there are any candidate signals to be processed + const totalHits = getTotalHitsValue(mergedSearchResults.hits.total); const lastSortIds = getSafeSortIds( searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort ); + + if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) { + ruleExecutionLogger.debug( + `[${cycleNum}] Found 0 events ${ + sortIds ? ` after cursor ${JSON.stringify(sortIds)}` : '' + }` + ); + break; + } else { + ruleExecutionLogger.debug( + `[${cycleNum}] Found ${ + mergedSearchResults.hits.hits.length + } of total ${totalHits} events${ + sortIds ? ` after cursor ${JSON.stringify(sortIds)}` : '' + }, last cursor ${JSON.stringify(lastSortIds)}` + ); + } + if (lastSortIds != null && lastSortIds.length !== 0) { sortIds = lastSortIds; hasSortId = true; @@ -108,22 +138,6 @@ export const searchAfterAndBulkCreate = async ({ } } - // determine if there are any candidate signals to be processed - const totalHits = getTotalHitsValue(mergedSearchResults.hits.total); - ruleExecutionLogger.debug(`totalHits: ${totalHits}`); - ruleExecutionLogger.debug( - `searchResult.hit.hits.length: ${mergedSearchResults.hits.hits.length}` - ); - - if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) { - ruleExecutionLogger.debug( - `${ - totalHits === 0 ? 'totalHits' : 'searchResult.hits.hits.length' - } was 0, exiting early` - ); - break; - } - // filter out the search results that match with the values found in the list. // the resulting set are signals to be indexed, given they are not duplicates // of signals already present in the signals index. @@ -158,9 +172,9 @@ export const searchAfterAndBulkCreate = async ({ addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult }); - ruleExecutionLogger.debug(`created ${bulkCreateResult.createdItemsCount} signals`); - ruleExecutionLogger.debug(`signalsCreatedCount: ${toReturn.createdSignalsCount}`); - ruleExecutionLogger.debug(`enrichedEvents.hits.hits: ${enrichedEvents.length}`); + ruleExecutionLogger.debug( + `[${cycleNum}] Created ${bulkCreateResult.createdItemsCount} alerts from ${enrichedEvents.length} events` + ); sendAlertTelemetryEvents( enrichedEvents, @@ -171,11 +185,14 @@ export const searchAfterAndBulkCreate = async ({ } if (!hasSortId) { - ruleExecutionLogger.debug('ran out of sort ids to sort on'); + ruleExecutionLogger.debug(`[${cycleNum}] Unable to fetch last event cursor`); break; } } catch (exc: unknown) { - ruleExecutionLogger.error(`[-] search_after_bulk_create threw an error ${exc}`); + ruleExecutionLogger.error( + 'Unable to extract/process events or create alerts', + JSON.stringify(exc) + ); return mergeReturns([ toReturn, createSearchAfterReturnType({ @@ -185,7 +202,7 @@ export const searchAfterAndBulkCreate = async ({ ]); } } - ruleExecutionLogger.debug(`[+] completed bulk index of ${toReturn.createdSignalsCount}`); + ruleExecutionLogger.debug(`Completed bulk indexing of ${toReturn.createdSignalsCount} alert`); return toReturn; }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts index a4687fa953dfe..713428ca08557 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts @@ -72,6 +72,6 @@ export function sendAlertTelemetryEvents( try { eventsTelemetry.queueTelemetryEvents(selectedEvents); } catch (exc) { - ruleExecutionLogger.error(`[-] queing telemetry events failed ${exc}`); + ruleExecutionLogger.error(`Queuing telemetry events failed: ${exc}`); } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts index ba91454b3aa02..c10403848474f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts @@ -104,14 +104,12 @@ export const singleSearchAfter = async < searchErrors, }; } catch (exc) { - ruleExecutionLogger.error(`[-] nextSearchAfter threw an error ${exc}`); + ruleExecutionLogger.error(`Searching events operation failed: ${exc}`); if ( exc.message.includes(`No mapping found for [${primaryTimestamp}] in order to sort on`) || (secondaryTimestamp && exc.message.includes(`No mapping found for [${secondaryTimestamp}] in order to sort on`)) ) { - ruleExecutionLogger.error(`[-] failure reason: ${exc.message}`); - const searchRes: SignalSearchResponse = { took: 0, timed_out: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts index d507977de11c8..0a18cd40b3a61 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts @@ -734,7 +734,7 @@ describe('utils', () => { expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({ newStatus: RuleExecutionStatus['partial failure'], message: - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled. If you have recently enrolled agents enabled with Endpoint Security through Fleet, this warning should stop once an alert is sent from an agent.', + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled. If you have recently enrolled agents enabled with Endpoint Security through Fleet, this warning should stop once an alert is sent from an agent.', }); }); @@ -768,7 +768,7 @@ describe('utils', () => { expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({ newStatus: RuleExecutionStatus['partial failure'], message: - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.', + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.', }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts index 17d3e0a4876f3..be19c354cbe1a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts @@ -91,7 +91,7 @@ export const hasReadIndexPrivileges = async (args: { const indexesString = JSON.stringify(indexesWithNoReadPrivileges); await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatus['partial failure'], - message: `This rule may not have the required read privileges to the following indices/index patterns: ${indexesString}`, + message: `This rule may not have the required read privileges to the following index patterns: ${indexesString}`, }); return true; } @@ -112,7 +112,7 @@ export const hasTimestampFields = async (args: { const { ruleName } = ruleExecutionLogger.context; if (isEmpty(timestampFieldCapsResponse.body.indices)) { - const errorString = `This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ${JSON.stringify( + const errorString = `This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ${JSON.stringify( inputIndices )} was found. This warning will continue to appear until a matching index is created or this rule is disabled. ${ ruleName === 'Endpoint Security' @@ -432,7 +432,9 @@ export const getRuleRangeTuples = ({ const intervalDuration = parseInterval(interval); if (intervalDuration == null) { ruleExecutionLogger.error( - 'Failed to compute gap between rule runs: could not parse rule interval' + `Failed to compute gap between rule runs: could not parse rule interval "${JSON.stringify( + interval + )}"` ); return { tuples, remainingGap: moment.duration(0) }; } diff --git a/x-pack/plugins/security_solution/server/lib/exceptions/api/manage_exceptions/route.ts b/x-pack/plugins/security_solution/server/lib/exceptions/api/manage_exceptions/route.ts index 88c3dc91b37a7..35dbfea0c2125 100644 --- a/x-pack/plugins/security_solution/server/lib/exceptions/api/manage_exceptions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/exceptions/api/manage_exceptions/route.ts @@ -16,50 +16,57 @@ import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; export const createSharedExceptionListRoute = (router: SecuritySolutionPluginRouter) => { - router.post( - { + router.versioned + .post({ path: SHARED_EXCEPTION_LIST_URL, - validate: { - body: buildRouteValidation< - typeof CreateSharedExceptionListRequest, - CreateSharedExceptionListRequest - >(CreateSharedExceptionListRequest), - }, + access: 'public', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response): Promise> => { - const siemResponse = buildSiemResponse(response); - const { description, name } = request.body; + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + body: buildRouteValidation< + typeof CreateSharedExceptionListRequest, + CreateSharedExceptionListRequest + >(CreateSharedExceptionListRequest), + }, + }, + }, + async (context, request, response): Promise> => { + const siemResponse = buildSiemResponse(response); + const { description, name } = request.body; - try { - const ctx = await context.resolve([ - 'core', - 'securitySolution', - 'alerting', - 'licensing', - 'lists', - ]); - const listsClient = ctx.securitySolution.getExceptionListClient(); - const createdSharedList = await listsClient?.createExceptionList({ - description, - immutable: false, - listId: uuidv4(), - meta: undefined, - name, - namespaceType: 'single', - tags: [], - type: 'detection', - version: 1, - }); - return response.ok({ body: createdSharedList }); - } catch (exc) { - return siemResponse.error({ - body: exc.message, - statusCode: 404, - }); + try { + const ctx = await context.resolve([ + 'core', + 'securitySolution', + 'alerting', + 'licensing', + 'lists', + ]); + const listsClient = ctx.securitySolution.getExceptionListClient(); + const createdSharedList = await listsClient?.createExceptionList({ + description, + immutable: false, + listId: uuidv4(), + meta: undefined, + name, + namespaceType: 'single', + tags: [], + type: 'detection', + version: 1, + }); + return response.ok({ body: createdSharedList }); + } catch (exc) { + return siemResponse.error({ + body: exc.message, + statusCode: 404, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index 889b01b1ea6cd..ca941bcacce3a 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -175,6 +175,7 @@ describe('RiskEngineDataClient', () => { "dynamic": "strict", "properties": Object { "@timestamp": Object { + "ignore_malformed": false, "type": "date", }, "host": Object { @@ -360,6 +361,7 @@ describe('RiskEngineDataClient', () => { dynamic: 'strict', properties: { '@timestamp': { + ignore_malformed: false, type: 'date', }, host: { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts index b7fa301869951..b1aefedc90cef 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts @@ -25,7 +25,7 @@ export const createIndex = async ({ index: options.index, }); if (isIndexExist) { - logger.info('${options.index} already exist'); + logger.info(`${options.index} already exist`); return; } diff --git a/x-pack/plugins/security_solution/server/lib/tags/routes/create_tag.ts b/x-pack/plugins/security_solution/server/lib/tags/routes/create_tag.ts index 6ddc14d64d53f..97499b76ae354 100644 --- a/x-pack/plugins/security_solution/server/lib/tags/routes/create_tag.ts +++ b/x-pack/plugins/security_solution/server/lib/tags/routes/create_tag.ts @@ -22,42 +22,47 @@ export const createTagRoute = ( logger: Logger, security: SetupPlugins['security'] ) => { - router.put( - { + router.versioned + .put({ path: INTERNAL_TAGS_URL, - validate: { body: buildRouteValidationWithExcess(createTagRequest) }, + access: 'internal', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const frameworkRequest = await buildFrameworkRequest(context, security, request); - const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; - const { name: tagName, description, color } = request.body; - try { - const tag = await createTag({ - savedObjectsClient, - tagName, - description, - color, - }); - return response.ok({ body: tag }); - } catch (err) { - const error = transformError(err); - logger.error(`Failed to create ${tagName} tag - ${JSON.stringify(error.message)}`); + }) + .addVersion( + { + version: '1', + validate: { request: { body: buildRouteValidationWithExcess(createTagRequest) } }, + }, + async (context, request, response) => { + const frameworkRequest = await buildFrameworkRequest(context, security, request); + const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; + const { name: tagName, description, color } = request.body; + try { + const tag = await createTag({ + savedObjectsClient, + tagName, + description, + color, + }); + return response.ok({ body: tag }); + } catch (err) { + const error = transformError(err); + logger.error(`Failed to create ${tagName} tag - ${JSON.stringify(error.message)}`); - const siemResponse = buildSiemResponse(response); - return siemResponse.error({ - statusCode: error.statusCode ?? 500, - body: i18n.translate( - 'xpack.securitySolution.dashboards.createSecuritySolutionTagErrorTitle', - { - values: { tagName, message: error.message }, - defaultMessage: `Failed to create {tagName} tag - {message}`, - } - ), - }); + const siemResponse = buildSiemResponse(response); + return siemResponse.error({ + statusCode: error.statusCode ?? 500, + body: i18n.translate( + 'xpack.securitySolution.dashboards.createSecuritySolutionTagErrorTitle', + { + values: { tagName, message: error.message }, + defaultMessage: `Failed to create {tagName} tag - {message}`, + } + ), + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts b/x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts index a88578365f037..cd07a71db5a1c 100644 --- a/x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts +++ b/x-pack/plugins/security_solution/server/lib/tags/routes/get_tags_by_name.ts @@ -22,45 +22,50 @@ export const getTagsByNameRoute = ( logger: Logger, security: SetupPlugins['security'] ) => { - router.get( - { + router.versioned + .get({ path: INTERNAL_TAGS_URL, - validate: { query: buildRouteValidationWithExcess(getTagsByNameRequest) }, + access: 'internal', options: { tags: ['access:securitySolution'], }, - }, - async (context, request, response) => { - const frameworkRequest = await buildFrameworkRequest(context, security, request); - const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; + }) + .addVersion( + { + version: '1', + validate: { request: { query: buildRouteValidationWithExcess(getTagsByNameRequest) } }, + }, + async (context, request, response) => { + const frameworkRequest = await buildFrameworkRequest(context, security, request); + const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; - const { name: tagName } = request.query; + const { name: tagName } = request.query; - try { - const tags = await findTagsByName({ - savedObjectsClient, - tagName, - }); + try { + const tags = await findTagsByName({ + savedObjectsClient, + tagName, + }); - return response.ok({ - body: tags, - }); - } catch (err) { - const error = transformError(err); - logger.error(`Failed to find ${tagName} tags - ${JSON.stringify(error.message)}`); + return response.ok({ + body: tags, + }); + } catch (err) { + const error = transformError(err); + logger.error(`Failed to find ${tagName} tags - ${JSON.stringify(error.message)}`); - const siemResponse = buildSiemResponse(response); - return siemResponse.error({ - statusCode: error.statusCode ?? 500, - body: i18n.translate( - 'xpack.securitySolution.dashboards.getSecuritySolutionTagsErrorTitle', - { - values: { tagName, message: error.message }, - defaultMessage: `Failed to find {tagName} tags - {message}`, - } - ), - }); + const siemResponse = buildSiemResponse(response); + return siemResponse.error({ + statusCode: error.statusCode ?? 500, + body: i18n.translate( + 'xpack.securitySolution.dashboards.getSecuritySolutionTagsErrorTitle', + { + values: { tagName, message: error.message }, + defaultMessage: `Failed to find {tagName} tags - {message}`, + } + ), + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 4ba3d0a2cbc07..2998854f484e4 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -17,10 +17,16 @@ import { Dataset } from '@kbn/rule-registry-plugin/server'; import type { ListPluginSetup } from '@kbn/lists-plugin/server'; import type { ILicense } from '@kbn/licensing-plugin/server'; +import { i18n } from '@kbn/i18n'; +import { endpointPackagePoliciesStatsSearchStrategyProvider } from './search_strategy/endpoint_package_policies_stats'; import { turnOffPolicyProtectionsIfNotSupported } from './endpoint/migrations/turn_off_policy_protections'; import { endpointSearchStrategyProvider } from './search_strategy/endpoint'; import { getScheduleNotificationResponseActionsService } from './lib/detection_engine/rule_response_actions/schedule_notification_response_actions'; -import { siemGuideConfig, siemGuideId } from '../common/guided_onboarding/siem_guide_config'; +import { + siemGuideId, + getSiemGuideConfig, + defaultGuideTranslations, +} from '../common/guided_onboarding/siem_guide_config'; import { createEqlAlertType, createIndicatorMatchAlertType, @@ -93,11 +99,13 @@ import { artifactService } from './lib/telemetry/artifact'; import { endpointFieldsProvider } from './search_strategy/endpoint_fields'; import { ENDPOINT_FIELDS_SEARCH_STRATEGY, + ENDPOINT_PACKAGE_POLICIES_STATS_STRATEGY, ENDPOINT_SEARCH_STRATEGY, } from '../common/endpoint/constants'; import { AppFeaturesService } from './lib/app_features_service/app_features_service'; import { registerRiskScoringTask } from './lib/risk_engine/tasks/risk_scoring_task'; +import { registerProtectionUpdatesNoteRoutes } from './endpoint/routes/protection_updates_note'; export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract'; @@ -190,6 +198,7 @@ export class Plugin implements ISecuritySolutionPlugin { this.endpointAppContextService.setup({ securitySolutionRequestContextFactory: requestContextFactory, + cloud: plugins.cloud, }); initUsageCollectors({ @@ -316,6 +325,7 @@ export class Plugin implements ISecuritySolutionPlugin { ); registerLimitedConcurrencyRoutes(core); registerPolicyRoutes(router, this.endpointContext); + registerProtectionUpdatesNoteRoutes(router, this.endpointContext); registerActionRoutes( router, this.endpointContext, @@ -360,6 +370,13 @@ export class Plugin implements ISecuritySolutionPlugin { endpointFieldsStrategy ); + const endpointPackagePoliciesStatsStrategy = + endpointPackagePoliciesStatsSearchStrategyProvider(this.endpointAppContextService); + plugins.data.search.registerSearchStrategy( + ENDPOINT_PACKAGE_POLICIES_STATS_STRATEGY, + endpointPackagePoliciesStatsStrategy + ); + const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider( depsStart.data, this.endpointContext, @@ -377,6 +394,32 @@ export class Plugin implements ISecuritySolutionPlugin { ); plugins.data.search.registerSearchStrategy(ENDPOINT_SEARCH_STRATEGY, endpointSearchStrategy); + + /** + * Register a config for the security guide + */ + if (depsStart.cloudExperiments && i18n.getLocale() === 'en') { + try { + depsStart.cloudExperiments + .getVariation('security-solutions.guided-onboarding-content', defaultGuideTranslations) + .then((variation) => { + plugins.guidedOnboarding.registerGuideConfig( + siemGuideId, + getSiemGuideConfig(variation) + ); + }); + } catch { + plugins.guidedOnboarding.registerGuideConfig( + siemGuideId, + getSiemGuideConfig(defaultGuideTranslations) + ); + } + } else { + plugins.guidedOnboarding.registerGuideConfig( + siemGuideId, + getSiemGuideConfig(defaultGuideTranslations) + ); + } }); setIsElasticCloudDeployment(plugins.cloud.isCloudEnabled ?? false); @@ -397,11 +440,6 @@ export class Plugin implements ISecuritySolutionPlugin { featureUsageService.setup(plugins.licensing); - /** - * Register a config for the security guide - */ - plugins.guidedOnboarding.registerGuideConfig(siemGuideId, siemGuideConfig); - return { setAppFeaturesConfigurator: appFeaturesService.setAppFeaturesConfigurator.bind(appFeaturesService), @@ -520,7 +558,6 @@ export class Plugin implements ISecuritySolutionPlugin { manifestManager, registerIngestCallback, licenseService, - cloud: plugins.cloud, exceptionListsClient: exceptionListClient, registerListsServerExtension: this.lists?.registerExtension, featureUsageService, @@ -533,6 +570,7 @@ export class Plugin implements ISecuritySolutionPlugin { createFleetActionsClient, esClient: core.elasticsearch.client.asInternalUser, appFeaturesService, + savedObjectsClient, }); this.telemetryReceiver.start( diff --git a/x-pack/plugins/security_solution/server/saved_objects.ts b/x-pack/plugins/security_solution/server/saved_objects.ts index 096b46528e76f..3f91bcf149ac6 100644 --- a/x-pack/plugins/security_solution/server/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/saved_objects.ts @@ -7,6 +7,7 @@ import type { CoreSetup } from '@kbn/core/server'; +import { protectionUpdatesNoteType } from './endpoint/lib/protection_updates_note/saved_object_mappings'; import { noteType, pinnedEventType, timelineType } from './lib/timeline/saved_object_mappings'; // eslint-disable-next-line no-restricted-imports import { legacyType as legacyRuleActionsType } from './lib/detection_engine/rule_actions_legacy'; @@ -24,6 +25,7 @@ const types = [ manifestType, signalsMigrationType, riskEngineConfigurationType, + protectionUpdatesNoteType, ]; export const savedObjectTypes = types.map((type) => type.name); diff --git a/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/actions/index.ts b/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/actions/index.ts index fbb57a0d22e11..3c24d29ee7b50 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/actions/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/actions/index.ts @@ -17,9 +17,7 @@ import type { } from '../../../../../../common/search_strategy/endpoint/response_actions'; export const allActions: EndpointFactory = { - buildDsl: (options: ActionRequestOptions, { authz }) => { - return buildResponseActionsQuery(options, authz); - }, + buildDsl: (options: ActionRequestOptions, { authz }) => buildResponseActionsQuery(options, authz), parse: async ( options: ActionRequestOptions, response: IEsSearchResponse, diff --git a/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/results/index.ts b/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/results/index.ts index 705018f001acb..292f4858ac3c7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/results/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/endpoint/factory/response_actions/results/index.ts @@ -16,9 +16,7 @@ import { buildActionResultsQuery } from './query.action_results.dsl'; import type { EndpointFactory } from '../../types'; export const actionResults: EndpointFactory = { - buildDsl: (options: ActionResponsesRequestOptions) => { - return buildActionResultsQuery(options); - }, + buildDsl: (options: ActionResponsesRequestOptions) => buildActionResultsQuery(options), parse: async (options, response): Promise => { const inspect = { dsl: [inspectStringifyObject(buildActionResultsQuery(options))], diff --git a/x-pack/plugins/security_solution/server/search_strategy/endpoint_package_policies_stats/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/endpoint_package_policies_stats/index.test.ts new file mode 100644 index 0000000000000..1d3e92592cb8e --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/endpoint_package_policies_stats/index.test.ts @@ -0,0 +1,109 @@ +/* + * Copyright 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 { createMockEndpointAppContextService } from '../../endpoint/mocks'; +import type { SearchStrategyDependencies } from '@kbn/data-plugin/server'; +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { requestEndpointPackagePoliciesStatsSearch } from '.'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services'; +import moment from 'moment'; + +const mockPackagePolicyResponse = (inputs: string[]) => ({ + items: inputs.map((input) => ({ + package: { name: 'endpoint' }, + inputs: [ + { + type: 'endpoint', + config: { policy: { value: { global_manifest_version: input } } }, + }, + ], + })), + total: inputs.length, + page: 1, + per_page: 10000, +}); + +describe('Endpoint package policies stats', () => { + let endpointAppContextService: EndpointAppContextService; + let mockSavedObjectClient: jest.Mocked; + + beforeEach(() => { + endpointAppContextService = createMockEndpointAppContextService(); + mockSavedObjectClient = savedObjectsClientMock.create(); + }); + + afterEach(() => endpointAppContextService.stop()); + + describe('Returns correct count of outdated manifests', () => { + const deps = { + savedObjectsClient: mockSavedObjectClient, + } as unknown as SearchStrategyDependencies; + + it('when no manifests are outdated.', async () => { + const listMock = endpointAppContextService.getInternalFleetServices().packagePolicy + .list as jest.Mock; + + listMock.mockResolvedValueOnce(mockPackagePolicyResponse(['latest', 'latest', 'latest'])); + const response = await requestEndpointPackagePoliciesStatsSearch( + endpointAppContextService, + deps + ); + expect( + endpointAppContextService.getInternalFleetServices().packagePolicy.list + ).toBeCalledTimes(1); + expect(response).toEqual({ + isPartial: false, + isRunning: false, + rawResponse: { outdatedManifestsCount: 0 }, + }); + }); + + it('when some manifests are outdated.', async () => { + const listMock = endpointAppContextService.getInternalFleetServices().packagePolicy + .list as jest.Mock; + + listMock.mockResolvedValueOnce( + mockPackagePolicyResponse(['2020-01-01', 'latest', '2020-01-01', 'latest']) + ); + const response = await requestEndpointPackagePoliciesStatsSearch( + endpointAppContextService, + deps + ); + expect(response).toEqual({ + isPartial: false, + isRunning: false, + rawResponse: { outdatedManifestsCount: 2 }, + }); + }); + + it('when all manifests are outdated but some of them not more than a month', async () => { + const listMock = endpointAppContextService.getInternalFleetServices().packagePolicy + .list as jest.Mock; + + listMock.mockResolvedValueOnce( + mockPackagePolicyResponse([ + '2020-01-01', + 'latest', + '2020-01-01', + 'latest', + moment.utc().subtract(1, 'week').format('YYYY-MM-DD'), + moment.utc().subtract(2, 'week').format('YYYY-MM-DD'), + ]) + ); + const response = await requestEndpointPackagePoliciesStatsSearch( + endpointAppContextService, + deps + ); + expect(response).toEqual({ + isPartial: false, + isRunning: false, + rawResponse: { outdatedManifestsCount: 2 }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/endpoint_package_policies_stats/index.ts b/x-pack/plugins/security_solution/server/search_strategy/endpoint_package_policies_stats/index.ts new file mode 100644 index 0000000000000..99b64cf1ed193 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/endpoint_package_policies_stats/index.ts @@ -0,0 +1,64 @@ +/* + * Copyright 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 { ISearchStrategy, SearchStrategyDependencies } from '@kbn/data-plugin/server'; + +import { from } from 'rxjs'; +import moment from 'moment'; +import type { IKibanaSearchResponse } from '@kbn/data-plugin/common'; +import type { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services'; +import { EndpointAuthorizationError } from '../../endpoint/errors'; + +export const endpointPackagePoliciesStatsSearchStrategyProvider = ( + context: EndpointAppContextService +): ISearchStrategy<{}, IKibanaSearchResponse<{ outdatedManifestsCount: number }>> => { + return { + search: (_, __, deps) => { + return from(requestEndpointPackagePoliciesStatsSearch(context, deps)); + }, + }; +}; + +export const requestEndpointPackagePoliciesStatsSearch = async ( + context: EndpointAppContextService, + deps: SearchStrategyDependencies +) => { + const { canReadEndpointList } = await context.getEndpointAuthz(deps.request); + + if (!canReadEndpointList) { + throw new EndpointAuthorizationError(); + } + + const fleetServices = context.getInternalFleetServices(); + + const rawResponse = await fleetServices.packagePolicy.list(deps.savedObjectsClient, { + perPage: 10000, + page: 1, + kuery: `ingest-package-policies.package.name:"endpoint"`, + }); + + const outdatedManifestsCount = rawResponse.items.reduce((acc, item) => { + const endpointInput = item.inputs.find((input) => input.type === 'endpoint'); + if (!endpointInput) { + return acc; + } + const manifestVersion = endpointInput.config?.policy?.value?.global_manifest_version; + if (!manifestVersion) { + return acc; + } + if (manifestVersion === 'latest') { + return acc; + } + if (moment.utc(manifestVersion, 'YYYY-MM-DD').isBefore(moment.utc().subtract(1, 'month'))) { + return acc + 1; + } + + return acc; + }, 0); + + return { rawResponse: { outdatedManifestsCount }, isRunning: false, isPartial: false }; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/factory.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/factory.ts index ec08cfbc30965..ac6232f597395 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/factory.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/factory.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { CtiQueries } from '../../../../../../common/search_strategy/security_solution/cti'; +import type { CtiQueries } from '../../../../../../common/search_strategy'; import type { SecuritySolutionFactory } from '../../types'; import { buildEventEnrichmentQuery } from './query'; import { parseEventEnrichmentResponse } from './response'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts index cef953b5fb557..ec1ca68817f2c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts @@ -5,56 +5,59 @@ * 2.0. */ -import type { CtiQueries } from '../../../../../../common/search_strategy/security_solution/cti'; +import type { EventEnrichmentRequestOptions } from '../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; -import type { SecuritySolutionFactory } from '../../types'; import { buildIndicatorShouldClauses } from './helpers'; -export const buildEventEnrichmentQuery: SecuritySolutionFactory['buildDsl'] = - ({ defaultIndex, eventFields, filterQuery, timerange: { from, to } }) => { - const filter = [ - ...createQueryFilterClauses(filterQuery), - { term: { 'event.type': 'indicator' } }, - { - range: { - '@timestamp': { - gte: from, - lte: to, - format: 'strict_date_optional_time', - }, +export const buildEventEnrichmentQuery = ({ + defaultIndex, + eventFields, + filterQuery, + timerange: { from, to }, +}: EventEnrichmentRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { term: { 'event.type': 'indicator' } }, + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', }, }, - ]; + }, + ]; - return { - allow_no_indices: true, - ignore_unavailable: true, - index: defaultIndex, - body: { - _source: false, - fields: [ - { field: '*', include_unmapped: true }, - { - field: '@timestamp', - format: 'strict_date_optional_time', - }, - { - field: 'code_signature.timestamp', - format: 'strict_date_optional_time', - }, - { - field: 'dll.code_signature.timestamp', - format: 'strict_date_optional_time', - }, - ], - stored_fields: ['*'], - query: { - bool: { - should: buildIndicatorShouldClauses(eventFields), - filter, - minimum_should_match: 1, - }, + return { + allow_no_indices: true, + ignore_unavailable: true, + index: defaultIndex, + body: { + _source: false, + fields: [ + { field: '*', include_unmapped: true }, + { + field: '@timestamp', + format: 'strict_date_optional_time', + }, + { + field: 'code_signature.timestamp', + format: 'strict_date_optional_time', + }, + { + field: 'dll.code_signature.timestamp', + format: 'strict_date_optional_time', + }, + ], + stored_fields: ['*'], + query: { + bool: { + should: buildIndicatorShouldClauses(eventFields), + filter, + minimum_should_match: 1, }, }, - }; + }, }; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts index 026cf28319e5e..f305eb3254b5a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts @@ -5,24 +5,26 @@ * 2.0. */ -import type { CtiQueries } from '../../../../../../common/search_strategy/security_solution/cti'; +import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { EventEnrichmentRequestOptions } from '../../../../../../common/api/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; -import type { SecuritySolutionFactory } from '../../types'; import { buildIndicatorEnrichments, getTotalCount } from './helpers'; import { buildEventEnrichmentQuery } from './query'; -export const parseEventEnrichmentResponse: SecuritySolutionFactory['parse'] = - async (options, response, deps) => { - const inspect = { - dsl: [inspectStringifyObject(buildEventEnrichmentQuery(options))], - }; - const totalCount = getTotalCount(response.rawResponse.hits.total); - const enrichments = buildIndicatorEnrichments(response.rawResponse.hits.hits); +export const parseEventEnrichmentResponse = async ( + options: EventEnrichmentRequestOptions, + response: IEsSearchResponse +) => { + const inspect = { + dsl: [inspectStringifyObject(buildEventEnrichmentQuery(options))], + }; + const totalCount = getTotalCount(response.rawResponse.hits.total); + const enrichments = buildIndicatorEnrichments(response.rawResponse.hits.hits); - return { - ...response, - enrichments, - inspect, - totalCount, - }; + return { + ...response, + enrichments, + inspect, + totalCount, }; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/index.ts index e43af97e84af0..5192f466ca5d7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/index.ts @@ -5,13 +5,11 @@ * 2.0. */ -import type { FactoryQueryTypes } from '../../../../../common/search_strategy/security_solution'; import { CtiQueries } from '../../../../../common/search_strategy/security_solution/cti'; -import type { SecuritySolutionFactory } from '../types'; import { eventEnrichment } from './event_enrichment'; import { dataSource } from './threat_intel_source'; -export const ctiFactoryTypes: Record> = { +export const ctiFactoryTypes = { [CtiQueries.eventEnrichment]: eventEnrichment, [CtiQueries.dataSource]: dataSource, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/index.ts index dcd311ece1f9f..5052c4cc73fa7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/index.ts @@ -10,15 +10,14 @@ import type { SecuritySolutionFactory } from '../../types'; import type { CtiDataSourceStrategyResponse, CtiQueries, - CtiDataSourceRequestOptions, } from '../../../../../../common/search_strategy/security_solution/cti'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { buildTiDataSourceQuery } from './query.threat_intel_source.dsl'; export const dataSource: SecuritySolutionFactory = { - buildDsl: (options: CtiDataSourceRequestOptions) => buildTiDataSourceQuery(options), + buildDsl: (options) => buildTiDataSourceQuery(options), parse: async ( - options: CtiDataSourceRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.test.ts index faeb33b2369a1..54663122eea4b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.test.ts @@ -7,8 +7,9 @@ import { buildTiDataSourceQuery } from './query.threat_intel_source.dsl'; import { CtiQueries } from '../../../../../../common/search_strategy/security_solution/cti'; +import type { ThreatIntelSourceRequestOptionsInput } from '../../../../../../common/api/search_strategy'; -export const mockOptions = { +export const mockOptions: ThreatIntelSourceRequestOptionsInput = { defaultIndex: ['logs-ti_*', 'filebeat-8*'], factoryQueryType: CtiQueries.dataSource, filterQuery: '', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.ts index 5aef67e1fc42d..0ea0ddbb3a5a9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/threat_intel_source/query.threat_intel_source.dsl.ts @@ -5,12 +5,12 @@ * 2.0. */ -import type { CtiDataSourceRequestOptions } from '../../../../../../common/search_strategy/security_solution/cti'; +import type { ThreatIntelSourceRequestOptions } from '../../../../../../common/api/search_strategy'; export const buildTiDataSourceQuery = ({ timerange, defaultIndex, -}: CtiDataSourceRequestOptions) => { +}: ThreatIntelSourceRequestOptions) => { const filter = []; if (timerange) { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts index 8937e480d0966..3db8573a6a606 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts @@ -8,12 +8,11 @@ import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { HostsRequestOptions } from '../../../../../../../common/api/search_strategy'; +import { HostsFields } from '../../../../../../../common/api/search_strategy/hosts/model/sort'; -import type { - HostAggEsItem, - HostsRequestOptions, -} from '../../../../../../../common/search_strategy'; -import { Direction, HostsFields, HostsQueries } from '../../../../../../../common/search_strategy'; +import type { HostAggEsItem } from '../../../../../../../common/search_strategy'; +import { Direction, HostsQueries } from '../../../../../../../common/search_strategy'; import { createMockEndpointAppContext } from '../../../../../../endpoint/mocks'; export const mockOptions: HostsRequestOptions = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts index 7abc747eb0c82..600c5414b4fbe 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts @@ -7,7 +7,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; -import type { HostsRequestOptions } from '../../../../../../common/search_strategy/security_solution'; import { RiskScoreEntity } from '../../../../../../common/search_strategy/security_solution'; import * as buildQuery from './query.all_hosts.dsl'; import * as buildRiskQuery from '../../risk_score/all/query.risk_score.dsl'; @@ -19,6 +18,7 @@ import { mockDeps as defaultMockDeps, } from './__mocks__'; import { get } from 'lodash/fp'; +import type { HostsRequestOptions } from '../../../../../../common/api/search_strategy'; class IndexNotFoundException extends Error { meta: { body: { error: { type: string } } }; @@ -130,6 +130,7 @@ describe('allHosts search strategy', () => { defaultIndex: ['ml_host_risk_score_latest_test-space'], filterQuery: { terms: { 'host.name': [hostName] } }, riskScoreEntity: RiskScoreEntity.host, + factoryQueryType: expect.stringContaining('RiskScore'), }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts index 98241e8336fa9..0131f5417d0d5 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts @@ -14,12 +14,12 @@ import type { HostAggEsItem, HostsStrategyResponse, HostsQueries, - HostsRequestOptions, HostsEdges, } from '../../../../../../common/search_strategy/security_solution/hosts'; import type { HostRiskScore } from '../../../../../../common/search_strategy'; import { + RiskQueries, RiskScoreEntity, getHostRiskIndex, buildHostNamesFilter, @@ -34,14 +34,14 @@ import type { EndpointAppContext } from '../../../../../endpoint/types'; import { buildRiskScoreQuery } from '../../risk_score/all/query.risk_score.dsl'; export const allHosts: SecuritySolutionFactory = { - buildDsl: (options: HostsRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildHostsQuery(options); }, parse: async ( - options: HostsRequestOptions, + options, response: IEsSearchResponse, deps?: { esClient: IScopedClusterClient; @@ -134,6 +134,7 @@ export async function getHostRiskData( defaultIndex: [getHostRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)], filterQuery: buildHostNamesFilter(hostNames), riskScoreEntity: RiskScoreEntity.host, + factoryQueryType: RiskQueries.hostsRiskScore, }) ); return hostRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts index 6a5cc8d008563..cb3c960e1f6ea 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts @@ -7,12 +7,9 @@ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; import { hostFieldsMap } from '@kbn/securitysolution-ecs'; -import type { - Direction, - HostsRequestOptions, - SortField, -} from '../../../../../../common/search_strategy'; -import { HostsFields } from '../../../../../../common/search_strategy'; +import { HostsFields } from '../../../../../../common/api/search_strategy/hosts/model/sort'; +import type { HostsRequestOptions } from '../../../../../../common/api/search_strategy'; +import type { Direction } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses, reduceFields } from '../../../../../utils/build_query'; import { assertUnreachable } from '../../../../../../common/utility_types'; import { HOSTS_FIELDS } from './helpers'; @@ -88,13 +85,13 @@ export const buildHostsQuery = ({ type QueryOrder = { lastSeen: Direction } | { _key: Direction }; -const getQueryOrder = (sort: SortField): QueryOrder => { +const getQueryOrder = (sort: HostsRequestOptions['sort']): QueryOrder => { switch (sort.field) { case HostsFields.lastSeen: return { lastSeen: sort.direction }; case HostsFields.hostName: return { _key: sort.direction }; default: - return assertUnreachable(sort.field); + return assertUnreachable(sort.field as never); } }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts index aa4d5c03b23be..3896197198ad1 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts @@ -6,10 +6,10 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { HostsFields } from '../../../../../../../common/api/search_strategy/hosts/model/sort'; import type { HostDetailsRequestOptions, SortField, - HostsFields, } from '../../../../../../../common/search_strategy'; import { Direction, HostsQueries } from '../../../../../../../common/search_strategy'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts index 27f00f5b8734a..96d3a941ef8b8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts @@ -17,7 +17,6 @@ import type { HostAggEsData, HostDetailsStrategyResponse, HostsQueries, - HostDetailsRequestOptions, EndpointFields, } from '../../../../../../common/search_strategy/security_solution/hosts'; @@ -28,9 +27,9 @@ import { formatHostItem, getHostEndpoint } from './helpers'; import type { EndpointAppContext } from '../../../../../endpoint/types'; export const hostDetails: SecuritySolutionFactory = { - buildDsl: (options: HostDetailsRequestOptions) => buildHostDetailsQuery(options), + buildDsl: (options) => buildHostDetailsQuery(options), parse: async ( - options: HostDetailsRequestOptions, + options, response: IEsSearchResponse, deps?: { esClient: IScopedClusterClient; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts index cee0abba2fdb9..f38e0f7b401aa 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts @@ -11,7 +11,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { HostsKpiQueries, HostsKpiHostsStrategyResponse, - HostsKpiHostsRequestOptions, } from '../../../../../../../common/search_strategy/security_solution/hosts'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; @@ -19,9 +18,9 @@ import { buildHostsKpiHostsQuery } from './query.hosts_kpi_hosts.dsl'; import { formatGeneralHistogramData } from '../../../common/format_general_histogram_data'; export const hostsKpiHosts: SecuritySolutionFactory = { - buildDsl: (options: HostsKpiHostsRequestOptions) => buildHostsKpiHostsQuery(options), + buildDsl: (options) => buildHostsKpiHostsQuery(options), parse: async ( - options: HostsKpiHostsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts index 0383a58a65353..73e5c25e53683 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { HostsKpiHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import type { KpiHostsRequestOptions } from '../../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; export const buildHostsKpiHostsQuery = ({ filterQuery, timerange: { from, to }, defaultIndex, -}: HostsKpiHostsRequestOptions) => { +}: KpiHostsRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts index 2147c48b5763a..8d0eef56b75b9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts @@ -11,7 +11,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { HostsKpiQueries, HostsKpiUniqueIpsStrategyResponse, - HostsKpiUniqueIpsRequestOptions, } from '../../../../../../../common/search_strategy/security_solution/hosts'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; @@ -19,9 +18,9 @@ import { buildHostsKpiUniqueIpsQuery } from './query.hosts_kpi_unique_ips.dsl'; import { formatGeneralHistogramData } from '../../../common/format_general_histogram_data'; export const hostsKpiUniqueIps: SecuritySolutionFactory = { - buildDsl: (options: HostsKpiUniqueIpsRequestOptions) => buildHostsKpiUniqueIpsQuery(options), + buildDsl: (options) => buildHostsKpiUniqueIpsQuery(options), parse: async ( - options: HostsKpiUniqueIpsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts index 290f1dc238348..0d18765a1b6a4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { HostsKpiUniqueIpsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import type { KpiUniqueIpsRequestOptions } from '../../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; export const buildHostsKpiUniqueIpsQuery = ({ filterQuery, timerange: { from, to }, defaultIndex, -}: HostsKpiUniqueIpsRequestOptions) => { +}: KpiUniqueIpsRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts index ce87321c3b18f..d4b1e310ae5d6 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts @@ -6,8 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { HostOverviewRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { HostOverviewRequestOptions } from '../../../../../../../common/search_strategy'; import { HostsQueries } from '../../../../../../../common/search_strategy'; export const mockOptions: HostOverviewRequestOptions = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts index c7d08f92ab55a..83e3f6eb93b98 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts @@ -11,7 +11,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { HostsOverviewStrategyResponse, HostsQueries, - HostOverviewRequestOptions, OverviewHostHit, } from '../../../../../../common/search_strategy/security_solution/hosts'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -19,9 +18,9 @@ import type { SecuritySolutionFactory } from '../../types'; import { buildOverviewHostQuery } from './query.overview_host.dsl'; export const hostOverview: SecuritySolutionFactory = { - buildDsl: (options: HostOverviewRequestOptions) => buildOverviewHostQuery(options), + buildDsl: (options) => buildOverviewHostQuery(options), parse: async ( - options: HostOverviewRequestOptions, + options, response: IEsSearchResponse ): Promise => { // @ts-expect-error specify aggregations type explicitly diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts index 2ad25f8907ff2..c55703e17cc47 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts @@ -6,8 +6,8 @@ */ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; +import type { HostOverviewRequestOptions } from '../../../../../../common/api/search_strategy/hosts/hosts'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; -import type { HostOverviewRequestOptions } from '../../../../../../common/search_strategy/security_solution/hosts'; export const buildOverviewHostQuery = ({ filterQuery, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts index f51b8082d0ffa..b5fc2ea7dbf73 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { SortField } from '../../../../../../../common/search_strategy'; -import { HostsQueries } from '../../../../../../../common/search_strategy'; +import type { HostUncommonProcessesRequestOptions } from '../../../../../../../common/api/search_strategy'; +import { Direction, HostsQueries } from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: HostUncommonProcessesRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', @@ -28,12 +28,15 @@ export const mockOptions = { fakePossibleCount: 50, querySize: 10, }, + sort: { + direction: Direction.desc, + field: '@timestamp', + }, timerange: { interval: '12h', from: '2020-09-06T15:23:52.757Z', to: '2020-09-07T15:23:52.757Z', }, - sort: {} as SortField, }; export const mockSearchStrategyResponse = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts index abb65529084c3..c7364dd8b375d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.ts @@ -6,17 +6,19 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { hostFieldsMap, processFieldsMap, userFieldsMap } from '@kbn/securitysolution-ecs'; +import type { HostUncommonProcessesRequestOptions } from '../../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; import { reduceFields } from '../../../../../../utils/build_query/reduce_fields'; -import type { RequestOptionsPaginated } from '../../../../../../../common/search_strategy/security_solution'; import { UNCOMMON_PROCESSES_FIELDS } from '../helpers'; export const buildQuery = ({ defaultIndex, filterQuery, - pagination: { querySize }, + pagination, timerange: { from, to }, -}: RequestOptionsPaginated) => { +}: HostUncommonProcessesRequestOptions) => { + const querySize = pagination?.querySize ?? 10; + const processUserFields = reduceFields(UNCOMMON_PROCESSES_FIELDS, { ...processFieldsMap, ...userFieldsMap, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts index 4f3f88eaa29c9..3033c40bfba52 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts @@ -7,7 +7,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; -import type { HostsUncommonProcessesRequestOptions } from '../../../../../../common/search_strategy/security_solution'; import * as buildQuery from './dsl/query.dsl'; import { uncommonProcesses } from '.'; import { @@ -15,6 +14,7 @@ import { mockSearchStrategyResponse, formattedSearchStrategyResponse, } from './__mocks__'; +import type { HostUncommonProcessesRequestOptions } from '../../../../../../common/api/search_strategy'; describe('uncommonProcesses search strategy', () => { const buildUncommonProcessesQuery = jest.spyOn(buildQuery, 'buildQuery'); @@ -36,7 +36,7 @@ describe('uncommonProcesses search strategy', () => { ...mockOptions.pagination, querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, }, - } as HostsUncommonProcessesRequestOptions; + } as HostUncommonProcessesRequestOptions; expect(() => { uncommonProcesses.buildDsl(overSizeOptions); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts index dfa63911c8d8a..920dbd351bf97 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts @@ -12,10 +12,7 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { processFieldsMap, userFieldsMap } from '@kbn/securitysolution-ecs'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import type { HostsQueries } from '../../../../../../common/search_strategy/security_solution'; -import type { - HostsUncommonProcessesRequestOptions, - HostsUncommonProcessesStrategyResponse, -} from '../../../../../../common/search_strategy/security_solution/hosts/uncommon_processes'; +import type { HostsUncommonProcessesStrategyResponse } from '../../../../../../common/search_strategy/security_solution/hosts/uncommon_processes'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -24,14 +21,14 @@ import { buildQuery } from './dsl/query.dsl'; import { formatUncommonProcessesData, getHits } from './helpers'; export const uncommonProcesses: SecuritySolutionFactory = { - buildDsl: (options: HostsUncommonProcessesRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildQuery(options); }, parse: async ( - options: HostsUncommonProcessesRequestOptions, + options, response: IEsSearchResponse ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/__mocks__/index.ts index e1c192af513bd..86cbb97ed68b9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/__mocks__/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { FirstLastSeenRequestOptions } from '../../../../../../common/search_strategy'; +import type { FirstLastSeenRequestOptionsInput } from '../../../../../../common/api/search_strategy'; import { Direction, FirstLastSeenQuery } from '../../../../../../common/search_strategy'; -export const mockOptions: FirstLastSeenRequestOptions = { +export const mockOptions: FirstLastSeenRequestOptionsInput = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.test.ts index c8edf6a96b2e2..658b7cc008bb8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.test.ts @@ -7,7 +7,6 @@ import { ZodError } from 'zod'; -import type { FirstLastSeenRequestOptions } from '../../../../../common/search_strategy'; import { Direction } from '../../../../../common/search_strategy'; import * as buildQuery from './query.first_or_last_seen.dsl'; import { firstOrLastSeen } from '.'; @@ -18,6 +17,7 @@ import { formattedSearchStrategyLastResponse, formattedSearchStrategyFirstResponse, } from './__mocks__'; +import type { FirstLastSeenRequestOptionsInput } from '../../../../../common/api/search_strategy'; describe('firstLastSeen search strategy', () => { describe('first seen search strategy', () => { @@ -54,7 +54,7 @@ describe('firstLastSeen search strategy', () => { describe('buildDsl', () => { test('should build dsl query', () => { - const options: FirstLastSeenRequestOptions = { ...mockOptions, order: Direction.desc }; + const options: FirstLastSeenRequestOptionsInput = { ...mockOptions, order: Direction.desc }; firstOrLastSeen.buildDsl(options); expect(buildFirstLastSeenQuery).toHaveBeenCalledWith(options); }); @@ -72,7 +72,7 @@ describe('firstLastSeen search strategy', () => { test('should throw an error when parse fails', async () => { try { await firstOrLastSeen.parse( - { invalidOption: 'key' } as unknown as FirstLastSeenRequestOptions, + { invalidOption: 'key' } as unknown as FirstLastSeenRequestOptionsInput, mockSearchStrategyLastSeenResponse ); } catch (error: unknown) { @@ -84,6 +84,9 @@ describe('firstLastSeen search strategy', () => { expect(error.flatten()).toMatchInlineSnapshot(` Object { "fieldErrors": Object { + "factoryQueryType": Array [ + "Invalid literal value, expected \\"firstlastseen\\"", + ], "field": Array [ "Required", ], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.ts index 396dfed867c18..9cd28bfc1c121 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.ts @@ -8,6 +8,7 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import { firstLastSeenRequestOptionsSchema } from '../../../../../common/api/search_strategy'; import type { FactoryQueryTypes, FirstLastSeenStrategyResponse, @@ -17,14 +18,15 @@ import { FirstLastSeenQuery } from '../../../../../common/search_strategy/securi import { inspectStringifyObject } from '../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../types'; import { buildFirstOrLastSeenQuery } from './query.first_or_last_seen.dsl'; -import { parseOptions } from './parse_options'; export const firstOrLastSeen: SecuritySolutionFactory = { - buildDsl: (options: unknown) => buildFirstOrLastSeenQuery(options), + buildDsl: (options) => buildFirstOrLastSeenQuery(options), parse: async ( - options: unknown, + options, response: IEsSearchResponse ): Promise => { + firstLastSeenRequestOptionsSchema.parse(options); + // First try to get the formatted field if it exists or not. const formattedField: string | null = getOr( null, @@ -36,7 +38,7 @@ export const firstOrLastSeen: SecuritySolutionFactory dsl: [inspectStringifyObject(buildFirstOrLastSeenQuery(options))], }; - const { order } = parseOptions(options); + const { order } = options; if (order === 'asc') { return { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/parse_options.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/parse_options.ts deleted file mode 100644 index 604932496230c..0000000000000 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/parse_options.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { firstLastSeenRequestOptionsSchema } from '../../../../../common/api/search_strategy/first_seen_last_seen/first_seen_last_seen'; - -export const parseOptions = (options: unknown) => firstLastSeenRequestOptionsSchema.parse(options); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts index cee6d19ce54ae..8ea331207d302 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts @@ -5,14 +5,12 @@ * 2.0. */ -import type { FirstLastSeenRequestOptions } from '../../../../../common/api/search_strategy/first_seen_last_seen/first_seen_last_seen'; +import type { FirstLastSeenRequestOptions } from '../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../utils/build_query'; -import { parseOptions } from './parse_options'; -export const buildFirstOrLastSeenQuery = (options: unknown) => { - const { field, value, defaultIndex, order, filterQuery }: FirstLastSeenRequestOptions = - parseOptions(options); +export const buildFirstOrLastSeenQuery = (options: FirstLastSeenRequestOptions) => { + const { field, value, defaultIndex, order, filterQuery } = options; const filter = [...createQueryFilterClauses(filterQuery), { term: { [field]: value } }]; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts index 4f6b910fbccf7..dda3395b54c1a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts @@ -5,9 +5,11 @@ * 2.0. */ +import type { MatrixHistogramRequestOptions } from '../../../../../../../common/api/search_strategy'; +import { MatrixHistogramQuery } from '../../../../../../../common/api/search_strategy'; import { MatrixHistogramType } from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: MatrixHistogramRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', @@ -23,6 +25,9 @@ export const mockOptions = { histogramType: MatrixHistogramType.alerts, timerange: { interval: '12h', from: '2020-09-08T14:23:04.482Z', to: '2020-09-09T14:23:04.482Z' }, stackByField: 'event.module', + includeMissingData: false, + isPtrIncluded: false, + factoryQueryType: MatrixHistogramQuery, }; export const expectedDsl = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/query.alerts_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/query.alerts_histogram.dsl.ts index 792829ed32f29..bee0f1ac7f457 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/query.alerts_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/query.alerts_histogram.dsl.ts @@ -6,12 +6,12 @@ */ import moment from 'moment'; +import type { MatrixHistogramRequestOptions } from '../../../../../../common/api/search_strategy'; import { createQueryFilterClauses, calculateTimeSeriesInterval, } from '../../../../../utils/build_query'; -import type { MatrixHistogramRequestOptions } from '../../../../../../common/search_strategy/security_solution/matrix_histogram'; export const buildAlertsHistogramQuery = ({ filterQuery, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts index 700580655f1b0..69b1977f17083 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts @@ -5,9 +5,11 @@ * 2.0. */ +import type { MatrixHistogramRequestOptions } from '../../../../../../../common/api/search_strategy'; +import { MatrixHistogramQuery } from '../../../../../../../common/api/search_strategy'; import { MatrixHistogramType } from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: MatrixHistogramRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', @@ -23,6 +25,9 @@ export const mockOptions = { histogramType: MatrixHistogramType.anomalies, timerange: { interval: '12h', from: '2020-09-08T15:14:35.566Z', to: '2020-09-09T15:14:35.566Z' }, stackByField: 'job_id', + includeMissingData: false, + isPtrIncluded: false, + factoryQueryType: MatrixHistogramQuery, }; export const expectedDsl = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/query.anomalies_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/query.anomalies_histogram.dsl.ts index 87e2664d74271..da1b72341c71a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/query.anomalies_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/query.anomalies_histogram.dsl.ts @@ -11,7 +11,7 @@ import { createQueryFilterClauses, calculateTimeSeriesInterval, } from '../../../../../utils/build_query'; -import type { MatrixHistogramRequestOptions } from '../../../../../../common/search_strategy/security_solution/matrix_histogram'; +import type { MatrixHistogramRequestOptions } from '../../../../../../common/api/search_strategy'; export const buildAnomaliesHistogramQuery = ({ filterQuery, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts index b917da12c9ad7..0f7145dc95320 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts @@ -5,9 +5,11 @@ * 2.0. */ +import type { MatrixHistogramRequestOptions } from '../../../../../../../common/api/search_strategy'; +import { MatrixHistogramQuery } from '../../../../../../../common/api/search_strategy'; import { MatrixHistogramType } from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: MatrixHistogramRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', @@ -22,6 +24,9 @@ export const mockOptions = { histogramType: MatrixHistogramType.authentications, timerange: { interval: '12h', from: '2020-09-08T15:22:00.325Z', to: '2020-09-09T15:22:00.325Z' }, stackByField: 'event.outcome', + includeMissingData: false, + isPtrIncluded: false, + factoryQueryType: MatrixHistogramQuery, }; export const expectedDsl = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/query.authentications_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/query.authentications_histogram.dsl.ts index 9ebd629d1e858..a0bae6ad6d322 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/query.authentications_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/query.authentications_histogram.dsl.ts @@ -11,7 +11,7 @@ import { createQueryFilterClauses, calculateTimeSeriesInterval, } from '../../../../../utils/build_query'; -import type { MatrixHistogramRequestOptions } from '../../../../../../common/search_strategy/security_solution/matrix_histogram'; +import type { MatrixHistogramRequestOptions } from '../../../../../../common/api/search_strategy'; export const buildAuthenticationsHistogramQuery = ({ filterQuery, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts index d4e721a5ebe80..5823206d58b63 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts @@ -5,9 +5,11 @@ * 2.0. */ +import type { MatrixHistogramRequestOptions } from '../../../../../../../common/api/search_strategy'; +import { MatrixHistogramQuery } from '../../../../../../../common/api/search_strategy'; import { MatrixHistogramType } from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: MatrixHistogramRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', @@ -23,6 +25,8 @@ export const mockOptions = { isPtrIncluded: false, timerange: { interval: '12h', from: '2020-09-08T15:41:15.528Z', to: '2020-09-09T15:41:15.529Z' }, stackByField: 'dns.question.registered_domain', + includeMissingData: false, + factoryQueryType: MatrixHistogramQuery, }; export const expectedDsl = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/query.dns_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/query.dns_histogram.dsl.ts index c547f0a6ada3f..825f121c87fb3 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/query.dns_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/query.dns_histogram.dsl.ts @@ -7,7 +7,7 @@ import moment from 'moment'; -import type { MatrixHistogramRequestOptions } from '../../../../../../common/search_strategy'; +import type { MatrixHistogramRequestOptions } from '../../../../../../common/api/search_strategy'; import { calculateTimeSeriesInterval, createQueryFilterClauses, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts index cb469a1708334..e1b25916ec978 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { MatrixHistogramRequestOptions } from '../../../../../../../common/search_strategy'; +import type { MatrixHistogramRequestOptions } from '../../../../../../../common/api/search_strategy'; import { MatrixHistogramQuery, MatrixHistogramType, @@ -37,6 +37,8 @@ export const mockOptions: MatrixHistogramRequestOptions = { timerange: { interval: '12h', from: '2020-09-08T16:11:26.215Z', to: '2020-09-09T16:11:26.215Z' }, stackByField: 'event.action', runtimeMappings, + includeMissingData: true, + isPtrIncluded: false, }; export const expectedDsl = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts index 2c7d3e017def2..fe09a005770fe 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts @@ -12,7 +12,7 @@ import { createQueryFilterClauses, calculateTimeSeriesInterval, } from '../../../../../utils/build_query'; -import type { MatrixHistogramRequestOptions } from '../../../../../../common/search_strategy/security_solution/matrix_histogram'; +import type { MatrixHistogramRequestOptions } from '../../../../../../common/api/search_strategy'; import * as i18n from './translations'; import type { BaseQuery } from './helpers'; import { buildThresholdCardinalityQuery, buildThresholdTermsQuery } from './helpers'; @@ -23,7 +23,7 @@ export const buildEventsHistogramQuery = ({ defaultIndex, stackByField = 'event.action', threshold, - includeMissingData = true, + includeMissingData, runtimeMappings, }: MatrixHistogramRequestOptions) => { const [queryFilterFirstClause, ...queryFilterClauses] = createQueryFilterClauses(filterQuery); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.test.ts index 885322e92cf6c..4b09dcf1971d9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.test.ts @@ -5,10 +5,7 @@ * 2.0. */ -import type { - MatrixHistogramRequestOptions, - MatrixHistogramType, -} from '../../../../../common/search_strategy/security_solution'; +import type { MatrixHistogramType } from '../../../../../common/search_strategy/security_solution'; import { matrixHistogram } from '.'; import { formattedAlertsSearchStrategyResponse, @@ -36,6 +33,7 @@ import { mockOptions as mockAuthenticationsOptions } from './authentications/__m import { mockOptions as mockEventsOptions } from './events/__mocks__'; import { mockOptions as mockDnsOptions } from './dns/__mocks__'; import { mockOptions as mockPreviewOptions } from './preview/__mocks__'; +import type { MatrixHistogramRequestOptions } from '../../../../../common/api/search_strategy/matrix_histogram/matrix_histogram'; describe('Alerts matrixHistogram search strategy', () => { const buildMatrixHistogramQuery = jest.spyOn(alertsMatrixHistogramConfig, 'buildDsl'); @@ -58,7 +56,7 @@ describe('Alerts matrixHistogram search strategy', () => { expect(() => { matrixHistogram.buildDsl(invalidOptions); - }).toThrowError(`This histogram type xxx is unknown to the server side`); + }).toThrowError(/This histogram type xxx is unknown to the server side/); }); }); @@ -94,7 +92,7 @@ describe('Anomalies matrixHistogram search strategy', () => { expect(() => { matrixHistogram.buildDsl(invalidOptions); - }).toThrowError(`This histogram type xxx is unknown to the server side`); + }).toThrowError(/This histogram type xxx is unknown to the server side/); }); }); @@ -130,7 +128,7 @@ describe('Authentications matrixHistogram search strategy', () => { expect(() => { matrixHistogram.buildDsl(invalidOptions); - }).toThrowError(`This histogram type xxx is unknown to the server side`); + }).toThrowError(/This histogram type xxx is unknown to the server side/); }); }); @@ -166,7 +164,7 @@ describe('Events matrixHistogram search strategy', () => { expect(() => { matrixHistogram.buildDsl(invalidOptions); - }).toThrowError(`This histogram type xxx is unknown to the server side`); + }).toThrowError(/This histogram type xxx is unknown to the server side/); }); }); @@ -202,7 +200,7 @@ describe('Dns matrixHistogram search strategy', () => { expect(() => { matrixHistogram.buildDsl(invalidOptions); - }).toThrowError(`This histogram type xxx is unknown to the server side`); + }).toThrowError(/This histogram type xxx is unknown to the server side/); }); }); @@ -235,7 +233,7 @@ describe('Preview matrixHistogram search strategy', () => { expect(() => { matrixHistogram.buildDsl(invalidOptions); - }).toThrowError(`This histogram type xxx is unknown to the server side`); + }).toThrowError(/This histogram type xxx is unknown to the server side/); }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts index a389200c87795..567bdcbd1f78b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/index.ts @@ -10,7 +10,6 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { FactoryQueryTypes, - MatrixHistogramRequestOptions, MatrixHistogramStrategyResponse, MatrixHistogramDataConfig, } from '../../../../../common/search_strategy/security_solution'; @@ -38,7 +37,7 @@ const matrixHistogramConfig: MatrixHistogramDataConfig = { }; export const matrixHistogram: SecuritySolutionFactory = { - buildDsl: (options: MatrixHistogramRequestOptions) => { + buildDsl: (options) => { const myConfig = getOr(null, options.histogramType, matrixHistogramConfig); if (myConfig == null) { throw new Error(`This histogram type ${options.histogramType} is unknown to the server side`); @@ -46,7 +45,7 @@ export const matrixHistogram: SecuritySolutionFactory ): Promise => { const myConfig = getOr(null, options.histogramType, matrixHistogramConfig); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts index 95f5bccb18303..4b7ef376b8cd3 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts @@ -5,15 +5,20 @@ * 2.0. */ +import type { MatrixHistogramRequestOptions } from '../../../../../../../common/api/search_strategy'; +import { MatrixHistogramQuery } from '../../../../../../../common/api/search_strategy'; import { MatrixHistogramType } from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: MatrixHistogramRequestOptions = { defaultIndex: ['.siem-preview-signals-default'], filterQuery: '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"match":{"signal.rule.id":"test-preview-id"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}', histogramType: MatrixHistogramType.preview, timerange: { interval: '12h', from: '2020-09-08T14:23:04.482Z', to: '2020-09-09T14:23:04.482Z' }, stackByField: 'event.category', + includeMissingData: false, + isPtrIncluded: false, + factoryQueryType: MatrixHistogramQuery, }; export const expectedDsl = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts index 2854ee25f9c43..56f1ce122bfd0 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts @@ -12,7 +12,7 @@ import { createQueryFilterClauses, calculateTimeSeriesInterval, } from '../../../../../utils/build_query'; -import type { MatrixHistogramRequestOptions } from '../../../../../../common/search_strategy/security_solution/matrix_histogram'; +import type { MatrixHistogramRequestOptions } from '../../../../../../common/api/search_strategy'; export const buildPreviewHistogramQuery = ({ filterQuery, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts index 3e3ccbe7a41e1..892ce7b8f1d86 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts @@ -6,8 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkDetailsRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { NetworkDetailsRequestOptions } from '../../../../../../../common/search_strategy'; import { NetworkQueries } from '../../../../../../../common/search_strategy'; export const mockOptions: NetworkDetailsRequestOptions = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/index.ts index 5201eca70a210..e7fa6c3e8d094 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/index.ts @@ -12,7 +12,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { NetworkDetailsStrategyResponse, NetworkQueries, - NetworkDetailsRequestOptions, } from '../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -23,9 +22,9 @@ import { buildNetworkDetailsQuery } from './query.details_network.dsl'; import { unflattenObject } from '../../../../helpers/format_response_object_values'; export const networkDetails: SecuritySolutionFactory = { - buildDsl: (options: NetworkDetailsRequestOptions) => buildNetworkDetailsQuery(options), + buildDsl: (options) => buildNetworkDetailsQuery(options), parse: async ( - options: NetworkDetailsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts index 9bac6909271ba..3d964fd81e583 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { NetworkDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/network'; +import type { NetworkDetailsRequestOptions } from '../../../../../../common/api/search_strategy'; const getAggs = (type: string, ip: string) => { return { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts index f49f2bc153ba4..7d89aae61439e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts @@ -6,8 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkDnsRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { NetworkDnsRequestOptions } from '../../../../../../../common/search_strategy'; import { Direction, NetworkDnsFields, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts index 6ff153f8eab4c..59b837ce7fbdc 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts @@ -12,27 +12,25 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import type { NetworkDnsStrategyResponse, - NetworkQueries, - NetworkDnsRequestOptions, NetworkDnsEdges, + NetworkQueries, } from '../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../utils/build_query'; -import type { SecuritySolutionFactory } from '../../types'; import { getDnsEdges } from './helpers'; import { buildDnsQuery } from './query.dns_network.dsl'; +import type { SecuritySolutionFactory } from '../../types'; export const networkDns: SecuritySolutionFactory = { - // @ts-expect-error dns_name_query_count is incompatbile. Maybe' is not assignable to type 'string | undefined - buildDsl: (options: NetworkDnsRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildDnsQuery(options); }, parse: async ( - options: NetworkDnsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const { activePage, fakePossibleCount } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts index 6197fe8c603df..122bc739c7187 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { assertUnreachable } from '../../../../../../common/utility_types'; -import type { SortField, NetworkDnsRequestOptions } from '../../../../../../common/search_strategy'; +import type { NetworkDnsRequestOptions } from '../../../../../../common/api/search_strategy'; import { Direction, NetworkDnsFields } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; @@ -19,20 +18,20 @@ type QueryOrder = | { dns_bytes_in: { order: Direction } } | { dns_bytes_out: { order: Direction } }; -const getQueryOrder = (sort: SortField): QueryOrder => { - switch (sort.field) { - case NetworkDnsFields.queryCount: - return { _count: { order: sort.direction } }; - case NetworkDnsFields.dnsName: - return { _key: { order: sort.direction } }; - case NetworkDnsFields.uniqueDomains: - return { unique_domains: { order: sort.direction } }; - case NetworkDnsFields.dnsBytesIn: - return { dns_bytes_in: { order: sort.direction } }; - case NetworkDnsFields.dnsBytesOut: - return { dns_bytes_out: { order: sort.direction } }; +const getQueryOrder = (sort: NetworkDnsRequestOptions['sort']): QueryOrder => { + if (sort.field === NetworkDnsFields.queryCount) { + return { _count: { order: sort.direction } }; + } else if (sort.field === NetworkDnsFields.dnsName) { + return { _key: { order: sort.direction } }; + } else if (sort.field === NetworkDnsFields.uniqueDomains) { + return { unique_domains: { order: sort.direction } }; + } else if (sort.field === NetworkDnsFields.dnsBytesIn) { + return { dns_bytes_in: { order: sort.direction } }; + } else if (sort.field === NetworkDnsFields.dnsBytesOut) { + return { dns_bytes_out: { order: sort.direction } }; + } else { + throw new Error(`Invalid NetworkDnsQuery sort field: ${JSON.stringify(sort)}`); } - assertUnreachable(sort.field); }; const getCountAgg = () => ({ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts index 32837920d5fc8..01ceb455b080c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts @@ -6,13 +6,14 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkHttpRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { +import type { SortField } from '../../../../../../../common/search_strategy'; +import { + Direction, + NetworkQueries, NetworkDnsFields, - NetworkHttpRequestOptions, - SortField, } from '../../../../../../../common/search_strategy'; -import { Direction, NetworkQueries } from '../../../../../../../common/search_strategy'; export const mockOptions: NetworkHttpRequestOptions = { defaultIndex: [ @@ -28,7 +29,10 @@ export const mockOptions: NetworkHttpRequestOptions = { factoryQueryType: NetworkQueries.http, filterQuery: '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 50, querySize: 10 }, - sort: { direction: Direction.desc } as SortField, + sort: { + direction: Direction.desc, + field: NetworkDnsFields.dnsName, + } as SortField, timerange: { interval: '12h', from: '2020-09-13T09:00:43.249Z', to: '2020-09-14T09:00:43.249Z' }, } as NetworkHttpRequestOptions; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts index 9fd347137504e..170b0ba67c329 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts @@ -13,7 +13,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import type { NetworkHttpStrategyResponse, NetworkQueries, - NetworkHttpRequestOptions, NetworkHttpEdges, } from '../../../../../../common/search_strategy/security_solution/network'; @@ -24,14 +23,14 @@ import { getHttpEdges } from './helpers'; import { buildHttpQuery } from './query.http_network.dsl'; export const networkHttp: SecuritySolutionFactory = { - buildDsl: (options: NetworkHttpRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildHttpQuery(options); }, parse: async ( - options: NetworkHttpRequestOptions, + options, response: IEsSearchResponse ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/query.http_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/query.http_network.dsl.ts index 372c6a096f1b5..4128de4c2ffbe 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/query.http_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/query.http_network.dsl.ts @@ -5,12 +5,10 @@ * 2.0. */ +import type { NetworkHttpRequestOptions } from '../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; -import type { - NetworkHttpRequestOptions, - SortField, -} from '../../../../../../common/search_strategy'; +import type { SortField } from '../../../../../../common/search_strategy'; const getCountAgg = () => ({ http_count: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts index 9cc45f3594c33..c9ff1d1409ce9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { FactoryQueryTypes } from '../../../../../common/search_strategy/security_solution'; import { NetworkQueries, NetworkKpiQueries, @@ -16,7 +15,6 @@ import { networkKpiNetworkEvents } from './kpi/network_events'; import { networkKpiTlsHandshakes } from './kpi/tls_handshakes'; import { networkKpiUniqueFlows } from './kpi/unique_flows'; import { networkKpiUniquePrivateIps } from './kpi/unique_private_ips'; -import type { SecuritySolutionFactory } from '../types'; import { networkDetails } from './details'; import { networkDns } from './dns'; import { networkHttp } from './http'; @@ -26,10 +24,9 @@ import { networkTopCountries } from './top_countries'; import { networkTopNFlow } from './top_n_flow'; import { networkUsers } from './users'; -export const networkFactory: Record< - NetworkQueries | NetworkKpiQueries, - SecuritySolutionFactory -> = { +// TODO: add safer type for the strategy map +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const networkFactory: Record = { [NetworkQueries.details]: networkDetails, [NetworkQueries.dns]: networkDns, [NetworkQueries.http]: networkHttp, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts index 110b5fb8b7ae4..09dcc714b444c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts @@ -9,16 +9,15 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { NetworkKpiQueries, NetworkKpiDnsStrategyResponse, - NetworkKpiDnsRequestOptions, } from '../../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; import { buildDnsQuery } from './query.network_kpi_dns.dsl'; export const networkKpiDns: SecuritySolutionFactory = { - buildDsl: (options: NetworkKpiDnsRequestOptions) => buildDnsQuery(options), + buildDsl: (options) => buildDnsQuery(options), parse: async ( - options: NetworkKpiDnsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts index 30f0fe405e122..a55cb8f026664 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts @@ -5,7 +5,8 @@ * 2.0. */ -import type { NetworkKpiDnsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import type { NetworkKpiDnsRequestOptions } from '../../../../../../../common/api/search_strategy'; + import { createQueryFilterClauses } from '../../../../../../utils/build_query'; const getDnsQueryFilter = () => [ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts index dd4570c259116..6751c829cc350 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts @@ -9,16 +9,15 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { NetworkKpiQueries, NetworkKpiNetworkEventsStrategyResponse, - NetworkKpiNetworkEventsRequestOptions, } from '../../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; import { buildNetworkEventsQuery } from './query.network_kpi_network_events.dsl'; export const networkKpiNetworkEvents: SecuritySolutionFactory = { - buildDsl: (options: NetworkKpiNetworkEventsRequestOptions) => buildNetworkEventsQuery(options), + buildDsl: (options) => buildNetworkEventsQuery(options), parse: async ( - options: NetworkKpiNetworkEventsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts index 4d996e7438688..96f67b8e17600 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { NetworkKpiNetworkEventsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import type { NetworkKpiEventsRequestOptions } from '../../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; import { getIpFilter } from '../common'; @@ -13,7 +13,7 @@ export const buildNetworkEventsQuery = ({ filterQuery, timerange: { from, to }, defaultIndex, -}: NetworkKpiNetworkEventsRequestOptions) => { +}: NetworkKpiEventsRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), ...getIpFilter(), diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts index 9e90f088ecd5e..3b8e65fb163af 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts @@ -9,16 +9,15 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { NetworkKpiQueries, NetworkKpiTlsHandshakesStrategyResponse, - NetworkKpiTlsHandshakesRequestOptions, } from '../../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; import { buildTlsHandshakeQuery } from './query.network_kpi_tls_handshakes.dsl'; export const networkKpiTlsHandshakes: SecuritySolutionFactory = { - buildDsl: (options: NetworkKpiTlsHandshakesRequestOptions) => buildTlsHandshakeQuery(options), + buildDsl: (options) => buildTlsHandshakeQuery(options), parse: async ( - options: NetworkKpiTlsHandshakesRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts index 6c60ddeb89a14..2797a2e814506 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { NetworkKpiTlsHandshakesRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import type { NetworkKpiTlsHandshakesRequestOptions } from '../../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; import { getIpFilter } from '../common'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts index f03629d92c7e0..e245d1af846f8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts @@ -11,16 +11,15 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { NetworkKpiQueries, NetworkKpiUniqueFlowsStrategyResponse, - NetworkKpiUniqueFlowsRequestOptions, } from '../../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; import { buildUniqueFlowsQuery } from './query.network_kpi_unique_flows.dsl'; export const networkKpiUniqueFlows: SecuritySolutionFactory = { - buildDsl: (options: NetworkKpiUniqueFlowsRequestOptions) => buildUniqueFlowsQuery(options), + buildDsl: (options) => buildUniqueFlowsQuery(options), parse: async ( - options: NetworkKpiUniqueFlowsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts index c713ecd6a1c07..29e4c386fc348 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts @@ -5,7 +5,8 @@ * 2.0. */ -import type { NetworkKpiUniqueFlowsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import type { NetworkKpiUniqueFlowsRequestOptions } from '../../../../../../../common/api/search_strategy'; + import { createQueryFilterClauses } from '../../../../../../utils/build_query'; import { getIpFilter } from '../common'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts index bef9505d5283f..a3d72e6e15898 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts @@ -11,7 +11,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { NetworkKpiQueries, NetworkKpiUniquePrivateIpsStrategyResponse, - NetworkKpiUniquePrivateIpsRequestOptions, } from '../../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; @@ -21,10 +20,9 @@ import { buildUniquePrivateIpsQuery } from './query.network_kpi_unique_private_i export const networkKpiUniquePrivateIps: SecuritySolutionFactory = { // @ts-expect-error auto_date_histogram.buckets is incompatible - buildDsl: (options: NetworkKpiUniquePrivateIpsRequestOptions) => - buildUniquePrivateIpsQuery(options), + buildDsl: (options) => buildUniquePrivateIpsQuery(options), parse: async ( - options: NetworkKpiUniquePrivateIpsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts index 97e4619cdd9c4..6b31e5af05797 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts @@ -5,10 +5,8 @@ * 2.0. */ -import type { - NetworkKpiUniquePrivateIpsRequestOptions, - UniquePrivateAttributeQuery, -} from '../../../../../../../common/search_strategy/security_solution/network'; +import type { NetworkKpiUniquePrivateIpsRequestOptions } from '../../../../../../../common/api/search_strategy'; +import type { UniquePrivateAttributeQuery } from '../../../../../../../common/search_strategy/security_solution/network'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; const getUniquePrivateIpsFilter = (attrQuery: UniquePrivateAttributeQuery) => ({ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts index 7f1865e658843..4574a6491a40b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts @@ -6,8 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkOverviewRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { NetworkOverviewRequestOptions } from '../../../../../../../common/search_strategy'; import { NetworkQueries } from '../../../../../../../common/search_strategy'; export const mockOptions: NetworkOverviewRequestOptions = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts index 6fd27fa4dea46..b0c5244593464 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts @@ -11,7 +11,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { NetworkQueries, NetworkOverviewStrategyResponse, - NetworkOverviewRequestOptions, OverviewNetworkHit, } from '../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -19,9 +18,9 @@ import type { SecuritySolutionFactory } from '../../types'; import { buildOverviewNetworkQuery } from './query.overview_network.dsl'; export const networkOverview: SecuritySolutionFactory = { - buildDsl: (options: NetworkOverviewRequestOptions) => buildOverviewNetworkQuery(options), + buildDsl: (options) => buildOverviewNetworkQuery(options), parse: async ( - options: NetworkOverviewRequestOptions, + options, response: IEsSearchResponse ): Promise => { // @ts-expect-error specify aggregations type explicitly diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.dsl.ts index 95b7d41ee440a..ff236881f05a4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.dsl.ts @@ -6,8 +6,8 @@ */ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; +import type { NetworkOverviewRequestOptions } from '../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; -import type { NetworkOverviewRequestOptions } from '../../../../../../common/search_strategy/security_solution/network'; export const buildOverviewNetworkQuery = ({ filterQuery, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts index 9957534bccc2e..4f7f61be13451 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts @@ -6,8 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkTlsRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { NetworkTlsRequestOptions } from '../../../../../../../common/search_strategy'; import { Direction, FlowTargetSourceDest, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts index 1dd07133d1d0c..0d39ffc062bc4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts @@ -13,7 +13,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import type { NetworkTlsStrategyResponse, NetworkQueries, - NetworkTlsRequestOptions, NetworkTlsEdges, } from '../../../../../../common/search_strategy/security_solution/network'; @@ -24,14 +23,14 @@ import { getNetworkTlsEdges } from './helpers'; import { buildNetworkTlsQuery } from './query.tls_network.dsl'; export const networkTls: SecuritySolutionFactory = { - buildDsl: (options: NetworkTlsRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildNetworkTlsQuery(options); }, parse: async ( - options: NetworkTlsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts index 9801cc7c0361f..faa07bd770fa2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts @@ -5,17 +5,14 @@ * 2.0. */ +import type { NetworkTlsRequestOptions } from '../../../../../../common/api/search_strategy'; import { assertUnreachable } from '../../../../../../common/utility_types'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; -import type { - Direction, - NetworkTlsRequestOptions, - SortField, -} from '../../../../../../common/search_strategy'; +import type { Direction } from '../../../../../../common/search_strategy'; import { NetworkTlsFields } from '../../../../../../common/search_strategy'; -const getAggs = (querySize: number, sort: SortField) => ({ +const getAggs = (querySize: number, sort: NetworkTlsRequestOptions['sort']) => ({ count: { cardinality: { field: 'tls.server.hash.sha1', @@ -99,11 +96,11 @@ interface QueryOrder { _key: Direction; } -const getQueryOrder = (sort: SortField): QueryOrder => { +const getQueryOrder = (sort: NetworkTlsRequestOptions['sort']): QueryOrder => { switch (sort.field) { case NetworkTlsFields._id: return { _key: sort.direction }; default: - return assertUnreachable(sort.field); + return assertUnreachable(sort.field as never); } }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts index 81802dd4aed9d..8835a98621ea3 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts @@ -6,8 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkTopCountriesRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { NetworkTopCountriesRequestOptions } from '../../../../../../../common/search_strategy'; import { Direction, FlowTargetSourceDest, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/helpers.ts index cc5f2a44783ee..d6be351ffea36 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/helpers.ts @@ -8,10 +8,10 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkTopCountriesRequestOptions } from '../../../../../../common/api/search_strategy'; import type { NetworkTopCountriesBuckets, NetworkTopCountriesEdges, - NetworkTopCountriesRequestOptions, FlowTargetSourceDest, } from '../../../../../../common/search_strategy/security_solution/network'; import { getOppositeField } from '../helpers'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/index.ts index cde9fb4bb44eb..c12db1dae90c9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/index.ts @@ -13,7 +13,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import type { NetworkTopCountriesStrategyResponse, NetworkQueries, - NetworkTopCountriesRequestOptions, NetworkTopCountriesEdges, } from '../../../../../../common/search_strategy/security_solution/network'; @@ -24,14 +23,14 @@ import { getTopCountriesEdges } from './helpers'; import { buildTopCountriesQuery } from './query.top_countries_network.dsl'; export const networkTopCountries: SecuritySolutionFactory = { - buildDsl: (options: NetworkTopCountriesRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildTopCountriesQuery(options); }, parse: async ( - options: NetworkTopCountriesRequestOptions, + options, response: IEsSearchResponse ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/query.top_countries_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/query.top_countries_network.dsl.ts index 9df7726427038..72f339fb939bc 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/query.top_countries_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/query.top_countries_network.dsl.ts @@ -5,13 +5,10 @@ * 2.0. */ +import type { NetworkTopCountriesRequestOptions } from '../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; import { assertUnreachable } from '../../../../../../common/utility_types'; -import type { - Direction, - NetworkTopCountriesRequestOptions, - SortField, -} from '../../../../../../common/search_strategy'; +import type { Direction } from '../../../../../../common/search_strategy'; import { FlowTargetSourceDest, NetworkTopTablesFields, @@ -77,7 +74,7 @@ export const buildTopCountriesQuery = ({ }; const getFlowTargetAggs = ( - sort: SortField, + sort: NetworkTopCountriesRequestOptions['sort'], flowTarget: FlowTargetSourceDest, querySize: number ) => ({ @@ -137,7 +134,7 @@ type QueryOrder = | { source_ips: Direction }; const getQueryOrder = ( - networkTopCountriesSortField: SortField + networkTopCountriesSortField: NetworkTopCountriesRequestOptions['sort'] ): QueryOrder => { switch (networkTopCountriesSortField.field) { case NetworkTopTablesFields.bytes_in: @@ -151,5 +148,8 @@ const getQueryOrder = ( case NetworkTopTablesFields.source_ips: return { source_ips: networkTopCountriesSortField.direction }; } - assertUnreachable(networkTopCountriesSortField.field); + + throw new Error( + `Invalid networkTopCountriesSortField ${JSON.stringify(networkTopCountriesSortField)}` + ); }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts index 77b0b1e459bcd..16d938b269f9b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts @@ -6,11 +6,9 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkTopNFlowRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { - NetworkTopNFlowRequestOptions, - NetworkTopNFlowStrategyResponse, -} from '../../../../../../../common/search_strategy'; +import type { NetworkTopNFlowStrategyResponse } from '../../../../../../../common/search_strategy'; import { Direction, FlowTargetSourceDest, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/helpers.ts index 54419b3902539..8f4722f723af4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/helpers.ts @@ -8,14 +8,12 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import { assertUnreachable } from '../../../../../../common/utility_types'; +import type { NetworkTopNFlowRequestOptions } from '../../../../../../common/api/search_strategy'; import type { Direction, GeoItem, - SortField, NetworkTopNFlowBuckets, NetworkTopNFlowEdges, - NetworkTopNFlowRequestOptions, AutonomousSystemItem, FlowTargetSourceDest, } from '../../../../../../common/search_strategy'; @@ -114,19 +112,19 @@ type QueryOrder = | { source_ips: Direction }; export const getQueryOrder = ( - networkTopNFlowSortField: SortField + networkTopNFlowSortField: NetworkTopNFlowRequestOptions['sort'] ): QueryOrder => { - switch (networkTopNFlowSortField.field) { - case NetworkTopTablesFields.bytes_in: - return { bytes_in: networkTopNFlowSortField.direction }; - case NetworkTopTablesFields.bytes_out: - return { bytes_out: networkTopNFlowSortField.direction }; - case NetworkTopTablesFields.flows: - return { flows: networkTopNFlowSortField.direction }; - case NetworkTopTablesFields.destination_ips: - return { destination_ips: networkTopNFlowSortField.direction }; - case NetworkTopTablesFields.source_ips: - return { source_ips: networkTopNFlowSortField.direction }; + if (networkTopNFlowSortField.field === NetworkTopTablesFields.bytes_in) { + return { bytes_in: networkTopNFlowSortField.direction }; + } else if (networkTopNFlowSortField.field === NetworkTopTablesFields.bytes_out) { + return { bytes_out: networkTopNFlowSortField.direction }; + } else if (networkTopNFlowSortField.field === NetworkTopTablesFields.flows) { + return { flows: networkTopNFlowSortField.direction }; + } else if (networkTopNFlowSortField.field === NetworkTopTablesFields.destination_ips) { + return { destination_ips: networkTopNFlowSortField.direction }; + } else if (networkTopNFlowSortField.field === NetworkTopTablesFields.source_ips) { + return { source_ips: networkTopNFlowSortField.direction }; + } else { + throw new Error(`Ordering on ${networkTopNFlowSortField.field} not currently supported`); } - assertUnreachable(networkTopNFlowSortField.field); }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/index.ts index 3a6a5176f726f..1ce4f185c26aa 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/index.ts @@ -13,7 +13,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import type { NetworkTopNFlowStrategyResponse, NetworkQueries, - NetworkTopNFlowRequestOptions, NetworkTopNFlowEdges, } from '../../../../../../common/search_strategy/security_solution/network'; @@ -24,14 +23,14 @@ import { getTopNFlowEdges } from './helpers'; import { buildTopNFlowQuery } from './query.top_n_flow_network.dsl'; export const networkTopNFlow: SecuritySolutionFactory = { - buildDsl: (options: NetworkTopNFlowRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildTopNFlowQuery(options); }, parse: async ( - options: NetworkTopNFlowRequestOptions, + options, response: IEsSearchResponse ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/query.top_n_flow_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/query.top_n_flow_network.dsl.ts index e3cb06ac2ced1..1dcb3605afcfd 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/query.top_n_flow_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/query.top_n_flow_network.dsl.ts @@ -5,12 +5,8 @@ * 2.0. */ -import type { - SortField, - FlowTargetSourceDest, - NetworkTopTablesFields, - NetworkTopNFlowRequestOptions, -} from '../../../../../../common/search_strategy'; +import type { NetworkTopNFlowRequestOptions } from '../../../../../../common/api/search_strategy'; +import type { FlowTargetSourceDest } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; import { getOppositeField } from '../helpers'; import { getQueryOrder } from './helpers'; @@ -28,10 +24,12 @@ export const buildTopNFlowQuery = ({ filterQuery, flowTarget, sort, - pagination: { querySize }, + pagination, timerange: { from, to }, ip, }: NetworkTopNFlowRequestOptions) => { + const querySize = pagination?.querySize ?? 10; + const filter = [ ...createQueryFilterClauses(filterQuery), { @@ -82,7 +80,7 @@ export const buildTopNFlowQuery = ({ }; const getFlowTargetAggs = ( - sort: SortField, + sort: NetworkTopNFlowRequestOptions['sort'], flowTarget: FlowTargetSourceDest, querySize: number ) => ({ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts index 6d7faf096d021..a4970837343d6 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts @@ -6,8 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { NetworkUsersRequestOptions } from '../../../../../../../common/api/search_strategy'; -import type { NetworkUsersRequestOptions } from '../../../../../../../common/search_strategy'; import { Direction, FlowTargetSourceDest, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.test.ts index e2b169e2acf73..e3159bcbf4b60 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.test.ts @@ -6,8 +6,6 @@ */ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; -import type { NetworkUsersRequestOptions } from '../../../../../../common/search_strategy/security_solution/network'; - import * as buildQuery from './query.users_network.dsl'; import { networkUsers } from '.'; import { @@ -15,6 +13,7 @@ import { mockSearchStrategyResponse, formattedSearchStrategyResponse, } from './__mocks__'; +import type { NetworkUsersRequestOptions } from '../../../../../../common/api/search_strategy'; describe('networkUsers search strategy', () => { const buildUsersQuery = jest.spyOn(buildQuery, 'buildUsersQuery'); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.ts index 17340c5c1ed0d..496dfa9c00485 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/index.ts @@ -13,7 +13,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import type { NetworkUsersStrategyResponse, NetworkQueries, - NetworkUsersRequestOptions, } from '../../../../../../common/search_strategy/security_solution/network'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -23,14 +22,14 @@ import { getUsersEdges } from './helpers'; import { buildUsersQuery } from './query.users_network.dsl'; export const networkUsers: SecuritySolutionFactory = { - buildDsl: (options: NetworkUsersRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildUsersQuery(options); }, parse: async ( - options: NetworkUsersRequestOptions, + options, response: IEsSearchResponse ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/query.users_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/query.users_network.dsl.ts index 3adab346d7063..3f3135d379d80 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/query.users_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/query.users_network.dsl.ts @@ -5,12 +5,9 @@ * 2.0. */ +import type { NetworkUsersRequestOptions } from '../../../../../../common/api/search_strategy'; import { assertUnreachable } from '../../../../../../common/utility_types'; -import type { - Direction, - SortField, - NetworkUsersRequestOptions, -} from '../../../../../../common/search_strategy'; +import type { Direction } from '../../../../../../common/search_strategy'; import { NetworkUsersFields } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; @@ -19,10 +16,12 @@ export const buildUsersQuery = ({ sort, filterQuery, flowTarget, - pagination: { querySize }, + pagination, defaultIndex, timerange: { from, to }, }: NetworkUsersRequestOptions) => { + const querySize = pagination?.querySize ?? 10; + const filter = [ ...createQueryFilterClauses(filterQuery), { @@ -93,13 +92,12 @@ export const buildUsersQuery = ({ type QueryOrder = { _count: Direction } | { _key: Direction }; -const getQueryOrder = (sort: SortField): QueryOrder => { - switch (sort.field) { - case NetworkUsersFields.name: - return { _key: sort.direction }; - case NetworkUsersFields.count: - return { _count: sort.direction }; - default: - return assertUnreachable(sort.field); +const getQueryOrder = (sort: NetworkUsersRequestOptions['sort']): QueryOrder => { + if (sort.field === NetworkUsersFields.name) { + return { _key: sort.direction }; + } else if (sort.field === NetworkUsersFields.count) { + return { _count: sort.direction }; + } else { + return assertUnreachable(sort.field as never); } }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/__mocks__/index.ts index 5cbc3a39db334..979f80440d44e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/__mocks__/index.ts @@ -10,13 +10,13 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import type { EndpointAppContextService } from '../../../../../../endpoint/endpoint_app_context_services'; import type { EndpointAppContext } from '../../../../../../endpoint/types'; -import type { UsersRelatedHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/related_entities/related_hosts'; import { RelatedEntitiesQueries } from '../../../../../../../common/search_strategy/security_solution/related_entities'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { allowedExperimentalValues } from '../../../../../../../common/experimental_features'; import { createMockConfig } from '../../../../../../lib/detection_engine/routes/__mocks__'; +import type { RelatedHostsRequestOptions } from '../../../../../../../common/api/search_strategy'; -export const mockOptions: UsersRelatedHostsRequestOptions = { +export const mockOptions: RelatedHostsRequestOptions = { defaultIndex: ['test_indices*'], factoryQueryType: RelatedEntitiesQueries.relatedHosts, userName: 'user1', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/index.ts index 3bd74e310d77f..ee4787c83c912 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/index.ts @@ -13,7 +13,6 @@ import type { SecuritySolutionFactory } from '../../types'; import type { EndpointAppContext } from '../../../../../endpoint/types'; import type { RelatedEntitiesQueries } from '../../../../../../common/search_strategy/security_solution/related_entities'; import type { - UsersRelatedHostsRequestOptions, UsersRelatedHostsStrategyResponse, RelatedHostBucket, RelatedHost, @@ -23,9 +22,9 @@ import { getHostRiskData } from '../../hosts/all'; import { inspectStringifyObject } from '../../../../../utils/build_query'; export const usersRelatedHosts: SecuritySolutionFactory = { - buildDsl: (options: UsersRelatedHostsRequestOptions) => buildRelatedHostsQuery(options), + buildDsl: (options) => buildRelatedHostsQuery(options), parse: async ( - options: UsersRelatedHostsRequestOptions, + options, response: IEsSearchResponse, deps?: { esClient: IScopedClusterClient; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/query.related_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/query.related_hosts.dsl.ts index cb8668c179fea..c03fb4a25c36f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/query.related_hosts.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_hosts/query.related_hosts.dsl.ts @@ -6,13 +6,13 @@ */ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; -import type { UsersRelatedHostsRequestOptions } from '../../../../../../common/search_strategy/security_solution/related_entities/related_hosts'; +import type { RelatedHostsRequestOptions } from '../../../../../../common/api/search_strategy'; export const buildRelatedHostsQuery = ({ userName, defaultIndex, from, -}: UsersRelatedHostsRequestOptions): ISearchRequestParams => { +}: RelatedHostsRequestOptions): ISearchRequestParams => { const now = new Date(); const filter = [ { term: { 'user.name': userName } }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/__mocks__/index.ts index 062bb595bce1c..c503928861472 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/__mocks__/index.ts @@ -10,13 +10,13 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import type { EndpointAppContextService } from '../../../../../../endpoint/endpoint_app_context_services'; import type { EndpointAppContext } from '../../../../../../endpoint/types'; -import type { HostsRelatedUsersRequestOptions } from '../../../../../../../common/search_strategy/security_solution/related_entities/related_users'; import { RelatedEntitiesQueries } from '../../../../../../../common/search_strategy/security_solution/related_entities'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { allowedExperimentalValues } from '../../../../../../../common/experimental_features'; import { createMockConfig } from '../../../../../../lib/detection_engine/routes/__mocks__'; +import type { RelatedUsersRequestOptions } from '../../../../../../../common/api/search_strategy'; -export const mockOptions: HostsRelatedUsersRequestOptions = { +export const mockOptions: RelatedUsersRequestOptions = { defaultIndex: ['test_indices*'], factoryQueryType: RelatedEntitiesQueries.relatedUsers, hostName: 'host1', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/index.ts index 1dce51f337130..abe95325ebce1 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/index.ts @@ -12,7 +12,6 @@ import type { RiskSeverity } from '../../../../../../common/search_strategy/secu import type { SecuritySolutionFactory } from '../../types'; import type { RelatedEntitiesQueries } from '../../../../../../common/search_strategy/security_solution/related_entities'; import type { - HostsRelatedUsersRequestOptions, HostsRelatedUsersStrategyResponse, RelatedUserBucket, RelatedUser, @@ -22,9 +21,9 @@ import { buildRelatedUsersQuery } from './query.related_users.dsl'; import { getUserRiskData } from '../../users/all'; export const hostsRelatedUsers: SecuritySolutionFactory = { - buildDsl: (options: HostsRelatedUsersRequestOptions) => buildRelatedUsersQuery(options), + buildDsl: (options) => buildRelatedUsersQuery(options), parse: async ( - options: HostsRelatedUsersRequestOptions, + options, response: IEsSearchResponse, deps?: { esClient: IScopedClusterClient; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/query.related_users.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/query.related_users.dsl.ts index 8824c4c359dec..8cc94a2e0c8f4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/query.related_users.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/related_entities/related_users/query.related_users.dsl.ts @@ -6,13 +6,13 @@ */ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; -import type { HostsRelatedUsersRequestOptions } from '../../../../../../common/search_strategy/security_solution/related_entities/related_users'; +import type { RelatedUsersRequestOptions } from '../../../../../../common/api/search_strategy'; export const buildRelatedUsersQuery = ({ hostName, defaultIndex, from, -}: HostsRelatedUsersRequestOptions): ISearchRequestParams => { +}: RelatedUsersRequestOptions): ISearchRequestParams => { const now = new Date(); const filter = [ { term: { 'host.name': hostName } }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts index 227744fcc88f1..b1e0d69f9c3c9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts @@ -10,16 +10,15 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { riskScore } from '.'; import type { IEsSearchResponse } from '@kbn/data-plugin/public'; -import type { - HostRiskScore, - RiskScoreRequestOptions, -} from '../../../../../../common/search_strategy'; +import type { HostRiskScore } from '../../../../../../common/search_strategy'; import { RiskScoreEntity, RiskSeverity } from '../../../../../../common/search_strategy'; import * as buildQuery from './query.risk_score.dsl'; import { get } from 'lodash/fp'; import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import { createMockEndpointAppContext } from '../../../../../endpoint/mocks'; +import type { RiskScoreRequestOptions } from '../../../../../../common/api/search_strategy'; +import { RiskQueries } from '../../../../../../common/api/search_strategy'; export const mockSearchStrategyResponse: IEsSearchResponse = { rawResponse: { @@ -79,6 +78,7 @@ export const mockOptions: RiskScoreRequestOptions = { defaultIndex: ['logs-*'], riskScoreEntity: RiskScoreEntity.host, includeAlertsCount: true, + factoryQueryType: RiskQueries.hostsRiskScore, }; describe('buildRiskScoreQuery search strategy', () => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts index 28602d82dbed6..82836855b8529 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts @@ -11,7 +11,6 @@ import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { AggregationsMinAggregate } from '@elastic/elasticsearch/lib/api/types'; import type { SecuritySolutionFactory } from '../../types'; import type { - RiskScoreRequestOptions, RiskQueries, BucketItem, HostRiskScore, @@ -26,7 +25,7 @@ import { getTotalCount } from '../../cti/event_enrichment/helpers'; export const riskScore: SecuritySolutionFactory< RiskQueries.hostsRiskScore | RiskQueries.usersRiskScore > = { - buildDsl: (options: RiskScoreRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } @@ -34,7 +33,7 @@ export const riskScore: SecuritySolutionFactory< return buildRiskScoreQuery(options); }, parse: async ( - options: RiskScoreRequestOptions, + options, response: IEsSearchResponse, deps?: { spaceId?: string; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/query.risk_score.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/query.risk_score.dsl.ts index fb105f6514ead..bb28b1de4fe63 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/query.risk_score.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/query.risk_score.dsl.ts @@ -6,10 +6,7 @@ */ import type { Sort } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { - RiskScoreRequestOptions, - RiskScoreSortField, -} from '../../../../../../common/search_strategy'; +import type { RiskScoreRequestOptions } from '../../../../../../common/api/search_strategy'; import { Direction, RiskScoreFields, @@ -65,7 +62,7 @@ export const buildRiskScoreQuery = ({ return dslQuery; }; -const getQueryOrder = (sort?: RiskScoreSortField): Sort => { +const getQueryOrder = (sort?: RiskScoreRequestOptions['sort']): Sort => { if (!sort) { return [ { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/__mocks__/index.ts index e494849cc6ceb..0cc4f4b5ab409 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/__mocks__/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { KpiRiskScoreRequestOptions } from '../../../../../../../common/search_strategy'; +import type { RiskScoreKpiRequestOptionsInput } from '../../../../../../../common/api/search_strategy'; import { RiskScoreEntity, RiskQueries } from '../../../../../../../common/search_strategy'; -export const mockOptions: KpiRiskScoreRequestOptions = { +export const mockOptions: RiskScoreKpiRequestOptionsInput = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/index.ts index 6e3901e66892e..333b59bc15c04 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/index.ts @@ -9,7 +9,6 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { - KpiRiskScoreRequestOptions, KpiRiskScoreStrategyResponse, RiskQueries, RiskSeverity, @@ -25,9 +24,9 @@ interface AggBucket { } export const kpiRiskScore: SecuritySolutionFactory = { - buildDsl: (options: KpiRiskScoreRequestOptions) => buildKpiRiskScoreQuery(options), + buildDsl: (options) => buildKpiRiskScoreQuery(options), parse: async ( - options: KpiRiskScoreRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/query.kpi_risk_score.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/query.kpi_risk_score.dsl.ts index f68eb647ad88c..4a98089eb239c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/query.kpi_risk_score.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/kpi/query.kpi_risk_score.dsl.ts @@ -5,15 +5,15 @@ * 2.0. */ +import type { RiskScoreKpiRequestOptions } from '../../../../../../common/api/search_strategy'; import { RiskScoreEntity, RiskScoreFields } from '../../../../../../common/search_strategy'; -import type { KpiRiskScoreRequestOptions } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; export const buildKpiRiskScoreQuery = ({ defaultIndex, filterQuery, entity, -}: KpiRiskScoreRequestOptions) => { +}: RiskScoreKpiRequestOptions) => { const filter = [...createQueryFilterClauses(filterQuery)]; const dslQuery = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/__mocks__/index.ts index 521f315d2411e..2670f2d5b9b65 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/__mocks__/index.ts @@ -9,11 +9,11 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { Direction } from '../../../../../../../common/search_strategy'; import { UsersQueries } from '../../../../../../../common/search_strategy/security_solution/users'; -import type { UsersRequestOptions } from '../../../../../../../common/search_strategy/security_solution/users/all'; import { UsersFields } from '../../../../../../../common/search_strategy/security_solution/users/common'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { createMockEndpointAppContext } from '../../../../../../endpoint/mocks'; +import type { UsersRequestOptions } from '../../../../../../../common/api/search_strategy'; export const mockOptions: UsersRequestOptions = { defaultIndex: ['test_indices*'], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts index 18bc75edb5304..4a654a5e86f61 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts @@ -10,11 +10,11 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import * as buildQuery from './query.all_users.dsl'; import { allUsers } from '.'; import { mockDeps, mockOptions, mockSearchStrategyResponse } from './__mocks__'; -import type { UsersRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/all'; import * as buildRiskQuery from '../../risk_score/all/query.risk_score.dsl'; import { get } from 'lodash/fp'; import { RiskScoreEntity } from '../../../../../../common/search_strategy'; +import type { UsersRequestOptions } from '../../../../../../common/api/search_strategy'; class IndexNotFoundException extends Error { meta: { body: { error: { type: string } } }; @@ -117,6 +117,7 @@ describe('allHosts search strategy', () => { defaultIndex: ['ml_user_risk_score_latest_test-space'], filterQuery: { terms: { 'user.name': userName } }, riskScoreEntity: RiskScoreEntity.user, + factoryQueryType: expect.stringContaining('RiskScore'), }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts index a3391a48b5e2b..8f1836e8a05dc 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts @@ -17,27 +17,27 @@ import { buildUsersQuery } from './query.all_users.dsl'; import type { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; import type { User, - UsersRequestOptions, UsersStrategyResponse, } from '../../../../../../common/search_strategy/security_solution/users/all'; import type { AllUsersAggEsItem } from '../../../../../../common/search_strategy/security_solution/users/common'; import { buildRiskScoreQuery } from '../../risk_score/all/query.risk_score.dsl'; import type { RiskSeverity, UserRiskScore } from '../../../../../../common/search_strategy'; import { - RiskScoreEntity, buildUserNamesFilter, getUserRiskIndex, + RiskScoreEntity, + RiskQueries, } from '../../../../../../common/search_strategy'; export const allUsers: SecuritySolutionFactory = { - buildDsl: (options: UsersRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildUsersQuery(options); }, parse: async ( - options: UsersRequestOptions, + options, response: IEsSearchResponse, deps?: { esClient: IScopedClusterClient; @@ -140,6 +140,7 @@ export async function getUserRiskData( defaultIndex: [getUserRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)], filterQuery: buildUserNamesFilter(userNames), riskScoreEntity: RiskScoreEntity.user, + factoryQueryType: RiskQueries.usersRiskScore, }) ); return userRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/query.all_users.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/query.all_users.dsl.ts index 56b90a2972c3e..792830bf65b78 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/query.all_users.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/query.all_users.dsl.ts @@ -6,20 +6,22 @@ */ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; +import type { UsersRequestOptions } from '../../../../../../common/api/search_strategy'; import type { Direction } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; -import type { UsersRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/all'; -import type { SortUsersField } from '../../../../../../common/search_strategy/security_solution/users/common'; import { UsersFields } from '../../../../../../common/search_strategy/security_solution/users/common'; import { assertUnreachable } from '../../../../../../common/utility_types'; export const buildUsersQuery = ({ defaultIndex, filterQuery, - pagination: { querySize }, + pagination, sort, timerange: { from, to }, }: UsersRequestOptions): ISearchRequestParams => { + // TODO: replace magic number with defaults + const { querySize } = pagination || { activePage: 0, querySize: 10 }; + const filter = [ ...createQueryFilterClauses(filterQuery), { @@ -80,13 +82,14 @@ export const buildUsersQuery = ({ type QueryOrder = { lastSeen: Direction } | { domain: Direction } | { _key: Direction }; -const getQueryOrder = (sort: SortUsersField): QueryOrder => { - switch (sort.field) { - case UsersFields.lastSeen: - return { lastSeen: sort.direction }; - case UsersFields.name: - return { _key: sort.direction }; - default: - return assertUnreachable(sort.field); +const getQueryOrder = (sort: UsersRequestOptions['sort']): QueryOrder => { + if (!sort) return assertUnreachable(sort); + + if (sort.field === UsersFields.lastSeen) { + return { lastSeen: sort.direction }; + } else if (sort.field === UsersFields.name) { + return { _key: sort.direction }; + } else { + throw new Error(`Invalid sort field provided for Users query: "${sort.field}"`); } }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/__mocks__/index.ts index 500e5bca6bca2..65da6d7a48d94 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/__mocks__/index.ts @@ -6,10 +6,8 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { - UserAuthenticationsRequestOptions, - AuthenticationHit, -} from '../../../../../../../common/search_strategy'; +import type { UserAuthenticationsRequestOptions } from '../../../../../../../common/api/search_strategy'; +import type { AuthenticationHit } from '../../../../../../../common/search_strategy'; import { Direction, UsersQueries, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.ts index 1c9f89254ff8f..5c10902482a50 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.ts @@ -6,7 +6,7 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { UserAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/users/authentications'; +import type { UserAuthenticationsRequestOptions } from '../../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; import { authenticationsFields } from '../helpers'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.ts new file mode 100644 index 0000000000000..7b85c45b2f8b6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright 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 { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; + +import * as buildQuery from './dsl/query.dsl'; +import { authentications } from '.'; +import { + mockOptions, + mockSearchStrategyResponse, + formattedSearchStrategyResponse, +} from './__mocks__'; +import type { UserAuthenticationsRequestOptions } from '../../../../../../common/api/search_strategy'; + +describe('authentications search strategy', () => { + const buildAuthenticationQuery = jest.spyOn(buildQuery, 'buildQuery'); + + afterEach(() => { + buildAuthenticationQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + authentications.buildDsl(mockOptions); + expect(buildAuthenticationQuery).toHaveBeenCalledWith(mockOptions); + }); + + test('should throw error if query size is greater equal than DEFAULT_MAX_TABLE_QUERY_SIZE ', () => { + const overSizeOptions = { + ...mockOptions, + pagination: { + ...mockOptions.pagination, + querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, + }, + } as UserAuthenticationsRequestOptions; + + expect(() => { + authentications.buildDsl(overSizeOptions); + }).toThrowError(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await authentications.parse(mockOptions, mockSearchStrategyResponse); + expect(result).toMatchObject(formattedSearchStrategyResponse); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.tsx deleted file mode 100644 index 3ad509e420757..0000000000000 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.tsx +++ /dev/null @@ -1,53 +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 { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; - -import * as buildQuery from './dsl/query.dsl'; -import { authentications } from '.'; -import { - mockOptions, - mockSearchStrategyResponse, - formattedSearchStrategyResponse, -} from './__mocks__'; -import type { UserAuthenticationsRequestOptions } from '../../../../../../common/search_strategy'; - -describe('authentications search strategy', () => { - const buildAuthenticationQuery = jest.spyOn(buildQuery, 'buildQuery'); - - afterEach(() => { - buildAuthenticationQuery.mockClear(); - }); - - describe('buildDsl', () => { - test('should build dsl query', () => { - authentications.buildDsl(mockOptions); - expect(buildAuthenticationQuery).toHaveBeenCalledWith(mockOptions); - }); - - test('should throw error if query size is greater equal than DEFAULT_MAX_TABLE_QUERY_SIZE ', () => { - const overSizeOptions = { - ...mockOptions, - pagination: { - ...mockOptions.pagination, - querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, - }, - } as UserAuthenticationsRequestOptions; - - expect(() => { - authentications.buildDsl(overSizeOptions); - }).toThrowError(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); - }); - }); - - describe('parse', () => { - test('should parse data correctly', async () => { - const result = await authentications.parse(mockOptions, mockSearchStrategyResponse); - expect(result).toMatchObject(formattedSearchStrategyResponse); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.tsx index 0024444247fa4..9ef7ab6a2f7da 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.tsx @@ -13,7 +13,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import type { AuthenticationHit, AuthenticationsEdges, - UserAuthenticationsRequestOptions, UserAuthenticationsStrategyResponse, } from '../../../../../../common/search_strategy'; import type { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; @@ -25,7 +24,7 @@ import { buildQuery as buildAuthenticationQuery } from './dsl/query.dsl'; import { formatAuthenticationData, getHits } from './helpers'; export const authentications: SecuritySolutionFactory = { - buildDsl: (options: UserAuthenticationsRequestOptions) => { + buildDsl: (options) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } @@ -33,7 +32,7 @@ export const authentications: SecuritySolutionFactory ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/index.ts index e8bba0eaba107..d8b8d3ba827f2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/index.ts @@ -12,17 +12,15 @@ import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; import { buildUsersKpiAuthenticationsQuery } from './query.users_kpi_authentications.dsl'; import type { - UsersKpiAuthenticationsRequestOptions, UsersKpiAuthenticationsStrategyResponse, UsersQueries, } from '../../../../../../../common/search_strategy'; import { formatGeneralHistogramData } from '../../../common/format_general_histogram_data'; export const usersKpiAuthentications: SecuritySolutionFactory = { - buildDsl: (options: UsersKpiAuthenticationsRequestOptions) => - buildUsersKpiAuthenticationsQuery(options), + buildDsl: (options) => buildUsersKpiAuthenticationsQuery(options), parse: async ( - options: UsersKpiAuthenticationsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/query.users_kpi_authentications.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/query.users_kpi_authentications.dsl.ts index fd087dbb17eff..e3e5af4a32a5e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/query.users_kpi_authentications.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/query.users_kpi_authentications.dsl.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { UsersKpiAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy'; +import type { AuthenticationsKpiRequestOptions } from '../../../../../../../common/api/search_strategy/users/kpi/authentications'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; export const buildUsersKpiAuthenticationsQuery = ({ filterQuery, timerange: { from, to }, defaultIndex, -}: UsersKpiAuthenticationsRequestOptions) => { +}: AuthenticationsKpiRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts index 1d47f5d034820..7086b1c6f4f45 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts @@ -15,10 +15,7 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { UsersQueries } from '../../../../../../../common/search_strategy/security_solution/users'; -import type { - TotalUsersKpiRequestOptions, - TotalUsersKpiStrategyResponse, -} from '../../../../../../../common/search_strategy/security_solution/users/kpi/total_users'; +import type { TotalUsersKpiStrategyResponse } from '../../../../../../../common/search_strategy/security_solution/users/kpi/total_users'; import { inspectStringifyObject } from '../../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../../types'; @@ -26,9 +23,9 @@ import { buildTotalUsersKpiQuery } from './query.build_total_users_kpi.dsl'; import { formatGeneralHistogramData } from '../../../common/format_general_histogram_data'; export const totalUsersKpi: SecuritySolutionFactory = { - buildDsl: (options: TotalUsersKpiRequestOptions) => buildTotalUsersKpiQuery(options), + buildDsl: (options) => buildTotalUsersKpiQuery(options), parse: async ( - options: TotalUsersKpiRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts index 7c5f2619e7f12..9ff10bd22cb1f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { HostsKpiHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import type { TotalUsersKpiRequestOptions } from '../../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; export const buildTotalUsersKpiQuery = ({ filterQuery, timerange: { from, to }, defaultIndex, -}: HostsKpiHostsRequestOptions) => { +}: TotalUsersKpiRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.ts.snap similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.ts.snap diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.ts new file mode 100644 index 0000000000000..0c02b0347d121 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as buildQuery from './query.managed_user_details.dsl'; +import { managedUserDetails } from '.'; +import type { AzureManagedUser } from '../../../../../../common/search_strategy/security_solution/users/managed_details'; +import type { IEsSearchResponse } from '@kbn/data-plugin/public'; +import type { ManagedUserDetailsRequestOptionsInput } from '../../../../../../common/api/search_strategy'; +import { UsersQueries } from '../../../../../../common/api/search_strategy'; + +export const mockOptions: ManagedUserDetailsRequestOptionsInput = { + defaultIndex: ['logs-*'], + userName: 'test-user-name', + factoryQueryType: UsersQueries.managedDetails, +}; + +export const mockSearchStrategyResponse: IEsSearchResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 124, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 1, + failed: 0, + }, + hits: { + max_score: null, + hits: [ + { + _index: '.test', + _id: '9AxbIocB-WLv2258YZtS', + _score: null, + _source: { + agent: { + name: 'docker-fleet-agent', + id: '9528bb69-1511-4631-a5af-1d7e93c02009', + type: 'filebeat', + ephemeral_id: '914fd1fa-aa37-4ab4-b36d-972ab9b19cde', + version: '8.8.0', + }, + '@timestamp': '2023-02-23T20:03:17.489Z', + host: { + hostname: 'docker-fleet-agent', + os: { + kernel: '5.10.47-linuxkit', + name: 'Ubuntu', + type: 'linux', + family: 'debian', + version: '20.04.5 LTS (Focal Fossa)', + platform: 'ubuntu', + }, + ip: ['172.26.0.7'], + name: 'docker-fleet-agent', + id: 'cff3d165179d4aef9596ddbb263e3adb', + mac: ['02-42-AC-1A-00-07'], + architecture: 'x86_64', + }, + event: { + agent_id_status: 'verified', + ingested: '2023-02-23T20:03:18Z', + provider: 'Azure AD', + kind: 'asset', + action: 'user-discovered', + type: ['user', 'info'], + dataset: 'entityanalytics_azure.users', + }, + user: { + full_name: 'Test user', + phone: ['1235559999'], + last_name: 'Test last name', + id: '39fac578-91fb-47f6-8f7a-fab05ce70d8b', + first_name: 'Taylor', + email: 'tes.user@elastic.co', + }, + }, + sort: [1677182597489], + }, + ], + }, + }, + total: 21, + loaded: 21, +}; + +describe('userDetails search strategy', () => { + const buildManagedUserDetailsQuery = jest.spyOn(buildQuery, 'buildManagedUserDetailsQuery'); + + afterEach(() => { + buildManagedUserDetailsQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + managedUserDetails.buildDsl(mockOptions); + expect(buildManagedUserDetailsQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await managedUserDetails.parse(mockOptions, mockSearchStrategyResponse); + expect(result).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.tsx deleted file mode 100644 index cbd601efa90fa..0000000000000 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as buildQuery from './query.managed_user_details.dsl'; -import { managedUserDetails } from '.'; -import type { - AzureManagedUser, - ManagedUserDetailsRequestOptions, -} from '../../../../../../common/search_strategy/security_solution/users/managed_details'; -import type { IEsSearchResponse } from '@kbn/data-plugin/public'; - -export const mockOptions: ManagedUserDetailsRequestOptions = { - defaultIndex: ['logs-*'], - userName: 'test-user-name', -}; - -export const mockSearchStrategyResponse: IEsSearchResponse = { - isPartial: false, - isRunning: false, - rawResponse: { - took: 124, - timed_out: false, - _shards: { - total: 2, - successful: 2, - skipped: 1, - failed: 0, - }, - hits: { - max_score: null, - hits: [ - { - _index: '.test', - _id: '9AxbIocB-WLv2258YZtS', - _score: null, - _source: { - agent: { - name: 'docker-fleet-agent', - id: '9528bb69-1511-4631-a5af-1d7e93c02009', - type: 'filebeat', - ephemeral_id: '914fd1fa-aa37-4ab4-b36d-972ab9b19cde', - version: '8.8.0', - }, - '@timestamp': '2023-02-23T20:03:17.489Z', - host: { - hostname: 'docker-fleet-agent', - os: { - kernel: '5.10.47-linuxkit', - name: 'Ubuntu', - type: 'linux', - family: 'debian', - version: '20.04.5 LTS (Focal Fossa)', - platform: 'ubuntu', - }, - ip: ['172.26.0.7'], - name: 'docker-fleet-agent', - id: 'cff3d165179d4aef9596ddbb263e3adb', - mac: ['02-42-AC-1A-00-07'], - architecture: 'x86_64', - }, - event: { - agent_id_status: 'verified', - ingested: '2023-02-23T20:03:18Z', - provider: 'Azure AD', - kind: 'asset', - action: 'user-discovered', - type: ['user', 'info'], - dataset: 'entityanalytics_azure.users', - }, - user: { - full_name: 'Test user', - phone: ['1235559999'], - last_name: 'Test last name', - id: '39fac578-91fb-47f6-8f7a-fab05ce70d8b', - first_name: 'Taylor', - email: 'tes.user@elastic.co', - }, - }, - sort: [1677182597489], - }, - ], - }, - }, - total: 21, - loaded: 21, -}; - -describe('userDetails search strategy', () => { - const buildManagedUserDetailsQuery = jest.spyOn(buildQuery, 'buildManagedUserDetailsQuery'); - - afterEach(() => { - buildManagedUserDetailsQuery.mockClear(); - }); - - describe('buildDsl', () => { - test('should build dsl query', () => { - managedUserDetails.buildDsl(mockOptions); - expect(buildManagedUserDetailsQuery).toHaveBeenCalledWith(mockOptions); - }); - }); - - describe('parse', () => { - test('should parse data correctly', async () => { - const result = await managedUserDetails.parse(mockOptions, mockSearchStrategyResponse); - expect(result).toMatchSnapshot(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts index def73159a4793..8175b2bda9c03 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts @@ -14,14 +14,13 @@ import { buildManagedUserDetailsQuery } from './query.managed_user_details.dsl'; import type { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; import type { AzureManagedUser, - ManagedUserDetailsRequestOptions, ManagedUserDetailsStrategyResponse, } from '../../../../../../common/search_strategy/security_solution/users/managed_details'; export const managedUserDetails: SecuritySolutionFactory = { - buildDsl: (options: ManagedUserDetailsRequestOptions) => buildManagedUserDetailsQuery(options), + buildDsl: (options) => buildManagedUserDetailsQuery(options), parse: async ( - options: ManagedUserDetailsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.ts index 9f29bc98f287f..f5b6d9a7ef8d7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.ts @@ -5,12 +5,14 @@ * 2.0. */ -import type { ManagedUserDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/managed_details'; +import type { ManagedUserDetailsRequestOptions } from '../../../../../../common/api/search_strategy'; +import { UsersQueries } from '../../../../../../common/api/search_strategy'; import { buildManagedUserDetailsQuery } from './query.managed_user_details.dsl'; export const mockOptions: ManagedUserDetailsRequestOptions = { defaultIndex: ['logs-*'], userName: 'test-user-name', + factoryQueryType: UsersQueries.managedDetails, }; describe('buildManagedUserDetailsQuery', () => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts index 8af2bbee0aa0a..ae295829c436a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts @@ -6,8 +6,8 @@ */ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; +import type { ManagedUserDetailsRequestOptions } from '../../../../../../common/api/search_strategy'; import { EVENT_KIND_ASSET_FILTER } from '../../../../../../common/search_strategy'; -import type { ManagedUserDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/managed_details'; export const buildManagedUserDetailsQuery = ({ userName, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__mocks__/index.ts index 9cf6a6089e21e..de5bc36045877 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__mocks__/index.ts @@ -6,10 +6,9 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { ObservedUserDetailsRequestOptions } from '../../../../../../../common/api/search_strategy'; import { UsersQueries } from '../../../../../../../common/search_strategy/security_solution/users'; -import type { ObservedUserDetailsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/users/observed_details'; - export const mockOptions: ObservedUserDetailsRequestOptions = { defaultIndex: ['test_indices*'], factoryQueryType: UsersQueries.observedDetails, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/index.test.ts.snap similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/index.test.ts.snap diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.test.tsx rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.test.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.ts index 7fd64694fc198..f91ab3b369466 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.ts @@ -12,16 +12,13 @@ import type { SecuritySolutionFactory } from '../../types'; import { buildObservedUserDetailsQuery } from './query.observed_user_details.dsl'; import type { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; -import type { - ObservedUserDetailsRequestOptions, - ObservedUserDetailsStrategyResponse, -} from '../../../../../../common/search_strategy/security_solution/users/observed_details'; +import type { ObservedUserDetailsStrategyResponse } from '../../../../../../common/search_strategy/security_solution/users/observed_details'; import { formatUserItem } from './helpers'; export const observedUserDetails: SecuritySolutionFactory = { - buildDsl: (options: ObservedUserDetailsRequestOptions) => buildObservedUserDetailsQuery(options), + buildDsl: (options) => buildObservedUserDetailsQuery(options), parse: async ( - options: ObservedUserDetailsRequestOptions, + options, response: IEsSearchResponse ): Promise => { const aggregations = response.rawResponse.aggregations; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.ts index efbf49231486e..d26af4d198ae9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.ts @@ -7,7 +7,7 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ISearchRequestParams } from '@kbn/data-plugin/common'; -import type { ObservedUserDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/observed_details'; +import type { ObservedUserDetailsRequestOptions } from '../../../../../../common/api/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; import { buildFieldsTermAggregation } from '../../hosts/details/helpers'; import { USER_FIELDS } from './helpers'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index 1acb6687b8ace..ede2f76c64bb9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -8,44 +8,30 @@ import { map, mergeMap } from 'rxjs/operators'; import type { ISearchStrategy, PluginStart } from '@kbn/data-plugin/server'; import { shimHitsTotal } from '@kbn/data-plugin/server'; -import { ENHANCED_ES_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; import type { KibanaRequest } from '@kbn/core/server'; import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; -import type { - FactoryQueryTypes, - StrategyResponseType, - StrategyRequestType, -} from '../../../common/search_strategy/security_solution'; +import { ENHANCED_ES_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; +import type { z } from 'zod'; +import { searchStrategyRequestSchema } from '../../../common/api/search_strategy'; import { securitySolutionFactory } from './factory'; -import type { SecuritySolutionFactory } from './factory/types'; import type { EndpointAppContext } from '../../endpoint/types'; -function isObj(req: unknown): req is Record { - return typeof req === 'object' && req !== null; -} -function assertValidRequestType( - req: unknown -): asserts req is StrategyRequestType & { factoryQueryType: FactoryQueryTypes } { - if (!isObj(req) || req.factoryQueryType == null) { - throw new Error('factoryQueryType is required'); - } -} - -export const securitySolutionSearchStrategyProvider = ( +export const securitySolutionSearchStrategyProvider = ( data: PluginStart, endpointContext: EndpointAppContext, getSpaceId?: (request: KibanaRequest) => string, ruleDataClient?: IRuleDataClient | null -): ISearchStrategy, StrategyResponseType> => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): ISearchStrategy, any> => { const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); return { search: (request, options, deps) => { - assertValidRequestType(request); + const parsedRequest = searchStrategyRequestSchema.parse(request); + + const queryFactory = securitySolutionFactory[parsedRequest.factoryQueryType]; - const queryFactory: SecuritySolutionFactory = - securitySolutionFactory[request.factoryQueryType]; - const dsl = queryFactory.buildDsl(request); + const dsl = queryFactory.buildDsl(parsedRequest); return es.search({ ...request, params: dsl }, options, deps).pipe( map((response) => { return { @@ -56,7 +42,7 @@ export const securitySolutionSearchStrategyProvider = - queryFactory.parse(request, esSearchRes, { + queryFactory.parse(parsedRequest, esSearchRes, { esClient: deps.esClient, savedObjectsClient: deps.savedObjectsClient, endpointContext, diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 8355537470d67..e73140fec20b7 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -17,7 +17,6 @@ "exclude": [ "target/**/*", "**/cypress/**", - "public/management/cypress_endpoint.config.ts", "public/management/cypress.config.ts" ], "kbn_references": [ @@ -149,7 +148,6 @@ "@kbn/expandable-flyout", "@kbn/securitysolution-grouping", "@kbn/securitysolution-data-table", - "@kbn/saved-objects-management-plugin", "@kbn/core-analytics-server", "@kbn/analytics-client", "@kbn/security-solution-side-nav", @@ -172,6 +170,8 @@ "@kbn/core-logging-server-mocks", "@kbn/core-lifecycle-browser", "@kbn/security-solution-features", - "@kbn/handlebars" + "@kbn/handlebars", + "@kbn/content-management-plugin", + "@kbn/subscription-tracking" ] } diff --git a/x-pack/plugins/security_solution_ess/public/breadcrumbs/breadcrumbs.test.ts b/x-pack/plugins/security_solution_ess/public/breadcrumbs/breadcrumbs.test.ts deleted file mode 100644 index 3b4c340488572..0000000000000 --- a/x-pack/plugins/security_solution_ess/public/breadcrumbs/breadcrumbs.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ChromeBreadcrumb } from '@kbn/core/public'; -import { emptyLastBreadcrumbUrl } from './breadcrumbs'; - -describe('emptyLastBreadcrumbUrl', () => { - it('should empty the URL and onClick function of the last breadcrumb', () => { - const breadcrumbs: ChromeBreadcrumb[] = [ - { text: 'Home', href: '/home', onClick: () => {} }, - { text: 'Breadcrumb 1', href: '/bc1', onClick: () => {} }, - { text: 'Last Breadcrumbs', href: '/last_bc', onClick: () => {} }, - ]; - - const expectedBreadcrumbs = [ - { text: 'Home', href: '/home', onClick: breadcrumbs[0].onClick }, - { text: 'Breadcrumb 1', href: '/bc1', onClick: breadcrumbs[1].onClick }, - { text: 'Last Breadcrumbs', href: '', onClick: undefined }, - ]; - - expect(emptyLastBreadcrumbUrl(breadcrumbs)).toEqual(expectedBreadcrumbs); - }); - - it('should return the original breadcrumbs if the input is empty', () => { - const emptyBreadcrumbs: ChromeBreadcrumb[] = []; - - expect(emptyLastBreadcrumbUrl(emptyBreadcrumbs)).toEqual(emptyBreadcrumbs); - }); -}); diff --git a/x-pack/plugins/security_solution_ess/public/breadcrumbs/breadcrumbs.ts b/x-pack/plugins/security_solution_ess/public/breadcrumbs/breadcrumbs.ts index ca76dc304ab99..f0305cfb95511 100644 --- a/x-pack/plugins/security_solution_ess/public/breadcrumbs/breadcrumbs.ts +++ b/x-pack/plugins/security_solution_ess/public/breadcrumbs/breadcrumbs.ts @@ -5,23 +5,11 @@ * 2.0. */ -import type { ChromeBreadcrumb } from '@kbn/core/public'; import type { Services } from '../common/services'; export const subscribeBreadcrumbs = (services: Services) => { - const { chrome, securitySolution } = services; + const { securitySolution, chrome } = services; securitySolution.getBreadcrumbsNav$().subscribe((breadcrumbsNav) => { - const breadcrumbs = [...breadcrumbsNav.leading, ...breadcrumbsNav.trailing]; - if (breadcrumbs.length > 0) { - chrome.setBreadcrumbs(emptyLastBreadcrumbUrl(breadcrumbs)); - } + chrome.setBreadcrumbs([...breadcrumbsNav.leading, ...breadcrumbsNav.trailing]); }); }; - -export const emptyLastBreadcrumbUrl = (breadcrumbs: ChromeBreadcrumb[]) => { - const lastBreadcrumb = breadcrumbs[breadcrumbs.length - 1]; - if (lastBreadcrumb) { - return [...breadcrumbs.slice(0, -1), { ...lastBreadcrumb, href: '', onClick: undefined }]; - } - return breadcrumbs; -}; diff --git a/x-pack/plugins/security_solution_serverless/kibana.jsonc b/x-pack/plugins/security_solution_serverless/kibana.jsonc index 3f9080adc5173..3756c1114c009 100644 --- a/x-pack/plugins/security_solution_serverless/kibana.jsonc +++ b/x-pack/plugins/security_solution_serverless/kibana.jsonc @@ -14,7 +14,6 @@ "requiredPlugins": [ "kibanaReact", "management", - "ml", "security", "securitySolution", "serverless", diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/auditbeat.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/auditbeat.tsx deleted file mode 100644 index a8ee074b135dd..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/auditbeat.tsx +++ /dev/null @@ -1,29 +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 { SVGProps } from 'react'; -import React from 'react'; -export const IconAuditbeat: React.FC> = ({ ...props }) => ( - - - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconAuditbeat; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/chart_arrow.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/chart_arrow.tsx new file mode 100644 index 0000000000000..a47515435fa86 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/chart_arrow.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconChartArrow: React.FC> = (props) => ( + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconChartArrow; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/dashboard.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/dashboard.tsx new file mode 100644 index 0000000000000..5ca7002cfaba0 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/dashboard.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconDashboard: React.FC> = ({ ...props }) => ( + + + + + + + + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconDashboard; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/data_connector.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/data_connector.tsx deleted file mode 100644 index 26a4ca9ea24c9..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/data_connector.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { SVGProps } from 'react'; -import React from 'react'; -export const IconDataConnector: React.FC> = ({ ...props }) => ( - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconDataConnector; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/data_view.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/data_view.tsx new file mode 100644 index 0000000000000..85461e05bd66d --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/data_view.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 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 { SVGProps } from 'react'; +import React from 'react'; +export const IconDataView: React.FC> = ({ ...props }) => ( + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconDataView; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/dev_tools.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/dev_tools.tsx deleted file mode 100644 index 49f2c129e4a79..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/dev_tools.tsx +++ /dev/null @@ -1,45 +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 { SVGProps } from 'react'; -import React from 'react'; -export const IconDevTools: React.FC> = ({ ...props }) => ( - - - - - - - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconDevTools; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/filebeat.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/filebeat.tsx new file mode 100644 index 0000000000000..0859cdb3329ab --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/filebeat.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconFilebeat: React.FC> = ({ ...props }) => ( + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconFilebeat; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/filebeat_chart.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/filebeat_chart.tsx new file mode 100644 index 0000000000000..df3243ba8d043 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/filebeat_chart.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 type { SVGProps } from 'react'; +import React from 'react'; +export const IconFilebeatChart: React.FC> = ({ ...props }) => ( + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconFilebeatChart; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/graph.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/graph.tsx deleted file mode 100644 index 9223de9461975..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/graph.tsx +++ /dev/null @@ -1,46 +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 { SVGProps } from 'react'; -import React from 'react'; -export const IconGraph: React.FC> = ({ ...props }) => ( - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconGraph; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/infra.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/infra.tsx new file mode 100644 index 0000000000000..5e4e1070d5f9e --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/infra.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconInfra: React.FC> = (props) => ( + + + + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconInfra; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/intuitive.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/intuitive.tsx new file mode 100644 index 0000000000000..cacf9dc7be11f --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/intuitive.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 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 { SVGProps } from 'react'; +import React from 'react'; +export const IconIntuitive: React.FC> = ({ ...props }) => ( + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconIntuitive; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/jobs.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/jobs.tsx new file mode 100644 index 0000000000000..a6eb6c20d446a --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/jobs.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 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 { SVGProps } from 'react'; +import React from 'react'; +export const IconJobs: React.FC> = (props) => ( + + + + + + + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconJobs; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/keyword.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/keyword.tsx new file mode 100644 index 0000000000000..f25d5c4d9b3f8 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/keyword.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconKeyword: React.FC> = (props) => ( + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconKeyword; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/logging.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/logging.tsx deleted file mode 100644 index baab149c29412..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/logging.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { SVGProps } from 'react'; -import React from 'react'; -export const IconLogging: React.FC> = ({ ...props }) => ( - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconLogging; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/manager.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/manager.tsx new file mode 100644 index 0000000000000..e5646c378b7a6 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/manager.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconManager: React.FC> = (props) => ( + + + + + + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconManager; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/marketing.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/marketing.tsx new file mode 100644 index 0000000000000..6161e492b0edb --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/marketing.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconMarketing: React.FC> = (props) => ( + + + + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconMarketing; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/product_features_alerting.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/product_features_alerting.tsx deleted file mode 100644 index f856b7a7494f4..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/product_features_alerting.tsx +++ /dev/null @@ -1,59 +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 { SVGProps } from 'react'; -import React from 'react'; -export const IconProductFeaturesAlerting: React.FC> = ({ ...props }) => ( - - - - - - - - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconProductFeaturesAlerting; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/rapid_bar_graph.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/rapid_bar_graph.tsx new file mode 100644 index 0000000000000..3303b13e8183e --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/rapid_bar_graph.tsx @@ -0,0 +1,46 @@ +/* + * Copyright 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 { SVGProps } from 'react'; +import React from 'react'; +export const IconRapidBarGraph: React.FC> = (props) => ( + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconRapidBarGraph; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/replication.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/replication.tsx new file mode 100644 index 0000000000000..18b4cf73d2adf --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/replication.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconReplication: React.FC> = ({ ...props }) => ( + + + + + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconReplication; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/reporting.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/reporting.tsx new file mode 100644 index 0000000000000..ba783401ef7e5 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/reporting.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconReporting: React.FC> = ({ ...props }) => ( + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconReporting; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/searchable_snapshots.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/searchable_snapshots.tsx deleted file mode 100644 index 9b199309fdee9..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/searchable_snapshots.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { SVGProps } from 'react'; -import React from 'react'; -export const IconSearchableSnapshots: React.FC> = ({ ...props }) => ( - - - - - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconSearchableSnapshots; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/security_shield.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/security_shield.tsx deleted file mode 100644 index 355718d77d1a0..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/security_shield.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { SVGProps } from 'react'; -import React from 'react'; -export const IconSecurityShield: React.FC> = ({ ...props }) => ( - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconSecurityShield; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/settings.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/settings.tsx new file mode 100644 index 0000000000000..dbf362deea340 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/settings.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconSettings: React.FC> = ({ ...props }) => ( + + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconSettings; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/siem.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/siem.tsx deleted file mode 100644 index 00b775af8fa36..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/siem.tsx +++ /dev/null @@ -1,46 +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 { SVGProps } from 'react'; -import React from 'react'; -export const IconSiem: React.FC> = ({ ...props }) => ( - - - - - - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconSiem; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/spaces.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/spaces.tsx deleted file mode 100644 index 1a5e6300b1b9f..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/spaces.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 type { SVGProps } from 'react'; -import React from 'react'; -export const IconSpaces: React.FC> = ({ ...props }) => ( - - - - - - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconSpaces; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/users_roles.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/users_roles.tsx new file mode 100644 index 0000000000000..3eb961f783f67 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/users_roles.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconUsersRoles: React.FC> = ({ ...props }) => ( + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconUsersRoles; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/visualization.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/visualization.tsx new file mode 100644 index 0000000000000..983dcec736015 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/common/icons/visualization.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconVisualization: React.FC> = ({ ...props }) => ( + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export default IconVisualization; diff --git a/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx b/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx index 2143eee79ebe9..f7e016d75c969 100644 --- a/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx +++ b/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx @@ -19,28 +19,38 @@ const withSuspenseIcon = (Component: React.ComponentType< export const IconLensLazy = withSuspenseIcon(React.lazy(() => import('./icons/lens'))); export const IconEndpointLazy = withSuspenseIcon(React.lazy(() => import('./icons/endpoint'))); -export const IconDataConnectorLazy = withSuspenseIcon( - React.lazy(() => import('./icons/data_connector')) -); export const IconIndexManagementLazy = withSuspenseIcon( React.lazy(() => import('./icons/index_management')) ); -export const IconSpacesLazy = withSuspenseIcon(React.lazy(() => import('./icons/spaces'))); -export const IconDevToolsLazy = withSuspenseIcon(React.lazy(() => import('./icons/dev_tools'))); export const IconFleetLazy = withSuspenseIcon(React.lazy(() => import('./icons/fleet'))); -export const IconAuditbeatLazy = withSuspenseIcon(React.lazy(() => import('./icons/auditbeat'))); -export const IconSiemLazy = withSuspenseIcon(React.lazy(() => import('./icons/siem'))); export const IconEcctlLazy = withSuspenseIcon(React.lazy(() => import('./icons/ecctl'))); -export const IconGraphLazy = withSuspenseIcon(React.lazy(() => import('./icons/graph'))); -export const IconLoggingLazy = withSuspenseIcon(React.lazy(() => import('./icons/logging'))); export const IconMapServicesLazy = withSuspenseIcon( React.lazy(() => import('./icons/map_services')) ); -export const IconSecurityShieldLazy = withSuspenseIcon( - React.lazy(() => import('./icons/security_shield')) -); -export const IconProductFeaturesAlertingLazy = withSuspenseIcon( - React.lazy(() => import('./icons/product_features_alerting')) -); export const IconTimelineLazy = withSuspenseIcon(React.lazy(() => import('./icons/timeline'))); export const IconOsqueryLazy = withSuspenseIcon(React.lazy(() => import('./icons/osquery'))); +export const IconUsersRolesLazy = withSuspenseIcon(React.lazy(() => import('./icons/users_roles'))); +export const IconReportingLazy = withSuspenseIcon(React.lazy(() => import('./icons/reporting'))); +export const IconVisualizationLazy = withSuspenseIcon( + React.lazy(() => import('./icons/visualization')) +); +export const IconMarketingLazy = withSuspenseIcon(React.lazy(() => import('./icons/marketing'))); +export const IconInfraLazy = withSuspenseIcon(React.lazy(() => import('./icons/infra'))); +export const IconKeywordLazy = withSuspenseIcon(React.lazy(() => import('./icons/keyword'))); +export const IconJobsLazy = withSuspenseIcon(React.lazy(() => import('./icons/jobs'))); +export const IconSettingsLazy = withSuspenseIcon(React.lazy(() => import('./icons/settings'))); +export const IconDashboardLazy = withSuspenseIcon(React.lazy(() => import('./icons/dashboard'))); +export const IconChartArrowLazy = withSuspenseIcon(React.lazy(() => import('./icons/chart_arrow'))); +export const IconManagerLazy = withSuspenseIcon(React.lazy(() => import('./icons/manager'))); +export const IconFilebeatLazy = withSuspenseIcon(React.lazy(() => import('./icons/filebeat'))); +export const IconDataViewLazy = withSuspenseIcon(React.lazy(() => import('./icons/data_view'))); +export const IconReplicationLazy = withSuspenseIcon( + React.lazy(() => import('./icons/replication')) +); +export const IconIntuitiveLazy = withSuspenseIcon(React.lazy(() => import('./icons/intuitive'))); +export const IconRapidBarGraphLazy = withSuspenseIcon( + React.lazy(() => import('./icons/rapid_bar_graph')) +); +export const IconFilebeatChartLazy = withSuspenseIcon( + React.lazy(() => import('./icons/filebeat_chart')) +); diff --git a/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/dashboard_landing_callout.tsx b/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/dashboard_landing_callout.tsx new file mode 100644 index 0000000000000..43102f0e50ae3 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/dashboard_landing_callout.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCallOut } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { LinkAnchor } from '@kbn/security-solution-navigation/links'; +import { css } from '@emotion/react'; +import { ExternalPageName } from '../../navigation/links/constants'; + +const linkAnchorCss = css` + text-decoration: underline; +`; + +export const DashboardsLandingCalloutComponent: React.FC = () => { + return ( + + + + + ), + visualizationLibrary: ( + + + + ), + }} + /> + + } + /> + ); +}; + +DashboardsLandingCalloutComponent.displayName = 'DashboardsLandingCalloutComponent'; +export const DashboardsLandingCallout = React.memo(DashboardsLandingCalloutComponent); + +// eslint-disable-next-line import/no-default-export +export default DashboardsLandingCallout; diff --git a/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/index.tsx b/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/index.tsx new file mode 100644 index 0000000000000..207a6f122c98e --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/index.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Services } from '../../common/services'; +import { ServicesProvider } from '../../common/services'; + +import { DashboardsLandingCallout } from './lazy'; + +export const getDashboardsLandingCallout = (services: Services): React.ComponentType => + function DashboardsLandingCalloutComponent() { + return ( + + + + ); + }; diff --git a/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/lazy.tsx b/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/lazy.tsx new file mode 100644 index 0000000000000..80ac8eacd281e --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/components/dashboards_landing_callout/lazy.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; + +const DashboardsLandingCalloutLazy = lazy(() => import('./dashboard_landing_callout')); + +export const DashboardsLandingCallout = () => ( + }> + + +); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/storage.ts b/x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/storage.ts index 7192a1337fde4..48e43b8fb49df 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/storage.ts +++ b/x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/storage.ts @@ -10,6 +10,7 @@ export const getStartedStorage = { getFinishedStepsFromStorageByCardId: jest.fn(() => []), getActiveProductsFromStorage: jest.fn(() => []), toggleActiveProductsInStorage: jest.fn(() => []), + resetAllExpandedCardStepsToStorage: jest.fn(), addFinishedStepToStorage: jest.fn(), removeFinishedStepFromStorage: jest.fn(), addExpandedCardStepToStorage: jest.fn(), diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_item.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_item.test.tsx index fdaa493b88e86..bb6844e75b7d1 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/card_item.test.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/card_item.test.tsx @@ -52,9 +52,6 @@ describe('CardItemComponent', () => { const step = getByText('1 step left'); expect(step).toBeInTheDocument(); - const time = getByText('• About 30 mins'); - expect(time).toBeInTheDocument(); - const step1 = queryByText('Step 1'); expect(step1).not.toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_item.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_item.tsx index 472b24ba6b5e2..23d697cc2748a 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/card_item.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/card_item.tsx @@ -70,28 +70,29 @@ const CardItemComponent: React.FC<{ return cardItem && hasActiveSteps ? ( - + - {cardItem.icon && } + {cardItem.icon && ( + + )} 0 && ( {i18n.STEPS_LEFT(stepsLeft)} )} - {timeInMins != null && timeInMins > 0 && ( - - {' • '} - {i18n.STEP_TIME_MIN(timeInMins)} - - )} )} diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_step.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_step.test.tsx deleted file mode 100644 index 43c9b2182656c..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/card_step.test.tsx +++ /dev/null @@ -1,93 +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 { render, fireEvent } from '@testing-library/react'; -import { CardStep } from './card_step'; -import type { StepId } from './types'; -import { GetSetUpCardId, IntroductionSteps, SectionId } from './types'; -import { ProductLine } from '../../common/product'; - -describe('CardStepComponent', () => { - const step = { - id: IntroductionSteps.getToKnowElasticSecurity, - }; - - const onStepClicked = jest.fn(); - const onStepButtonClicked = jest.fn(); - const expandedSteps = new Set([IntroductionSteps.getToKnowElasticSecurity]); - - const props = { - activeProducts: new Set([ProductLine.security]), - cardId: GetSetUpCardId.introduction, - expandedSteps, - finishedStepsByCard: new Set(), - onStepButtonClicked, - onStepClicked, - sectionId: SectionId.getSetUp, - stepId: step.id, - }; - const testStepTitle = 'Get to know Elastic Security'; - - it('should toggle step expansion on click', () => { - const { getByText } = render(); - - const stepTitle = getByText(testStepTitle); - fireEvent.click(stepTitle); - - expect(onStepClicked).toHaveBeenCalledTimes(1); - expect(onStepClicked).toHaveBeenCalledWith({ - sectionId: SectionId.getSetUp, - stepId: IntroductionSteps.getToKnowElasticSecurity, - cardId: GetSetUpCardId.introduction, - isExpanded: false, - }); - }); - - it('should render step title, badges, and description when expanded', () => { - const { getByText, getByTestId } = render(); - - const stepTitle = getByText(testStepTitle); - fireEvent.click(stepTitle); - - const badge1 = getByText('Analytics'); - const badge2 = getByText('Cloud'); - const badge3 = getByText('EDR'); - expect(badge1).toBeInTheDocument(); - expect(badge2).toBeInTheDocument(); - expect(badge3).toBeInTheDocument(); - - const description1 = getByTestId(`${IntroductionSteps.getToKnowElasticSecurity}-description-0`); - const description2 = getByTestId(`${IntroductionSteps.getToKnowElasticSecurity}-description-1`); - expect(description1).toBeInTheDocument(); - expect(description2).toBeInTheDocument(); - }); - - it('should render expended steps', () => { - const { getByTestId } = render(); - - const splitPanel = getByTestId('split-panel'); - expect(splitPanel).toBeInTheDocument(); - }); - - it('should render collapsed steps', () => { - const { queryByTestId } = render(); - - const splitPanel = queryByTestId('split-panel'); - expect(splitPanel).not.toBeInTheDocument(); - }); - - it('should render check icon when stepId is in finishedStepsByCard', () => { - const finishedStepsByCard = new Set([IntroductionSteps.getToKnowElasticSecurity]); - - const { getByTestId } = render( - - ); - - const checkIcon = getByTestId(`${step.id}-icon`); - expect(checkIcon.getAttribute('data-euiicon-type')).toEqual('checkInCircleFilled'); - }); -}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_step.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_step.tsx deleted file mode 100644 index 50f1a0b16133f..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/card_step.tsx +++ /dev/null @@ -1,198 +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 { - EuiPanel, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiBadge, - EuiSplitPanel, - EuiSpacer, - EuiText, - useEuiTheme, - EuiButtonEmpty, -} from '@elastic/eui'; -import { css } from '@emotion/react'; -import React, { useCallback, useMemo } from 'react'; - -import type { CardId, OnStepButtonClicked, OnStepClicked, SectionId, StepId } from './types'; -import icon_step from './images/icon_step.svg'; -import icon_cross from './images/icon_cross.svg'; -import { UNDO_MARK_AS_DONE_TITLE, MARK_AS_DONE_TITLE } from './translations'; -import { getStepsByActiveProduct } from './helpers'; -import type { ProductLine } from '../../common/product'; -import { getProductBadges } from './badge'; - -const CardStepComponent: React.FC<{ - activeProducts: Set; - cardId: CardId; - expandedSteps: Set; - finishedStepsByCard: Set; - onStepButtonClicked: OnStepButtonClicked; - onStepClicked: OnStepClicked; - sectionId: SectionId; - stepId: StepId; -}> = ({ - activeProducts, - cardId, - expandedSteps, - finishedStepsByCard = new Set(), - onStepButtonClicked, - onStepClicked, - sectionId, - stepId, -}) => { - const { euiTheme } = useEuiTheme(); - - const expandStep = expandedSteps.has(stepId); - const steps = useMemo( - () => getStepsByActiveProduct({ activeProducts, cardId, sectionId }), - [activeProducts, cardId, sectionId] - ); - const { title, productLineRequired, description, splitPanel } = - steps?.find((step) => step.id === stepId) ?? {}; - - const badges = useMemo(() => getProductBadges(productLineRequired), [productLineRequired]); - - const toggleStep = useCallback( - (e) => { - e.preventDefault(); - const newState = !expandStep; - onStepClicked({ stepId, cardId, sectionId, isExpanded: newState }); - }, - [cardId, expandStep, onStepClicked, sectionId, stepId] - ); - - const isDone = finishedStepsByCard.has(stepId); - - const hasStepContent = description || splitPanel; - - const handleStepButtonClicked = useCallback( - (e) => { - e.preventDefault(); - onStepButtonClicked({ stepId, cardId, sectionId, undo: isDone ? true : false }); - }, - [cardId, isDone, onStepButtonClicked, sectionId, stepId] - ); - - return ( - - - - - - - - - {title} - - {badges.map((badge) => ( - - {badge.name} - - ))} - - - -
    - - {isDone ? UNDO_MARK_AS_DONE_TITLE : MARK_AS_DONE_TITLE} - - -
    -
    -
    - {expandStep && hasStepContent && ( - <> - - - {description && ( - - - {description?.map((desc, index) => ( -

    - {desc} -

    - ))} -
    -
    - )} - {splitPanel && ( - - {splitPanel} - - )} -
    - - )} -
    - ); -}; - -CardStepComponent.displayName = 'CardStepComponent'; - -export const CardStep = React.memo(CardStepComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/card_step.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/__mocks__/index.tsx similarity index 100% rename from x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/card_step.tsx rename to x-pack/plugins/security_solution_serverless/public/get_started/card_step/__mocks__/index.tsx diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_step/index.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/index.test.tsx new file mode 100644 index 0000000000000..a20b2c2c270e3 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/index.test.tsx @@ -0,0 +1,93 @@ +/* + * Copyright 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 { render, fireEvent } from '@testing-library/react'; +import { CardStep } from '.'; +import type { StepId } from '../types'; +import { GetSetUpCardId, IntroductionSteps, SectionId } from '../types'; +import { ProductLine } from '../../../common/product'; + +describe('CardStepComponent', () => { + const step = { + id: IntroductionSteps.getToKnowElasticSecurity, + }; + + const onStepClicked = jest.fn(); + const onStepButtonClicked = jest.fn(); + const expandedSteps = new Set([IntroductionSteps.getToKnowElasticSecurity]); + + const props = { + activeProducts: new Set([ProductLine.security]), + cardId: GetSetUpCardId.introduction, + expandedSteps, + finishedStepsByCard: new Set(), + onStepButtonClicked, + onStepClicked, + sectionId: SectionId.getSetUp, + stepId: step.id, + }; + const testStepTitle = 'Get to know Elastic Security'; + + it('should toggle step expansion on click', () => { + const { getByText } = render(); + + const stepTitle = getByText(testStepTitle); + fireEvent.click(stepTitle); + + expect(onStepClicked).toHaveBeenCalledTimes(1); + expect(onStepClicked).toHaveBeenCalledWith({ + sectionId: SectionId.getSetUp, + stepId: IntroductionSteps.getToKnowElasticSecurity, + cardId: GetSetUpCardId.introduction, + isExpanded: false, + }); + }); + + it('should render step title, badges, and description when expanded', () => { + const { getByText, getByTestId } = render(); + + const stepTitle = getByText(testStepTitle); + fireEvent.click(stepTitle); + + const badge1 = getByText('Analytics'); + const badge2 = getByText('Cloud'); + const badge3 = getByText('EDR'); + expect(badge1).toBeInTheDocument(); + expect(badge2).toBeInTheDocument(); + expect(badge3).toBeInTheDocument(); + + const description1 = getByTestId(`${IntroductionSteps.getToKnowElasticSecurity}-description-0`); + const description2 = getByTestId(`${IntroductionSteps.getToKnowElasticSecurity}-description-1`); + expect(description1).toBeInTheDocument(); + expect(description2).toBeInTheDocument(); + }); + + it('should render expended steps', () => { + const { getByTestId } = render(); + + const splitPanel = getByTestId('split-panel'); + expect(splitPanel).toBeInTheDocument(); + }); + + it('should render collapsed steps', () => { + const { queryByTestId } = render(); + + const splitPanel = queryByTestId('split-panel'); + expect(splitPanel).not.toBeInTheDocument(); + }); + + it('should render check icon when stepId is in finishedStepsByCard', () => { + const finishedStepsByCard = new Set([IntroductionSteps.getToKnowElasticSecurity]); + + const { getByTestId } = render( + + ); + + const checkIcon = getByTestId(`${step.id}-icon`); + expect(checkIcon.getAttribute('data-euiicon-type')).toEqual('checkInCircleFilled'); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_step/index.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/index.tsx new file mode 100644 index 0000000000000..c48ea85d80cc8 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/index.tsx @@ -0,0 +1,204 @@ +/* + * Copyright 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 { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiBadge, + useEuiTheme, + EuiButtonEmpty, + useEuiBackgroundColorCSS, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import React, { useCallback, useMemo } from 'react'; + +import classnames from 'classnames'; +import type { CardId, OnStepButtonClicked, OnStepClicked, SectionId, StepId } from '../types'; +import icon_step from '../images/icon_step.svg'; +import icon_cross from '../images/icon_cross.svg'; +import { UNDO_MARK_AS_DONE_TITLE, MARK_AS_DONE_TITLE } from '../translations'; +import { getStepsByActiveProduct } from '../helpers'; +import type { ProductLine } from '../../../common/product'; +import { getProductBadges } from '../badge'; +import { StepContent } from './step_content'; + +const CardStepComponent: React.FC<{ + activeProducts: Set; + cardId: CardId; + expandedSteps: Set; + finishedStepsByCard: Set; + onStepButtonClicked: OnStepButtonClicked; + onStepClicked: OnStepClicked; + sectionId: SectionId; + stepId: StepId; +}> = ({ + activeProducts, + cardId, + expandedSteps, + finishedStepsByCard = new Set(), + onStepButtonClicked, + onStepClicked, + sectionId, + stepId, +}) => { + const { euiTheme } = useEuiTheme(); + const backgroundColorStyles = useEuiBackgroundColorCSS(); + const isExpandedStep = expandedSteps.has(stepId); + const steps = useMemo( + () => getStepsByActiveProduct({ activeProducts, cardId, sectionId }), + [activeProducts, cardId, sectionId] + ); + const { title, productLineRequired, description, splitPanel } = + steps?.find((step) => step.id === stepId) ?? {}; + + const badges = useMemo(() => getProductBadges(productLineRequired), [productLineRequired]); + + const toggleStep = useCallback( + (e) => { + e.preventDefault(); + const newState = !isExpandedStep; + onStepClicked({ stepId, cardId, sectionId, isExpanded: newState }); + }, + [cardId, isExpandedStep, onStepClicked, sectionId, stepId] + ); + + const isDone = finishedStepsByCard.has(stepId); + + const hasStepContent = description != null || splitPanel != null; + + const handleStepButtonClicked = useCallback( + (e) => { + e.preventDefault(); + onStepButtonClicked({ stepId, cardId, sectionId, undo: isDone ? true : false }); + }, + [cardId, isDone, onStepButtonClicked, sectionId, stepId] + ); + + const panelClassNames = classnames({ + 'step-panel-collapsed': !isExpandedStep, + 'step-panel-expanded': isExpandedStep, + }); + + return ( + + + + + + + + + {title} + + {badges.map((badge) => ( + + {badge.name} + + ))} + + + +
    + {isExpandedStep && ( + + {isDone ? UNDO_MARK_AS_DONE_TITLE : MARK_AS_DONE_TITLE} + + )} + {/* Use button here to avoid styles added by EUI*/} + +
    +
    +
    + +
    + ); +}; + +CardStepComponent.displayName = 'CardStepComponent'; + +export const CardStep = React.memo(CardStepComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_step/step_content.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/step_content.test.tsx new file mode 100644 index 0000000000000..cb6a18387a538 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/step_content.test.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { StepContent } from './step_content'; + +describe('StepContent', () => { + it('renders nothing when hasStepContent is false', () => { + const { container } = render( + + ); + + expect(container.firstChild).toBeNull(); + }); + + it('renders step content when hasStepContent is true and isExpandedStep is true', () => { + const description = ['Description Line 1', 'Description Line 2']; + const splitPanel =
    {'Split Panel Content'}
    ; + const { getByTestId, getByText } = render( + + ); + + const splitPanelElement = getByTestId('split-panel'); + + expect(getByText('Description Line 1')).toBeInTheDocument(); + expect(getByText('Description Line 2')).toBeInTheDocument(); + + expect(splitPanelElement).toBeInTheDocument(); + expect(splitPanelElement).toHaveTextContent('Split Panel Content'); + }); + + it('renders nothing when hasStepContent is true but isExpandedStep is false', () => { + const { container } = render( + + ); + + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/card_step/step_content.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/step_content.tsx new file mode 100644 index 0000000000000..372d773829259 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/card_step/step_content.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 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 { EuiFlexGroup, EuiFlexItem, useEuiTheme, useEuiShadow, EuiText } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; + +const LEFT_CONTENT_PANEL_WIDTH = 486; +const RIGHT_CONTENT_PANEL_WIDTH = 510; +const RIGHT_CONTENT_HEIGHT = 270; +const RIGHT_CONTENT_WIDTH = 480; + +const StepContentComponent = ({ + description, + hasStepContent, + isExpandedStep, + splitPanel, + stepId, +}: { + description?: React.ReactNode[]; + hasStepContent: boolean; + isExpandedStep: boolean; + splitPanel?: React.ReactNode; + stepId: string; +}) => { + const { euiTheme } = useEuiTheme(); + const shadow = useEuiShadow('s'); + + return hasStepContent && isExpandedStep ? ( + <> + + {description && ( + + + {description?.map((desc, index) => ( +

    + {desc} +

    + ))} +
    +
    + )} + {splitPanel && ( + + {splitPanel && ( +
    + {splitPanel} +
    + )} +
    + )} +
    + + ) : null; +}; +export const StepContent = React.memo(StepContentComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/get_started.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/get_started.test.tsx index 51896789c6b31..5c67562385897 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/get_started.test.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/get_started.test.tsx @@ -31,7 +31,7 @@ describe('GetStartedComponent', () => { it('should render page title, subtitle, and description', () => { const { getByText } = render(); - const pageTitle = getByText('Welcome'); + const pageTitle = getByText('Welcome!'); const subtitle = getByText(`Let's get started`); const description = getByText( `Set up your Elastic Security workspace. Use the toggles below to curate a list of tasks that best fits your environment` diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/get_started.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/get_started.tsx index 6468ebd602543..126f9b178236d 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/get_started.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/get_started.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiTitle, useEuiTheme, useEuiShadow } from '@elastic/eui'; +import { EuiTitle, useEuiTheme, useEuiShadow, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { css } from '@emotion/react'; @@ -19,7 +19,7 @@ import { import type { SecurityProductTypes } from '../../common/config'; import { ProductSwitch } from './product_switch'; import { useTogglePanel } from './use_toggle_panel'; -import { useKibana } from '../common/services'; +import { ProductLine } from '../../common/product'; const CONTENT_WIDTH = 1150; @@ -30,6 +30,7 @@ export interface GetStartedProps { export const GetStartedComponent: React.FC = ({ productTypes }) => { const { euiTheme } = useEuiTheme(); const shadow = useEuiShadow('s'); + const { onProductSwitchChanged, onCardClicked, @@ -44,7 +45,10 @@ export const GetStartedComponent: React.FC = ({ productTypes }) expandedCardSteps, }, } = useTogglePanel({ productTypes }); - const services = useKibana().services; + const productTier = productTypes.find( + (product) => product.product_line === ProductLine.security + )?.product_tier; + return ( = ({ productTypes }) size="l" css={css` padding-left: ${euiTheme.size.xs}; + padding-bottom: ${euiTheme.size.l}; `} > {GET_STARTED_PAGE_TITLE} @@ -78,12 +83,24 @@ export const GetStartedComponent: React.FC = ({ productTypes }) } description={ <> - {GET_STARTED_PAGE_SUBTITLE} + + {GET_STARTED_PAGE_SUBTITLE} + + {GET_STARTED_PAGE_DESCRIPTION} } > - + { @@ -42,9 +42,7 @@ export const reducer = (state: TogglePanelReducer, action: ReducerActions): Togg : new Set(), }; - if (action.type === GetStartedPageActions.AddFinishedStep) { - finishedSteps[action.payload.cardId].add(action.payload.stepId); - } + finishedSteps[action.payload.cardId].add(action.payload.stepId); const { activeSections, totalStepsLeft, totalActiveSteps } = updateActiveSections({ activeProducts: state.activeProducts, @@ -71,9 +69,7 @@ export const reducer = (state: TogglePanelReducer, action: ReducerActions): Togg : new Set(), }; - if (action.type === GetStartedPageActions.RemoveFinishedStep) { - finishedSteps[action.payload.cardId].delete(action.payload.stepId); - } + finishedSteps[action.payload.cardId].delete(action.payload.stepId); const { activeSections, totalStepsLeft, totalActiveSteps } = updateActiveSections({ activeProducts: state.activeProducts, @@ -112,26 +108,44 @@ export const reducer = (state: TogglePanelReducer, action: ReducerActions): Togg action.type === GetStartedPageActions.ToggleExpandedCardStep && action.payload.isStepExpanded != null ) { - const expandedSteps = new Set( - [...state.expandedCardSteps[action.payload.cardId]?.expandedSteps] ?? [] - ); - if (action.payload.isStepExpanded === true && action.payload.stepId) { - expandedSteps.add(action.payload.stepId); + // It allows Only One step open at a time + const expandedSteps = new Set(); + if (action.payload.isStepExpanded === true && action.payload.stepId != null) { + return { + ...state, + expandedCardSteps: Object.entries(state.expandedCardSteps).reduce((acc, [cardId, card]) => { + if (action.payload.stepId != null && cardId === action.payload.cardId) { + expandedSteps.add(action.payload.stepId); + + acc[action.payload.cardId] = { + expandedSteps: [...expandedSteps], + isExpanded: state.expandedCardSteps[action.payload.cardId]?.isExpanded, + }; + } else { + // Remove all other expanded steps in other cards + acc[cardId as CardId] = { + expandedSteps: [], + isExpanded: card.isExpanded, + }; + } + return acc; + }, {} as ExpandedCardSteps), + }; } if (action.payload.isStepExpanded === false && action.payload.stepId) { expandedSteps.delete(action.payload.stepId); - } - return { - ...state, - expandedCardSteps: { - ...state.expandedCardSteps, - [action.payload.cardId]: { - expandedSteps: [...expandedSteps], - isExpanded: state.expandedCardSteps[action.payload.cardId]?.isExpanded, + return { + ...state, + expandedCardSteps: { + ...state.expandedCardSteps, + [action.payload.cardId]: { + expandedSteps: [...expandedSteps], + isExpanded: state.expandedCardSteps[action.payload.cardId]?.isExpanded, + }, }, - }, - }; + }; + } } return state; diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/sections.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/sections.tsx index c056e463dfb88..67f7d224e3165 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/sections.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/sections.tsx @@ -18,12 +18,17 @@ import * as i18n from './translations'; import explore from './images/explore.svg'; import { ProductLine } from '../../common/product'; import { FleetOverviewLink } from './step_links/fleet_overview_link'; -import { EndpointManagementLink } from './step_links/endpoint_management_link'; -import { IntegrationsLink } from './step_links/integrations_link'; -import { RulesManagementLink } from './step_links/rules_management_link'; -import { OverviewLink } from './step_links/overview_link'; -import { AlertsLink } from './step_links/alerts_link'; -import { ExploreLink } from './step_links/explore_link'; +import { InstallAgentButton } from './step_links/install_agent_button'; +import { AddIntegrationButton } from './step_links/add_integration_button'; +import { AlertsButton } from './step_links/alerts_link'; +import connectToDataSources from './images/connect_to_existing_sources.png'; +import endalbePrebuiltRules from './images/enable_prebuilt_rules.png'; +import deployElasticAgent from './images/deploy_elastic_agent_to_protect_your_endpoint.png'; +import learnAboutElasticAgent from './images/learn_about_elastic_agent.png'; +import viewAlerts from './images/view_alerts.png'; +import analyzeDataUsingDashboards from './images/analyze_data_using_dashboards.png'; +import { AddElasticRulesButton } from './step_links/add_elastic_rules_button'; +import { DashboardButton } from './step_links/dashboard_button'; export const introductionSteps = [ { @@ -36,12 +41,12 @@ export const introductionSteps = [ className="vidyard_iframe" frameBorder="0" height="100%" + width="100%" referrerPolicy="no-referrer" sandbox="allow-scripts allow-same-origin" scrolling="no" src="//play.vidyard.com/K6kKDBbP9SpXife9s2tHNP.html?" title={i18n.WATCH_OVERVIEW_VIDEO_HEADER} - width="100%" /> ), timeInMinutes: 3, @@ -52,23 +57,39 @@ const configureSteps = [ { id: ConfigureSteps.learnAbout, title: i18n.CONFIGURE_STEP1, - description: [], + description: [ + i18n.CONFIGURE_STEP1_DESCRIPTION1, + i18n.CONFIGURE_STEP1_DESCRIPTION2, + , + ], + splitPanel: ( + {i18n.CONFIGURE_STEP1} + ), }, { id: ConfigureSteps.deployElasticAgent, title: i18n.CONFIGURE_STEP2, - description: [i18n.CONFIGURE_STEP2_DESCRIPTION1, ], + description: [i18n.CONFIGURE_STEP2_DESCRIPTION1, ], + splitPanel: ( + {i18n.CONFIGURE_STEP2} + ), }, { id: ConfigureSteps.connectToDataSources, title: i18n.CONFIGURE_STEP3, - description: [i18n.CONFIGURE_STEP3_DESCRIPTION1, ], + description: [i18n.CONFIGURE_STEP3_DESCRIPTION1, ], productLineRequired: [ProductLine.security], + splitPanel: ( + {i18n.CONFIGURE_STEP3} + ), }, { id: ConfigureSteps.enablePrebuiltRules, title: i18n.CONFIGURE_STEP4, - description: [i18n.CONFIGURE_STEP4_DESCRIPTION1, ], + description: [i18n.CONFIGURE_STEP4_DESCRIPTION1, ], + splitPanel: ( + {i18n.CONFIGURE_STEP4} + ), }, ]; @@ -76,12 +97,16 @@ const exploreSteps = [ { id: ExploreSteps.viewAlerts, title: i18n.EXPLORE_STEP1, - description: [i18n.EXPLORE_STEP1_DESCRIPTION1, ], + description: [i18n.EXPLORE_STEP1_DESCRIPTION1, ], + splitPanel: {i18n.EXPLORE_STEP1}, }, { id: ExploreSteps.analyzeData, title: i18n.EXPLORE_STEP2, - description: [, ], + description: [i18n.EXPLORE_STEP2_DESCRIPTION1, ], + splitPanel: ( + {i18n.EXPLORE_STEP2} + ), }, ]; diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/add_elastic_rules_button.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/add_elastic_rules_button.tsx new file mode 100644 index 0000000000000..01b86c4f1c073 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/add_elastic_rules_button.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { LinkButton } from '@kbn/security-solution-navigation/links'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; + +const AddElasticRulesButtonComponent = () => ( + + + +); + +export const AddElasticRulesButton = React.memo(AddElasticRulesButtonComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/add_integration_button.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/add_integration_button.tsx new file mode 100644 index 0000000000000..1c69e318ca093 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/add_integration_button.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { LinkButton } from '@kbn/security-solution-navigation/links'; +import { ExternalPageName } from '../../navigation/links/constants'; + +const AddIntegrationButtonComponent = () => ( + + + +); + +export const AddIntegrationButton = React.memo(AddIntegrationButtonComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/alerts_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/alerts_link.tsx index 00b6b02c9a800..6d814ca14ed78 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/alerts_link.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/alerts_link.tsx @@ -6,36 +6,18 @@ */ import { FormattedMessage } from '@kbn/i18n-react'; -import { LinkAnchor, useGetLinkProps } from '@kbn/security-solution-navigation/links'; +import { LinkButton } from '@kbn/security-solution-navigation/links'; import { SecurityPageName } from '@kbn/security-solution-navigation'; -import React, { useCallback } from 'react'; +import React from 'react'; -const AlertsLinkComponent = () => { - const getLinkProps = useGetLinkProps(); - const onClick = useCallback((e) => { - // TODO: telemetry https://github.com/elastic/kibana/issues/163247 - }, []); - const { onClick: onLinkClicked } = getLinkProps({ - id: SecurityPageName.alerts, - onClick, - }); - return ( +const AlertsButtonComponent = () => ( + - - - ), - }} + id="xpack.securitySolutionServerless.getStarted.togglePanel.explore.step1.description2.button" + defaultMessage="View alerts" /> - ); -}; + +); -export const AlertsLink = React.memo(AlertsLinkComponent); +export const AlertsButton = React.memo(AlertsButtonComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/dashboard_button.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/dashboard_button.tsx new file mode 100644 index 0000000000000..3ded0074bfd50 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/dashboard_button.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { LinkButton } from '@kbn/security-solution-navigation/links'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; + +const DashboardButtonComponent = () => ( + + + +); + +export const DashboardButton = React.memo(DashboardButtonComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/endpoint_management_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/endpoint_management_link.tsx deleted file mode 100644 index 51f762b4d0a82..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/endpoint_management_link.tsx +++ /dev/null @@ -1,46 +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. - */ -/* - * Copyright 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 } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { LinkAnchor, useGetLinkProps } from '@kbn/security-solution-navigation/links'; -import { SecurityPageName } from '@kbn/security-solution-navigation'; - -const EndpointManagementLinkComponent = () => { - const getLinkProps = useGetLinkProps(); - const onClick = useCallback((e) => { - // TODO: telemetry https://github.com/elastic/kibana/issues/163247 - }, []); - const { onClick: onLinkClicked } = getLinkProps({ - id: SecurityPageName.endpoints, - onClick, - }); - return ( - - - - ), - }} - /> - ); -}; - -export const EndpointManagementLink = React.memo(EndpointManagementLinkComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/explore_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/explore_link.tsx deleted file mode 100644 index 3fc6fde33b275..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/explore_link.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useCallback } from 'react'; -import { LinkAnchor, useGetLinkProps } from '@kbn/security-solution-navigation/links'; -import { SecurityPageName } from '@kbn/security-solution-navigation'; - -const ExploreLinkComponent = () => { - const getLinkProps = useGetLinkProps(); - const onClick = useCallback((e) => { - // TODO: telemetry https://github.com/elastic/kibana/issues/163247 - }, []); - const { onClick: onLinkClicked } = getLinkProps({ - id: SecurityPageName.exploreLanding, - onClick, - }); - - return ( - - - - ), - }} - /> - ); -}; - -export const ExploreLink = React.memo(ExploreLinkComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/fleet_overview_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/fleet_overview_link.tsx index 7c354ed9701d6..1629a798f253a 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/fleet_overview_link.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/fleet_overview_link.tsx @@ -7,30 +7,19 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiLink } from '@elastic/eui'; -import { CONFIGURE_STEP1_DESCRIPTION1 } from '../translations'; +import { EuiButton } from '@elastic/eui'; const FleetOverviewLinkComponent = () => ( - <> - <>{CONFIGURE_STEP1_DESCRIPTION1} + - - - ), - }} + defaultMessage="Go here to learn more!" /> - + ); export const FleetOverviewLink = React.memo(FleetOverviewLinkComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/install_agent_button.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/install_agent_button.tsx new file mode 100644 index 0000000000000..cf233a84ccf52 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/install_agent_button.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright 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 { FormattedMessage } from '@kbn/i18n-react'; +import { LinkButton } from '@kbn/security-solution-navigation/links'; +import { ExternalPageName } from '../../navigation/links/constants'; + +const InstallAgentButtonComponent = () => ( + + + +); + +export const InstallAgentButton = React.memo(InstallAgentButtonComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/integrations_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/integrations_link.tsx deleted file mode 100644 index ecc0dbab0ae31..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/integrations_link.tsx +++ /dev/null @@ -1,44 +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 { FormattedMessage } from '@kbn/i18n-react'; -import React, { useCallback } from 'react'; -import { EuiLink } from '@elastic/eui'; -import { useNavigation } from '@kbn/security-solution-navigation'; - -const IntegrationsLinkComponent = () => { - const { getAppUrl, navigateTo } = useNavigation(); - - const integrationsUrl = getAppUrl({ appId: 'integrations', path: '/browse/security' }); - const onClick = useCallback( - (e) => { - e.preventDefault(); - // TODO: telemetry https://github.com/elastic/kibana/issues/163247 - navigateTo({ url: integrationsUrl }); - }, - [navigateTo, integrationsUrl] - ); - return ( - - - - ), - }} - /> - ); -}; - -export const IntegrationsLink = React.memo(IntegrationsLinkComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/overview_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/overview_link.tsx deleted file mode 100644 index 94783e7ce35d3..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/overview_link.tsx +++ /dev/null @@ -1,44 +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 { FormattedMessage } from '@kbn/i18n-react'; -import React, { useCallback } from 'react'; -import { LinkAnchor, useGetLinkProps } from '@kbn/security-solution-navigation/links'; -import { SecurityPageName } from '@kbn/security-solution-navigation'; -import { EXPLORE_STEP2_DESCRIPTION1 } from '../translations'; - -const OverviewLinkComponent = () => { - const getLinkProps = useGetLinkProps(); - const onClick = useCallback((e) => { - // TODO: telemetry https://github.com/elastic/kibana/issues/163247 - }, []); - const { onClick: onLinkClicked } = getLinkProps({ - id: SecurityPageName.overview, - onClick, - }); - return ( - <> - {EXPLORE_STEP2_DESCRIPTION1} - - - - ), - }} - /> - - ); -}; - -export const OverviewLink = React.memo(OverviewLinkComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/rules_management_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/step_links/rules_management_link.tsx deleted file mode 100644 index 4d64dfd88824e..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/step_links/rules_management_link.tsx +++ /dev/null @@ -1,40 +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 { FormattedMessage } from '@kbn/i18n-react'; -import React, { useCallback } from 'react'; -import { LinkAnchor, useGetLinkProps } from '@kbn/security-solution-navigation/links'; -import { SecurityPageName } from '@kbn/security-solution-navigation'; - -const RulesManagementLinkComponent = () => { - const getLinkProps = useGetLinkProps(); - const onClick = useCallback((e) => { - // TODO: telemetry https://github.com/elastic/kibana/issues/163247 - }, []); - const { onClick: onLinkClicked } = getLinkProps({ - id: SecurityPageName.rules, - onClick, - }); - return ( - - - - ), - }} - /> - ); -}; - -export const RulesManagementLink = React.memo(RulesManagementLinkComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/storage.test.ts b/x-pack/plugins/security_solution_serverless/public/get_started/storage.test.ts index ec90cc6710461..5df60b91897df 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/storage.test.ts +++ b/x-pack/plugins/security_solution_serverless/public/get_started/storage.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { getStartedStorage } from './storage'; -import { GetSetUpCardId, IntroductionSteps, type StepId } from './types'; +import { defaultExpandedCards, getStartedStorage } from './storage'; +import { ConfigureSteps, GetSetUpCardId, IntroductionSteps, type StepId } from './types'; import { storage } from '../common/lib/storage'; import type { MockStorage } from '../common/lib/__mocks__/storage'; import { ProductLine } from '../../common/product'; @@ -139,4 +139,102 @@ describe('useStorage', () => { [GetSetUpCardId.introduction]: [], }); }); + + it('should get all expanded card steps from storage', () => { + (mockStorage.get as jest.Mock).mockReturnValueOnce({ + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + }); + const result = getStartedStorage.getAllExpandedCardStepsFromStorage(); + expect(mockStorage.get).toHaveBeenCalledWith('EXPANDED_CARDS'); + expect(result).toEqual({ + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + }); + }); + + it('should get default expanded card steps from storage', () => { + (mockStorage.get as jest.Mock).mockReturnValueOnce(null); + const result = getStartedStorage.getAllExpandedCardStepsFromStorage(); + expect(mockStorage.get).toHaveBeenCalledWith('EXPANDED_CARDS'); + expect(result).toEqual(defaultExpandedCards); + }); + + it('should reset card steps in storage', () => { + (mockStorage.get as jest.Mock).mockReturnValueOnce({ + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + }); + getStartedStorage.resetAllExpandedCardStepsToStorage(); + expect(mockStorage.set).toHaveBeenCalledWith('EXPANDED_CARDS', { + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [], + }, + }); + }); + + it('should add a step to expanded card steps in storage', () => { + (mockStorage.get as jest.Mock).mockReturnValueOnce({ + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + }); + getStartedStorage.addExpandedCardStepToStorage( + GetSetUpCardId.configure, + ConfigureSteps.learnAbout + ); + expect(mockStorage.set).toHaveBeenCalledWith('EXPANDED_CARDS', { + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + [GetSetUpCardId.configure]: { + isExpanded: true, + expandedSteps: [ConfigureSteps.learnAbout], + }, + }); + }); + + it('should remove a step from expanded card steps in storage', () => { + (mockStorage.get as jest.Mock).mockReturnValueOnce({ + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + }); + getStartedStorage.removeExpandedCardStepFromStorage( + GetSetUpCardId.introduction, + IntroductionSteps.getToKnowElasticSecurity + ); + expect(mockStorage.set).toHaveBeenCalledWith('EXPANDED_CARDS', { + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [], + }, + }); + }); + + it('should update a card from expanded card steps in storage', () => { + (mockStorage.get as jest.Mock).mockReturnValueOnce({ + [GetSetUpCardId.introduction]: { + isExpanded: true, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + }); + getStartedStorage.removeExpandedCardStepFromStorage(GetSetUpCardId.introduction); + expect(mockStorage.set).toHaveBeenCalledWith('EXPANDED_CARDS', { + [GetSetUpCardId.introduction]: { + isExpanded: false, + expandedSteps: [IntroductionSteps.getToKnowElasticSecurity], + }, + }); + }); }); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/storage.ts b/x-pack/plugins/security_solution_serverless/public/get_started/storage.ts index 56ba6acdb8e04..233096eef71ae 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/storage.ts +++ b/x-pack/plugins/security_solution_serverless/public/get_started/storage.ts @@ -7,12 +7,19 @@ import type { ProductLine } from '../../common/product'; import type { CardId, StepId } from './types'; +import { GetSetUpCardId } from './types'; import { storage } from '../common/lib/storage'; export const ACTIVE_PRODUCTS_STORAGE_KEY = 'ACTIVE_PRODUCTS'; export const FINISHED_STEPS_STORAGE_KEY = 'FINISHED_STEPS'; export const EXPANDED_CARDS_STORAGE_KEY = 'EXPANDED_CARDS'; +export const defaultExpandedCards = { + [GetSetUpCardId.configure]: { isExpanded: true, expandedSteps: [] }, + [GetSetUpCardId.introduction]: { isExpanded: true, expandedSteps: [] }, + [GetSetUpCardId.explore]: { isExpanded: true, expandedSteps: [] }, +}; + export const getStartedStorage = { getActiveProductsFromStorage: () => { const activeProducts: ProductLine[] = storage.get(ACTIVE_PRODUCTS_STORAGE_KEY); @@ -39,6 +46,7 @@ export const getStartedStorage = { storage.get(FINISHED_STEPS_STORAGE_KEY) ?? {}; return allFinishedSteps; }, + addFinishedStepToStorage: (cardId: CardId, stepId: StepId) => { const finishedSteps: Record = storage.get(FINISHED_STEPS_STORAGE_KEY) ?? {}; const card: StepId[] = finishedSteps[cardId] ?? []; @@ -56,10 +64,28 @@ export const getStartedStorage = { } storage.set(FINISHED_STEPS_STORAGE_KEY, { ...finishedSteps, [cardId]: steps }); }, - getAllExpandedCardStepsFromStorage: () => storage.get(EXPANDED_CARDS_STORAGE_KEY) ?? {}, + getAllExpandedCardStepsFromStorage: () => { + const storageData = storage.get(EXPANDED_CARDS_STORAGE_KEY); + + return !storageData || Object.keys(storageData).length === 0 + ? defaultExpandedCards + : storageData; + }, + resetAllExpandedCardStepsToStorage: () => { + const activeCards: Record = + getStartedStorage.getAllExpandedCardStepsFromStorage(); + + storage.set( + EXPANDED_CARDS_STORAGE_KEY, + Object.entries(activeCards).reduce((acc, [cardId, card]) => { + acc[cardId as CardId] = { ...card, expandedSteps: [] }; + return acc; + }, {} as Record) + ); + }, addExpandedCardStepToStorage: (cardId: CardId, stepId?: StepId) => { const activeCards: Record = - storage.get(EXPANDED_CARDS_STORAGE_KEY) ?? {}; + getStartedStorage.getAllExpandedCardStepsFromStorage(); const card = activeCards[cardId] ? { ...activeCards[cardId], isExpanded: true } : { diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/translations.ts b/x-pack/plugins/security_solution_serverless/public/get_started/translations.ts index 6b141c2b455b4..d0df5f323cf7d 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/translations.ts +++ b/x-pack/plugins/security_solution_serverless/public/get_started/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export const GET_STARTED_PAGE_TITLE = i18n.translate( 'xpack.securitySolutionServerless.getStarted.title', { - defaultMessage: `Welcome`, + defaultMessage: `Welcome!`, } ); @@ -28,47 +28,6 @@ export const GET_STARTED_PAGE_DESCRIPTION = i18n.translate( } ); -export const WELCOME_PANEL_PROJECT_CREATED_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.getStarted.welcomePanel.projectCreated.title', - { - defaultMessage: `Project created`, - } -); - -export const WELCOME_PANEL_PROJECT_CREATED_DESCRIPTION = i18n.translate( - 'xpack.securitySolutionServerless.getStarted.welcomePanel.projectCreated.description', - { - defaultMessage: `View all projects here.`, - } -); - -export const WELCOME_PANEL_INVITE_YOUR_TEAM_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.getStarted.welcomePanel.inviteYourTeam.title', - { - defaultMessage: 'Invite your team', - } -); - -export const WELCOME_PANEL_INVITE_YOUR_TEAM_DESCRIPTION = i18n.translate( - 'xpack.securitySolutionServerless.getStarted.welcomePanel.inviteYourTeam.description', - { - defaultMessage: `Boost security through collaboration`, - } -); - -export const WELCOME_PANEL_PROGRESS_TRACKER_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.getStarted.welcomePanel.progressTracker.title', - { - defaultMessage: 'Progress tracker', - } -); - -export const WELCOME_PANEL_PROGRESS_TRACKER_DESCRIPTION = (tasks: number) => - i18n.translate('xpack.securitySolutionServerless.getStarted.welcomePanel.progressTracker.note', { - defaultMessage: `{tasks, plural, =1 {task} other {tasks}} completed`, - values: { tasks }, - }); - export const STEP_TIME_MIN = (min: number) => i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.progressTracker.stepTimeMin', @@ -161,7 +120,7 @@ export const CONFIGURE_TITLE = i18n.translate( export const CONFIGURE_STEP1 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step1', { - defaultMessage: 'Learn about Elastic Agent and Fleet', + defaultMessage: 'Learn about Elastic Agent', } ); @@ -169,22 +128,28 @@ export const CONFIGURE_STEP1_DESCRIPTION1 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step1.description1', { defaultMessage: - 'Elastic Agent is a single, unified way to add monitoring for logs, metrics, and other types of data to a host. It can also protect hosts from security threats, query data from operating systems, forward data from remote services or hardware, and more.', + 'Deploy the Elastic Agent to each endpoint you want to protect. This allows it to monitor and protect them by collecting data and enforcing your security policies. It sends that data to the Elastic Stack for analysis and storage.', + } +); + +export const CONFIGURE_STEP1_DESCRIPTION2 = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step1.description2', + { + defaultMessage: 'In the next step, you will deploy the Elastic Agent to your endpoints.', } ); export const CONFIGURE_STEP2 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step2', { - defaultMessage: 'Deploy Elastic Defend to protect your endpoints', + defaultMessage: 'Deploy Elastic Agent to protect your endpoints', } ); export const CONFIGURE_STEP2_DESCRIPTION1 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step2.description1', { - defaultMessage: - 'Elastic Defend provides organizations with prevention, detection, and response capabilities with deep visibility for EPP, EDR, SIEM, and Security Analytics use cases across Windows, macOS, and Linux operating systems running on both traditional endpoints and public cloud environments.', + defaultMessage: 'Deploy the Elastic Agent to each endpoint you want to monitor or protect.', } ); @@ -199,7 +164,7 @@ export const CONFIGURE_STEP3_DESCRIPTION1 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step3.description1', { defaultMessage: - 'Elastic integrations provide an easy way to connect Elastic to external services and systems, and quickly get insights or take action. They can collect new sources of data, and they often ship with out-of-the-box assets like dashboards, visualizations, and pipelines to extract structured fields out of logs and events.', + 'Use third-party integrations to import data from common sources and help you gather relevant information in one place. To find integrations for your use case, search for tools and data providers on the Add integrations page.', } ); @@ -213,7 +178,7 @@ export const CONFIGURE_STEP3_BUTTON = i18n.translate( export const CONFIGURE_STEP4 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step4', { - defaultMessage: 'Enable prebuilt rules or create your own', + defaultMessage: 'Enable prebuilt rules', } ); @@ -221,7 +186,7 @@ export const CONFIGURE_STEP4_DESCRIPTION1 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.configure.step4.description1', { defaultMessage: - 'Rules run periodically and search for suspicious events, sequences, machine learning anomalies, and more! When a rule’s criteria are met, a detection alert is created.', + 'Elastic Security comes with prebuilt detection rules that run in the background and create alerts when their conditions are met.', } ); @@ -250,14 +215,14 @@ export const EXPLORE_STEP1_DESCRIPTION1 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.explore.step1.description1', { defaultMessage: - 'The Alerts page displays all detection alerts following rule configuration from above. From the Alerts page, you can prioritize, triage, investigate alerts, and escalate alerts to a Case. Rules must be enabled for any alerts to be created.', + 'Visualize, sort, filter, and investigate alerts from across your infrastructure. Examine individual alerts of interest, and discover general patterns in alert volume and severity.', } ); export const EXPLORE_STEP2 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.explore.step2', { - defaultMessage: 'Analyze data dashboards', + defaultMessage: 'Analyze data using dashboards', } ); @@ -265,7 +230,7 @@ export const EXPLORE_STEP2_DESCRIPTION1 = i18n.translate( 'xpack.securitySolutionServerless.getStarted.togglePanel.explore.step2.description1', { defaultMessage: - 'The Overview dashboard provides a high-level snapshot of alerts and events. It helps you assess overall system health and find anomalies that may require further investigation.', + 'Use dashboards to visualize data and stay up-to-date with key information. Create your own, or use Elastic’s default dashboards — including alerts, user authentication events, known vulnerabilities, and more.', } ); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/types.ts b/x-pack/plugins/security_solution_serverless/public/get_started/types.ts index e425c6fe6370a..45fac68dca11e 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/types.ts +++ b/x-pack/plugins/security_solution_serverless/public/get_started/types.ts @@ -10,15 +10,6 @@ import type React from 'react'; import type { ProductLine } from '../../common/product'; -export interface HeaderSection { - description?: (params: { - totalActiveSteps: number | null; - totalStepsLeft: number | null; - }) => React.ReactNode | null; - icon: EuiIconProps; - id: string; - title: string; -} export interface Section { cards?: Card[]; icon?: EuiIconProps; @@ -92,8 +83,11 @@ export interface ActiveCard { stepsLeft: number; activeStepIds: StepId[] | undefined; } - -export type ExpandedCardSteps = Record; +export interface ExpandedCardStep { + isExpanded: boolean; + expandedSteps: StepId[]; +} +export type ExpandedCardSteps = Record; export interface TogglePanelReducer { activeProducts: Set; activeSections: ActiveSections | null; diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/use_setup_cards.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/use_setup_cards.tsx index 3c0a94a6ab6e2..294589b4f017d 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/use_setup_cards.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/use_setup_cards.tsx @@ -118,6 +118,7 @@ export const useSetUpSections = ({ borderRadius="none" css={css` margin: ${euiTheme.size.l} 0; + padding-top: 4px; `} key={currentSection.id} data-test-subj={`section-${currentSection.id}`} diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.test.tsx index 3061dcfdd0acb..0828d1d141dda 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.test.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.test.tsx @@ -167,7 +167,7 @@ describe('useTogglePanel', () => { ); }); - test('should call addFinishedStepToStorage when onStepClicked is executed', () => { + test('should reset all the card steps in storage when a step is expanded. (As it allows only one step open at a time)', () => { const { result } = renderHook(() => useTogglePanel({ productTypes })); const { onStepClicked } = result.current; @@ -181,14 +181,31 @@ describe('useTogglePanel', () => { }); }); - expect(getStartedStorage.addFinishedStepToStorage).toHaveBeenCalledTimes(1); - expect(getStartedStorage.addFinishedStepToStorage).toHaveBeenCalledWith( + expect(getStartedStorage.resetAllExpandedCardStepsToStorage).toHaveBeenCalledTimes(1); + }); + + test('should add the current step to storage when it is expanded', () => { + const { result } = renderHook(() => useTogglePanel({ productTypes })); + + const { onStepClicked } = result.current; + + act(() => { + onStepClicked({ + stepId: IntroductionSteps.getToKnowElasticSecurity, + cardId: GetSetUpCardId.introduction, + sectionId: SectionId.getSetUp, + isExpanded: true, + }); + }); + + expect(getStartedStorage.addExpandedCardStepToStorage).toHaveBeenCalledTimes(1); + expect(getStartedStorage.addExpandedCardStepToStorage).toHaveBeenCalledWith( GetSetUpCardId.introduction, IntroductionSteps.getToKnowElasticSecurity ); }); - test('should not call addFinishedStepToStorage when the step is going to be collapsed', () => { + test('should remove the current step from storage when it is collapsed', () => { const { result } = renderHook(() => useTogglePanel({ productTypes })); const { onStepClicked } = result.current; @@ -202,7 +219,11 @@ describe('useTogglePanel', () => { }); }); - expect(getStartedStorage.addFinishedStepToStorage).not.toHaveBeenCalledTimes(1); + expect(getStartedStorage.removeExpandedCardStepFromStorage).toHaveBeenCalledTimes(1); + expect(getStartedStorage.removeExpandedCardStepFromStorage).toHaveBeenCalledWith( + GetSetUpCardId.introduction, + IntroductionSteps.getToKnowElasticSecurity + ); }); test('should call addFinishedStepToStorage when onStepButtonClicked is executed', () => { diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.tsx index b51c3d09d942d..361e56e9d90e6 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/use_toggle_panel.tsx @@ -23,6 +23,7 @@ export const useTogglePanel = ({ productTypes }: { productTypes: SecurityProduct getAllFinishedStepsFromStorage, getActiveProductsFromStorage, toggleActiveProductsInStorage, + resetAllExpandedCardStepsToStorage, addFinishedStepToStorage, removeFinishedStepFromStorage, addExpandedCardStepToStorage, @@ -79,17 +80,18 @@ export const useTogglePanel = ({ productTypes }: { productTypes: SecurityProduct payload: { stepId, cardId, isStepExpanded: isExpanded }, }); if (isExpanded) { - dispatch({ - type: GetStartedPageActions.AddFinishedStep, - payload: { stepId, cardId, sectionId }, - }); - addFinishedStepToStorage(cardId, stepId); + // It allows Only One step open at a time + resetAllExpandedCardStepsToStorage(); addExpandedCardStepToStorage(cardId, stepId); } else { removeExpandedCardStepFromStorage(cardId, stepId); } }, - [addExpandedCardStepToStorage, addFinishedStepToStorage, removeExpandedCardStepFromStorage] + [ + addExpandedCardStepToStorage, + removeExpandedCardStepFromStorage, + resetAllExpandedCardStepsToStorage, + ] ); const onCardClicked: OnCardClicked = useCallback( diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel.test.tsx deleted file mode 100644 index 24655ac3bdeab..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render, type RenderResult } from '@testing-library/react'; -import { WelcomePanel } from './welcome_panel'; -jest.mock('@elastic/eui', () => { - const original = jest.requireActual('@elastic/eui'); - return { - ...original, - useEuiTheme: jest.fn().mockReturnValue({ - euiTheme: { base: 16, size: { xs: '4px' }, colors: { mediumShade: '' } }, - }), - }; -}); - -describe('WelcomePanel', () => { - let result: RenderResult; - const props = { - totalActiveSteps: 3, - totalStepsLeft: 2, - }; - - beforeEach(() => { - result = render(); - }); - - it('should render the welcome panel with project created header card', () => { - const { getByText } = result; - - expect(getByText('Project created')).toBeInTheDocument(); - }); - - it('should render the welcome panel with invite your team header card', () => { - const { getByText } = result; - - expect(getByText('Invite your team')).toBeInTheDocument(); - }); - - it('should render the welcome panel with progress tracker header card', () => { - const { getByText } = result; - - expect(getByText('Progress tracker')).toBeInTheDocument(); - }); - - it('should render the project created header card with the correct icon', () => { - const { getByTestId } = result; - - expect(getByTestId('projectCreatedIcon')).toBeInTheDocument(); - }); - - it('should render the invite your team header card with the correct icon', () => { - const { getByTestId } = result; - - expect(getByTestId('inviteYourTeamIcon')).toBeInTheDocument(); - }); - - it('should render the progress tracker header card with the correct icon', () => { - const { getByTestId } = result; - - expect(getByTestId('progressTrackerIcon')).toBeInTheDocument(); - }); - - it('should render the project tracker card with the correct description', () => { - const { getByText } = result; - - expect(getByText('1 of 3')).toBeInTheDocument(); - expect(getByText('tasks completed')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel.tsx deleted file mode 100644 index 43eb336e227b2..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel.tsx +++ /dev/null @@ -1,105 +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 { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, useEuiTheme } from '@elastic/eui'; - -import { css } from '@emotion/react'; -import React from 'react'; -import progress from './images/progress.svg'; -import invite from './images/invite.svg'; -import type { HeaderSection } from './types'; -import { - WELCOME_PANEL_PROJECT_CREATED_TITLE, - WELCOME_PANEL_PROJECT_CREATED_DESCRIPTION, - WELCOME_PANEL_INVITE_YOUR_TEAM_TITLE, - WELCOME_PANEL_INVITE_YOUR_TEAM_DESCRIPTION, - WELCOME_PANEL_PROGRESS_TRACKER_TITLE, -} from './translations'; -import { ProgressTracker } from './progress_tracker'; - -const headerCards: HeaderSection[] = [ - { - icon: { type: 'checkInCircleFilled', color: '#00BFB3' }, - title: WELCOME_PANEL_PROJECT_CREATED_TITLE, - description: () => WELCOME_PANEL_PROJECT_CREATED_DESCRIPTION, - id: 'projectCreated', - }, - { - icon: { type: invite }, - title: WELCOME_PANEL_INVITE_YOUR_TEAM_TITLE, - description: () => WELCOME_PANEL_INVITE_YOUR_TEAM_DESCRIPTION, - id: 'inviteYourTeam', - }, - { - icon: { type: progress }, - title: WELCOME_PANEL_PROGRESS_TRACKER_TITLE, - id: 'progressTracker', - description: ({ - totalActiveSteps, - totalStepsLeft, - }: { - totalActiveSteps?: number | null; - totalStepsLeft?: number | null; - }) => , - }, -]; - -const WelcomePanelComponent = ({ - totalActiveSteps, - totalStepsLeft, -}: { - totalActiveSteps: number | null; - totalStepsLeft: number | null; -}) => { - const { euiTheme } = useEuiTheme(); - - return ( - - {headerCards.map((item, index) => { - return ( - - - ) : undefined - } - title={ - - {item.title} - - } - description={ - - {item?.description?.({ totalActiveSteps, totalStepsLeft })} - - } - hasBorder - paddingSize="l" - /> - - ); - })} - - ); -}; - -export const WelcomePanel = React.memo(WelcomePanelComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/welcome_panel.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/__mocks__/index.tsx similarity index 100% rename from x-pack/plugins/security_solution_serverless/public/get_started/__mocks__/welcome_panel.tsx rename to x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/__mocks__/index.tsx diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.test.tsx new file mode 100644 index 0000000000000..7b1f82d368f5a --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.test.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 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 { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; // For DOM matchers like toBeInTheDocument +import { ChangePlanLink } from './change_plan_link'; // Replace with your import path +import { ProductTier } from '../../../common/product'; + +jest.mock('../../common/services', () => ({ + useKibana: jest.fn().mockReturnValue({ + services: { + cloud: { + projectsUrl: 'https://cloud.elastic.co/projects', + }, + }, + }), +})); + +describe('ChangePlanLink', () => { + it('renders nothing when productTier is undefined', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('renders the link and badge when productTier is defined', () => { + const { getByText, getByTestId } = render( + + ); + const badge = getByTestId('product-tier-badge'); + const link = getByText('Change plan'); + expect(badge).toBeInTheDocument(); + expect(link).toBeInTheDocument(); + }); + + it('does not render badge when productTier is defined', () => { + const { queryByTestId } = render(); + const badge = queryByTestId('product-tier-badge'); + expect(badge).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.tsx new file mode 100644 index 0000000000000..19d6afd4a5843 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { SecurityPageName } from '@kbn/security-solution-plugin/common'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + useEuiTheme, + useEuiBackgroundColorCSS, + EuiLink, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import type { ProductTier } from '../../../common/product'; +import { ProductTierBadge } from './product_tier_badge'; +import { WELCOME_PANEL_PROJECT_CREATED_CHANGE_PLAN_TITLE } from './translations'; +import { getCloudUrl } from '../../navigation/links/util'; +import { useKibana } from '../../common/services'; + +const ChangePlanLinkComponent = ({ productTier }: { productTier: ProductTier | undefined }) => { + const { euiTheme } = useEuiTheme(); + const { cloud } = useKibana().services; + const backgroundColorStyles = useEuiBackgroundColorCSS(); + return productTier ? ( + <> + {/*
    cannot appear as a descendant of

    , EuiSpacer is a div */} + + + + + + {WELCOME_PANEL_PROJECT_CREATED_CHANGE_PLAN_TITLE} + + + + + + + ) : null; +}; + +export const ChangePlanLink = React.memo(ChangePlanLinkComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/index.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/index.test.tsx new file mode 100644 index 0000000000000..e63324d3454d6 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/index.test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 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 { render, type RenderResult } from '@testing-library/react'; +import { I18nProvider } from '@kbn/i18n-react'; + +import { WelcomePanel } from '.'; +import { ProductTier } from '../../../common/product'; +jest.mock('@kbn/security-solution-navigation/src/context'); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + useEuiTheme: jest.fn().mockReturnValue({ + euiTheme: { + base: 16, + size: { xs: '4px' }, + colors: { mediumShade: '' }, + border: { radius: { medium: '4px' } }, + }, + }), + }; +}); + +jest.mock('../../common/services', () => ({ + useKibana: jest.fn().mockReturnValue({ + services: { + cloud: { + projectsUrl: 'projectsUrl', + organizationUrl: 'organizationUrl', + }, + }, + }), +})); + +describe('WelcomePanel', () => { + let result: RenderResult; + const props = { + totalActiveSteps: 3, + totalStepsLeft: 2, + productTier: ProductTier.complete, + }; + + beforeEach(() => { + result = render(, { + wrapper: ({ children }) => {children}, + }); + }); + + it('should render the welcome panel with project created header card', () => { + const { getByText } = result; + + expect(getByText('Project created')).toBeInTheDocument(); + }); + + it('should render the welcome panel with invite your team header card', () => { + const { getByText } = result; + + expect(getByText('Invite your team')).toBeInTheDocument(); + }); + + it('should render the welcome panel with progress tracker header card', () => { + const { getByText } = result; + + expect(getByText('Progress tracker')).toBeInTheDocument(); + }); + + it('should render the project created header card with the correct icon', () => { + const { getByTestId } = result; + + expect(getByTestId('projectCreatedIcon')).toBeInTheDocument(); + }); + + it('should render the invite your team header card with the correct icon', () => { + const { getByTestId } = result; + + expect(getByTestId('inviteYourTeamIcon')).toBeInTheDocument(); + }); + + it('should render the progress tracker header card with the correct icon', () => { + const { getByTestId } = result; + + expect(getByTestId('progressTrackerIcon')).toBeInTheDocument(); + }); + + it('should render the project tracker card with the correct description', () => { + const { getByText } = result; + + expect(getByText('1 of 3')).toBeInTheDocument(); + expect(getByText('tasks completed')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/index.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/index.tsx new file mode 100644 index 0000000000000..5d271f20dcfcc --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/index.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, useEuiTheme } from '@elastic/eui'; + +import { css } from '@emotion/react'; +import React from 'react'; + +import type { ProductTier } from '../../../common/product'; +import { useWelcomePanel } from './use_welcome_panel'; + +const WelcomePanelComponent = ({ + totalActiveSteps, + totalStepsLeft, + productTier, +}: { + totalActiveSteps: number | null; + totalStepsLeft: number | null; + productTier: ProductTier | undefined; +}) => { + const { euiTheme } = useEuiTheme(); + const headerCards = useWelcomePanel({ + productTier, + totalActiveSteps, + totalStepsLeft, + }); + + return ( + + {headerCards.map((item, index) => { + return ( + + + ) : undefined + } + title={ + + {item.title} + + } + description={ + <> + + {item.description && item.description} + + {item.footer && ( + + {item.footer} + + )} + + } + hasBorder + paddingSize="l" + /> + + ); + })} + + ); +}; + +export const WelcomePanel = React.memo(WelcomePanelComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/product_tier_badge.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/product_tier_badge.test.tsx new file mode 100644 index 0000000000000..e739d2cb95ea5 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/product_tier_badge.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { ProductTierBadge } from './product_tier_badge'; +import { ProductTier } from '../../../common/product'; + +describe('ProductTierBadge', () => { + it('renders nothing when productTier is undefined', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('renders the badge with the correct text when productTier is essential', () => { + const { getByTestId } = render(); + const badge = getByTestId('product-tier-badge'); + expect(badge).toHaveTextContent('Essential'); + }); + + it('renders the badge with the correct text when productTier is complete', () => { + const { getByTestId } = render(); + const badge = getByTestId('product-tier-badge'); + expect(badge).toHaveTextContent('Complete'); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/product_tier_badge.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/product_tier_badge.tsx new file mode 100644 index 0000000000000..559db32d83cf5 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/product_tier_badge.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiBadge, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { ProductTier } from '../../../common/product'; +import { PRODUCT_TIER_ESSENTIAL, PRODUCT_TIER_COMPLETE } from './translations'; + +const ProductTierBadgeComponent = ({ productTier }: { productTier: ProductTier | undefined }) => { + const { euiTheme } = useEuiTheme(); + return productTier ? ( + + + {productTier === ProductTier.essentials && PRODUCT_TIER_ESSENTIAL} + {productTier === ProductTier.complete && PRODUCT_TIER_COMPLETE} + + + ) : null; +}; + +export const ProductTierBadge = React.memo(ProductTierBadgeComponent); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/progress_tracker.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/progress_tracker.test.tsx new file mode 100644 index 0000000000000..7cc7c5c9f59e7 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/progress_tracker.test.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { ProgressTracker } from './progress_tracker'; // Replace with your import path + +describe('ProgressTracker', () => { + it('renders nothing when totalActiveSteps and totalStepsLeft are null', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('renders nothing when totalActiveSteps is null', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('renders nothing when totalStepsLeft is null', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('renders the progress description when both totalActiveSteps and totalStepsLeft are provided', () => { + const { getByText } = render(); + expect(getByText('5 of 10')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/progress_tracker.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/progress_tracker.tsx similarity index 100% rename from x-pack/plugins/security_solution_serverless/public/get_started/progress_tracker.tsx rename to x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/progress_tracker.tsx diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/translations.ts b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/translations.ts new file mode 100644 index 0000000000000..313bbe65b5a1c --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/translations.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const WELCOME_PANEL_PROJECT_CREATED_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.welcomePanel.projectCreated.title', + { + defaultMessage: `Project created`, + } +); + +export const WELCOME_PANEL_PROJECT_CREATED_CHANGE_PLAN_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.welcomePanel.projectCreated.changePlan.title', + { + defaultMessage: `Change plan`, + } +); + +export const PRODUCT_TIER_ESSENTIAL = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.welcomePanel.productTier.essential', + { + defaultMessage: `Essential`, + } +); + +export const PRODUCT_TIER_COMPLETE = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.welcomePanel.productTier.complete', + { + defaultMessage: `Complete`, + } +); + +export const WELCOME_PANEL_INVITE_YOUR_TEAM_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.welcomePanel.inviteYourTeam.title', + { + defaultMessage: 'Invite your team', + } +); + +export const WELCOME_PANEL_INVITE_YOUR_TEAM_DESCRIPTION = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.welcomePanel.inviteYourTeam.description', + { + defaultMessage: `Boost security through collaboration`, + } +); + +export const WELCOME_PANEL_PROGRESS_TRACKER_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.getStarted.welcomePanel.progressTracker.title', + { + defaultMessage: 'Progress tracker', + } +); + +export const WELCOME_PANEL_PROGRESS_TRACKER_DESCRIPTION = (tasks: number) => + i18n.translate('xpack.securitySolutionServerless.getStarted.welcomePanel.progressTracker.note', { + defaultMessage: `{tasks, plural, =1 {task} other {tasks}} completed`, + values: { tasks }, + }); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/types.ts b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/types.ts new file mode 100644 index 0000000000000..cb38a8bfa0339 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { EuiIconProps } from '@elastic/eui'; + +export interface HeaderSection { + description?: React.ReactNode | null; + footer?: React.ReactNode | null; + icon: EuiIconProps; + id: string; + title: string; +} diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/use_welcome_panel.test.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/use_welcome_panel.test.tsx new file mode 100644 index 0000000000000..8eff5a580033d --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/use_welcome_panel.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useWelcomePanel } from './use_welcome_panel'; +import { ProductTier } from '../../../common/product'; + +jest.mock('../../common/services', () => ({ + useKibana: jest.fn(() => ({ + services: { + cloud: { projectsUrl: 'projectsUrl' }, + }, + })), +})); + +describe('useWelcomePanel', () => { + it('should return the correct welcome panel sections', () => { + const productTier = ProductTier.essentials; + const totalActiveSteps = 5; + const totalStepsLeft = 3; + + const { result } = renderHook(() => + useWelcomePanel({ productTier, totalActiveSteps, totalStepsLeft }) + ); + + expect(result.current[0].title).toBe('Project created'); + expect(result.current[1].title).toBe('Invite your team'); + expect(result.current[2].title).toBe('Progress tracker'); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/use_welcome_panel.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/use_welcome_panel.tsx new file mode 100644 index 0000000000000..dfa3d61ae3e64 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/use_welcome_panel.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 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 { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; +import progress from '../images/progress.svg'; +import invite from '../images/invite.svg'; +import type { HeaderSection } from './types'; +import { + WELCOME_PANEL_PROJECT_CREATED_TITLE, + WELCOME_PANEL_INVITE_YOUR_TEAM_TITLE, + WELCOME_PANEL_INVITE_YOUR_TEAM_DESCRIPTION, + WELCOME_PANEL_PROGRESS_TRACKER_TITLE, +} from './translations'; +import { ProgressTracker } from './progress_tracker'; +import { useKibana } from '../../common/services'; +import { getCloudUrl } from '../../navigation/links/util'; +import type { ProductTier } from '../../../common/product'; +import { ChangePlanLink } from './change_plan_link'; + +export const useWelcomePanel = ({ + productTier, + totalActiveSteps, + totalStepsLeft, +}: { + productTier: ProductTier | undefined; + totalActiveSteps: number | null; + totalStepsLeft: number | null; +}): HeaderSection[] => { + const { cloud } = useKibana().services; + + const welcomePanel: HeaderSection[] = useMemo( + () => [ + { + icon: { type: 'checkInCircleFilled', color: '#00BFB3' }, + title: WELCOME_PANEL_PROJECT_CREATED_TITLE, + description: ( + + + + ), + }} + /> + ), + id: 'projectCreated', + footer: , + }, + { + icon: { type: invite }, + title: WELCOME_PANEL_INVITE_YOUR_TEAM_TITLE, + description: <>{WELCOME_PANEL_INVITE_YOUR_TEAM_DESCRIPTION}, + id: 'inviteYourTeam', + footer: ( + <> + + + + + + + + + ), + }, + { + icon: { type: progress }, + title: WELCOME_PANEL_PROGRESS_TRACKER_TITLE, + id: 'progressTracker', + description: ( + + ), + }, + ], + [cloud, productTier, totalActiveSteps, totalStepsLeft] + ); + + return welcomePanel; +}; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/index.ts b/x-pack/plugins/security_solution_serverless/public/navigation/index.ts index 84842a90e1f74..19684479e7dd6 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/index.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/index.ts @@ -27,6 +27,7 @@ export const configureNavigation = ( if (!serverConfig.developer.disableManagementUrlRedirect) { management.setLandingPageRedirect(SECURITY_PROJECT_SETTINGS_PATH); } + management.setIsSidebarEnabled(false); serverless.setProjectHome(APP_PATH); serverless.setSideNavComponent(getSecuritySideNavComponent(services)); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts index 0d0b606976bfe..520cbdd192ae4 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts @@ -32,10 +32,14 @@ export const SecurityPagePath = { export enum ExternalPageName { // Osquery osquery = 'osquery:', + // Analytics + maps = 'maps:', + visualize = 'visualize:', // Machine Learning // Ref: packages/default-nav/ml/default_navigation.ts mlOverview = 'ml:overview', mlNotifications = 'ml:notifications', + mlMemoryUsage = 'ml:memoryUsage', mlAnomalyDetection = 'ml:anomalyDetection', mlAnomalyExplorer = 'ml:anomalyExplorer', mlSingleMetricViewer = 'ml:singleMetricViewer', @@ -47,7 +51,8 @@ export enum ExternalPageName { mlNodes = 'ml:nodes', mlFileUpload = 'ml:fileUpload', mlIndexDataVisualizer = 'ml:indexDataVisualizer', - mlExplainLogRateSpikes = 'ml:explainLogRateSpikes', + mlDataComparison = 'ml:dataComparison', + mlExplainLogRateSpikes = 'ml:logRateAnalysis', mlLogPatternAnalysis = 'ml:logPatternAnalysis', mlChangePointDetections = 'ml:changePointDetections', // Dev Tools diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_links.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_links.ts index ed324cfb6f52c..32c7587097616 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_links.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_links.ts @@ -13,14 +13,21 @@ import type { ProjectLinkCategory, ProjectNavigationLink } from '../types'; import * as i18n from './ml_translations'; import { IconLensLazy, - IconEndpointLazy, - IconSpacesLazy, - IconIndexManagementLazy, - IconDataConnectorLazy, - IconDevToolsLazy, - IconFleetLazy, - IconAuditbeatLazy, - IconSiemLazy, + IconMarketingLazy, + IconInfraLazy, + IconFilebeatChartLazy, + IconJobsLazy, + IconKeywordLazy, + IconDashboardLazy, + IconVisualizationLazy, + IconSettingsLazy, + IconChartArrowLazy, + IconManagerLazy, + IconFilebeatLazy, + IconReplicationLazy, + IconDataViewLazy, + IconIntuitiveLazy, + IconRapidBarGraphLazy, } from '../../../common/lazy_icons'; // appLinks configures the Security Solution pages links @@ -38,7 +45,11 @@ export const mlAppLink: LinkItem = { export const mlNavCategories: ProjectLinkCategory[] = [ { type: LinkCategoryType.separator, - linkIds: [ExternalPageName.mlOverview, ExternalPageName.mlNotifications], + linkIds: [ + ExternalPageName.mlOverview, + ExternalPageName.mlNotifications, + ExternalPageName.mlMemoryUsage, + ], }, { type: LinkCategoryType.title, @@ -62,12 +73,16 @@ export const mlNavCategories: ProjectLinkCategory[] = [ { type: LinkCategoryType.title, label: i18n.MODEL_MANAGEMENT_CATEGORY, - linkIds: [ExternalPageName.mlNodesOverview, ExternalPageName.mlNodes], + linkIds: [ExternalPageName.mlNodesOverview], }, { type: LinkCategoryType.title, label: i18n.DATA_VISUALIZER_CATEGORY, - linkIds: [ExternalPageName.mlFileUpload, ExternalPageName.mlIndexDataVisualizer], + linkIds: [ + ExternalPageName.mlFileUpload, + ExternalPageName.mlIndexDataVisualizer, + ExternalPageName.mlDataComparison, + ], }, { type: LinkCategoryType.title, @@ -91,91 +106,97 @@ export const mlNavLinks: ProjectNavigationLink[] = [ { id: ExternalPageName.mlNotifications, title: i18n.NOTIFICATIONS_TITLE, - landingIcon: IconEndpointLazy, + landingIcon: IconMarketingLazy, description: i18n.NOTIFICATIONS_DESC, }, + { + id: ExternalPageName.mlMemoryUsage, + title: i18n.MEMORY_USAGE_TITLE, + landingIcon: IconInfraLazy, + description: i18n.MEMORY_USAGE_DESC, + }, { id: ExternalPageName.mlAnomalyDetection, title: i18n.ANOMALY_DETECTION_TITLE, - landingIcon: IconSpacesLazy, + landingIcon: IconJobsLazy, description: i18n.ANOMALY_DETECTION_DESC, }, { id: ExternalPageName.mlAnomalyExplorer, title: i18n.ANOMALY_EXPLORER_TITLE, - landingIcon: IconIndexManagementLazy, + landingIcon: IconKeywordLazy, description: i18n.ANOMALY_EXPLORER_DESC, }, { id: ExternalPageName.mlSingleMetricViewer, title: i18n.SINGLE_METRIC_VIEWER_TITLE, - landingIcon: IconDataConnectorLazy, + landingIcon: IconVisualizationLazy, description: i18n.SINGLE_METRIC_VIEWER_DESC, }, { id: ExternalPageName.mlSettings, title: i18n.SETTINGS_TITLE, - landingIcon: IconDevToolsLazy, + landingIcon: IconSettingsLazy, description: i18n.SETTINGS_DESC, }, { id: ExternalPageName.mlDataFrameAnalytics, title: i18n.DATA_FRAME_ANALYTICS_TITLE, - landingIcon: IconIndexManagementLazy, + landingIcon: IconJobsLazy, description: i18n.DATA_FRAME_ANALYTICS_DESC, }, { id: ExternalPageName.mlResultExplorer, title: i18n.RESULT_EXPLORER_TITLE, - landingIcon: IconFleetLazy, + landingIcon: IconDashboardLazy, description: i18n.RESULT_EXPLORER_DESC, }, { id: ExternalPageName.mlAnalyticsMap, title: i18n.ANALYTICS_MAP_TITLE, - landingIcon: IconAuditbeatLazy, + landingIcon: IconChartArrowLazy, description: i18n.ANALYTICS_MAP_DESC, }, { id: ExternalPageName.mlNodesOverview, title: i18n.NODES_OVERVIEW_TITLE, - landingIcon: IconSiemLazy, + landingIcon: IconManagerLazy, description: i18n.NODES_OVERVIEW_DESC, }, - { - id: ExternalPageName.mlNodes, - title: i18n.NODES_TITLE, - landingIcon: IconEndpointLazy, - description: i18n.NODES_DESC, - }, { id: ExternalPageName.mlFileUpload, title: i18n.FILE_UPLOAD_TITLE, - landingIcon: IconEndpointLazy, + landingIcon: IconFilebeatLazy, description: i18n.FILE_UPLOAD_DESC, }, { id: ExternalPageName.mlIndexDataVisualizer, title: i18n.INDEX_DATA_VISUALIZER_TITLE, - landingIcon: IconEndpointLazy, + landingIcon: IconDataViewLazy, description: i18n.INDEX_DATA_VISUALIZER_DESC, }, + { + id: ExternalPageName.mlDataComparison, + title: i18n.DATA_COMPARISON_TITLE, + landingIcon: IconRapidBarGraphLazy, + description: i18n.DATA_COMPARISON_DESC, + }, { id: ExternalPageName.mlExplainLogRateSpikes, - title: i18n.EXPLAIN_LOG_RATE_SPIKES_TITLE, - landingIcon: IconEndpointLazy, - description: i18n.EXPLAIN_LOG_RATE_SPIKES_DESC, + title: i18n.LOG_RATE_ANALYSIS_TITLE, + landingIcon: IconFilebeatChartLazy, + description: i18n.LOG_RATE_ANALYSIS_DESC, }, { id: ExternalPageName.mlLogPatternAnalysis, title: i18n.LOG_PATTERN_ANALYSIS_TITLE, - landingIcon: IconEndpointLazy, + landingIcon: IconReplicationLazy, description: i18n.LOG_PATTERN_ANALYSIS_DESC, }, { id: ExternalPageName.mlChangePointDetections, title: i18n.CHANGE_POINT_DETECTIONS_TITLE, - landingIcon: IconEndpointLazy, + landingIcon: IconIntuitiveLazy, description: i18n.CHANGE_POINT_DETECTIONS_DESC, }, ]; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_translations.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_translations.ts index 301e5b05bfa67..36a28d561bda5 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_translations.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/ml_translations.ts @@ -69,6 +69,18 @@ export const NOTIFICATIONS_DESC = i18n.translate( defaultMessage: 'Notifications page', } ); +export const MEMORY_USAGE_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.ml.memoryUsage.title', + { + defaultMessage: 'Memory usage', + } +); +export const MEMORY_USAGE_DESC = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.ml.memoryUsage.desc', + { + defaultMessage: 'Memory usage page', + } +); export const ANOMALY_DETECTION_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.anomalyDetection.title', { @@ -90,7 +102,7 @@ export const ANOMALY_EXPLORER_TITLE = i18n.translate( export const ANOMALY_EXPLORER_DESC = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.anomalyExplorer.desc', { - defaultMessage: 'Anomaly explorer Page', + defaultMessage: 'Anomaly explorer page', } ); export const SINGLE_METRIC_VIEWER_TITLE = i18n.translate( @@ -156,13 +168,13 @@ export const ANALYTICS_MAP_DESC = i18n.translate( export const NODES_OVERVIEW_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.nodesOverview.title', { - defaultMessage: 'Nodes overview', + defaultMessage: 'Trained models', } ); export const NODES_OVERVIEW_DESC = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.nodesOverview.desc', { - defaultMessage: 'Nodes overview page', + defaultMessage: 'Trained models page', } ); export const NODES_TITLE = i18n.translate( @@ -180,37 +192,49 @@ export const NODES_DESC = i18n.translate( export const FILE_UPLOAD_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.fileUpload.title', { - defaultMessage: 'File', + defaultMessage: 'File data visualizer', } ); export const FILE_UPLOAD_DESC = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.fileUpload.desc', { - defaultMessage: 'File page', + defaultMessage: 'File data visualizer page', } ); export const INDEX_DATA_VISUALIZER_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.indexDataVisualizer.title', { - defaultMessage: 'Data view', + defaultMessage: 'Data view data visualizer', } ); export const INDEX_DATA_VISUALIZER_DESC = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.indexDataVisualizer.desc', { - defaultMessage: 'Data view page', + defaultMessage: 'Data view data visualizer page', + } +); +export const DATA_COMPARISON_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.ml.datComparison.title', + { + defaultMessage: 'Data comparison', + } +); +export const DATA_COMPARISON_DESC = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.ml.datComparison.desc', + { + defaultMessage: 'Data comparison page', } ); -export const EXPLAIN_LOG_RATE_SPIKES_TITLE = i18n.translate( +export const LOG_RATE_ANALYSIS_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.explainLogRateSpikes.title', { - defaultMessage: 'Explain log rate spikes', + defaultMessage: 'Log Rate Analysis', } ); -export const EXPLAIN_LOG_RATE_SPIKES_DESC = i18n.translate( +export const LOG_RATE_ANALYSIS_DESC = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.explainLogRateSpikes.desc', { - defaultMessage: 'Explain log rate spikes page', + defaultMessage: 'Log Rate Analysis Page', } ); export const LOG_PATTERN_ANALYSIS_TITLE = i18n.translate( @@ -228,12 +252,12 @@ export const LOG_PATTERN_ANALYSIS_DESC = i18n.translate( export const CHANGE_POINT_DETECTIONS_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.changePointDetections.title', { - defaultMessage: 'Change point detections', + defaultMessage: 'Change point detection', } ); export const CHANGE_POINT_DETECTIONS_DESC = i18n.translate( 'xpack.securitySolutionServerless.navLinks.ml.changePointDetections.desc', { - defaultMessage: 'Change point detections page', + defaultMessage: 'Change point detection page', } ); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts index 039ffcfda7dd2..69e560a929c09 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts @@ -11,12 +11,11 @@ import type { LinkItem } from '@kbn/security-solution-plugin/public'; import { ExternalPageName, SecurityPagePath } from '../constants'; import type { ProjectLinkCategory, ProjectNavigationLink } from '../types'; import { - IconGraphLazy, - IconLoggingLazy, - IconIndexManagementLazy, - IconSecurityShieldLazy, IconMapServicesLazy, - IconProductFeaturesAlertingLazy, + IconIndexManagementLazy, + IconUsersRolesLazy, + IconReportingLazy, + IconVisualizationLazy, } from '../../../common/lazy_icons'; import * as i18n from './project_settings_translations'; @@ -43,7 +42,7 @@ export const createProjectSettingsLinkFromManage = (manageLink: LinkItem): LinkI return { ...projectSettingsAppLink, - links: projectSettingsSubLinks, // cloudDefend and endpoints links are added in the projectAppLinksSwitcher on runtime + links: projectSettingsSubLinks, }; }; @@ -53,17 +52,23 @@ export const projectSettingsNavCategories: ProjectLinkCategory[] = [ linkIds: [ ExternalPageName.cloudUsersAndRoles, ExternalPageName.cloudBilling, - ExternalPageName.integrationsSecurity, SecurityPageName.entityAnalyticsManagement, ], }, + { + type: LinkCategoryType.separator, + linkIds: [ + ExternalPageName.integrationsSecurity, + ExternalPageName.maps, + ExternalPageName.visualize, + ], + }, { type: LinkCategoryType.accordion, label: i18n.MANAGEMENT_CATEGORY_TITLE, categories: [ { label: i18n.DATA_CATEGORY_TITLE, - iconType: IconIndexManagementLazy, linkIds: [ ExternalPageName.managementIndexManagement, ExternalPageName.managementTransforms, @@ -75,7 +80,6 @@ export const projectSettingsNavCategories: ProjectLinkCategory[] = [ }, { label: i18n.ALERTS_INSIGHTS_CATEGORY_TITLE, - iconType: IconProductFeaturesAlertingLazy, linkIds: [ ExternalPageName.managementCases, ExternalPageName.managementTriggersActionsConnectors, @@ -84,7 +88,6 @@ export const projectSettingsNavCategories: ProjectLinkCategory[] = [ }, { label: i18n.CONTENT_CATEGORY_TITLE, - iconType: IconSecurityShieldLazy, linkIds: [ ExternalPageName.managementObjects, ExternalPageName.managementFiles, @@ -94,7 +97,6 @@ export const projectSettingsNavCategories: ProjectLinkCategory[] = [ }, { label: i18n.OTHER_CATEGORY_TITLE, - iconType: IconMapServicesLazy, linkIds: [ExternalPageName.managementApiKeys, ExternalPageName.managementSettings], }, ], @@ -107,13 +109,13 @@ export const projectSettingsNavLinks: ProjectNavigationLink[] = [ id: ExternalPageName.cloudUsersAndRoles, title: i18n.CLOUD_USERS_ROLES_TITLE, description: i18n.CLOUD_USERS_ROLES_DESCRIPTION, - landingIcon: IconGraphLazy, + landingIcon: IconUsersRolesLazy, }, { id: ExternalPageName.cloudBilling, title: i18n.CLOUD_BILLING_TITLE, description: i18n.CLOUD_BILLING_DESCRIPTION, - landingIcon: IconLoggingLazy, + landingIcon: IconReportingLazy, }, { id: ExternalPageName.integrationsSecurity, @@ -121,6 +123,18 @@ export const projectSettingsNavLinks: ProjectNavigationLink[] = [ description: i18n.INTEGRATIONS_DESCRIPTION, landingIcon: IconIndexManagementLazy, }, + { + id: ExternalPageName.maps, + title: i18n.MAPS_TITLE, + description: i18n.MAPS_DESCRIPTION, + landingIcon: IconMapServicesLazy, + }, + { + id: ExternalPageName.visualize, + title: i18n.VISUALIZE_TITLE, + description: i18n.VISUALIZE_DESCRIPTION, + landingIcon: IconVisualizationLazy, + }, { id: ExternalPageName.managementIndexManagement, title: i18n.MANAGEMENT_INDEX_MANAGEMENT_TITLE, diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts index 036c3a350a74f..56ef414457dfa 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts @@ -174,3 +174,28 @@ export const OTHER_CATEGORY_TITLE = i18n.translate( defaultMessage: 'OTHER', } ); +export const MAPS_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.projectSettings.maps.title', + { + defaultMessage: 'Maps', + } +); +export const MAPS_DESCRIPTION = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.projectSettings.maps.description', + { + defaultMessage: 'Plot geographic data', + } +); +export const VISUALIZE_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.projectSettings.visualize.title', + { + defaultMessage: 'Visualize library', + } +); + +export const VISUALIZE_DESCRIPTION = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.projectSettings.visualize.description', + { + defaultMessage: 'Visualize library page', + } +); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/types.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/types.ts index 929724a0ddfad..8062fc3fc9a1d 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/types.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/types.ts @@ -11,6 +11,7 @@ import type { NavigationLink, LinkCategory, } from '@kbn/security-solution-navigation'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { ExternalPageName } from './constants'; export type ProjectPageName = SecurityPageName | ExternalPageName | 'root'; @@ -18,3 +19,4 @@ export type ProjectPageName = SecurityPageName | ExternalPageName | 'root'; export type ProjectNavigationLink = NavigationLink; export type ProjectLinkCategory = LinkCategory; export type ProjectNavLinks = Observable; +export type GetCloudUrl = (cloudUrlKey: string, cloud: CloudStart) => string | undefined; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts index 109f28ba04624..0572dcdedb2ea 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts @@ -6,8 +6,7 @@ */ import { APP_UI_ID } from '@kbn/security-solution-plugin/common'; -import type { CloudStart } from '@kbn/cloud-plugin/public'; -import type { ProjectPageName } from './types'; +import type { GetCloudUrl, ProjectPageName } from './types'; export const getNavLinkIdFromProjectPageName = (projectNavLinkId: ProjectPageName): string => { const cleanId = projectNavLinkId.replace(/\/(.*)$/, ''); // remove any trailing path @@ -23,7 +22,7 @@ export const getProjectPageNameFromNavLinkId = (navLinkId: string): ProjectPageN export const isCloudLink = (linkId: string): boolean => linkId.startsWith('cloud:'); export const getCloudLinkKey = (linkId: string): string => linkId.replace('cloud:', ''); -export const getCloudUrl = (cloudUrlKey: string, cloud: CloudStart): string | undefined => { +export const getCloudUrl: GetCloudUrl = (cloudUrlKey, cloud) => { switch (cloudUrlKey) { case 'billing': return cloud.billingUrl; @@ -37,6 +36,8 @@ export const getCloudUrl = (cloudUrlKey: string, cloud: CloudStart): string | un return cloud.profileUrl; case 'usersAndRoles': return cloud.usersAndRolesUrl; + case 'projects': + return cloud.projectsUrl; default: return undefined; } diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree.ts b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree.ts index 7210498a97d57..55b82759e6a02 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree.ts @@ -35,6 +35,10 @@ const HIDDEN_BREADCRUMBS = new Set([ SecurityPageName.sessions, ]); +const isBreadcrumbHidden = (id: ProjectPageName): boolean => + HIDDEN_BREADCRUMBS.has(id) || + id.startsWith('management:'); /* management sub-pages set their breadcrumbs themselves */ + export const subscribeNavigationTree = (services: Services): void => { const { serverless, getProjectNavLinks$ } = services; @@ -59,13 +63,12 @@ export const getFormatChromeProjectNavNodes = (services: Services) => { const navLinkId = getNavLinkIdFromProjectPageName(id); if (chrome.navLinks.has(navLinkId)) { - const breadcrumbHidden = HIDDEN_BREADCRUMBS.has(id); const link: ChromeProjectNavigationNode = { id: navLinkId, title, path: [...path, navLinkId], deepLink: chrome.navLinks.get(navLinkId), - ...(breadcrumbHidden && { breadcrumbStatus: 'hidden' }), + ...(isBreadcrumbHidden(id) && { breadcrumbStatus: 'hidden' }), }; // check default navigation for children const defaultChildrenNav = getDefaultChildrenNav(id, link); diff --git a/x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx b/x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx index 9c3f8e5012a67..71809249b266e 100644 --- a/x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { EuiHorizontalRule, EuiPageHeader, EuiSpacer } from '@elastic/eui'; import { - LandingLinksIcons, + LandingLinksIconsCategories, LandingLinksIconsCategoriesGroups, } from '@kbn/security-solution-navigation/landing_links'; -import type { AccordionLinkCategory } from '@kbn/security-solution-navigation'; +import type { AccordionLinkCategory, NavigationLink } from '@kbn/security-solution-navigation'; import { isAccordionLinkCategory, isSeparatorLinkCategory, @@ -25,13 +25,21 @@ export const ProjectSettingsRoute: React.FC = () => { const projectSettingsLink = useNavLink(SecurityPageName.projectSettings); const { links = [], categories = [], title } = projectSettingsLink ?? {}; - const iconLinkIds = - categories.find((category) => isSeparatorLinkCategory(category))?.linkIds ?? []; - const iconLinks = links.filter(({ id }) => iconLinkIds.includes(id)); + const iconLinks = categories.reduce((acc, category) => { + if (isSeparatorLinkCategory(category)) { + const categoryLinks = links.filter(({ id }) => category.linkIds.includes(id)); + + acc.push(...categoryLinks); + } + return acc; + }, []); const accordionCategories = (categories.filter((category) => isAccordionLinkCategory(category)) ?? []) as AccordionLinkCategory[]; + const separatorCategories = (categories.filter((category) => isSeparatorLinkCategory(category)) ?? + []) as AccordionLinkCategory[]; + return ( @@ -39,7 +47,7 @@ export const ProjectSettingsRoute: React.FC = () => { - + diff --git a/x-pack/plugins/security_solution_serverless/public/plugin.ts b/x-pack/plugins/security_solution_serverless/public/plugin.ts index 05ea3185ce43c..b562e16293c66 100644 --- a/x-pack/plugins/security_solution_serverless/public/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/public/plugin.ts @@ -8,6 +8,7 @@ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import { getSecurityGetStartedComponent } from './get_started'; +import { getDashboardsLandingCallout } from './components/dashboards_landing_callout'; import type { SecuritySolutionServerlessPluginSetup, SecuritySolutionServerlessPluginStart, @@ -57,6 +58,7 @@ export class SecuritySolutionServerlessPlugin registerUpsellings(securitySolution.getUpselling(), this.config.productTypes, services); securitySolution.setGetStartedPage(getSecurityGetStartedComponent(services, productTypes)); + securitySolution.setDashboardsLandingCallout(getDashboardsLandingCallout(services)); securitySolution.setIsILMAvailable(false); configureNavigation(services, this.config); diff --git a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering.ts b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering.ts index e7e1ef475f24f..c2770a53ff41d 100644 --- a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering.ts +++ b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering.ts @@ -4,21 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - CNVM_POLICY_TEMPLATE, -} from '@kbn/cloud-security-posture-plugin/common/constants'; import { ProductLine } from '../../common/product'; import { getCloudSecurityUsageRecord } from './cloud_security_metering_task'; -import type { PostureType } from './types'; +import { CLOUD_DEFEND, CNVM, CSPM, KSPM } from './constants'; +import type { CloudSecuritySolutions } from './types'; import type { MeteringCallbackInput, Tier, UsageRecord } from '../types'; import type { ServerlessSecurityConfig } from '../config'; -export const CLOUD_SECURITY_TASK_TYPE = 'cloud_security'; -export const AGGREGATION_PRECISION_THRESHOLD = 40000; - export const cloudSecurityMetringCallback = async ({ esClient, cloudSetup, @@ -36,28 +28,26 @@ export const cloudSecurityMetringCallback = async ({ const tier: Tier = getCloudProductTier(config); try { - const postureTypes: PostureType[] = [ - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - CNVM_POLICY_TEMPLATE, - ]; + const cloudSecuritySolutions: CloudSecuritySolutions[] = [CSPM, KSPM, CNVM, CLOUD_DEFEND]; const cloudSecurityUsageRecords = await Promise.all( - postureTypes.map((postureType) => + cloudSecuritySolutions.map((cloudSecuritySolution) => getCloudSecurityUsageRecord({ esClient, projectId, logger, taskId, lastSuccessfulReport, - postureType, + cloudSecuritySolution, tier, }) ) ); // remove any potential undefined values from the array, - return cloudSecurityUsageRecords.filter(Boolean) as UsageRecord[]; + return cloudSecurityUsageRecords + .filter((record) => record !== undefined && record.length > 0) + .flatMap((record) => record) as UsageRecord[]; } catch (err) { logger.error(`Failed to fetch Cloud Security metering data ${err}`); return []; diff --git a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.test.ts b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.test.ts index 66cb9bc748c09..8e03c61050439 100644 --- a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.test.ts +++ b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.test.ts @@ -6,40 +6,33 @@ */ import Chance from 'chance'; import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - CNVM_POLICY_TEMPLATE, -} from '@kbn/cloud-security-posture-plugin/common/constants'; -import { CLOUD_SECURITY_TASK_TYPE, getCloudProductTier } from './cloud_security_metering'; + +import { getCloudProductTier } from './cloud_security_metering'; import { getCloudSecurityUsageRecord } from './cloud_security_metering_task'; import type { ServerlessSecurityConfig } from '../config'; -import type { PostureType } from './types'; +import type { CloudSecuritySolutions } from './types'; import type { ProductTier } from '../../common/product'; +import { CLOUD_SECURITY_TASK_TYPE, CSPM, KSPM, CNVM } from './constants'; const mockEsClient = elasticsearchServiceMock.createStart().client.asInternalUser; const logger: ReturnType = loggingSystemMock.createLogger(); const chance = new Chance(); -const postureTypes: PostureType[] = [ - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - CNVM_POLICY_TEMPLATE, -]; +const cloudSecuritySolutions: CloudSecuritySolutions[] = [CSPM, KSPM, CNVM]; describe('getCloudSecurityUsageRecord', () => { beforeEach(() => { jest.resetAllMocks(); }); - it('should return undefined if postureType is missing', async () => { + it('should return undefined if cloudSecuritySolution is missing', async () => { // Mock Elasticsearch search to throw an error mockEsClient.search.mockRejectedValue({}); const projectId = chance.guid(); const taskId = chance.guid(); - const postureType = CSPM_POLICY_TEMPLATE; + const cloudSecuritySolution = CSPM; const tier = 'essentials' as ProductTier; @@ -49,16 +42,16 @@ describe('getCloudSecurityUsageRecord', () => { logger, taskId, lastSuccessfulReport: new Date(), - postureType, + cloudSecuritySolution, tier, }); expect(result).toBeUndefined(); }); - test.each(postureTypes)( - 'should return usageRecords with correct values for cspm and kspm when Elasticsearch response has aggregations', - async (postureType) => { + test.each(cloudSecuritySolutions)( + 'should return usageRecords with correct values for cspm, kspm, and cnvm when Elasticsearch response has aggregations', + async (cloudSecuritySolution) => { // @ts-ignore mockEsClient.search.mockResolvedValueOnce({ hits: { hits: [{ _id: 'someRecord', _index: 'mockIndex' }] }, // mocking for indexHasDataInDateRange @@ -87,28 +80,32 @@ describe('getCloudSecurityUsageRecord', () => { logger, taskId, lastSuccessfulReport: new Date(), - postureType, + cloudSecuritySolution, tier, }); - expect(result).toEqual({ - id: expect.stringContaining(`${CLOUD_SECURITY_TASK_TYPE}_${postureType}_${projectId}`), - usage_timestamp: '2023-07-30T15:11:41.738Z', - creation_timestamp: expect.any(String), // Expect a valid ISO string - usage: { - type: CLOUD_SECURITY_TASK_TYPE, - sub_type: postureType, - quantity: 10, - period_seconds: expect.any(Number), - }, - source: { - id: taskId, - instance_group_id: projectId, - metadata: { - tier: 'essentials', + expect(result).toEqual([ + { + id: expect.stringContaining( + `${CLOUD_SECURITY_TASK_TYPE}_${cloudSecuritySolution}_${projectId}` + ), + usage_timestamp: '2023-07-30T15:11:41.738Z', + creation_timestamp: expect.any(String), // Expect a valid ISO string + usage: { + type: CLOUD_SECURITY_TASK_TYPE, + sub_type: cloudSecuritySolution, + quantity: 10, + period_seconds: expect.any(Number), + }, + source: { + id: taskId, + instance_group_id: projectId, + metadata: { + tier: 'essentials', + }, }, }, - }); + ]); } ); @@ -118,7 +115,7 @@ describe('getCloudSecurityUsageRecord', () => { const projectId = chance.guid(); const taskId = chance.guid(); - const postureType = CSPM_POLICY_TEMPLATE; + const cloudSecuritySolution = CSPM; const tier = 'essentials' as ProductTier; @@ -128,7 +125,7 @@ describe('getCloudSecurityUsageRecord', () => { logger, taskId, lastSuccessfulReport: new Date(), - postureType, + cloudSecuritySolution, tier, }); @@ -141,7 +138,7 @@ describe('getCloudSecurityUsageRecord', () => { const projectId = chance.guid(); const taskId = chance.guid(); - const postureType = CSPM_POLICY_TEMPLATE; + const cloudSecuritySolution = CSPM; const tier = 'essentials' as ProductTier; @@ -151,7 +148,7 @@ describe('getCloudSecurityUsageRecord', () => { logger, taskId, lastSuccessfulReport: new Date(), - postureType, + cloudSecuritySolution, tier, }); @@ -175,6 +172,100 @@ describe('should return the relevant product tier', () => { expect(tier).toBe('complete'); }); + it('should return usageRecords with correct values for cloud defend', async () => { + const cloudSecuritySolution = 'cloud_defend'; + // @ts-ignore + mockEsClient.search.mockResolvedValueOnce({ + hits: { hits: [{ _id: 'someRecord', _index: 'mockIndex' }] }, // mocking for indexHasDataInDateRange + }); + + // @ts-ignore + mockEsClient.search.mockResolvedValueOnce({ + aggregations: { + asset_count_groups: { + buckets: [ + { + key_as_string: 'true', + unique_assets: { + value: 10, + }, + min_timestamp: { + value_as_string: '2023-07-30T15:11:41.738Z', + }, + }, + { + key_as_string: 'false', + unique_assets: { + value: 5, + }, + min_timestamp: { + value_as_string: '2023-07-30T15:11:41.738Z', + }, + }, + ], + }, + }, + }); + + const projectId = chance.guid(); + const taskId = chance.guid(); + + const tier = 'essentials' as ProductTier; + + const result = await getCloudSecurityUsageRecord({ + esClient: mockEsClient, + projectId, + logger, + taskId, + lastSuccessfulReport: new Date(), + cloudSecuritySolution, + tier, + }); + + expect(result).toEqual([ + { + id: expect.stringContaining( + `${CLOUD_SECURITY_TASK_TYPE}_${cloudSecuritySolution}_${projectId}` + ), + usage_timestamp: '2023-07-30T15:11:41.738Z', + creation_timestamp: expect.any(String), // Expect a valid ISO string + usage: { + type: CLOUD_SECURITY_TASK_TYPE, + sub_type: `${cloudSecuritySolution}_block_action_enabled_true`, + quantity: 10, + period_seconds: expect.any(Number), + }, + source: { + id: taskId, + instance_group_id: projectId, + metadata: { + tier: 'essentials', + }, + }, + }, + { + id: expect.stringContaining( + `${CLOUD_SECURITY_TASK_TYPE}_${cloudSecuritySolution}_${projectId}` + ), + usage_timestamp: '2023-07-30T15:11:41.738Z', + creation_timestamp: expect.any(String), // Expect a valid ISO string + usage: { + type: CLOUD_SECURITY_TASK_TYPE, + sub_type: `${cloudSecuritySolution}_block_action_enabled_false`, + quantity: 5, + period_seconds: expect.any(Number), + }, + source: { + id: taskId, + instance_group_id: projectId, + metadata: { + tier: 'essentials', + }, + }, + }, + ]); + }); + it('should return none tier in case cloud product line is missing ', async () => { const serverlessSecurityConfig = { enabled: true, diff --git a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.ts b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.ts index 95fe58df0f174..1a9501424ba00 100644 --- a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.ts +++ b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task.ts @@ -5,88 +5,65 @@ * 2.0. */ -import { - CNVM_POLICY_TEMPLATE, - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - LATEST_FINDINGS_INDEX_PATTERN, - LATEST_VULNERABILITIES_INDEX_PATTERN, -} from '@kbn/cloud-security-posture-plugin/common/constants'; +import type { Logger } from '@kbn/core/server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { UsageRecord } from '../types'; - import { AGGREGATION_PRECISION_THRESHOLD, + ASSETS_SAMPLE_GRANULARITY, + CLOUD_DEFEND, CLOUD_SECURITY_TASK_TYPE, -} from './cloud_security_metering'; -import { cloudSecurityMetringTaskProperties } from './cloud_security_metering_task_config'; + CNVM, + CSPM, + KSPM, + METERING_CONFIGS, + THRESHOLD_MINUTES, +} from './constants'; +import type { Tier, UsageRecord } from '../types'; import type { CloudSecurityMeteringCallbackInput, - PostureType, - ResourceCountAggregation, + CloudSecuritySolutions, + AssetCountAggregation, + CloudDefendAssetCountAggregation, } from './types'; -const ASSETS_SAMPLE_GRANULARITY = '24h'; - -const queryParams = { - [CSPM_POLICY_TEMPLATE]: { - index: LATEST_FINDINGS_INDEX_PATTERN, - assets_identifier: 'resource.id', - }, - [KSPM_POLICY_TEMPLATE]: { - index: LATEST_FINDINGS_INDEX_PATTERN, - assets_identifier: 'agent.id', - }, - [CNVM_POLICY_TEMPLATE]: { - index: LATEST_VULNERABILITIES_INDEX_PATTERN, - assets_identifier: 'cloud.instance.id', - }, -}; - -export const getCloudSecurityUsageRecord = async ({ - esClient, - projectId, - logger, - taskId, - postureType, - tier, -}: CloudSecurityMeteringCallbackInput): Promise => { - try { - if (!postureType) { - logger.error('posture type is missing'); - return; - } - - if (!(await indexHasDataInDateRange(esClient, postureType))) return; - - const response = await esClient.search( - getAggQueryByPostureType(postureType) - ); +export const getUsageRecords = ( + assetCountAggregations: AssetCountAggregation[], + cloudSecuritySolution: CloudSecuritySolutions, + taskId: string, + tier: Tier, + projectId: string, + periodSeconds: number, + logger: Logger +): UsageRecord[] => { + const usageRecords = assetCountAggregations.map((assetCountAggregation) => { + const assetCount = assetCountAggregation.unique_assets.value; - if (!response.aggregations) { - return; - } - const resourceCount = response.aggregations.unique_assets.value; - if (resourceCount > AGGREGATION_PRECISION_THRESHOLD) { + if (assetCount > AGGREGATION_PRECISION_THRESHOLD) { logger.warn( - `The number of unique resources for {${postureType}} is ${resourceCount}, which is higher than the AGGREGATION_PRECISION_THRESHOLD of ${AGGREGATION_PRECISION_THRESHOLD}.` + `The number of unique resources for {${cloudSecuritySolution}} is ${assetCount}, which is higher than the AGGREGATION_PRECISION_THRESHOLD of ${AGGREGATION_PRECISION_THRESHOLD}.` ); } - const minTimestamp = response.aggregations - ? new Date(response.aggregations.min_timestamp.value_as_string).toISOString() - : new Date().toISOString(); + + const minTimestamp = new Date( + assetCountAggregation.min_timestamp.value_as_string + ).toISOString(); const creationTimestamp = new Date().toISOString(); - const usageRecord = { - id: `${CLOUD_SECURITY_TASK_TYPE}_${postureType}_${projectId}_${creationTimestamp}`, + const subType = + cloudSecuritySolution === CLOUD_DEFEND + ? `${CLOUD_DEFEND}_block_action_enabled_${assetCountAggregation.key_as_string}` + : cloudSecuritySolution; + + const usageRecord: UsageRecord = { + id: `${CLOUD_SECURITY_TASK_TYPE}_${cloudSecuritySolution}_${projectId}_${creationTimestamp}`, usage_timestamp: minTimestamp, creation_timestamp: creationTimestamp, usage: { type: CLOUD_SECURITY_TASK_TYPE, - sub_type: postureType, - quantity: resourceCount, - period_seconds: cloudSecurityMetringTaskProperties.periodSeconds, + sub_type: subType, + quantity: assetCount, + period_seconds: periodSeconds, }, source: { id: taskId, @@ -95,40 +72,85 @@ export const getCloudSecurityUsageRecord = async ({ }, }; - logger.debug(`Fetched ${postureType} metring data`); - return usageRecord; - } catch (err) { - logger.error(`Failed to fetch ${postureType} metering data ${err}`); - } + }); + return usageRecords; }; -const indexHasDataInDateRange = async (esClient: ElasticsearchClient, postureType: PostureType) => { - const response = await esClient.search({ - index: queryParams[postureType].index, - size: 1, - _source: false, - query: getSearchQueryByPostureType(postureType), - }); +export const getAggregationByCloudSecuritySolution = ( + cloudSecuritySolution: CloudSecuritySolutions +) => { + if (cloudSecuritySolution === CLOUD_DEFEND) { + return { + asset_count_groups: { + terms: { + field: 'cloud_defend.block_action_enabled', + }, + aggs: { + unique_assets: { + cardinality: { + field: METERING_CONFIGS[cloudSecuritySolution].assets_identifier, + }, + }, + min_timestamp: { + min: { + field: '@timestamp', + }, + }, + }, + }, + }; + } - return response.hits.hits.length > 0; + return { + unique_assets: { + cardinality: { + field: METERING_CONFIGS[cloudSecuritySolution].assets_identifier, + precision_threshold: AGGREGATION_PRECISION_THRESHOLD, + }, + }, + min_timestamp: { + min: { + field: '@timestamp', + }, + }, + }; }; -export const getSearchQueryByPostureType = (postureType: PostureType) => { +export const getSearchQueryByCloudSecuritySolution = ( + cloudSecuritySolution: CloudSecuritySolutions, + searchFrom: Date +) => { const mustFilters = []; - mustFilters.push({ - range: { - '@timestamp': { - gte: `now-${ASSETS_SAMPLE_GRANULARITY}`, + if (cloudSecuritySolution === CLOUD_DEFEND) { + mustFilters.push({ + range: { + '@timestamp': { + gt: searchFrom.toISOString(), + }, }, - }, - }); + }); + } + + if ( + cloudSecuritySolution === CSPM || + cloudSecuritySolution === KSPM || + cloudSecuritySolution === CNVM + ) { + mustFilters.push({ + range: { + '@timestamp': { + gte: `now-${ASSETS_SAMPLE_GRANULARITY}`, + }, + }, + }); + } - if (postureType === CSPM_POLICY_TEMPLATE || postureType === KSPM_POLICY_TEMPLATE) { + if (cloudSecuritySolution === CSPM || cloudSecuritySolution === KSPM) { mustFilters.push({ term: { - 'rule.benchmark.posture_type': postureType, + 'rule.benchmark.posture_type': cloudSecuritySolution, }, }); } @@ -140,25 +162,111 @@ export const getSearchQueryByPostureType = (postureType: PostureType) => { }; }; -export const getAggQueryByPostureType = (postureType: PostureType) => { - const query = { - index: queryParams[postureType].index, - query: getSearchQueryByPostureType(postureType), +export const getAssetAggQueryByCloudSecuritySolution = ( + cloudSecuritySolution: CloudSecuritySolutions, + searchFrom: Date +) => { + const query = getSearchQueryByCloudSecuritySolution(cloudSecuritySolution, searchFrom); + const aggs = getAggregationByCloudSecuritySolution(cloudSecuritySolution); + + return { + index: METERING_CONFIGS[cloudSecuritySolution].index, + query, size: 0, - aggs: { - unique_assets: { - cardinality: { - field: queryParams[postureType].assets_identifier, - precision_threshold: AGGREGATION_PRECISION_THRESHOLD, - }, - }, - min_timestamp: { - min: { - field: '@timestamp', - }, - }, - }, + aggs, }; +}; + +export const getAssetAggByCloudSecuritySolution = async ( + esClient: ElasticsearchClient, + cloudSecuritySolution: CloudSecuritySolutions, + searchFrom: Date +): Promise => { + const assetsAggQuery = getAssetAggQueryByCloudSecuritySolution(cloudSecuritySolution, searchFrom); + + if (cloudSecuritySolution === CLOUD_DEFEND) { + const response = await esClient.search( + assetsAggQuery + ); + + if (!response.aggregations || !response.aggregations.asset_count_groups.buckets.length) + return []; + return response.aggregations.asset_count_groups.buckets; + } + + const response = await esClient.search(assetsAggQuery); + if (!response.aggregations) return []; + + return [response.aggregations]; +}; + +const indexHasDataInDateRange = async ( + esClient: ElasticsearchClient, + cloudSecuritySolution: CloudSecuritySolutions, + searchFrom: Date +) => { + const response = await esClient.search({ + index: METERING_CONFIGS[cloudSecuritySolution].index, + size: 1, + _source: false, + query: getSearchQueryByCloudSecuritySolution(cloudSecuritySolution, searchFrom), + }); + + return response.hits.hits.length > 0; +}; + +const getSearchStartDate = (lastSuccessfulReport: Date): Date => { + const initialDate = new Date(); + const thresholdDate = new Date(initialDate.getTime() - THRESHOLD_MINUTES * 60 * 1000); + + let lastSuccessfulReport1; + + if (lastSuccessfulReport) { + lastSuccessfulReport1 = new Date(lastSuccessfulReport); + + const searchFrom = + lastSuccessfulReport && lastSuccessfulReport1 > thresholdDate + ? lastSuccessfulReport1 + : thresholdDate; + return searchFrom; + } + return thresholdDate; +}; + +export const getCloudSecurityUsageRecord = async ({ + esClient, + projectId, + taskId, + lastSuccessfulReport, + cloudSecuritySolution, + tier, + logger, +}: CloudSecurityMeteringCallbackInput): Promise => { + try { + const searchFrom = getSearchStartDate(lastSuccessfulReport); + + if (!(await indexHasDataInDateRange(esClient, cloudSecuritySolution, searchFrom))) return; + + const periodSeconds = Math.floor((new Date().getTime() - searchFrom.getTime()) / 1000); - return query; + const assetCountAggregations = await getAssetAggByCloudSecuritySolution( + esClient, + cloudSecuritySolution, + searchFrom + ); + + const usageRecords = await getUsageRecords( + assetCountAggregations, + cloudSecuritySolution, + taskId, + tier, + projectId, + periodSeconds, + logger + ); + + return usageRecords; + } catch (err) { + logger.error(`Failed to fetch ${cloudSecuritySolution} metering data ${err}`); + } }; diff --git a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task_config.ts b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task_config.ts index aa35b9e870c2c..c9b0ccd7672e3 100644 --- a/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task_config.ts +++ b/x-pack/plugins/security_solution_serverless/server/cloud_security/cloud_security_metering_task_config.ts @@ -8,7 +8,7 @@ import { cloudSecurityMetringCallback } from './cloud_security_metering'; import type { MetringTaskProperties } from '../types'; -const TASK_INTERVAL = 3600; // 1 hour +const TASK_INTERVAL = 1800; // 30 minutes export const cloudSecurityMetringTaskProperties: MetringTaskProperties = { taskType: 'cloud-security-usage-reporting-task', diff --git a/x-pack/plugins/security_solution_serverless/server/cloud_security/constants.ts b/x-pack/plugins/security_solution_serverless/server/cloud_security/constants.ts new file mode 100644 index 0000000000000..43cf1f06c47f8 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/cloud_security/constants.ts @@ -0,0 +1,45 @@ +/* + * Copyright 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 { + CNVM_POLICY_TEMPLATE, + CSPM_POLICY_TEMPLATE, + KSPM_POLICY_TEMPLATE, + LATEST_FINDINGS_INDEX_PATTERN, + LATEST_VULNERABILITIES_INDEX_PATTERN, +} from '@kbn/cloud-security-posture-plugin/common/constants'; +import { INTEGRATION_PACKAGE_NAME } from '@kbn/cloud-defend-plugin/common/constants'; + +const CLOUD_DEFEND_HEARTBEAT_INDEX = 'metrics-cloud_defend.heartbeat'; +export const CLOUD_SECURITY_TASK_TYPE = 'cloud_security'; +export const AGGREGATION_PRECISION_THRESHOLD = 40000; +export const ASSETS_SAMPLE_GRANULARITY = '24h'; +export const THRESHOLD_MINUTES = 30; + +export const CSPM = CSPM_POLICY_TEMPLATE; +export const KSPM = KSPM_POLICY_TEMPLATE; +export const CNVM = CNVM_POLICY_TEMPLATE; +export const CLOUD_DEFEND = INTEGRATION_PACKAGE_NAME; + +export const METERING_CONFIGS = { + [CSPM]: { + index: LATEST_FINDINGS_INDEX_PATTERN, + assets_identifier: 'resource.id', + }, + [KSPM]: { + index: LATEST_FINDINGS_INDEX_PATTERN, + assets_identifier: 'agent.id', + }, + [CNVM]: { + index: LATEST_VULNERABILITIES_INDEX_PATTERN, + assets_identifier: 'cloud.instance.id', + }, + [CLOUD_DEFEND]: { + index: CLOUD_DEFEND_HEARTBEAT_INDEX, + assets_identifier: 'agent.id', + }, +}; diff --git a/x-pack/plugins/security_solution_serverless/server/cloud_security/types.ts b/x-pack/plugins/security_solution_serverless/server/cloud_security/types.ts index be4dbeebf52bd..62ded11d5ad1e 100644 --- a/x-pack/plugins/security_solution_serverless/server/cloud_security/types.ts +++ b/x-pack/plugins/security_solution_serverless/server/cloud_security/types.ts @@ -5,14 +5,17 @@ * 2.0. */ -import type { - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - CNVM_POLICY_TEMPLATE, -} from '@kbn/cloud-security-posture-plugin/common/constants'; +import type { CSPM, KSPM, CNVM, CLOUD_DEFEND } from './constants'; import type { MeteringCallbackInput, Tier } from '../types'; -export interface ResourceCountAggregation { +export interface CloudDefendAssetCountAggregation { + asset_count_groups: AssetCountAggregationBucket; +} +export interface AssetCountAggregationBucket { + buckets: AssetCountAggregation[]; +} +export interface AssetCountAggregation { + key_as_string: string; min_timestamp: MinTimestamp; unique_assets: { value: number; @@ -24,14 +27,11 @@ export interface MinTimestamp { value_as_string: string; } -export type PostureType = - | typeof CSPM_POLICY_TEMPLATE - | typeof KSPM_POLICY_TEMPLATE - | typeof CNVM_POLICY_TEMPLATE; +export type CloudSecuritySolutions = typeof CSPM | typeof KSPM | typeof CNVM | typeof CLOUD_DEFEND; export interface CloudSecurityMeteringCallbackInput extends Omit { projectId: string; - postureType: PostureType; + cloudSecuritySolution: CloudSecuritySolutions; tier: Tier; } diff --git a/x-pack/plugins/security_solution_serverless/server/endpoint/constants/metering.ts b/x-pack/plugins/security_solution_serverless/server/endpoint/constants/metering.ts index bcb41d5d0d553..0733df8c79d9c 100644 --- a/x-pack/plugins/security_solution_serverless/server/endpoint/constants/metering.ts +++ b/x-pack/plugins/security_solution_serverless/server/endpoint/constants/metering.ts @@ -10,4 +10,11 @@ export const METERING_TASK = { TYPE: 'serverless-security:endpoint-usage-reporting-task', VERSION: '1.0.0', INTERVAL: '5m', + // 1 hour + SAMPLE_PERIOD_SECONDS: 3600, + THRESHOLD_MINUTES: 30, + USAGE_TYPE_PREFIX: 'security_solution_', + MISSING_PROJECT_ID: 'missing_project_id', + // 3x of interval + LOOK_BACK_LIMIT_MINUTES: 15, }; diff --git a/x-pack/plugins/security_solution_serverless/server/endpoint/services/metering_service.test.ts b/x-pack/plugins/security_solution_serverless/server/endpoint/services/metering_service.test.ts new file mode 100644 index 0000000000000..e459fdcff3bde --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/endpoint/services/metering_service.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright 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 { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { type ElasticsearchClientMock, elasticsearchServiceMock } from '@kbn/core/server/mocks'; +import type { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; +import type { EndpointHeartbeat } from '@kbn/security-solution-plugin/common/endpoint/types'; +import { ENDPOINT_HEARTBEAT_INDEX } from '@kbn/security-solution-plugin/common/endpoint/constants'; + +import { ProductLine, ProductTier } from '../../../common/product'; + +import type { ServerlessSecurityConfig } from '../../config'; +import { METERING_TASK } from '../constants/metering'; + +import { EndpointMeteringService } from './metering_service'; + +describe('EndpointMeteringService', () => { + function buildDefaultUsageRecordArgs() { + return { + logger: loggingSystemMock.createLogger(), + taskId: 'test-task-id', + cloudSetup: { + serverless: { + projectId: 'test-project-id', + }, + } as CloudSetup, + esClient: elasticsearchServiceMock.createElasticsearchClient(), + abortController: new AbortController(), + lastSuccessfulReport: new Date(), + config: { + productTypes: [ + { + product_line: ProductLine.endpoint, + product_tier: ProductTier.essentials, + }, + ], + } as ServerlessSecurityConfig, + }; + } + + function buildEsSearchResponse( + { + agentId, + timestamp, + }: { + agentId: string; + timestamp: Date; + } = { + agentId: 'test-agent-id', + timestamp: new Date(), + } + ): SearchResponse> { + return { + hits: { + hits: [ + { + _index: ENDPOINT_HEARTBEAT_INDEX, + _id: 'test-heartbeat-doc-id', + _source: { + agent: { + id: agentId, + }, + event: { + ingested: timestamp.toISOString(), + }, + }, + }, + ], + }, + } as SearchResponse>; + } + + it.each(Object.values(ProductTier))( + 'can correctly getUsageRecords for %s tier', + async (tier: ProductTier) => { + const esSearchResponse = buildEsSearchResponse(); + const heartbeatDocSrc = esSearchResponse.hits.hits[0]._source; + const agentId = heartbeatDocSrc!.agent.id; + const timestamp = new Date(heartbeatDocSrc!.event.ingested); + timestamp.setMinutes(0); + timestamp.setSeconds(0); + timestamp.setMilliseconds(0); + + const args = buildDefaultUsageRecordArgs(); + args.config.productTypes[0] = { + ...args.config.productTypes[0], + product_tier: tier, + }; + (args.esClient as ElasticsearchClientMock).search.mockResolvedValueOnce(esSearchResponse); + const endpointMeteringService = new EndpointMeteringService(); + const usageRecords = await endpointMeteringService.getUsageRecords(args); + + expect(usageRecords[0]).toEqual({ + id: `endpoint-${agentId}-${timestamp.toISOString()}`, + usage_timestamp: heartbeatDocSrc!.event.ingested, + creation_timestamp: heartbeatDocSrc!.event.ingested, + usage: { + type: `${METERING_TASK.USAGE_TYPE_PREFIX}endpoint`, + period_seconds: METERING_TASK.SAMPLE_PERIOD_SECONDS, + quantity: 1, + }, + source: { + id: args.taskId, + instance_group_id: args.cloudSetup.serverless.projectId, + metadata: { + tier, + }, + }, + }); + } + ); + + it.each([ProductLine.endpoint, ProductLine.cloud])( + 'can correctly getUsageRecords for %s product line', + async (productLine: ProductLine) => { + const esSearchResponse = buildEsSearchResponse(); + const heartbeatDocSrc = esSearchResponse.hits.hits[0]._source; + const agentId = heartbeatDocSrc!.agent.id; + const timestamp = new Date(heartbeatDocSrc!.event.ingested); + timestamp.setMinutes(0); + timestamp.setSeconds(0); + timestamp.setMilliseconds(0); + + const args = buildDefaultUsageRecordArgs(); + args.config.productTypes[0] = { + ...args.config.productTypes[0], + product_line: productLine, + }; + (args.esClient as ElasticsearchClientMock).search.mockResolvedValueOnce(esSearchResponse); + const endpointMeteringService = new EndpointMeteringService(); + const usageRecords = await endpointMeteringService.getUsageRecords(args); + const usageTypePostfix = + productLine === ProductLine.endpoint + ? productLine + : `${ProductLine.cloud}_${ProductLine.endpoint}`; + + expect(usageRecords[0]).toEqual({ + id: `endpoint-${agentId}-${timestamp.toISOString()}`, + usage_timestamp: heartbeatDocSrc!.event.ingested, + creation_timestamp: heartbeatDocSrc!.event.ingested, + usage: { + type: `${METERING_TASK.USAGE_TYPE_PREFIX}${usageTypePostfix}`, + period_seconds: METERING_TASK.SAMPLE_PERIOD_SECONDS, + quantity: 1, + }, + source: { + id: args.taskId, + instance_group_id: args.cloudSetup.serverless.projectId, + metadata: { + tier: args.config.productTypes[0].product_tier, + }, + }, + }); + } + ); +}); diff --git a/x-pack/plugins/security_solution_serverless/server/endpoint/services/metering_service.ts b/x-pack/plugins/security_solution_serverless/server/endpoint/services/metering_service.ts index 307ba5714e0f4..765ffcfeb76a4 100644 --- a/x-pack/plugins/security_solution_serverless/server/endpoint/services/metering_service.ts +++ b/x-pack/plugins/security_solution_serverless/server/endpoint/services/metering_service.ts @@ -6,7 +6,7 @@ */ import type { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import type { ElasticsearchClient } from '@kbn/core/server'; +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { ENDPOINT_HEARTBEAT_INDEX } from '@kbn/security-solution-plugin/common/endpoint/constants'; import type { EndpointHeartbeat } from '@kbn/security-solution-plugin/common/endpoint/types'; @@ -15,9 +15,7 @@ import { ProductLine, ProductTier } from '../../../common/product'; import type { UsageRecord, MeteringCallbackInput } from '../../types'; import type { ServerlessSecurityConfig } from '../../config'; -// 1 hour -const SAMPLE_PERIOD_SECONDS = 3600; -const THRESHOLD_MINUTES = 30; +import { METERING_TASK } from '../constants/metering'; export class EndpointMeteringService { private type: ProductLine.endpoint | `${ProductLine.cloud}_${ProductLine.endpoint}` | undefined; @@ -30,6 +28,7 @@ export class EndpointMeteringService { abortController, lastSuccessfulReport, config, + logger, }: MeteringCallbackInput): Promise => { this.setType(config); if (!this.type) { @@ -55,6 +54,7 @@ export class EndpointMeteringService { const { agent, event } = _source; const record = this.buildMeteringRecord({ + logger, agentId: agent.id, timestampStr: event.ingested, taskId, @@ -70,7 +70,7 @@ export class EndpointMeteringService { abortController: AbortController, since?: Date ): Promise>> { - const thresholdDate = new Date(Date.now() - THRESHOLD_MINUTES * 60 * 1000); + const thresholdDate = new Date(Date.now() - METERING_TASK.THRESHOLD_MINUTES * 60 * 1000); const searchFrom = since && since > thresholdDate ? since : thresholdDate; return esClient.search( @@ -90,11 +90,13 @@ export class EndpointMeteringService { } private buildMeteringRecord({ + logger, agentId, timestampStr, taskId, - projectId = '', + projectId, }: { + logger: Logger; agentId: string; timestampStr: string; taskId: string; @@ -105,26 +107,32 @@ export class EndpointMeteringService { timestamp.setSeconds(0); timestamp.setMilliseconds(0); - return { + const usageRecord = { // keep endpoint instead of this.type as id prefix so // we don't double count in the event of add-on changes - id: `endpoint-${agentId}-${timestamp}`, + id: `endpoint-${agentId}-${timestamp.toISOString()}`, usage_timestamp: timestampStr, creation_timestamp: timestampStr, usage: { // type postfix is used to determine the PLI to bill - type: `security_solution_${this.type}`, - period_seconds: SAMPLE_PERIOD_SECONDS, + type: `${METERING_TASK.USAGE_TYPE_PREFIX}${this.type}`, + period_seconds: METERING_TASK.SAMPLE_PERIOD_SECONDS, quantity: 1, }, source: { id: taskId, - instance_group_id: projectId, + instance_group_id: projectId || METERING_TASK.MISSING_PROJECT_ID, metadata: { tier: this.tier, }, }, }; + + if (!projectId) { + logger.error(`project id missing for record: ${JSON.stringify(usageRecord)}`); + } + + return usageRecord; } private setType(config: ServerlessSecurityConfig) { diff --git a/x-pack/plugins/security_solution_serverless/server/plugin.ts b/x-pack/plugins/security_solution_serverless/server/plugin.ts index f4937ea4f0b32..e960d6743942c 100644 --- a/x-pack/plugins/security_solution_serverless/server/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/server/plugin.ts @@ -13,6 +13,7 @@ import type { Logger, } from '@kbn/core/server'; +import { SECURITY_PROJECT_SETTINGS } from '@kbn/serverless-security-settings'; import { getProductAppFeatures } from '../common/pli/pli_features'; import type { ServerlessSecurityConfig } from './config'; @@ -41,7 +42,7 @@ export class SecuritySolutionServerlessPlugin > { private config: ServerlessSecurityConfig; - private cspmUsageReportingTask: SecurityUsageReportingTask | undefined; + private cloudSecurityUsageReportingTask: SecurityUsageReportingTask | undefined; private endpointUsageReportingTask: SecurityUsageReportingTask | undefined; private readonly logger: Logger; @@ -64,14 +65,12 @@ export class SecuritySolutionServerlessPlugin pluginsSetup.securitySolution.setAppFeaturesConfigurator(appFeaturesConfigurator); } - pluginsSetup.ml.setFeaturesEnabled({ ad: true, dfa: true, nlp: false }); - - this.cspmUsageReportingTask = new SecurityUsageReportingTask({ + this.cloudSecurityUsageReportingTask = new SecurityUsageReportingTask({ core: coreSetup, logFactory: this.initializerContext.logger, config: this.config, taskManager: pluginsSetup.taskManager, - cloudSetup: pluginsSetup.cloudSetup, + cloudSetup: pluginsSetup.cloud, taskType: cloudSecurityMetringTaskProperties.taskType, taskTitle: cloudSecurityMetringTaskProperties.taskTitle, version: cloudSecurityMetringTaskProperties.version, @@ -87,8 +86,14 @@ export class SecuritySolutionServerlessPlugin version: ENDPOINT_METERING_TASK.VERSION, meteringCallback: endpointMeteringService.getUsageRecords, taskManager: pluginsSetup.taskManager, - cloudSetup: pluginsSetup.cloudSetup, + cloudSetup: pluginsSetup.cloud, + options: { + lookBackLimitMinutes: ENDPOINT_METERING_TASK.LOOK_BACK_LIMIT_MINUTES, + }, }); + + pluginsSetup.serverless.setupProjectSettings(SECURITY_PROJECT_SETTINGS); + return {}; } @@ -96,7 +101,7 @@ export class SecuritySolutionServerlessPlugin const internalESClient = _coreStart.elasticsearch.client.asInternalUser; const internalSOClient = _coreStart.savedObjects.createInternalRepository(); - this.cspmUsageReportingTask?.start({ + this.cloudSecurityUsageReportingTask?.start({ taskManager: pluginsSetup.taskManager, interval: cloudSecurityMetringTaskProperties.interval, }); diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts new file mode 100644 index 0000000000000..abd36d9ccb8da --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts @@ -0,0 +1,256 @@ +/* + * Copyright 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 { assign } from 'lodash'; + +import type { CoreSetup, ElasticsearchClient } from '@kbn/core/server'; +import type { + TaskManagerSetupContract, + ConcreteTaskInstance, +} from '@kbn/task-manager-plugin/server'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; +import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { coreMock } from '@kbn/core/server/mocks'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; + +import { ProductLine, ProductTier } from '../../common/product'; + +import { usageReportingService } from '../common/services'; +import type { ServerlessSecurityConfig } from '../config'; +import type { SecurityUsageReportingTaskSetupContract, UsageRecord } from '../types'; + +import { SecurityUsageReportingTask } from './usage_reporting_task'; + +describe('SecurityUsageReportingTask', () => { + const TITLE = 'test-task-title'; + const TYPE = 'test-task-type'; + const VERSION = 'test-task-version'; + const TASK_ID = `${TYPE}:${VERSION}`; + const USAGE_TYPE = 'test-usage-type'; + const PROJECT_ID = 'test-project-id'; + + const { createSetup: coreSetupMock } = coreMock; + const { createSetup: tmSetupMock, createStart: tmStartMock } = taskManagerMock; + + let mockTask: SecurityUsageReportingTask; + let mockEsClient: jest.Mocked; + let mockCore: CoreSetup; + let mockTaskManagerSetup: jest.Mocked; + let reportUsageSpy: jest.SpyInstance; + let meteringCallbackMock: jest.Mock; + let taskArgs: SecurityUsageReportingTaskSetupContract; + let usageRecord: UsageRecord; + + function buildMockTaskInstance(overrides?: Partial): ConcreteTaskInstance { + const timestamp = new Date(new Date().setMinutes(-15)); + return assign( + { + id: `${TYPE}:${VERSION}`, + runAt: timestamp, + attempts: 0, + ownerId: '', + status: TaskStatus.Running, + startedAt: timestamp, + scheduledAt: timestamp, + retryAt: timestamp, + params: {}, + state: { + lastSuccessfulReport: timestamp, + }, + taskType: TYPE, + }, + overrides + ); + } + + function buildUsageRecord() { + const ts = new Date().toISOString(); + return { + id: `endpoint-agentId-${ts}`, + usage_timestamp: ts, + creation_timestamp: ts, + usage: { + type: USAGE_TYPE, + period_seconds: 3600, + quantity: 1, + }, + source: { + id: TASK_ID, + instance_group_id: PROJECT_ID, + metadata: { + tier: ProductTier.complete, + }, + }, + }; + } + + function buildTaskArgs( + overrides?: Partial + ): SecurityUsageReportingTaskSetupContract { + return assign( + { + core: mockCore, + logFactory: loggingSystemMock.create(), + config: { + productTypes: [ + { + product_line: ProductLine.security, + product_tier: ProductTier.complete, + }, + ], + } as ServerlessSecurityConfig, + taskManager: mockTaskManagerSetup, + cloudSetup: { + serverless: { + projectId: PROJECT_ID, + }, + } as CloudSetup, + taskType: TYPE, + taskTitle: TITLE, + version: VERSION, + meteringCallback: meteringCallbackMock, + }, + overrides + ); + } + + async function setupMocks() { + mockCore = coreSetupMock(); + mockEsClient = (await mockCore.getStartServices())[0].elasticsearch.client + .asInternalUser as jest.Mocked; + mockTaskManagerSetup = tmSetupMock(); + usageRecord = buildUsageRecord(); + reportUsageSpy = jest.spyOn(usageReportingService, 'reportUsage'); + meteringCallbackMock = jest.fn().mockResolvedValueOnce([usageRecord]); + taskArgs = buildTaskArgs(); + mockTask = new SecurityUsageReportingTask(taskArgs); + } + + beforeEach(async () => { + await setupMocks(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('task lifecycle', () => { + it('should create task', () => { + expect(mockTask).toBeInstanceOf(SecurityUsageReportingTask); + }); + + it('should register task', () => { + expect(mockTaskManagerSetup.registerTaskDefinitions).toHaveBeenCalled(); + }); + + it('should schedule task', async () => { + const mockTaskManagerStart = tmStartMock(); + await mockTask.start({ taskManager: mockTaskManagerStart, interval: '5m' }); + expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled(); + }); + }); + + describe('task logic', () => { + async function runTask(taskInstance = buildMockTaskInstance(), callNum: number = 0) { + const mockTaskManagerStart = tmStartMock(); + await mockTask.start({ taskManager: mockTaskManagerStart, interval: '5m' }); + const createTaskRunner = + mockTaskManagerSetup.registerTaskDefinitions.mock.calls[callNum][0][TYPE].createTaskRunner; + const taskRunner = createTaskRunner({ taskInstance }); + return taskRunner.run(); + } + + it('should call metering callback', async () => { + const task = await runTask(); + expect(meteringCallbackMock).toHaveBeenCalledWith( + expect.objectContaining({ + esClient: mockEsClient, + cloudSetup: taskArgs.cloudSetup, + taskId: TASK_ID, + config: taskArgs.config, + lastSuccessfulReport: task?.state.lastSuccessfulReport, + }) + ); + }); + + it('should report metering records', async () => { + await runTask(); + expect(reportUsageSpy).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + creation_timestamp: usageRecord.creation_timestamp, + id: usageRecord.id, + source: { + id: TASK_ID, + instance_group_id: PROJECT_ID, + metadata: { tier: ProductTier.complete }, + }, + usage: { period_seconds: 3600, quantity: 1, type: USAGE_TYPE }, + usage_timestamp: usageRecord.usage_timestamp, + }), + ]) + ); + }); + + describe('lastSuccessfulReport', () => { + it('should set lastSuccessfulReport correctly if report success', async () => { + reportUsageSpy.mockResolvedValueOnce({ status: 201 }); + const taskInstance = buildMockTaskInstance(); + const task = await runTask(taskInstance); + const newLastSuccessfulReport = task?.state.lastSuccessfulReport; + expect(newLastSuccessfulReport).toEqual(expect.any(Date)); + expect(newLastSuccessfulReport).not.toEqual(taskInstance.state.lastSuccessfulReport); + }); + + describe('and response is NOT 201', () => { + beforeEach(() => { + reportUsageSpy.mockResolvedValueOnce({ status: 500 }); + }); + + it('should set lastSuccessfulReport correctly', async () => { + const lastSuccessfulReport = new Date(new Date().setMinutes(-15)); + const taskInstance = buildMockTaskInstance({ state: { lastSuccessfulReport } }); + const task = await runTask(taskInstance); + const newLastSuccessfulReport = task?.state.lastSuccessfulReport as Date; + + expect(newLastSuccessfulReport).toEqual(taskInstance.state.lastSuccessfulReport); + }); + + describe('and lookBackLimitMinutes is set', () => { + it('should limit lastSuccessfulReport if past threshold', async () => { + taskArgs = buildTaskArgs({ options: { lookBackLimitMinutes: 5 } }); + mockTask = new SecurityUsageReportingTask(taskArgs); + + const lastSuccessfulReport = new Date(new Date().setMinutes(-30)); + const taskInstance = buildMockTaskInstance({ state: { lastSuccessfulReport } }); + const task = await runTask(taskInstance, 1); + const newLastSuccessfulReport = task?.state.lastSuccessfulReport as Date; + + // should be ~5 minutes so asserting between 4-6 minutes ago + const sixMinutesAgo = new Date().setMinutes(-6); + expect(newLastSuccessfulReport.getTime()).toBeGreaterThanOrEqual(sixMinutesAgo); + const fourMinutesAgo = new Date().setMinutes(-4); + expect(newLastSuccessfulReport.getTime()).toBeLessThanOrEqual(fourMinutesAgo); + }); + + it('should NOT limit lastSuccessfulReport if NOT past threshold', async () => { + taskArgs = buildTaskArgs({ options: { lookBackLimitMinutes: 30 } }); + mockTask = new SecurityUsageReportingTask(taskArgs); + + const lastSuccessfulReport = new Date(new Date().setMinutes(-15)); + const taskInstance = buildMockTaskInstance({ state: { lastSuccessfulReport } }); + const task = await runTask(taskInstance, 1); + const newLastSuccessfulReport = task?.state.lastSuccessfulReport as Date; + + expect(newLastSuccessfulReport).toEqual(taskInstance.state.lastSuccessfulReport); + }); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts index 673bda309aec7..f2947bef9602f 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts @@ -44,6 +44,7 @@ export class SecurityUsageReportingTask { taskTitle, version, meteringCallback, + options, } = setupContract; this.cloudSetup = cloudSetup; @@ -60,7 +61,12 @@ export class SecurityUsageReportingTask { createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => { return { run: async () => { - return this.runTask(taskInstance, core, meteringCallback); + return this.runTask( + taskInstance, + core, + meteringCallback, + options?.lookBackLimitMinutes + ); }, cancel: async () => {}, }; @@ -101,7 +107,8 @@ export class SecurityUsageReportingTask { private runTask = async ( taskInstance: ConcreteTaskInstance, core: CoreSetup, - meteringCallback: MeteringCallback + meteringCallback: MeteringCallback, + lookBackLimitMinutes?: number ) => { // if task was not `.start()`'d yet, then exit if (!this.wasStarted) { @@ -120,6 +127,9 @@ export class SecurityUsageReportingTask { const lastSuccessfulReport = taskInstance.state.lastSuccessfulReport; let usageRecords: UsageRecord[] = []; + // save usage record query time so we can use it to know where + // the next query range should start + const meteringCallbackTime = new Date(); try { usageRecords = await meteringCallback({ esClient, @@ -131,7 +141,7 @@ export class SecurityUsageReportingTask { config: this.config, }); } catch (err) { - this.logger.error(`failed to retrieve usage records: ${JSON.stringify(err)}`); + this.logger.error(`failed to retrieve usage records: ${err}`); return; } @@ -153,17 +163,46 @@ export class SecurityUsageReportingTask { `usage records report was sent successfully: ${usageReportResponse.status}, ${usageReportResponse.statusText}` ); } catch (err) { - this.logger.error(`Failed to send usage records report ${JSON.stringify(err)} `); + this.logger.error(`Failed to send usage records report ${err} `); } } const state = { lastSuccessfulReport: - usageReportResponse?.status === 201 ? new Date() : taskInstance.state.lastSuccessfulReport, + usageReportResponse?.status === 201 + ? meteringCallbackTime + : this.getFailedLastSuccessfulReportTime( + meteringCallbackTime, + taskInstance.state.lastSuccessfulReport, + lookBackLimitMinutes + ), }; return { state }; }; + private getFailedLastSuccessfulReportTime( + meteringCallbackTime: Date, + lastSuccessfulReport: Date, + lookBackLimitMinutes?: number + ): Date { + const nextLastSuccessfulReport = lastSuccessfulReport || meteringCallbackTime; + + if (!lookBackLimitMinutes) { + return nextLastSuccessfulReport; + } + + const lookBackLimitTime = new Date(meteringCallbackTime.setMinutes(-lookBackLimitMinutes)); + + if (nextLastSuccessfulReport > lookBackLimitTime) { + return nextLastSuccessfulReport; + } + + this.logger.error( + `lastSuccessfulReport time of ${nextLastSuccessfulReport.toISOString()} is past the limit of ${lookBackLimitMinutes} minutes, adjusting lastSuccessfulReport to ${lookBackLimitTime.toISOString()}` + ); + return lookBackLimitTime; + } + private get taskId() { return `${this.taskType}:${this.version}`; } diff --git a/x-pack/plugins/security_solution_serverless/server/types.ts b/x-pack/plugins/security_solution_serverless/server/types.ts index 63fb4d0685738..6f9c87dd92b18 100644 --- a/x-pack/plugins/security_solution_serverless/server/types.ts +++ b/x-pack/plugins/security_solution_serverless/server/types.ts @@ -17,9 +17,9 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { SecuritySolutionEssPluginSetup } from '@kbn/security-solution-ess/server'; -import type { MlPluginSetup } from '@kbn/ml-plugin/server'; import type { FleetStartContract } from '@kbn/fleet-plugin/server'; +import type { ServerlessPluginSetup } from '@kbn/serverless/server'; import type { ProductTier } from '../common/product'; import type { ServerlessSecurityConfig } from './config'; @@ -33,10 +33,10 @@ export interface SecuritySolutionServerlessPluginSetupDeps { security: SecurityPluginSetup; securitySolution: SecuritySolutionPluginSetup; securitySolutionEss: SecuritySolutionEssPluginSetup; + serverless: ServerlessPluginSetup; features: PluginSetupContract; - ml: MlPluginSetup; taskManager: TaskManagerSetupContract; - cloudSetup: CloudSetup; + cloud: CloudSetup; } export interface SecuritySolutionServerlessPluginStartDeps { @@ -76,6 +76,10 @@ export interface UsageSourceMetadata { export type Tier = ProductTier | 'none'; +export interface SecurityUsageReportingTaskSetupContractOptions { + lookBackLimitMinutes?: number; +} + export interface SecurityUsageReportingTaskSetupContract { core: CoreSetup; logFactory: LoggerFactory; @@ -86,6 +90,7 @@ export interface SecurityUsageReportingTaskSetupContract { taskTitle: string; version: string; meteringCallback: MeteringCallback; + options?: SecurityUsageReportingTaskSetupContractOptions; } export interface SecurityUsageReportingTaskStartContract { diff --git a/x-pack/plugins/security_solution_serverless/tsconfig.json b/x-pack/plugins/security_solution_serverless/tsconfig.json index 0f517caa077aa..2aa2b979180f5 100644 --- a/x-pack/plugins/security_solution_serverless/tsconfig.json +++ b/x-pack/plugins/security_solution_serverless/tsconfig.json @@ -32,7 +32,6 @@ "@kbn/i18n", "@kbn/shared-ux-page-kibana-template", "@kbn/features-plugin", - "@kbn/ml-plugin", "@kbn/kibana-utils-plugin", "@kbn/task-manager-plugin", "@kbn/cloud-plugin", @@ -40,7 +39,10 @@ "@kbn/security-solution-features", "@kbn/cases-plugin", "@kbn/fleet-plugin", + "@kbn/serverless-security-settings", "@kbn/core-elasticsearch-server", - "@kbn/usage-collection-plugin" + "@kbn/usage-collection-plugin", + "@kbn/cloud-defend-plugin", + "@kbn/core-logging-server-mocks" ] } diff --git a/x-pack/plugins/serverless/jest.config.js b/x-pack/plugins/serverless/jest.config.js new file mode 100644 index 0000000000000..1b7860b8f3d78 --- /dev/null +++ b/x-pack/plugins/serverless/jest.config.js @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/serverless'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/serverless', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/x-pack/plugins/serverless/{common,public,server}/**/*.{ts,tsx}'], +}; diff --git a/x-pack/plugins/serverless/kibana.jsonc b/x-pack/plugins/serverless/kibana.jsonc index 35b21a5fc39b5..d8993d5ac7c72 100644 --- a/x-pack/plugins/serverless/kibana.jsonc +++ b/x-pack/plugins/serverless/kibana.jsonc @@ -14,7 +14,6 @@ ], "requiredPlugins": [ "kibanaReact", - "management", "cloud" ], "optionalPlugins": [], diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx index d49447e4d36dd..6333d9a5f58a0 100644 --- a/x-pack/plugins/serverless/public/plugin.tsx +++ b/x-pack/plugins/serverless/public/plugin.tsx @@ -49,7 +49,6 @@ export class ServerlessPlugin dependencies: ServerlessPluginStartDependencies ): ServerlessPluginStart { const { developer } = this.config; - const { management } = dependencies; if (developer && developer.projectSwitcher && developer.projectSwitcher.enabled) { const { currentType } = developer.projectSwitcher; @@ -61,13 +60,15 @@ export class ServerlessPlugin } core.chrome.setChromeStyle('project'); - management.setIsSidebarEnabled(false); // Casting the "chrome.projects" service to an "internal" type: this is intentional to obscure the property from Typescript. const { project } = core.chrome as InternalChromeStart; if (dependencies.cloud.projectsUrl) { project.setProjectsUrl(dependencies.cloud.projectsUrl); } + if (dependencies.cloud.serverless.projectName) { + project.setProjectName(dependencies.cloud.serverless.projectName); + } return { setSideNavComponent: (sideNavigationComponent) => diff --git a/x-pack/plugins/serverless/public/types.ts b/x-pack/plugins/serverless/public/types.ts index 1de2e2fd75e1a..84fc2565905d6 100644 --- a/x-pack/plugins/serverless/public/types.ts +++ b/x-pack/plugins/serverless/public/types.ts @@ -12,7 +12,6 @@ import type { SideNavComponent, ChromeProjectNavigationNode, } from '@kbn/core-chrome-browser'; -import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { Observable } from 'rxjs'; @@ -31,11 +30,9 @@ export interface ServerlessPluginStart { } export interface ServerlessPluginSetupDependencies { - management: ManagementSetup; cloud: CloudSetup; } export interface ServerlessPluginStartDependencies { - management: ManagementStart; cloud: CloudStart; } diff --git a/x-pack/plugins/serverless/server/mocks.ts b/x-pack/plugins/serverless/server/mocks.ts new file mode 100644 index 0000000000000..28b336d4b6c58 --- /dev/null +++ b/x-pack/plugins/serverless/server/mocks.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +function createSetupContract() { + return { + setupProjectSettings: jest.fn(), + }; +} + +function createStartContract() { + return {}; +} + +export const serverlessPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/x-pack/plugins/serverless/server/plugin.test.ts b/x-pack/plugins/serverless/server/plugin.test.ts new file mode 100644 index 0000000000000..d002325368be6 --- /dev/null +++ b/x-pack/plugins/serverless/server/plugin.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock } from '@kbn/core/server/mocks'; +import { config } from './config'; +import { ServerlessPlugin } from './plugin'; + +describe('Serverless Plugin', () => { + let plugin: ServerlessPlugin; + let mockCoreSetup: ReturnType; + let mockCoreStart: ReturnType; + beforeEach(() => { + plugin = new ServerlessPlugin( + coreMock.createPluginInitializerContext( + config.schema.validate({ + enabled: true, + }) + ) + ); + + mockCoreSetup = coreMock.createSetup({ + pluginStartContract: {}, + }); + mockCoreStart = coreMock.createStart(); + }); + + describe('start()', () => { + it('throws if project settings are not set up', () => { + plugin.setup(mockCoreSetup); + expect(() => plugin.start(mockCoreStart)).toThrowError( + "The uiSettings allowlist for serverless hasn't been set up. Make sure to set up your serverless project settings with setupProjectSettings()" + ); + }); + }); +}); diff --git a/x-pack/plugins/serverless/server/plugin.ts b/x-pack/plugins/serverless/server/plugin.ts index a2b1121d0c562..eefe74e6de903 100644 --- a/x-pack/plugins/serverless/server/plugin.ts +++ b/x-pack/plugins/serverless/server/plugin.ts @@ -13,6 +13,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { getConfigDirectory } from '@kbn/utils'; import { ProjectType } from '@kbn/serverless-types'; +import { ALL_COMMON_SETTINGS } from '@kbn/serverless-common-settings'; import { ServerlessPluginSetup, ServerlessPluginStart } from './types'; import { ServerlessConfig } from './config'; import { API_SWITCH_PROJECT } from '../common'; @@ -35,6 +36,13 @@ const typeToIdMap: Record = { export class ServerlessPlugin implements Plugin { private readonly config: ServerlessConfig; + private projectSettingsAdded: boolean = false; + + private setupProjectSettings(core: CoreSetup, keys: string[]): void { + const settings = [...ALL_COMMON_SETTINGS].concat(keys); + core.uiSettings.setAllowlist(settings); + this.projectSettingsAdded = true; + } constructor(private readonly context: PluginInitializerContext) { this.config = this.context.config.get(); @@ -75,10 +83,17 @@ export class ServerlessPlugin implements Plugin this.setupProjectSettings(core, keys), + }; } public start(_core: CoreStart) { + if (!this.projectSettingsAdded) { + throw new Error( + "The uiSettings allowlist for serverless hasn't been set up. Make sure to set up your serverless project settings with setupProjectSettings()" + ); + } return {}; } diff --git a/x-pack/plugins/serverless/server/types.ts b/x-pack/plugins/serverless/server/types.ts index 92a804b34a948..71c06ce1b96b4 100644 --- a/x-pack/plugins/serverless/server/types.ts +++ b/x-pack/plugins/serverless/server/types.ts @@ -5,8 +5,9 @@ * 2.0. */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ServerlessPluginSetup {} +export interface ServerlessPluginSetup { + setupProjectSettings(keys: string[]): void; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessPluginStart {} diff --git a/x-pack/plugins/serverless/tsconfig.json b/x-pack/plugins/serverless/tsconfig.json index 88f7b5af1636c..e8224182afae9 100644 --- a/x-pack/plugins/serverless/tsconfig.json +++ b/x-pack/plugins/serverless/tsconfig.json @@ -17,7 +17,6 @@ "@kbn/config-schema", "@kbn/core", "@kbn/kibana-react-plugin", - "@kbn/management-plugin", "@kbn/serverless-project-switcher", "@kbn/serverless-types", "@kbn/utils", @@ -25,5 +24,6 @@ "@kbn/core-chrome-browser-internal", "@kbn/i18n-react", "@kbn/cloud-plugin", + "@kbn/serverless-common-settings", ] } diff --git a/x-pack/plugins/serverless_observability/kibana.jsonc b/x-pack/plugins/serverless_observability/kibana.jsonc index d4c0169ed46ac..0c68668e473ea 100644 --- a/x-pack/plugins/serverless_observability/kibana.jsonc +++ b/x-pack/plugins/serverless_observability/kibana.jsonc @@ -22,7 +22,6 @@ "observabilityShared", "kibanaReact", "management", - "ml", "cloud" ], "optionalPlugins": [], diff --git a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx index 3808a64e3baff..55cf3960ab0bd 100644 --- a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx +++ b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx @@ -66,14 +66,15 @@ const navigationTree: NavigationTreeDefinition = { defaultMessage: 'Log rate analysis', }), link: 'ml:logRateAnalysis', - icon: 'beaker', getIsActive: ({ pathNameSerialized, prepend }) => { return pathNameSerialized.includes(prepend('/app/ml/aiops/log_rate_analysis')); }, }, { + title: i18n.translate('xpack.serverlessObservability.ml.changePointDetection', { + defaultMessage: 'Change point detection', + }), link: 'ml:changePointDetections', - icon: 'beaker', getIsActive: ({ pathNameSerialized, prepend }) => { return pathNameSerialized.includes( prepend('/app/ml/aiops/change_point_detection') diff --git a/x-pack/plugins/serverless_observability/public/plugin.ts b/x-pack/plugins/serverless_observability/public/plugin.ts index e208fd5f8cadd..bbc2ac7e0435b 100644 --- a/x-pack/plugins/serverless_observability/public/plugin.ts +++ b/x-pack/plugins/serverless_observability/public/plugin.ts @@ -45,6 +45,7 @@ export class ServerlessObservabilityPlugin observabilityShared.setIsSidebarEnabled(false); serverless.setProjectHome('/app/observability/landing'); serverless.setSideNavComponent(getObservabilitySideNavComponent(core, { serverless, cloud })); + management.setIsSidebarEnabled(false); management.setupCardsNavigation({ enabled: true, hideLinksTo: [appIds.RULES], diff --git a/x-pack/plugins/serverless_observability/server/plugin.ts b/x-pack/plugins/serverless_observability/server/plugin.ts index ae7bcd8baa064..973bfde638e8c 100644 --- a/x-pack/plugins/serverless_observability/server/plugin.ts +++ b/x-pack/plugins/serverless_observability/server/plugin.ts @@ -7,6 +7,7 @@ import type { PluginInitializerContext, Plugin, CoreSetup } from '@kbn/core/server'; +import { OBSERVABILITY_PROJECT_SETTINGS } from '@kbn/serverless-observability-settings'; import type { ServerlessObservabilityPluginSetup, ServerlessObservabilityPluginStart, @@ -26,7 +27,7 @@ export class ServerlessObservabilityPlugin constructor(_initializerContext: PluginInitializerContext) {} public setup(_coreSetup: CoreSetup, pluginsSetup: SetupDependencies) { - pluginsSetup.ml.setFeaturesEnabled({ ad: true, dfa: false, nlp: false }); + pluginsSetup.serverless.setupProjectSettings(OBSERVABILITY_PROJECT_SETTINGS); return {}; } diff --git a/x-pack/plugins/serverless_observability/server/types.ts b/x-pack/plugins/serverless_observability/server/types.ts index 5ebad2274b9a5..347415d17c454 100644 --- a/x-pack/plugins/serverless_observability/server/types.ts +++ b/x-pack/plugins/serverless_observability/server/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { MlPluginSetup } from '@kbn/ml-plugin/server'; +import { ServerlessPluginSetup } from '@kbn/serverless/server'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessObservabilityPluginSetup {} @@ -16,5 +16,5 @@ export interface ServerlessObservabilityPluginStart {} export interface StartDependencies {} export interface SetupDependencies { - ml: MlPluginSetup; + serverless: ServerlessPluginSetup; } diff --git a/x-pack/plugins/serverless_observability/tsconfig.json b/x-pack/plugins/serverless_observability/tsconfig.json index dec3814883e0e..c0894de19697e 100644 --- a/x-pack/plugins/serverless_observability/tsconfig.json +++ b/x-pack/plugins/serverless_observability/tsconfig.json @@ -22,12 +22,12 @@ "@kbn/observability-shared-plugin", "@kbn/kibana-react-plugin", "@kbn/shared-ux-chrome-navigation", - "@kbn/ml-plugin", "@kbn/i18n", "@kbn/management-cards-navigation", "@kbn/cloud-plugin", "@kbn/data-plugin", "@kbn/observability-plugin", "@kbn/io-ts-utils", + "@kbn/serverless-observability-settings", ] } diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc index d056148e3aa79..2ae8f0dbff987 100644 --- a/x-pack/plugins/serverless_search/kibana.jsonc +++ b/x-pack/plugins/serverless_search/kibana.jsonc @@ -7,18 +7,18 @@ "id": "serverlessSearch", "server": true, "browser": true, - "configPath": ["xpack", "serverless", "search"], + "configPath": [ + "xpack", + "serverless", + "search" + ], "requiredPlugins": [ "cloud", "console", "dashboard", "devTools", "discover", - "enterpriseSearch", - "grokdebugger", "management", - "ml", - "painlessLab", "searchprofiler", "security", "serverless", @@ -26,6 +26,8 @@ "visualizations" ], "optionalPlugins": [], - "requiredBundles": ["kibanaReact"] + "requiredBundles": [ + "kibanaReact" + ] } } diff --git a/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.scss b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.scss new file mode 100644 index 0000000000000..b74c06cd58030 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.scss @@ -0,0 +1,3 @@ +.apiKeySuccessPanel { + background-color: transparentize($euiColorSuccess, .9); +} diff --git a/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx index 07d93fb8bcfe2..671f1c1ff8cf1 100644 --- a/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx @@ -14,10 +14,9 @@ import { EuiIcon, EuiPanel, EuiSpacer, - EuiSplitPanel, EuiStep, EuiText, - EuiThemeProvider, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -29,9 +28,10 @@ import { useKibanaServices } from '../../hooks/use_kibana'; import { MANAGEMENT_API_KEYS } from '../../routes'; import { CreateApiKeyFlyout } from './create_api_key_flyout'; import { CreateApiKeyResponse } from './types'; +import './api_key.scss'; export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: string) => void }) => { - const { cloud, http, userProfile } = useKibanaServices(); + const { http, user } = useKibanaServices(); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const { data } = useQuery({ queryKey: ['apiKey'], @@ -49,11 +49,11 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri setIsFlyoutOpen(false)} setApiKey={saveApiKey} - username={userProfile.user.full_name || userProfile.user.username} + username={user?.full_name || user?.username || ''} /> )} {apiKey ? ( - + ) : ( - - - {i18n.translate('xpack.serverlessSearch.apiKey.stepOneDescription', { - defaultMessage: 'Unique identifier for authentication and authorization. ', + +

    + {i18n.translate('xpack.serverlessSearch.apiKey.panel.title', { + defaultMessage: 'Prepare an API Key', })} - - - - - - - - setIsFlyoutOpen(true)} - > - - {i18n.translate('xpack.serverlessSearch.apiKey.newButtonLabel', { - defaultMessage: 'New', - })} - - - - - - - - {i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', { - defaultMessage: 'Manage', +

    + + + {i18n.translate('xpack.serverlessSearch.apiKey.panel.description', { + defaultMessage: + 'An API key is a private, unique identifier for authentication and authorization.', + })} + + + + + + + + setIsFlyoutOpen(true)} + > + + {i18n.translate('xpack.serverlessSearch.apiKey.newButtonLabel', { + defaultMessage: 'New', })} - - + + + + + + + + {i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', { + defaultMessage: 'Manage', + })} + + + + + + + {!!data?.apiKeys && ( + + + + + + + 0 ? 'success' : 'warning'}> + {data.apiKeys.length} + + ), + }} + /> + - - - {!!data?.apiKeys && ( - - - - - - - 0 ? 'success' : 'warning'}> - {data.apiKeys.length} - - ), - }} - /> - - - - )} - - - + )} + + )} - - - - - - {i18n.translate('xpack.serverlessSearch.apiKey.stepTwoDescription', { - defaultMessage: 'Unique identifier for specific project. ', - })} - - - - - - - {cloud.cloudId} - - - - ); }; diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx index 97a3b5887389f..c46a43879622d 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx @@ -17,13 +17,12 @@ import { EuiTitle, EuiToolTip, } from '@elastic/eui'; -import { Connector } from '@kbn/enterprise-search-plugin/common/types/connectors'; +import { Connector, ConnectorServerSideDefinition } from '@kbn/search-connectors'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useQuery } from '@tanstack/react-query'; import React from 'react'; -import { ConnectorServerSideDefinition } from '@kbn/enterprise-search-plugin/common/connectors/connectors'; import { LEARN_MORE_LABEL } from '../../../common/i18n_string'; import { PLUGIN_ID } from '../../../common'; import { useKibanaServices } from '../hooks/use_kibana'; @@ -31,8 +30,8 @@ import { useKibanaServices } from '../hooks/use_kibana'; export const ConnectorsOverview = () => { const { http } = useKibanaServices(); - const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/`); - const connectorsPath = assetBasePath + 'connectors.svg'; + const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`); + const connectorsPath = assetBasePath + '/connectors.svg'; const { data } = useQuery({ queryKey: ['fetchConnectors'], diff --git a/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx b/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx index 0d1cf8efa67c8..28748db40cedb 100644 --- a/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx @@ -10,7 +10,25 @@ import { ElasticsearchOverview as Overview } from './overview'; describe('', () => { beforeEach(() => { - core.http.fetch.mockResolvedValueOnce({ apiKeys: [] }); + core.http.fetch.mockImplementation((url) => { + let fetchedUrl: string; + if (typeof url === 'string') { + fetchedUrl = url; + } + + return new Promise((resolve, reject) => { + switch (fetchedUrl) { + case '/internal/serverless_search/api_keys': + resolve({ apiKeys: [] }); + return; + case '/internal/serverless_search/connectors': + resolve({}); + return; + default: + return reject(`unknown path requested ${fetchedUrl}`); + } + }); + }); }); test('renders without throwing an error', () => { @@ -33,7 +51,11 @@ describe('', () => { }); test('api key', () => { const { getByRole } = render(); - expect(getByRole('heading', { name: 'Store your API key and Cloud ID' })).toBeDefined(); + expect(getByRole('heading', { level: 2, name: 'Prepare an API Key' })).toBeDefined(); + }); + test('cloud id', () => { + const { getByRole } = render(); + expect(getByRole('heading', { name: 'Copy your connection details' })).toBeDefined(); }); test('configure client', () => { const { getByRole } = render(); diff --git a/x-pack/plugins/serverless_search/public/application/components/overview.tsx b/x-pack/plugins/serverless_search/public/application/components/overview.tsx index 0fb599c770044..fb6205ab42ab2 100644 --- a/x-pack/plugins/serverless_search/public/application/components/overview.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/overview.tsx @@ -9,11 +9,15 @@ import { EuiAvatar, EuiButtonEmpty, EuiCard, + EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiPageTemplate, EuiPanel, + EuiSpacer, EuiText, + EuiThemeProvider, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { @@ -34,7 +38,7 @@ import type { LanguageDefinitionSnippetArguments, } from '@kbn/search-api-panels'; import { useQuery } from '@tanstack/react-query'; -import { Connector } from '@kbn/enterprise-search-plugin/common/types/connectors'; +import { Connector } from '@kbn/search-connectors'; import { docLinks } from '../../../common/doc_links'; import { PLUGIN_ID } from '../../../common'; import { useKibanaServices } from '../hooks/use_kibana'; @@ -48,12 +52,12 @@ export const ElasticsearchOverview = () => { const [selectedLanguage, setSelectedLanguage] = useState(javascriptDefinition); const [clientApiKey, setClientApiKey] = useState(API_KEY_PLACEHOLDER); - const { application, cloud, http, userProfile, share } = useKibanaServices(); + const { application, cloud, http, user, share } = useKibanaServices(); const elasticsearchURL = useMemo(() => { return cloud?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER; }, [cloud]); - const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/`); + const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`); const codeSnippetArguments: LanguageDefinitionSnippetArguments = { url: elasticsearchURL, apiKey: clientApiKey, @@ -69,7 +73,7 @@ export const ElasticsearchOverview = () => { - + @@ -107,19 +111,71 @@ export const ElasticsearchOverview = () => { } - links={[ - { - href: docLinks.securityApis, - label: i18n.translate('xpack.serverlessSearch.configureClient.basicConfigLabel', { - defaultMessage: 'Basic configuration', - }), - }, - ]} + links={[]} title={i18n.translate('xpack.serverlessSearch.apiKey.title', { - defaultMessage: 'Store your API key and Cloud ID', + defaultMessage: 'Prepare an API Key', + })} + /> + + + + + +
    + {i18n.translate('xpack.serverlessSearch.cloudIdDetails.id.title', { + defaultMessage: 'Cloud ID', + })} +
    +
    + + + + + {cloud.cloudId} + + + +
    + + +
    + {i18n.translate('xpack.serverlessSearch.cloudIdDetails.url.title', { + defaultMessage: 'Cloud URL', + })} +
    +
    + + + + + {cloud.elasticsearchUrl} + + + +
    + + } + links={[]} + title={i18n.translate('xpack.serverlessSearch.cloudIdDetails.title', { + defaultMessage: 'Copy your connection details', })} />
    diff --git a/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx b/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx index 3a11ee645ffad..48df795609213 100644 --- a/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx +++ b/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx @@ -9,12 +9,12 @@ import { CloudStart } from '@kbn/cloud-plugin/public'; import type { CoreStart } from '@kbn/core/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import { useKibana as useKibanaBase } from '@kbn/kibana-react-plugin/public'; -import { GetUserProfileResponse, UserProfileData } from '@kbn/security-plugin/common'; +import { AuthenticatedUser } from '@kbn/security-plugin/common'; export interface ServerlessSearchContext { cloud: CloudStart; share: SharePluginStart; - userProfile: GetUserProfileResponse; + user?: AuthenticatedUser; } type ServerlessSearchKibanaContext = CoreStart & ServerlessSearchContext; diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx index 0879f86bc2b73..10d963100f89c 100644 --- a/x-pack/plugins/serverless_search/public/layout/nav.tsx +++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx @@ -93,12 +93,16 @@ const navigationTree: NavigationTreeDefinition = { defaultMessage: 'Index Management', }), link: 'management:index_management', + breadcrumbStatus: + 'hidden' /* management sub-pages set their breadcrumbs themselves */, }, { title: i18n.translate('xpack.serverlessSearch.nav.content.pipelines', { defaultMessage: 'Pipelines', }), link: 'management:ingest_pipelines', + breadcrumbStatus: + 'hidden' /* management sub-pages set their breadcrumbs themselves */, }, ], }, @@ -110,6 +114,8 @@ const navigationTree: NavigationTreeDefinition = { children: [ { link: 'management:api_keys', + breadcrumbStatus: + 'hidden' /* management sub-pages set their breadcrumbs themselves */, }, ], }, diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index c3faeb15f4384..d8dfb1224de7b 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -5,9 +5,16 @@ * 2.0. */ -import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { + AppMountParameters, + CoreSetup, + CoreStart, + DEFAULT_APP_CATEGORIES, + Plugin, +} from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { appIds } from '@kbn/management-cards-navigation'; +import { AuthenticatedUser } from '@kbn/security-plugin/common'; import { createServerlessSearchSideNavComponent as createComponent } from './layout/nav'; import { docLinks } from '../common/doc_links'; import { @@ -35,16 +42,23 @@ export class ServerlessSearchPlugin title: i18n.translate('xpack.serverlessSearch.app.elasticsearch.title', { defaultMessage: 'Elasticsearch', }), + euiIconType: 'logoElastic', + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, appRoute: '/app/elasticsearch', async mount({ element }: AppMountParameters) { const { renderApp } = await import('./application/elasticsearch'); const [coreStart, services] = await core.getStartServices(); const { security } = services; docLinks.setDocLinks(coreStart.docLinks.links); + let user: AuthenticatedUser | undefined; + try { + const response = await security.authc.getCurrentUser(); + user = response; + } catch { + user = undefined; + } - const userProfile = await security.userProfiles.getCurrent(); - - return await renderApp(element, coreStart, { userProfile, ...services }); + return await renderApp(element, coreStart, { user, ...services }); }, }); @@ -54,16 +68,15 @@ export class ServerlessSearchPlugin defaultMessage: 'Connectors', }), appRoute: '/app/connectors', + euiIconType: 'logoElastic', + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, searchable: false, async mount({ element }: AppMountParameters) { const { renderApp } = await import('./application/connectors'); const [coreStart, services] = await core.getStartServices(); - const { security } = services; - docLinks.setDocLinks(coreStart.docLinks.links); - - const userProfile = await security.userProfiles.getCurrent(); - return await renderApp(element, coreStart, { userProfile, ...services }); + docLinks.setDocLinks(coreStart.docLinks.links); + return await renderApp(element, coreStart, { ...services }); }, }); @@ -76,6 +89,7 @@ export class ServerlessSearchPlugin ): ServerlessSearchPluginStart { serverless.setProjectHome('/app/elasticsearch'); serverless.setSideNavComponent(createComponent(core, { serverless, cloud })); + management.setIsSidebarEnabled(false); management.setupCardsNavigation({ enabled: true, hideLinksTo: [appIds.MAINTENANCE_WINDOWS], diff --git a/x-pack/plugins/serverless_search/server/plugin.ts b/x-pack/plugins/serverless_search/server/plugin.ts index 2798a4333d35c..2651d6a2f38da 100644 --- a/x-pack/plugins/serverless_search/server/plugin.ts +++ b/x-pack/plugins/serverless_search/server/plugin.ts @@ -13,7 +13,7 @@ import type { CoreSetup, } from '@kbn/core/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; -import { EnterpriseSearchPluginStart } from '@kbn/enterprise-search-plugin/server'; +import { SEARCH_PROJECT_SETTINGS } from '@kbn/serverless-search-settings'; import { registerApiKeyRoutes } from './routes/api_key_routes'; import { registerIndicesRoutes } from './routes/indices_routes'; @@ -27,9 +27,9 @@ import type { import { registerConnectorsRoutes } from './routes/connectors_routes'; export interface RouteDependencies { + http: CoreSetup['http']; logger: Logger; router: IRouter; - search: EnterpriseSearchPluginStart; security: SecurityPluginStart; } @@ -46,7 +46,6 @@ export class ServerlessSearchPlugin private readonly config: ServerlessSearchConfig; private readonly logger: Logger; private security?: SecurityPluginStart; - private enterpriseSearch?: EnterpriseSearchPluginStart; constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); @@ -58,13 +57,12 @@ export class ServerlessSearchPlugin pluginsSetup: SetupDependencies ) { const router = http.createRouter(); - getStartServices().then(([, { enterpriseSearch, security }]) => { + getStartServices().then(([, { security }]) => { this.security = security; - this.enterpriseSearch = enterpriseSearch; const dependencies = { + http, logger: this.logger, router, - search: this.enterpriseSearch, security: this.security, }; @@ -73,7 +71,7 @@ export class ServerlessSearchPlugin registerIndicesRoutes(dependencies); }); - pluginsSetup.ml.setFeaturesEnabled({ ad: false, dfa: false, nlp: false }); + pluginsSetup.serverless.setupProjectSettings(SEARCH_PROJECT_SETTINGS); return {}; } diff --git a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts index c74c18f6d7414..766564bb9eaa7 100644 --- a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts @@ -5,16 +5,18 @@ * 2.0. */ +import { CONNECTOR_DEFINITIONS, fetchConnectors } from '@kbn/search-connectors'; import { RouteDependencies } from '../plugin'; -export const registerConnectorsRoutes = ({ router, search, security }: RouteDependencies) => { +export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => { router.get( { path: '/internal/serverless_search/connectors', validate: {}, }, async (context, request, response) => { - const connectors = await search.connectorsService.getConnectors(request); + const { client } = (await context.core).elasticsearch; + const connectors = await fetchConnectors(client.asCurrentUser); return response.ok({ body: { @@ -31,7 +33,14 @@ export const registerConnectorsRoutes = ({ router, search, security }: RouteDepe validate: {}, }, async (context, request, response) => { - const connectors = await search.connectorsService.getConnectorTypes(); + const connectors = CONNECTOR_DEFINITIONS.map((connector) => ({ + ...connector, + iconPath: connector.iconPath + ? http.basePath.prepend( + `/plugins/enterpriseSearch/assets/source_icons/${connector.iconPath}` + ) + : 'logoEnterpriseSearch', + })); return response.ok({ body: { diff --git a/x-pack/plugins/serverless_search/server/types.ts b/x-pack/plugins/serverless_search/server/types.ts index 38100d36f330e..c2eee3d0078a7 100644 --- a/x-pack/plugins/serverless_search/server/types.ts +++ b/x-pack/plugins/serverless_search/server/types.ts @@ -6,8 +6,7 @@ */ import type { SecurityPluginStart } from '@kbn/security-plugin/server'; -import type { EnterpriseSearchPluginStart } from '@kbn/enterprise-search-plugin/server'; -import type { MlPluginSetup } from '@kbn/ml-plugin/server'; +import type { ServerlessPluginSetup } from '@kbn/serverless/server'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessSearchPluginSetup {} @@ -15,9 +14,8 @@ export interface ServerlessSearchPluginSetup {} export interface ServerlessSearchPluginStart {} export interface StartDependencies { - enterpriseSearch: EnterpriseSearchPluginStart; security: SecurityPluginStart; } export interface SetupDependencies { - ml: MlPluginSetup; + serverless: ServerlessPluginSetup; } diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index 1187c90cd1a79..925b1536baaae 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -17,7 +17,6 @@ "kbn_references": [ "@kbn/core", "@kbn/config-schema", - "@kbn/enterprise-search-plugin", "@kbn/management-plugin", "@kbn/shared-ux-chrome-navigation", "@kbn/doc-links", @@ -28,11 +27,12 @@ "@kbn/security-plugin", "@kbn/cloud-plugin", "@kbn/share-plugin", - "@kbn/ml-plugin", "@kbn/management-cards-navigation", "@kbn/core-elasticsearch-server", "@kbn/search-api-panels", + "@kbn/serverless-search-settings", "@kbn/core-lifecycle-browser", "@kbn/react-kibana-context-theme", + "@kbn/search-connectors", ] } diff --git a/x-pack/plugins/session_view/public/components/detail_panel_description_list/index.tsx b/x-pack/plugins/session_view/public/components/detail_panel_description_list/index.tsx index 3d942fc42326e..99dc0d546233f 100644 --- a/x-pack/plugins/session_view/public/components/detail_panel_description_list/index.tsx +++ b/x-pack/plugins/session_view/public/components/detail_panel_description_list/index.tsx @@ -23,6 +23,7 @@ export const DetailPanelDescriptionList = ({ listItems }: DetailPanelDescription return ( { const descriptionList: CSSObject = { padding: `${euiTheme.size.base} ${euiTheme.size.s} `, alignItems: 'flex-start', + rowGap: 0, }; const tabListTitle = { - width: '40%', display: 'flex', - marginTop: '0px', }; const tabListDescription = { - width: '60%', display: 'flex', - marginTop: '0px', }; return { diff --git a/x-pack/plugins/spaces/public/mocks.ts b/x-pack/plugins/spaces/public/mocks.ts index 9146a0aa2c99b..66b916e459cc5 100644 --- a/x-pack/plugins/spaces/public/mocks.ts +++ b/x-pack/plugins/spaces/public/mocks.ts @@ -15,6 +15,7 @@ const createApiMock = (): jest.Mocked => ({ getActiveSpace$: jest.fn().mockReturnValue(of()), getActiveSpace: jest.fn(), ui: createApiUiMock(), + hasOnlyDefaultSpace: false, }); type SpacesApiUiMock = Omit, 'components'> & { diff --git a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index 2c46a577810f1..77801c3f8dadd 100644 --- a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap +++ b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -1,171 +1,52 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NavControlPopover renders without crashing 1`] = ` - + +
    +
    +
    + +
    +
    +
    + `; diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx index 54411b7ac85d5..420730ea696ac 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx @@ -12,8 +12,7 @@ import { EuiSelectable, EuiSelectableListItem, } from '@elastic/eui'; -import { act, waitFor } from '@testing-library/react'; -import { shallow } from 'enzyme'; +import { act, render, waitFor } from '@testing-library/react'; import React from 'react'; import * as Rx from 'rxjs'; @@ -70,7 +69,7 @@ describe('NavControlPopover', () => { it('renders without crashing', () => { const spacesManager = spacesManagerMock.create(); - const wrapper = shallow( + const { baseElement } = render( { navigateToUrl={jest.fn()} /> ); - expect(wrapper).toMatchSnapshot(); + expect(baseElement).toMatchSnapshot(); }); it('renders a SpaceAvatar with the active space', async () => { diff --git a/x-pack/plugins/spaces/public/plugin.test.ts b/x-pack/plugins/spaces/public/plugin.test.ts index 2e81e6c039668..a3a09db311b9c 100644 --- a/x-pack/plugins/spaces/public/plugin.test.ts +++ b/x-pack/plugins/spaces/public/plugin.test.ts @@ -149,4 +149,28 @@ describe('Spaces plugin', () => { expect(coreStart.chrome.navControls.registerLeft).not.toHaveBeenCalled(); }); }); + + it('determines hasOnlyDefaultSpace correctly when maxSpaces=1', () => { + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + + const plugin = new SpacesPlugin(coreMock.createPluginInitializerContext({ maxSpaces: 1 })); + const spacesSetup = plugin.setup(coreSetup, {}); + const spacesStart = plugin.start(coreStart); + + expect(spacesSetup.hasOnlyDefaultSpace).toBe(true); + expect(spacesStart.hasOnlyDefaultSpace).toBe(true); + }); + + it('determines hasOnlyDefaultSpace correctly when maxSpaces=1000', () => { + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + + const plugin = new SpacesPlugin(coreMock.createPluginInitializerContext({ maxSpaces: 1000 })); + const spacesSetup = plugin.setup(coreSetup, {}); + const spacesStart = plugin.start(coreStart); + + expect(spacesSetup.hasOnlyDefaultSpace).toBe(false); + expect(spacesStart.hasOnlyDefaultSpace).toBe(false); + }); }); diff --git a/x-pack/plugins/spaces/public/plugin.tsx b/x-pack/plugins/spaces/public/plugin.tsx index 28a54e7768f3e..02aad20bee27b 100644 --- a/x-pack/plugins/spaces/public/plugin.tsx +++ b/x-pack/plugins/spaces/public/plugin.tsx @@ -53,6 +53,8 @@ export class SpacesPlugin implements Plugin, plugins: PluginsSetup) { + const hasOnlyDefaultSpace = this.config.maxSpaces === 1; + this.spacesManager = new SpacesManager(core.http); this.spacesApi = { ui: getUiApi({ @@ -61,6 +63,7 @@ export class SpacesPlugin implements Plugin this.spacesManager.onActiveSpaceChange$, getActiveSpace: () => this.spacesManager.getActiveSpace(), + hasOnlyDefaultSpace, }; if (!this.isServerless) { @@ -85,7 +88,7 @@ export class SpacesPlugin implements Plugin; + /** + * Determines whether Kibana supports multiple spaces or only the default space. + * + * When `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden. + */ + hasOnlyDefaultSpace: boolean; + /** * UI components and services to add spaces capabilities to an application. */ diff --git a/x-pack/plugins/spaces/server/mocks.ts b/x-pack/plugins/spaces/server/mocks.ts index 1824fa352012a..f43a8e82a6408 100644 --- a/x-pack/plugins/spaces/server/mocks.ts +++ b/x-pack/plugins/spaces/server/mocks.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { of } from 'rxjs'; + import { spacesClientServiceMock } from './spaces_client/spaces_client_service.mock'; import { spacesServiceMock } from './spaces_service/spaces_service.mock'; @@ -12,12 +14,14 @@ function createSetupMock() { return { spacesService: spacesServiceMock.createSetupContract(), spacesClient: spacesClientServiceMock.createSetup(), + hasOnlyDefaultSpace$: of(false), }; } function createStartMock() { return { spacesService: spacesServiceMock.createStartContract(), + hasOnlyDefaultSpace$: of(false), }; } diff --git a/x-pack/plugins/spaces/server/plugin.test.ts b/x-pack/plugins/spaces/server/plugin.test.ts index de448e962ad68..3ac505c6e2e9a 100644 --- a/x-pack/plugins/spaces/server/plugin.test.ts +++ b/x-pack/plugins/spaces/server/plugin.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { lastValueFrom } from 'rxjs'; + import type { CoreSetup } from '@kbn/core/server'; import { coreMock } from '@kbn/core/server/mocks'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; @@ -26,6 +28,12 @@ describe('Spaces plugin', () => { const spacesSetup = plugin.setup(core, { features, licensing }); expect(spacesSetup).toMatchInlineSnapshot(` Object { + "hasOnlyDefaultSpace$": Observable { + "operator": [Function], + "source": Observable { + "_subscribe": [Function], + }, + }, "spacesClient": Object { "registerClientWrapper": [Function], "setClientRepositoryFactory": [Function], @@ -85,6 +93,12 @@ describe('Spaces plugin', () => { const spacesStart = plugin.start(coreStart); expect(spacesStart).toMatchInlineSnapshot(` Object { + "hasOnlyDefaultSpace$": Observable { + "operator": [Function], + "source": Observable { + "_subscribe": [Function], + }, + }, "spacesService": Object { "createSpacesClient": [Function], "getActiveSpace": [Function], @@ -97,4 +111,40 @@ describe('Spaces plugin', () => { `); }); }); + + it('determines hasOnlyDefaultSpace$ correctly when maxSpaces=1', async () => { + const initializerContext = coreMock.createPluginInitializerContext({ maxSpaces: 1 }); + const core = coreMock.createSetup() as CoreSetup; + const features = featuresPluginMock.createSetup(); + const licensing = licensingMock.createSetup(); + + const usageCollection = usageCollectionPluginMock.createSetupContract(); + + const plugin = new SpacesPlugin(initializerContext); + + const spacesSetup = plugin.setup(core, { features, licensing, usageCollection }); + const coreStart = coreMock.createStart(); + const spacesStart = plugin.start(coreStart); + + await expect(lastValueFrom(spacesSetup.hasOnlyDefaultSpace$)).resolves.toEqual(true); + await expect(lastValueFrom(spacesStart.hasOnlyDefaultSpace$)).resolves.toEqual(true); + }); + + it('determines hasOnlyDefaultSpace$ correctly when maxSpaces=1000', async () => { + const initializerContext = coreMock.createPluginInitializerContext({ maxSpaces: 1000 }); + const core = coreMock.createSetup() as CoreSetup; + const features = featuresPluginMock.createSetup(); + const licensing = licensingMock.createSetup(); + + const usageCollection = usageCollectionPluginMock.createSetupContract(); + + const plugin = new SpacesPlugin(initializerContext); + + const spacesSetup = plugin.setup(core, { features, licensing, usageCollection }); + const coreStart = coreMock.createStart(); + const spacesStart = plugin.start(coreStart); + + await expect(lastValueFrom(spacesSetup.hasOnlyDefaultSpace$)).resolves.toEqual(false); + await expect(lastValueFrom(spacesStart.hasOnlyDefaultSpace$)).resolves.toEqual(false); + }); }); diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 893d3db22d709..9084a74e24265 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -6,6 +6,7 @@ */ import type { Observable } from 'rxjs'; +import { map } from 'rxjs'; import type { CoreSetup, @@ -76,6 +77,13 @@ export interface SpacesPluginSetup { */ registerClientWrapper: (wrapper: SpacesClientWrapper) => void; }; + + /** + * Determines whether Kibana supports multiple spaces or only the default space. + * + * When `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden. + */ + hasOnlyDefaultSpace$: Observable; } /** @@ -84,6 +92,13 @@ export interface SpacesPluginSetup { export interface SpacesPluginStart { /** Service for interacting with spaces. */ spacesService: SpacesServiceStart; + + /** + * Determines whether Kibana supports multiple spaces or only the default space. + * + * When `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden. + */ + hasOnlyDefaultSpace$: Observable; } export class SpacesPlugin @@ -99,12 +114,15 @@ export class SpacesPlugin private readonly spacesService: SpacesService; + private readonly hasOnlyDefaultSpace$: Observable; + private spacesServiceStart?: SpacesServiceStart; private defaultSpaceService?: DefaultSpaceService; constructor(private readonly initializerContext: PluginInitializerContext) { this.config$ = initializerContext.config.create(); + this.hasOnlyDefaultSpace$ = this.config$.pipe(map(({ maxSpaces }) => maxSpaces === 1)); this.log = initializerContext.logger.get(); this.spacesService = new SpacesService(); this.spacesClientService = new SpacesClientService((message) => this.log.debug(message)); @@ -195,6 +213,7 @@ export class SpacesPlugin return { spacesClient: spacesClientSetup, spacesService: spacesServiceSetup, + hasOnlyDefaultSpace$: this.hasOnlyDefaultSpace$, }; } @@ -208,6 +227,7 @@ export class SpacesPlugin return { spacesService: this.spacesServiceStart, + hasOnlyDefaultSpace$: this.hasOnlyDefaultSpace$, }; } diff --git a/x-pack/plugins/stack_alerts/common/constants.ts b/x-pack/plugins/stack_alerts/common/constants.ts index cac00873face2..e846b249081e0 100644 --- a/x-pack/plugins/stack_alerts/common/constants.ts +++ b/x-pack/plugins/stack_alerts/common/constants.ts @@ -6,3 +6,5 @@ */ export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; + +export const MAX_SELECTABLE_GROUP_BY_TERMS = 4; diff --git a/x-pack/plugins/stack_alerts/common/esql_query_utils.test.ts b/x-pack/plugins/stack_alerts/common/esql_query_utils.test.ts new file mode 100644 index 0000000000000..64aad9c156958 --- /dev/null +++ b/x-pack/plugins/stack_alerts/common/esql_query_utils.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { rowToDocument, toEsQueryHits, transformDatatableToEsqlTable } from './esql_query_utils'; + +describe('ESQL query utils', () => { + describe('rowToDocument', () => { + it('correctly converts ESQL row to document', () => { + expect( + rowToDocument( + [ + { name: '@timestamp', type: 'date' }, + { name: 'ecs.version', type: 'keyword' }, + { name: 'error.code', type: 'keyword' }, + ], + ['2023-07-12T13:32:04.174Z', '1.8.0', null] + ) + ).toEqual({ + '@timestamp': '2023-07-12T13:32:04.174Z', + 'ecs.version': '1.8.0', + 'error.code': null, + }); + }); + }); + + describe('toEsQueryHits', () => { + it('correctly converts ESQL table to ES query hits', () => { + expect( + toEsQueryHits({ + columns: [ + { name: '@timestamp', type: 'date' }, + { name: 'ecs.version', type: 'keyword' }, + { name: 'error.code', type: 'keyword' }, + ], + values: [['2023-07-12T13:32:04.174Z', '1.8.0', null]], + }) + ).toEqual({ + hits: [ + { + _id: 'esql_query_document', + _index: '', + _source: { + '@timestamp': '2023-07-12T13:32:04.174Z', + 'ecs.version': '1.8.0', + 'error.code': null, + }, + }, + ], + total: 1, + }); + }); + }); + + describe('transformDatatableToEsqlTable', () => { + it('correctly converts data table to ESQL table', () => { + expect( + transformDatatableToEsqlTable({ + type: 'datatable', + columns: [ + { id: '@timestamp', name: '@timestamp', meta: { type: 'date' } }, + { id: 'ecs.version', name: 'ecs.version', meta: { type: 'string' } }, + { id: 'error.code', name: 'error.code', meta: { type: 'string' } }, + ], + rows: [ + { + '@timestamp': '2023-07-12T13:32:04.174Z', + 'ecs.version': '1.8.0', + 'error.code': null, + }, + ], + }) + ).toEqual({ + columns: [ + { + name: '@timestamp', + type: 'date', + }, + { + name: 'ecs.version', + type: 'string', + }, + { + name: 'error.code', + type: 'string', + }, + ], + values: [['2023-07-12T13:32:04.174Z', '1.8.0', null]], + }); + }); + }); +}); diff --git a/x-pack/plugins/stack_alerts/common/esql_query_utils.ts b/x-pack/plugins/stack_alerts/common/esql_query_utils.ts new file mode 100644 index 0000000000000..c74d3640c7fd7 --- /dev/null +++ b/x-pack/plugins/stack_alerts/common/esql_query_utils.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Datatable } from '@kbn/expressions-plugin/common'; + +type EsqlDocument = Record; + +interface EsqlHit { + _id: string; + _index: string; + _source: EsqlDocument; +} + +interface EsqlResultColumn { + name: string; + type: string; +} + +type EsqlResultRow = Array; + +export interface EsqlTable { + columns: EsqlResultColumn[]; + values: EsqlResultRow[]; +} + +const ESQL_DOCUMENT_ID = 'esql_query_document'; + +export const rowToDocument = (columns: EsqlResultColumn[], row: EsqlResultRow): EsqlDocument => { + return columns.reduce>((acc, column, i) => { + acc[column.name] = row[i]; + + return acc; + }, {}); +}; + +export const toEsQueryHits = (results: EsqlTable) => { + const hits: EsqlHit[] = results.values.map((row) => { + const document = rowToDocument(results.columns, row); + return { + _id: ESQL_DOCUMENT_ID, + _index: '', + _source: document, + }; + }); + + return { + hits, + total: hits.length, + }; +}; + +export const transformDatatableToEsqlTable = (results: Datatable): EsqlTable => { + const columns: EsqlResultColumn[] = results.columns.map((c) => ({ + name: c.id, + type: c.meta.type, + })); + const values: EsqlResultRow[] = results.rows.map((r) => Object.values(r)); + return { columns, values }; +}; diff --git a/x-pack/plugins/stack_alerts/common/index.ts b/x-pack/plugins/stack_alerts/common/index.ts index 4a9be641712f1..afafef61eb76b 100644 --- a/x-pack/plugins/stack_alerts/common/index.ts +++ b/x-pack/plugins/stack_alerts/common/index.ts @@ -12,3 +12,6 @@ export { getHumanReadableComparator, } from './comparator'; export { STACK_ALERTS_FEATURE_ID } from './constants'; + +export type { EsqlTable } from './esql_query_utils'; +export { rowToDocument, transformDatatableToEsqlTable, toEsQueryHits } from './esql_query_utils'; diff --git a/x-pack/plugins/stack_alerts/kibana.jsonc b/x-pack/plugins/stack_alerts/kibana.jsonc index 668d38d291a34..73b81c6dfd352 100644 --- a/x-pack/plugins/stack_alerts/kibana.jsonc +++ b/x-pack/plugins/stack_alerts/kibana.jsonc @@ -22,7 +22,8 @@ "kibanaUtils" ], "requiredBundles": [ - "esUiShared" + "esUiShared", + "textBasedLanguages" ] } } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts index 55a34c7b92a79..f99b97634fc5a 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts @@ -22,6 +22,7 @@ export const DEFAULT_VALUES = { TERM_SIZE: 5, GROUP_BY: 'all', EXCLUDE_PREVIOUS_HITS: true, + CAN_SELECT_MULTI_TERMS: true, }; export const COMMON_EXPRESSION_ERRORS = { @@ -48,6 +49,13 @@ export const ONLY_ES_QUERY_EXPRESSION_ERRORS = { timeField: new Array(), }; +export const ONLY_ESQL_QUERY_EXPRESSION_ERRORS = { + esqlQuery: new Array(), + timeField: new Array(), + thresholdComparator: new Array(), + threshold0: new Array(), +}; + const ALL_EXPRESSION_ERROR_ENTRIES = { ...COMMON_EXPRESSION_ERRORS, ...SEARCH_SOURCE_ONLY_EXPRESSION_ERRORS, diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx index 2119879c26b39..7a9bb590af73b 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx @@ -351,6 +351,7 @@ export const EsQueryExpression: React.FC< (exclude) => setParam('excludeHitsFromPreviousRun', exclude), [setParam] )} + canSelectMultiTerms={DEFAULT_VALUES.CAN_SELECT_MULTI_TERMS} /> diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.test.tsx new file mode 100644 index 0000000000000..025ac9deac732 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.test.tsx @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import { I18nProvider } from '@kbn/i18n-react'; + +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { EsqlQueryExpression } from './esql_query_expression'; +import { EsQueryRuleParams, SearchType } from '../types'; + +jest.mock('../validation', () => ({ + hasExpressionValidationErrors: jest.fn(), +})); +const { hasExpressionValidationErrors } = jest.requireMock('../validation'); + +jest.mock('@kbn/text-based-editor', () => ({ + fetchFieldsFromESQL: jest.fn(), +})); +const { fetchFieldsFromESQL } = jest.requireMock('@kbn/text-based-editor'); +const { getFields } = jest.requireMock('@kbn/triggers-actions-ui-plugin/public'); + +const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => ( + {children} +)); + +const dataMock = dataPluginMock.createStartContract(); +const dataViewMock = dataViewPluginMocks.createStartContract(); +const unifiedSearchMock = unifiedSearchPluginMock.createStartContract(); +const chartsStartMock = chartPluginMock.createStartContract(); + +const defaultEsqlQueryExpressionParams: EsQueryRuleParams = { + size: 100, + thresholdComparator: '>', + threshold: [0], + timeWindowSize: 15, + timeWindowUnit: 's', + index: ['test-index'], + timeField: '@timestamp', + aggType: 'count', + groupBy: 'all', + searchType: SearchType.esqlQuery, + esqlQuery: { esql: '' }, + excludeHitsFromPreviousRun: false, +}; + +describe('EsqlQueryRuleTypeExpression', () => { + beforeEach(() => { + jest.clearAllMocks(); + + hasExpressionValidationErrors.mockReturnValue(false); + }); + + it('should render EsqlQueryRuleTypeExpression with expected components', () => { + const result = render( + {}} + setRuleProperty={() => {}} + errors={{ esqlQuery: [], timeField: [], timeWindowSize: [] }} + data={dataMock} + dataViews={dataViewMock} + defaultActionGroupId="" + actionGroups={[]} + charts={chartsStartMock} + onChangeMetaData={() => {}} + />, + { + wrapper: AppWrapper, + } + ); + + expect(result.getByTestId('queryEsqlEditor')).toBeInTheDocument(); + expect(result.getByTestId('timeFieldSelect')).toBeInTheDocument(); + expect(result.getByTestId('timeWindowSizeNumber')).toBeInTheDocument(); + expect(result.getByTestId('timeWindowUnitSelect')).toBeInTheDocument(); + expect(result.queryByTestId('testQuerySuccess')).not.toBeInTheDocument(); + expect(result.queryByTestId('testQueryError')).not.toBeInTheDocument(); + }); + + test('should render Test Query button disabled if alert params are invalid', async () => { + hasExpressionValidationErrors.mockReturnValue(true); + const result = render( + {}} + setRuleProperty={() => {}} + errors={{ esqlQuery: [], timeField: [], timeWindowSize: [] }} + data={dataMock} + dataViews={dataViewMock} + defaultActionGroupId="" + actionGroups={[]} + charts={chartsStartMock} + onChangeMetaData={() => {}} + />, + { + wrapper: AppWrapper, + } + ); + + const button = result.getByTestId('testQuery'); + expect(button).toBeInTheDocument(); + expect(button).toBeDisabled(); + }); + + test('should show success message if Test Query is successful', async () => { + fetchFieldsFromESQL.mockResolvedValue({ + type: 'datatable', + columns: [ + { id: '@timestamp', name: '@timestamp', meta: { type: 'date' } }, + { id: 'ecs.version', name: 'ecs.version', meta: { type: 'string' } }, + { id: 'error.code', name: 'error.code', meta: { type: 'string' } }, + ], + rows: [ + { + '@timestamp': '2023-07-12T13:32:04.174Z', + 'ecs.version': '1.8.0', + 'error.code': null, + }, + ], + }); + getFields.mockResolvedValue([]); + const result = render( + {}} + setRuleProperty={() => {}} + errors={{ esqlQuery: [], timeField: [], timeWindowSize: [] }} + data={dataMock} + dataViews={dataViewMock} + defaultActionGroupId="" + actionGroups={[]} + charts={chartsStartMock} + onChangeMetaData={() => {}} + />, + { + wrapper: AppWrapper, + } + ); + + fireEvent.click(result.getByTestId('testQuery')); + await waitFor(() => expect(fetchFieldsFromESQL).toBeCalled()); + + expect(result.getByTestId('testQuerySuccess')).toBeInTheDocument(); + expect(result.getByText('Query matched 1 documents in the last 15s.')).toBeInTheDocument(); + expect(result.queryByTestId('testQueryError')).not.toBeInTheDocument(); + }); + + test('should show error message if Test Query is throws error', async () => { + fetchFieldsFromESQL.mockRejectedValue('Error getting test results.!'); + const result = render( + {}} + setRuleProperty={() => {}} + errors={{ esqlQuery: [], timeField: [], timeWindowSize: [] }} + data={dataMock} + dataViews={dataViewMock} + defaultActionGroupId="" + actionGroups={[]} + charts={chartsStartMock} + onChangeMetaData={() => {}} + />, + { + wrapper: AppWrapper, + } + ); + + fireEvent.click(result.getByTestId('testQuery')); + await waitFor(() => expect(fetchFieldsFromESQL).toBeCalled()); + + expect(result.queryByTestId('testQuerySuccess')).not.toBeInTheDocument(); + expect(result.getByTestId('testQueryError')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.tsx new file mode 100644 index 0000000000000..8f3edd45a1e28 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.tsx @@ -0,0 +1,260 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, Fragment, useEffect, useCallback } from 'react'; +import { get } from 'lodash'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { getFields, RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { TextBasedLangEditor } from '@kbn/text-based-languages/public'; +import { fetchFieldsFromESQL } from '@kbn/text-based-editor'; +import { AggregateQuery, getIndexPatternFromESQLQuery } from '@kbn/es-query'; +import { parseDuration } from '@kbn/alerting-plugin/common'; +import { + firstFieldOption, + getTimeFieldOptions, + getTimeOptions, + parseAggregationResults, +} from '@kbn/triggers-actions-ui-plugin/public/common'; +import { EsQueryRuleParams, EsQueryRuleMetaData, SearchType } from '../types'; +import { DEFAULT_VALUES } from '../constants'; +import { useTriggerUiActionServices } from '../util'; +import { hasExpressionValidationErrors } from '../validation'; +import { TestQueryRow } from '../test_query_row'; +import { rowToDocument, toEsQueryHits, transformDatatableToEsqlTable } from '../../../../common'; + +export const EsqlQueryExpression: React.FC< + RuleTypeParamsExpressionProps, EsQueryRuleMetaData> +> = ({ ruleParams, setRuleParams, setRuleProperty, errors }) => { + const { expressions, http } = useTriggerUiActionServices(); + const { esqlQuery, timeWindowSize, timeWindowUnit, timeField } = ruleParams; + + const [currentRuleParams, setCurrentRuleParams] = useState< + EsQueryRuleParams + >({ + ...ruleParams, + timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, + timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, + // ESQL queries compare conditions within the ES query + // so only 'met' results are returned, therefore the threshold should always be 0 + threshold: [0], + thresholdComparator: DEFAULT_VALUES.THRESHOLD_COMPARATOR, + size: DEFAULT_VALUES.SIZE, + esqlQuery: esqlQuery ?? { esql: '' }, + aggType: DEFAULT_VALUES.AGGREGATION_TYPE, + groupBy: DEFAULT_VALUES.GROUP_BY, + termSize: DEFAULT_VALUES.TERM_SIZE, + searchType: SearchType.esqlQuery, + }); + const [query, setQuery] = useState({ esql: '' }); + const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); + const [detectTimestamp, setDetectTimestamp] = useState(false); + + const setParam = useCallback( + (paramField: string, paramValue: unknown) => { + setCurrentRuleParams((currentParams) => ({ + ...currentParams, + [paramField]: paramValue, + })); + setRuleParams(paramField, paramValue); + }, + [setRuleParams] + ); + + const setDefaultExpressionValues = async () => { + setRuleProperty('params', currentRuleParams); + setQuery(esqlQuery ?? { esql: '' }); + if (esqlQuery && 'esql' in esqlQuery) { + if (esqlQuery.esql) { + refreshTimeFields(esqlQuery); + } + } + if (timeField) { + setTimeFieldOptions([firstFieldOption, { text: timeField, value: timeField }]); + } + }; + useEffect(() => { + setDefaultExpressionValues(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onTestQuery = useCallback(async () => { + const window = `${timeWindowSize}${timeWindowUnit}`; + const emptyResult = { + testResults: { results: [], truncated: false }, + isGrouped: true, + timeWindow: window, + }; + + if (hasExpressionValidationErrors(currentRuleParams)) { + return emptyResult; + } + const timeWindow = parseDuration(window); + const now = Date.now(); + const table = await fetchFieldsFromESQL(esqlQuery, expressions, { + from: new Date(now - timeWindow).toISOString(), + to: new Date(now).toISOString(), + }); + if (table) { + const esqlTable = transformDatatableToEsqlTable(table); + const hits = toEsQueryHits(esqlTable); + return { + testResults: parseAggregationResults({ + isCountAgg: true, + isGroupAgg: false, + esResult: { + took: 0, + timed_out: false, + _shards: { failed: 0, successful: 0, total: 0 }, + hits, + }, + }), + isGrouped: false, + timeWindow: window, + rawResults: { + cols: esqlTable.columns.map((col) => ({ + id: col.name, + })), + rows: esqlTable.values.slice(0, 5).map((row) => rowToDocument(esqlTable.columns, row)), + }, + }; + } + return emptyResult; + }, [timeWindowSize, timeWindowUnit, currentRuleParams, esqlQuery, expressions]); + + const refreshTimeFields = async (q: AggregateQuery) => { + let hasTimestamp = false; + const indexPattern: string = getIndexPatternFromESQLQuery(get(q, 'esql')); + const currentEsFields = await getFields(http, [indexPattern]); + const timeFields = getTimeFieldOptions(currentEsFields); + setTimeFieldOptions([firstFieldOption, ...timeFields]); + + const timestampField = timeFields.find((field) => field.value === '@timestamp'); + if (timestampField) { + setParam('timeField', timestampField.value); + hasTimestamp = true; + } + setDetectTimestamp(hasTimestamp); + }; + + return ( + + +
    + +
    +
    + + + { + setQuery(q); + setParam('esqlQuery', q); + refreshTimeFields(q); + }} + expandCodeEditor={() => true} + isCodeEditorExpanded={true} + onTextLangQuerySubmit={() => {}} + detectTimestamp={detectTimestamp} + hideMinimizeButton={true} + hideRunQueryText={true} + /> + + + +
    + +
    +
    + + 0 && timeField !== undefined} + error={errors.timeField} + > + 0 && timeField !== undefined} + fullWidth + name="timeField" + data-test-subj="timeFieldSelect" + value={timeField || ''} + onChange={(e) => { + setParam('timeField', e.target.value); + }} + /> + + + +
    + +
    +
    + + + + 0} + error={errors.timeWindowSize} + > + 0} + min={0} + value={timeWindowSize || ''} + onChange={(e) => { + const { value } = e.target; + const timeWindowSizeVal = value !== '' ? parseInt(value, 10) : undefined; + setParam('timeWindowSize', timeWindowSizeVal); + }} + /> + + + + + { + setParam('timeWindowUnit', e.target.value); + }} + options={getTimeOptions(timeWindowSize ?? 1)} + /> + + + + + +
    + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx index ecbfd306e1ead..09a4c3da9b5ec 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx @@ -23,7 +23,6 @@ import { EsQueryRuleTypeExpression } from './expression'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { Subject } from 'rxjs'; import { ISearchSource } from '@kbn/data-plugin/common'; -import { IUiSettingsClient } from '@kbn/core/public'; import { findTestSubject } from '@elastic/eui/lib/test'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { act } from 'react-dom/test-utils'; @@ -75,6 +74,20 @@ const defaultSearchSourceRuleParams: EsQueryRuleParams aggType: 'count', }; +const defaultEsqlRuleParams: EsQueryRuleParams = { + size: 100, + thresholdComparator: '>', + threshold: [0], + timeWindowSize: 15, + timeWindowUnit: 's', + index: ['test-index'], + timeField: '@timestamp', + searchType: SearchType.esqlQuery, + esqlQuery: { esql: 'test' }, + excludeHitsFromPreviousRun: true, + aggType: 'count', +}; + const dataViewPluginMock = dataViewPluginMocks.createStartContract(); const chartsStartMock = chartPluginMock.createStartContract(); const unifiedSearchMock = unifiedSearchPluginMock.createStartContract(); @@ -82,7 +95,7 @@ const httpMock = httpServiceMock.createStartContract(); const docLinksMock = docLinksServiceMock.createStartContract(); export const uiSettingsMock = { get: jest.fn(), -} as unknown as IUiSettingsClient; +}; const mockSearchResult = new Subject(); const searchSourceFieldsMock = { @@ -150,7 +163,10 @@ dataMock.query.savedQueries.findSavedQueries = jest.fn(() => (httpMock.post as jest.Mock).mockImplementation(() => Promise.resolve({ fields: [] })); const Wrapper: React.FC<{ - ruleParams: EsQueryRuleParams | EsQueryRuleParams; + ruleParams: + | EsQueryRuleParams + | EsQueryRuleParams + | EsQueryRuleParams; metadata?: EsQueryRuleMetaData; }> = ({ ruleParams, metadata }) => { const [currentRuleParams, setCurrentRuleParams] = useState(ruleParams); @@ -192,7 +208,10 @@ const Wrapper: React.FC<{ }; const setup = ( - ruleParams: EsQueryRuleParams | EsQueryRuleParams, + ruleParams: + | EsQueryRuleParams + | EsQueryRuleParams + | EsQueryRuleParams, metadata?: EsQueryRuleMetaData ) => { return mountWithIntl( @@ -213,11 +232,28 @@ const setup = ( }; describe('EsQueryRuleTypeExpression', () => { + beforeEach(() => { + jest.clearAllMocks(); + + uiSettingsMock.get.mockReturnValue(true); + }); + test('should render options by default', async () => { const wrapper = setup({} as EsQueryRuleParams); expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeTruthy(); expect(findTestSubject(wrapper, 'queryFormType_searchSource').exists()).toBeTruthy(); expect(findTestSubject(wrapper, 'queryFormType_esQuery').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormType_esqlQuery').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormTypeChooserCancel').exists()).toBeFalsy(); + }); + + test('should hide ESQL option when not enabled', async () => { + uiSettingsMock.get.mockReturnValueOnce(false); + const wrapper = setup({} as EsQueryRuleParams); + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormType_searchSource').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormType_esQuery').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormType_esqlQuery').exists()).toBeFalsy(); expect(findTestSubject(wrapper, 'queryFormTypeChooserCancel').exists()).toBeFalsy(); }); @@ -257,6 +293,23 @@ describe('EsQueryRuleTypeExpression', () => { expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeTruthy(); }); + test('should switch to ESQL form type on selection and return back on cancel', async () => { + let wrapper = setup({} as EsQueryRuleParams); + await act(async () => { + findTestSubject(wrapper, 'queryFormType_esqlQuery').simulate('click'); + }); + wrapper = await wrapper.update(); + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeFalsy(); + expect(wrapper.exists('[data-test-subj="queryEsqlEditor"]')).toBeTruthy(); + + await act(async () => { + findTestSubject(wrapper, 'queryFormTypeChooserCancel').simulate('click'); + }); + wrapper = await wrapper.update(); + expect(wrapper.exists('[data-test-subj="queryEsqlEditor"]')).toBeFalsy(); + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeTruthy(); + }); + test('should render QueryDSL view without the form type chooser', async () => { let wrapper: ReactWrapper; await act(async () => { @@ -282,4 +335,19 @@ describe('EsQueryRuleTypeExpression', () => { expect(findTestSubject(wrapper!, 'queryFormTypeChooserCancel').exists()).toBeFalsy(); expect(findTestSubject(wrapper!, 'selectDataViewExpression').exists()).toBeTruthy(); }); + + test('should render ESQL view without the form type chooser', async () => { + let wrapper: ReactWrapper; + await act(async () => { + wrapper = setup(defaultEsqlRuleParams, { + adHocDataViewList: [], + isManagementPage: false, + }); + wrapper = await wrapper.update(); + }); + wrapper = await wrapper!.update(); + expect(findTestSubject(wrapper!, 'queryFormTypeChooserTitle').exists()).toBeFalsy(); + expect(findTestSubject(wrapper!, 'queryFormTypeChooserCancel').exists()).toBeFalsy(); + expect(wrapper.exists('[data-test-subj="queryEsqlEditor"]')).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx index 30045fee81a29..1653799a233df 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx @@ -15,8 +15,9 @@ import { EsQueryRuleParams, EsQueryRuleMetaData, SearchType } from '../types'; import { SearchSourceExpression, SearchSourceExpressionProps } from './search_source_expression'; import { EsQueryExpression } from './es_query_expression'; import { QueryFormTypeChooser } from './query_form_type_chooser'; -import { isSearchSourceRule } from '../util'; +import { isEsqlQueryRule, isSearchSourceRule } from '../util'; import { ALL_EXPRESSION_ERROR_KEYS } from '../constants'; +import { EsqlQueryExpression } from './esql_query_expression'; function areSearchSourceExpressionPropsEqual( prevProps: Readonly>, @@ -37,6 +38,7 @@ export const EsQueryRuleTypeExpression: React.FunctionComponent< > = (props) => { const { ruleParams, errors, setRuleProperty, setRuleParams } = props; const isSearchSource = isSearchSourceRule(ruleParams); + const isEsqlQuery = isEsqlQueryRule(ruleParams); // metadata provided only when open alert from Discover page const isManagementPage = props.metadata?.isManagementPage ?? true; @@ -95,10 +97,14 @@ export const EsQueryRuleTypeExpression: React.FunctionComponent< )} - {ruleParams.searchType && !isSearchSource && ( + {ruleParams.searchType && !isSearchSource && !isEsqlQuery && ( )} + {ruleParams.searchType && isEsqlQuery && ( + + )} + ); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx index bff6792bdda3f..0077a035d1e8d 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { + EuiBetaBadge, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, @@ -19,39 +20,25 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { SearchType } from '../types'; +import { useTriggerUiActionServices } from '../util'; -const FORM_TYPE_ITEMS: Array<{ formType: SearchType; label: string; description: string }> = [ - { - formType: SearchType.searchSource, - label: i18n.translate( - 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeLabel', +export const ExperimentalBadge = React.memo(() => ( + +)); +ExperimentalBadge.displayName = 'ExperimentalBadge'; export interface QueryFormTypeProps { searchType: SearchType | null; @@ -62,16 +49,82 @@ export const QueryFormTypeChooser: React.FC = ({ searchType, onFormTypeSelect, }) => { + const { uiSettings } = useTriggerUiActionServices(); + const isEsqlEnabled = uiSettings?.get('discover:enableESQL'); + + const formTypeItems = useMemo(() => { + const items: Array<{ formType: SearchType; label: string; description: string }> = [ + { + formType: SearchType.searchSource, + label: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeLabel', + { + defaultMessage: 'KQL or Lucene', + } + ), + description: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeDescription', + { + defaultMessage: 'Use KQL or Lucene to define a text-based query.', + } + ), + }, + { + formType: SearchType.esQuery, + label: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.queryDslFormTypeLabel', + { + defaultMessage: 'Query DSL', + } + ), + description: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.queryDslFormTypeDescription', + { + defaultMessage: 'Use the Elasticsearch Query DSL to define a query.', + } + ), + }, + ]; + + if (isEsqlEnabled) { + items.push({ + formType: SearchType.esqlQuery, + label: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeLabel', + { + defaultMessage: 'ES|QL', + } + ), + description: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeDescription', + { + defaultMessage: 'Use ES|QL to define a text-based query.', + } + ), + }); + } + return items; + }, [isEsqlEnabled]); + if (searchType) { - const activeFormTypeItem = FORM_TYPE_ITEMS.find((item) => item.formType === searchType); + const activeFormTypeItem = formTypeItems.find((item) => item.formType === searchType); return ( <> - -
    {activeFormTypeItem?.label}
    -
    + + + +
    {activeFormTypeItem?.label}
    +
    +
    + {activeFormTypeItem?.formType === SearchType.esqlQuery && ( + + + + )} +
    = ({ - {FORM_TYPE_ITEMS.map((item) => ( + {formTypeItems.map((item) => ( = ({ color="primary" label={ - {item.label} + + + {item.label} + + {item.formType === SearchType.esqlQuery && ( + + + + )} +

    {item.description}

    diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx index 1cc45801ffe65..b6c4cdd72bf62 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx @@ -14,12 +14,8 @@ import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { - mapAndFlattenFilters, - getTime, - type SavedQuery, - type ISearchSource, -} from '@kbn/data-plugin/public'; +import { mapAndFlattenFilters, getTime } from '@kbn/data-plugin/public'; +import type { SavedQuery, ISearchSource } from '@kbn/data-plugin/public'; import { BUCKET_SELECTOR_FIELD, buildAggregation, @@ -51,7 +47,9 @@ interface LocalState extends CommonRuleParams { interface LocalStateAction { type: SearchSourceParamsAction['type'] | keyof CommonRuleParams; - payload: SearchSourceParamsAction['payload'] | (number[] | number | string | boolean | undefined); + payload: + | SearchSourceParamsAction['payload'] + | (number[] | number | string | string[] | boolean | undefined); } type LocalStateReducer = (prevState: LocalState, action: LocalStateAction) => LocalState; @@ -201,7 +199,8 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp ); const onChangeSelectedTermField = useCallback( - (selectedTermField?: string) => dispatch({ type: 'termField', payload: selectedTermField }), + (selectedTermField?: string | string[]) => + dispatch({ type: 'termField', payload: selectedTermField }), [] ); @@ -372,6 +371,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp onCopyQuery={onCopyQuery} excludeHitsFromPreviousRun={ruleConfiguration.excludeHitsFromPreviousRun} onChangeExcludeHitsFromPreviousRun={onChangeExcludeHitsFromPreviousRun} + canSelectMultiTerms={DEFAULT_VALUES.CAN_SELECT_MULTI_TERMS} /> diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx index 119f0e02cdca2..267b289cf5ca8 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx @@ -217,6 +217,33 @@ describe('RuleCommonExpressions', () => { ); }); + test(`should use multiple group by terms`, async () => { + const aggType = 'avg'; + const thresholdComparator = 'between'; + const timeWindowSize = 987; + const timeWindowUnit = 's'; + const threshold = [3, 1003]; + const groupBy = 'top'; + const termSize = '27'; + const termField = ['term', 'term2']; + + const wrapper = await setup({ + ruleParams: getCommonParams({ + aggType, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + termSize, + termField, + groupBy, + threshold, + }), + }); + expect(wrapper.find('button[data-test-subj="groupByExpression"]').text()).toEqual( + `grouped over ${groupBy} ${termSize} 'term,term2'` + ); + }); + test(`should disable excludeHitsFromPreviousRuns when groupBy is not all`, async () => { const aggType = 'avg'; const thresholdComparator = 'between'; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx index d74fce210354c..cee08144f307e 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx @@ -52,6 +52,7 @@ export interface RuleCommonExpressionsProps extends CommonRuleParams { onTestFetch: TestQueryRowProps['fetch']; onCopyQuery?: TestQueryRowProps['copyQuery']; onChangeExcludeHitsFromPreviousRun: (exclude: boolean) => void; + canSelectMultiTerms?: boolean; } export const RuleCommonExpressions: React.FC = ({ @@ -82,6 +83,7 @@ export const RuleCommonExpressions: React.FC = ({ onCopyQuery, excludeHitsFromPreviousRun, onChangeExcludeHitsFromPreviousRun, + canSelectMultiTerms, }) => { const [isExcludeHitsDisabled, setIsExcludeHitsDisabled] = useState(false); @@ -127,6 +129,7 @@ export const RuleCommonExpressions: React.FC = ({ errors={errors} fields={esFields} display="fullWidth" + canSelectMultiTerms={canSelectMultiTerms} onChangeSelectedGroupBy={onChangeSelectedGroupBy} onChangeSelectedTermField={onChangeSelectedTermField} onChangeSelectedTermSize={onChangeSelectedTermSize} diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row.tsx index b5c7ff3fb8054..ce215d7e881f1 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row.tsx @@ -12,12 +12,14 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiSpacer, EuiText, EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { ParsedAggregationResults } from '@kbn/triggers-actions-ui-plugin/common'; import { useTestQuery } from './use_test_query'; +import { TestQueryRowTable } from './test_query_row_table'; export interface TestQueryRowProps { fetch: () => Promise<{ @@ -27,14 +29,24 @@ export interface TestQueryRowProps { }>; copyQuery?: () => string; hasValidationErrors: boolean; + showTable?: boolean; } export const TestQueryRow: React.FC = ({ fetch, copyQuery, hasValidationErrors, + showTable, }) => { - const { onTestQuery, testQueryResult, testQueryError, testQueryLoading } = useTestQuery(fetch); + const { + onTestQuery, + testQueryResult, + testQueryError, + testQueryLoading, + testQueryRawResults, + testQueryAlerts, + } = useTestQuery(fetch); + const [copiedMessage, setCopiedMessage] = useState(null); return ( @@ -124,6 +136,12 @@ export const TestQueryRow: React.FC = ({ )} + {showTable && testQueryRawResults && ( + <> + + + + )} ); }; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row_table.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row_table.test.tsx new file mode 100644 index 0000000000000..54b575be36f11 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row_table.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { I18nProvider } from '@kbn/i18n-react'; +import { TestQueryRowTable } from './test_query_row_table'; + +const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => ( + {children} +)); + +describe('TestQueryRow', () => { + it('should render the datagrid', () => { + const result = render( + , + { + wrapper: AppWrapper, + } + ); + + expect(result.getByTestId('test-query-row-datagrid')).toBeInTheDocument(); + expect(result.getAllByTestId('dataGridRowCell')).toHaveLength(2); + expect(result.queryByText('Alerts generated')).not.toBeInTheDocument(); + expect(result.queryAllByTestId('alert-badge')).toHaveLength(0); + }); + + it('should render the datagrid and alerts if provided', () => { + const result = render( + , + { + wrapper: AppWrapper, + } + ); + + expect(result.getByTestId('test-query-row-datagrid')).toBeInTheDocument(); + expect(result.getAllByTestId('dataGridRowCell')).toHaveLength(2); + expect(result.getByText('Alerts generated')).toBeInTheDocument(); + expect(result.getAllByTestId('alert-badge')).toHaveLength(2); + }); +}); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row_table.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row_table.tsx new file mode 100644 index 0000000000000..504880ee8da43 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row_table.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { css } from '@emotion/react'; +import { + EuiDataGrid, + EuiPanel, + EuiDataGridColumn, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiBadge, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const styles = { + grid: css` + .euiDataGridHeaderCell { + background: none; + } + .euiDataGridHeader .euiDataGridHeaderCell { + border-top: none; + } + `, +}; + +export interface TestQueryRowTableProps { + rawResults: { cols: EuiDataGridColumn[]; rows: Array> }; + alerts: string[] | null; +} + +export const TestQueryRowTable: React.FC = ({ rawResults, alerts }) => { + return ( + + c.id), + setVisibleColumns: () => {}, + }} + rowCount={rawResults.rows.length} + gridStyle={{ + border: 'horizontal', + rowHover: 'none', + }} + renderCellValue={({ rowIndex, columnId }) => rawResults.rows[rowIndex][columnId]} + pagination={{ + pageIndex: 0, + pageSize: 10, + onChangeItemsPerPage: () => {}, + onChangePage: () => {}, + }} + toolbarVisibility={false} + /> + + {alerts && ( + + + +
    + {i18n.translate('xpack.stackAlerts.esQuery.ui.testQueryAlerts', { + defaultMessage: 'Alerts generated', + })} +
    +
    +
    + {alerts.map((alert, index) => { + return ( + + + {alert} + + + ); + })} +
    + )} +
    + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts index 2067bf816eff7..44c2a1cf55717 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts @@ -20,6 +20,10 @@ describe('useTestQuery', () => { }, isGrouped: false, timeWindow: '1s', + rawResults: { + cols: [{ id: 'ungrouped', name: 'ungrouped', field: 'ungrouped', actions: false }], + rows: [{ ungrouped: 'test' }], + }, }), }); await act(async () => { @@ -29,6 +33,11 @@ describe('useTestQuery', () => { expect(result.current.testQueryError).toBe(null); expect(result.current.testQueryResult).toContain('1s'); expect(result.current.testQueryResult).toContain('1 document'); + expect(result.current.testQueryRawResults).toEqual({ + cols: [{ id: 'ungrouped', name: 'ungrouped', field: 'ungrouped', actions: false }], + rows: [{ ungrouped: 'test' }], + }); + expect(result.current.testQueryAlerts).toEqual(['query matched']); }); test('returning a valid result for grouped result', async () => { @@ -44,6 +53,10 @@ describe('useTestQuery', () => { }, isGrouped: true, timeWindow: '1s', + rawResults: { + cols: [{ id: 'grouped', name: 'grouped', field: 'grouped', actions: false }], + rows: [{ grouped: 'test' }], + }, }), }); await act(async () => { @@ -55,6 +68,11 @@ describe('useTestQuery', () => { expect(result.current.testQueryResult).toContain( 'Grouped query matched 2 groups in the last 1s.' ); + expect(result.current.testQueryRawResults).toEqual({ + cols: [{ id: 'grouped', name: 'grouped', field: 'grouped', actions: false }], + rows: [{ grouped: 'test' }], + }); + expect(result.current.testQueryAlerts).toEqual(['a', 'b']); }); test('returning an error', async () => { @@ -68,5 +86,7 @@ describe('useTestQuery', () => { expect(result.current.testQueryLoading).toBe(false); expect(result.current.testQueryError).toContain(errorMsg); expect(result.current.testQueryResult).toBe(null); + expect(result.current.testQueryRawResults).toBe(null); + expect(result.current.testQueryAlerts).toBe(null); }); }); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.ts index 06f230bafdccb..bac58eb2f0f43 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.ts @@ -8,17 +8,22 @@ import { useState, useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import type { ParsedAggregationResults } from '@kbn/triggers-actions-ui-plugin/common'; +import { EuiDataGridColumn } from '@elastic/eui'; interface TestQueryResponse { result: string | null; error: string | null; isLoading: boolean; + rawResults: { cols: EuiDataGridColumn[]; rows: Array> } | null; + alerts: string[] | null; } const TEST_QUERY_INITIAL_RESPONSE: TestQueryResponse = { result: null, error: null, isLoading: false, + rawResults: null, + alerts: null, }; /** @@ -30,6 +35,7 @@ export function useTestQuery( testResults: ParsedAggregationResults; isGrouped: boolean; timeWindow: string; + rawResults?: { cols: EuiDataGridColumn[]; rows: Array> }; }> ) { const [testQueryResponse, setTestQueryResponse] = useState( @@ -46,10 +52,12 @@ export function useTestQuery( result: null, error: null, isLoading: true, + rawResults: null, + alerts: null, }); try { - const { testResults, isGrouped, timeWindow } = await fetch(); + const { testResults, isGrouped, timeWindow, rawResults } = await fetch(); if (isGrouped) { setTestQueryResponse({ @@ -62,6 +70,11 @@ export function useTestQuery( }), error: null, isLoading: false, + rawResults: rawResults ?? null, + alerts: + testResults.results.length > 0 + ? testResults.results.map((result) => result.group) + : null, }); } else { const ungroupedQueryResponse = @@ -73,6 +86,8 @@ export function useTestQuery( }), error: null, isLoading: false, + rawResults: rawResults ?? null, + alerts: ungroupedQueryResponse.count > 0 ? ['query matched'] : null, }); } } catch (err) { @@ -85,6 +100,8 @@ export function useTestQuery( values: { message: message ? `${err.message}: ${message}` : err.message }, }), isLoading: false, + rawResults: null, + alerts: null, }); } }, [fetch]); @@ -94,5 +111,7 @@ export function useTestQuery( testQueryResult: testQueryResponse.result, testQueryError: testQueryResponse.error, testQueryLoading: testQueryResponse.isLoading, + testQueryRawResults: testQueryResponse.rawResults, + testQueryAlerts: testQueryResponse.alerts, }; } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts index d517d23af52b3..93b184f855232 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts @@ -9,10 +9,12 @@ import { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { EuiComboBoxOptionOption } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { AggregateQuery } from '@kbn/es-query'; export enum SearchType { esQuery = 'esQuery', searchSource = 'searchSource', + esqlQuery = 'esqlQuery', } export interface CommonRuleParams { @@ -25,7 +27,7 @@ export interface CommonRuleParams { aggField?: string; groupBy?: string; termSize?: number; - termField?: string; + termField?: string | string[]; excludeHitsFromPreviousRun: boolean; } @@ -38,6 +40,8 @@ export interface EsQueryRuleMetaData { export type EsQueryRuleParams = T extends SearchType.searchSource ? CommonEsQueryRuleParams & OnlySearchSourceRuleParams + : T extends SearchType.esqlQuery + ? CommonEsQueryRuleParams & OnlyEsqlQueryRuleParams : CommonEsQueryRuleParams & OnlyEsQueryRuleParams; export interface OnlyEsQueryRuleParams { @@ -53,4 +57,10 @@ export interface OnlySearchSourceRuleParams { savedQueryId?: string; } +export interface OnlyEsqlQueryRuleParams { + searchType?: 'esqlQuery'; + esqlQuery: AggregateQuery; + timeField: string; +} + export type DataViewOption = EuiComboBoxOptionOption; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts index 5568924e845e4..7ca42220c3ebf 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts @@ -17,6 +17,12 @@ export const isSearchSourceRule = ( return ruleParams.searchType === 'searchSource'; }; +export const isEsqlQueryRule = ( + ruleParams: EsQueryRuleParams +): ruleParams is EsQueryRuleParams => { + return ruleParams.searchType === 'esqlQuery'; +}; + export const convertFieldSpecToFieldOption = (fieldSpec: FieldSpec[]): FieldOption[] => { return (fieldSpec ?? []) .filter((spec: FieldSpec) => spec.isMapped || spec.runtimeField) diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts index 0b40110f60072..54363fe1010f3 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts @@ -103,6 +103,46 @@ describe('expression params validation', () => { expect(validateExpression(initialParams).errors.termField[0]).toBe('Term field is required.'); }); + test('if termField property is an array but has no items should return proper error message', () => { + const initialParams: EsQueryRuleParams = { + index: ['test'], + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + timeField: '', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'top', + termSize: 10, + termField: [], + }; + expect(validateExpression(initialParams).errors.termField.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.termField[0]).toBe('Term field is required.'); + }); + + test('if termField property is an array but has more than 4 items, should return proper error message', () => { + const initialParams: EsQueryRuleParams = { + index: ['test'], + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + timeField: '', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'top', + termSize: 10, + termField: ['term', 'term2', 'term3', 'term4', 'term5'], + }; + expect(validateExpression(initialParams).errors.termField.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.termField[0]).toBe( + 'Cannot select more than 4 terms' + ); + }); + test('if esQuery property is invalid JSON should return proper error message', () => { const initialParams: EsQueryRuleParams = { index: ['test'], @@ -278,4 +318,63 @@ describe('expression params validation', () => { expect(validateExpression(initialParams).errors.size.length).toBe(0); expect(hasExpressionValidationErrors(initialParams)).toBe(false); }); + + test('if esqlQuery property is not set should return proper error message', () => { + const initialParams = { + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + timeField: '@timestamp', + searchType: SearchType.esqlQuery, + } as EsQueryRuleParams; + expect(validateExpression(initialParams).errors.esqlQuery.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.esqlQuery[0]).toBe(`ES|QL query is required.`); + }); + + test('if esqlQuery timeField property is not defined should return proper error message', () => { + const initialParams = { + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + esqlQuery: { esql: 'test' }, + searchType: SearchType.esqlQuery, + } as EsQueryRuleParams; + expect(validateExpression(initialParams).errors.timeField.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.timeField[0]).toBe('Time field is required.'); + }); + + test('if esqlQuery thresholdComparator property is not gt should return proper error message', () => { + const initialParams = { + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + esqlQuery: { esql: 'test' }, + searchType: SearchType.esqlQuery, + thresholdComparator: '<', + timeField: '@timestamp', + } as EsQueryRuleParams; + expect(validateExpression(initialParams).errors.thresholdComparator.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.thresholdComparator[0]).toBe( + 'Threshold comparator is required to be greater than.' + ); + }); + + test('if esqlQuery threshold property is not 0 should return proper error message', () => { + const initialParams = { + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [8], + esqlQuery: { esql: 'test' }, + searchType: SearchType.esqlQuery, + timeField: '@timestamp', + } as EsQueryRuleParams; + expect(validateExpression(initialParams).errors.threshold0.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.threshold0[0]).toBe( + 'Threshold is required to be 0.' + ); + }); }); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts index 9e6eaeb7b6de0..5830a506a0073 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts @@ -12,11 +12,14 @@ import { builtInComparators, builtInAggregationTypes, builtInGroupByTypes, + COMPARATORS, } from '@kbn/triggers-actions-ui-plugin/public'; +import { MAX_SELECTABLE_GROUP_BY_TERMS } from '../../../common/constants'; import { EsQueryRuleParams, SearchType } from './types'; -import { isSearchSourceRule } from './util'; +import { isEsqlQueryRule, isSearchSourceRule } from './util'; import { COMMON_EXPRESSION_ERRORS, + ONLY_ESQL_QUERY_EXPRESSION_ERRORS, ONLY_ES_QUERY_EXPRESSION_ERRORS, SEARCH_SOURCE_ONLY_EXPRESSION_ERRORS, } from './constants'; @@ -70,7 +73,7 @@ const validateCommonParams = (ruleParams: EsQueryRuleParams) => { groupBy && builtInGroupByTypes[groupBy].validNormalizedTypes && builtInGroupByTypes[groupBy].validNormalizedTypes.length > 0 && - !termField + (!termField || termField.length <= 0) ) { errors.termField.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredTermFieldText', { @@ -79,6 +82,22 @@ const validateCommonParams = (ruleParams: EsQueryRuleParams) => { ); } + if ( + groupBy && + builtInGroupByTypes[groupBy].validNormalizedTypes && + builtInGroupByTypes[groupBy].validNormalizedTypes.length > 0 && + termField && + Array.isArray(termField) && + termField.length > MAX_SELECTABLE_GROUP_BY_TERMS + ) { + errors.termField.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.overNumberedTermFieldText', { + defaultMessage: `Cannot select more than {max} terms`, + values: { max: MAX_SELECTABLE_GROUP_BY_TERMS }, + }) + ); + } + if (!threshold || threshold.length === 0 || threshold[0] === undefined) { errors.threshold0.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredThreshold0Text', { @@ -221,6 +240,46 @@ const validateEsQueryParams = (ruleParams: EsQueryRuleParams return errors; }; +const validateEsqlQueryParams = (ruleParams: EsQueryRuleParams) => { + const errors: typeof ONLY_ESQL_QUERY_EXPRESSION_ERRORS = defaultsDeep( + {}, + ONLY_ESQL_QUERY_EXPRESSION_ERRORS + ); + if (!ruleParams.esqlQuery) { + errors.esqlQuery.push( + i18n.translate('xpack.stackAlerts.esqlQuery.ui.validation.error.requiredQueryText', { + defaultMessage: 'ES|QL query is required.', + }) + ); + } + if (!ruleParams.timeField) { + errors.timeField.push( + i18n.translate('xpack.stackAlerts.esqlQuery.ui.validation.error.requiredTimeFieldText', { + defaultMessage: 'Time field is required.', + }) + ); + } + if (ruleParams.thresholdComparator !== COMPARATORS.GREATER_THAN) { + errors.thresholdComparator.push( + i18n.translate( + 'xpack.stackAlerts.esqlQuery.ui.validation.error.requiredThresholdComparatorText', + { + defaultMessage: 'Threshold comparator is required to be greater than.', + } + ) + ); + } + if (ruleParams.threshold && ruleParams.threshold[0] !== 0) { + errors.threshold0.push( + i18n.translate('xpack.stackAlerts.esqlQuery.ui.validation.error.requiredThreshold0Text', { + defaultMessage: 'Threshold is required to be 0.', + }) + ); + } + + return errors; +}; + export const validateExpression = (ruleParams: EsQueryRuleParams): ValidationResult => { const validationResult = { errors: {} }; @@ -234,8 +293,7 @@ export const validateExpression = (ruleParams: EsQueryRuleParams): ValidationRes * It's important to report searchSource rule related errors only into errors.searchConfiguration prop. * For example errors.index is a mistake to report searchSource rule related errors. It will lead to issues. */ - const isSearchSource = isSearchSourceRule(ruleParams); - if (isSearchSource) { + if (isSearchSourceRule(ruleParams)) { validationResult.errors = { ...validationResult.errors, ...validateSearchSourceParams(ruleParams), @@ -243,6 +301,14 @@ export const validateExpression = (ruleParams: EsQueryRuleParams): ValidationRes return validationResult; } + if (isEsqlQueryRule(ruleParams)) { + validationResult.errors = { + ...validationResult.errors, + ...validateEsqlQueryParams(ruleParams), + }; + return validationResult; + } + const esQueryErrors = validateEsQueryParams(ruleParams as EsQueryRuleParams); validationResult.errors = { ...validationResult.errors, ...esQueryErrors }; return validationResult; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/index.ts b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/index.ts index 0ed978e6edd0e..7921471550f37 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/index.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/index.ts @@ -15,11 +15,11 @@ export function getRuleType(): RuleTypeModel { return { id: '.geo-containment', description: i18n.translate('xpack.stackAlerts.geoContainment.descriptionText', { - defaultMessage: 'Alert when an entity is contained within a geo boundary.', + defaultMessage: 'Alert when an entity is contained or no longer contained within a boundary.', }), iconClass: 'globe', documentationUrl: null, - ruleParamsExpression: lazy(() => import('./query_builder')), + ruleParamsExpression: lazy(() => import('./rule_form')), validate: validateExpression, requiresAppContext: false, }; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap deleted file mode 100644 index abef05ed9b343..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap +++ /dev/null @@ -1,278 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render BoundaryIndexExpression 1`] = ` - - - - - - - - - - - - } -/> -`; - -exports[`should render EntityIndexExpression 1`] = ` - - - - - - } - labelType="label" - > - - - - - - - } -/> -`; - -exports[`should render EntityIndexExpression w/ invalid flag if invalid 1`] = ` - - - - - - } - labelType="label" - > - - - - - - - } -/> -`; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/__snapshots__/entity_by_expression.test.tsx.snap b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/__snapshots__/entity_by_expression.test.tsx.snap deleted file mode 100644 index 16b77c6b2d5ab..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/__snapshots__/entity_by_expression.test.tsx.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render entity by expression with aggregatable field options for entity 1`] = ` -
    -
    - -
    -
    -`; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx deleted file mode 100644 index f53b1bfab5982..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx +++ /dev/null @@ -1,176 +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, { Fragment, FunctionComponent, useEffect, useRef } from 'react'; -import { EuiFormRow } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { HttpSetup } from '@kbn/core/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; -import { DataViewField, DataView } from '@kbn/data-plugin/common'; -import { ES_GEO_SHAPE_TYPES, GeoContainmentAlertParams } from '../../types'; -import { GeoIndexPatternSelect } from '../util_components/geo_index_pattern_select'; -import { SingleFieldSelect } from '../util_components/single_field_select'; -import { ExpressionWithPopover } from '../util_components/expression_with_popover'; - -interface Props { - ruleParams: GeoContainmentAlertParams; - errors: IErrorObject; - boundaryIndexPattern: DataView; - boundaryNameField?: string; - setBoundaryIndexPattern: (boundaryIndexPattern?: DataView) => void; - setBoundaryGeoField: (boundaryGeoField?: string) => void; - setBoundaryNameField: (boundaryNameField?: string) => void; - data: DataPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; -} - -interface KibanaDeps { - http: HttpSetup; -} - -export const BoundaryIndexExpression: FunctionComponent = ({ - ruleParams, - errors, - boundaryIndexPattern, - boundaryNameField, - setBoundaryIndexPattern, - setBoundaryGeoField, - setBoundaryNameField, - data, - unifiedSearch, -}) => { - // eslint-disable-next-line react-hooks/exhaustive-deps - const BOUNDARY_NAME_ENTITY_TYPES = ['string', 'number', 'ip']; - const { http } = useKibana().services; - const IndexPatternSelect = (unifiedSearch.ui && unifiedSearch.ui.IndexPatternSelect) || null; - const { boundaryGeoField } = ruleParams; - // eslint-disable-next-line react-hooks/exhaustive-deps - const nothingSelected: DataViewField = { - name: '', - type: 'string', - } as DataViewField; - - const usePrevious = (value: T): T | undefined => { - const ref = useRef(); - useEffect(() => { - ref.current = value; - }); - return ref.current; - }; - - const oldIndexPattern = usePrevious(boundaryIndexPattern); - const fields = useRef<{ - geoFields: DataViewField[]; - boundaryNameFields: DataViewField[]; - }>({ - geoFields: [], - boundaryNameFields: [], - }); - useEffect(() => { - if (oldIndexPattern !== boundaryIndexPattern) { - fields.current.geoFields = - (boundaryIndexPattern.fields && - boundaryIndexPattern.fields.length && - boundaryIndexPattern.fields.filter((field: DataViewField) => - ES_GEO_SHAPE_TYPES.includes(field.type) - )) || - []; - if (fields.current.geoFields.length) { - setBoundaryGeoField(fields.current.geoFields[0].name); - } - - fields.current.boundaryNameFields = [ - ...(boundaryIndexPattern.fields ?? []).filter((field: DataViewField) => { - return ( - BOUNDARY_NAME_ENTITY_TYPES.includes(field.type) && - !field.name.startsWith('_') && - !field.name.endsWith('keyword') - ); - }), - nothingSelected, - ]; - if (fields.current.boundaryNameFields.length) { - setBoundaryNameField(fields.current.boundaryNameFields[0].name); - } - } - }, [ - BOUNDARY_NAME_ENTITY_TYPES, - boundaryIndexPattern, - nothingSelected, - oldIndexPattern, - setBoundaryGeoField, - setBoundaryNameField, - ]); - - const indexPopover = ( - - - { - if (!_indexPattern) { - return; - } - setBoundaryIndexPattern(_indexPattern); - }} - value={boundaryIndexPattern.id} - IndexPatternSelectComponent={IndexPatternSelect} - indexPatternService={data.indexPatterns} - http={http} - includedGeoTypes={ES_GEO_SHAPE_TYPES} - /> - - - - - - { - setBoundaryNameField(name === nothingSelected.name ? undefined : name); - }} - fields={fields.current.boundaryNameFields} - /> - - - ); - - return ( - - ); -}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_by_expression.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_by_expression.test.tsx deleted file mode 100644 index 7d1ef64448311..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_by_expression.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { mount } from 'enzyme'; -import { EntityByExpression, getValidIndexPatternFields } from './entity_by_expression'; -import { DataViewField } from '@kbn/data-views-plugin/public'; - -const defaultProps = { - errors: { - index: [], - indexId: [], - geoField: [], - entity: [], - dateField: [], - boundaryType: [], - boundaryIndexTitle: [], - boundaryIndexId: [], - boundaryGeoField: [], - name: ['Name is required.'], - interval: [], - alertTypeId: [], - actionConnectors: [], - }, - entity: 'FlightNum', - setAlertParamsEntity: (arg: string) => {}, - indexFields: [ - { - count: 0, - name: 'DestLocation', - type: 'geo_point', - esTypes: ['geo_point'], - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - { - count: 0, - name: 'FlightNum', - type: 'string', - esTypes: ['keyword'], - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - { - count: 0, - name: 'OriginLocation', - type: 'geo_point', - esTypes: ['geo_point'], - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - { - count: 0, - name: 'timestamp', - type: 'date', - esTypes: ['date'], - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - ] as DataViewField[], - isInvalid: false, -}; - -test('should render entity by expression with aggregatable field options for entity', async () => { - const component = mount(); - expect(component.render()).toMatchSnapshot(); -}); -// - -test('should only use valid index fields', async () => { - // Only the string index field should match - const indexFields = getValidIndexPatternFields(defaultProps.indexFields); - expect(indexFields.length).toEqual(1); - - // Set all agg fields to false, invalidating them for use - const invalidIndexFields = defaultProps.indexFields.map((field) => ({ - ...field, - aggregatable: false, - })); - - const noIndexFields = getValidIndexPatternFields(invalidIndexFields as DataViewField[]); - expect(noIndexFields.length).toEqual(0); -}); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_by_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_by_expression.tsx deleted file mode 100644 index b0b02fe27f8ef..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_by_expression.tsx +++ /dev/null @@ -1,92 +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, { FunctionComponent, useEffect, useRef } from 'react'; -import { EuiFormRow } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import { DataViewField } from '@kbn/data-views-plugin/public'; -import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; -import { SingleFieldSelect } from '../util_components/single_field_select'; -import { ExpressionWithPopover } from '../util_components/expression_with_popover'; - -interface Props { - errors: IErrorObject; - entity: string; - setAlertParamsEntity: (entity: string) => void; - indexFields: DataViewField[]; - isInvalid: boolean; -} - -const ENTITY_TYPES = ['string', 'number', 'ip']; -export function getValidIndexPatternFields(fields: DataViewField[]): DataViewField[] { - return fields.filter((field) => { - const isSpecifiedSupportedField = ENTITY_TYPES.includes(field.type); - const hasLeadingUnderscore = field.name.startsWith('_'); - const isAggregatable = !!field.aggregatable; - return isSpecifiedSupportedField && isAggregatable && !hasLeadingUnderscore; - }); -} - -export const EntityByExpression: FunctionComponent = ({ - errors, - entity, - setAlertParamsEntity, - indexFields, - isInvalid, -}) => { - const usePrevious = (value: T): T | undefined => { - const ref = useRef(); - useEffect(() => { - ref.current = value; - }); - return ref.current; - }; - - const oldIndexFields = usePrevious(indexFields); - const fields = useRef<{ - indexFields: DataViewField[]; - }>({ - indexFields: [], - }); - useEffect(() => { - if (!_.isEqual(oldIndexFields, indexFields)) { - fields.current.indexFields = getValidIndexPatternFields(indexFields); - if (!entity && fields.current.indexFields.length) { - setAlertParamsEntity(fields.current.indexFields[0].name); - } - } - }, [indexFields, oldIndexFields, setAlertParamsEntity, entity]); - - const indexPopover = ( - - _entity && setAlertParamsEntity(_entity)} - fields={fields.current.indexFields} - /> - - ); - - return ( - - ); -}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx deleted file mode 100644 index fe75c7b22dcc2..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/expressions/entity_index_expression.tsx +++ /dev/null @@ -1,172 +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, { Fragment, FunctionComponent, useEffect, useRef } from 'react'; -import { EuiFormRow } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { HttpSetup } from '@kbn/core/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { - IErrorObject, - RuleTypeParamsExpressionProps, -} from '@kbn/triggers-actions-ui-plugin/public'; -import { DataViewField, DataView } from '@kbn/data-plugin/common'; -import { ES_GEO_FIELD_TYPES } from '../../types'; -import { GeoIndexPatternSelect } from '../util_components/geo_index_pattern_select'; -import { SingleFieldSelect } from '../util_components/single_field_select'; -import { ExpressionWithPopover } from '../util_components/expression_with_popover'; - -interface Props { - dateField: string; - geoField: string; - errors: IErrorObject; - setAlertParamsDate: (date: string) => void; - setAlertParamsGeoField: (geoField: string) => void; - setRuleProperty: RuleTypeParamsExpressionProps['setRuleProperty']; - setIndexPattern: (indexPattern: DataView) => void; - indexPattern: DataView; - isInvalid: boolean; - data: DataPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; -} - -interface KibanaDeps { - http: HttpSetup; -} - -export const EntityIndexExpression: FunctionComponent = ({ - setAlertParamsDate, - setAlertParamsGeoField, - errors, - setIndexPattern, - indexPattern, - isInvalid, - dateField: timeField, - geoField, - data, - unifiedSearch, -}) => { - const { http } = useKibana().services; - const IndexPatternSelect = (unifiedSearch.ui && unifiedSearch.ui.IndexPatternSelect) || null; - - const usePrevious = (value: T): T | undefined => { - const ref = useRef(); - useEffect(() => { - ref.current = value; - }); - return ref.current; - }; - - const oldIndexPattern = usePrevious(indexPattern); - const fields = useRef<{ - dateFields: DataViewField[]; - geoFields: DataViewField[]; - }>({ - dateFields: [], - geoFields: [], - }); - useEffect(() => { - if (oldIndexPattern !== indexPattern) { - fields.current.geoFields = - (indexPattern.fields && - indexPattern.fields.length && - indexPattern.fields.filter((field: DataViewField) => - ES_GEO_FIELD_TYPES.includes(field.type) - )) || - []; - if (fields.current.geoFields.length) { - setAlertParamsGeoField(fields.current.geoFields[0].name); - } - - fields.current.dateFields = - (indexPattern.fields && - indexPattern.fields.length && - indexPattern.fields.filter((field: DataViewField) => field.type === 'date')) || - []; - if (fields.current.dateFields.length) { - setAlertParamsDate(fields.current.dateFields[0].name); - } - } - }, [indexPattern, oldIndexPattern, setAlertParamsDate, setAlertParamsGeoField]); - - const indexPopover = ( - - - { - // reset time field and expression fields if indices are deleted - if (!_indexPattern) { - return; - } - setIndexPattern(_indexPattern); - }} - value={indexPattern.id} - IndexPatternSelectComponent={IndexPatternSelect} - indexPatternService={data.indexPatterns} - http={http} - includedGeoTypes={ES_GEO_FIELD_TYPES} - /> - - - } - > - - _timeField && setAlertParamsDate(_timeField) - } - fields={fields.current.dateFields} - /> - - - - _geoField && setAlertParamsGeoField(_geoField) - } - fields={fields.current.geoFields} - /> - - - ); - - return ( - - ); -}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/geo_containment_alert_type_expression.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/geo_containment_alert_type_expression.test.tsx deleted file mode 100644 index 5ef79b7379035..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/geo_containment_alert_type_expression.test.tsx +++ /dev/null @@ -1,88 +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 { EntityIndexExpression } from './expressions/entity_index_expression'; -import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; -import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; -import { DataView } from '@kbn/data-plugin/common'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; - -const dataStartMock = dataPluginMock.createStartContract(); -const unifiedSearchStartMock = unifiedSearchPluginMock.createStartContract(); - -const alertParams = { - index: '', - indexId: '', - geoField: '', - entity: '', - dateField: '', - boundaryType: '', - boundaryIndexTitle: '', - boundaryIndexId: '', - boundaryGeoField: '', -}; - -test('should render EntityIndexExpression', async () => { - const component = shallow( - {}} - setAlertParamsGeoField={() => {}} - setRuleProperty={() => {}} - setIndexPattern={() => {}} - indexPattern={'' as unknown as DataView} - isInvalid={false} - data={dataStartMock} - unifiedSearch={unifiedSearchStartMock} - /> - ); - - expect(component).toMatchSnapshot(); -}); - -test('should render EntityIndexExpression w/ invalid flag if invalid', async () => { - const component = shallow( - {}} - setAlertParamsGeoField={() => {}} - setRuleProperty={() => {}} - setIndexPattern={() => {}} - indexPattern={'' as unknown as DataView} - isInvalid={true} - data={dataStartMock} - unifiedSearch={unifiedSearchStartMock} - /> - ); - - expect(component).toMatchSnapshot(); -}); - -test('should render BoundaryIndexExpression', async () => { - const component = shallow( - {}} - setBoundaryGeoField={() => {}} - setBoundaryNameField={() => {}} - boundaryNameField={'testNameField'} - data={dataStartMock} - unifiedSearch={unifiedSearchStartMock} - /> - ); - - expect(component).toMatchSnapshot(); -}); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx deleted file mode 100644 index d49ee6c15b83a..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/index.tsx +++ /dev/null @@ -1,298 +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, { Fragment, useEffect, useState } from 'react'; -import { EuiCallOut, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; -import { fromKueryExpression, luceneStringToDsl } from '@kbn/es-query'; -import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; -import type { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import type { Query } from '@kbn/es-query'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { HttpSetup } from '@kbn/core-http-browser'; -import type { DocLinksStart } from '@kbn/core-doc-links-browser'; -import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; -import type { CoreStart } from '@kbn/core/public'; -import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { STACK_ALERTS_FEATURE_ID } from '../../../../common/constants'; -import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; -import { EntityByExpression } from './expressions/entity_by_expression'; -import { EntityIndexExpression } from './expressions/entity_index_expression'; -import type { GeoContainmentAlertParams } from '../types'; - -const DEFAULT_VALUES = { - TRACKING_EVENT: '', - ENTITY: '', - INDEX: '', - INDEX_ID: '', - DATE_FIELD: '', - BOUNDARY_TYPE: 'entireIndex', // Only one supported currently. Will eventually be more - GEO_FIELD: '', - BOUNDARY_INDEX: '', - BOUNDARY_INDEX_ID: '', - BOUNDARY_GEO_FIELD: '', - BOUNDARY_NAME_FIELD: '', - DELAY_OFFSET_WITH_UNITS: '0m', -}; - -interface KibanaDeps { - http: HttpSetup; - docLinks: DocLinksStart; - dataViews: DataViewsPublicPluginStart; - uiSettings: IUiSettingsClient; - notifications: CoreStart['notifications']; - storage: IStorageWrapper; - usageCollection: UsageCollectionStart; -} - -function validateQuery(query: Query) { - try { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - query.language === 'kuery' ? fromKueryExpression(query.query) : luceneStringToDsl(query.query); - } catch (err) { - return false; - } - return true; -} - -export const GeoContainmentAlertTypeExpression: React.FunctionComponent< - RuleTypeParamsExpressionProps -> = ({ ruleParams, ruleInterval, setRuleParams, setRuleProperty, errors, data, unifiedSearch }) => { - const { - index, - indexId, - indexQuery, - geoField, - entity, - dateField, - boundaryType, - boundaryIndexTitle, - boundaryIndexId, - boundaryIndexQuery, - boundaryGeoField, - boundaryNameField, - } = ruleParams; - - const { http, docLinks, uiSettings, notifications, storage, usageCollection, dataViews } = - useKibana().services; - - const [indexPattern, _setIndexPattern] = useState({ - id: '', - title: '', - } as DataView); - const setIndexPattern = (_indexPattern?: DataView) => { - if (_indexPattern) { - _setIndexPattern(_indexPattern); - if (_indexPattern.title) { - setRuleParams('index', _indexPattern.title); - } - if (_indexPattern.id) { - setRuleParams('indexId', _indexPattern.id); - } - } - }; - const [indexQueryInput, setIndexQueryInput] = useState( - indexQuery || { - query: '', - language: 'kuery', - } - ); - const [boundaryIndexPattern, _setBoundaryIndexPattern] = useState({ - id: '', - title: '', - } as DataView); - const setBoundaryIndexPattern = (_indexPattern?: DataView) => { - if (_indexPattern) { - _setBoundaryIndexPattern(_indexPattern); - if (_indexPattern.title) { - setRuleParams('boundaryIndexTitle', _indexPattern.title); - } - if (_indexPattern.id) { - setRuleParams('boundaryIndexId', _indexPattern.id); - } - } - }; - const [boundaryIndexQueryInput, setBoundaryIndexQueryInput] = useState( - boundaryIndexQuery || { - query: '', - language: 'kuery', - } - ); - - const hasExpressionErrors = false; - const expressionErrorMessage = i18n.translate( - 'xpack.stackAlerts.geoContainment.fixErrorInExpressionBelowValidationMessage', - { - defaultMessage: 'Expression contains errors.', - } - ); - - useEffect(() => { - const initToDefaultParams = async () => { - setRuleProperty('params', { - ...ruleParams, - index: index ?? DEFAULT_VALUES.INDEX, - indexId: indexId ?? DEFAULT_VALUES.INDEX_ID, - entity: entity ?? DEFAULT_VALUES.ENTITY, - dateField: dateField ?? DEFAULT_VALUES.DATE_FIELD, - boundaryType: boundaryType ?? DEFAULT_VALUES.BOUNDARY_TYPE, - geoField: geoField ?? DEFAULT_VALUES.GEO_FIELD, - boundaryIndexTitle: boundaryIndexTitle ?? DEFAULT_VALUES.BOUNDARY_INDEX, - boundaryIndexId: boundaryIndexId ?? DEFAULT_VALUES.BOUNDARY_INDEX_ID, - boundaryGeoField: boundaryGeoField ?? DEFAULT_VALUES.BOUNDARY_GEO_FIELD, - boundaryNameField: boundaryNameField ?? DEFAULT_VALUES.BOUNDARY_NAME_FIELD, - }); - if (!data.indexPatterns) { - return; - } - if (indexId) { - const _indexPattern = await data.indexPatterns.get(indexId); - setIndexPattern(_indexPattern); - } - if (boundaryIndexId) { - const _boundaryIndexPattern = await data.indexPatterns.get(boundaryIndexId); - setBoundaryIndexPattern(_boundaryIndexPattern); - } - }; - initToDefaultParams(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - - {hasExpressionErrors ? ( - - - - - - ) : null} - - -
    - -
    -
    - - setRuleParams('dateField', _date)} - setAlertParamsGeoField={(_geoField) => setRuleParams('geoField', _geoField)} - setRuleProperty={setRuleProperty} - setIndexPattern={setIndexPattern} - indexPattern={indexPattern} - isInvalid={!indexId || !dateField || !geoField} - data={data} - unifiedSearch={unifiedSearch} - /> - setRuleParams('entity', entityName)} - indexFields={indexPattern.fields} - isInvalid={indexId && dateField && geoField ? !entity : false} - /> - - - { - if (query.language) { - if (validateQuery(query)) { - setRuleParams('indexQuery', query); - } - setIndexQueryInput(query); - } - }} - appName={STACK_ALERTS_FEATURE_ID} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - storage, - usageCollection, - }} - /> - - - -
    - -
    -
    - - - _geoField && setRuleParams('boundaryGeoField', _geoField) - } - setBoundaryNameField={(_boundaryNameField: string | undefined) => - _boundaryNameField - ? setRuleParams('boundaryNameField', _boundaryNameField) - : setRuleParams('boundaryNameField', '') - } - boundaryNameField={boundaryNameField} - data={data} - unifiedSearch={unifiedSearch} - /> - - - { - if (query.language) { - if (validateQuery(query)) { - setRuleParams('boundaryIndexQuery', query); - } - setBoundaryIndexQueryInput(query); - } - }} - appName={STACK_ALERTS_FEATURE_ID} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - storage, - usageCollection, - }} - /> - - -
    - ); -}; - -// eslint-disable-next-line import/no-default-export -export { GeoContainmentAlertTypeExpression as default }; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/__snapshots__/geo_index_pattern_select.test.tsx.snap b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/__snapshots__/geo_index_pattern_select.test.tsx.snap deleted file mode 100644 index f5f4c35f0edde..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/__snapshots__/geo_index_pattern_select.test.tsx.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render with error when data view does not have geo_point field 1`] = ` - - - - - -`; - -exports[`should render without error after mounting 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/expression_with_popover.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/expression_with_popover.tsx deleted file mode 100644 index 9509e3a534f44..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/expression_with_popover.tsx +++ /dev/null @@ -1,79 +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, { ReactNode, useState } from 'react'; -import { - EuiButtonIcon, - EuiExpression, - EuiFlexGroup, - EuiFlexItem, - EuiPopover, - EuiPopoverTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export const ExpressionWithPopover: ({ - popoverContent, - expressionDescription, - defaultValue, - value, - isInvalid, -}: { - popoverContent: ReactNode; - expressionDescription: ReactNode; - defaultValue?: ReactNode; - value?: ReactNode; - isInvalid?: boolean; -}) => JSX.Element = ({ popoverContent, expressionDescription, defaultValue, value, isInvalid }) => { - const [popoverOpen, setPopoverOpen] = useState(false); - - return ( - setPopoverOpen(true)} - isInvalid={isInvalid} - /> - } - isOpen={popoverOpen} - closePopover={() => setPopoverOpen(false)} - ownFocus - anchorPosition="downLeft" - zIndex={8000} - display="block" - > -
    - - - {expressionDescription} - - setPopoverOpen(false)} - /> - - - - {popoverContent} -
    -
    - ); -}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/geo_index_pattern_select.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/geo_index_pattern_select.test.tsx deleted file mode 100644 index 01abe69d5f8c1..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/geo_index_pattern_select.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { GeoIndexPatternSelect } from './geo_index_pattern_select'; -import { DataViewsContract } from '@kbn/data-plugin/public'; -import { HttpSetup } from '@kbn/core/public'; - -class MockIndexPatternSelectComponent extends React.Component { - render() { - return 'MockIndexPatternSelectComponent'; - } -} - -function makeMockIndexPattern(id: string, fields: unknown) { - return { - id, - fields, - }; -} - -const mockIndexPatternService: DataViewsContract = { - get(id: string) { - if (id === 'foobar_with_geopoint') { - return makeMockIndexPattern(id, [{ type: 'geo_point' }]); - } else if (id === 'foobar_without_geopoint') { - return makeMockIndexPattern(id, [{ type: 'string' }]); - } - }, -} as unknown as DataViewsContract; - -test('should render without error after mounting', async () => { - const component = shallow( - {}} - value={'foobar_with_geopoint'} - includedGeoTypes={['geo_point']} - indexPatternService={mockIndexPatternService} - IndexPatternSelectComponent={MockIndexPatternSelectComponent} - /> - ); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - expect(component).toMatchSnapshot(); -}); - -test('should render with error when data view does not have geo_point field', async () => { - const component = shallow( - {}} - value={'foobar_without_geopoint'} - includedGeoTypes={['geo_point']} - indexPatternService={mockIndexPatternService} - IndexPatternSelectComponent={MockIndexPatternSelectComponent} - /> - ); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - expect(component).toMatchSnapshot(); -}); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/geo_index_pattern_select.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/geo_index_pattern_select.tsx deleted file mode 100644 index b2a235feb1a80..0000000000000 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/geo_index_pattern_select.tsx +++ /dev/null @@ -1,178 +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, { Component } from 'react'; -import { EuiCallOut, EuiFormRow, EuiLink, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { DataViewsContract } from '@kbn/data-plugin/public'; -import { HttpSetup } from '@kbn/core/public'; -import { DataView } from '@kbn/data-plugin/common'; - -interface Props { - onChange: (indexPattern: DataView) => void; - value: string | undefined; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - IndexPatternSelectComponent: any; - indexPatternService: DataViewsContract | undefined; - http: HttpSetup; - includedGeoTypes: string[]; -} - -interface State { - doesIndexPatternHaveGeoField: boolean; - noIndexPatternsExist: boolean; -} - -export class GeoIndexPatternSelect extends Component { - private _isMounted: boolean = false; - - state = { - doesIndexPatternHaveGeoField: false, - noIndexPatternsExist: false, - }; - - componentWillUnmount() { - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - if (this.props.value) { - this._loadIndexPattern(this.props.value); - } - } - - _loadIndexPattern = async (indexPatternId: string) => { - if (!indexPatternId || indexPatternId.length === 0 || !this.props.indexPatternService) { - return; - } - - let indexPattern; - try { - indexPattern = await this.props.indexPatternService.get(indexPatternId); - } catch (err) { - return; - } - - if (!this._isMounted || indexPattern.id !== indexPatternId) { - return; - } - - this.setState({ - doesIndexPatternHaveGeoField: indexPattern.fields.some((field) => { - return this.props.includedGeoTypes.includes(field.type); - }), - }); - - return indexPattern; - }; - - _onIndexPatternSelect = async (indexPatternId: string) => { - const indexPattern = await this._loadIndexPattern(indexPatternId); - if (indexPattern) { - this.props.onChange(indexPattern); - } - }; - - _onNoIndexPatterns = () => { - this.setState({ noIndexPatternsExist: true }); - }; - - _renderNoIndexPatternWarning() { - if (!this.state.noIndexPatternsExist) { - return null; - } - - return ( - <> - -

    - - - - -

    -

    - - - - -

    -
    - - - ); - } - - render() { - const IndexPatternSelectComponent = this.props.IndexPatternSelectComponent; - const isIndexPatternInvalid = !!this.props.value && !this.state.doesIndexPatternHaveGeoField; - const error = isIndexPatternInvalid - ? i18n.translate('xpack.stackAlerts.geoContainment.noGeoFieldInIndexPattern.message', { - defaultMessage: - 'Data view does not contain any allowed geospatial fields. Must have one of type {geoFields}.', - values: { - geoFields: this.props.includedGeoTypes.join(', '), - }, - }) - : ''; - - return ( - <> - {this._renderNoIndexPatternWarning()} - - - {IndexPatternSelectComponent ? ( - - ) : ( -
    - )} - - - ); - } -} diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.test.tsx new file mode 100644 index 0000000000000..971d8e24df7d9 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { BoundaryForm } from './boundary_form'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { GeoContainmentAlertParams } from '../types'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; + +jest.mock('./query_input', () => { + return { + QueryInput: () =>
    mock query input
    , + }; +}); + +test('should not call prop callbacks on render', async () => { + const DATA_VIEW_TITLE = 'my-boundaries*'; + const DATA_VIEW_ID = '1234'; + const mockDataView = { + id: DATA_VIEW_ID, + fields: [ + { + name: 'location', + type: 'geo_shape', + }, + ], + title: DATA_VIEW_TITLE, + }; + const props = { + data: { + indexPatterns: { + get: async () => mockDataView, + }, + } as unknown as DataPublicPluginStart, + getValidationError: () => null, + ruleParams: { + boundaryIndexTitle: DATA_VIEW_TITLE, + boundaryIndexId: DATA_VIEW_ID, + boundaryGeoField: 'location', + boundaryNameField: 'name', + boundaryIndexQuery: { + query: 'population > 1000', + language: 'kuery', + }, + } as unknown as GeoContainmentAlertParams, + setDataViewId: jest.fn(), + setDataViewTitle: jest.fn(), + setGeoField: jest.fn(), + setNameField: jest.fn(), + setQuery: jest.fn(), + unifiedSearch: { + ui: { + IndexPatternSelect: () => { + return '
    mock IndexPatternSelect
    '; + }, + }, + } as unknown as UnifiedSearchPublicPluginStart, + }; + + const wrapper = mountWithIntl(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + // Assert that geospatial dataView fields are loaded + // to ensure test is properly awaiting async useEffect + let geoFieldsLoaded = false; + wrapper.findWhere((n) => { + if ( + n.name() === 'SingleFieldSelect' && + n.props().value === 'location' && + n.props().fields.length === 1 + ) { + geoFieldsLoaded = true; + } + return false; + }); + expect(geoFieldsLoaded).toBe(true); + + expect(props.setDataViewId).not.toHaveBeenCalled(); + expect(props.setDataViewTitle).not.toHaveBeenCalled(); + expect(props.setGeoField).not.toHaveBeenCalled(); + expect(props.setNameField).not.toHaveBeenCalled(); + expect(props.setQuery).not.toHaveBeenCalled(); +}); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx new file mode 100644 index 0000000000000..9674ee3a7fb02 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/boundary_form.tsx @@ -0,0 +1,233 @@ +/* + * Copyright 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, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFormRow, EuiPanel, EuiSkeletonText, EuiSpacer, EuiTitle } from '@elastic/eui'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; +import type { Query } from '@kbn/es-query'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { GeoContainmentAlertParams } from '../types'; +import { DataViewSelect } from './data_view_select'; +import { SingleFieldSelect } from './single_field_select'; +import { QueryInput } from './query_input'; + +export const BOUNDARY_GEO_FIELD_TYPES = ['geo_shape']; + +function getGeoFields(fields: DataViewField[]) { + return fields.filter((field: DataViewField) => BOUNDARY_GEO_FIELD_TYPES.includes(field.type)); +} + +function getNameFields(fields: DataViewField[]) { + return fields.filter( + (field: DataViewField) => + ['string', 'number', 'ip'].includes(field.type) && + !field.name.startsWith('_') && + !field.name.endsWith('keyword') + ); +} + +interface Props { + data: DataPublicPluginStart; + getValidationError: (key: string) => string | null; + ruleParams: GeoContainmentAlertParams; + setDataViewId: (id: string) => void; + setDataViewTitle: (title: string) => void; + setGeoField: (fieldName: string) => void; + setNameField: (fieldName: string | undefined) => void; + setQuery: (query: Query) => void; + unifiedSearch: UnifiedSearchPublicPluginStart; +} + +export const BoundaryForm = (props: Props) => { + const [isLoading, setIsLoading] = useState(false); + const [dataView, setDataView] = useState(); + const [dataViewNotFound, setDataViewNotFound] = useState(false); + const [geoFields, setGeoFields] = useState([]); + const [nameFields, setNameFields] = useState([]); + + useEffect(() => { + if (!props.ruleParams.boundaryIndexId || props.ruleParams.boundaryIndexId === dataView?.id) { + return; + } + + let ignore = false; + setIsLoading(true); + setDataViewNotFound(false); + props.data.indexPatterns + .get(props.ruleParams.boundaryIndexId) + .then((nextDataView) => { + if (!ignore) { + setDataView(nextDataView); + setGeoFields(getGeoFields(nextDataView.fields)); + setNameFields(getNameFields(nextDataView.fields)); + setIsLoading(false); + } + }) + .catch(() => { + if (!ignore) { + setDataViewNotFound(true); + setIsLoading(false); + } + }); + + return () => { + ignore = true; + }; + }, [props.ruleParams.boundaryIndexId, dataView?.id, props.data.indexPatterns]); + + function getDataViewError() { + const validationError = props.getValidationError('boundaryIndexTitle'); + if (validationError) { + return validationError; + } + + if (dataView && geoFields.length === 0) { + return i18n.translate('xpack.stackAlerts.geoContainment.noGeoFieldInIndexPattern.message', { + defaultMessage: + 'Data view does not contain geospatial fields. Must have one of type: {geoFieldTypes}.', + values: { + geoFieldTypes: BOUNDARY_GEO_FIELD_TYPES.join(', '), + }, + }); + } + + if (dataViewNotFound) { + return i18n.translate('xpack.stackAlerts.geoContainment.dataViewNotFound', { + defaultMessage: `Unable to find data view '{id}'`, + values: { id: props.ruleParams.indexId }, + }); + } + + return null; + } + + const dataViewError = getDataViewError(); + const geoFieldError = props.getValidationError('boundaryGeoField'); + + return ( + + +
    + +
    +
    + + + + + + { + if (!nextDataView.id) { + return; + } + props.setDataViewId(nextDataView.id); + props.setDataViewTitle(nextDataView.title); + + const nextGeoFields = getGeoFields(nextDataView.fields); + if (nextGeoFields.length) { + props.setGeoField(nextGeoFields[0].name); + } else if ('boundaryGeoField' in props.ruleParams) { + props.setGeoField(''); + } + + // do not attempt to auto select name field + // its optional plus there can be many matches so auto selecting the correct field is improbable + if ('boundaryNameField' in props.ruleParams) { + props.setNameField(undefined); + } + }} + unifiedSearch={props.unifiedSearch} + /> + + + {props.ruleParams.boundaryIndexId && ( + <> + + { + if (fieldName) { + props.setGeoField(fieldName); + } + }} + fields={geoFields} + /> + + + + { + if (fieldName) { + props.setNameField(fieldName); + } + }} + fields={nameFields} + /> + + + + { + props.setQuery(query); + }} + query={props.ruleParams.boundaryIndexQuery} + /> + + + )} + +
    + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/data_view_select.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/data_view_select.tsx new file mode 100644 index 0000000000000..5b162a1fb5ff0 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/data_view_select.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import { i18n } from '@kbn/i18n'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; + +interface Props { + data: DataPublicPluginStart; + dataViewId?: string; + isInvalid: boolean; + onChange: (dataview: DataView) => void; + unifiedSearch: UnifiedSearchPublicPluginStart; +} + +export const DataViewSelect = (props: Props) => { + const [isLoading, setIsLoading] = useState(false); + const isMounted = useMountedState(); + + return ( + { + if (!dataViewId) { + return; + } + try { + setIsLoading(true); + const dataView = await props.data.indexPatterns.get(dataViewId); + if (isMounted()) { + props.onChange(dataView); + setIsLoading(false); + } + } catch (error) { + // ignore indexPatterns.get error, + // if data view does not exist, select will not update rule params + if (isMounted()) { + setIsLoading(false); + } + } + }} + placeholder={i18n.translate('xpack.stackAlerts.geoContainment.dataViewSelectPlaceholder', { + defaultMessage: 'Select data view', + })} + /> + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.test.tsx new file mode 100644 index 0000000000000..da0f52897dd42 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { EntityForm } from './entity_form'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { GeoContainmentAlertParams } from '../types'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; + +jest.mock('./query_input', () => { + return { + QueryInput: () =>
    mock query input
    , + }; +}); + +test('should not call prop callbacks on render', async () => { + const DATA_VIEW_TITLE = 'my-entities*'; + const DATA_VIEW_ID = '1234'; + const mockDataView = { + id: DATA_VIEW_ID, + fields: [ + { + name: 'location', + type: 'geo_point', + }, + ], + title: DATA_VIEW_TITLE, + }; + const props = { + data: { + indexPatterns: { + get: async () => mockDataView, + }, + } as unknown as DataPublicPluginStart, + getValidationError: () => null, + ruleParams: { + index: DATA_VIEW_TITLE, + indexId: DATA_VIEW_ID, + geoField: 'location', + entity: 'entity_id', + dateField: 'time', + indexQuery: { + query: 'population > 1000', + language: 'kuery', + }, + } as unknown as GeoContainmentAlertParams, + setDataViewId: jest.fn(), + setDataViewTitle: jest.fn(), + setDateField: jest.fn(), + setEntityField: jest.fn(), + setGeoField: jest.fn(), + setQuery: jest.fn(), + unifiedSearch: { + ui: { + IndexPatternSelect: () => { + return '
    mock IndexPatternSelect
    '; + }, + }, + } as unknown as UnifiedSearchPublicPluginStart, + }; + + const wrapper = mountWithIntl(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + // Assert that geospatial dataView fields are loaded + // to ensure test is properly awaiting async useEffect + let geoFieldsLoaded = false; + wrapper.findWhere((n) => { + if ( + n.name() === 'SingleFieldSelect' && + n.props().value === 'location' && + n.props().fields.length === 1 + ) { + geoFieldsLoaded = true; + } + return false; + }); + expect(geoFieldsLoaded).toBe(true); + + expect(props.setDataViewId).not.toHaveBeenCalled(); + expect(props.setDataViewTitle).not.toHaveBeenCalled(); + expect(props.setDateField).not.toHaveBeenCalled(); + expect(props.setEntityField).not.toHaveBeenCalled(); + expect(props.setGeoField).not.toHaveBeenCalled(); + expect(props.setQuery).not.toHaveBeenCalled(); +}); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx new file mode 100644 index 0000000000000..3923b494fa045 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/entity_form.tsx @@ -0,0 +1,277 @@ +/* + * Copyright 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, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFormRow, EuiPanel, EuiSkeletonText, EuiSpacer, EuiTitle } from '@elastic/eui'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { Query } from '@kbn/es-query'; +import type { GeoContainmentAlertParams } from '../types'; +import { DataViewSelect } from './data_view_select'; +import { SingleFieldSelect } from './single_field_select'; +import { QueryInput } from './query_input'; + +export const ENTITY_GEO_FIELD_TYPES = ['geo_point', 'geo_shape']; + +function getDateFields(fields: DataViewField[]) { + return fields.filter((field: DataViewField) => field.type === 'date'); +} + +function getEntityFields(fields: DataViewField[]) { + return fields.filter( + (field: DataViewField) => + field.aggregatable && + ['string', 'number', 'ip'].includes(field.type) && + !field.name.startsWith('_') + ); +} + +function getGeoFields(fields: DataViewField[]) { + return fields.filter((field: DataViewField) => ENTITY_GEO_FIELD_TYPES.includes(field.type)); +} + +interface Props { + data: DataPublicPluginStart; + getValidationError: (key: string) => string | null; + ruleParams: GeoContainmentAlertParams; + setDataViewId: (id: string) => void; + setDataViewTitle: (title: string) => void; + setDateField: (fieldName: string) => void; + setEntityField: (fieldName: string) => void; + setGeoField: (fieldName: string) => void; + setQuery: (query: Query) => void; + unifiedSearch: UnifiedSearchPublicPluginStart; +} + +export const EntityForm = (props: Props) => { + const [isLoading, setIsLoading] = useState(false); + const [dataView, setDataView] = useState(); + const [dataViewNotFound, setDataViewNotFound] = useState(false); + const [dateFields, setDateFields] = useState([]); + const [entityFields, setEntityFields] = useState([]); + const [geoFields, setGeoFields] = useState([]); + + useEffect(() => { + if (!props.ruleParams.indexId || props.ruleParams.indexId === dataView?.id) { + return; + } + + let ignore = false; + setIsLoading(true); + setDataViewNotFound(false); + props.data.indexPatterns + .get(props.ruleParams.indexId) + .then((nextDataView) => { + if (!ignore) { + setDataView(nextDataView); + setDateFields(getDateFields(nextDataView.fields)); + setEntityFields(getEntityFields(nextDataView.fields)); + setGeoFields(getGeoFields(nextDataView.fields)); + setIsLoading(false); + } + }) + .catch(() => { + if (!ignore) { + setDataViewNotFound(true); + setIsLoading(false); + } + }); + + return () => { + ignore = true; + }; + }, [props.ruleParams.indexId, dataView?.id, props.data.indexPatterns]); + + function getDataViewError() { + const validationError = props.getValidationError('index'); + if (validationError) { + return validationError; + } + + if (dataView && dateFields.length === 0) { + return i18n.translate('xpack.stackAlerts.geoContainment.noDateFieldInIndexPattern.message', { + defaultMessage: 'Data view does not contain date fields.', + }); + } + + if (dataView && geoFields.length === 0) { + return i18n.translate('xpack.stackAlerts.geoContainment.noGeoFieldInIndexPattern.message', { + defaultMessage: + 'Data view does not contain geospatial fields. Must have one of type: {geoFieldTypes}.', + values: { + geoFieldTypes: ENTITY_GEO_FIELD_TYPES.join(', '), + }, + }); + } + + if (dataViewNotFound) { + return i18n.translate('xpack.stackAlerts.geoContainment.dataViewNotFound', { + defaultMessage: `Unable to find data view '{id}'`, + values: { id: props.ruleParams.indexId }, + }); + } + + return null; + } + + const dataViewError = getDataViewError(); + const dateFieldError = props.getValidationError('dateField'); + const geoFieldError = props.getValidationError('geoField'); + const entityFieldError = props.getValidationError('entity'); + + return ( + + +
    + +
    +
    + + + + + + { + if (!nextDataView.id) { + return; + } + props.setDataViewId(nextDataView.id); + props.setDataViewTitle(nextDataView.title); + + const nextDateFields = getDateFields(nextDataView.fields); + if (nextDateFields.length) { + props.setDateField(nextDateFields[0].name); + } else if ('dateField' in props.ruleParams) { + props.setDateField(''); + } + + // do not attempt to auto select entity field + // there can be many matches so auto selecting the correct field is improbable + if ('entity' in props.ruleParams) { + props.setEntityField(''); + } + + const nextGeoFields = getGeoFields(nextDataView.fields); + if (nextGeoFields.length) { + props.setGeoField(nextGeoFields[0].name); + } else if ('geoField' in props.ruleParams) { + props.setGeoField(''); + } + }} + unifiedSearch={props.unifiedSearch} + /> + + + {props.ruleParams.indexId && ( + <> + + { + if (fieldName) { + props.setDateField(fieldName); + } + }} + fields={dateFields} + /> + + + + { + if (fieldName) { + props.setGeoField(fieldName); + } + }} + fields={geoFields} + /> + + + + { + if (fieldName) { + props.setEntityField(fieldName); + } + }} + fields={entityFields} + /> + + + + { + props.setQuery(query); + }} + query={props.ruleParams.indexQuery} + /> + + + )} + +
    + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/index.ts b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/index.ts new file mode 100644 index 0000000000000..dba9cea07ed8d --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RuleForm } from './rule_form'; + +// eslint-disable-next-line import/no-default-export +export default RuleForm; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx new file mode 100644 index 0000000000000..c07ce2ec8e090 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { Query } from '@kbn/es-query'; +import { fromKueryExpression, luceneStringToDsl } from '@kbn/es-query'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; +import type { CoreStart } from '@kbn/core/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { STACK_ALERTS_FEATURE_ID } from '../../../../common/constants'; + +function validateQuery(query: Query) { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + query.language === 'kuery' ? fromKueryExpression(query.query) : luceneStringToDsl(query.query); + } catch (err) { + return false; + } + return true; +} + +interface Props { + dataView?: DataView; + onChange: (query: Query) => void; + query?: Query; +} + +export const QueryInput = (props: Props) => { + const { + data, + dataViews, + docLinks, + http, + notifications, + storage, + uiSettings, + unifiedSearch, + usageCollection, + } = useKibana<{ + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + docLinks: DocLinksStart; + http: HttpSetup; + notifications: CoreStart['notifications']; + uiSettings: IUiSettingsClient; + storage: IStorageWrapper; + unifiedSearch: UnifiedSearchPublicPluginStart; + usageCollection: UsageCollectionStart; + }>().services; + + const [localQuery, setLocalQuery] = useState( + props.query || { + query: '', + language: 'kuery', + } + ); + + return ( + { + if (query.language) { + setLocalQuery(query); + if (validateQuery(query)) { + props.onChange(query); + } + } + }} + appName={STACK_ALERTS_FEATURE_ID} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + dataViews, + storage, + usageCollection, + }} + /> + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/rule_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/rule_form.tsx new file mode 100644 index 0000000000000..52e6baae16536 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/rule_form.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import type { Query } from '@kbn/es-query'; +import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import type { GeoContainmentAlertParams } from '../types'; +import { BoundaryForm } from './boundary_form'; +import { EntityForm } from './entity_form'; + +export const RuleForm: React.FunctionComponent< + RuleTypeParamsExpressionProps +> = (props) => { + function getValidationError(key: string) { + return props.errors[key]?.length > 0 && key in props.ruleParams + ? (props.errors[key] as string[])[0] + : null; + } + + return ( + <> + props.setRuleParams('indexId', id)} + setDataViewTitle={(title: string) => props.setRuleParams('index', title)} + setDateField={(fieldName: string) => props.setRuleParams('dateField', fieldName)} + setEntityField={(fieldName: string) => props.setRuleParams('entity', fieldName)} + setGeoField={(fieldName: string) => props.setRuleParams('geoField', fieldName)} + setQuery={(query: Query) => props.setRuleParams('indexQuery', query)} + unifiedSearch={props.unifiedSearch} + /> + + + + { + props.setRuleParams('boundaryIndexId', id); + // TODO remove unused param 'boundaryType' + props.setRuleParams('boundaryType', 'entireIndex'); + }} + setDataViewTitle={(title: string) => props.setRuleParams('boundaryIndexTitle', title)} + setGeoField={(fieldName: string) => props.setRuleParams('boundaryGeoField', fieldName)} + setNameField={(fieldName: string | undefined) => + props.setRuleParams('boundaryNameField', fieldName) + } + setQuery={(query: Query) => props.setRuleParams('boundaryIndexQuery', query)} + unifiedSearch={props.unifiedSearch} + /> + + + + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/single_field_select.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/single_field_select.tsx similarity index 92% rename from x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/single_field_select.tsx rename to x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/single_field_select.tsx index 7783c943301f3..71ef2d301a9c7 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/query_builder/util_components/single_field_select.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/single_field_select.tsx @@ -33,13 +33,14 @@ function fieldsToOptions(fields?: DataViewField[]): Array void; fields: DataViewField[]; } -export function SingleFieldSelect({ placeholder, value, onChange, fields }: Props) { +export function SingleFieldSelect({ isInvalid, placeholder, value, onChange, fields }: Props) { function renderOption( option: EuiComboBoxOptionOption, searchValue: string, @@ -71,6 +72,7 @@ export function SingleFieldSelect({ placeholder, value, onChange, fields }: Prop return ( ); } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/types.ts b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/types.ts index b34dd9ec4f8d2..4eb717a23e437 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/types.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/types.ts @@ -22,6 +22,3 @@ export interface GeoContainmentAlertParams extends RuleTypeParams { indexQuery?: Query; boundaryIndexQuery?: Query; } - -export const ES_GEO_FIELD_TYPES = ['geo_point', 'geo_shape']; -export const ES_GEO_SHAPE_TYPES = ['geo_shape']; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.test.ts b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.test.ts index 5312f5018d933..30aef903c6ef5 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.test.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.test.ts @@ -105,7 +105,7 @@ describe('expression params validation', () => { }; expect(validateExpression(initialParams).errors.boundaryIndexTitle.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.boundaryIndexTitle[0]).toBe( - 'Boundary data view title is required.' + 'Boundary data view is required.' ); }); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.ts b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.ts index 71fd748ea5df1..04fb961ac559b 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/validation.ts @@ -69,7 +69,7 @@ export const validateExpression = (alertParams: GeoContainmentAlertParams): Vali if (!boundaryIndexTitle) { errors.boundaryIndexTitle.push( i18n.translate('xpack.stackAlerts.geoContainment.error.requiredBoundaryIndexTitleText', { - defaultMessage: 'Boundary data view title is required.', + defaultMessage: 'Boundary data view is required.', }) ); } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts b/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts index 83679f34fbb53..4e539d1f41784 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts @@ -34,7 +34,7 @@ export interface IndexThresholdRuleParams extends RuleTypeParams { aggField?: string; groupBy?: string; termSize?: number; - termField?: string; + termField?: string | string[]; thresholdComparator?: string; threshold: number[]; timeWindowSize: number; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/constants.ts b/x-pack/plugins/stack_alerts/server/rule_types/constants.ts index 68ab6698f2d31..a5cfb6dc5364a 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/constants.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/constants.ts @@ -4,5 +4,25 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { IRuleTypeAlerts } from '@kbn/alerting-plugin/server'; +import { StackAlert } from '@kbn/alerts-as-data-utils'; +import { ALERT_EVALUATION_VALUE } from '@kbn/rule-data-utils'; +import { ALERT_NAMESPACE } from '@kbn/rule-data-utils'; export const STACK_AAD_INDEX_NAME = 'stack'; + +export const ALERT_TITLE = `${ALERT_NAMESPACE}.title` as const; +// kibana.alert.evaluation.conditions - human readable string that shows the conditions set by the user +export const ALERT_EVALUATION_CONDITIONS = `${ALERT_NAMESPACE}.evaluation.conditions` as const; + +export const STACK_ALERTS_AAD_CONFIG: IRuleTypeAlerts = { + context: STACK_AAD_INDEX_NAME, + mappings: { + fieldMap: { + [ALERT_TITLE]: { type: 'keyword', array: false, required: false }, + [ALERT_EVALUATION_CONDITIONS]: { type: 'keyword', array: false, required: false }, + [ALERT_EVALUATION_VALUE]: { type: 'keyword', array: false, required: false }, + }, + }, + shouldWrite: true, +}; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.test.ts index 9936e63aa6ed1..f9f4ab51b660f 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.test.ts @@ -167,6 +167,7 @@ describe('getContextConditionsDescription', () => { comparator: Comparator.GT, threshold: [10], aggType: 'count', + searchType: 'esQuery', }); expect(result).toBe(`Number of matching documents is greater than 10`); }); @@ -177,6 +178,7 @@ describe('getContextConditionsDescription', () => { threshold: [10], aggType: 'count', isRecovered: true, + searchType: 'esQuery', }); expect(result).toBe(`Number of matching documents is NOT greater than 10`); }); @@ -187,6 +189,7 @@ describe('getContextConditionsDescription', () => { threshold: [10, 20], aggType: 'count', isRecovered: true, + searchType: 'esQuery', }); expect(result).toBe(`Number of matching documents is NOT between 10 and 20`); }); @@ -197,6 +200,7 @@ describe('getContextConditionsDescription', () => { threshold: [10], aggType: 'count', group: 'host-1', + searchType: 'esQuery', }); expect(result).toBe(`Number of matching documents for group "host-1" is greater than 10`); }); @@ -208,6 +212,7 @@ describe('getContextConditionsDescription', () => { aggType: 'count', isRecovered: true, group: 'host-1', + searchType: 'esQuery', }); expect(result).toBe(`Number of matching documents for group "host-1" is NOT greater than 10`); }); @@ -218,6 +223,7 @@ describe('getContextConditionsDescription', () => { threshold: [10], aggType: 'min', aggField: 'numericField', + searchType: 'esQuery', }); expect(result).toBe( `Number of matching documents where min of numericField is greater than 10` @@ -231,6 +237,7 @@ describe('getContextConditionsDescription', () => { aggType: 'min', aggField: 'numericField', isRecovered: true, + searchType: 'esQuery', }); expect(result).toBe( `Number of matching documents where min of numericField is NOT greater than 10` @@ -244,6 +251,7 @@ describe('getContextConditionsDescription', () => { group: 'host-1', aggType: 'max', aggField: 'numericField', + searchType: 'esQuery', }); expect(result).toBe( `Number of matching documents for group "host-1" where max of numericField is greater than 10` @@ -258,9 +266,31 @@ describe('getContextConditionsDescription', () => { group: 'host-1', aggType: 'max', aggField: 'numericField', + searchType: 'esQuery', }); expect(result).toBe( `Number of matching documents for group "host-1" where max of numericField is NOT greater than 10` ); }); + + it('should return conditions correctly for ESQL search type', () => { + const result = getContextConditionsDescription({ + comparator: Comparator.GT, + threshold: [0], + aggType: 'count', + searchType: 'esqlQuery', + }); + expect(result).toBe(`Query matched documents`); + }); + + it('should return conditions correctly ESQL search type when isRecovered is true', () => { + const result = getContextConditionsDescription({ + comparator: Comparator.GT, + threshold: [0], + aggType: 'count', + isRecovered: true, + searchType: 'esqlQuery', + }); + expect(result).toBe(`Query did NOT match documents`); + }); }); diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.ts index b84601314fbdd..5de1fc15c1120 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/action_context.ts @@ -11,6 +11,7 @@ import { AlertInstanceContext } from '@kbn/alerting-plugin/server'; import { EsQueryRuleParams } from './rule_type_params'; import { Comparator } from '../../../common/comparator_types'; import { getHumanReadableComparator } from '../../../common'; +import { isEsqlQueryRule } from './util'; // rule type context provided to actions export interface ActionContext extends EsQueryRuleActionContext { @@ -79,6 +80,7 @@ export function addMessages({ } interface GetContextConditionsDescriptionOpts { + searchType: 'searchSource' | 'esQuery' | 'esqlQuery'; comparator: Comparator; threshold: number[]; aggType: string; @@ -88,6 +90,7 @@ interface GetContextConditionsDescriptionOpts { } export function getContextConditionsDescription({ + searchType, comparator, threshold, aggType, @@ -95,15 +98,23 @@ export function getContextConditionsDescription({ isRecovered = false, group, }: GetContextConditionsDescriptionOpts) { - return i18n.translate('xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription', { - defaultMessage: - 'Number of matching documents{groupCondition}{aggCondition} is {negation}{thresholdComparator} {threshold}', - values: { - aggCondition: aggType === 'count' ? '' : ` where ${aggType} of ${aggField}`, - groupCondition: group ? ` for group "${group}"` : '', - thresholdComparator: getHumanReadableComparator(comparator), - threshold: threshold.join(' and '), - negation: isRecovered ? 'NOT ' : '', - }, - }); + return isEsqlQueryRule(searchType) + ? i18n.translate('xpack.stackAlerts.esQuery.esqlAlertTypeContextConditionsDescription', { + defaultMessage: 'Query{negation} documents{groupCondition}', + values: { + groupCondition: group ? ` for group "${group}"` : '', + negation: isRecovered ? ' did NOT match' : ' matched', + }, + }) + : i18n.translate('xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription', { + defaultMessage: + 'Number of matching documents{groupCondition}{aggCondition} is {negation}{thresholdComparator} {threshold}', + values: { + aggCondition: aggType === 'count' ? '' : ` where ${aggType} of ${aggField}`, + groupCondition: group ? ` for group "${group}"` : '', + thresholdComparator: getHumanReadableComparator(comparator), + threshold: threshold.join(' and '), + negation: isRecovered ? 'NOT ' : '', + }, + }); } diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.test.ts index c33457fab43b1..43f84acf65e78 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.test.ts @@ -17,6 +17,7 @@ import { ISearchStartSearchSource } from '@kbn/data-plugin/common'; import { EsQueryRuleParams } from './rule_type_params'; import { FetchEsQueryOpts } from './lib/fetch_es_query'; import { FetchSearchSourceQueryOpts } from './lib/fetch_search_source_query'; +import { FetchEsqlQueryOpts } from './lib/fetch_esql_query'; const logger = loggerMock.create(); const scopedClusterClientMock = elasticsearchServiceMock.createScopedClusterClient(); @@ -44,6 +45,10 @@ jest.mock('./lib/fetch_search_source_query', () => ({ fetchSearchSourceQuery: (...args: [FetchSearchSourceQueryOpts]) => mockFetchSearchSourceQuery(...args), })); +const mockFetchEsqlQuery = jest.fn(); +jest.mock('./lib/fetch_esql_query', () => ({ + fetchEsqlQuery: (...args: [FetchEsqlQueryOpts]) => mockFetchEsqlQuery(...args), +})); const mockGetRecoveredAlerts = jest.fn().mockReturnValue([]); const mockSetLimitReached = jest.fn(); @@ -86,6 +91,8 @@ describe('es_query executor', () => { excludeHitsFromPreviousRun: true, aggType: 'count', groupBy: 'all', + searchConfiguration: {}, + esqlQuery: { esql: 'test-query' }, }; describe('executor', () => { @@ -171,12 +178,12 @@ describe('es_query executor', () => { }); await executor(coreMock, { ...defaultExecutorOptions, - params: { ...defaultProps, searchConfiguration: {}, searchType: 'searchSource' }, + params: { ...defaultProps, searchType: 'searchSource' }, }); expect(mockFetchSearchSourceQuery).toHaveBeenCalledWith({ ruleId: 'test-rule-id', alertLimit: 1000, - params: { ...defaultProps, searchConfiguration: {}, searchType: 'searchSource' }, + params: { ...defaultProps, searchType: 'searchSource' }, latestTimestamp: undefined, services: { searchSourceClient: searchSourceClientMock, @@ -188,6 +195,42 @@ describe('es_query executor', () => { expect(mockFetchEsQuery).not.toHaveBeenCalled(); }); + it('should call fetchEsqlQuery if searchType is esqlQuery', async () => { + mockFetchEsqlQuery.mockResolvedValueOnce({ + parsedResults: { + results: [ + { + group: 'all documents', + count: 491, + hits: [], + }, + ], + truncated: false, + }, + dateStart: new Date().toISOString(), + dateEnd: new Date().toISOString(), + }); + await executor(coreMock, { + ...defaultExecutorOptions, + params: { ...defaultProps, searchType: 'esqlQuery' }, + }); + expect(mockFetchEsqlQuery).toHaveBeenCalledWith({ + ruleId: 'test-rule-id', + alertLimit: 1000, + params: { ...defaultProps, searchType: 'esqlQuery' }, + services: { + scopedClusterClient: scopedClusterClientMock, + logger, + share: undefined, + dataViews: undefined, + }, + spacePrefix: '', + publicBaseUrl: 'https://localhost:5601', + }); + expect(mockFetchEsQuery).not.toHaveBeenCalled(); + expect(mockFetchSearchSourceQuery).not.toHaveBeenCalled(); + }); + it('should not create alert if compare function returns false for ungrouped alert', async () => { mockFetchEsQuery.mockResolvedValueOnce({ parsedResults: { @@ -455,6 +498,78 @@ describe('es_query executor', () => { expect(mockSetLimitReached).toHaveBeenCalledWith(false); }); + it('should create alert if there are hits for ESQL alert', async () => { + mockFetchEsqlQuery.mockResolvedValueOnce({ + parsedResults: { + results: [ + { + group: 'all documents', + count: 198, + hits: [], + }, + ], + truncated: false, + }, + dateStart: new Date().toISOString(), + dateEnd: new Date().toISOString(), + link: 'https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id', + }); + await executor(coreMock, { + ...defaultExecutorOptions, + params: { + ...defaultProps, + searchType: 'esqlQuery', + threshold: [0], + thresholdComparator: '>=' as Comparator, + }, + }); + + expect(mockReport).toHaveBeenCalledTimes(1); + expect(mockReport).toHaveBeenNthCalledWith(1, { + actionGroup: 'query matched', + context: { + conditions: 'Query matched documents', + date: new Date(mockNow).toISOString(), + hits: [], + link: 'https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id', + message: `rule 'test-rule-name' is active: + +- Value: 198 +- Conditions Met: Query matched documents over 5m +- Timestamp: ${new Date(mockNow).toISOString()} +- Link: https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id`, + title: "rule 'test-rule-name' matched query", + value: 198, + }, + id: 'query matched', + payload: { + kibana: { + alert: { + evaluation: { + conditions: 'Query matched documents', + value: 198, + }, + reason: `rule 'test-rule-name' is active: + +- Value: 198 +- Conditions Met: Query matched documents over 5m +- Timestamp: ${new Date(mockNow).toISOString()} +- Link: https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id`, + title: "rule 'test-rule-name' matched query", + url: 'https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id', + }, + }, + }, + state: { + dateEnd: new Date(mockNow).toISOString(), + dateStart: new Date(mockNow).toISOString(), + latestTimestamp: undefined, + }, + }); + expect(mockSetLimitReached).toHaveBeenCalledTimes(1); + expect(mockSetLimitReached).toHaveBeenCalledWith(false); + }); + it('should set limit as reached if results are truncated', async () => { mockFetchEsQuery.mockResolvedValueOnce({ parsedResults: { @@ -670,6 +785,74 @@ describe('es_query executor', () => { - Value: 0 - Conditions Met: Number of matching documents for group \"host-2\" is NOT greater than or equal to 200 over 5m - Timestamp: ${new Date(mockNow).toISOString()} +- Link: https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id`, + title: "rule 'test-rule-name' recovered", + url: 'https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id', + }, + }, + }, + }); + expect(mockSetLimitReached).toHaveBeenCalledTimes(1); + expect(mockSetLimitReached).toHaveBeenCalledWith(false); + }); + + it('should correctly handle recovered alerts for ESQL alert', async () => { + mockGetRecoveredAlerts.mockReturnValueOnce([ + { + alert: { + getId: () => 'query matched', + }, + }, + ]); + mockFetchEsqlQuery.mockResolvedValueOnce({ + parsedResults: { + results: [], + truncated: false, + }, + dateStart: new Date().toISOString(), + dateEnd: new Date().toISOString(), + link: 'https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id', + }); + await executor(coreMock, { + ...defaultExecutorOptions, + params: { + ...defaultProps, + searchType: 'esqlQuery', + threshold: [0], + thresholdComparator: '>=' as Comparator, + }, + }); + + expect(mockReport).not.toHaveBeenCalled(); + expect(mockSetAlertData).toHaveBeenCalledTimes(1); + expect(mockSetAlertData).toHaveBeenNthCalledWith(1, { + id: 'query matched', + context: { + conditions: 'Query did NOT match documents', + date: new Date(mockNow).toISOString(), + hits: [], + link: 'https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id', + message: `rule 'test-rule-name' is recovered: + +- Value: 0 +- Conditions Met: Query did NOT match documents over 5m +- Timestamp: ${new Date(mockNow).toISOString()} +- Link: https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id`, + title: "rule 'test-rule-name' recovered", + value: 0, + }, + payload: { + kibana: { + alert: { + evaluation: { + conditions: 'Query did NOT match documents', + value: 0, + }, + reason: `rule 'test-rule-name' is recovered: + +- Value: 0 +- Conditions Met: Query did NOT match documents over 5m +- Timestamp: ${new Date(mockNow).toISOString()} - Link: https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id`, title: "rule 'test-rule-name' recovered", url: 'https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/test-rule-id', diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts index ae8ae99ba26a2..c048bfd36cefb 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/executor.ts @@ -12,22 +12,29 @@ import { isGroupAggregation, UngroupedGroupId } from '@kbn/triggers-actions-ui-p import { ALERT_EVALUATION_VALUE, ALERT_REASON, ALERT_URL } from '@kbn/rule-data-utils'; import { expandFlattenedAlert } from '@kbn/alerting-plugin/server/alerts_client/lib'; -import { ALERT_TITLE, ALERT_EVALUATION_CONDITIONS } from './fields'; import { ComparatorFns } from '../../../common'; import { addMessages, EsQueryRuleActionContext, getContextConditionsDescription, } from './action_context'; -import { ExecutorOptions, OnlyEsQueryRuleParams, OnlySearchSourceRuleParams } from './types'; +import { + ExecutorOptions, + OnlyEsQueryRuleParams, + OnlySearchSourceRuleParams, + OnlyEsqlQueryRuleParams, +} from './types'; import { ActionGroupId, ConditionMetAlertInstanceId } from './constants'; import { fetchEsQuery } from './lib/fetch_es_query'; import { EsQueryRuleParams } from './rule_type_params'; import { fetchSearchSourceQuery } from './lib/fetch_search_source_query'; -import { isEsQueryRule } from './util'; +import { isEsqlQueryRule, isSearchSourceRule } from './util'; +import { fetchEsqlQuery } from './lib/fetch_esql_query'; +import { ALERT_EVALUATION_CONDITIONS, ALERT_TITLE } from '..'; export async function executor(core: CoreSetup, options: ExecutorOptions) { - const esQueryRule = isEsQueryRule(options.params.searchType); + const searchSourceRule = isSearchSourceRule(options.params.searchType); + const esqlQueryRule = isEsqlQueryRule(options.params.searchType); const { rule: { id: ruleId, name }, services, @@ -54,34 +61,47 @@ export async function executor(core: CoreSetup, options: ExecutorOptions = {}; for (const result of parsedResults.results) { const alertId = result.group; @@ -105,6 +125,7 @@ export async function executor(core: CoreSetup, options: ExecutorOptions { + const id = 'test-id'; + const { + type, + version, + attributes: { timeFieldName, fields, title }, + } = stubbedSavedObjectIndexPattern(id); + + return new DataView({ + spec: { id, type, version, timeFieldName, fields: JSON.parse(fields), title }, + fieldFormats: fieldFormatsMock, + shortDotsEnable: false, + metaFields: ['_id', '_type', '_score'], + }); +}; + +const defaultParams: OnlyEsqlQueryRuleParams = { + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + esqlQuery: { esql: 'from test' }, + excludeHitsFromPreviousRun: false, + searchType: 'esqlQuery', + aggType: 'count', + groupBy: 'all', + timeField: 'time', +}; + +describe('fetchEsqlQuery', () => { + describe('getEsqlQuery', () => { + const dataViewMock = createDataView(); + afterAll(() => { + jest.resetAllMocks(); + }); + + const fakeNow = new Date('2020-02-09T23:15:41.941Z'); + + beforeAll(() => { + jest.resetAllMocks(); + global.Date.now = jest.fn(() => fakeNow.getTime()); + }); + + it('should generate the correct query', async () => { + const params = defaultParams; + const { query, dateStart, dateEnd } = getEsqlQuery(dataViewMock, params, undefined); + + expect(query).toMatchInlineSnapshot(` + Object { + "filter": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "time": Object { + "format": "strict_date_optional_time", + "gt": "2020-02-09T23:10:41.941Z", + "lte": "2020-02-09T23:15:41.941Z", + }, + }, + }, + ], + }, + }, + "query": "from test", + } + `); + expect(dateStart).toMatch('2020-02-09T23:10:41.941Z'); + expect(dateEnd).toMatch('2020-02-09T23:15:41.941Z'); + }); + + it('should generate the correct query with the alertLimit', async () => { + const params = defaultParams; + const { query, dateStart, dateEnd } = getEsqlQuery(dataViewMock, params, 100); + + expect(query).toMatchInlineSnapshot(` + Object { + "filter": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "time": Object { + "format": "strict_date_optional_time", + "gt": "2020-02-09T23:10:41.941Z", + "lte": "2020-02-09T23:15:41.941Z", + }, + }, + }, + ], + }, + }, + "query": "from test | limit 100", + } + `); + expect(dateStart).toMatch('2020-02-09T23:10:41.941Z'); + expect(dateEnd).toMatch('2020-02-09T23:15:41.941Z'); + }); + }); +}); diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts new file mode 100644 index 0000000000000..ae729e51703d4 --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts @@ -0,0 +1,111 @@ +/* + * Copyright 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 { DataView, DataViewsContract, getTime } from '@kbn/data-plugin/common'; +import { parseAggregationResults } from '@kbn/triggers-actions-ui-plugin/common'; +import { SharePluginStart } from '@kbn/share-plugin/server'; +import { IScopedClusterClient, Logger } from '@kbn/core/server'; +import { OnlyEsqlQueryRuleParams } from '../types'; +import { EsqlTable, toEsQueryHits } from '../../../../common'; + +export interface FetchEsqlQueryOpts { + ruleId: string; + alertLimit: number | undefined; + params: OnlyEsqlQueryRuleParams; + spacePrefix: string; + publicBaseUrl: string; + services: { + logger: Logger; + scopedClusterClient: IScopedClusterClient; + share: SharePluginStart; + dataViews: DataViewsContract; + }; +} + +export async function fetchEsqlQuery({ + ruleId, + alertLimit, + params, + services, + spacePrefix, + publicBaseUrl, +}: FetchEsqlQueryOpts) { + const { logger, scopedClusterClient, dataViews } = services; + const esClient = scopedClusterClient.asCurrentUser; + const dataView = await dataViews.create({ + timeFieldName: params.timeField, + }); + + const { query, dateStart, dateEnd } = getEsqlQuery(dataView, params, alertLimit); + + logger.debug(`ES|QL query rule (${ruleId}) query: ${JSON.stringify(query)}`); + + const response = await esClient.transport.request({ + method: 'POST', + path: '/_esql', + body: query, + }); + + const link = `${publicBaseUrl}${spacePrefix}/app/management/insightsAndAlerting/triggersActions/rule/${ruleId}`; + + return { + link, + numMatches: Number(response.values.length), + parsedResults: parseAggregationResults({ + isCountAgg: true, + isGroupAgg: false, + esResult: { + took: 0, + timed_out: false, + _shards: { failed: 0, successful: 0, total: 0 }, + hits: toEsQueryHits(response), + }, + resultLimit: alertLimit, + }), + dateStart, + dateEnd, + }; +} + +export const getEsqlQuery = ( + dataView: DataView, + params: OnlyEsqlQueryRuleParams, + alertLimit: number | undefined +) => { + const timeRange = { + from: `now-${params.timeWindowSize}${params.timeWindowUnit}`, + to: 'now', + }; + const timerangeFilter = getTime(dataView, timeRange); + const dateStart = timerangeFilter?.query.range[params.timeField].gte; + const dateEnd = timerangeFilter?.query.range[params.timeField].lte; + const rangeFilter: unknown[] = [ + { + range: { + [params.timeField]: { + lte: dateEnd, + gt: dateStart, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const query = { + query: alertLimit ? `${params.esqlQuery.esql} | limit ${alertLimit}` : params.esqlQuery.esql, + filter: { + bool: { + filter: rangeFilter, + }, + }, + }; + return { + query, + dateStart, + dateEnd, + }; +}; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts index 199aa47240c54..8c63e0e0ac7f0 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts @@ -19,7 +19,11 @@ import type { ESSearchResponse, ESSearchRequest } from '@kbn/es-types'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { coreMock } from '@kbn/core/server/mocks'; import { ActionGroupId, ConditionMetAlertInstanceId } from './constants'; -import { OnlyEsQueryRuleParams, OnlySearchSourceRuleParams } from './types'; +import { + OnlyEsqlQueryRuleParams, + OnlyEsQueryRuleParams, + OnlySearchSourceRuleParams, +} from './types'; import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import { Comparator } from '../../../common/comparator_types'; import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings'; @@ -108,6 +112,10 @@ describe('ruleType', () => { "description": "The indices the rule queries.", "name": "index", }, + Object { + "description": "ES|QL query field used to fetch data from Elasticsearch.", + "name": "esqlQuery", + }, ], } `); @@ -596,10 +604,6 @@ describe('ruleType', () => { groupBy: 'all', }; - afterAll(() => { - jest.resetAllMocks(); - }); - it('validator succeeds with valid search source params', async () => { expect(ruleType.validate.params.validate(defaultParams)).toBeTruthy(); }); @@ -710,6 +714,144 @@ describe('ruleType', () => { ); }); }); + + describe('ESQL query', () => { + const dataViewMock = { + id: 'test-id', + title: 'test-title', + timeFieldName: 'time-field', + fields: [ + { + name: 'message', + type: 'string', + displayName: 'message', + scripted: false, + filterable: false, + aggregatable: false, + }, + { + name: 'timestamp', + type: 'date', + displayName: 'timestamp', + scripted: false, + filterable: false, + aggregatable: false, + }, + ], + toSpec: () => { + return { id: 'test-id', title: 'test-title', timeFieldName: 'timestamp', fields: [] }; + }, + }; + const defaultParams: OnlyEsqlQueryRuleParams = { + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + esqlQuery: { esql: 'test' }, + timeField: 'timestamp', + searchType: 'esqlQuery', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'all', + }; + + it('validator succeeds with valid ESQL query params', async () => { + expect(ruleType.validate.params.validate(defaultParams)).toBeTruthy(); + }); + + it('validator fails with invalid ESQL query params - esQuery provided', async () => { + const paramsSchema = ruleType.validate.params; + const params: Partial> = { + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.LT, + threshold: [0], + esQuery: '', + searchType: 'esqlQuery', + aggType: 'count', + groupBy: 'all', + }; + + expect(() => paramsSchema.validate(params)).toThrowErrorMatchingInlineSnapshot( + `"[esQuery]: a value wasn't expected to be present"` + ); + }); + + it('rule executor handles no documents returned by ES', async () => { + const params = defaultParams; + const ruleServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); + + (ruleServices.dataViews.create as jest.Mock).mockResolvedValueOnce({ + ...dataViewMock.toSpec(), + toSpec: () => dataViewMock.toSpec(), + }); + + const searchResult = { + columns: [ + { name: 'timestamp', type: 'date' }, + { name: 'message', type: 'keyword' }, + ], + values: [], + }; + ruleServices.scopedClusterClient.asCurrentUser.transport.request.mockResolvedValueOnce( + searchResult + ); + + await invokeExecutor({ params, ruleServices }); + expect(ruleServices.alertsClient.report).not.toHaveBeenCalled(); + }); + + it('rule executor schedule actions when condition met', async () => { + const params = defaultParams; + const ruleServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); + + (ruleServices.dataViews.create as jest.Mock).mockResolvedValueOnce({ + ...dataViewMock.toSpec(), + toSpec: () => dataViewMock.toSpec(), + }); + + const searchResult = { + columns: [ + { name: 'timestamp', type: 'date' }, + { name: 'message', type: 'keyword' }, + ], + values: [ + ['timestamp', 'message'], + ['timestamp', 'message'], + ['timestamp', 'message'], + ], + }; + ruleServices.scopedClusterClient.asCurrentUser.transport.request.mockResolvedValueOnce( + searchResult + ); + + await invokeExecutor({ params, ruleServices }); + + expect(ruleServices.alertsClient.report).toHaveBeenCalledTimes(1); + + expect(ruleServices.alertsClient.report).toHaveBeenCalledWith( + expect.objectContaining({ + actionGroup: 'query matched', + id: 'query matched', + payload: expect.objectContaining({ + kibana: { + alert: { + url: expect.any(String), + reason: expect.any(String), + title: "rule 'rule-name' matched query", + evaluation: { + conditions: 'Query matched documents', + value: 3, + }, + }, + }, + }), + }) + ); + }); + }); }); function generateResults( @@ -756,7 +898,7 @@ async function invokeExecutor({ ruleServices, state, }: { - params: OnlySearchSourceRuleParams | OnlyEsQueryRuleParams; + params: OnlySearchSourceRuleParams | OnlyEsQueryRuleParams | OnlyEsqlQueryRuleParams; ruleServices: RuleExecutorServicesMock; state?: EsQueryRuleState; }) { diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts index 01d6ee1497b3c..fc70403b174d3 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts @@ -8,11 +8,8 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '@kbn/core/server'; import { extractReferences, injectReferences } from '@kbn/data-plugin/common'; -import { IRuleTypeAlerts } from '@kbn/alerting-plugin/server'; -import { ALERT_EVALUATION_VALUE } from '@kbn/rule-data-utils'; import { StackAlert } from '@kbn/alerts-as-data-utils'; -import { STACK_AAD_INDEX_NAME } from '..'; -import { ALERT_TITLE, ALERT_EVALUATION_CONDITIONS } from './fields'; +import { STACK_ALERTS_AAD_CONFIG } from '..'; import { RuleType } from '../../types'; import { ActionContext } from './action_context'; import { @@ -25,7 +22,7 @@ import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { ExecutorOptions } from './types'; import { ActionGroupId, ES_QUERY_ID } from './constants'; import { executor } from './executor'; -import { isEsQueryRule } from './util'; +import { isSearchSourceRule } from './util'; export function getRuleType( core: CoreSetup @@ -134,6 +131,13 @@ export function getRuleType( } ); + const actionVariableEsqlQueryLabel = i18n.translate( + 'xpack.stackAlerts.esQuery.actionVariableContextEsqlQueryLabel', + { + defaultMessage: 'ES|QL query field used to fetch data from Elasticsearch.', + } + ); + const actionVariableContextLinkLabel = i18n.translate( 'xpack.stackAlerts.esQuery.actionVariableContextLinkLabel', { @@ -142,18 +146,6 @@ export function getRuleType( } ); - const alerts: IRuleTypeAlerts = { - context: STACK_AAD_INDEX_NAME, - mappings: { - fieldMap: { - [ALERT_TITLE]: { type: 'keyword', array: false, required: false }, - [ALERT_EVALUATION_CONDITIONS]: { type: 'keyword', array: false, required: false }, - [ALERT_EVALUATION_VALUE]: { type: 'keyword', array: false, required: false }, - }, - }, - shouldWrite: true, - }; - return { id: ES_QUERY_ID, name: ruleTypeName, @@ -179,25 +171,27 @@ export function getRuleType( { name: 'searchConfiguration', description: actionVariableSearchConfigurationLabel }, { name: 'esQuery', description: actionVariableContextQueryLabel }, { name: 'index', description: actionVariableContextIndexLabel }, + { name: 'esqlQuery', description: actionVariableEsqlQueryLabel }, ], }, useSavedObjectReferences: { extractReferences: (params) => { - if (isEsQueryRule(params.searchType)) { - return { params: params as EsQueryRuleParamsExtractedParams, references: [] }; + if (isSearchSourceRule(params.searchType)) { + const [searchConfiguration, references] = extractReferences(params.searchConfiguration); + const newParams = { ...params, searchConfiguration } as EsQueryRuleParamsExtractedParams; + return { params: newParams, references }; } - const [searchConfiguration, references] = extractReferences(params.searchConfiguration); - const newParams = { ...params, searchConfiguration } as EsQueryRuleParamsExtractedParams; - return { params: newParams, references }; + + return { params: params as EsQueryRuleParamsExtractedParams, references: [] }; }, injectReferences: (params, references) => { - if (isEsQueryRule(params.searchType)) { - return params; + if (isSearchSourceRule(params.searchType)) { + return { + ...params, + searchConfiguration: injectReferences(params.searchConfiguration, references), + }; } - return { - ...params, - searchConfiguration: injectReferences(params.searchConfiguration, references), - }; + return params; }, }, minimumLicenseRequired: 'basic', @@ -207,6 +201,6 @@ export function getRuleType( }, producer: STACK_ALERTS_FEATURE_ID, doesSetRecoveryContext: true, - alerts, + alerts: STACK_ALERTS_AAD_CONFIG, }; } diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts index 7e99ded206b2c..b6011e72bbcc7 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts @@ -179,15 +179,51 @@ describe('ruleType Params validate()', () => { }); it('fails for invalid termField', async () => { + params.termField = ['term', 'term 2']; + params.termSize = 1; + expect(onValidate()).not.toThrow(); + + params.termField = 'term'; + params.termSize = 1; + expect(onValidate()).not.toThrow(); + + // string or array of string params.groupBy = 'top'; params.termField = 42; - expect(onValidate()).toThrowErrorMatchingInlineSnapshot( - `"[termField]: expected value of type [string] but got [number]"` + expect(onValidate()).toThrow(`[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [number] +- [termField.1]: expected value of type [array] but got [number]`); + + // no array other than array of stings + params.termField = [1, 2, 3]; + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [Array] +- [termField.1.0]: expected value of type [string] but got [number]` ); + // no empty string params.termField = ''; - expect(onValidate()).toThrowErrorMatchingInlineSnapshot( - `"[termField]: value has length [0] but it must have a minimum length of [1]."` + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: value has length [0] but it must have a minimum length of [1]. +- [termField.1]: could not parse array value from json input` + ); + + // no array with one element -> has to be a string + params.termField = ['term']; + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [Array] +- [termField.1]: array size is [1], but cannot be smaller than [2]` + ); + + // no array that has more than 4 elements + params.termField = ['term', 'term2', 'term3', 'term4', 'term4']; + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [Array] +- [termField.1]: array size is [5], but cannot be greater than [4]` ); }); diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts index b9380eeafe304..5469c6fa60247 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts @@ -15,9 +15,11 @@ import { } from '@kbn/triggers-actions-ui-plugin/server'; import { RuleTypeState } from '@kbn/alerting-plugin/server'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { MAX_SELECTABLE_GROUP_BY_TERMS } from '../../../common/constants'; import { ComparatorFnNames } from '../../../common'; import { Comparator } from '../../../common/comparator_types'; import { getComparatorSchemaType } from '../lib/comparator'; +import { isEsqlQueryRule, isSearchSourceRule } from './util'; export const ES_QUERY_MAX_HITS_PER_EXECUTION = 10000; @@ -47,12 +49,20 @@ const EsQueryRuleParamsSchemaProperties = { // how to group groupBy: schema.string({ validate: validateGroupBy, defaultValue: 'all' }), // field to group on (for groupBy: top) - termField: schema.maybe(schema.string({ minLength: 1 })), + termField: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string(), { minSize: 2, maxSize: MAX_SELECTABLE_GROUP_BY_TERMS }), + ]) + ), // limit on number of groups returned termSize: schema.maybe(schema.number({ min: 1 })), - searchType: schema.oneOf([schema.literal('searchSource'), schema.literal('esQuery')], { - defaultValue: 'esQuery', - }), + searchType: schema.oneOf( + [schema.literal('searchSource'), schema.literal('esQuery'), schema.literal('esqlQuery')], + { + defaultValue: 'esQuery', + } + ), timeField: schema.conditional( schema.siblingRef('searchType'), schema.literal('esQuery'), @@ -79,6 +89,13 @@ const EsQueryRuleParamsSchemaProperties = { schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), schema.never() ), + // esqlQuery rule params only + esqlQuery: schema.conditional( + schema.siblingRef('searchType'), + schema.literal('esqlQuery'), + schema.object({ esql: schema.string({ minLength: 1 }) }), + schema.never() + ), }; export const EsQueryRuleParamsSchema = schema.object(EsQueryRuleParamsSchemaProperties, { @@ -142,7 +159,7 @@ function validateParams(anyParams: unknown): string | undefined { } } - if (searchType === 'searchSource') { + if (isSearchSourceRule(searchType) || isEsqlQueryRule(searchType)) { return; } diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/types.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/types.ts index b20f52f03ebe5..5f3ec1ba5e240 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/types.ts @@ -11,15 +11,26 @@ import { ActionContext } from './action_context'; import { EsQueryRuleParams, EsQueryRuleState } from './rule_type_params'; import { ActionGroupId } from './constants'; -export type OnlyEsQueryRuleParams = Omit & { +export type OnlyEsQueryRuleParams = Omit & { searchType: 'esQuery'; timeField: string; }; -export type OnlySearchSourceRuleParams = Omit & { +export type OnlySearchSourceRuleParams = Omit< + EsQueryRuleParams, + 'esQuery' | 'index' | 'esqlQuery' +> & { searchType: 'searchSource'; }; +export type OnlyEsqlQueryRuleParams = Omit< + EsQueryRuleParams, + 'esQuery' | 'index' | 'searchConfiguration' +> & { + searchType: 'esqlQuery'; + timeField: string; +}; + export type ExecutorOptions

    = RuleExecutorOptions< P, EsQueryRuleState, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/util.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/util.ts index 064a7f64b4c32..d10218fea7d4f 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/util.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/util.ts @@ -8,5 +8,13 @@ import { EsQueryRuleParams } from './rule_type_params'; export function isEsQueryRule(searchType: EsQueryRuleParams['searchType']) { - return searchType !== 'searchSource'; + return searchType === 'esQuery'; +} + +export function isSearchSourceRule(searchType: EsQueryRuleParams['searchType']) { + return searchType === 'searchSource'; +} + +export function isEsqlQueryRule(searchType: EsQueryRuleParams['searchType']) { + return searchType === 'esqlQuery'; } diff --git a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts index e4f990b20f1a7..1e0aab0bb7930 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts @@ -183,6 +183,7 @@ describe('ruleType', () => { threshold: [1], }; + const ruleName = uuidv4(); await ruleType.executor({ executionId: uuidv4(), startedAt: new Date(), @@ -200,7 +201,7 @@ describe('ruleType', () => { spaceId: uuidv4(), rule: { id: uuidv4(), - name: uuidv4(), + name: ruleName, tags: [], consumer: '', producer: '', @@ -225,7 +226,36 @@ describe('ruleType', () => { flappingSettings: DEFAULT_FLAPPING_SETTINGS, }); - expect(alertServices.alertFactory.create).toHaveBeenCalledWith('all documents'); + expect(alertServices.alertsClient.report).toHaveBeenCalledWith({ + actionGroup: 'threshold met', + context: { + conditions: 'foo is less than 1', + date: '1970-01-01T00:00:00.000Z', + group: 'all documents', + message: `alert '${ruleName}' is active for group 'all documents': + +- Value: 0 +- Conditions Met: foo is less than 1 over 5m +- Timestamp: 1970-01-01T00:00:00.000Z`, + title: `alert ${ruleName} group all documents met threshold`, + value: 0, + }, + id: 'all documents', + payload: { + kibana: { + alert: { + evaluation: { conditions: 'foo is less than 1', value: 0 }, + reason: `alert '${ruleName}' is active for group 'all documents': + +- Value: 0 +- Conditions Met: foo is less than 1 over 5m +- Timestamp: 1970-01-01T00:00:00.000Z`, + title: `alert ${ruleName} group all documents met threshold`, + }, + }, + }, + state: {}, + }); }); it('should ensure a null result does not fire actions', async () => { diff --git a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts index 4b9a7e69ac31c..caec66b632b5d 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts @@ -11,22 +11,26 @@ import { TIME_SERIES_BUCKET_SELECTOR_FIELD, } from '@kbn/triggers-actions-ui-plugin/server'; import { isGroupAggregation } from '@kbn/triggers-actions-ui-plugin/common'; -import { RuleType, RuleExecutorOptions, StackAlertsStartDeps } from '../../types'; -import { Params, ParamsSchema } from './rule_type_params'; -import { ActionContext, BaseActionContext, addMessages } from './action_context'; +import { StackAlert } from '@kbn/alerts-as-data-utils'; +import { ALERT_EVALUATION_VALUE, ALERT_REASON } from '@kbn/rule-data-utils'; +import { expandFlattenedAlert } from '@kbn/alerting-plugin/server/alerts_client/lib'; +import { ALERT_EVALUATION_CONDITIONS, ALERT_TITLE, STACK_ALERTS_AAD_CONFIG } from '..'; import { ComparatorFns, getComparatorScript, getHumanReadableComparator, STACK_ALERTS_FEATURE_ID, } from '../../../common'; +import { ActionContext, BaseActionContext, addMessages } from './action_context'; +import { Params, ParamsSchema } from './rule_type_params'; +import { RuleType, RuleExecutorOptions, StackAlertsStartDeps } from '../../types'; export const ID = '.index-threshold'; export const ActionGroupId = 'threshold met'; export function getRuleType( data: Promise -): RuleType { +): RuleType { const ruleTypeName = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeTitle', { defaultMessage: 'Index threshold', }); @@ -204,10 +208,11 @@ export function getRuleType( executor, producer: STACK_ALERTS_FEATURE_ID, doesSetRecoveryContext: true, + alerts: STACK_ALERTS_AAD_CONFIG, }; async function executor( - options: RuleExecutorOptions + options: RuleExecutorOptions ) { const { rule: { id: ruleId, name }, @@ -215,9 +220,9 @@ export function getRuleType( params, logger, } = options; - const { alertFactory, scopedClusterClient } = services; + const { alertsClient, scopedClusterClient } = services; - const alertLimit = alertFactory.alertLimit.getValue(); + const alertLimit = alertsClient!.getAlertLimitValue(); const compareFn = ComparatorFns.get(params.thresholdComparator); if (compareFn == null) { @@ -310,12 +315,23 @@ export function getRuleType( conditions: humanFn, }; const actionContext = addMessages(name, baseContext, params); - const alert = alertFactory.create(alertId); - alert.scheduleActions(ActionGroupId, actionContext); + + alertsClient!.report({ + id: alertId, + actionGroup: ActionGroupId, + state: {}, + context: actionContext, + payload: expandFlattenedAlert({ + [ALERT_REASON]: actionContext.message, + [ALERT_TITLE]: actionContext.title, + [ALERT_EVALUATION_CONDITIONS]: actionContext.conditions, + [ALERT_EVALUATION_VALUE]: actionContext.value, + }), + }); logger.debug(`scheduled actionGroup: ${JSON.stringify(actionContext)}`); } - alertFactory.alertLimit.setLimitReached(result.truncated); + alertsClient!.setAlertLimitReached(result.truncated); const { getRecoveredAlerts } = services.alertFactory.done(); for (const recoveredAlert of getRecoveredAlerts()) { @@ -330,7 +346,16 @@ export function getRuleType( )} ${params.threshold.join(' and ')}`, }; const recoveryContext = addMessages(name, baseContext, params, true); - recoveredAlert.setContext(recoveryContext); + alertsClient?.setAlertData({ + id: alertId, + context: recoveryContext, + payload: expandFlattenedAlert({ + [ALERT_REASON]: recoveryContext.message, + [ALERT_TITLE]: recoveryContext.title, + [ALERT_EVALUATION_CONDITIONS]: recoveryContext.conditions, + [ALERT_EVALUATION_VALUE]: recoveryContext.value, + }), + }); } return { state: {} }; diff --git a/x-pack/plugins/stack_alerts/tsconfig.json b/x-pack/plugins/stack_alerts/tsconfig.json index 207e883aa8902..08a4f0ca99e9c 100644 --- a/x-pack/plugins/stack_alerts/tsconfig.json +++ b/x-pack/plugins/stack_alerts/tsconfig.json @@ -44,6 +44,9 @@ "@kbn/discover-plugin", "@kbn/rule-data-utils", "@kbn/alerts-as-data-utils", + "@kbn/text-based-languages", + "@kbn/text-based-editor", + "@kbn/expressions-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/stack_connectors/common/gen_ai/schema.ts b/x-pack/plugins/stack_connectors/common/gen_ai/schema.ts index bae0a9f532d41..f82b89ee7c2b6 100644 --- a/x-pack/plugins/stack_connectors/common/gen_ai/schema.ts +++ b/x-pack/plugins/stack_connectors/common/gen_ai/schema.ts @@ -35,12 +35,13 @@ export const GenAiStreamActionParamsSchema = schema.object({ }); export const GenAiStreamingResponseSchema = schema.any(); + export const GenAiRunActionResponseSchema = schema.object( { - id: schema.string(), - object: schema.string(), - created: schema.number(), - model: schema.string(), + id: schema.maybe(schema.string()), + object: schema.maybe(schema.string()), + created: schema.maybe(schema.number()), + model: schema.maybe(schema.string()), usage: schema.object( { prompt_tokens: schema.number(), @@ -59,8 +60,8 @@ export const GenAiRunActionResponseSchema = schema.object( }, { unknowns: 'ignore' } ), - finish_reason: schema.string(), - index: schema.number(), + finish_reason: schema.maybe(schema.string()), + index: schema.maybe(schema.number()), }, { unknowns: 'ignore' } ) diff --git a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts index d2961d4725d39..900f7cd334241 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts @@ -609,6 +609,21 @@ describe('execute()', () => { took: 0, errors: true, items: [ + { + create: { + _index: 'indexme', + _id: '7buTjHQB0SuNSiS9Hayt', + status: 400, + error: { + type: 'document_parsing_exception', + reason: `[1:10] failed to parse field [bytes] of type [long] in document with id '39XQLIoB8kAjguvyIMeJ'. Preview of field's value: 'foo'`, + caused_by: { + type: 'illegal_argument_exception', + reason: `For input string: \"foo\"`, + }, + }, + }, + }, { index: { _index: 'indexme', @@ -633,7 +648,175 @@ describe('execute()', () => { config, secrets, params, - services, + services: { ...services, scopedClusterClient }, + configurationUtilities, + logger: mockedLogger, + }) + ).toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "message": "error indexing documents", + "serviceMessage": "[1:10] failed to parse field [bytes] of type [long] in document with id '39XQLIoB8kAjguvyIMeJ'. Preview of field's value: 'foo';failed to parse (For input string: \\"foo\\";field name cannot be an empty string)", + "status": "error", + } + `); + }); + + test('resolves with an error when an error occurs in the indexing operation - malformed response', async () => { + const secrets = {}; + // minimal params + const config = { index: 'index-value', refresh: false, executionTimeField: null }; + const params = { + documents: [{ '': 'bob' }], + indexOverride: null, + }; + + const actionId = 'some-id'; + const scopedClusterClient = elasticsearchClientMock + .createClusterClient() + .asScoped().asCurrentUser; + // @ts-expect-error + scopedClusterClient.bulk.mockResponse({ + took: 0, + errors: true, + }); + + expect( + await connectorType.executor({ + actionId, + config, + secrets, + params, + services: { ...services, scopedClusterClient }, + configurationUtilities, + logger: mockedLogger, + }) + ).toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "message": "error indexing documents", + "serviceMessage": "Indexing error but no reason returned.", + "status": "error", + } + `); + }); + + test('resolves with an error when an error occurs in the indexing operation - malformed error response', async () => { + const secrets = {}; + // minimal params + const config = { index: 'index-value', refresh: false, executionTimeField: null }; + const params = { + documents: [{ '': 'bob' }], + indexOverride: null, + }; + + const actionId = 'some-id'; + const scopedClusterClient = elasticsearchClientMock + .createClusterClient() + .asScoped().asCurrentUser; + scopedClusterClient.bulk.mockResponse({ + took: 0, + errors: true, + items: [ + { + create: { + _index: 'indexme', + _id: '7buTjHQB0SuNSiS9Hayt', + status: 400, + error: { + type: 'document_parsing_exception', + reason: `[1:10] failed to parse field [bytes] of type [long] in document with id '39XQLIoB8kAjguvyIMeJ'. Preview of field's value: 'foo'`, + caused_by: { + type: 'illegal_argument_exception', + reason: `For input string: \"foo\"`, + }, + }, + }, + }, + { + index: { + _index: 'indexme', + _id: '7buTjHQB0SuNSiS9Hayt', + status: 400, + }, + }, + ], + }); + + expect( + await connectorType.executor({ + actionId, + config, + secrets, + params, + services: { ...services, scopedClusterClient }, + configurationUtilities, + logger: mockedLogger, + }) + ).toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "message": "error indexing documents", + "serviceMessage": "[1:10] failed to parse field [bytes] of type [long] in document with id '39XQLIoB8kAjguvyIMeJ'. Preview of field's value: 'foo' (For input string: \\"foo\\")", + "status": "error", + } + `); + }); + + test('resolves with an error when an error occurs in the indexing operation - error with no reason', async () => { + const secrets = {}; + // minimal params + const config = { index: 'index-value', refresh: false, executionTimeField: null }; + const params = { + documents: [{ '': 'bob' }], + indexOverride: null, + }; + + const actionId = 'some-id'; + const scopedClusterClient = elasticsearchClientMock + .createClusterClient() + .asScoped().asCurrentUser; + scopedClusterClient.bulk.mockResponse({ + took: 0, + errors: true, + items: [ + { + create: { + _index: 'indexme', + _id: '7buTjHQB0SuNSiS9Hayt', + status: 400, + error: { + type: 'document_parsing_exception', + caused_by: { + type: 'illegal_argument_exception', + }, + }, + }, + }, + { + index: { + _index: 'indexme', + _id: '7buTjHQB0SuNSiS9Hayt', + status: 400, + error: { + type: 'mapper_parsing_exception', + reason: 'failed to parse', + caused_by: { + type: 'illegal_argument_exception', + }, + }, + }, + }, + ], + }); + + expect( + await connectorType.executor({ + actionId, + config, + secrets, + params, + services: { ...services, scopedClusterClient }, configurationUtilities, logger: mockedLogger, }) @@ -641,7 +824,7 @@ describe('execute()', () => { Object { "actionId": "some-id", "message": "error indexing documents", - "serviceMessage": "Cannot read properties of undefined (reading 'items')", + "serviceMessage": "failed to parse", "status": "error", } `); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts index b4ee9ff01e474..5057fdb5d8312 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { find } from 'lodash'; +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; @@ -25,6 +25,10 @@ import { ALERT_HISTORY_PREFIX, buildAlertHistoryDocument, } from '@kbn/actions-plugin/common'; +import { + BulkOperationType, + BulkResponseItem, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; export type ESIndexConnectorType = ConnectorType< ConnectorTypeConfigType, @@ -124,15 +128,29 @@ async function executor( try { const result = await services.scopedClusterClient.bulk(bulkParams); - const err = find(result.items, 'index.error.reason'); - if (err) { - return wrapErr( - `${err.index?.error?.reason}${ - err.index?.error?.caused_by ? ` (${err.index?.error?.caused_by?.reason})` : '' - }`, - actionId, - logger - ); + if (result.errors) { + const errReason: string[] = []; + const errCausedBy: string[] = []; + // extract error reason and caused by + (result.items ?? []).forEach((item: Partial>) => { + for (const [_, responseItem] of Object.entries(item)) { + const reason = get(responseItem, 'error.reason'); + const causedBy = get(responseItem, 'error.caused_by.reason'); + if (reason) { + errReason.push(reason); + } + if (causedBy) { + errCausedBy.push(causedBy); + } + } + }); + + const errMessage = + errReason.length > 0 + ? `${errReason.join(';')}${errCausedBy.length > 0 ? ` (${errCausedBy.join(';')})` : ''}` + : `Indexing error but no reason returned.`; + + return wrapErr(errMessage, actionId, logger); } return { status: 'ok', data: result, actionId }; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor/index.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor/index.ts index 41daa9d2148ad..1aa698e715db2 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor/index.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export * from './locations'; export * from './state'; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor/locations.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor/locations.ts deleted file mode 100644 index e37621c47ddf9..0000000000000 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor/locations.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { CheckGeoType, SummaryType } from '../common'; - -// IO type for validation -export const MonitorLocationType = t.type({ - up_history: t.number, - down_history: t.number, - timestamp: t.string, - summary: SummaryType, - geo: CheckGeoType, -}); - -// Typescript type for type checking -export type MonitorLocation = t.TypeOf; - -export const MonitorLocationsType = t.intersection([ - t.type({ - monitorId: t.string, - up_history: t.number, - down_history: t.number, - }), - t.partial({ locations: t.array(MonitorLocationType) }), -]); -export type MonitorLocations = t.TypeOf; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index 99ce709c2672e..af2304acb9e24 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -46,6 +46,10 @@ export const TLSSensitiveFieldsCodec = t.partial({ export const TLSCodec = t.intersection([TLSFieldsCodec, TLSSensitiveFieldsCodec]); +const MonitorLocationsCodec = t.array(t.union([MonitorServiceLocationCodec, PrivateLocationCodec])); + +export type MonitorLocations = t.TypeOf; + // CommonFields export const CommonFieldsCodec = t.intersection([ t.interface({ @@ -56,7 +60,7 @@ export const CommonFieldsCodec = t.intersection([ [ConfigKey.SCHEDULE]: ScheduleCodec, [ConfigKey.APM_SERVICE_NAME]: t.string, [ConfigKey.TAGS]: t.array(t.string), - [ConfigKey.LOCATIONS]: t.array(t.union([MonitorServiceLocationCodec, PrivateLocationCodec])), + [ConfigKey.LOCATIONS]: MonitorLocationsCodec, [ConfigKey.MONITOR_QUERY_ID]: t.string, [ConfigKey.CONFIG_ID]: t.string, }), diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts index d5f6fa883e4b5..d6a40ff10a642 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts @@ -33,7 +33,7 @@ export const OverviewStatusMetaDataCodec = t.interface({ monitorQueryId: t.string, configId: t.string, status: t.string, - location: t.string, + locationId: t.string, timestamp: t.string, ping: OverviewPingCodec, }); diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts index fe019d4a07af5..51adff582c478 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts @@ -148,6 +148,7 @@ export const PingType = t.intersection([ timestamp: t.string, monitor: MonitorType, docId: t.string, + observer: ObserverCodec, }), t.partial({ '@timestamp': t.string, @@ -198,7 +199,6 @@ export const PingType = t.intersection([ uid: t.string, }), }), - observer: ObserverCodec, resolve: t.partial({ ip: t.string, rtt: t.partial({ @@ -263,37 +263,6 @@ export const PingStatusType = t.intersection([ export type PingStatus = t.TypeOf; -// Convenience function for tests etc that makes an empty ping -// object with the minimum of fields. -export const makePing = (f: { - docId?: string; - type?: string; - id?: string; - timestamp?: string; - ip?: string; - status?: string; - duration?: number; - location?: string; - name?: string; - url?: string; -}): Ping => { - return { - docId: f.docId || 'myDocId', - timestamp: f.timestamp || '2020-07-07T01:14:08Z', - monitor: { - id: f.id || 'myId', - type: f.type || 'myType', - ip: f.ip || '127.0.0.1', - status: f.status || 'up', - duration: { us: f.duration || 100000 }, - name: f.name, - check_group: 'myCheckGroup', - }, - ...(f.location ? { observer: { geo: { name: f.location } } } : {}), - ...(f.url ? { url: { full: f.url } } : {}), - }; -}; - export const PingsResponseType = t.type({ total: t.number, pings: t.array(PingType), diff --git a/x-pack/plugins/synthetics/e2e/helpers/synthetics_runner.ts b/x-pack/plugins/synthetics/e2e/helpers/synthetics_runner.ts index 3fb156a6ba0b3..c097e214ae3eb 100644 --- a/x-pack/plugins/synthetics/e2e/helpers/synthetics_runner.ts +++ b/x-pack/plugins/synthetics/e2e/helpers/synthetics_runner.ts @@ -114,6 +114,7 @@ export class SyntheticsRunner { params: { kibanaUrl: this.kibanaUrl, getService: this.getService }, playwrightOptions: { headless, + testIdAttribute: 'data-test-subj', chromiumSandbox: false, timeout: 60 * 1000, viewport: { diff --git a/x-pack/plugins/synthetics/e2e/synthetics/journeys/services/data/browser_docs.ts b/x-pack/plugins/synthetics/e2e/synthetics/journeys/services/data/browser_docs.ts index cbed9b9f259c3..22576524545c1 100644 --- a/x-pack/plugins/synthetics/e2e/synthetics/journeys/services/data/browser_docs.ts +++ b/x-pack/plugins/synthetics/e2e/synthetics/journeys/services/data/browser_docs.ts @@ -7,13 +7,13 @@ import { DocOverrides } from './sample_docs'; -export const getGeoData = (locationName?: string) => ({ +export const getGeoData = (locationName?: string, locationId?: string) => ({ observer: { geo: { name: locationName ?? 'North America - US Central', location: '41.8780, 93.0977', }, - name: locationName ?? 'North America - US Central', + name: locationId ?? 'us_central', }, }); diff --git a/x-pack/plugins/synthetics/e2e/synthetics/journeys/test_run_details.journey.ts b/x-pack/plugins/synthetics/e2e/synthetics/journeys/test_run_details.journey.ts index 433583a8ce3c8..5189db3c4c359 100644 --- a/x-pack/plugins/synthetics/e2e/synthetics/journeys/test_run_details.journey.ts +++ b/x-pack/plugins/synthetics/e2e/synthetics/journeys/test_run_details.journey.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { journey, step, before, after } from '@elastic/synthetics'; +import { journey, step, before, after, expect } from '@elastic/synthetics'; import { recordVideo } from '../../helpers/record_video'; import { byTestId } from '../../helpers/utils'; import { syntheticsAppPageProvider } from '../page_objects/synthetics_app'; @@ -50,6 +50,14 @@ journey(`TestRunDetailsPage`, async ({ page, params }) => { await syntheticsApp.navigateToOverview(true); }); + step('verified overview card contents', async () => { + await page.waitForSelector('text=https://www.google.com'); + const cardItem = await page.getByTestId('https://www.google.com-metric-item'); + expect(await cardItem.textContent()).toBe( + 'https://www.google.comNorth America - US CentralDuration155 ms' + ); + }); + step('Monitor is as up in summary page', async () => { await page.hover('text=https://www.google.com'); await page.click('[aria-label="Open actions menu"]'); diff --git a/x-pack/plugins/synthetics/kibana.jsonc b/x-pack/plugins/synthetics/kibana.jsonc index 511666996f829..d03d0d384938f 100644 --- a/x-pack/plugins/synthetics/kibana.jsonc +++ b/x-pack/plugins/synthetics/kibana.jsonc @@ -41,7 +41,8 @@ "kibanaUtils", "observability", "spaces", - "indexLifecycleManagement" + "indexLifecycleManagement", + "unifiedDocViewer" ] } } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/certificates/fingerprint_col.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/certificates/fingerprint_col.tsx index 138ae4d1073cc..3e6fdd997f162 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/certificates/fingerprint_col.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/certificates/fingerprint_col.tsx @@ -31,6 +31,7 @@ export const FingerprintCol: React.FC = ({ cert }) => { {(copy) => ( - - - - {!hideEnabled && ( - <> - {ENABLED_LABEL} - - {monitor && ( - { - dispatch(getMonitorAction.get({ monitorId: configId })); - }} - /> - )} - - + + + {!hideEnabled && ( + <> + {ENABLED_LABEL} + + {monitor && ( + { + dispatch(getMonitorAction.get({ monitorId: configId })); + }} + /> + )} + + + )} + {URL_LABEL} + + {url ? ( + + {url} + + ) : ( + + {UN_AVAILABLE_LABEL} + )} - {URL_LABEL} - - {url ? ( - - {url} - - ) : ( - - {UN_AVAILABLE_LABEL} - - )} - - {LAST_RUN_LABEL} - - {latestPing?.timestamp ? ( - - {LAST_MODIFIED_LABEL} - - - {monitor[ConfigKey.PROJECT_ID] && ( - <> - {PROJECT_ID_LABEL} - {monitor[ConfigKey.PROJECT_ID]} - + + {LAST_RUN_LABEL} + + {latestPing?.timestamp ? ( + + {LAST_MODIFIED_LABEL} + + + {monitor[ConfigKey.PROJECT_ID] && ( + <> + {PROJECT_ID_LABEL} + + {monitor[ConfigKey.PROJECT_ID]} + + + )} + {MONITOR_ID_ITEM_TEXT} + {monitor.id} + {MONITOR_TYPE_LABEL} + + + + {FREQUENCY_LABEL} + + {frequencyStr(monitor[ConfigKey.SCHEDULE])} + - {!hideLocations && ( - <> - {LOCATIONS_LABEL} - - - - - )} + {!hideLocations && ( + <> + {LOCATIONS_LABEL} + + + + + )} - {TAGS_LABEL} - - - - - + {TAGS_LABEL} + + + + ); }; @@ -213,13 +206,6 @@ const Time = ({ timestamp }: { timestamp?: string }) => { return timestamp ? : null; }; -export const WrapperStyle = euiStyled.div` - .euiDescriptionList.euiDescriptionList--column > *, - .euiDescriptionList.euiDescriptionList--responsiveColumn > * { - margin-top: ${({ theme }) => theme.eui.euiSizeS}; - } -`; - const FREQUENCY_LABEL = i18n.translate('xpack.synthetics.management.monitorList.frequency', { defaultMessage: 'Frequency', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx index 425a1f2ee0c60..f9dd560e4742e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx @@ -30,12 +30,14 @@ export const FleetPermissionsCallout = () => { */ export const NoPermissionsTooltip = ({ canEditSynthetics = true, + canUsePublicLocations = true, children, }: { canEditSynthetics?: boolean; + canUsePublicLocations?: boolean; children: ReactNode; }) => { - const disabledMessage = getRestrictionReasonLabel(canEditSynthetics); + const disabledMessage = getRestrictionReasonLabel(canEditSynthetics, canUsePublicLocations); if (disabledMessage) { return ( @@ -47,8 +49,16 @@ export const NoPermissionsTooltip = ({ return <>{children}; }; -function getRestrictionReasonLabel(canEditSynthetics = true): string | undefined { - return !canEditSynthetics ? CANNOT_PERFORM_ACTION_SYNTHETICS : undefined; +function getRestrictionReasonLabel( + canEditSynthetics = true, + canUsePublicLocations = true +): string | undefined { + const message = !canEditSynthetics ? CANNOT_PERFORM_ACTION_SYNTHETICS : undefined; + if (message) { + return message; + } + + return !canUsePublicLocations ? CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS : undefined; } export const NEED_PERMISSIONS_PRIVATE_LOCATIONS = i18n.translate( @@ -83,3 +93,10 @@ export const CANNOT_PERFORM_ACTION_SYNTHETICS = i18n.translate( defaultMessage: 'You do not have sufficient permissions to perform this action.', } ); + +export const CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS = i18n.translate( + 'xpack.synthetics.monitorManagement.canUsePublicLocations', + { + defaultMessage: 'You do not have sufficient permissions to use Elastic managed locations.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx new file mode 100644 index 0000000000000..591c91a902146 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 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 { EuiButtonIcon, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +import React, { useState } from 'react'; +import { useUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public'; +import { buildDataTableRecord } from '@kbn/discover-utils'; +import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { useFetcher } from '@kbn/observability-shared-plugin/public'; +import { DataTableRecord } from '@kbn/discover-utils/src/types'; +import { useDateFormat } from '../../../../../hooks/use_date_format'; +import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout'; +import { useSyntheticsDataView } from '../../../contexts/synthetics_data_view_context'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; +import { Ping } from '../../../../../../common/runtime_types'; + +export const ViewDocument = ({ ping }: { ping: Ping }) => { + const { data } = useUnifiedDocViewerServices(); + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + + const dataView = useSyntheticsDataView(); + const formatter = useDateFormat(); + + const { data: hit } = useFetcher>(async () => { + if (!dataView?.id || !isFlyoutVisible) return; + const response = await data.search + .search({ + params: { + index: SYNTHETICS_INDEX_PATTERN, + body: { + query: { + ids: { + values: [ping.docId], + }, + }, + fields: ['*'], + _source: false, + }, + }, + }) + .toPromise(); + const docs = response?.rawResponse?.hits?.hits ?? []; + if (docs.length > 0) { + return buildDataTableRecord(docs[0], dataView); + } + }, [data, dataView, ping.docId, isFlyoutVisible]); + + return ( + <> + { + setIsFlyoutVisible(true); + }} + /> + {isFlyoutVisible && ( + setIsFlyoutVisible(false)} ownFocus={true}> + + +

    + {INDEXED_AT} {formatter(ping.timestamp)} +

    + + + + {dataView?.id && hit ? ( + + ) : ( + + )} + + + )} + + ); +}; + +const INDEXED_AT = i18n.translate('xpack.synthetics.monitorDetails.summary.indexedAt', { + defaultMessage: 'Indexed at', +}); + +export const INSPECT_DOCUMENT = i18n.translate( + 'xpack.synthetics.monitorDetails.action.inspectDocument', + { + defaultMessage: 'Inspect document', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx index e66e852739d32..3414f2b2bfd13 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx @@ -50,6 +50,7 @@ export const StepDetailsLinkIcon = ({ return ( { return ( - + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/view_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/view_errors.tsx index 0ca0b19a4f58e..e41b2be1a7c11 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/view_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/view_errors.tsx @@ -16,6 +16,7 @@ export const ErrorsLink = ({ disabled }: { disabled?: boolean }) => { return ( ( toggleDetails(item)} aria-label={expandedMap[item._id] ? 'Collapse' : 'Expand'} iconType={expandedMap[item._id] ? 'arrowDown' : 'arrowRight'} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx index 3b863847968e9..558e42401d6ee 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx @@ -96,7 +96,6 @@ export const TimingDetails = ({ step }: { step: JourneyStep }) => { return ( { return ( ({ props: ({ field, setValue, locations, trigger }) => { return { options: Object.values(locations).map((location) => ({ - label: locations?.find((loc) => location.id === loc.id)?.label || '', - id: location.id || '', + label: location.label, + id: location.id, isServiceManaged: location.isServiceManaged || false, isInvalid: location.isInvalid, disabled: location.isInvalid, @@ -417,7 +418,9 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ : location.isServiceManaged ? 'default' : 'primary', - label: locations?.find((loc) => location.id === loc.id)?.label ?? location.id, + label: + (location.label || locations?.find((loc) => location.id === loc.id)?.label) ?? + location.id, id: location.id || '', isServiceManaged: location.isServiceManaged || false, })), @@ -483,66 +486,78 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ helpText: i18n.translate('xpack.synthetics.monitorConfig.edit.enabled.label', { defaultMessage: `When disabled, the monitor doesn't run any tests. You can enable it at any time.`, }), - props: ({ setValue, field, trigger }): EuiSwitchProps => ({ - id: 'syntheticsMontiorConfigIsEnabled', - label: i18n.translate('xpack.synthetics.monitorConfig.enabled.label', { - defaultMessage: 'Enable Monitor', - }), - checked: field?.value || false, - onChange: async (event) => { - setValue(ConfigKey.ENABLED, !!event.target.checked); - await trigger(ConfigKey.ENABLED); - }, - 'data-test-subj': 'syntheticsEnableSwitch', - // enabled is an allowed field for read only - // isDisabled: readOnly, - }), + props: ({ setValue, field, trigger, formState }): EuiSwitchProps => { + const isProjectMonitor = + formState.defaultValues?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; + return { + id: 'syntheticsMontiorConfigIsEnabled', + label: i18n.translate('xpack.synthetics.monitorConfig.enabled.label', { + defaultMessage: 'Enable Monitor', + }), + checked: field?.value || false, + onChange: async (event) => { + setValue(ConfigKey.ENABLED, !!event.target.checked); + await trigger(ConfigKey.ENABLED); + }, + 'data-test-subj': 'syntheticsEnableSwitch', + // enabled is an allowed field for read only + disabled: !isProjectMonitor && readOnly, + }; + }, }, [AlertConfigKey.STATUS_ENABLED]: { fieldKey: AlertConfigKey.STATUS_ENABLED, component: Switch, controlled: true, - props: ({ setValue, field, trigger }): EuiSwitchProps => ({ - id: 'syntheticsMonitorConfigIsAlertEnabled', - label: field?.value - ? i18n.translate('xpack.synthetics.monitorConfig.enabledAlerting.label', { - defaultMessage: 'Disable status alerts on this monitor', - }) - : i18n.translate('xpack.synthetics.monitorConfig.disabledAlerting.label', { - defaultMessage: 'Enable status alerts on this monitor', - }), - checked: field?.value || false, - onChange: async (event) => { - setValue(AlertConfigKey.STATUS_ENABLED, !!event.target.checked); - await trigger(AlertConfigKey.STATUS_ENABLED); - }, - 'data-test-subj': 'syntheticsAlertStatusSwitch', - // alert config is an allowed field for read only - // isDisabled: readOnly, - }), + props: ({ setValue, field, trigger, formState }): EuiSwitchProps => { + const isProjectMonitor = + formState.defaultValues?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; + return { + id: 'syntheticsMonitorConfigIsAlertEnabled', + label: field?.value + ? i18n.translate('xpack.synthetics.monitorConfig.enabledAlerting.label', { + defaultMessage: 'Disable status alerts on this monitor', + }) + : i18n.translate('xpack.synthetics.monitorConfig.disabledAlerting.label', { + defaultMessage: 'Enable status alerts on this monitor', + }), + checked: field?.value || false, + onChange: async (event) => { + setValue(AlertConfigKey.STATUS_ENABLED, !!event.target.checked); + await trigger(AlertConfigKey.STATUS_ENABLED); + }, + 'data-test-subj': 'syntheticsAlertStatusSwitch', + // alert config is an allowed field for read only + disabled: !isProjectMonitor && readOnly, + }; + }, }, [AlertConfigKey.TLS_ENABLED]: { fieldKey: AlertConfigKey.TLS_ENABLED, component: Switch, controlled: true, - props: ({ setValue, field, trigger }): EuiSwitchProps => ({ - id: 'syntheticsMonitorConfigIsTlsAlertEnabled', - label: field?.value - ? i18n.translate('xpack.synthetics.monitorConfig.edit.alertTlsEnabled.label', { - defaultMessage: 'Disable TLS alerts on this monitor.', - }) - : i18n.translate('xpack.synthetics.monitorConfig.create.alertTlsEnabled.label', { - defaultMessage: 'Enable TLS alerts on this monitor.', - }), - checked: field?.value || false, - onChange: async (event) => { - setValue(AlertConfigKey.TLS_ENABLED, !!event.target.checked); - await trigger(AlertConfigKey.TLS_ENABLED); - }, - 'data-test-subj': 'syntheticsAlertStatusSwitch', - // alert config is an allowed field for read only - // isDisabled: readOnly, - }), + props: ({ setValue, field, trigger, formState }): EuiSwitchProps => { + const isProjectMonitor = + formState.defaultValues?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; + return { + id: 'syntheticsMonitorConfigIsTlsAlertEnabled', + label: field?.value + ? i18n.translate('xpack.synthetics.monitorConfig.edit.alertTlsEnabled.label', { + defaultMessage: 'Disable TLS alerts on this monitor.', + }) + : i18n.translate('xpack.synthetics.monitorConfig.create.alertTlsEnabled.label', { + defaultMessage: 'Enable TLS alerts on this monitor.', + }), + checked: field?.value || false, + onChange: async (event) => { + setValue(AlertConfigKey.TLS_ENABLED, !!event.target.checked); + await trigger(AlertConfigKey.TLS_ENABLED); + }, + 'data-test-subj': 'syntheticsAlertStatusSwitch', + // alert config is an allowed field for read only + disabled: !isProjectMonitor && readOnly, + }; + }, }, [ConfigKey.TAGS]: { fieldKey: ConfigKey.TAGS, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx index 33c8ec03e5ff9..bfd7ac5149a88 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx @@ -18,7 +18,8 @@ export const MonitorForm: React.FC<{ defaultValues?: SyntheticsMonitor; space?: string; readOnly?: boolean; -}> = ({ children, defaultValues, space, readOnly = false }) => { + canUsePublicLocations: boolean; +}> = ({ children, defaultValues, space, readOnly = false, canUsePublicLocations }) => { const methods = useFormWrapped({ mode: 'onSubmit', reValidateMode: 'onSubmit', @@ -43,7 +44,7 @@ export const MonitorForm: React.FC<{ > {children} - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx index 4b73d71b90b09..316e57e4a5065 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx @@ -16,7 +16,11 @@ import { format } from './formatter'; import { MonitorFields as MonitorFieldsType } from '../../../../../../common/runtime_types'; import { runOnceMonitor } from '../../../state/manual_test_runs/api'; -export const RunTestButton = () => { +export const RunTestButton = ({ + canUsePublicLocations = true, +}: { + canUsePublicLocations?: boolean; +}) => { const { formState, getValues, handleSubmit } = useFormContext(); const [inProgress, setInProgress] = useState(false); @@ -56,7 +60,7 @@ export const RunTestButton = () => { { +export const ActionBar = ({ + readOnly = false, + canUsePublicLocations = true, +}: { + readOnly: boolean; + canUsePublicLocations: boolean; +}) => { const { monitorId } = useParams<{ monitorId: string }>(); const history = useHistory(); const { @@ -59,6 +65,7 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => { onClick={() => { setMonitorPendingDeletion(defaultValues as SyntheticsMonitor); }} + isDisabled={!canEditSynthetics || !canUsePublicLocations} > {DELETE_MONITOR_LABEL} @@ -75,16 +82,19 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => { - + - + {isEdit ? UPDATE_MONITOR_LABEL : CREATE_MONITOR_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx index 69b53c24d4cbc..16573ce19f57a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx @@ -12,6 +12,7 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useTrackPageview, useFetcher } from '@kbn/observability-shared-plugin/public'; import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { useCanUsePublicLocations } from '../../../../hooks/use_capabilities'; import { EditMonitorNotFound } from './edit_monitor_not_found'; import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout'; import { ConfigKey, SourceType } from '../../../../../common/runtime_types'; @@ -50,11 +51,15 @@ export const MonitorEditPage: React.FC = () => { data?.id ); + const canUsePublicLocations = useCanUsePublicLocations(data?.[ConfigKey.LOCATIONS]); + if (monitorNotFoundError) { return ; } - const isReadOnly = data?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; + const isReadOnly = + data?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT || !canUsePublicLocations; + const projectId = data?.[ConfigKey.PROJECT_ID]; if (locationsError) { @@ -87,8 +92,13 @@ export const MonitorEditPage: React.FC = () => { return data && locationsLoaded && !loading && !error ? ( <> - + { @@ -32,12 +34,9 @@ export const MonitorSteps = ({ return ( <> - {readOnly ? ( - <> - - - - ) : null} + {isEditFlow && ( + + )} {isEditFlow ? ( steps.map((step) => (
    diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx index d20453be1c2ed..6f6f4533bb7dc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx @@ -5,27 +5,65 @@ * 2.0. */ import React from 'react'; -import { EuiCallOut } from '@elastic/eui'; +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -export const ReadOnlyCallout = ({ projectId }: { projectId?: string }) => { - return ( - - } - iconType="document" - > -

    - {projectId} }} - /> -

    -
    - ); +export const ReadOnlyCallout = ({ + projectId, + canUsePublicLocations, +}: { + projectId?: string; + canUsePublicLocations?: boolean; +}) => { + if (projectId) { + return ( + <> + + } + iconType="document" + > +

    + {projectId} }} + /> +

    +
    + + + ); + } + + if (!canUsePublicLocations) { + return ( + <> + + } + iconType="alert" + > +

    + +

    +
    + + + ); + } + + return null; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx index 4e4f9a6c61b17..969e98a21c36c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx @@ -8,7 +8,7 @@ import { useTimeZone } from '@kbn/observability-shared-plugin/public'; import { useParams } from 'react-router-dom'; import { useMemo } from 'react'; import { useSelectedLocation } from './use_selected_location'; -import { PingState } from '../../../../../../common/runtime_types'; +import { Ping, PingState } from '../../../../../../common/runtime_types'; import { EXCLUDE_RUN_ONCE_FILTER, SUMMARY_FILTER, @@ -113,10 +113,11 @@ export function useMonitorErrors(monitorIdArg?: string) { return prev; }, defaultValues) ?? defaultValues; + const hits = data?.aggregations?.latest.hits.hits ?? []; + const hasActiveError: boolean = - data?.aggregations?.latest.hits.hits.length === 1 && - (data?.aggregations?.latest.hits.hits[0]._source as { monitor: { status: string } }).monitor - .status === 'down' && + hits.length === 1 && + (hits[0]?._source as Ping).monitor?.status === 'down' && !!errorStates?.length; return { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx index df9785bb9b98c..eabea42a34162 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx @@ -7,6 +7,7 @@ import { useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { ServiceLocation } from '../../../../../../common/runtime_types'; import { useSelectedMonitor } from './use_selected_monitor'; import { selectSelectedLocationId, setMonitorDetailsLocationAction } from '../../../state'; import { useUrlParams, useLocations } from '../../../hooks'; @@ -41,8 +42,12 @@ export const useSelectedLocation = (updateUrl = true) => { monitor?.locations, ]); - return useMemo( - () => locations.find((loc) => loc.id === urlLocationId) ?? null, - [urlLocationId, locations] - ); + return useMemo(() => { + let selLoc = locations.find((loc) => loc.id === urlLocationId) ?? null; + if (!selLoc) { + selLoc = + (monitor?.locations?.find((loc) => loc.id === urlLocationId) as ServiceLocation) ?? null; + } + return selLoc; + }, [locations, urlLocationId, monitor?.locations]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/monitor_selector.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/monitor_selector.tsx index c19c93125f885..e4c7dec97f299 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/monitor_selector.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/monitor_selector.tsx @@ -23,6 +23,7 @@ export const MonitorSelector = () => { const button = ( ); }; - -export const WrapperStyle = euiStyled.div` - .euiDescriptionList.euiDescriptionList--column > *, - .euiDescriptionList.euiDescriptionList--responsiveColumn > * { - margin-top: ${({ theme }) => theme.eui.euiSizeS}; - } -`; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx index 97b5f2b521716..0bbbd3a5f247b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx @@ -21,6 +21,7 @@ import { } from '@elastic/eui'; import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; +import { INSPECT_DOCUMENT, ViewDocument } from '../../common/components/view_document'; import { ExpandRowColumn, toggleDetails, @@ -206,6 +207,20 @@ export const TestRunsTable = ({ show: false, }, }, + { + align: 'right' as const, + actions: [ + { + 'data-test-subj': 'syntheticsViewPingDocument', + isPrimary: true, + name: INSPECT_DOCUMENT, + description: INSPECT_DOCUMENT, + icon: 'inspect' as const, + type: 'button' as const, + render: (ping: Ping) => , + }, + ], + }, ...(!isBrowserMonitor ? [ { @@ -229,10 +244,17 @@ export const TestRunsTable = ({ 'data-test-subj': `row-${item.monitor.check_group}`, onClick: (evt: MouseEvent) => { const targetElem = evt.target as HTMLElement; + const isTableRow = + targetElem.parentElement?.classList.contains('euiTableCellContent') || + targetElem.parentElement?.classList.contains('euiTableCellContent__text') || + targetElem?.classList.contains('euiTableCellContent') || + targetElem?.classList.contains('euiBadge__text'); // we dont want to capture image click event if ( + isTableRow && targetElem.tagName !== 'IMG' && targetElem.tagName !== 'path' && + targetElem.tagName !== 'BUTTON' && !targetElem.parentElement?.classList.contains('euiLink') ) { if (item.monitor.type !== MONITOR_TYPES.BROWSER) { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx index 55fb54dd0d0dc..a05bea3f7925e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx @@ -9,6 +9,9 @@ import { EuiButton, EuiToolTip } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { useDispatch, useSelector } from 'react-redux'; +import { CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS } from '../common/components/permissions'; +import { useCanUsePublicLocations } from '../../../../hooks/use_capabilities'; +import { ConfigKey } from '../../../../../common/constants/monitor_management'; import { TEST_NOW_ARIA_LABEL, TEST_SCHEDULED_LABEL } from '../monitor_add_edit/form/run_test_btn'; import { useSelectedMonitor } from './hooks/use_selected_monitor'; import { @@ -22,7 +25,13 @@ export const RunTestManually = () => { const { monitor } = useSelectedMonitor(); const testInProgress = useSelector(manualTestRunInProgressSelector(monitor?.config_id)); - const content = testInProgress ? TEST_SCHEDULED_LABEL : TEST_NOW_ARIA_LABEL; + const canUsePublicLocations = useCanUsePublicLocations(monitor?.[ConfigKey.LOCATIONS]); + + const content = !canUsePublicLocations + ? CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS + : testInProgress + ? TEST_SCHEDULED_LABEL + : TEST_NOW_ARIA_LABEL; return ( @@ -31,6 +40,7 @@ export const RunTestManually = () => { color="success" iconType="beaker" isLoading={!Boolean(monitor) || testInProgress} + isDisabled={!canUsePublicLocations} onClick={() => { if (monitor) { dispatch( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_can_use_public_loc_id.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_can_use_public_loc_id.ts new file mode 100644 index 0000000000000..16842a401dc46 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_can_use_public_loc_id.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useSelector } from 'react-redux'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { selectOverviewState } from '../../../state'; + +export const useCanUsePublicLocById = (configId: string) => { + const { + data: { monitors }, + } = useSelector(selectOverviewState); + + const hasManagedLocation = monitors?.filter( + (mon) => mon.configId === configId && mon.location.isServiceManaged + ); + + const canUsePublicLocations = + useKibana().services?.application?.capabilities.uptime.elasticManagedLocationsEnabled ?? true; + + return hasManagedLocation ? !!canUsePublicLocations : true; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx index 8c3d1a1fe1fd9..684fb9fad8036 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useCanEditSynthetics } from '../../../../../../hooks/use_capabilities'; import { isStatusEnabled, @@ -55,6 +56,15 @@ export function useMonitorListColumns({ return alertStatus(fields[ConfigKey.CONFIG_ID]) === FETCH_STATUS.LOADING; }; + const canUsePublicLocations = + useKibana().services?.application?.capabilities.uptime.elasticManagedLocationsEnabled ?? true; + + const isPublicLocationsAllowed = (fields: EncryptedSyntheticsSavedMonitor) => { + const publicLocations = fields.locations.some((loc) => loc.isServiceManaged); + + return publicLocations ? Boolean(canUsePublicLocations) : true; + }; + const columns: Array> = [ { align: 'left' as const, @@ -121,7 +131,7 @@ export function useMonitorListColumns({ ) : null, }, @@ -166,14 +176,18 @@ export function useMonitorListColumns({ 'data-test-subj': 'syntheticsMonitorEditAction', isPrimary: true, name: (fields) => ( - + {labels.EDIT_LABEL} ), description: labels.EDIT_LABEL, icon: 'pencil' as const, type: 'icon' as const, - enabled: (fields) => canEditSynthetics && !isActionLoading(fields), + enabled: (fields) => + canEditSynthetics && !isActionLoading(fields) && isPublicLocationsAllowed(fields), onClick: (fields) => { history.push({ pathname: `/edit-monitor/${fields[ConfigKey.CONFIG_ID]}`, @@ -184,7 +198,10 @@ export function useMonitorListColumns({ 'data-test-subj': 'syntheticsMonitorDeleteAction', isPrimary: true, name: (fields) => ( - + {labels.DELETE_LABEL} ), @@ -192,7 +209,8 @@ export function useMonitorListColumns({ icon: 'trash' as const, type: 'icon' as const, color: 'danger' as const, - enabled: (fields) => canEditSynthetics && !isActionLoading(fields), + enabled: (fields) => + canEditSynthetics && !isActionLoading(fields) && isPublicLocationsAllowed(fields), onClick: (fields) => { setMonitorPendingDeletion(fields); }, @@ -207,7 +225,8 @@ export function useMonitorListColumns({ isStatusEnabled(fields[ConfigKey.ALERT_CONFIG]) ? 'bellSlash' : 'bell', type: 'icon' as const, color: 'danger' as const, - enabled: (fields) => canEditSynthetics && !isActionLoading(fields), + enabled: (fields) => + canEditSynthetics && !isActionLoading(fields) && isPublicLocationsAllowed(fields), onClick: (fields) => { updateAlertEnabledState({ monitor: { @@ -240,6 +259,7 @@ export function useMonitorListColumns({ render: () => ( { const canEditSynthetics = useCanEditSynthetics(); + const canUsePublicLocations = useCanUsePublicLocations(monitor?.[ConfigKey.LOCATIONS]); + const monitorName = monitor[ConfigKey.NAME]; const statusLabels = useMemo(() => { return { @@ -63,11 +68,14 @@ export const MonitorEnabled = ({ {isLoading || initialLoading ? ( ) : ( - + { - const theme = useTheme(); - const { locations: allLocations } = useLocations(); - - const locationsToDisplay = locations - .map((loc) => { - const fullLoc = allLocations.find((l) => l.id === loc.id); - if (fullLoc) { - return { - id: fullLoc.id, - label: fullLoc.label, - ...getLocationStatusColor(theme, fullLoc.label, monitorId, status), - }; - } - }) - .filter(Boolean) as LocationsStatus; +export const MonitorLocations = ({ locations, monitorId, overviewStatus }: Props) => { + const { + eui: { euiColorVis9, euiColorVis0, euiColorDisabled }, + } = useTheme(); + + const locationsToDisplay = locations.map((loc) => { + const locById = `${monitorId}-${loc.id}`; + + let status: string = 'unknown'; + let color = euiColorDisabled; + + if (overviewStatus?.downConfigs[locById]) { + status = 'down'; + color = euiColorVis9; + } else if (overviewStatus?.upConfigs[locById]) { + status = 'up'; + color = euiColorVis0; + } + + return { + id: loc.id, + label: loc.label ?? loc.id, + status, + color, + }; + }); return ( ); }; - -function getLocationStatusColor( - euiTheme: ReturnType, - locationLabel: string | undefined, - monitorId: string, - overviewStatus: OverviewStatusState | null -) { - const { - eui: { euiColorVis9, euiColorVis0, euiColorDisabled }, - } = euiTheme; - - const locById = `${monitorId}-${locationLabel}`; - - if (overviewStatus?.downConfigs[locById]) { - return { status: 'down', color: euiColorVis9 }; - } else if (overviewStatus?.upConfigs[locById]) { - return { status: 'up', color: euiColorVis0 }; - } - - return { status: 'unknown', color: euiColorDisabled }; -} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx index 2a6f71aeca9f2..55dcdb3d06341 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx @@ -14,10 +14,13 @@ import { EuiPanel, EuiLoadingSpinner, EuiContextMenuPanelItemDescriptor, + EuiToolTip, } from '@elastic/eui'; import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; +import { TEST_SCHEDULED_LABEL } from '../../../monitor_add_edit/form/run_test_btn'; +import { useCanUsePublicLocById } from '../../hooks/use_can_use_public_loc_id'; import { toggleStatusAlert } from '../../../../../../../common/runtime_types/monitor_management/alert_config'; import { manualTestMonitorAction, @@ -101,8 +104,7 @@ export function ActionsPopover({ }: Props) { const euiShadow = useEuiShadow('l'); const dispatch = useDispatch(); - const location = useLocationName({ locationId }); - const locationName = location?.label || monitor.location.id; + const locationName = useLocationName(monitor); const detailUrl = useMonitorDetailLocator({ configId: monitor.configId, @@ -112,6 +114,8 @@ export function ActionsPopover({ const canEditSynthetics = useCanEditSynthetics(); + const canUsePublicLocations = useCanUsePublicLocById(monitor.configId); + const labels = useMemo( () => ({ enabledSuccessLabel: enabledSuccessLabel(monitor.name), @@ -163,7 +167,6 @@ export function ActionsPopover({ }; const alertLoading = alertStatus(monitor.configId) === FETCH_STATUS.LOADING; - let popoverItems: EuiContextMenuPanelItemDescriptor[] = [ { name: actionsMenuGoToMonitorName, @@ -172,9 +175,17 @@ export function ActionsPopover({ }, quickInspectPopoverItem, { - name: runTestManually, + name: testInProgress ? ( + + {runTestManually} + + ) : ( + + {runTestManually} + + ), icon: 'beaker', - disabled: testInProgress, + disabled: testInProgress || !canUsePublicLocations, onClick: () => { dispatch(manualTestMonitorAction.get({ configId: monitor.configId, name: monitor.name })); dispatch(setFlyoutConfig(null)); @@ -193,12 +204,15 @@ export function ActionsPopover({ }, { name: ( - + {enableLabel} ), icon: 'invert', - disabled: !canEditSynthetics, + disabled: !canEditSynthetics || !canUsePublicLocations, onClick: () => { if (status !== FETCH_STATUS.LOADING) { updateMonitorEnabledState(!monitor.isEnabled); @@ -207,11 +221,14 @@ export function ActionsPopover({ }, { name: ( - + {monitor.isStatusAlertEnabled ? disableAlertLabel : enableMonitorAlertLabel} ), - disabled: !canEditSynthetics, + disabled: !canEditSynthetics || !canUsePublicLocations, icon: alertLoading ? ( ) : monitor.isStatusAlertEnabled ? ( @@ -244,6 +261,7 @@ export function ActionsPopover({ button={ {testTime} - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx index 8cf6ac4ac0971..c736d47846ada 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx @@ -40,6 +40,7 @@ describe('Monitor Detail Flyout', () => { full: 'https://www.elastic.co', }, tags: ['tag1', 'tag2'], + observer: {}, }, }); jest.spyOn(statusByLocation, 'useStatusByLocation').mockReturnValue({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx index 3fb27e202f996..fefef3c973eb5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx @@ -6,11 +6,7 @@ */ import React from 'react'; import { MetricItem } from './metric_item'; -import { - useLast50DurationChart, - useLocationName, - useStatusByLocationOverview, -} from '../../../../hooks'; +import { useLast50DurationChart, useStatusByLocationOverview } from '../../../../hooks'; import { MonitorOverviewItem } from '../../../../../../../common/runtime_types'; export interface FlyoutParamProps { @@ -27,10 +23,10 @@ export const OverviewGridItem = ({ monitor: MonitorOverviewItem; onClick: (params: FlyoutParamProps) => void; }) => { - const locationName = - useLocationName({ locationId: monitor.location?.id })?.label || monitor.location?.id; - - const { timestamp } = useStatusByLocationOverview(monitor.configId, locationName); + const { timestamp } = useStatusByLocationOverview({ + configId: monitor.configId, + locationId: monitor.location.id, + }); const { data, medianDuration, maxDuration, avgDuration, minDuration } = useLast50DurationChart({ locationId: monitor.location?.id, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_text.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_text.tsx index 71024bf9899a7..42bb72f541648 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_text.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_text.tsx @@ -16,6 +16,7 @@ export const ParamsText = ({ text }: { text: string }) => { , setExpandedRows: (update: Record) => any ) => { + // prevent expanding on row click if not expandable + if (!rowShouldExpand(ping)) { + return; + } + // If already expanded, collapse if (expandedRows[ping.docId]) { delete expandedRows[ping.docId]; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_data_view_context.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_data_view_context.tsx new file mode 100644 index 0000000000000..4e38a3de25388 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_data_view_context.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext } from 'react'; +import { useFetcher } from '@kbn/observability-shared-plugin/public'; +import { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../common/constants'; + +export const SyntheticsDataViewContext = createContext({} as DataView); + +export const SyntheticsDataViewContextProvider: React.FC<{ + dataViews: DataViewsPublicPluginStart; +}> = ({ children, dataViews }) => { + const { data } = useFetcher>(async () => { + return dataViews.create({ title: SYNTHETICS_INDEX_PATTERN }); + }, []); + + return ; +}; + +export const useSyntheticsDataView = () => useContext(SyntheticsDataViewContext); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx index d03b70d94768c..f798880608bcc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import { useLocationName } from './use_location_name'; import { WrappedHelper } from '../utils/testing'; +import { MonitorOverviewItem } from '../../../../common/runtime_types'; describe('useLocationName', () => { beforeEach(() => { @@ -47,16 +48,11 @@ describe('useLocationName', () => { const { result } = renderHook( () => useLocationName({ - locationId: 'us_central', - }), + location: { id: 'us_central' }, + } as MonitorOverviewItem), { wrapper: WrapperWithState } ); - expect(result.current).toEqual({ - id: 'us_central', - isServiceManaged: true, - label: 'US Central', - url: 'mockUrl', - }); + expect(result.current).toEqual('US Central'); }); it('returns the location id if matching location cannot be found', () => { @@ -92,10 +88,10 @@ describe('useLocationName', () => { const { result } = renderHook( () => useLocationName({ - locationId: 'us_west', - }), + location: { id: 'us_west' }, + } as MonitorOverviewItem), { wrapper: WrapperWithState } ); - expect(result.current).toEqual(undefined); + expect(result.current).toEqual('us_west'); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.tsx index 31782083541e2..b25112b15bb10 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.tsx @@ -7,9 +7,10 @@ import { useMemo, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; +import { MonitorOverviewItem } from '../../../../common/runtime_types'; import { selectServiceLocationsState, getServiceLocations } from '../state'; -export function useLocationName({ locationId }: { locationId: string }) { +export function useLocationName(monitor: MonitorOverviewItem) { const dispatch = useDispatch(); const { locationsLoaded, locations } = useSelector(selectServiceLocationsState); useEffect(() => { @@ -17,12 +18,14 @@ export function useLocationName({ locationId }: { locationId: string }) { dispatch(getServiceLocations()); } }); + const locationId = monitor?.location.id; return useMemo(() => { - if (!locationsLoaded) { - return undefined; + if (!locationsLoaded || monitor.location.label) { + return monitor.location.label ?? monitor.location.id; } else { - return locations.find((location) => location.id === locationId); + const location = locations.find((loc) => loc.id === locationId); + return location?.label ?? (monitor.location.label || monitor.location.id); } - }, [locationsLoaded, locations, locationId]); + }, [locationsLoaded, locations, locationId, monitor]); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.test.tsx index 16fdc7cd5a497..30a09853ba27f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.test.tsx @@ -120,34 +120,34 @@ describe('useMonitorsSortedByStatus', () => { [`test-monitor-1-${location2.label}`]: { configId: 'test-monitor-1', monitorQueryId: 'test-monitor-1', - location: location2.label, + locationId: location2.label, }, [`test-monitor-2-${location2.label}`]: { configId: 'test-monitor-2', monitorQueryId: 'test-monitor-2', - location: location2.label, + locationId: location2.label, }, [`test-monitor-3-${location2.label}`]: { configId: 'test-monitor-3', monitorQueryId: 'test-monitor-3', - location: location2.label, + locationId: location2.label, }, }, downConfigs: { [`test-monitor-1-${location1.label}`]: { configId: 'test-monitor-1', monitorQueryId: 'test-monitor-1', - location: location1.label, + locationId: location1.label, }, [`test-monitor-2-${location1.label}`]: { configId: 'test-monitor-2', monitorQueryId: 'test-monitor-2', - location: location1.label, + locationId: location1.label, }, [`test-monitor-3${location1.label}`]: { configId: 'test-monitor-3', monitorQueryId: 'test-monitor-3', - location: location1.label, + locationId: location1.label, }, }, pendingConfigs: { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.tsx index 2148b7e5eeb30..fd4c353a1f91d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_monitors_sorted_by_status.tsx @@ -37,11 +37,11 @@ export function useMonitorsSortedByStatus() { const { downConfigs, pendingConfigs } = status; const downMonitorMap: Record = {}; - Object.values(downConfigs).forEach(({ location, configId }) => { + Object.values(downConfigs).forEach(({ locationId, configId }) => { if (downMonitorMap[configId]) { - downMonitorMap[configId].push(location); + downMonitorMap[configId].push(locationId); } else { - downMonitorMap[configId] = [location]; + downMonitorMap[configId] = [locationId]; } }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location_overview.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location_overview.ts index 019e50d8cbe4d..f7c5b57984a4b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location_overview.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location_overview.ts @@ -9,22 +9,28 @@ import { useSelector } from 'react-redux'; import { OverviewStatusState } from '../../../../common/runtime_types'; import { selectOverviewStatus } from '../state/overview_status'; -export function useStatusByLocationOverview(configId: string, locationName?: string) { +export function useStatusByLocationOverview({ + configId, + locationId, +}: { + configId: string; + locationId: string; +}) { const { status } = useSelector(selectOverviewStatus); - return getConfigStatusByLocation(status, configId, locationName); + return getConfigStatusByLocation(status, configId, locationId); } export const getConfigStatusByLocation = ( status: OverviewStatusState | null, configId: string, - locationName?: string + locationId: string ) => { - if (!locationName || !status) { + if (!status) { return { status: 'unknown' }; } const allConfigs = status.allConfigs; - const configIdByLocation = `${configId}-${locationName}`; + const configIdByLocation = `${configId}-${locationId}`; const config = allConfigs[configIdByLocation]; return { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx index f9b37b64df403..bac16812f1bf2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx @@ -18,6 +18,7 @@ import { import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { InspectorContextProvider } from '@kbn/observability-shared-plugin/public'; import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; +import { SyntheticsDataViewContextProvider } from './contexts/synthetics_data_view_context'; import { SyntheticsAppProps } from './contexts'; import { @@ -99,32 +100,34 @@ const Application = (props: SyntheticsAppProps) => { fleet: startPlugins.fleet, }} > - - - - - - - -
    - - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    + + + + + + + + +
    + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/x-pack/plugins/synthetics/public/hooks/use_capabilities.ts b/x-pack/plugins/synthetics/public/hooks/use_capabilities.ts index 5cde14df84f0e..082f9d9450d7c 100644 --- a/x-pack/plugins/synthetics/public/hooks/use_capabilities.ts +++ b/x-pack/plugins/synthetics/public/hooks/use_capabilities.ts @@ -6,7 +6,20 @@ */ import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { MonitorLocations } from '../../common/runtime_types'; export const useCanEditSynthetics = () => { return !!useKibana().services?.application?.capabilities.uptime.save; }; + +export const useCanUsePublicLocations = (monLocations?: MonitorLocations) => { + const canUsePublicLocations = + useKibana().services?.application?.capabilities.uptime.elasticManagedLocationsEnabled ?? true; + const publicLocations = monLocations?.some((loc) => loc.isServiceManaged); + + if (!publicLocations) { + return true; + } + + return !!canUsePublicLocations; +}; diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index 28ac4ba859e5f..182a6d2645553 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -184,7 +184,7 @@ function registerSyntheticsRoutesWithNavigation( plugins.observabilityShared.navigation.registerSections( from(core.getStartServices()).pipe( map(([coreStart]) => { - if (coreStart.application.capabilities.uptime.show) { + if (coreStart.application.capabilities.uptime?.show) { return [ { label: 'Synthetics', diff --git a/x-pack/plugins/synthetics/server/alert_rules/common.test.ts b/x-pack/plugins/synthetics/server/alert_rules/common.test.ts index f58eb9e4ce3c3..2edcd37a95927 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/common.test.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/common.test.ts @@ -202,7 +202,7 @@ describe('setRecoveredAlertsContext', () => { configId, monitorQueryId: 'stale-config', status: 'up', - location: '', + locationId: '', ping: { '@timestamp': new Date().toISOString(), state: { @@ -235,7 +235,7 @@ describe('setRecoveredAlertsContext', () => { configId, monitorQueryId: 'stale-config', status: 'down', - location: 'location', + locationId: 'location', ping: { '@timestamp': new Date().toISOString(), state: { @@ -294,7 +294,7 @@ describe('setRecoveredAlertsContext', () => { configId, monitorQueryId: 'stale-config', status: 'down', - location: 'location', + locationId: 'location', ping: { '@timestamp': new Date().toISOString(), state: { @@ -355,7 +355,7 @@ describe('setRecoveredAlertsContext', () => { configId, monitorQueryId: 'stale-config', status: 'down', - location: 'location', + locationId: 'location', ping: { state: { id: '123456', diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts index 61173f7b0c227..dde24f409b0e3 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts @@ -93,7 +93,7 @@ export const registerSyntheticsStatusCheckRule = ( ); Object.entries(downConfigs).forEach(([idWithLocation, { ping, configId }]) => { - const locationId = statusRule.getLocationId(ping.observer?.geo?.name!) ?? ''; + const locationId = ping.observer.name ?? ''; const alertId = idWithLocation; const monitorSummary = getMonitorSummary( ping, diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts index bb9183b661b04..ff8ecebccf968 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts @@ -97,7 +97,7 @@ describe('StatusRuleExecutor', () => { const staleDownConfigs = await statusRule.markDeletedConfigs({ id1: { - location: 'us-east-1', + locationId: 'us-east-1', configId: 'id1', status: 'down', timestamp: '2021-06-01T00:00:00.000Z', @@ -105,7 +105,7 @@ describe('StatusRuleExecutor', () => { ping: {} as any, }, '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_dev': { - location: 'US Central DEV', + locationId: 'us_central_dev', configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', status: 'down', timestamp: '2021-06-01T00:00:00.000Z', @@ -113,7 +113,7 @@ describe('StatusRuleExecutor', () => { ping: {} as any, }, '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_qa': { - location: 'US Central QA', + locationId: 'us_central_qa', configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', status: 'down', timestamp: '2021-06-01T00:00:00.000Z', @@ -126,7 +126,7 @@ describe('StatusRuleExecutor', () => { id1: { configId: 'id1', isDeleted: true, - location: 'us-east-1', + locationId: 'us-east-1', monitorQueryId: 'test', ping: {}, status: 'down', @@ -135,7 +135,7 @@ describe('StatusRuleExecutor', () => { '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_dev': { configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', isLocationRemoved: true, - location: 'US Central DEV', + locationId: 'us_central_dev', monitorQueryId: 'test', ping: {}, status: 'down', @@ -175,7 +175,7 @@ describe('StatusRuleExecutor', () => { const staleDownConfigs = await statusRule.markDeletedConfigs({ id1: { - location: 'us-east-1', + locationId: 'us-east-1', configId: 'id1', status: 'down', timestamp: '2021-06-01T00:00:00.000Z', @@ -183,7 +183,7 @@ describe('StatusRuleExecutor', () => { ping: {} as any, }, '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_dev': { - location: 'US Central DEV', + locationId: 'us_central_dev', configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', status: 'down', timestamp: '2021-06-01T00:00:00.000Z', @@ -191,7 +191,7 @@ describe('StatusRuleExecutor', () => { ping: {} as any, }, '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_qa': { - location: 'US Central QA', + locationId: 'us_central_qa', configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', status: 'down', timestamp: '2021-06-01T00:00:00.000Z', @@ -204,7 +204,7 @@ describe('StatusRuleExecutor', () => { id1: { configId: 'id1', isDeleted: true, - location: 'us-east-1', + locationId: 'us-east-1', monitorQueryId: 'test', ping: {}, status: 'down', @@ -213,7 +213,7 @@ describe('StatusRuleExecutor', () => { '2548dab3-4752-4b4d-89a2-ae3402b6fb04-us_central_dev': { configId: '2548dab3-4752-4b4d-89a2-ae3402b6fb04', isLocationRemoved: true, - location: 'US Central DEV', + locationId: 'us_central_dev', monitorQueryId: 'test', ping: {}, status: 'down', diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts index 2bb27ddb4a23b..0861b29c8134d 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts @@ -13,7 +13,6 @@ import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { SyntheticsServerSetup } from '../../types'; import { UptimeEsClient } from '../../lib'; import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants'; -import { getAllLocations } from '../../synthetics_service/get_all_locations'; import { getAllMonitors, processMonitors, @@ -49,8 +48,6 @@ export class StatusRuleExecutor { syntheticsMonitorClient: SyntheticsMonitorClient; monitors: Array> = []; - public locationIdNameMap: Record = {}; - constructor( previousStartedAt: Date | null, p: StatusRuleParams, @@ -69,28 +66,7 @@ export class StatusRuleExecutor { this.syntheticsMonitorClient = syntheticsMonitorClient; } - async getAllLocationNames() { - const { publicLocations, privateLocations } = await getAllLocations({ - server: this.server, - syntheticsMonitorClient: this.syntheticsMonitorClient, - savedObjectsClient: this.soClient, - }); - - publicLocations.forEach((loc) => { - this.locationIdNameMap[loc.label] = loc.id; - }); - - privateLocations.forEach((loc) => { - this.locationIdNameMap[loc.label] = loc.id; - }); - } - - getLocationId(name: string) { - return this.locationIdNameMap[name]; - } - async getMonitors() { - await this.getAllLocationNames(); this.monitors = await getAllMonitors({ soClient: this.soClient, filter: `${monitorAttributes}.${AlertConfigKey.STATUS_ENABLED}: true`, @@ -99,20 +75,15 @@ export class StatusRuleExecutor { const { allIds, enabledMonitorQueryIds, - listOfLocations, + monitorLocationIds, monitorLocationMap, projectMonitorsCount, monitorQueryIdToConfigIdMap, - } = await processMonitors( - this.monitors, - this.server, - this.soClient, - this.syntheticsMonitorClient - ); + } = processMonitors(this.monitors, this.server, this.soClient, this.syntheticsMonitorClient); return { enabledMonitorQueryIds, - listOfLocations, + monitorLocationIds, allIds, monitorLocationMap, projectMonitorsCount, @@ -124,7 +95,7 @@ export class StatusRuleExecutor { prevDownConfigs: OverviewStatus['downConfigs'] = {} ): Promise { const { - listOfLocations, + monitorLocationIds, enabledMonitorQueryIds, allIds, monitorLocationMap, @@ -138,7 +109,7 @@ export class StatusRuleExecutor { if (enabledMonitorQueryIds.length > 0) { const currentStatus = await queryMonitorStatus( this.esClient, - listOfLocations, + monitorLocationIds, { to: 'now', from, @@ -201,9 +172,7 @@ export class StatusRuleExecutor { delete downConfigs[locPlusId]; } else { const { locations } = monitor.attributes; - const isLocationRemoved = !locations.some( - (l) => l.id === this.getLocationId(downConfig.location) - ); + const isLocationRemoved = !locations.some((l) => l.id === downConfig.locationId); if (isLocationRemoved) { staleDownConfigs[locPlusId] = { ...downConfig, isLocationRemoved: true }; delete downConfigs[locPlusId]; diff --git a/x-pack/plugins/synthetics/server/alert_rules/tls_rule/tls_rule_executor.ts b/x-pack/plugins/synthetics/server/alert_rules/tls_rule/tls_rule_executor.ts index dbd97c2e0b51c..0ca15a53efc0a 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/tls_rule/tls_rule_executor.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/tls_rule/tls_rule_executor.ts @@ -70,20 +70,15 @@ export class TLSRuleExecutor { const { allIds, enabledMonitorQueryIds, - listOfLocations, + monitorLocationIds, monitorLocationMap, projectMonitorsCount, monitorQueryIdToConfigIdMap, - } = await processMonitors( - this.monitors, - this.server, - this.soClient, - this.syntheticsMonitorClient - ); + } = processMonitors(this.monitors, this.server, this.soClient, this.syntheticsMonitorClient); return { enabledMonitorQueryIds, - listOfLocations, + monitorLocationIds, allIds, monitorLocationMap, projectMonitorsCount, diff --git a/x-pack/plugins/synthetics/server/feature.ts b/x-pack/plugins/synthetics/server/feature.ts index 36a5888928680..1a19baa01dcbe 100644 --- a/x-pack/plugins/synthetics/server/feature.ts +++ b/x-pack/plugins/synthetics/server/feature.ts @@ -7,6 +7,11 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { i18n } from '@kbn/i18n'; +import { + SubFeaturePrivilegeGroupConfig, + SubFeaturePrivilegeGroupType, +} from '@kbn/features-plugin/common'; import { syntheticsMonitorType, syntheticsParamType } from '../common/types/saved_objects'; import { SYNTHETICS_RULE_TYPES } from '../common/constants/synthetics_alerts'; import { privateLocationsSavedObjectName } from '../common/saved_objects/private_locations'; @@ -27,6 +32,24 @@ const ruleTypes = [ OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, ]; +const elasticManagedLocationsEnabledPrivilege: SubFeaturePrivilegeGroupConfig = { + groupType: 'independent' as SubFeaturePrivilegeGroupType, + privileges: [ + { + id: 'elastic_managed_locations_enabled', + name: i18n.translate('xpack.synthetics.features.elasticManagedLocations', { + defaultMessage: 'Elastic managed locations enabled', + }), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + ui: ['elasticManagedLocationsEnabled'], + }, + ], +}; + export const uptimeFeature = { id: PLUGIN.ID, name: PLUGIN.NAME, @@ -94,4 +117,12 @@ export const uptimeFeature = { ui: ['show', 'alerting:save'], }, }, + subFeatures: [ + { + name: i18n.translate('xpack.synthetics.features.app', { + defaultMessage: 'Synthetics', + }), + privilegeGroups: [elasticManagedLocationsEnabledPrivilege], + }, + ], }; diff --git a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts index b714177e6cee9..e18e4f23b34c8 100644 --- a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts +++ b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts @@ -34,7 +34,7 @@ const fields = [ export async function queryMonitorStatus( esClient: UptimeEsClient, - listOfLocations: string[], + monitorLocationIds: string[], range: { from: string; to: string }, monitorQueryIds: string[], monitorLocationsMap: Record, @@ -50,7 +50,7 @@ export async function queryMonitorStatus( | 'allIds' > > { - const idSize = Math.trunc(DEFAULT_MAX_ES_BUCKET_SIZE / listOfLocations.length || 1); + const idSize = Math.trunc(DEFAULT_MAX_ES_BUCKET_SIZE / monitorLocationIds.length || 1); const pageCount = Math.ceil(monitorQueryIds.length / idSize); let up = 0; let down = 0; @@ -95,8 +95,8 @@ export async function queryMonitorStatus( aggs: { location: { terms: { - field: 'observer.geo.name', - size: listOfLocations.length || 100, + field: 'observer.name', + size: monitorLocationIds.length || 100, }, aggs: { status: { @@ -122,10 +122,10 @@ export async function queryMonitorStatus( }, }); - if (listOfLocations.length > 0) { + if (monitorLocationIds.length > 0) { params.body.query.bool.filter.push({ terms: { - 'observer.geo.name': listOfLocations, + 'observer.name': monitorLocationIds, }, }); } @@ -144,7 +144,7 @@ export async function queryMonitorStatus( // discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are // in monitorLocationsMap but not in listOfLocations const monLocations = monitorLocationsMap?.[queryId]; - const monQueriedLocations = intersection(monLocations, listOfLocations); + const monQueriedLocations = intersection(monLocations, monitorLocationIds); monQueriedLocations?.forEach((monLocation) => { const locationSummary = locationSummaries.find( (summary) => summary.location === monLocation @@ -154,14 +154,14 @@ export async function queryMonitorStatus( const { ping } = locationSummary; const downCount = ping.summary?.down ?? 0; const upCount = ping.summary?.up ?? 0; - const configId = ping.config_id!; + const configId = ping.config_id; const monitorQueryId = ping.monitor.id; const meta = { ping, configId, monitorQueryId, - location: monLocation, + locationId: monLocation, timestamp: ping['@timestamp'], }; diff --git a/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts b/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts index 982d9f60f90ff..405863865757d 100644 --- a/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts +++ b/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts @@ -57,7 +57,7 @@ export const getSyntheticsCertsRoute: SyntheticsRestApiRouteFactory< }; } - const { enabledMonitorQueryIds } = await processMonitors( + const { enabledMonitorQueryIds } = processMonitors( monitors, server, savedObjectsClient, diff --git a/x-pack/plugins/synthetics/server/routes/common.ts b/x-pack/plugins/synthetics/server/routes/common.ts index 84fa407296f09..b29e3578aba87 100644 --- a/x-pack/plugins/synthetics/server/routes/common.ts +++ b/x-pack/plugins/synthetics/server/routes/common.ts @@ -81,7 +81,7 @@ export const getMonitors = async ( monitorQueryIds, } = context.request.query; - const filterStr = await getMonitorFilters({ + const { filtersStr } = await getMonitorFilters({ filter, monitorTypes, tags, @@ -100,7 +100,7 @@ export const getMonitors = async ( sortOrder, searchFields: SEARCH_FIELDS, search: query ? `${query}*` : undefined, - filter: filterStr, + filter: filtersStr, searchAfter, fields, }; @@ -129,7 +129,7 @@ export const getMonitorFilters = async ({ }) => { const locationFilter = await parseLocationFilter(context, locations); - return [ + const filtersStr = [ filter, getKqlFilter({ field: 'tags', values: tags }), getKqlFilter({ field: 'project_id', values: projects }), @@ -140,6 +140,7 @@ export const getMonitorFilters = async ({ ] .filter((f) => !!f) .join(' AND '); + return { filtersStr, locationFilter }; }; export const getKqlFilter = ({ @@ -172,7 +173,7 @@ export const getKqlFilter = ({ const parseLocationFilter = async (context: RouteContext, locations?: string | string[]) => { if (!locations || locations?.length === 0) { - return ''; + return; } const { allLocations } = await getAllLocations(context); @@ -183,7 +184,7 @@ const parseLocationFilter = async (context: RouteContext, locations?: string | s .filter((val) => !!val); } - return findLocationItem(locations, allLocations)?.id ?? ''; + return [findLocationItem(locations, allLocations)?.id ?? '']; }; export const findLocationItem = ( diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts index 4841ca581c077..0d5825dbbdd3f 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts @@ -6,6 +6,7 @@ */ import { schema } from '@kbn/config-schema'; import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { validatePermissions } from './edit_monitor'; import { SyntheticsServerSetup } from '../../types'; import { RouteContext, SyntheticsRestApiRouteFactory } from '../types'; import { syntheticsMonitorType } from '../../../common/types/saved_objects'; @@ -39,10 +40,13 @@ export const deleteSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => const { monitorId } = request.params; try { - const errors = await deleteMonitor({ + const { errors, res } = await deleteMonitor({ routeContext, monitorId, }); + if (res) { + return res; + } if (errors && errors.length > 0) { return response.ok({ @@ -68,7 +72,7 @@ export const deleteMonitor = async ({ routeContext: RouteContext; monitorId: string; }) => { - const { spaceId, savedObjectsClient, server, syntheticsMonitorClient } = routeContext; + const { response, spaceId, savedObjectsClient, server, syntheticsMonitorClient } = routeContext; const { logger, telemetry, stackVersion } = server; const { monitor, monitorWithSecret } = await getMonitorToDelete( @@ -78,6 +82,17 @@ export const deleteMonitor = async ({ spaceId ); + const err = await validatePermissions(routeContext, monitor.attributes.locations); + if (err) { + return { + res: response.forbidden({ + body: { + message: err, + }, + }), + }; + } + let deletePromise; try { @@ -113,7 +128,7 @@ export const deleteMonitor = async ({ ) ); - return errors; + return { errors }; } catch (e) { if (deletePromise) { await deletePromise; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts index d99261b49f2cf..69960d458c684 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsUpdateResponse, SavedObject } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import { getDecryptedMonitor } from '../../saved_objects/synthetics_monitor'; import { getPrivateLocations } from '../../synthetics_service/get_private_locations'; import { mergeSourceMonitor } from './helper'; import { RouteContext, SyntheticsRestApiRouteFactory } from '../types'; @@ -18,6 +19,7 @@ import { SyntheticsMonitorWithSecretsAttributes, SyntheticsMonitor, ConfigKey, + MonitorLocations, } from '../../../common/runtime_types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; import { validateMonitor } from './monitor_validation'; @@ -39,10 +41,10 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( }), body: schema.any(), }, + writeAccess: true, handler: async (routeContext): Promise => { const { request, response, savedObjectsClient, server } = routeContext; - const { encryptedSavedObjects, logger } = server; - const encryptedSavedObjectsClient = encryptedSavedObjects.getClient(); + const { logger } = server; const monitor = request.body as SyntheticsMonitor; const { monitorId } = request.params; @@ -55,14 +57,11 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( /* Decrypting the previous monitor before editing ensures that all existing fields remain * on the object, even in flows where decryption does not take place, such as the enabled tab * on the monitor list table. We do not decrypt monitors in bulk for the monitor list table */ - const decryptedPreviousMonitor = - await encryptedSavedObjectsClient.getDecryptedAsInternalUser( - syntheticsMonitorType, - monitorId, - { - namespace: previousMonitor.namespaces?.[0], - } - ); + const decryptedPreviousMonitor = await getDecryptedMonitor( + server, + monitorId, + previousMonitor.namespaces?.[0]! + ); const normalizedPreviousMonitor = normalizeSecrets(decryptedPreviousMonitor).attributes; const editedMonitor = mergeSourceMonitor(normalizedPreviousMonitor, monitor); @@ -74,6 +73,15 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( return response.badRequest({ body: { message, attributes: { details, ...payload } } }); } + const err = await validatePermissions(routeContext, editedMonitor.locations); + if (err) { + return response.forbidden({ + body: { + message: err, + }, + }); + } + const monitorWithRevision = { ...validationResult.decodedMonitor, /* reset config hash to empty string. Ensures that the synthetics agent is able @@ -230,3 +238,22 @@ export const syncEditedMonitor = async ({ throw e; } }; + +export const validatePermissions = async ( + { server, response, request }: RouteContext, + monitorLocations: MonitorLocations +) => { + const hasPublicLocations = monitorLocations?.some((loc) => loc.isServiceManaged); + if (!hasPublicLocations) { + return; + } + + const elasticManagedLocationsEnabled = + Boolean( + (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime + .elasticManagedLocationsEnabled + ) ?? true; + if (!elasticManagedLocationsEnabled) { + return "You don't have permission to use public locations"; + } +}; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts index 770fb102c5369..549c812eec27f 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts @@ -91,7 +91,7 @@ export const getSyntheticsMonitorOverviewRoute: SyntheticsRestApiRouteFactory = locations: queriedLocations, } = request.query as MonitorsQuery; - const filtersStr = await getMonitorFilters({ + const { filtersStr } = await getMonitorFilters({ ...request.query, context: routeContext, }); diff --git a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts index 983ae3cb3d854..02e5dfa61e2bc 100644 --- a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts +++ b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts @@ -15,39 +15,80 @@ import { EncryptedSyntheticsMonitorAttributes } from '../../../common/runtime_ty import { RouteContext } from '../types'; import { getUptimeESMockClient } from '../../legacy_uptime/lib/requests/test_helpers'; +import * as commonLibs from '../common'; +import { SyntheticsServerSetup } from '../../types'; +import { mockEncryptedSO } from '../../synthetics_service/utils/mocks'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; +import { loggerMock } from '@kbn/logging-mocks'; +import * as allLocationsFn from '../../synthetics_service/get_all_locations'; +const allLocations: any = [ + { + id: 'us_central_qa', + label: 'US Central QA', + }, + { + id: 'us_central', + label: 'North America - US Central', + }, +]; +jest.spyOn(allLocationsFn, 'getAllLocations').mockResolvedValue({ + publicLocations: allLocations, + privateLocations: [], + allLocations, +}); + jest.mock('../../saved_objects/synthetics_monitor/get_all_monitors', () => ({ ...jest.requireActual('../../saved_objects/synthetics_monitor/get_all_monitors'), getAllMonitors: jest.fn(), })); -jest.mock('../common', () => ({ - getMonitors: jest.fn().mockReturnValue({ - per_page: 10, - saved_objects: [ - { - id: 'mon-1', - attributes: { - enabled: false, - locations: ['us-east1', 'us-west1', 'japan'], - }, +jest.spyOn(commonLibs, 'getMonitors').mockResolvedValue({ + per_page: 10, + saved_objects: [ + { + id: 'mon-1', + attributes: { + enabled: false, + locations: [{ id: 'us-east1' }, { id: 'us-west1' }, { id: 'japan' }], }, - { - id: 'mon-2', - attributes: { - enabled: true, - locations: ['us-east1', 'us-west1', 'japan'], - schedule: { - number: '10', - unit: 'm', - }, + }, + { + id: 'mon-2', + attributes: { + enabled: true, + locations: [{ id: 'us-east1' }, { id: 'us-west1' }, { id: 'japan' }], + schedule: { + number: '10', + unit: 'm', }, }, - ], - }), - getMonitorFilters: () => '', -})); + }, + ], +} as any); describe('current status route', () => { + const logger = loggerMock.create(); + + const serverMock: SyntheticsServerSetup = { + logger, + config: { + service: { + username: 'dev', + password: '12345', + manifestUrl: 'http://localhost:8080/api/manifest', + }, + }, + spaces: { + spacesService: { + getSpaceId: jest.fn().mockReturnValue('test-space'), + }, + }, + encryptedSavedObjects: mockEncryptedSO(), + coreStart: { + savedObjects: savedObjectsServiceMock.createStartContract(), + }, + } as unknown as SyntheticsServerSetup; + describe('periodToMs', () => { it('returns 0 for unsupported unit type', () => { // @ts-expect-error Providing invalid value to test handler in function @@ -190,7 +231,7 @@ describe('current status route', () => { 'id1-Asia/Pacific - Japan': { configId: 'id1', monitorQueryId: 'id1', - location: 'Asia/Pacific - Japan', + locationId: 'Asia/Pacific - Japan', status: 'up', ping: expect.any(Object), timestamp: expect.any(String), @@ -198,7 +239,7 @@ describe('current status route', () => { 'id2-Asia/Pacific - Japan': { configId: 'id2', monitorQueryId: 'id2', - location: 'Asia/Pacific - Japan', + locationId: 'Asia/Pacific - Japan', status: 'up', ping: expect.any(Object), timestamp: expect.any(String), @@ -208,7 +249,7 @@ describe('current status route', () => { 'id2-Europe - Germany': { configId: 'id2', monitorQueryId: 'id2', - location: 'Europe - Germany', + locationId: 'Europe - Germany', status: 'down', ping: expect.any(Object), timestamp: expect.any(String), @@ -355,7 +396,7 @@ describe('current status route', () => { 'id1-Asia/Pacific - Japan': { configId: 'id1', monitorQueryId: 'id1', - location: 'Asia/Pacific - Japan', + locationId: 'Asia/Pacific - Japan', status: 'up', ping: expect.any(Object), timestamp: expect.any(String), @@ -363,7 +404,7 @@ describe('current status route', () => { 'id2-Asia/Pacific - Japan': { configId: 'id2', monitorQueryId: 'id2', - location: 'Asia/Pacific - Japan', + locationId: 'Asia/Pacific - Japan', status: 'up', ping: expect.any(Object), timestamp: expect.any(String), @@ -373,7 +414,7 @@ describe('current status route', () => { 'id2-Europe - Germany': { configId: 'id2', monitorQueryId: 'id2', - location: 'Europe - Germany', + locationId: 'Europe - Germany', status: 'down', ping: expect.any(Object), timestamp: expect.any(String), @@ -525,7 +566,7 @@ describe('current status route', () => { 'id1-Asia/Pacific - Japan': { configId: 'id1', monitorQueryId: 'id1', - location: 'Asia/Pacific - Japan', + locationId: 'Asia/Pacific - Japan', status: 'up', ping: expect.any(Object), timestamp: expect.any(String), @@ -533,7 +574,7 @@ describe('current status route', () => { 'id2-Asia/Pacific - Japan': { configId: 'id2', monitorQueryId: 'id2', - location: 'Asia/Pacific - Japan', + locationId: 'Asia/Pacific - Japan', status: 'up', ping: expect.any(Object), timestamp: expect.any(String), @@ -543,7 +584,7 @@ describe('current status route', () => { 'id2-Europe - Germany': { configId: 'id2', monitorQueryId: 'id2', - location: 'Europe - Germany', + locationId: 'Europe - Germany', status: 'down', ping: expect.any(Object), timestamp: expect.any(String), @@ -729,17 +770,17 @@ describe('current status route', () => { }, ]) ); - expect( - await getStatus( - { - uptimeEsClient, - savedObjectsClient: savedObjectsClientMock.create(), - } as unknown as RouteContext, - { - locations, - } - ) - ).toEqual( + const result = await getStatus( + { + uptimeEsClient, + savedObjectsClient: savedObjectsClientMock.create(), + server: serverMock, + } as unknown as RouteContext, + { + locations, + } + ); + expect(result).toEqual( expect.objectContaining({ disabledCount, }) diff --git a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts index 4de7341d10626..7d28d9b4f0576 100644 --- a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts +++ b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts @@ -38,9 +38,8 @@ export function periodToMs(schedule: { number: string; unit: Unit }) { export async function getStatus(context: RouteContext, params: OverviewStatusQuery) { const { uptimeEsClient, syntheticsMonitorClient, savedObjectsClient, server } = context; - const { query, locations: qLocations, scopeStatusByLocation = true } = params; + const { query, scopeStatusByLocation = true } = params; - const queryLocations = qLocations && !Array.isArray(qLocations) ? [qLocations] : qLocations; /** * Walk through all monitor saved objects, bucket IDs by disabled/enabled status. * @@ -48,7 +47,7 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue * latest ping for all enabled monitors. */ - const filtersStr = await getMonitorFilters({ + const { filtersStr, locationFilter: queryLocations } = await getMonitorFilters({ ...params, context, }); @@ -73,12 +72,12 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue allIds, disabledCount, maxPeriod, - listOfLocations, + monitorLocationIds, monitorLocationMap, disabledMonitorsCount, projectMonitorsCount, monitorQueryIdToConfigIdMap, - } = await processMonitors( + } = processMonitors( allMonitors, server, savedObjectsClient, @@ -89,8 +88,8 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue // Account for locations filter const listOfLocationAfterFilter = queryLocations && scopeStatusByLocation - ? intersection(listOfLocations, queryLocations) - : listOfLocations; + ? intersection(monitorLocationIds, queryLocations) + : monitorLocationIds; const range = { from: moment().subtract(maxPeriod, 'milliseconds').subtract(20, 'minutes').toISOString(), diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts index 01f4946b3508f..8c4df4a2461f8 100644 --- a/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts +++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { toClientContract } from '../settings/private_locations/helpers'; +import { getPrivateLocationsAndAgentPolicies } from '../settings/private_locations/get_private_locations'; import { SyntheticsRestApiRouteFactory } from '../types'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; @@ -13,16 +15,37 @@ export const getServiceLocationsRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'GET', path: SYNTHETICS_API_URLS.SERVICE_LOCATIONS, validate: {}, - handler: async ({ server, savedObjectsClient, syntheticsMonitorClient }): Promise => { - const { throttling, allLocations } = await getAllLocations({ - server, - syntheticsMonitorClient, - savedObjectsClient, - }); + handler: async ({ + request, + server, + savedObjectsClient, + syntheticsMonitorClient, + }): Promise => { + const elasticManagedLocationsEnabled = + Boolean( + (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime + .elasticManagedLocationsEnabled + ) ?? true; - return { - locations: allLocations, - throttling, - }; + if (elasticManagedLocationsEnabled) { + const { throttling, allLocations } = await getAllLocations({ + server, + syntheticsMonitorClient, + savedObjectsClient, + }); + + return { + locations: allLocations, + throttling, + }; + } else { + const { locations: privateLocations, agentPolicies } = + await getPrivateLocationsAndAgentPolicies(savedObjectsClient, syntheticsMonitorClient); + + const result = toClientContract({ locations: privateLocations }, agentPolicies).locations; + return { + locations: result, + }; + } }, }); diff --git a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts index 1d25503159c58..33af8075616da 100644 --- a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts +++ b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts @@ -45,7 +45,7 @@ describe('processMonitors', () => { const monitorClient = new SyntheticsMonitorClient(syntheticsService, serverMock); it('should return a processed data', async () => { - const result = await processMonitors(testMonitors, serverMock, soClient, monitorClient); + const result = processMonitors(testMonitors, serverMock, soClient, monitorClient); expect(result).toEqual({ allIds: [ 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22', @@ -60,15 +60,15 @@ describe('processMonitors', () => { '7f796001-a795-4c0b-afdb-3ce74edea775', ], disabledMonitorQueryIds: ['test-project-id-default'], - listOfLocations: ['US Central QA', 'US Central Staging', 'North America - US Central'], + monitorLocationIds: ['us_central_qa', 'us_central_staging', 'us_central'], maxPeriod: 600000, monitorLocationMap: { '7f796001-a795-4c0b-afdb-3ce74edea775': [ - 'US Central QA', - 'North America - US Central', - 'US Central Staging', + 'us_central_qa', + 'us_central', + 'us_central_staging', ], - 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22': ['US Central QA', 'US Central Staging'], + 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22': ['us_central_qa', 'us_central_staging'], }, monitorQueryIdToConfigIdMap: { '7f796001-a795-4c0b-afdb-3ce74edea775': '7f796001-a795-4c0b-afdb-3ce74edea775', @@ -81,7 +81,7 @@ describe('processMonitors', () => { it('should return a processed data where location label is missing', async () => { testMonitors[0].attributes.locations[0].label = undefined; - const result = await processMonitors(testMonitors, serverMock, soClient, monitorClient); + const result = processMonitors(testMonitors, serverMock, soClient, monitorClient); expect(result).toEqual({ allIds: [ 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22', @@ -96,20 +96,15 @@ describe('processMonitors', () => { '7f796001-a795-4c0b-afdb-3ce74edea775', ], disabledMonitorQueryIds: ['test-project-id-default'], - listOfLocations: [ - 'US Central Staging', - 'us_central_qa', - 'US Central QA', - 'North America - US Central', - ], + monitorLocationIds: ['us_central_qa', 'us_central_staging', 'us_central'], maxPeriod: 600000, monitorLocationMap: { '7f796001-a795-4c0b-afdb-3ce74edea775': [ - 'US Central QA', - 'North America - US Central', - 'US Central Staging', + 'us_central_qa', + 'us_central', + 'us_central_staging', ], - 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22': ['US Central Staging', 'us_central_qa'], + 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22': ['us_central_qa', 'us_central_staging'], }, monitorQueryIdToConfigIdMap: { '7f796001-a795-4c0b-afdb-3ce74edea775': '7f796001-a795-4c0b-afdb-3ce74edea775', @@ -160,7 +155,7 @@ describe('processMonitors', () => { ) ); - const result = await processMonitors(testMonitors, serverMock, soClient, monitorClient); + const result = processMonitors(testMonitors, serverMock, soClient, monitorClient); expect(result).toEqual({ allIds: [ 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22', @@ -175,15 +170,15 @@ describe('processMonitors', () => { '7f796001-a795-4c0b-afdb-3ce74edea775', ], disabledMonitorQueryIds: ['test-project-id-default'], - listOfLocations: ['US Central Staging', 'US Central QA', 'North America - US Central'], + monitorLocationIds: ['us_central_qa', 'us_central_staging', 'us_central'], maxPeriod: 600000, monitorLocationMap: { '7f796001-a795-4c0b-afdb-3ce74edea775': [ - 'US Central QA', - 'North America - US Central', - 'US Central Staging', + 'us_central_qa', + 'us_central', + 'us_central_staging', ], - 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22': ['US Central Staging', 'US Central QA'], + 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22': ['us_central_qa', 'us_central_staging'], }, monitorQueryIdToConfigIdMap: { '7f796001-a795-4c0b-afdb-3ce74edea775': '7f796001-a795-4c0b-afdb-3ce74edea775', diff --git a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts index 9e629ba304a14..b8e9a08bf3dd1 100644 --- a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts +++ b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts @@ -10,16 +10,13 @@ import { SavedObjectsFindOptions, SavedObjectsFindResult, } from '@kbn/core-saved-objects-api-server'; -import pMap from 'p-map'; import { intersection } from 'lodash'; import { SyntheticsServerSetup } from '../../types'; import { syntheticsMonitorType } from '../../../common/types/saved_objects'; import { periodToMs } from '../../routes/overview_status/overview_status'; -import { getAllLocations } from '../../synthetics_service/get_all_locations'; import { ConfigKey, EncryptedSyntheticsMonitorAttributes, - ServiceLocation, SourceType, } from '../../../common/runtime_types'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; @@ -59,7 +56,7 @@ export const getAllMonitors = async ({ return hits; }; -export const processMonitors = async ( +export const processMonitors = ( allMonitors: Array>, server: SyntheticsServerSetup, soClient: SavedObjectsClientContract, @@ -83,22 +80,6 @@ export const processMonitors = async ( const monitorLocationMap: Record = {}; const monitorQueryIdToConfigIdMap: Record = {}; - let allLocations: ServiceLocation[] | null = null; - - const getLocationLabel = async (locationId: string) => { - if (!allLocations) { - const { publicLocations, privateLocations } = await getAllLocations({ - server, - syntheticsMonitorClient, - savedObjectsClient: soClient, - }); - - allLocations = [...publicLocations, ...privateLocations]; - } - - return allLocations.find((loc) => loc.id === locationId)?.label ?? locationId; - }; - for (const monitor of allMonitors) { const attrs = monitor.attributes; @@ -108,8 +89,9 @@ export const processMonitors = async ( monitorQueryIdToConfigIdMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = attrs[ConfigKey.CONFIG_ID]; + const monitorLocations = attrs[ConfigKey.LOCATIONS].map((location) => location.id); + if (attrs[ConfigKey.ENABLED] === false) { - const monitorLocations = attrs[ConfigKey.LOCATIONS].map((location) => location.label); const queriedLocations = Array.isArray(queryLocations) ? queryLocations : [queryLocations]; const intersectingLocations = intersection( monitorLocations, @@ -119,32 +101,12 @@ export const processMonitors = async ( disabledMonitorsCount += 1; disabledMonitorQueryIds.push(attrs[ConfigKey.MONITOR_QUERY_ID]); } else { - const missingLabels = new Set(); - enabledMonitorQueryIds.push(attrs[ConfigKey.MONITOR_QUERY_ID]); - const monLocs = new Set([ - ...(attrs[ConfigKey.LOCATIONS] - .filter((loc) => { - if (!loc.label) { - missingLabels.add(loc.id); - } - return loc.label; - }) - .map((location) => location.label) as string[]), - ]); - - // since label wasn't always part of location, there can be a case where we have a location - // with an id but no label. We need to fetch the label from the API - // Adding a migration to add the label to the saved object is a future consideration - const locLabels = await pMap([...missingLabels], async (locationId) => - getLocationLabel(locationId) - ); - const monitorLocations = [...monLocs, ...locLabels]; monitorLocationMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = queryLocations ? intersection(monitorLocations, queryLocations) : monitorLocations; - listOfLocationsSet = new Set([...listOfLocationsSet, ...monLocs, ...locLabels]); + listOfLocationsSet = new Set([...listOfLocationsSet, ...monitorLocations]); maxPeriod = Math.max(maxPeriod, periodToMs(attrs[ConfigKey.SCHEDULE])); } @@ -159,7 +121,7 @@ export const processMonitors = async ( monitorLocationMap, disabledMonitorsCount, projectMonitorsCount, - listOfLocations: [...listOfLocationsSet], + monitorLocationIds: [...listOfLocationsSet], monitorQueryIdToConfigIdMap, }; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_all_locations.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_all_locations.ts index 491bde96d4067..6eb6a62547156 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_all_locations.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_all_locations.ts @@ -47,6 +47,11 @@ const getServicePublicLocations = async ( server: SyntheticsServerSetup, syntheticsMonitorClient: SyntheticsMonitorClient ) => { + if (!syntheticsMonitorClient.syntheticsService.isAllowed) { + return { + locations: [], + }; + } if (syntheticsMonitorClient.syntheticsService.locations.length === 0) { return await getServiceLocations(server); } diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index eae3fb0082030..561a0e5a90e4c 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -608,7 +608,7 @@ export class SyntheticsService { await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser({ type: syntheticsParamType, perPage: 1000, - namespaces: spaceId ? [spaceId] : undefined, + namespaces: spaceId ? [spaceId] : [ALL_SPACES_ID], }); for await (const response of finder.find()) { diff --git a/x-pack/plugins/synthetics/tsconfig.json b/x-pack/plugins/synthetics/tsconfig.json index 12bc371fef915..ff288ec3cee97 100644 --- a/x-pack/plugins/synthetics/tsconfig.json +++ b/x-pack/plugins/synthetics/tsconfig.json @@ -77,6 +77,8 @@ "@kbn/core-saved-objects-server-mocks", "@kbn/shared-ux-page-kibana-template", "@kbn/observability-ai-assistant-plugin", + "@kbn/unified-doc-viewer-plugin", + "@kbn/discover-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index 6a06ea93f3dcb..39a9afb0f0d14 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -190,6 +190,7 @@ describe('EphemeralTaskLifecycle', () => { task: taskManagerMock.createTask(), result: TaskRunResult.Success, persistence: TaskPersistence.Ephemeral, + isExpired: false, }) ) ); diff --git a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts index 9671698329447..e457797d5ae1b 100644 --- a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts @@ -6,20 +6,30 @@ */ import sinon from 'sinon'; -import { Subject, Observable } from 'rxjs'; +import { Subject } from 'rxjs'; import { take, bufferCount, skip } from 'rxjs/operators'; -import { isTaskPollingCycleEvent, isTaskRunEvent } from '../task_events'; +import { + isTaskManagerMetricEvent, + isTaskManagerStatEvent, + isTaskPollingCycleEvent, + isTaskRunEvent, +} from '../task_events'; import { TaskLifecycleEvent } from '../polling_lifecycle'; import { AggregatedStat } from '../lib/runtime_statistics_aggregator'; -import { taskPollingLifecycleMock } from '../polling_lifecycle.mock'; import { TaskManagerConfig } from '../config'; import { createAggregator } from './create_aggregator'; import { TaskClaimMetric, TaskClaimMetricsAggregator } from './task_claim_metrics_aggregator'; import { taskClaimFailureEvent, taskClaimSuccessEvent } from './task_claim_metrics_aggregator.test'; -import { getTaskRunFailedEvent, getTaskRunSuccessEvent } from './task_run_metrics_aggregator.test'; +import { + getTaskRunFailedEvent, + getTaskRunSuccessEvent, + getTaskManagerStatEvent, +} from './task_run_metrics_aggregator.test'; import { TaskRunMetric, TaskRunMetricsAggregator } from './task_run_metrics_aggregator'; import * as TaskClaimMetricsAggregatorModule from './task_claim_metrics_aggregator'; import { metricsAggregatorMock } from './metrics_aggregator.mock'; +import { getTaskManagerMetricEvent } from './task_overdue_metrics_aggregator.test'; +import { TaskOverdueMetric, TaskOverdueMetricsAggregator } from './task_overdue_metrics_aggregator'; const mockMetricsAggregator = metricsAggregatorMock.create(); const config: TaskManagerConfig = { @@ -72,7 +82,7 @@ describe('createAggregator', () => { describe('with TaskClaimMetricsAggregator', () => { test('returns a cumulative count of successful polling cycles and total polling cycles', async () => { - const pollingCycleEvents = [ + const events = [ taskClaimSuccessEvent, taskClaimSuccessEvent, taskClaimSuccessEvent, @@ -86,16 +96,13 @@ describe('createAggregator', () => { taskClaimSuccessEvent, ]; const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); const taskClaimAggregator = createAggregator({ key: 'task_claim', - taskPollingLifecycle, + events$, config, - resetMetrics$: new Subject(), - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskPollingCycleEvent(taskEvent), + reset$: new Subject(), + eventFilter: (event: TaskLifecycleEvent) => isTaskPollingCycleEvent(event), metricsAggregator: new TaskClaimMetricsAggregator(), }); @@ -105,8 +112,8 @@ describe('createAggregator', () => { // skip initial metric which is just initialized data which // ensures we don't stall on combineLatest skip(1), - take(pollingCycleEvents.length), - bufferCount(pollingCycleEvents.length) + take(events.length), + bufferCount(events.length) ) .subscribe((metrics: Array>) => { expect(metrics[0]).toEqual({ @@ -156,15 +163,15 @@ describe('createAggregator', () => { resolve(); }); - for (const event of pollingCycleEvents) { + for (const event of events) { events$.next(event); } }); }); - test('resets count when resetMetric$ event is received', async () => { - const resetMetrics$ = new Subject(); - const pollingCycleEvents1 = [ + test('resets count when reset$ event is received', async () => { + const reset$ = new Subject(); + const events1 = [ taskClaimSuccessEvent, taskClaimSuccessEvent, taskClaimSuccessEvent, @@ -173,7 +180,7 @@ describe('createAggregator', () => { taskClaimSuccessEvent, ]; - const pollingCycleEvents2 = [ + const events2 = [ taskClaimSuccessEvent, taskClaimFailureEvent, taskClaimFailureEvent, @@ -181,16 +188,13 @@ describe('createAggregator', () => { taskClaimSuccessEvent, ]; const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); const taskClaimAggregator = createAggregator({ key: 'task_claim', - taskPollingLifecycle, + events$, config, - resetMetrics$, - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskPollingCycleEvent(taskEvent), + reset$, + eventFilter: (event: TaskLifecycleEvent) => isTaskPollingCycleEvent(event), metricsAggregator: new TaskClaimMetricsAggregator(), }); @@ -200,8 +204,8 @@ describe('createAggregator', () => { // skip initial metric which is just initialized data which // ensures we don't stall on combineLatest skip(1), - take(pollingCycleEvents1.length + pollingCycleEvents2.length), - bufferCount(pollingCycleEvents1.length + pollingCycleEvents2.length) + take(events1.length + events2.length), + bufferCount(events1.length + events2.length) ) .subscribe((metrics: Array>) => { expect(metrics[0]).toEqual({ @@ -252,11 +256,11 @@ describe('createAggregator', () => { resolve(); }); - for (const event of pollingCycleEvents1) { + for (const event of events1) { events$.next(event); } - resetMetrics$.next(true); - for (const event of pollingCycleEvents2) { + reset$.next(true); + for (const event of events2) { events$.next(event); } }); @@ -265,7 +269,7 @@ describe('createAggregator', () => { test('resets count when configured metrics reset interval expires', async () => { const clock = sinon.useFakeTimers(); clock.tick(0); - const pollingCycleEvents1 = [ + const events1 = [ taskClaimSuccessEvent, taskClaimSuccessEvent, taskClaimSuccessEvent, @@ -274,7 +278,7 @@ describe('createAggregator', () => { taskClaimSuccessEvent, ]; - const pollingCycleEvents2 = [ + const events2 = [ taskClaimSuccessEvent, taskClaimFailureEvent, taskClaimFailureEvent, @@ -282,19 +286,16 @@ describe('createAggregator', () => { taskClaimSuccessEvent, ]; const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); const taskClaimAggregator = createAggregator({ key: 'task_claim', - taskPollingLifecycle, + events$, config: { ...config, metrics_reset_interval: 10, }, - resetMetrics$: new Subject(), - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskPollingCycleEvent(taskEvent), + reset$: new Subject(), + eventFilter: (event: TaskLifecycleEvent) => isTaskPollingCycleEvent(event), metricsAggregator: new TaskClaimMetricsAggregator(), }); @@ -304,8 +305,8 @@ describe('createAggregator', () => { // skip initial metric which is just initialized data which // ensures we don't stall on combineLatest skip(1), - take(pollingCycleEvents1.length + pollingCycleEvents2.length), - bufferCount(pollingCycleEvents1.length + pollingCycleEvents2.length) + take(events1.length + events2.length), + bufferCount(events1.length + events2.length) ) .subscribe((metrics: Array>) => { expect(metrics[0]).toEqual({ @@ -356,11 +357,11 @@ describe('createAggregator', () => { resolve(); }); - for (const event of pollingCycleEvents1) { + for (const event of events1) { events$.next(event); } clock.tick(20); - for (const event of pollingCycleEvents2) { + for (const event of events2) { events$.next(event); } @@ -370,30 +371,38 @@ describe('createAggregator', () => { }); describe('with TaskRunMetricsAggregator', () => { - test('returns a cumulative count of successful task runs and total task runs, broken down by type', async () => { + test('returns a cumulative count of successful task runs, on time task runs and total task runs, broken down by type, along with histogram of run delays', async () => { const taskRunEvents = [ + getTaskManagerStatEvent(3.234), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(10.45), getTaskRunSuccessEvent('telemetry'), + getTaskManagerStatEvent(3.454), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(35.45), getTaskRunSuccessEvent('report'), + getTaskManagerStatEvent(8.85673), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(4.5745), getTaskRunSuccessEvent('alerting:.index-threshold'), + getTaskManagerStatEvent(11.564), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.78), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(3.7863), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.245), getTaskRunFailedEvent('actions:webhook'), ]; const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); const taskRunAggregator = createAggregator({ key: 'task_run', - taskPollingLifecycle, + events$, config, - resetMetrics$: new Subject(), - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + reset$: new Subject(), + eventFilter: (event: TaskLifecycleEvent) => + isTaskRunEvent(event) || isTaskManagerStatEvent(event), metricsAggregator: new TaskRunMetricsAggregator(), }); @@ -410,123 +419,337 @@ describe('createAggregator', () => { expect(metrics[0]).toEqual({ key: 'task_run', value: { - overall: { success: 1, total: 1 }, - by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, }, }, }); expect(metrics[1]).toEqual({ key: 'task_run', value: { - overall: { success: 2, total: 2 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[2]).toEqual({ key: 'task_run', value: { - overall: { success: 3, total: 3 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[3]).toEqual({ key: 'task_run', value: { - overall: { success: 4, total: 4 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[4]).toEqual({ key: 'task_run', value: { - overall: { success: 4, total: 5 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 3 }, - 'alerting:example': { success: 2, total: 3 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[5]).toEqual({ key: 'task_run', value: { - overall: { success: 5, total: 6 }, + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 3, total: 4 }, - 'alerting:.index-threshold': { success: 1, total: 1 }, - 'alerting:example': { success: 2, total: 3 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[6]).toEqual({ key: 'task_run', value: { - overall: { success: 6, total: 7 }, + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 4, total: 5 }, - 'alerting:.index-threshold': { success: 1, total: 1 }, - 'alerting:example': { success: 3, total: 4 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[7]).toEqual({ key: 'task_run', value: { - overall: { success: 6, total: 8 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 4, total: 6 }, - 'alerting:.index-threshold': { success: 1, total: 1 }, - 'alerting:example': { success: 3, total: 5 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[8]).toEqual({ key: 'task_run', value: { - overall: { success: 7, total: 9 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 5, total: 7 }, - 'alerting:.index-threshold': { success: 1, total: 1 }, - 'alerting:example': { success: 4, total: 6 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[9]).toEqual({ key: 'task_run', value: { - overall: { success: 7, total: 10 }, + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[10]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [4, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[11]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 5, + not_timed_out: 6, + total: 6, + delay: { counts: [4, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[12]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 5, + not_timed_out: 6, + total: 6, + delay: { counts: [4, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[13]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 6, + not_timed_out: 7, + total: 7, + delay: { counts: [4, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 4, not_timed_out: 5, total: 5 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[14]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 6, + not_timed_out: 7, + total: 7, + delay: { counts: [5, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 4, not_timed_out: 5, total: 5 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[15]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 6, + not_timed_out: 8, + total: 8, + delay: { counts: [5, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 4, not_timed_out: 6, total: 6 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 5, total: 5 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[16]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 6, + not_timed_out: 8, + total: 8, + delay: { counts: [6, 2, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - actions: { success: 0, total: 1 }, - alerting: { success: 5, total: 7 }, - 'actions:webhook': { success: 0, total: 1 }, - 'alerting:.index-threshold': { success: 1, total: 1 }, - 'alerting:example': { success: 4, total: 6 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 4, not_timed_out: 6, total: 6 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 5, total: 5 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[17]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 7, + not_timed_out: 9, + total: 9, + delay: { counts: [6, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 5, not_timed_out: 7, total: 7 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 4, not_timed_out: 6, total: 6 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[18]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 7, + not_timed_out: 9, + total: 9, + delay: { counts: [7, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 5, not_timed_out: 7, total: 7 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 4, not_timed_out: 6, total: 6 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[19]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 7, + not_timed_out: 10, + total: 10, + delay: { counts: [7, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + actions: { success: 0, not_timed_out: 1, total: 1 }, + alerting: { success: 5, not_timed_out: 7, total: 7 }, + 'actions:webhook': { success: 0, not_timed_out: 1, total: 1 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 4, not_timed_out: 6, total: 6 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); @@ -540,33 +763,41 @@ describe('createAggregator', () => { }); test('resets count when resetMetric$ event is received', async () => { - const resetMetrics$ = new Subject(); + const reset$ = new Subject(); const taskRunEvents1 = [ + getTaskManagerStatEvent(3.234), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(10.45), getTaskRunSuccessEvent('telemetry'), + getTaskManagerStatEvent(3.454), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(35.45), getTaskRunSuccessEvent('report'), + getTaskManagerStatEvent(8.85673), getTaskRunFailedEvent('alerting:example'), ]; const taskRunEvents2 = [ + getTaskManagerStatEvent(4.5745), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(11.564), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.78), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(3.7863), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.245), getTaskRunFailedEvent('actions:webhook'), ]; const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); const taskRunAggregator = createAggregator({ key: 'task_run', - taskPollingLifecycle, + events$, config, - resetMetrics$, - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + reset$, + eventFilter: (event: TaskLifecycleEvent) => + isTaskRunEvent(event) || isTaskManagerStatEvent(event), metricsAggregator: new TaskRunMetricsAggregator(), }); @@ -583,119 +814,329 @@ describe('createAggregator', () => { expect(metrics[0]).toEqual({ key: 'task_run', value: { - overall: { success: 1, total: 1 }, - by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, }, }, }); expect(metrics[1]).toEqual({ key: 'task_run', value: { - overall: { success: 2, total: 2 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[2]).toEqual({ key: 'task_run', value: { - overall: { success: 3, total: 3 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[3]).toEqual({ key: 'task_run', value: { - overall: { success: 4, total: 4 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[4]).toEqual({ key: 'task_run', value: { - overall: { success: 4, total: 5 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 3 }, - 'alerting:example': { success: 2, total: 3 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); - // reset event should have been received here expect(metrics[5]).toEqual({ key: 'task_run', value: { - overall: { success: 1, total: 1 }, + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[6]).toEqual({ key: 'task_run', value: { - overall: { success: 2, total: 2 }, + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[7]).toEqual({ key: 'task_run', value: { - overall: { success: 2, total: 3 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 2, total: 3 }, - 'alerting:example': { success: 2, total: 3 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[8]).toEqual({ key: 'task_run', value: { - overall: { success: 3, total: 4 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 3, total: 4 }, - 'alerting:example': { success: 3, total: 4 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[9]).toEqual({ key: 'task_run', value: { - overall: { success: 3, total: 5 }, + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + // reset event should have been received here + expect(metrics[10]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, + }, + by_type: { + alerting: { success: 0, not_timed_out: 0, total: 0 }, + 'alerting:example': { success: 0, not_timed_out: 0, total: 0 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[11]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { - actions: { success: 0, total: 1 }, - alerting: { success: 3, total: 4 }, - 'actions:webhook': { success: 0, total: 1 }, - 'alerting:example': { success: 3, total: 4 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[12]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[13]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[14]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[15]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[16]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [3, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[17]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[18]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [4, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[19]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 5, + total: 5, + delay: { counts: [4, 1], values: [10, 20] }, + }, + by_type: { + actions: { success: 0, not_timed_out: 1, total: 1 }, + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'actions:webhook': { success: 0, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, }, }, }); @@ -705,7 +1146,7 @@ describe('createAggregator', () => { for (const event of taskRunEvents1) { events$.next(event); } - resetMetrics$.next(true); + reset$.next(true); for (const event of taskRunEvents2) { events$.next(event); } @@ -716,34 +1157,42 @@ describe('createAggregator', () => { const clock = sinon.useFakeTimers(); clock.tick(0); const taskRunEvents1 = [ + getTaskManagerStatEvent(3.234), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(10.45), getTaskRunSuccessEvent('telemetry'), + getTaskManagerStatEvent(3.454), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(35.45), getTaskRunSuccessEvent('report'), + getTaskManagerStatEvent(8.85673), getTaskRunFailedEvent('alerting:example'), ]; const taskRunEvents2 = [ + getTaskManagerStatEvent(4.5745), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(11.564), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.78), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(3.7863), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.245), getTaskRunFailedEvent('actions:webhook'), ]; const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); const taskRunAggregator = createAggregator({ key: 'task_run', - taskPollingLifecycle, + events$, config: { ...config, metrics_reset_interval: 10, }, - resetMetrics$: new Subject(), - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + reset$: new Subject(), + eventFilter: (event: TaskLifecycleEvent) => + isTaskRunEvent(event) || isTaskManagerStatEvent(event), metricsAggregator: new TaskRunMetricsAggregator(), }); @@ -760,119 +1209,329 @@ describe('createAggregator', () => { expect(metrics[0]).toEqual({ key: 'task_run', value: { - overall: { success: 1, total: 1 }, - by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, }, }, }); expect(metrics[1]).toEqual({ key: 'task_run', value: { - overall: { success: 2, total: 2 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[2]).toEqual({ key: 'task_run', value: { - overall: { success: 3, total: 3 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[3]).toEqual({ key: 'task_run', value: { - overall: { success: 4, total: 4 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[4]).toEqual({ key: 'task_run', value: { - overall: { success: 4, total: 5 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 2, total: 3 }, - 'alerting:example': { success: 2, total: 3 }, - report: { success: 1, total: 1 }, - telemetry: { success: 1, total: 1 }, + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); - // reset event should have been received here expect(metrics[5]).toEqual({ key: 'task_run', value: { - overall: { success: 1, total: 1 }, + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { - alerting: { success: 1, total: 1 }, - 'alerting:example': { success: 1, total: 1 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[6]).toEqual({ key: 'task_run', value: { - overall: { success: 2, total: 2 }, + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 2, total: 2 }, - 'alerting:example': { success: 2, total: 2 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[7]).toEqual({ key: 'task_run', value: { - overall: { success: 2, total: 3 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 2, total: 3 }, - 'alerting:example': { success: 2, total: 3 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[8]).toEqual({ key: 'task_run', value: { - overall: { success: 3, total: 4 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { - alerting: { success: 3, total: 4 }, - 'alerting:example': { success: 3, total: 4 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, }, }, }); expect(metrics[9]).toEqual({ key: 'task_run', value: { - overall: { success: 3, total: 5 }, + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + // reset event should have been received here + expect(metrics[10]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, + }, + by_type: { + alerting: { success: 0, not_timed_out: 0, total: 0 }, + 'alerting:example': { success: 0, not_timed_out: 0, total: 0 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[11]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[12]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[13]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[14]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[15]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[16]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [3, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[17]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[18]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [4, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[19]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 5, + total: 5, + delay: { counts: [4, 1], values: [10, 20] }, + }, by_type: { - actions: { success: 0, total: 1 }, - alerting: { success: 3, total: 4 }, - 'actions:webhook': { success: 0, total: 1 }, - 'alerting:example': { success: 3, total: 4 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + actions: { success: 0, not_timed_out: 1, total: 1 }, + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'actions:webhook': { success: 0, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, }, }, }); @@ -892,8 +1551,185 @@ describe('createAggregator', () => { }); }); - test('should filter task lifecycle events using specified taskEventFilter', () => { - const pollingCycleEvents = [ + describe('with TaskOverdueMetricsAggregator', () => { + test('returns latest values for task overdue by time', async () => { + const events = [ + getTaskManagerMetricEvent({ + numOverdueTasks: { + 'alerting:example': [{ key: 40, doc_count: 1 }], + 'alerting:.index-threshold': [ + { key: 20, doc_count: 2 }, + { key: 120, doc_count: 1 }, + ], + 'actions:webhook': [{ key: 0, doc_count: 2 }], + 'actions:.email': [{ key: 0, doc_count: 1 }], + total: [ + { key: 0, doc_count: 3 }, + { key: 20, doc_count: 2 }, + { key: 40, doc_count: 1 }, + { key: 120, doc_count: 1 }, + ], + }, + }), + getTaskManagerMetricEvent({ + numOverdueTasks: { + total: [], + }, + }), + getTaskManagerMetricEvent({ + numOverdueTasks: { + telemetry: [ + { key: 0, doc_count: 1 }, + { key: 20, doc_count: 1 }, + ], + reporting: [{ key: 0, doc_count: 1 }], + 'actions:webhook': [ + { key: 0, doc_count: 3 }, + { key: 30, doc_count: 2 }, + { key: 50, doc_count: 1 }, + ], + 'actions:.email': [{ key: 0, doc_count: 11 }], + total: [ + { key: 0, doc_count: 16 }, + { key: 20, doc_count: 1 }, + { key: 30, doc_count: 2 }, + { key: 50, doc_count: 1 }, + ], + }, + }), + ]; + const events$ = new Subject(); + + const taskOverdueAggregator = createAggregator({ + key: 'task_overdue', + events$, + config, + reset$: new Subject(), + eventFilter: (event: TaskLifecycleEvent) => isTaskManagerMetricEvent(event), + metricsAggregator: new TaskOverdueMetricsAggregator(), + }); + + return new Promise((resolve) => { + taskOverdueAggregator + .pipe( + // skip initial metric which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + take(events.length), + bufferCount(events.length) + ) + .subscribe((metrics: Array>) => { + expect(metrics[0]).toEqual({ + key: 'task_overdue', + value: { + overall: { + overdue_by: { + counts: [3, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + by_type: { + 'alerting:example': { + overdue_by: { + counts: [0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50], + }, + }, + 'alerting:__index-threshold': { + overdue_by: { + counts: [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + alerting: { + overdue_by: { + counts: [0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + 'actions:webhook': { + overdue_by: { + counts: [2], + values: [10], + }, + }, + 'actions:__email': { + overdue_by: { + counts: [1], + values: [10], + }, + }, + actions: { + overdue_by: { + counts: [3], + values: [10], + }, + }, + }, + }, + }); + expect(metrics[1]).toEqual({ + key: 'task_overdue', + value: { + overall: { overdue_by: { counts: [], values: [] } }, + by_type: {}, + }, + }); + expect(metrics[2]).toEqual({ + key: 'task_overdue', + value: { + overall: { + overdue_by: { + counts: [16, 0, 1, 2, 0, 1], + values: [10, 20, 30, 40, 50, 60], + }, + }, + by_type: { + telemetry: { + overdue_by: { + counts: [1, 0, 1], + values: [10, 20, 30], + }, + }, + reporting: { + overdue_by: { + counts: [1], + values: [10], + }, + }, + 'actions:webhook': { + overdue_by: { + counts: [3, 0, 0, 2, 0, 1], + values: [10, 20, 30, 40, 50, 60], + }, + }, + 'actions:__email': { + overdue_by: { + counts: [11], + values: [10], + }, + }, + actions: { + overdue_by: { + counts: [14, 0, 0, 2, 0, 1], + values: [10, 20, 30, 40, 50, 60], + }, + }, + }, + }, + }); + resolve(); + }); + + for (const event of events) { + events$.next(event); + } + }); + }); + }); + + test('should filter task lifecycle events using specified eventFilter', () => { + const events = [ taskClaimSuccessEvent, taskClaimSuccessEvent, taskClaimSuccessEvent, @@ -906,17 +1742,15 @@ describe('createAggregator', () => { taskClaimFailureEvent, taskClaimSuccessEvent, ]; - const taskEventFilter = jest.fn().mockReturnValue(true); + const eventFilter = jest.fn().mockReturnValue(true); const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); + const aggregator = createAggregator({ key: 'test', - taskPollingLifecycle, + events$, config, - resetMetrics$: new Subject(), - taskEventFilter, + reset$: new Subject(), + eventFilter, metricsAggregator: new TaskClaimMetricsAggregator(), }); @@ -926,27 +1760,27 @@ describe('createAggregator', () => { // skip initial metric which is just initialized data which // ensures we don't stall on combineLatest skip(1), - take(pollingCycleEvents.length), - bufferCount(pollingCycleEvents.length) + take(events.length), + bufferCount(events.length) ) .subscribe(() => { resolve(); }); - for (const event of pollingCycleEvents) { + for (const event of events) { events$.next(event); } - expect(taskEventFilter).toHaveBeenCalledTimes(pollingCycleEvents.length); + expect(eventFilter).toHaveBeenCalledTimes(events.length); }); }); - test('should call metricAggregator to process task lifecycle events', () => { + test('should call metricAggregator to process events', () => { const spy = jest .spyOn(TaskClaimMetricsAggregatorModule, 'TaskClaimMetricsAggregator') .mockImplementation(() => mockMetricsAggregator); - const pollingCycleEvents = [ + const events = [ taskClaimSuccessEvent, taskClaimSuccessEvent, taskClaimSuccessEvent, @@ -959,18 +1793,16 @@ describe('createAggregator', () => { taskClaimFailureEvent, taskClaimSuccessEvent, ]; - const taskEventFilter = jest.fn().mockReturnValue(true); + const eventFilter = jest.fn().mockReturnValue(true); const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); + const aggregator = createAggregator({ key: 'test', - taskPollingLifecycle, + events$, config, - resetMetrics$: new Subject(), - taskEventFilter, - metricsAggregator: mockMetricsAggregator, + reset$: new Subject(), + eventFilter, + metricsAggregator: new TaskClaimMetricsAggregator(), }); return new Promise((resolve) => { @@ -979,22 +1811,20 @@ describe('createAggregator', () => { // skip initial metric which is just initialized data which // ensures we don't stall on combineLatest skip(1), - take(pollingCycleEvents.length), - bufferCount(pollingCycleEvents.length) + take(events.length), + bufferCount(events.length) ) .subscribe(() => { resolve(); }); - for (const event of pollingCycleEvents) { + for (const event of events) { events$.next(event); } expect(mockMetricsAggregator.initialMetric).toHaveBeenCalledTimes(1); - expect(mockMetricsAggregator.processTaskLifecycleEvent).toHaveBeenCalledTimes( - pollingCycleEvents.length - ); - expect(mockMetricsAggregator.collect).toHaveBeenCalledTimes(pollingCycleEvents.length); + expect(mockMetricsAggregator.processEvent).toHaveBeenCalledTimes(events.length); + expect(mockMetricsAggregator.collect).toHaveBeenCalledTimes(events.length); expect(mockMetricsAggregator.reset).not.toHaveBeenCalled(); spy.mockRestore(); }); @@ -1005,8 +1835,8 @@ describe('createAggregator', () => { .spyOn(TaskClaimMetricsAggregatorModule, 'TaskClaimMetricsAggregator') .mockImplementation(() => mockMetricsAggregator); - const resetMetrics$ = new Subject(); - const pollingCycleEvents = [ + const reset$ = new Subject(); + const events = [ taskClaimSuccessEvent, taskClaimSuccessEvent, taskClaimSuccessEvent, @@ -1019,17 +1849,15 @@ describe('createAggregator', () => { taskClaimFailureEvent, taskClaimSuccessEvent, ]; - const taskEventFilter = jest.fn().mockReturnValue(true); + const eventFilter = jest.fn().mockReturnValue(true); const events$ = new Subject(); - const taskPollingLifecycle = taskPollingLifecycleMock.create({ - events$: events$ as Observable, - }); + const aggregator = createAggregator({ key: 'test', - taskPollingLifecycle, + events$, config, - resetMetrics$, - taskEventFilter, + reset$, + eventFilter, metricsAggregator: mockMetricsAggregator, }); @@ -1039,30 +1867,28 @@ describe('createAggregator', () => { // skip initial metric which is just initialized data which // ensures we don't stall on combineLatest skip(1), - take(pollingCycleEvents.length), - bufferCount(pollingCycleEvents.length) + take(events.length), + bufferCount(events.length) ) .subscribe(() => { resolve(); }); - for (const event of pollingCycleEvents) { + for (const event of events) { events$.next(event); } for (let i = 0; i < 5; i++) { - events$.next(pollingCycleEvents[i]); + events$.next(events[i]); } - resetMetrics$.next(true); - for (let i = 0; i < pollingCycleEvents.length; i++) { - events$.next(pollingCycleEvents[i]); + reset$.next(true); + for (let i = 0; i < events.length; i++) { + events$.next(events[i]); } expect(mockMetricsAggregator.initialMetric).toHaveBeenCalledTimes(1); - expect(mockMetricsAggregator.processTaskLifecycleEvent).toHaveBeenCalledTimes( - pollingCycleEvents.length - ); - expect(mockMetricsAggregator.collect).toHaveBeenCalledTimes(pollingCycleEvents.length); + expect(mockMetricsAggregator.processEvent).toHaveBeenCalledTimes(events.length); + expect(mockMetricsAggregator.collect).toHaveBeenCalledTimes(events.length); expect(mockMetricsAggregator.reset).toHaveBeenCalledTimes(1); spy.mockRestore(); }); diff --git a/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts b/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts index cece8c0f70b23..32d625524ead6 100644 --- a/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts +++ b/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts @@ -7,41 +7,43 @@ import { combineLatest, filter, interval, map, merge, Observable, startWith } from 'rxjs'; import { JsonValue } from '@kbn/utility-types'; -import { TaskLifecycleEvent, TaskPollingLifecycle } from '../polling_lifecycle'; import { AggregatedStat, AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; import { TaskManagerConfig } from '../config'; import { ITaskMetricsAggregator } from './types'; +import { TaskLifecycleEvent } from '../polling_lifecycle'; export interface CreateMetricsAggregatorOpts { key: string; config: TaskManagerConfig; - resetMetrics$: Observable; - taskPollingLifecycle: TaskPollingLifecycle; - taskEventFilter: (taskEvent: TaskLifecycleEvent) => boolean; + reset$?: Observable; + events$: Observable; + eventFilter: (event: TaskLifecycleEvent) => boolean; metricsAggregator: ITaskMetricsAggregator; } export function createAggregator({ key, - taskPollingLifecycle, config, - resetMetrics$, - taskEventFilter, + reset$, + events$, + eventFilter, metricsAggregator, }: CreateMetricsAggregatorOpts): AggregatedStatProvider { - // Resets the aggregators either when the reset interval has passed or - // a resetMetrics$ event is received - merge( - interval(config.metrics_reset_interval).pipe(map(() => true)), - resetMetrics$.pipe(map(() => true)) - ).subscribe(() => { - metricsAggregator.reset(); - }); + if (reset$) { + // Resets the aggregators either when the reset interval has passed or + // a reset$ event is received + merge( + interval(config.metrics_reset_interval).pipe(map(() => true)), + reset$.pipe(map(() => true)) + ).subscribe(() => { + metricsAggregator.reset(); + }); + } - const taskEvents$: Observable = taskPollingLifecycle.events.pipe( - filter((taskEvent: TaskLifecycleEvent) => taskEventFilter(taskEvent)), - map((taskEvent: TaskLifecycleEvent) => { - metricsAggregator.processTaskLifecycleEvent(taskEvent); + const taskEvents$: Observable = events$.pipe( + filter((event: TaskLifecycleEvent) => eventFilter(event)), + map((event: TaskLifecycleEvent) => { + metricsAggregator.processTaskLifecycleEvent(event); return metricsAggregator.collect(); }) ); diff --git a/x-pack/plugins/task_manager/server/metrics/index.ts b/x-pack/plugins/task_manager/server/metrics/index.ts index 5e2a73f91dd73..36878d3dbd8ee 100644 --- a/x-pack/plugins/task_manager/server/metrics/index.ts +++ b/x-pack/plugins/task_manager/server/metrics/index.ts @@ -9,18 +9,28 @@ import { Observable } from 'rxjs'; import { TaskManagerConfig } from '../config'; import { Metrics, createMetricsAggregators, createMetricsStream } from './metrics_stream'; import { TaskPollingLifecycle } from '../polling_lifecycle'; +import { TaskManagerMetricsCollector } from './task_metrics_collector'; export type { Metrics } from './metrics_stream'; -export function metricsStream( - config: TaskManagerConfig, - resetMetrics$: Observable, - taskPollingLifecycle?: TaskPollingLifecycle -): Observable { +interface MetricsStreamOpts { + config: TaskManagerConfig; + reset$: Observable; // emits when counter metrics should be reset + taskPollingLifecycle?: TaskPollingLifecycle; // subscribe to task lifecycle events + taskManagerMetricsCollector?: TaskManagerMetricsCollector; // subscribe to collected task manager metrics +} + +export function metricsStream({ + config, + reset$, + taskPollingLifecycle, + taskManagerMetricsCollector, +}: MetricsStreamOpts): Observable { return createMetricsStream( createMetricsAggregators({ config, - resetMetrics$, + reset$, taskPollingLifecycle, + taskManagerMetricsCollector, }) ); } diff --git a/x-pack/plugins/task_manager/server/metrics/lib/counter.test.ts b/x-pack/plugins/task_manager/server/metrics/lib/counter.test.ts new file mode 100644 index 0000000000000..de915e64890b8 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/counter.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Counter } from './counter'; + +describe('Counter', () => { + let counter: Counter; + beforeEach(() => { + counter = new Counter(); + }); + + test('should correctly initialize', () => { + expect(counter.get()).toEqual(0); + }); + + test('should correctly return initialCount', () => { + expect(counter.initialCount()).toEqual(0); + }); + + test('should correctly increment counter', () => { + counter.increment(); + counter.increment(); + expect(counter.get()).toEqual(2); + }); + + test('should correctly reset counter', () => { + counter.increment(); + counter.increment(); + counter.increment(); + counter.increment(); + counter.increment(); + counter.increment(); + counter.increment(); + expect(counter.get()).toEqual(7); + + counter.reset(); + expect(counter.get()).toEqual(0); + }); +}); diff --git a/x-pack/plugins/task_manager/server/metrics/lib/counter.ts b/x-pack/plugins/task_manager/server/metrics/lib/counter.ts new file mode 100644 index 0000000000000..be851cb84c049 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/counter.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export class Counter { + private count = 0; + + public initialCount(): number { + return 0; + } + + public get(): number { + return this.count; + } + + public increment() { + this.count++; + } + + public reset() { + this.count = 0; + } +} diff --git a/x-pack/plugins/task_manager/server/metrics/lib/get_task_type_group.test.ts b/x-pack/plugins/task_manager/server/metrics/lib/get_task_type_group.test.ts new file mode 100644 index 0000000000000..1948161d6d1e7 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/get_task_type_group.test.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getTaskTypeGroup } from './get_task_type_group'; + +describe('getTaskTypeGroup', () => { + test('should correctly group based on task type prefix', () => { + expect(getTaskTypeGroup('alerting:abc')).toEqual('alerting'); + expect(getTaskTypeGroup('actions:def')).toEqual('actions'); + }); + + test('should return undefined if no match', () => { + expect(getTaskTypeGroup('alerting-abc')).toBeUndefined(); + expect(getTaskTypeGroup('fooalertingbar')).toBeUndefined(); + }); +}); diff --git a/x-pack/plugins/task_manager/server/metrics/lib/get_task_type_group.ts b/x-pack/plugins/task_manager/server/metrics/lib/get_task_type_group.ts new file mode 100644 index 0000000000000..2bfb5e7f6afe9 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/get_task_type_group.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const taskTypeGrouping = new Set(['alerting:', 'actions:']); + +export function getTaskTypeGroup(taskType: string): string | undefined { + for (const group of taskTypeGrouping) { + if (taskType.startsWith(group)) { + return group.replace(':', ''); + } + } +} diff --git a/x-pack/plugins/task_manager/server/metrics/lib/index.ts b/x-pack/plugins/task_manager/server/metrics/lib/index.ts new file mode 100644 index 0000000000000..6c48961bd1021 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { Counter } from './counter'; +export { getTaskTypeGroup } from './get_task_type_group'; +export { MetricCounterService } from './metric_counter_service'; +export { type SerializedHistogram, SimpleHistogram } from './simple_histogram'; +export { unflattenObject } from './unflatten_object'; diff --git a/x-pack/plugins/task_manager/server/metrics/lib/metric_counter_service.test.ts b/x-pack/plugins/task_manager/server/metrics/lib/metric_counter_service.test.ts new file mode 100644 index 0000000000000..75117fe5d99ce --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/metric_counter_service.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MetricCounterService } from './metric_counter_service'; + +describe('MetricCounterService', () => { + test('should correctly initialize', () => { + const counterService = new MetricCounterService(['success', 'total']); + expect(counterService.collect()).toEqual({ success: 0, total: 0 }); + }); + + test('should correctly initialize with initial namespace', () => { + const counterService = new MetricCounterService(['success', 'total'], 'overall'); + expect(counterService.collect()).toEqual({ overall: { success: 0, total: 0 } }); + }); + + test('should correctly initialize with initial flattened namespace', () => { + const counterService = new MetricCounterService(['success', 'total'], 'overall.count'); + expect(counterService.collect()).toEqual({ overall: { count: { success: 0, total: 0 } } }); + }); + + test('should throw error if no keys provided', () => { + expect(() => { + new MetricCounterService([]); + }).toThrowErrorMatchingInlineSnapshot( + `"Metrics counter service must be initialized with at least one key"` + ); + }); + + test('should correctly return initialMetrics', () => { + const counterService = new MetricCounterService(['success', 'total']); + expect(counterService.initialMetrics()).toEqual({ success: 0, total: 0 }); + }); + + test('should correctly increment counter', () => { + const counterService = new MetricCounterService(['success', 'total']); + counterService.increment('success'); + counterService.increment('success'); + expect(counterService.collect()).toEqual({ success: 2, total: 0 }); + }); + + test('should correctly increment counter for new namespace', () => { + const counterService = new MetricCounterService(['success', 'total'], 'overall'); + counterService.increment('success', 'foo'); + expect(counterService.collect()).toEqual({ + overall: { success: 0, total: 0 }, + foo: { success: 1, total: 0 }, + }); + }); + + test('should correctly increment counter for nested namespace', () => { + const counterService = new MetricCounterService(['success', 'total'], 'overall'); + counterService.increment('success', 'foo.bar'); + counterService.increment('total', 'foo.baz'); + expect(counterService.collect()).toEqual({ + overall: { success: 0, total: 0 }, + foo: { bar: { success: 1, total: 0 }, baz: { success: 0, total: 1 } }, + }); + }); + + test('should correctly reset counter', () => { + const counterService = new MetricCounterService(['success', 'total'], 'overall'); + counterService.increment('success', 'overall'); + counterService.increment('total', 'overall'); + counterService.increment('success', 'foo.bar'); + counterService.increment('success', 'foo.bar'); + counterService.increment('total', 'foo.bar'); + counterService.increment('total', 'foo.bar'); + counterService.increment('total', 'foo.bar'); + counterService.increment('total', 'foo.bar'); + counterService.increment('total', 'foo.baz'); + counterService.increment('success', 'foo.cheese.whiz'); + expect(counterService.collect()).toEqual({ + overall: { success: 1, total: 1 }, + foo: { + bar: { success: 2, total: 4 }, + baz: { success: 0, total: 1 }, + cheese: { whiz: { success: 1, total: 0 } }, + }, + }); + + counterService.reset(); + expect(counterService.collect()).toEqual({ + overall: { success: 0, total: 0 }, + foo: { + bar: { success: 0, total: 0 }, + baz: { success: 0, total: 0 }, + cheese: { whiz: { success: 0, total: 0 } }, + }, + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/metrics/lib/metric_counter_service.ts b/x-pack/plugins/task_manager/server/metrics/lib/metric_counter_service.ts new file mode 100644 index 0000000000000..4624b84966166 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/metric_counter_service.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { JsonObject } from '@kbn/utility-types'; +import { Counter } from './counter'; +import { unflattenObject } from './unflatten_object'; + +export class MetricCounterService { + private readonly counters = new Map(); + private readonly keys: string[]; + + constructor(keys: string[], initialNamespace?: string) { + if (!keys || !keys.length) { + throw new Error('Metrics counter service must be initialized with at least one key'); + } + + this.keys = keys; + this.initializeCountersForNamespace(initialNamespace); + } + + public initialMetrics(): T { + return this.toJson(true); + } + + public reset() { + this.counters.forEach((val: Counter) => { + val.reset(); + }); + } + + public increment(key: string, namespace?: string) { + // initialize counters for namespace if necessary + this.initializeCountersForNamespace(namespace); + + // increment counter in specified namespace + this.counters.get(this.buildCounterKey(key, namespace))?.increment(); + } + + public collect(): T { + return this.toJson(); + } + + private initializeCountersForNamespace(namespace?: string) { + for (const key of this.keys) { + const counterKey = this.buildCounterKey(key, namespace); + if (!this.counters.get(counterKey)) { + this.counters.set(counterKey, new Counter()); + } + } + } + + private buildCounterKey(key: string, namespace?: string) { + const prefix = namespace ? `${namespace}.` : ''; + return `${prefix}${key}`; + } + + private toJson(initialMetric: boolean = false): T { + const collected: Record = {}; + this.counters.forEach((val: Counter, key: string) => { + collected[key] = initialMetric ? val.initialCount() : val.get(); + }); + + return unflattenObject(collected); + } +} diff --git a/x-pack/plugins/task_manager/server/metrics/simple_histogram.test.ts b/x-pack/plugins/task_manager/server/metrics/lib/simple_histogram.test.ts similarity index 79% rename from x-pack/plugins/task_manager/server/metrics/simple_histogram.test.ts rename to x-pack/plugins/task_manager/server/metrics/lib/simple_histogram.test.ts index 30b5d0bd6b21a..9c24aabbdd575 100644 --- a/x-pack/plugins/task_manager/server/metrics/simple_histogram.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/lib/simple_histogram.test.ts @@ -53,6 +53,8 @@ describe('SimpleHistogram', () => { test('should correctly record values', () => { const histogram = new SimpleHistogram(100, 10); + histogram.record(0); + histogram.record(10); histogram.record(23); histogram.record(34); histogram.record(21); @@ -64,8 +66,8 @@ describe('SimpleHistogram', () => { histogram.record(2); expect(histogram.get()).toEqual([ - { value: 10, count: 2 }, - { value: 20, count: 0 }, + { value: 10, count: 3 }, + { value: 20, count: 1 }, { value: 30, count: 2 }, { value: 40, count: 2 }, { value: 50, count: 0 }, @@ -77,6 +79,33 @@ describe('SimpleHistogram', () => { ]); }); + test('should correctly record values with specific increment', () => { + const histogram = new SimpleHistogram(100, 10); + histogram.record(0); + histogram.record(23, 2); + histogram.record(34); + histogram.record(21); + histogram.record(56); + histogram.record(78); + histogram.record(33, 4); + histogram.record(99, 5); + histogram.record(1); + histogram.record(2); + + expect(histogram.get()).toEqual([ + { value: 10, count: 3 }, + { value: 20, count: 0 }, + { value: 30, count: 3 }, + { value: 40, count: 5 }, + { value: 50, count: 0 }, + { value: 60, count: 1 }, + { value: 70, count: 0 }, + { value: 80, count: 1 }, + { value: 90, count: 0 }, + { value: 100, count: 5 }, + ]); + }); + test('should ignore values less than 0 and greater than max', () => { const histogram = new SimpleHistogram(100, 10); histogram.record(23); @@ -176,4 +205,21 @@ describe('SimpleHistogram', () => { expect(histogram.get(true)).toEqual([]); }); + + test('should correctly serialize histogram data', () => { + const histogram = new SimpleHistogram(100, 10); + histogram.record(23); + histogram.record(34); + histogram.record(21); + histogram.record(56); + histogram.record(78); + histogram.record(33); + histogram.record(99); + histogram.record(1); + histogram.record(2); + expect(histogram.serialize()).toEqual({ + counts: [2, 0, 2, 2, 0, 1, 0, 1, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], + }); + }); }); diff --git a/x-pack/plugins/task_manager/server/metrics/simple_histogram.ts b/x-pack/plugins/task_manager/server/metrics/lib/simple_histogram.ts similarity index 80% rename from x-pack/plugins/task_manager/server/metrics/simple_histogram.ts rename to x-pack/plugins/task_manager/server/metrics/lib/simple_histogram.ts index 3b2cb89a7f5dd..7940ee3c48ee5 100644 --- a/x-pack/plugins/task_manager/server/metrics/simple_histogram.ts +++ b/x-pack/plugins/task_manager/server/metrics/lib/simple_histogram.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { JsonObject } from '@kbn/utility-types'; import { last } from 'lodash'; interface Bucket { @@ -13,6 +14,11 @@ interface Bucket { count: number; } +export interface SerializedHistogram extends JsonObject { + counts: number[]; + values: number[]; +} + export class SimpleHistogram { private maxValue: number; private bucketSize: number; @@ -34,14 +40,14 @@ export class SimpleHistogram { } } - public record(value: number) { + public record(value: number, increment: number = 1) { if (value < 0 || value > this.maxValue) { return; } for (let i = 0; i < this.histogramBuckets.length; i++) { if (value >= this.histogramBuckets[i].min && value < this.histogramBuckets[i].max) { - this.histogramBuckets[i].count++; + this.histogramBuckets[i].count += increment; break; } @@ -68,6 +74,18 @@ export class SimpleHistogram { })); } + public serialize(): SerializedHistogram { + const counts: number[] = []; + const values: number[] = []; + + for (const { count, value } of this.get(true)) { + counts.push(count); + values.push(value); + } + + return { counts, values }; + } + private initializeBuckets() { let i = 0; while (i < this.maxValue) { diff --git a/x-pack/plugins/task_manager/server/metrics/lib/unflatten_object.test.ts b/x-pack/plugins/task_manager/server/metrics/lib/unflatten_object.test.ts new file mode 100644 index 0000000000000..ccd11f7e92947 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/unflatten_object.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright 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 { unflattenObject } from './unflatten_object'; + +describe('unflattenObject', () => { + test('should unflatten an object', () => { + const obj = { + a: true, + 'b.baz[0].a': false, + 'b.baz[0].b': 'foo', + 'b.baz[1]': 'bar', + 'b.baz[2]': true, + 'b.foo': 'bar', + 'b.baz[3][0]': 1, + 'b.baz[3][1]': 2, + 'c.b.foo': 'cheese', + }; + + expect(unflattenObject(obj)).toEqual({ + a: true, + b: { + foo: 'bar', + baz: [ + { + a: false, + b: 'foo', + }, + 'bar', + true, + [1, 2], + ], + }, + c: { + b: { + foo: 'cheese', + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/metrics/lib/unflatten_object.ts b/x-pack/plugins/task_manager/server/metrics/lib/unflatten_object.ts new file mode 100644 index 0000000000000..23f2553223a35 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/lib/unflatten_object.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { set } from '@kbn/safer-lodash-set'; + +interface GenericObject { + [key: string]: unknown; +} +export const unflattenObject = (object: object): T => + Object.entries(object).reduce((acc, [key, value]) => { + set(acc, key, value); + return acc; + }, {} as T); diff --git a/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts b/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts index 29558308c5196..6f4a342fcb3a2 100644 --- a/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts +++ b/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts @@ -11,15 +11,23 @@ import { set } from '@kbn/safer-lodash-set'; import { TaskLifecycleEvent, TaskPollingLifecycle } from '../polling_lifecycle'; import { TaskManagerConfig } from '../config'; import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; -import { isTaskPollingCycleEvent, isTaskRunEvent } from '../task_events'; +import { + isTaskManagerStatEvent, + isTaskManagerMetricEvent, + isTaskPollingCycleEvent, + isTaskRunEvent, +} from '../task_events'; import { TaskClaimMetric, TaskClaimMetricsAggregator } from './task_claim_metrics_aggregator'; import { createAggregator } from './create_aggregator'; import { TaskRunMetric, TaskRunMetricsAggregator } from './task_run_metrics_aggregator'; +import { TaskOverdueMetric, TaskOverdueMetricsAggregator } from './task_overdue_metrics_aggregator'; +import { TaskManagerMetricsCollector } from './task_metrics_collector'; export interface Metrics { last_update: string; metrics: { task_claim?: Metric; task_run?: Metric; + task_overdue?: Metric; }; } @@ -30,35 +38,51 @@ export interface Metric { interface CreateMetricsAggregatorsOpts { config: TaskManagerConfig; - resetMetrics$: Observable; + reset$: Observable; taskPollingLifecycle?: TaskPollingLifecycle; + taskManagerMetricsCollector?: TaskManagerMetricsCollector; } export function createMetricsAggregators({ config, - resetMetrics$, + reset$, taskPollingLifecycle, + taskManagerMetricsCollector, }: CreateMetricsAggregatorsOpts): AggregatedStatProvider { const aggregators: AggregatedStatProvider[] = []; if (taskPollingLifecycle) { aggregators.push( createAggregator({ key: 'task_claim', - taskPollingLifecycle, + events$: taskPollingLifecycle.events, config, - resetMetrics$, - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskPollingCycleEvent(taskEvent), + reset$, + eventFilter: (event: TaskLifecycleEvent) => isTaskPollingCycleEvent(event), metricsAggregator: new TaskClaimMetricsAggregator(), }), createAggregator({ key: 'task_run', - taskPollingLifecycle, + events$: taskPollingLifecycle.events, config, - resetMetrics$, - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + reset$, + eventFilter: (event: TaskLifecycleEvent) => + isTaskRunEvent(event) || isTaskManagerStatEvent(event), metricsAggregator: new TaskRunMetricsAggregator(), }) ); } + + if (taskManagerMetricsCollector) { + aggregators.push( + createAggregator({ + key: 'task_overdue', + events$: taskManagerMetricsCollector.events, + config, + eventFilter: (event: TaskLifecycleEvent) => isTaskManagerMetricEvent(event), + metricsAggregator: new TaskOverdueMetricsAggregator(), + }) + ); + } + return merge(...aggregators); } diff --git a/x-pack/plugins/task_manager/server/metrics/success_rate_counter.test.ts b/x-pack/plugins/task_manager/server/metrics/success_rate_counter.test.ts deleted file mode 100644 index eb34f3a34c005..0000000000000 --- a/x-pack/plugins/task_manager/server/metrics/success_rate_counter.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { SuccessRateCounter } from './success_rate_counter'; - -describe('SuccessRateCounter', () => { - let successRateCounter: SuccessRateCounter; - beforeEach(() => { - successRateCounter = new SuccessRateCounter(); - }); - - test('should correctly initialize', () => { - expect(successRateCounter.get()).toEqual({ success: 0, total: 0 }); - }); - - test('should correctly return initialMetrics', () => { - expect(successRateCounter.initialMetric()).toEqual({ success: 0, total: 0 }); - }); - - test('should correctly increment counter when success is true', () => { - successRateCounter.increment(true); - successRateCounter.increment(true); - expect(successRateCounter.get()).toEqual({ success: 2, total: 2 }); - }); - - test('should correctly increment counter when success is false', () => { - successRateCounter.increment(false); - successRateCounter.increment(false); - expect(successRateCounter.get()).toEqual({ success: 0, total: 2 }); - }); - - test('should correctly reset counter', () => { - successRateCounter.increment(true); - successRateCounter.increment(true); - successRateCounter.increment(false); - successRateCounter.increment(false); - successRateCounter.increment(true); - successRateCounter.increment(true); - successRateCounter.increment(false); - expect(successRateCounter.get()).toEqual({ success: 4, total: 7 }); - - successRateCounter.reset(); - expect(successRateCounter.get()).toEqual({ success: 0, total: 0 }); - }); -}); diff --git a/x-pack/plugins/task_manager/server/metrics/success_rate_counter.ts b/x-pack/plugins/task_manager/server/metrics/success_rate_counter.ts deleted file mode 100644 index d9c61575a2698..0000000000000 --- a/x-pack/plugins/task_manager/server/metrics/success_rate_counter.ts +++ /dev/null @@ -1,44 +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 { JsonObject } from '@kbn/utility-types'; - -export interface SuccessRate extends JsonObject { - success: number; - total: number; -} - -export class SuccessRateCounter { - private success = 0; - private total = 0; - - public initialMetric(): SuccessRate { - return { - success: 0, - total: 0, - }; - } - - public get(): SuccessRate { - return { - success: this.success, - total: this.total, - }; - } - - public increment(success: boolean) { - if (success) { - this.success++; - } - this.total++; - } - - public reset() { - this.success = 0; - this.total = 0; - } -} diff --git a/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts b/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts index 2dc1a50e8d00e..8bb1269bae2fb 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts @@ -5,64 +5,63 @@ * 2.0. */ +import { JsonObject } from '@kbn/utility-types'; import { isOk } from '../lib/result_type'; import { TaskLifecycleEvent } from '../polling_lifecycle'; import { TaskRun } from '../task_events'; -import { SimpleHistogram } from './simple_histogram'; -import { SuccessRate, SuccessRateCounter } from './success_rate_counter'; +import { type SerializedHistogram, SimpleHistogram, MetricCounterService } from './lib'; import { ITaskMetricsAggregator } from './types'; const HDR_HISTOGRAM_MAX = 30000; // 30 seconds const HDR_HISTOGRAM_BUCKET_SIZE = 100; // 100 millis -export type TaskClaimMetric = SuccessRate & { - duration: { - counts: number[]; - values: number[]; - }; +enum TaskClaimKeys { + SUCCESS = 'success', + TOTAL = 'total', +} +interface TaskClaimCounts extends JsonObject { + [TaskClaimKeys.SUCCESS]: number; + [TaskClaimKeys.TOTAL]: number; +} + +export type TaskClaimMetric = TaskClaimCounts & { + duration: SerializedHistogram; }; export class TaskClaimMetricsAggregator implements ITaskMetricsAggregator { - private claimSuccessRate = new SuccessRateCounter(); + private counter: MetricCounterService = new MetricCounterService( + Object.values(TaskClaimKeys) + ); private durationHistogram = new SimpleHistogram(HDR_HISTOGRAM_MAX, HDR_HISTOGRAM_BUCKET_SIZE); public initialMetric(): TaskClaimMetric { return { - ...this.claimSuccessRate.initialMetric(), + ...this.counter.initialMetrics(), duration: { counts: [], values: [] }, }; } public collect(): TaskClaimMetric { return { - ...this.claimSuccessRate.get(), - duration: this.serializeHistogram(), + ...this.counter.collect(), + duration: this.durationHistogram.serialize(), }; } public reset() { - this.claimSuccessRate.reset(); + this.counter.reset(); this.durationHistogram.reset(); } public processTaskLifecycleEvent(taskEvent: TaskLifecycleEvent) { const success = isOk((taskEvent as TaskRun).event); - this.claimSuccessRate.increment(success); + if (success) { + this.counter.increment(TaskClaimKeys.SUCCESS); + } + this.counter.increment(TaskClaimKeys.TOTAL); if (taskEvent.timing) { const durationInMs = taskEvent.timing.stop - taskEvent.timing.start; this.durationHistogram.record(durationInMs); } } - - private serializeHistogram() { - const counts: number[] = []; - const values: number[] = []; - - for (const { count, value } of this.durationHistogram.get(true)) { - counts.push(count); - values.push(value); - } - - return { counts, values }; - } } diff --git a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.test.ts b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.test.ts new file mode 100644 index 0000000000000..964bf10f0e9b2 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.test.ts @@ -0,0 +1,393 @@ +/* + * Copyright 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 sinon from 'sinon'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { taskStoreMock } from '../task_store.mock'; +import { TaskManagerMetricsCollector } from './task_metrics_collector'; + +describe('TaskManagerMetricsCollector', () => { + let clock: sinon.SinonFakeTimers; + const mockTaskStore = taskStoreMock.create({}); + const logger = loggingSystemMock.create().get(); + + beforeEach(() => { + clock = sinon.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] }); + }); + + afterEach(() => { + jest.clearAllMocks(); + clock.restore(); + }); + + test('intializes the metrics collector with the provided interval and emits events at each interval', async () => { + mockTaskStore.aggregate.mockResolvedValueOnce({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 4, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + overallOverdueByHistogram: { + buckets: [ + { key: 0, doc_count: 1 }, + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + }, + byTaskType: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'taskType1', + doc_count: 3, + overdueByHistogram: { + buckets: [ + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + }, + }, + { + key: 'taskType2', + doc_count: 1, + overdueByHistogram: { + buckets: [{ key: 0, doc_count: 1 }], + }, + }, + ], + }, + }, + }); + mockTaskStore.aggregate.mockResolvedValueOnce({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 0, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + overallOverdueByHistogram: { + buckets: [], + }, + byTaskType: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + }); + mockTaskStore.aggregate.mockResolvedValueOnce({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 1, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + overallOverdueByHistogram: { + buckets: [{ key: 0, doc_count: 1 }], + }, + byTaskType: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'taskType3', + doc_count: 1, + overdueByHistogram: { + buckets: [{ key: 0, doc_count: 1 }], + }, + }, + ], + }, + }, + }); + const pollInterval = 100; + const halfInterval = Math.floor(pollInterval / 2); + + const taskManagerMetricsCollector = new TaskManagerMetricsCollector({ + logger, + pollInterval, + store: mockTaskStore, + }); + const handler = jest.fn(); + taskManagerMetricsCollector.events.subscribe(handler); + + expect(mockTaskStore.aggregate).toHaveBeenCalledTimes(1); + expect(mockTaskStore.aggregate).toHaveBeenCalledWith({ + aggs: { + overallOverdueByHistogram: { + histogram: { + field: 'overdueBySeconds', + min_doc_count: 1, + interval: 10, + }, + }, + byTaskType: { + terms: { field: 'task.taskType', size: 500 }, + aggs: { + overdueByHistogram: { + histogram: { + field: 'overdueBySeconds', + interval: 10, + }, + }, + }, + }, + }, + runtime_mappings: { + overdueBySeconds: { + type: 'long', + script: { + source: ` + def runAt = doc['task.runAt']; + if(!runAt.empty) { + emit((new Date().getTime() - runAt.value.getMillis()) / 1000); + } else { + def retryAt = doc['task.retryAt']; + if(!retryAt.empty) { + emit((new Date().getTime() - retryAt.value.getMillis()) / 1000); + } else { + emit(0); + } + } + `, + }, + }, + }, + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'task.status': 'idle' } }, + { range: { 'task.runAt': { lte: 'now' } } }, + ], + }, + }, + { + bool: { + must: [ + { + bool: { + should: [ + { term: { 'task.status': 'running' } }, + { term: { 'task.status': 'claiming' } }, + ], + }, + }, + { range: { 'task.retryAt': { lte: 'now' } } }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + size: 0, + }); + + await new Promise((resolve) => setImmediate(resolve)); + clock.tick(halfInterval); + expect(mockTaskStore.aggregate).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + event: { + tag: 'ok', + value: { + numOverdueTasks: { + taskType1: [ + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + taskType2: [{ key: 0, doc_count: 1 }], + total: [ + { key: 0, doc_count: 1 }, + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + }, + }, + }, + type: 'TASK_MANAGER_METRIC', + }); + clock.tick(halfInterval); + + await new Promise((resolve) => setImmediate(resolve)); + expect(mockTaskStore.aggregate).toHaveBeenCalledTimes(2); + expect(handler).toHaveBeenCalledTimes(2); + expect(handler).toHaveBeenCalledWith({ + event: { + tag: 'ok', + value: { + numOverdueTasks: { + total: [], + }, + }, + }, + type: 'TASK_MANAGER_METRIC', + }); + + await new Promise((resolve) => setImmediate(resolve)); + clock.tick(pollInterval + 10); + await new Promise((resolve) => setImmediate(resolve)); + expect(mockTaskStore.aggregate).toHaveBeenCalledTimes(3); + expect(handler).toHaveBeenCalledTimes(3); + expect(handler).toHaveBeenCalledWith({ + event: { + tag: 'ok', + value: { + numOverdueTasks: { + taskType3: [{ key: 0, doc_count: 1 }], + total: [{ key: 0, doc_count: 1 }], + }, + }, + }, + type: 'TASK_MANAGER_METRIC', + }); + }); + + test('emits empty metric when querying for data fails and continues to query on interval', async () => { + mockTaskStore.aggregate.mockImplementationOnce(async () => { + throw new Error('failed to query'); + }); + mockTaskStore.aggregate.mockResolvedValueOnce({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 4, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + overallOverdueByHistogram: { + buckets: [ + { key: 0, doc_count: 1 }, + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + }, + byTaskType: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'taskType1', + doc_count: 3, + overdueByHistogram: { + buckets: [ + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + }, + }, + { + key: 'taskType2', + doc_count: 1, + overdueByHistogram: { + buckets: [{ key: 0, doc_count: 1 }], + }, + }, + ], + }, + }, + }); + const pollInterval = 100; + const halfInterval = Math.floor(pollInterval / 2); + + const taskManagerMetricsCollector = new TaskManagerMetricsCollector({ + logger, + pollInterval, + store: mockTaskStore, + }); + const handler = jest.fn(); + taskManagerMetricsCollector.events.subscribe(handler); + + await new Promise((resolve) => setImmediate(resolve)); + clock.tick(halfInterval); + + expect(mockTaskStore.aggregate).toHaveBeenCalledTimes(1); + expect(logger.debug).toHaveBeenCalledWith( + `Error querying for task manager metrics - failed to query` + ); + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + event: { + tag: 'ok', + value: { + numOverdueTasks: { + total: [], + }, + }, + }, + type: 'TASK_MANAGER_METRIC', + }); + + clock.tick(halfInterval); + + await new Promise((resolve) => setImmediate(resolve)); + expect(mockTaskStore.aggregate).toHaveBeenCalledTimes(2); + expect(handler).toHaveBeenCalledTimes(2); + expect(handler).toHaveBeenCalledWith({ + event: { + tag: 'ok', + value: { + numOverdueTasks: { + taskType1: [ + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + taskType2: [{ key: 0, doc_count: 1 }], + total: [ + { key: 0, doc_count: 1 }, + { key: 10, doc_count: 1 }, + { key: 60, doc_count: 2 }, + ], + }, + }, + }, + type: 'TASK_MANAGER_METRIC', + }); + }); + + test('handles malformed result', async () => { + mockTaskStore.aggregate.mockResolvedValueOnce({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 4, relation: 'eq' }, max_score: null, hits: [] }, + // no aggregation in result + }); + const pollInterval = 100; + const halfInterval = Math.floor(pollInterval / 2); + + const taskManagerMetricsCollector = new TaskManagerMetricsCollector({ + logger, + pollInterval, + store: mockTaskStore, + }); + const handler = jest.fn(); + taskManagerMetricsCollector.events.subscribe(handler); + + await new Promise((resolve) => setImmediate(resolve)); + clock.tick(halfInterval); + + expect(mockTaskStore.aggregate).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith({ + event: { + tag: 'ok', + value: { + numOverdueTasks: { + total: [], + }, + }, + }, + type: 'TASK_MANAGER_METRIC', + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts new file mode 100644 index 0000000000000..d588a001693b9 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts @@ -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 { Logger } from '@kbn/core/server'; +import { + AggregationsStringTermsBucket, + AggregationsStringTermsBucketKeys, + AggregationsTermsAggregateBase, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { Observable, Subject } from 'rxjs'; +import { TaskStore } from '../task_store'; +import { + IdleTaskWithExpiredRunAt, + RunningOrClaimingTaskWithExpiredRetryAt, +} from '../queries/mark_available_tasks_as_claimed'; +import { ITaskEventEmitter, TaskLifecycleEvent } from '../polling_lifecycle'; +import { asTaskManagerMetricEvent } from '../task_events'; +import { asOk } from '../lib/result_type'; + +const DEFAULT_POLL_INTERVAL = 5000; // query every 5 seconds +interface ConstructorOpts { + logger: Logger; + store: TaskStore; + pollInterval?: number; +} + +export interface TaskManagerMetrics { + numOverdueTasks: { + total: AggregationsStringTermsBucketKeys[]; + [key: string]: AggregationsStringTermsBucketKeys[]; + }; +} + +type OverdueTaskAggBucket = AggregationsStringTermsBucketKeys & { + overdueByHistogram: AggregationsStringTermsBucket; +}; + +export class TaskManagerMetricsCollector implements ITaskEventEmitter { + private store: TaskStore; + private logger: Logger; + private readonly pollInterval: number; + + private running: boolean = false; + + // emit collected metrics + private metrics$ = new Subject(); + + constructor({ logger, store, pollInterval }: ConstructorOpts) { + this.store = store; + this.logger = logger; + this.pollInterval = pollInterval ?? DEFAULT_POLL_INTERVAL; + + this.start(); + } + + public get events(): Observable { + return this.metrics$; + } + + private start() { + if (!this.running) { + this.running = true; + this.runCollectionCycle(); + } + } + + private async runCollectionCycle() { + const start = Date.now(); + try { + const results = await this.store.aggregate({ + size: 0, + query: { + bool: { + filter: [ + { + bool: { + should: [IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt], + }, + }, + ], + }, + }, + runtime_mappings: { + overdueBySeconds: { + type: 'long', + script: { + source: ` + def runAt = doc['task.runAt']; + if(!runAt.empty) { + emit((new Date().getTime() - runAt.value.getMillis()) / 1000); + } else { + def retryAt = doc['task.retryAt']; + if(!retryAt.empty) { + emit((new Date().getTime() - retryAt.value.getMillis()) / 1000); + } else { + emit(0); + } + } + `, + }, + }, + }, + aggs: { + overallOverdueByHistogram: { + histogram: { + field: 'overdueBySeconds', + min_doc_count: 1, + interval: 10, + }, + }, + byTaskType: { + terms: { + field: 'task.taskType', + size: 500, + }, + aggs: { + overdueByHistogram: { + histogram: { + field: 'overdueBySeconds', + interval: 10, + }, + }, + }, + }, + }, + }); + + const aggregations = + (results?.aggregations as { + overallOverdueByHistogram: AggregationsTermsAggregateBase; + byTaskType: AggregationsTermsAggregateBase; + }) ?? {}; + const byTaskType = ((aggregations.byTaskType.buckets as OverdueTaskAggBucket[]) ?? []).reduce( + (acc: Record, bucket: OverdueTaskAggBucket) => { + acc[bucket.key] = bucket?.overdueByHistogram?.buckets ?? []; + return acc; + }, + {} + ); + const metrics = { + numOverdueTasks: { + total: + (aggregations?.overallOverdueByHistogram + ?.buckets as AggregationsStringTermsBucketKeys[]) ?? [], + ...byTaskType, + }, + }; + this.metrics$.next(asTaskManagerMetricEvent(asOk(metrics))); + } catch (e) { + this.logger.debug(`Error querying for task manager metrics - ${e.message}`); + // emit empty metrics so we don't have stale metrics + this.metrics$.next(asTaskManagerMetricEvent(asOk({ numOverdueTasks: { total: [] } }))); + } + if (this.running) { + // Set the next runCycle call + setTimeout( + this.runCollectionCycle.bind(this), + Math.max(this.pollInterval - (Date.now() - start), 0) + ); + } + } +} diff --git a/x-pack/plugins/task_manager/server/metrics/task_overdue_metrics_aggregator.test.ts b/x-pack/plugins/task_manager/server/metrics/task_overdue_metrics_aggregator.test.ts new file mode 100644 index 0000000000000..836eee60f1640 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/task_overdue_metrics_aggregator.test.ts @@ -0,0 +1,243 @@ +/* + * Copyright 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 { asOk } from '../lib/result_type'; +import { asTaskManagerMetricEvent } from '../task_events'; +import { TaskManagerMetrics } from './task_metrics_collector'; +import { TaskOverdueMetricsAggregator } from './task_overdue_metrics_aggregator'; + +export const getTaskManagerMetricEvent = (value: TaskManagerMetrics) => { + return asTaskManagerMetricEvent(asOk(value)); +}; + +describe('TaskOverdueMetricsAggregator', () => { + let taskOverdueMetricsAggregator: TaskOverdueMetricsAggregator; + beforeEach(() => { + taskOverdueMetricsAggregator = new TaskOverdueMetricsAggregator(); + }); + + test('should correctly initialize', () => { + expect(taskOverdueMetricsAggregator.collect()).toEqual({ + overall: { overdue_by: { counts: [], values: [] } }, + by_type: {}, + }); + }); + + test('should correctly return initialMetrics', () => { + expect(taskOverdueMetricsAggregator.initialMetric()).toEqual({ + overall: { overdue_by: { counts: [], values: [] } }, + by_type: {}, + }); + }); + + test('should correctly process task manager metric event', () => { + taskOverdueMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerMetricEvent({ + numOverdueTasks: { + telemetry: [ + { key: 0, doc_count: 1 }, + { key: 20, doc_count: 1 }, + ], + total: [ + { key: 0, doc_count: 1 }, + { key: 20, doc_count: 1 }, + ], + }, + }) + ); + expect(taskOverdueMetricsAggregator.collect()).toEqual({ + overall: { overdue_by: { counts: [1, 0, 1], values: [10, 20, 30] } }, + by_type: { + telemetry: { overdue_by: { counts: [1, 0, 1], values: [10, 20, 30] } }, + }, + }); + }); + + test('should correctly process empty task manager metric event', () => { + taskOverdueMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerMetricEvent({ + numOverdueTasks: { + total: [], + }, + }) + ); + expect(taskOverdueMetricsAggregator.collect()).toEqual({ + overall: { overdue_by: { counts: [], values: [] } }, + by_type: {}, + }); + }); + + test('should correctly return latest metric event', () => { + taskOverdueMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerMetricEvent({ + numOverdueTasks: { + telemetry: [ + { key: 0, doc_count: 1 }, + { key: 20, doc_count: 1 }, + ], + total: [ + { key: 0, doc_count: 1 }, + { key: 20, doc_count: 1 }, + ], + }, + }) + ); + taskOverdueMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerMetricEvent({ + numOverdueTasks: { + telemetry: [{ key: 40, doc_count: 1 }], + total: [{ key: 40, doc_count: 1 }], + }, + }) + ); + expect(taskOverdueMetricsAggregator.collect()).toEqual({ + overall: { overdue_by: { counts: [0, 0, 0, 0, 1], values: [10, 20, 30, 40, 50] } }, + by_type: { + telemetry: { overdue_by: { counts: [0, 0, 0, 0, 1], values: [10, 20, 30, 40, 50] } }, + }, + }); + }); + + test('should correctly group alerting and action task types', () => { + taskOverdueMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerMetricEvent({ + numOverdueTasks: { + 'alerting:example': [{ key: 40, doc_count: 1 }], + 'alerting:.index-threshold': [ + { key: 20, doc_count: 2 }, + { key: 120, doc_count: 1 }, + ], + 'actions:webhook': [{ key: 0, doc_count: 2 }], + 'actions:.email': [{ key: 0, doc_count: 1 }], + total: [ + { key: 0, doc_count: 3 }, + { key: 20, doc_count: 2 }, + { key: 40, doc_count: 1 }, + { key: 120, doc_count: 1 }, + ], + }, + }) + ); + + expect(taskOverdueMetricsAggregator.collect()).toEqual({ + overall: { + overdue_by: { + counts: [3, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + by_type: { + 'alerting:example': { + overdue_by: { + counts: [0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50], + }, + }, + 'alerting:__index-threshold': { + overdue_by: { + counts: [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + alerting: { + overdue_by: { + counts: [0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + 'actions:webhook': { + overdue_by: { + counts: [2], + values: [10], + }, + }, + 'actions:__email': { + overdue_by: { + counts: [1], + values: [10], + }, + }, + actions: { + overdue_by: { + counts: [3], + values: [10], + }, + }, + }, + }); + }); + + test('should correctly ignore reset events', () => { + taskOverdueMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerMetricEvent({ + numOverdueTasks: { + 'alerting:example': [{ key: 40, doc_count: 1 }], + 'alerting:.index-threshold': [ + { key: 20, doc_count: 2 }, + { key: 120, doc_count: 1 }, + ], + 'actions:webhook': [{ key: 0, doc_count: 2 }], + 'actions:.email': [{ key: 0, doc_count: 1 }], + total: [ + { key: 0, doc_count: 3 }, + { key: 20, doc_count: 2 }, + { key: 40, doc_count: 1 }, + { key: 120, doc_count: 1 }, + ], + }, + }) + ); + taskOverdueMetricsAggregator.reset(); + expect(taskOverdueMetricsAggregator.collect()).toEqual({ + overall: { + overdue_by: { + counts: [3, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + by_type: { + 'alerting:example': { + overdue_by: { + counts: [0, 0, 0, 0, 1], + + values: [10, 20, 30, 40, 50], + }, + }, + 'alerting:__index-threshold': { + overdue_by: { + counts: [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + alerting: { + overdue_by: { + counts: [0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], + }, + }, + 'actions:webhook': { + overdue_by: { + counts: [2], + values: [10], + }, + }, + 'actions:__email': { + overdue_by: { + counts: [1], + values: [10], + }, + }, + actions: { + overdue_by: { + counts: [3], + values: [10], + }, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/metrics/task_overdue_metrics_aggregator.ts b/x-pack/plugins/task_manager/server/metrics/task_overdue_metrics_aggregator.ts new file mode 100644 index 0000000000000..735e93abe0e72 --- /dev/null +++ b/x-pack/plugins/task_manager/server/metrics/task_overdue_metrics_aggregator.ts @@ -0,0 +1,101 @@ +/* + * Copyright 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 { JsonObject } from '@kbn/utility-types'; +import { keys, mapValues } from 'lodash'; +import { isOk, unwrap } from '../lib/result_type'; +import { TaskLifecycleEvent } from '../polling_lifecycle'; +import { TaskManagerMetric } from '../task_events'; +import { + unflattenObject, + getTaskTypeGroup, + type SerializedHistogram, + SimpleHistogram, +} from './lib'; +import { TaskManagerMetrics } from './task_metrics_collector'; +import { ITaskMetricsAggregator } from './types'; + +const HDR_HISTOGRAM_MAX = 1800; // 30 minutes +const HDR_HISTOGRAM_BUCKET_SIZE = 10; // 10 seconds + +const OVERDUE_BY_KEY = 'overdue_by'; + +enum TaskOverdueMetricKeys { + OVERALL = 'overall', + BY_TYPE = 'by_type', +} + +interface TaskOverdueHistogram extends JsonObject { + [OVERDUE_BY_KEY]: SerializedHistogram; +} +export interface TaskOverdueMetric extends JsonObject { + [TaskOverdueMetricKeys.OVERALL]: TaskOverdueHistogram; + [TaskOverdueMetricKeys.BY_TYPE]: { + [key: string]: TaskOverdueHistogram; + }; +} + +export class TaskOverdueMetricsAggregator implements ITaskMetricsAggregator { + private histograms: Record = {}; + + public initialMetric(): TaskOverdueMetric { + return { + by_type: {}, + overall: { overdue_by: { counts: [], values: [] } }, + }; + } + + public collect(): TaskOverdueMetric { + if (keys(this.histograms).length === 0) { + return { + by_type: {}, + overall: { overdue_by: { counts: [], values: [] } }, + }; + } + return unflattenObject(mapValues(this.histograms, (hist) => hist.serialize())); + } + + public reset() { + // no-op because this metric is not a counter + } + + public processTaskLifecycleEvent(taskEvent: TaskLifecycleEvent) { + this.histograms = {}; + let metric: TaskManagerMetrics; + if (isOk((taskEvent as TaskManagerMetric).event)) { + metric = unwrap((taskEvent as TaskManagerMetric).event) as TaskManagerMetrics; + + for (const key of Object.keys(metric.numOverdueTasks)) { + const hist = new SimpleHistogram(HDR_HISTOGRAM_MAX, HDR_HISTOGRAM_BUCKET_SIZE); + (metric.numOverdueTasks[key] ?? []).forEach((bucket) => { + const overdueInSec = parseInt(bucket.key, 10); + hist.record(overdueInSec, bucket.doc_count); + + if (key === 'total') { + this.histograms[`${TaskOverdueMetricKeys.OVERALL}.${OVERDUE_BY_KEY}`] = hist; + } else { + const taskType = key.replaceAll('.', '__'); + const taskTypeGroup = getTaskTypeGroup(taskType); + this.histograms[`${TaskOverdueMetricKeys.BY_TYPE}.${taskType}.${OVERDUE_BY_KEY}`] = + hist; + + if (taskTypeGroup) { + const groupHist = + this.histograms[ + `${TaskOverdueMetricKeys.BY_TYPE}.${taskTypeGroup}.${OVERDUE_BY_KEY}` + ] ?? new SimpleHistogram(HDR_HISTOGRAM_MAX, HDR_HISTOGRAM_BUCKET_SIZE); + groupHist.record(overdueInSec, bucket.doc_count); + this.histograms[ + `${TaskOverdueMetricKeys.BY_TYPE}.${taskTypeGroup}.${OVERDUE_BY_KEY}` + ] = groupHist; + } + } + }); + } + } + } +} diff --git a/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.test.ts b/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.test.ts index e3654fd9a21d5..628952619814d 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.test.ts @@ -8,11 +8,16 @@ import * as uuid from 'uuid'; import { asOk, asErr } from '../lib/result_type'; import { TaskStatus } from '../task'; -import { asTaskRunEvent, TaskPersistence } from '../task_events'; +import { + asTaskManagerStatEvent, + asTaskRunEvent, + TaskManagerStats, + TaskPersistence, +} from '../task_events'; import { TaskRunResult } from '../task_running'; import { TaskRunMetricsAggregator } from './task_run_metrics_aggregator'; -export const getTaskRunSuccessEvent = (type: string) => { +export const getTaskRunSuccessEvent = (type: string, isExpired: boolean = false) => { const id = uuid.v4(); return asTaskRunEvent( id, @@ -33,6 +38,7 @@ export const getTaskRunSuccessEvent = (type: string) => { }, persistence: TaskPersistence.Recurring, result: TaskRunResult.Success, + isExpired, }), { start: 1689698780490, @@ -41,7 +47,7 @@ export const getTaskRunSuccessEvent = (type: string) => { ); }; -export const getTaskRunFailedEvent = (type: string) => { +export const getTaskRunFailedEvent = (type: string, isExpired: boolean = false) => { const id = uuid.v4(); return asTaskRunEvent( id, @@ -63,10 +69,15 @@ export const getTaskRunFailedEvent = (type: string) => { }, persistence: TaskPersistence.Recurring, result: TaskRunResult.Failed, + isExpired, }) ); }; +export const getTaskManagerStatEvent = (value: number, id: TaskManagerStats = 'runDelay') => { + return asTaskManagerStatEvent(id, asOk(value)); +}; + describe('TaskRunMetricsAggregator', () => { let taskRunMetricsAggregator: TaskRunMetricsAggregator; beforeEach(() => { @@ -75,14 +86,13 @@ describe('TaskRunMetricsAggregator', () => { test('should correctly initialize', () => { expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 0, total: 0 }, - by_type: {}, + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, }); }); test('should correctly return initialMetrics', () => { expect(taskRunMetricsAggregator.initialMetric()).toEqual({ - overall: { success: 0, total: 0 }, + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, by_type: {}, }); }); @@ -91,9 +101,36 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry')); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 2, total: 2 }, + overall: { success: 2, not_timed_out: 2, total: 2, delay: { counts: [], values: [] } }, + by_type: { + telemetry: { success: 2, not_timed_out: 2, total: 2 }, + }, + }); + }); + + test('should correctly process task manager runDelay stat', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(3.343)); + expect(taskRunMetricsAggregator.collect()).toEqual({ + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [1], values: [10] } }, + }); + }); + + test('should ignore task manager stats that are not runDelays', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerStatEvent(3.343, 'pollingDelay') + ); + expect(taskRunMetricsAggregator.collect()).toEqual({ + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, + }); + }); + + test('should correctly process task run success event where task run has timed out', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry', true)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry', true)); + expect(taskRunMetricsAggregator.collect()).toEqual({ + overall: { success: 2, not_timed_out: 0, total: 2, delay: { counts: [], values: [] } }, by_type: { - telemetry: { success: 2, total: 2 }, + telemetry: { success: 2, not_timed_out: 0, total: 2 }, }, }); }); @@ -102,9 +139,20 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 0, total: 2 }, + overall: { success: 0, not_timed_out: 2, total: 2, delay: { counts: [], values: [] } }, by_type: { - telemetry: { success: 0, total: 2 }, + telemetry: { success: 0, not_timed_out: 2, total: 2 }, + }, + }); + }); + + test('should correctly process task run failure event where task run has timed out', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry', true)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry', true)); + expect(taskRunMetricsAggregator.collect()).toEqual({ + overall: { success: 0, not_timed_out: 0, total: 2, delay: { counts: [], values: [] } }, + by_type: { + telemetry: { success: 0, not_timed_out: 0, total: 2 }, }, }); }); @@ -112,13 +160,13 @@ describe('TaskRunMetricsAggregator', () => { test('should correctly process different task types', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); - taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report', true)); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 3, total: 4 }, + overall: { success: 3, not_timed_out: 3, total: 4, delay: { counts: [], values: [] } }, by_type: { - report: { success: 2, total: 2 }, - telemetry: { success: 1, total: 2 }, + report: { success: 2, not_timed_out: 1, total: 2 }, + telemetry: { success: 1, not_timed_out: 2, total: 2 }, }, }); }); @@ -128,7 +176,9 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); - taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('alerting:example')); + taskRunMetricsAggregator.processTaskLifecycleEvent( + getTaskRunSuccessEvent('alerting:example', true) + ); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('alerting:example')); taskRunMetricsAggregator.processTaskLifecycleEvent( getTaskRunSuccessEvent('alerting:.index-threshold') @@ -140,32 +190,39 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('alerting:example')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('actions:.email')); taskRunMetricsAggregator.processTaskLifecycleEvent( - getTaskRunSuccessEvent('alerting:.index-threshold') + getTaskRunSuccessEvent('alerting:.index-threshold', true) ); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 11, total: 14 }, + overall: { success: 11, not_timed_out: 12, total: 14, delay: { counts: [], values: [] } }, by_type: { - actions: { success: 3, total: 3 }, - 'actions:.email': { success: 1, total: 1 }, - 'actions:webhook': { success: 2, total: 2 }, - alerting: { success: 5, total: 7 }, - 'alerting:example': { success: 3, total: 5 }, - 'alerting:.index-threshold': { success: 2, total: 2 }, - report: { success: 2, total: 2 }, - telemetry: { success: 1, total: 2 }, + actions: { success: 3, not_timed_out: 3, total: 3 }, + 'actions:__email': { success: 1, not_timed_out: 1, total: 1 }, + 'actions:webhook': { success: 2, not_timed_out: 2, total: 2 }, + alerting: { success: 5, not_timed_out: 5, total: 7 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 5 }, + 'alerting:__index-threshold': { success: 2, not_timed_out: 1, total: 2 }, + report: { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 2, total: 2 }, }, }); }); test('should correctly reset counter', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(3.343)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(25.45)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(6.4478)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(9.241)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); - taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('alerting:example')); + taskRunMetricsAggregator.processTaskLifecycleEvent( + getTaskRunSuccessEvent('alerting:example', true) + ); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('alerting:example')); taskRunMetricsAggregator.processTaskLifecycleEvent( - getTaskRunSuccessEvent('alerting:.index-threshold') + getTaskRunSuccessEvent('alerting:.index-threshold', true) ); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('actions:webhook')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('alerting:example')); @@ -177,31 +234,36 @@ describe('TaskRunMetricsAggregator', () => { getTaskRunSuccessEvent('alerting:.index-threshold') ); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 11, total: 14 }, + overall: { + success: 11, + not_timed_out: 12, + total: 14, + delay: { counts: [3, 0, 1], values: [10, 20, 30] }, + }, by_type: { - actions: { success: 3, total: 3 }, - 'actions:.email': { success: 1, total: 1 }, - 'actions:webhook': { success: 2, total: 2 }, - alerting: { success: 5, total: 7 }, - 'alerting:example': { success: 3, total: 5 }, - 'alerting:.index-threshold': { success: 2, total: 2 }, - report: { success: 2, total: 2 }, - telemetry: { success: 1, total: 2 }, + actions: { success: 3, not_timed_out: 3, total: 3 }, + 'actions:__email': { success: 1, not_timed_out: 1, total: 1 }, + 'actions:webhook': { success: 2, not_timed_out: 2, total: 2 }, + alerting: { success: 5, not_timed_out: 5, total: 7 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 5 }, + 'alerting:__index-threshold': { success: 2, not_timed_out: 1, total: 2 }, + report: { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 2, total: 2 }, }, }); taskRunMetricsAggregator.reset(); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 0, total: 0 }, + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, by_type: { - actions: { success: 0, total: 0 }, - 'actions:.email': { success: 0, total: 0 }, - 'actions:webhook': { success: 0, total: 0 }, - alerting: { success: 0, total: 0 }, - 'alerting:example': { success: 0, total: 0 }, - 'alerting:.index-threshold': { success: 0, total: 0 }, - report: { success: 0, total: 0 }, - telemetry: { success: 0, total: 0 }, + actions: { success: 0, not_timed_out: 0, total: 0 }, + 'actions:__email': { success: 0, not_timed_out: 0, total: 0 }, + 'actions:webhook': { success: 0, not_timed_out: 0, total: 0 }, + alerting: { success: 0, not_timed_out: 0, total: 0 }, + 'alerting:example': { success: 0, not_timed_out: 0, total: 0 }, + 'alerting:__index-threshold': { success: 0, not_timed_out: 0, total: 0 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, }, }); }); diff --git a/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts b/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts index c25d80f112df1..91edc9f783cd5 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts @@ -6,80 +6,122 @@ */ import { JsonObject } from '@kbn/utility-types'; -import { isOk, unwrap } from '../lib/result_type'; +import { merge } from 'lodash'; +import { isOk, Ok, unwrap } from '../lib/result_type'; import { TaskLifecycleEvent } from '../polling_lifecycle'; -import { ErroredTask, RanTask, TaskRun } from '../task_events'; -import { SuccessRate, SuccessRateCounter } from './success_rate_counter'; +import { + ErroredTask, + RanTask, + TaskRun, + isTaskManagerStatEvent, + isTaskRunEvent, + TaskManagerStat, +} from '../task_events'; +import { + getTaskTypeGroup, + MetricCounterService, + SimpleHistogram, + type SerializedHistogram, +} from './lib'; import { ITaskMetricsAggregator } from './types'; -const taskTypeGrouping = new Set(['alerting:', 'actions:']); +const HDR_HISTOGRAM_MAX = 1800; // 30 minutes +const HDR_HISTOGRAM_BUCKET_SIZE = 10; // 10 seconds + +enum TaskRunKeys { + SUCCESS = 'success', + NOT_TIMED_OUT = 'not_timed_out', + TOTAL = 'total', +} + +enum TaskRunMetricKeys { + OVERALL = 'overall', + BY_TYPE = 'by_type', +} + +interface TaskRunCounts extends JsonObject { + [TaskRunKeys.SUCCESS]: number; + [TaskRunKeys.NOT_TIMED_OUT]: number; + [TaskRunKeys.TOTAL]: number; +} + +export interface TaskRunMetrics extends JsonObject { + [TaskRunMetricKeys.OVERALL]: TaskRunCounts; + [TaskRunMetricKeys.BY_TYPE]: { + [key: string]: TaskRunCounts; + }; +} export interface TaskRunMetric extends JsonObject { - overall: SuccessRate; - by_type: { - [key: string]: SuccessRate; + overall: TaskRunMetrics['overall'] & { + delay: SerializedHistogram; }; + by_type: TaskRunMetrics['by_type']; } export class TaskRunMetricsAggregator implements ITaskMetricsAggregator { - private taskRunSuccessRate = new SuccessRateCounter(); - private taskRunCounter: Map = new Map(); + private counter: MetricCounterService = new MetricCounterService( + Object.values(TaskRunKeys), + TaskRunMetricKeys.OVERALL + ); + private delayHistogram = new SimpleHistogram(HDR_HISTOGRAM_MAX, HDR_HISTOGRAM_BUCKET_SIZE); public initialMetric(): TaskRunMetric { - return { - overall: this.taskRunSuccessRate.initialMetric(), + return merge(this.counter.initialMetrics(), { by_type: {}, - }; + overall: { delay: { counts: [], values: [] } }, + }); } public collect(): TaskRunMetric { - return { - overall: this.taskRunSuccessRate.get(), - by_type: this.collectTaskTypeEntries(), - }; + return merge(this.counter.collect(), { overall: { delay: this.delayHistogram.serialize() } }); } public reset() { - this.taskRunSuccessRate.reset(); - for (const taskType of this.taskRunCounter.keys()) { - this.taskRunCounter.get(taskType)!.reset(); - } + this.counter.reset(); + this.delayHistogram.reset(); } public processTaskLifecycleEvent(taskEvent: TaskLifecycleEvent) { - const { task }: RanTask | ErroredTask = unwrap((taskEvent as TaskRun).event); - const taskType = task.taskType; - - const taskTypeSuccessRate: SuccessRateCounter = - this.taskRunCounter.get(taskType) ?? new SuccessRateCounter(); + if (isTaskRunEvent(taskEvent)) { + this.processTaskRunEvent(taskEvent); + } else if (isTaskManagerStatEvent(taskEvent)) { + this.processTaskManagerStatEvent(taskEvent); + } + } + private processTaskRunEvent(taskEvent: TaskRun) { + const { task, isExpired }: RanTask | ErroredTask = unwrap(taskEvent.event); const success = isOk((taskEvent as TaskRun).event); - this.taskRunSuccessRate.increment(success); - taskTypeSuccessRate.increment(success); - this.taskRunCounter.set(taskType, taskTypeSuccessRate); - - const taskTypeGroup = this.getTaskTypeGroup(taskType); - if (taskTypeGroup) { - const taskTypeGroupSuccessRate: SuccessRateCounter = - this.taskRunCounter.get(taskTypeGroup) ?? new SuccessRateCounter(); - taskTypeGroupSuccessRate.increment(success); - this.taskRunCounter.set(taskTypeGroup, taskTypeGroupSuccessRate); + const taskType = task.taskType.replaceAll('.', '__'); + const taskTypeGroup = getTaskTypeGroup(taskType); + + // increment the total counters + this.incrementCounters(TaskRunKeys.TOTAL, taskType, taskTypeGroup); + + // increment success counters + if (success) { + this.incrementCounters(TaskRunKeys.SUCCESS, taskType, taskTypeGroup); + } + + // increment expired counters + if (!isExpired) { + this.incrementCounters(TaskRunKeys.NOT_TIMED_OUT, taskType, taskTypeGroup); } } - private collectTaskTypeEntries() { - const collected: Record = {}; - for (const [key, value] of this.taskRunCounter) { - collected[key] = value.get(); + private processTaskManagerStatEvent(taskEvent: TaskManagerStat) { + if (taskEvent.id === 'runDelay') { + const delayInSec = Math.round((taskEvent.event as Ok).value); + this.delayHistogram.record(delayInSec); } - return collected; } - private getTaskTypeGroup(taskType: string): string | undefined { - for (const group of taskTypeGrouping) { - if (taskType.startsWith(group)) { - return group.replaceAll(':', ''); - } + private incrementCounters(key: TaskRunKeys, taskType: string, group?: string) { + this.counter.increment(key, TaskRunMetricKeys.OVERALL); + this.counter.increment(key, `${TaskRunMetricKeys.BY_TYPE}.${taskType}`); + if (group) { + this.counter.increment(key, `${TaskRunMetricKeys.BY_TYPE}.${group}`); } } } diff --git a/x-pack/plugins/task_manager/server/metrics/types.ts b/x-pack/plugins/task_manager/server/metrics/types.ts index 7fbee1fe8abdd..98a30a7a2d350 100644 --- a/x-pack/plugins/task_manager/server/metrics/types.ts +++ b/x-pack/plugins/task_manager/server/metrics/types.ts @@ -11,5 +11,5 @@ export interface ITaskMetricsAggregator { initialMetric: () => T; collect: () => T; reset: () => void; - processTaskLifecycleEvent: (taskEvent: TaskLifecycleEvent) => void; + processTaskLifecycleEvent: (event: TaskLifecycleEvent) => void; } diff --git a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts index 9507b3ab0e4cd..b2dae1a045d6a 100644 --- a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts @@ -647,6 +647,7 @@ const mockTaskRunEvent = ( persistence: persistence ?? (task.schedule ? TaskPersistence.Recurring : TaskPersistence.NonRecurring), result, + isExpired: false, }), timing ); diff --git a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts index 8d4ef4fab2eba..bbbb13dd9060f 100644 --- a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts @@ -362,7 +362,7 @@ const mockTaskRunEvent = ( ) => { const task = mockTaskInstance(overrides); const persistence = TaskPersistence.Recurring; - return asTaskRunEvent(task.id, asOk({ task, persistence, result }), timing); + return asTaskRunEvent(task.id, asOk({ task, persistence, result, isExpired: false }), timing); }; const mockTaskInstance = (overrides: Partial = {}): ConcreteTaskInstance => ({ diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts index 91e81013b726f..9dfa3e8cadfe7 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts @@ -905,6 +905,7 @@ const mockTaskRunEvent = ( persistence: persistence ?? (task.schedule ? TaskPersistence.Recurring : TaskPersistence.NonRecurring), result, + isExpired: false, }), timing ); diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 3b8ab4a54be1f..0c1bafe9f4fc1 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -36,6 +36,7 @@ import { TASK_MANAGER_INDEX } from './constants'; import { AdHocTaskCounter } from './lib/adhoc_task_counter'; import { setupIntervalLogging } from './lib/log_health_metrics'; import { metricsStream, Metrics } from './metrics'; +import { TaskManagerMetricsCollector } from './metrics/task_metrics_collector'; export interface TaskManagerSetupContract { /** @@ -88,6 +89,7 @@ export class TaskManagerPlugin private shouldRunBackgroundTasks: boolean; private readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; private adHocTaskCounter: AdHocTaskCounter; + private taskManagerMetricsCollector?: TaskManagerMetricsCollector; private nodeRoles: PluginInitializerContext['node']['roles']; constructor(private readonly initContext: PluginInitializerContext) { @@ -249,6 +251,10 @@ export class TaskManagerPlugin // Only poll for tasks if configured to run tasks if (this.shouldRunBackgroundTasks) { + this.taskManagerMetricsCollector = new TaskManagerMetricsCollector({ + logger: this.logger, + store: taskStore, + }); this.taskPollingLifecycle = new TaskPollingLifecycle({ config: this.config!, definitions: this.definitions, @@ -285,9 +291,12 @@ export class TaskManagerPlugin this.ephemeralTaskLifecycle ).subscribe((stat) => this.monitoringStats$.next(stat)); - metricsStream(this.config!, this.resetMetrics$, this.taskPollingLifecycle).subscribe((metric) => - this.metrics$.next(metric) - ); + metricsStream({ + config: this.config!, + reset$: this.resetMetrics$, + taskPollingLifecycle: this.taskPollingLifecycle, + taskManagerMetricsCollector: this.taskManagerMetricsCollector, + }).subscribe((metric) => this.metrics$.next(metric)); const taskScheduling = new TaskScheduling({ logger: this.logger, diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index de5f617a27fc7..e7509df01cfc8 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -27,6 +27,7 @@ import { TaskManagerStat, asTaskManagerStatEvent, EphemeralTaskRejectedDueToCapacity, + TaskManagerMetric, } from './task_events'; import { fillPool, FillPoolResult, TimedFillPoolResult } from './lib/fill_pool'; import { Middleware } from './lib/middleware'; @@ -42,6 +43,10 @@ import { TaskTypeDictionary } from './task_type_dictionary'; import { delayOnClaimConflicts } from './polling'; import { TaskClaiming, ClaimOwnershipResult } from './queries/task_claiming'; +export interface ITaskEventEmitter { + get events(): Observable; +} + export type TaskPollingLifecycleOpts = { logger: Logger; definitions: TaskTypeDictionary; @@ -61,12 +66,13 @@ export type TaskLifecycleEvent = | TaskRunRequest | TaskPollingCycle | TaskManagerStat + | TaskManagerMetric | EphemeralTaskRejectedDueToCapacity; /** * The public interface into the task manager system. */ -export class TaskPollingLifecycle { +export class TaskPollingLifecycle implements ITaskEventEmitter { private definitions: TaskTypeDictionary; private store: TaskStore; diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.ts index 07042650a37f9..60237338fb09c 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts @@ -322,10 +322,10 @@ export class TaskClaiming { max_docs: size, } ); - apmTrans?.end('success'); + apmTrans.end('success'); return result; } catch (err) { - apmTrans?.end('failure'); + apmTrans.end('failure'); throw err; } } diff --git a/x-pack/plugins/task_manager/server/task_events.ts b/x-pack/plugins/task_manager/server/task_events.ts index e8808322b397a..277eddee6e435 100644 --- a/x-pack/plugins/task_manager/server/task_events.ts +++ b/x-pack/plugins/task_manager/server/task_events.ts @@ -15,6 +15,7 @@ import { PollingError } from './polling'; import { TaskRunResult } from './task_running'; import { EphemeralTaskInstanceRequest } from './ephemeral_task_lifecycle'; import type { EventLoopDelayConfig } from './config'; +import { TaskManagerMetrics } from './metrics/task_metrics_collector'; export enum TaskPersistence { Recurring = 'recurring', @@ -28,6 +29,7 @@ export enum TaskEventType { TASK_RUN = 'TASK_RUN', TASK_RUN_REQUEST = 'TASK_RUN_REQUEST', TASK_POLLING_CYCLE = 'TASK_POLLING_CYCLE', + TASK_MANAGER_METRIC = 'TASK_MANAGER_METRIC', TASK_MANAGER_STAT = 'TASK_MANAGER_STAT', EPHEMERAL_TASK_DELAYED_DUE_TO_CAPACITY = 'EPHEMERAL_TASK_DELAYED_DUE_TO_CAPACITY', } @@ -70,6 +72,7 @@ export interface RanTask { task: ConcreteTaskInstance; persistence: TaskPersistence; result: TaskRunResult; + isExpired: boolean; } export type ErroredTask = RanTask & { error: Error; @@ -81,6 +84,7 @@ export type TaskClaim = TaskEvent; export type TaskRunRequest = TaskEvent; export type EphemeralTaskRejectedDueToCapacity = TaskEvent; export type TaskPollingCycle = TaskEvent>; +export type TaskManagerMetric = TaskEvent; export type TaskManagerStats = | 'load' @@ -88,7 +92,8 @@ export type TaskManagerStats = | 'claimDuration' | 'queuedEphemeralTasks' | 'ephemeralTaskDelay' - | 'workerUtilization'; + | 'workerUtilization' + | 'runDelay'; export type TaskManagerStat = TaskEvent; export type OkResultOf = EventType extends TaskEvent @@ -173,6 +178,15 @@ export function asTaskManagerStatEvent( }; } +export function asTaskManagerMetricEvent( + event: Result +): TaskManagerMetric { + return { + type: TaskEventType.TASK_MANAGER_METRIC, + event, + }; +} + export function asEphemeralTaskRejectedDueToCapacityEvent( id: string, event: Result, @@ -217,6 +231,11 @@ export function isTaskManagerWorkerUtilizationStatEvent( ): taskEvent is TaskManagerStat { return taskEvent.type === TaskEventType.TASK_MANAGER_STAT && taskEvent.id === 'workerUtilization'; } +export function isTaskManagerMetricEvent( + taskEvent: TaskEvent +): taskEvent is TaskManagerStat { + return taskEvent.type === TaskEventType.TASK_MANAGER_METRIC; +} export function isEphemeralTaskRejectedDueToCapacityEvent( taskEvent: TaskEvent ): taskEvent is EphemeralTaskRejectedDueToCapacity { diff --git a/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts b/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts index a41297c90f67b..1b0a1b9c5b02d 100644 --- a/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts @@ -347,6 +347,7 @@ export class EphemeralTaskManagerRunner implements TaskRunner { task: { ...this.instance.task, state }, persistence: TaskPersistence.Ephemeral, result: TaskRunResult.Success, + isExpired: false, }), taskTiming ) @@ -360,6 +361,7 @@ export class EphemeralTaskManagerRunner implements TaskRunner { task: { ...this.instance.task, state }, persistence: TaskPersistence.Ephemeral, result: TaskRunResult.Failed, + isExpired: false, error, }), taskTiming diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index 7e897840f72c7..1ae176d6993b3 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -16,6 +16,7 @@ import { asTaskMarkRunningEvent, TaskRun, TaskPersistence, + asTaskManagerStatEvent, } from '../task_events'; import { ConcreteTaskInstance, TaskStatus } from '../task'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; @@ -922,11 +923,15 @@ describe('TaskManagerRunner', () => { persistence: TaskPersistence.Recurring, task: originalInstance, result: TaskRunResult.Failed, + isExpired: false, }) ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); test('tasks that return runAt override the schedule', async () => { @@ -1254,12 +1259,56 @@ describe('TaskManagerRunner', () => { task: instance, persistence: TaskPersistence.NonRecurring, result: TaskRunResult.Success, + isExpired: false, }) ) ) ); }); + test('emits TaskEvent when a task is run successfully but completes after timeout', async () => { + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 0, 0, 0).valueOf()); + const id = _.random(1, 20).toString(); + const onTaskEvent = jest.fn(); + const { runner, instance } = await readyToRunStageSetup({ + onTaskEvent, + instance: { + id, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `1s`, + createTaskRunner: () => ({ + async run() { + return { state: {} }; + }, + }), + }, + }, + }); + + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 10, 0, 0).valueOf()); + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith( + withAnyTiming( + asTaskRunEvent( + id, + asOk({ + task: instance, + persistence: TaskPersistence.NonRecurring, + result: TaskRunResult.Success, + isExpired: true, + }) + ) + ) + ); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + }); + test('emits TaskEvent when a recurring task is run successfully', async () => { const id = _.random(1, 20).toString(); const runAt = minutesFromNow(_.random(5)); @@ -1292,12 +1341,58 @@ describe('TaskManagerRunner', () => { task: instance, persistence: TaskPersistence.Recurring, result: TaskRunResult.Success, + isExpired: false, }) ) ) ); }); + test('emits TaskEvent when a recurring task is run successfully but completes after timeout', async () => { + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 0, 0, 0).valueOf()); + const id = _.random(1, 20).toString(); + const runAt = minutesFromNow(_.random(5)); + const onTaskEvent = jest.fn(); + const { runner, instance } = await readyToRunStageSetup({ + onTaskEvent, + instance: { + id, + schedule: { interval: '1m' }, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `1s`, + createTaskRunner: () => ({ + async run() { + return { runAt, state: {} }; + }, + }), + }, + }, + }); + + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 10, 0, 0).valueOf()); + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith( + withAnyTiming( + asTaskRunEvent( + id, + asOk({ + task: instance, + persistence: TaskPersistence.Recurring, + result: TaskRunResult.Success, + isExpired: true, + }) + ) + ) + ); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + }); + test('emits TaskEvent when a recurring task returns a success result with hasError=true', async () => { const id = _.random(1, 20).toString(); const runAt = minutesFromNow(_.random(5)); @@ -1331,6 +1426,50 @@ describe('TaskManagerRunner', () => { persistence: TaskPersistence.Recurring, result: TaskRunResult.Success, error: new Error(`Alerting task failed to run.`), + isExpired: false, + }) + ) + ) + ); + }); + + test('emits TaskEvent when a recurring task returns a success result with hasError=true but completes after timeout', async () => { + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 0, 0, 0).valueOf()); + const id = _.random(1, 20).toString(); + const runAt = minutesFromNow(_.random(5)); + const onTaskEvent = jest.fn(); + const { runner, instance } = await readyToRunStageSetup({ + onTaskEvent, + instance: { + id, + schedule: { interval: '1m' }, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `1s`, + createTaskRunner: () => ({ + async run() { + return { runAt, state: {}, hasError: true }; + }, + }), + }, + }, + }); + + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 10, 0, 0).valueOf()); + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith( + withAnyTiming( + asTaskRunEvent( + id, + asErr({ + task: instance, + persistence: TaskPersistence.Recurring, + result: TaskRunResult.Success, + isExpired: true, + error: new Error(`Alerting task failed to run.`), }) ) ) @@ -1368,11 +1507,57 @@ describe('TaskManagerRunner', () => { task: instance, persistence: TaskPersistence.NonRecurring, result: TaskRunResult.RetryScheduled, + isExpired: false, }) ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledTimes(2); + }); + + test('emits TaskEvent when a task run throws an error and has timed out', async () => { + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 0, 0, 0).valueOf()); + const id = _.random(1, 20).toString(); + const error = new Error('Dangit!'); + const onTaskEvent = jest.fn(); + const { runner, instance } = await readyToRunStageSetup({ + onTaskEvent, + instance: { + id, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `1s`, + createTaskRunner: () => ({ + async run() { + throw error; + }, + }), + }, + }, + }); + fakeTimer = sinon.useFakeTimers(new Date(2023, 1, 1, 0, 10, 0, 0).valueOf()); + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith( + withAnyTiming( + asTaskRunEvent( + id, + asErr({ + error, + task: instance, + persistence: TaskPersistence.NonRecurring, + result: TaskRunResult.Failed, + isExpired: true, + }) + ) + ) + ); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); test('emits TaskEvent when a task run returns an error', async () => { @@ -1409,11 +1594,15 @@ describe('TaskManagerRunner', () => { task: instance, persistence: TaskPersistence.Recurring, result: TaskRunResult.RetryScheduled, + isExpired: false, }) ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); test('emits TaskEvent when a task returns an error and is marked as failed', async () => { @@ -1461,11 +1650,15 @@ describe('TaskManagerRunner', () => { task: originalInstance, persistence: TaskPersistence.NonRecurring, result: TaskRunResult.Failed, + isExpired: false, }) ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); }); diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index e8ec5cb0f2d91..63f520ade1031 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -33,11 +33,13 @@ import { import { asTaskMarkRunningEvent, asTaskRunEvent, + asTaskManagerStatEvent, startTaskTimerWithEventLoopMonitoring, TaskMarkRunning, TaskPersistence, TaskRun, TaskTiming, + TaskManagerStat, } from '../task_events'; import { intervalFromDate, maxIntervalFromDate } from '../lib/intervals'; import { @@ -101,7 +103,7 @@ type Opts = { definitions: TaskTypeDictionary; instance: ConcreteTaskInstance; store: Updatable; - onTaskEvent?: (event: TaskRun | TaskMarkRunning) => void; + onTaskEvent?: (event: TaskRun | TaskMarkRunning | TaskManagerStat) => void; defaultMaxAttempts: number; executionContext: ExecutionContextStart; usageCounter?: UsageCounter; @@ -149,7 +151,7 @@ export class TaskManagerRunner implements TaskRunner { private bufferedTaskStore: Updatable; private beforeRun: Middleware['beforeRun']; private beforeMarkRunning: Middleware['beforeMarkRunning']; - private onTaskEvent: (event: TaskRun | TaskMarkRunning) => void; + private onTaskEvent: (event: TaskRun | TaskMarkRunning | TaskManagerStat) => void; private defaultMaxAttempts: number; private uuid: string; private readonly executionContext: ExecutionContextStart; @@ -310,6 +312,13 @@ export class TaskManagerRunner implements TaskRunner { const stopTaskTimer = startTaskTimerWithEventLoopMonitoring(this.eventLoopDelayConfig); + this.onTaskEvent( + asTaskManagerStatEvent( + 'runDelay', + asOk(getTaskDelayInSeconds(this.instance.task.scheduledAt)) + ) + ); + try { this.task = this.definition.createTaskRunner(modifiedContext); @@ -447,7 +456,7 @@ export class TaskManagerRunner implements TaskRunner { TASK_MANAGER_TRANSACTION_TYPE_MARK_AS_RUNNING, TASK_MANAGER_TRANSACTION_TYPE ); - apmTrans?.addLabels({ entityId: this.taskType }); + apmTrans.addLabels({ entityId: this.taskType }); const now = new Date(); try { @@ -734,6 +743,8 @@ export class TaskManagerRunner implements TaskRunner { ): Promise> { const { task } = this.instance; + const taskHasExpired = this.isExpired; + await eitherAsync( result, async ({ runAt, schedule, hasError }: SuccessfulRunResult) => { @@ -754,11 +765,16 @@ export class TaskManagerRunner implements TaskRunner { this.id, asErr({ ...processedResult, + isExpired: taskHasExpired, error: new Error(`Alerting task failed to run.`), }), taskTiming ) - : asTaskRunEvent(this.id, asOk(processedResult), taskTiming); + : asTaskRunEvent( + this.id, + asOk({ ...processedResult, isExpired: taskHasExpired }), + taskTiming + ); this.onTaskEvent(taskRunEvent); }, async ({ error }: FailedRunResult) => { @@ -769,6 +785,7 @@ export class TaskManagerRunner implements TaskRunner { task, persistence: task.schedule ? TaskPersistence.Recurring : TaskPersistence.NonRecurring, result: await this.processResultForRecurringTask(result), + isExpired: this.isExpired, error, }), taskTiming @@ -885,3 +902,8 @@ export function calculateDelay(attempts: number) { return defaultBackoffPerFailure * Math.pow(2, attempts - 2); } } + +export function getTaskDelayInSeconds(scheduledAt: Date) { + const now = new Date(); + return (now.valueOf() - scheduledAt.valueOf()) / 1000; +} diff --git a/x-pack/plugins/task_manager/server/task_scheduling.test.ts b/x-pack/plugins/task_manager/server/task_scheduling.test.ts index 1a87bd59ec036..9f822b9cd2d76 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.test.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.test.ts @@ -700,6 +700,7 @@ describe('TaskScheduling', () => { }, result: TaskRunResult.Success, persistence: TaskPersistence.Ephemeral, + isExpired: false, }) ) ); @@ -743,6 +744,7 @@ describe('TaskScheduling', () => { }, result: TaskRunResult.Failed, persistence: TaskPersistence.Ephemeral, + isExpired: false, }) ) ); 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 8e517ada4ddca..fde387ee53caf 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4796,7 +4796,7 @@ "total": { "type": "long", "_meta": { - "description": "Total number of shards for span and trasnaction indices" + "description": "Total number of shards for span and transaction indices" } } } @@ -4810,7 +4810,7 @@ "count": { "type": "long", "_meta": { - "description": "Total number of transaction and span documents overall" + "description": "Total number of metric documents overall" } } } @@ -4820,7 +4820,7 @@ "size_in_bytes": { "type": "long", "_meta": { - "description": "Size of the index in byte units overall." + "description": "Size of the metric indicess in byte units overall." } } } @@ -5067,6 +5067,22 @@ } } }, + "top_traces": { + "properties": { + "max": { + "type": "long", + "_meta": { + "description": "Max number of documents in top 100 traces withing the last day" + } + }, + "median": { + "type": "long", + "_meta": { + "description": "Median number of documents in top 100 traces within the last day" + } + } + } + }, "tasks": { "properties": { "aggregated_transactions": { @@ -5278,6 +5294,20 @@ } } } + }, + "top_traces": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long", + "_meta": { + "description": "Execution time in milliseconds for the \"top_traces\" task" + } + } + } + } + } } } } @@ -6149,6 +6179,18 @@ }, "isElasticStaffOwned": { "type": "boolean" + }, + "deploymentId": { + "type": "keyword", + "_meta": { + "description": "The ESS Deployment ID" + } + }, + "projectId": { + "type": "keyword", + "_meta": { + "description": "The Serverless Project ID" + } } } }, diff --git a/x-pack/plugins/threat_intelligence/cypress/cypress.config.ts b/x-pack/plugins/threat_intelligence/cypress/cypress.config.ts index fd438bf8c4d2c..611e00f08686e 100644 --- a/x-pack/plugins/threat_intelligence/cypress/cypress.config.ts +++ b/x-pack/plugins/threat_intelligence/cypress/cypress.config.ts @@ -38,6 +38,8 @@ export default defineCypressConfig({ viewportHeight: 946, viewportWidth: 1680, env: { + grepFilterSpecs: true, + grepTags: '@ess', protocol: 'http', hostname: 'localhost', configport: '5601', @@ -45,6 +47,10 @@ export default defineCypressConfig({ e2e: { baseUrl: 'http://localhost:5601', experimentalMemoryManagement: true, - specPattern: './cypress/e2e/**/*.cy.ts', + setupNodeEvents(on, config) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('@cypress/grep/src/plugin')(config); + return config; + }, }, }); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/block_list.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/block_list.cy.ts index ffe13397e0ebb..63f58acf99484 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/block_list.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/block_list.cy.ts @@ -35,7 +35,7 @@ const FIRST_BLOCK_LIST_NEW_DESCRIPTION = 'the first description'; const SECOND_BLOCK_LIST_NEW_NAME = 'second blocklist entry'; const SECOND_BLOCK_LIST_NEW_DESCRIPTION = 'the second description'; -describe('Block list with invalid indicators', () => { +describe('Block list with invalid indicators', { tags: '@ess' }, () => { beforeEach(() => { esArchiverLoad('threat_intelligence/invalid_indicators_data'); login(); @@ -56,7 +56,7 @@ describe('Block list with invalid indicators', () => { }); }); -describe('Block list interactions', () => { +describe('Block list interactions', { tags: '@ess' }, () => { beforeEach(() => { esArchiverLoad('threat_intelligence/indicators_data'); login(); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/cases.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/cases.cy.ts index 39dffd2da0a32..18caadc01319d 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/cases.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/cases.cy.ts @@ -32,7 +32,7 @@ import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; const THREAT_INTELLIGENCE = '/app/security/threat_intelligence/indicators'; -describe('Cases with invalid indicators', () => { +describe('Cases with invalid indicators', { tags: '@ess' }, () => { beforeEach(() => { esArchiverLoad('threat_intelligence/invalid_indicators_data'); login(); @@ -58,7 +58,7 @@ describe('Cases with invalid indicators', () => { }); }); -describe('Cases interactions', () => { +describe('Cases interactions', { tags: '@ess' }, () => { beforeEach(() => { esArchiverLoad('threat_intelligence/indicators_data'); login(); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/empty_page.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/empty_page.cy.ts index ddb688ff6b7c1..b7c6083480c38 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/empty_page.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/empty_page.cy.ts @@ -14,7 +14,7 @@ import { const THREAT_INTEL_PATH = '/app/security/threat_intelligence/'; -describe('Empty Page', () => { +describe('Empty Page', { tags: '@ess' }, () => { beforeEach(() => { login(); visit(THREAT_INTEL_PATH); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts index 3b7e360813c0e..cbb95e9a610f1 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts @@ -56,7 +56,7 @@ const THREAT_INTELLIGENCE = '/app/security/threat_intelligence/indicators'; const URL_WITH_CONTRADICTORY_FILTERS = '/app/security/threat_intelligence/indicators?indicators=(filterQuery:(language:kuery,query:%27%27),filters:!((%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,index:%27%27,key:threat.indicator.type,negate:!f,params:(query:file),type:phrase),query:(match_phrase:(threat.indicator.type:file))),(%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,index:%27%27,key:threat.indicator.type,negate:!f,params:(query:url),type:phrase),query:(match_phrase:(threat.indicator.type:url)))),timeRange:(from:now/d,to:now/d))'; -describe('Invalid Indicators', () => { +describe('Invalid Indicators', { tags: '@ess' }, () => { describe('verify the grid loads even with missing fields', () => { beforeEach(() => { esArchiverLoad('threat_intelligence/invalid_indicators_data'); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts index 379147b900a47..9ee37105d1bc0 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts @@ -31,7 +31,7 @@ import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; const THREAT_INTELLIGENCE = '/app/security/threat_intelligence/indicators'; -describe('Indicators query bar interaction', () => { +describe('Indicators query bar interaction', { tags: '@ess' }, () => { beforeEach(() => { esArchiverLoad('threat_intelligence/indicators_data'); login(); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts index 7c2fcdc2b98b2..51f87abcba5bd 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts @@ -26,7 +26,7 @@ import { login, visit } from '../tasks/login'; const THREAT_INTELLIGENCE = '/app/security/threat_intelligence/indicators'; -describe('Timeline', () => { +describe('Timeline', { tags: '@ess' }, () => { beforeEach(() => { esArchiverLoad('threat_intelligence/indicators_data'); login(); diff --git a/x-pack/plugins/threat_intelligence/package.json b/x-pack/plugins/threat_intelligence/package.json index a887c13c3ba4b..1e934863b52f2 100644 --- a/x-pack/plugins/threat_intelligence/package.json +++ b/x-pack/plugins/threat_intelligence/package.json @@ -5,11 +5,11 @@ "license": "Elastic License 2.0", "scripts": { "cypress": "../../../node_modules/.bin/cypress", - "cypress:open": "TZ=UTC node ../security_solution/scripts/start_cypress_parallel open --spec './cypress/e2e/**/*.cy.ts' --config-file ../../plugins/threat_intelligence/cypress/cypress.config.ts --ftr-config-file ../../test/threat_intelligence_cypress/cli_config_parallel", + "cypress:open": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel open --spec './cypress/e2e/**/*.cy.ts' --config-file ../../plugins/threat_intelligence/cypress/cypress.config.ts --ftr-config-file ../../test/threat_intelligence_cypress/cli_config_parallel", "cypress:run": "yarn cypress:run:reporter --browser chrome --spec './cypress/e2e/**/*.cy.ts'; status=$?; yarn junit:merge && exit $status", "cypress:run:spec": "yarn cypress:run:reporter --browser chrome --spec ${SPEC_LIST:-'./cypress/e2e/**/*.cy.ts'}; status=$?; yarn junit:merge && exit $status", "cypress:run:cases": "yarn cypress:run:reporter --browser chrome --spec './cypress/e2e/cases/*.cy.ts' --ftr-config-file ../../test/security_solution_cypress/cli_config; status=$?; yarn junit:merge && exit $status", - "cypress:run:reporter": "TZ=UTC node ../security_solution/scripts/start_cypress_parallel run --config-file ../../plugins/threat_intelligence/cypress/cypress.config.ts --ftr-config-file ../../test/threat_intelligence_cypress/cli_config_parallel --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json", + "cypress:run:reporter": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel run --config-file ../../plugins/threat_intelligence/cypress/cypress.config.ts --ftr-config-file ../../test/threat_intelligence_cypress/cli_config_parallel --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json", "cypress:run:respops": "yarn cypress:run:reporter --browser chrome --spec ./cypress/e2e/detection_alerts/*.cy.ts,./cypress/e2e/detection_rules/*.cy.ts,./cypress/e2e/exceptions/*.cy.ts --ftr-config-file ../../x-pack/test/security_solution_cypress/cli_config; status=$?; yarn junit:merge && exit $status", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-threat-intelligence/cypress/results/mochawesome*.json > ../../../target/kibana-threat-intelligence/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-threat-intelligence/cypress/results/output.json --reportDir ../../../target/kibana-threat-intelligence/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-threat-intelligence/cypress/results/*.xml ../../../target/junit/" } diff --git a/x-pack/plugins/threat_intelligence/public/components/paywall.stories.tsx b/x-pack/plugins/threat_intelligence/public/components/paywall.stories.tsx index 14de7d5b907fb..d607eb1a91d44 100644 --- a/x-pack/plugins/threat_intelligence/public/components/paywall.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/components/paywall.stories.tsx @@ -14,5 +14,5 @@ export default { }; export function BasicPaywall() { - return ; + return ; } diff --git a/x-pack/plugins/threat_intelligence/public/components/paywall.tsx b/x-pack/plugins/threat_intelligence/public/components/paywall.tsx index 8f095a2fd9baa..a01ca32a06946 100644 --- a/x-pack/plugins/threat_intelligence/public/components/paywall.tsx +++ b/x-pack/plugins/threat_intelligence/public/components/paywall.tsx @@ -6,24 +6,17 @@ */ import React, { VFC } from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, -} from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { SubscriptionButtonEmpty } from '@kbn/subscription-tracking'; +import type { SubscriptionContextData } from '@kbn/subscription-tracking'; -interface PaywallProps { - /** - * Can be obtained using `http.basePath.prepend('/app/management/stack/license_management')` - */ - licenseManagementHref: string; -} +const subscriptionContext: SubscriptionContextData = { + feature: 'threat-intelligence', + source: 'security__threat-intelligence', +}; -export const Paywall: VFC = ({ licenseManagementHref }) => { +export const Paywall: VFC = () => { return ( } @@ -59,12 +52,12 @@ export const Paywall: VFC = ({ licenseManagementHref }) => {
    - + - +
    diff --git a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx index 1378e412b9255..98c79339ad78d 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx @@ -6,16 +6,13 @@ */ import React, { FC } from 'react'; + import { Paywall } from '../components/paywall'; -import { useKibana } from '../hooks/use_kibana'; import { useSecurityContext } from '../hooks/use_security_context'; import { SecuritySolutionPluginTemplateWrapper } from './security_solution_plugin_template_wrapper'; export const EnterpriseGuard: FC = ({ children }) => { const { licenseService } = useSecurityContext(); - const { - services: { http }, - } = useKibana(); if (licenseService.isEnterprise()) { return <>{children}; @@ -23,9 +20,7 @@ export const EnterpriseGuard: FC = ({ children }) => { return ( - + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx b/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx index 249a9d05afbc9..d13c3f561e748 100644 --- a/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx @@ -12,6 +12,7 @@ import { CoreStart, IUiSettingsClient } from '@kbn/core/public'; import { TimelinesUIStart } from '@kbn/timelines-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; @@ -107,7 +108,9 @@ export const StoryProvidersComponent: VFC = ({ - {children} + + {children} + diff --git a/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx index 37360284b6aa7..12c42052ee26f 100644 --- a/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx @@ -17,6 +17,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import { createTGridMocks } from '@kbn/timelines-plugin/public/mock'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; @@ -141,11 +142,13 @@ export const TestProvidersComponent: FC = ({ children }) => ( - - - {children} - - + + + + {children} + + + diff --git a/x-pack/plugins/threat_intelligence/public/plugin.tsx b/x-pack/plugins/threat_intelligence/public/plugin.tsx index 49f6b3b7724bf..e30e9f92c0a5b 100755 --- a/x-pack/plugins/threat_intelligence/public/plugin.tsx +++ b/x-pack/plugins/threat_intelligence/public/plugin.tsx @@ -11,6 +11,7 @@ import { Provider as ReduxStoreProvider } from 'react-redux'; import React, { Suspense } from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { ExternalReferenceAttachmentType } from '@kbn/cases-plugin/public/client/attachment_framework/types'; +import { SubscriptionTrackingProvider } from '@kbn/subscription-tracking'; import { generateAttachmentType } from './modules/cases/utils/attachments'; import { KibanaContextProvider } from './hooks/use_kibana'; import { @@ -43,11 +44,16 @@ export const createApp = - - }> - - - + + + }> + + + + diff --git a/x-pack/plugins/threat_intelligence/tsconfig.json b/x-pack/plugins/threat_intelligence/tsconfig.json index 5576b8e2ea926..661186c943b54 100644 --- a/x-pack/plugins/threat_intelligence/tsconfig.json +++ b/x-pack/plugins/threat_intelligence/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.base.json", "compilerOptions": { - "outDir": "target/types", + "outDir": "target/types" }, "include": [ "common/**/*", @@ -32,9 +32,8 @@ "@kbn/utility-types", "@kbn/ui-theme", "@kbn/securitysolution-io-ts-list-types", - "@kbn/core-ui-settings-browser" + "@kbn/core-ui-settings-browser", + "@kbn/subscription-tracking" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/timelines/common/api/search_strategy/index.ts b/x-pack/plugins/timelines/common/api/search_strategy/index.ts new file mode 100644 index 0000000000000..4ff442c13f62a --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export * from './index_fields'; + +import * as timelineSchemas from './timeline/timeline'; + +export * from './timeline/timeline'; + +export * from './model/timeline_events_queries'; + +export * from './model/runtime_mappings'; + +export const searchStrategyRequestSchema = z.discriminatedUnion('factoryQueryType', [ + timelineSchemas.timelineEventsAllSchema, + timelineSchemas.timelineEventsDetailsSchema, + timelineSchemas.timelineEventsLastEventTimeRequestSchema, + timelineSchemas.timelineKpiRequestOptionsSchema, +]); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/index_fields.ts b/x-pack/plugins/timelines/common/api/search_strategy/index_fields.ts new file mode 100644 index 0000000000000..6d4ada5213fda --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/index_fields.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +const indexFieldsRequestBase = z.object({ + onlyCheckIfIndicesExist: z.boolean().optional(), + includeUnmapped: z.boolean().optional(), +}); + +export const indexFieldsRequestSchema = z.union([ + indexFieldsRequestBase.extend({ + indices: z.array(z.string()), + }), + indexFieldsRequestBase.extend({ dataViewId: z.string() }), +]); + +export type IndexFieldsRequestInput = z.input; + +export type IndexFieldsRequest = z.infer; diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/filter_query.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/filter_query.ts new file mode 100644 index 0000000000000..89c6e24dad231 --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/filter_query.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +const esMatchQuerySchema = z.object({ + match: z.record( + z.string(), + z.object({ + query: z.string(), + operator: z.string(), + zero_terms_query: z.string(), + }) + ), +}); + +export type ESMatchQuery = z.infer; + +const esQueryStringQuerySchema = z.object({ + query_string: z.object({ + query: z.string(), + analyze_wildcard: z.boolean(), + }), +}); + +export type ESQueryStringQuery = z.infer; + +const esTermQuerySchema = z.object({ + term: z.record(z.string(), z.string()), +}); + +export type ESTermQuery = z.infer; + +const esBoolQuerySchema = z.object({ + bool: z.object({ + filter: z.array(z.object({})), + must: z.array(z.object({})), + must_not: z.array(z.object({})), + should: z.array(z.object({})), + }), +}); + +export type ESBoolQuery = z.infer; + +const esRangeQuerySchema = z.object({ + range: z.record( + z.string(), + z.object({ + gte: z.number(), + lte: z.number(), + format: z.string(), + }) + ), +}); + +export type ESRangeQuery = z.infer; + +const jsonObjectSchema = z.record(z.string(), z.any()); + +export type JsonObject = z.infer; + +export type ESQuery = + | ESRangeQuery + | ESQueryStringQuery + | ESMatchQuery + | ESTermQuery + | ESBoolQuery + | JsonObject; + +const esQuerySchema = z.union([ + esRangeQuerySchema, + esQueryStringQuerySchema, + esMatchQuerySchema, + esTermQuerySchema, + esBoolQuerySchema, + jsonObjectSchema, +]); + +export const filterQuery = z.union([z.string(), z.undefined(), esQuerySchema]); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/language.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/language.ts new file mode 100644 index 0000000000000..0301e2aa0581f --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/language.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export const language = z.union([z.literal('kuery'), z.literal('lucene')]); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/order.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/order.ts new file mode 100644 index 0000000000000..226234a211e82 --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/order.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { Direction } from '../../../search_strategy'; + +export const order = z.enum([Direction.asc, Direction.desc]); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/pagination.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/pagination.ts new file mode 100644 index 0000000000000..b6472a608627e --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/pagination.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export type PaginationInputPaginatedInput = z.input; + +export const pagination = z + .object({ + /** The activePage parameter defines the page of results you want to fetch */ + activePage: z.number(), + /** The cursorStart parameter defines the start of the results to be displayed */ + cursorStart: z.number().optional(), + /** The querySize parameter is the number of items to be returned */ + querySize: z.number(), + }) + .passthrough() + .optional(); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/runtime_mappings.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/runtime_mappings.ts new file mode 100644 index 0000000000000..b3f16c1ed1236 --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/runtime_mappings.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export type MappingRuntimeFieldType = + | 'boolean' + | 'date' + | 'double' + | 'geo_point' + | 'ip' + | 'keyword' + | 'long' + | 'lookup'; + +export const runtimeMappings = z + .record( + z.object({ + type: z.union([ + z.literal('boolean'), + z.literal('date'), + z.literal('double'), + z.literal('geo_point'), + z.literal('ip'), + z.literal('keyword'), + z.literal('long'), + z.literal('lookup'), + ]), + script: z + .union([ + z.string(), + z.object({ source: z.string() }), + z.object({ id: z.string(), params: z.record(z.any()) }), + ]) + .optional(), + fetch_fields: z.array(z.string()).optional(), + format: z.string().optional(), + input_field: z.string().optional(), + target_field: z.string().optional(), + target_index: z.string().optional(), + }) + ) + .optional(); + +export type RunTimeMappings = z.infer; diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/sort.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/sort.ts new file mode 100644 index 0000000000000..5a0d5ab9474ec --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/sort.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { order } from './order'; + +export const sortItem = z.object({ + direction: order, + field: z.string(), + esTypes: z.array(z.string()).optional(), + type: z.string().optional(), +}); + +export const sort = z.array(sortItem); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/timeline_events_queries.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/timeline_events_queries.ts new file mode 100644 index 0000000000000..96bd4a1090d3d --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/timeline_events_queries.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum TimelineEventsQueries { + all = 'eventsAll', + details = 'eventsDetails', + kpi = 'eventsKpi', + lastEventTime = 'eventsLastEventTime', +} diff --git a/x-pack/plugins/timelines/common/api/search_strategy/model/timerange.ts b/x-pack/plugins/timelines/common/api/search_strategy/model/timerange.ts new file mode 100644 index 0000000000000..f04ad37a82839 --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/model/timerange.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +export const timerange = z.object({ + interval: z.string(), + from: z.string(), + to: z.string(), +}); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/eql.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/eql.ts new file mode 100644 index 0000000000000..c07aa3bf8c6cd --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/eql.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { filterQuery } from '../model/filter_query'; +import { runtimeMappings } from '../model/runtime_mappings'; +import { sort } from '../model/sort'; +import { requestPaginated } from './request_paginated'; + +export const timelineEqlRequestOptionsSchema = requestPaginated.extend({ + sort, + filterQuery, + eventCategoryField: z.string().optional(), + tiebreakerField: z.string().optional(), + timestampField: z.string().optional(), + fieldRequested: z.array(z.string()), + size: z.number().optional(), + runTimeMappings: runtimeMappings.optional(), + language: z.literal('eql'), +}); + +export type TimelineEqlRequestOptionsInput = z.input; + +export type TimelineEqlRequestOptions = z.infer; diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_all.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_all.ts new file mode 100644 index 0000000000000..84c046c348ab4 --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_all.ts @@ -0,0 +1,45 @@ +/* + * Copyright 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 { z } from 'zod'; +import { language } from '../model/language'; +import { runtimeMappings } from '../model/runtime_mappings'; +import { sortItem } from '../model/sort'; +import { TimelineEventsQueries } from '../model/timeline_events_queries'; +import { requestPaginated } from './request_paginated'; + +const extendedSortItem = sortItem.extend({ + esTypes: z.array(z.string()), +}); + +const sort = z.array(extendedSortItem); + +export const timelineEventsAllSchema = requestPaginated.extend({ + authFilter: z.object({}).optional(), + excludeEcsData: z.boolean().optional(), + fieldRequested: z.array(z.string()), + sort, + filterQuery: z.any(), + fields: z.array( + z.union([ + z.string(), + z.object({ + field: z.string(), + include_unmapped: z.boolean(), + }), + ]) + ), + runtimeMappings, + language, + factoryQueryType: z.literal(TimelineEventsQueries.all), +}); + +export type TimelineEventsAllOptionsInput = z.input; + +export type TimelineEventsAllOptions = z.infer; + +export type SortItem = z.infer; diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_details.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_details.ts new file mode 100644 index 0000000000000..42444cf2d30e4 --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_details.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { runtimeMappings } from '../model/runtime_mappings'; +import { TimelineEventsQueries } from '../model/timeline_events_queries'; +import { requestPaginated } from './request_paginated'; + +export const timelineEventsDetailsSchema = requestPaginated.partial().extend({ + indexName: z.string(), + eventId: z.string(), + authFilter: z.object({}).optional(), + runtimeMappings, + factoryQueryType: z.literal(TimelineEventsQueries.details), +}); + +export type TimelineEventsDetailsRequestOptionsInput = z.input; + +export type TimelineEventsDetailsRequestOptions = z.infer; diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_last_event_time.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_last_event_time.ts new file mode 100644 index 0000000000000..6599c8e3fd91f --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/events_last_event_time.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { TimelineEventsQueries } from '../model/timeline_events_queries'; +import { timelineRequestBasicOptionsSchema } from './request_basic'; + +export enum LastEventIndexKey { + hostDetails = 'hostDetails', + hosts = 'hosts', + users = 'users', + userDetails = 'userDetails', + ipDetails = 'ipDetails', + network = 'network', +} + +export const timelineEventsLastEventTimeRequestSchema = timelineRequestBasicOptionsSchema + .omit({ + runtimeMappings: true, + filterQuery: true, + timerange: true, + }) + .extend({ + indexKey: z.enum([ + LastEventIndexKey.hostDetails, + LastEventIndexKey.hosts, + LastEventIndexKey.users, + LastEventIndexKey.userDetails, + LastEventIndexKey.ipDetails, + LastEventIndexKey.network, + ]), + details: z.object({ + hostName: z.string().nullable().optional(), + userName: z.string().nullable().optional(), + ip: z.string().nullable().optional(), + }), + factoryQueryType: z.literal(TimelineEventsQueries.lastEventTime), + }); + +export type TimelineEventsLastEventTimeRequestOptionsInput = z.input< + typeof timelineEventsLastEventTimeRequestSchema +>; + +export type TimelineEventsLastEventTimeRequestOptions = z.infer< + typeof timelineEventsLastEventTimeRequestSchema +>; diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/kpi.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/kpi.ts new file mode 100644 index 0000000000000..a0776ae6c8feb --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/kpi.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { TimelineEventsQueries } from '../model/timeline_events_queries'; +import { timelineRequestBasicOptionsSchema } from './request_basic'; + +export const timelineKpiRequestOptionsSchema = timelineRequestBasicOptionsSchema.extend({ + factoryQueryType: z.literal(TimelineEventsQueries.kpi), +}); + +export type TimelineKpiRequestOptionsInput = z.input; + +export type TimelineKpiRequestOptions = z.infer; diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/request_basic.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/request_basic.ts new file mode 100644 index 0000000000000..5e8ea1caaa0fb --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/request_basic.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { filterQuery } from '../model/filter_query'; +import { runtimeMappings } from '../model/runtime_mappings'; +import { timerange } from '../model/timerange'; + +export const timelineRequestBasicOptionsSchema = z.object({ + indexType: z.string().optional(), + timerange: timerange.optional(), + filterQuery, + defaultIndex: z.array(z.string()).optional(), + entityType: z.enum(['events', 'sessions']).optional(), + runtimeMappings, + params: z.any().optional(), + filterStatus: z + .union([z.literal('open'), z.literal('closed'), z.literal('acknowledged')]) + .optional(), +}); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/request_paginated.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/request_paginated.ts new file mode 100644 index 0000000000000..e9565d3233f99 --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/request_paginated.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pagination } from '../model/pagination'; +import { timelineRequestBasicOptionsSchema } from './request_basic'; + +export const requestPaginated = timelineRequestBasicOptionsSchema.extend({ + pagination, +}); diff --git a/x-pack/plugins/timelines/common/api/search_strategy/timeline/timeline.ts b/x-pack/plugins/timelines/common/api/search_strategy/timeline/timeline.ts new file mode 100644 index 0000000000000..dcb42e8f8fadd --- /dev/null +++ b/x-pack/plugins/timelines/common/api/search_strategy/timeline/timeline.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './events_all'; + +export * from './eql'; + +export * from './events_details'; + +export * from './events_last_event_time'; + +export * from './kpi'; diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 4d2c772651b64..0b8169996473d 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -5,6 +5,16 @@ * 2.0. */ +export { + LastEventIndexKey, + type TimelineEventsAllOptionsInput, + type TimelineEventsDetailsRequestOptionsInput, + type TimelineEventsLastEventTimeRequestOptionsInput, + type TimelineKpiRequestOptionsInput, + type TimelineEqlRequestOptionsInput, + TimelineEventsQueries, +} from './api/search_strategy'; + // Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase. // If you're using functions/types/etc... internally or within integration tests it's best to import directly from their paths // than expose the functions/types/etc... here. You should _only_ expose functions/types/etc... that need to be shared with other plugins here. @@ -48,24 +58,13 @@ export type { TimelineEdges, TimelineItem, TimelineEventsAllStrategyResponse, - TimelineEventsAllRequestOptions, TimelineEventsDetailsItem, TimelineEventsDetailsStrategyResponse, - TimelineEventsDetailsRequestOptions, TimelineEventsLastEventTimeStrategyResponse, - TimelineEventsLastEventTimeRequestOptions, - TimelineEqlRequestOptions, TimelineEqlResponse, - TimelineKpiStrategyRequest, TimelineKpiStrategyResponse, TotalValue, PaginationInputPaginated, } from './search_strategy'; -export { - Direction, - EntityType, - LastEventIndexKey, - EMPTY_BROWSER_FIELDS, - EMPTY_INDEX_FIELDS, -} from './search_strategy'; +export { Direction, EntityType, EMPTY_BROWSER_FIELDS, EMPTY_INDEX_FIELDS } from './search_strategy'; diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts index ce2094334889f..ade162b367faf 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts @@ -5,13 +5,9 @@ * 2.0. */ -import { JsonObject } from '@kbn/utility-types'; - import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { CursorType, Inspect, Maybe, PaginationInputPaginated } from '../../../common'; -import type { TimelineRequestOptionsPaginated } from '../..'; -import type { RunTimeMappings } from '../eql'; export interface TimelineEdges { node: TimelineItem; @@ -37,14 +33,3 @@ export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse { pageInfo: Pick; inspect?: Maybe; } - -type AlertWorkflowStatus = 'open' | 'closed' | 'acknowledged'; -export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsPaginated { - authFilter?: JsonObject; - excludeEcsData?: boolean; - fieldRequested: string[]; - fields: string[] | Array<{ field: string; include_unmapped: boolean }>; - language: 'eql' | 'kuery' | 'lucene'; - runtimeMappings: RunTimeMappings; - filterStatus?: AlertWorkflowStatus; -} diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts index 036a7ddb8462f..ae95943139b9b 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts @@ -5,12 +5,9 @@ * 2.0. */ -import { JsonObject } from '@kbn/utility-types'; - import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { Inspect, Maybe } from '../../../common'; -import { TimelineRequestOptionsPaginated } from '../..'; export interface TimelineEventsDetailsItem { ariaRowindex?: Maybe; @@ -28,10 +25,3 @@ export interface TimelineEventsDetailsStrategyResponse extends IEsSearchResponse inspect?: Maybe; rawEventData?: Maybe; } - -export interface TimelineEventsDetailsRequestOptions - extends Partial { - indexName: string; - eventId: string; - authFilter?: JsonObject; -} diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts index 4c9419ccf802a..66758bbcb94d7 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts @@ -6,30 +6,14 @@ */ import { EuiComboBoxOptionOption } from '@elastic/eui'; -import type { - EqlSearchStrategyRequest, - EqlSearchStrategyResponse, - EqlRequestParams, -} from '@kbn/data-plugin/common'; +import type { EqlSearchStrategyResponse } from '@kbn/data-plugin/common'; import type { RuntimeFieldSpec, RuntimePrimitiveTypes } from '@kbn/data-views-plugin/common'; import { EqlSearchResponse, Inspect, Maybe, PaginationInputPaginated } from '../../..'; -import { TimelineEdges, TimelineEventsAllRequestOptions } from '../..'; - -type EqlBody = Pick; +import { TimelineEdges } from '../..'; export type RunTimeMappings = | Record & { type: RuntimePrimitiveTypes }> | undefined; -export interface TimelineEqlRequestOptions - extends EqlSearchStrategyRequest, - Omit { - eventCategoryField?: string; - tiebreakerField?: string; - timestampField?: string; - size?: number; - runtime_mappings?: RunTimeMappings; - body?: Omit & EqlBody & { runtime_mappings?: RunTimeMappings }; -} export interface TimelineEqlResponse extends EqlSearchStrategyResponse> { edges: TimelineEdges[]; diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts index ef0a5d1af265e..96b74e4e42435 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts @@ -10,13 +10,6 @@ export * from './details'; export * from './last_event_time'; export * from './eql'; -export enum TimelineEventsQueries { - all = 'eventsAll', - details = 'eventsDetails', - kpi = 'eventsKpi', - lastEventTime = 'eventsLastEventTime', -} - export const EntityType = { EVENTS: 'events', SESSIONS: 'sessions', diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts index a78ba38f4b55e..13e91cd0bca05 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts @@ -7,16 +7,6 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { Inspect, Maybe } from '../../../common'; -import { TimelineRequestBasicOptions } from '../..'; - -export enum LastEventIndexKey { - hostDetails = 'hostDetails', - hosts = 'hosts', - users = 'users', - userDetails = 'userDetails', - ipDetails = 'ipDetails', - network = 'network', -} export interface LastTimeDetails { hostName?: Maybe; @@ -28,8 +18,6 @@ export interface TimelineEventsLastEventTimeStrategyResponse extends IEsSearchRe lastSeen: Maybe; inspect?: Maybe; } -export type TimelineKpiStrategyRequest = Omit; - export interface TimelineKpiStrategyResponse extends IEsSearchResponse { destinationIpCount: number; inspect?: Maybe; @@ -38,9 +26,3 @@ export interface TimelineKpiStrategyResponse extends IEsSearchResponse { sourceIpCount: number; userCount: number; } - -export interface TimelineEventsLastEventTimeRequestOptions - extends Omit { - indexKey: LastEventIndexKey; - details: LastTimeDetails; -} diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts index 93a61626946b2..aaa9696bb827d 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts @@ -5,46 +5,30 @@ * 2.0. */ -import type { IEsSearchRequest } from '@kbn/data-plugin/common'; -import { ESQuery } from '../../typed_json'; import { - TimelineEventsQueries, - TimelineEventsAllRequestOptions, TimelineEventsAllStrategyResponse, - TimelineEventsDetailsRequestOptions, TimelineEventsDetailsStrategyResponse, - TimelineEventsLastEventTimeRequestOptions, TimelineEventsLastEventTimeStrategyResponse, TimelineKpiStrategyResponse, - EntityType, } from './events'; -import { PaginationInputPaginated, TimerangeInput, SortField } from '../common'; -import type { RunTimeMappings } from './events/eql'; +import { SortField } from '../common'; +import { + TimelineEventsAllOptionsInput, + TimelineEventsDetailsRequestOptionsInput, + TimelineEventsLastEventTimeRequestOptionsInput, + TimelineEventsQueries, + TimelineKpiRequestOptionsInput, +} from '../../api/search_strategy'; export * from './events'; export type TimelineFactoryQueryTypes = TimelineEventsQueries; -export interface TimelineRequestBasicOptions extends IEsSearchRequest { - timerange?: TimerangeInput; - filterQuery: ESQuery | string | undefined; - defaultIndex: string[]; - factoryQueryType?: TimelineFactoryQueryTypes; - entityType?: EntityType; - runtimeMappings: RunTimeMappings; -} - export interface TimelineRequestSortField extends SortField { esTypes: string[]; type: string; } -export interface TimelineRequestOptionsPaginated - extends TimelineRequestBasicOptions { - pagination: Pick; - sort: Array>; -} - export type TimelineStrategyResponseType = T extends TimelineEventsQueries.all ? TimelineEventsAllStrategyResponse @@ -58,11 +42,11 @@ export type TimelineStrategyResponseType = export type TimelineStrategyRequestType = T extends TimelineEventsQueries.all - ? TimelineEventsAllRequestOptions + ? TimelineEventsAllOptionsInput : T extends TimelineEventsQueries.details - ? TimelineEventsDetailsRequestOptions + ? TimelineEventsDetailsRequestOptionsInput : T extends TimelineEventsQueries.kpi - ? TimelineRequestBasicOptions + ? TimelineKpiRequestOptionsInput : T extends TimelineEventsQueries.lastEventTime - ? TimelineEventsLastEventTimeRequestOptions + ? TimelineEventsLastEventTimeRequestOptionsInput : never; diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts index d9c9ee2374bf1..6af36c4ad6f73 100644 --- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts @@ -26,6 +26,7 @@ import { IndexFieldsStrategyResponse, } from '../../../common/search_strategy'; import { StartPlugins } from '../../types'; +import { parseOptions } from './parse_options'; const apmIndexPattern = 'apm-*-transaction*'; const apmDataStreamsPattern = 'traces-apm*'; @@ -100,9 +101,11 @@ export const requestIndexFieldSearch = async ( indexPatterns: DataViewsServerPluginStart, useInternalUser?: boolean ): Promise => { + const options = parseOptions(request); + const indexPatternsFetcherAsCurrentUser = new IndexPatternsFetcher(esClient.asCurrentUser); const indexPatternsFetcherAsInternalUser = new IndexPatternsFetcher(esClient.asInternalUser); - if ('dataViewId' in request && 'indices' in request) { + if ('dataViewId' in options && 'indices' in options) { throw new Error('Provide index field search with either `dataViewId` or `indices`, not both'); } @@ -120,10 +123,10 @@ export const requestIndexFieldSearch = async ( let runtimeMappings = {}; // if dataViewId is provided, get fields and indices from the Kibana Data View - if ('dataViewId' in request) { + if ('dataViewId' in options) { let dataView; try { - dataView = await dataViewService.get(request.dataViewId); + dataView = await dataViewService.get(options.dataViewId); } catch (r) { if ( r.output.payload.statusCode === 404 && @@ -148,14 +151,14 @@ export const requestIndexFieldSearch = async ( [] ); - if (!request.onlyCheckIfIndicesExist) { + if (!options.onlyCheckIfIndicesExist) { const dataViewSpec = dataView.toSpec(); const fieldDescriptor = [Object.values(dataViewSpec.fields ?? {})]; runtimeMappings = dataViewSpec.runtimeFieldMap ?? {}; indexFields = await formatIndexFields(beatFields, fieldDescriptor, patternList); } - } else if ('indices' in request) { - const patternList = dedupeIndexName(request.indices); + } else if ('indices' in options) { + const patternList = dedupeIndexName(options.indices); indicesExist = (await findExistingIndices(patternList, esUser)).reduce( (acc: string[], doesIndexExist, i) => { if (doesIndexExist) { @@ -165,11 +168,11 @@ export const requestIndexFieldSearch = async ( }, [] ); - if (!request.onlyCheckIfIndicesExist) { + if (!options.onlyCheckIfIndicesExist) { const fieldDescriptor = ( await Promise.all( indicesExist.map(async (index, n) => { - const fieldCapsOptions = request.includeUnmapped + const fieldCapsOptions = options.includeUnmapped ? { includeUnmapped: true, allow_no_indices: true } : undefined; if (index.startsWith('.alerts-observability') || useInternalUser) { diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/parse_options.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/parse_options.ts new file mode 100644 index 0000000000000..ea47e6de98d2b --- /dev/null +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/parse_options.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { indexFieldsRequestSchema } from '../../../common/api/search_strategy'; + +export const parseOptions = (options: unknown) => indexFieldsRequestSchema.parse(options); diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts index c73d36a15d40c..c0e145aa501f6 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { Direction, TimelineEqlRequestOptions } from '../../../../common/search_strategy'; +import { TimelineEqlRequestOptions } from '../../../../common/api/search_strategy'; +import { Direction } from '../../../../common/search_strategy'; import { buildEqlDsl, parseEqlResponse } from './helpers'; import { eventsResponse, sequenceResponse } from './__mocks__'; const defaultArgs = { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts index e9a2ef7e49cda..e84bf7bd9bda9 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts @@ -7,6 +7,7 @@ import { isEmpty } from 'lodash/fp'; import type { EqlSearchStrategyResponse } from '@kbn/data-plugin/common'; +import { TimelineEqlRequestOptions } from '../../../../common/api/search_strategy/timeline/eql'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../common/constants'; import { EqlSearchResponse, @@ -14,10 +15,7 @@ import { EventHit, TimelineEdges, } from '../../../../common/search_strategy'; -import { - TimelineEqlRequestOptions, - TimelineEqlResponse, -} from '../../../../common/search_strategy/timeline/events/eql'; +import { TimelineEqlResponse } from '../../../../common/search_strategy/timeline/events/eql'; import { inspectStringifyObject } from '../../../utils/build_query'; import { TIMELINE_EVENTS_FIELDS } from '../factory/helpers/constants'; import { formatTimelineData } from '../factory/helpers/format_timeline_data'; @@ -107,7 +105,12 @@ export const parseEqlResponse = async ( options: TimelineEqlRequestOptions, response: EqlSearchStrategyResponse> ): Promise => { - const { activePage, querySize } = options.pagination; + const { + pagination: { activePage, querySize } = { + activePage: 0, + querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, + }, + } = options; let edges: TimelineEdges[] = []; if (response.rawResponse.body.hits.sequences !== undefined) { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts index 0b59a213e848d..2f52dd9e224f2 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts @@ -8,12 +8,11 @@ import { map, mergeMap } from 'rxjs/operators'; import { ISearchStrategy, PluginStart, shimHitsTotal } from '@kbn/data-plugin/server'; import { EqlSearchStrategyResponse, EQL_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; +import { TimelineEqlRequestOptions } from '../../../../common/api/search_strategy'; import { EqlSearchResponse } from '../../../../common/search_strategy'; -import { - TimelineEqlRequestOptions, - TimelineEqlResponse, -} from '../../../../common/search_strategy/timeline/events/eql'; +import { TimelineEqlResponse } from '../../../../common/search_strategy/timeline/events/eql'; import { buildEqlDsl, parseEqlResponse } from './helpers'; +import { parseOptions } from './parse_options'; export const timelineEqlSearchStrategyProvider = ( data: PluginStart @@ -21,7 +20,9 @@ export const timelineEqlSearchStrategyProvider = ( const esEql = data.search.getSearchStrategy(EQL_SEARCH_STRATEGY); return { search: (request, options, deps) => { - const dsl = buildEqlDsl(request); + const parsedOptions = parseOptions(request); + const dsl = buildEqlDsl(parsedOptions); + return esEql.search({ ...request, params: dsl }, options, deps).pipe( map((response) => { return { @@ -33,7 +34,7 @@ export const timelineEqlSearchStrategyProvider = ( }), mergeMap(async (esSearchRes) => parseEqlResponse( - request, + parsedOptions, esSearchRes as unknown as EqlSearchStrategyResponse> ) ) diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/parse_options.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/parse_options.ts new file mode 100644 index 0000000000000..e0c85d604ad8b --- /dev/null +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/parse_options.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { timelineEqlRequestOptionsSchema } from '../../../../common/api/search_strategy/timeline/eql'; + +export const parseOptions = (options: unknown) => timelineEqlRequestOptionsSchema.parse(options); diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts index 42740a622d06b..7dea40b3a6c1f 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts @@ -8,12 +8,11 @@ import { cloneDeep, getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { buildAlertFieldsRequest as buildFieldsRequest } from '@kbn/alerts-as-data-utils'; +import { TimelineEventsQueries } from '../../../../../../common/api/search_strategy'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import { EventHit, - TimelineEventsQueries, TimelineEventsAllStrategyResponse, - TimelineEventsAllRequestOptions, TimelineEdges, } from '../../../../../../common/search_strategy'; import { TimelineFactory } from '../../types'; @@ -23,7 +22,7 @@ import { formatTimelineData } from '../../helpers/format_timeline_data'; import { TIMELINE_EVENTS_FIELDS } from '../../helpers/constants'; export const timelineEventsAll: TimelineFactory = { - buildDsl: ({ authFilter, ...options }: TimelineEventsAllRequestOptions) => { + buildDsl: ({ authFilter, ...options }) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } @@ -32,14 +31,19 @@ export const timelineEventsAll: TimelineFactory = { return buildTimelineEventsAllQuery({ ...queryOptions, authFilter }); }, parse: async ( - options: TimelineEventsAllRequestOptions, + options, response: IEsSearchResponse ): Promise => { // eslint-disable-next-line prefer-const let { fieldRequested, ...queryOptions } = cloneDeep(options); queryOptions.fields = buildFieldsRequest(fieldRequested, queryOptions.excludeEcsData); - const { activePage, querySize } = options.pagination; + const { + pagination: { activePage, querySize } = { + activePage: undefined, + querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, + }, + } = options; const producerBuckets = getOr([], 'aggregations.producers.buckets', response.rawResponse); const totalCount = response.rawResponse.hits.total || 0; const hits = response.rawResponse.hits.hits; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.test.ts index 77e0d8b377eb1..9a3a4a0261002 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TimelineEventsQueries } from '../../../../../../common/api/search_strategy'; import { Direction } from '../../../../../../common/search_strategy'; import { buildTimelineEventsAllQuery } from './query.events_all.dsl'; @@ -12,10 +13,11 @@ describe('buildTimelineEventsAllQuery', () => { it('should return ip details query if index key is ipDetails', () => { const defaultIndex = ['.siem-signals-default']; const query = buildTimelineEventsAllQuery({ + factoryQueryType: TimelineEventsQueries.all, fields: [], defaultIndex, filterQuery: '', - language: 'eql', + language: 'kuery', pagination: { activePage: 0, querySize: 100, diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts index 5ae88bcf6f460..2695ed06132bb 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts @@ -7,13 +7,12 @@ import { ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash/fp'; - import { - TimerangeFilter, - TimerangeInput, - TimelineEventsAllRequestOptions, - TimelineRequestSortField, -} from '../../../../../../common/search_strategy'; + SortItem, + TimelineEventsAllOptions, +} from '../../../../../../common/api/search_strategy/timeline/events_all'; + +import { TimerangeFilter, TimerangeInput } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; import { getPreferredEsType } from './helpers'; @@ -22,11 +21,12 @@ export const buildTimelineEventsAllQuery = ({ defaultIndex, fields, filterQuery, - pagination: { activePage, querySize }, + pagination = { activePage: 0, querySize: 0 }, runtimeMappings, sort, timerange, -}: Omit) => { +}: Omit) => { + const { activePage, querySize } = pagination; const filterClause = [...createQueryFilterClauses(filterQuery)]; const getTimerangeFilter = (timerangeOption: TimerangeInput | undefined): TimerangeFilter[] => { if (timerangeOption) { @@ -51,7 +51,7 @@ export const buildTimelineEventsAllQuery = ({ const filters = [...filterClause, ...getTimerangeFilter(timerange), { match_all: {} }]; const filter = authFilter != null ? [...filters, authFilter] : filters; - const getSortField = (sortFields: TimelineRequestSortField[]) => + const getSortField = (sortFields: SortItem[]) => sortFields.map((item) => { const field: string = item.field === 'timestamp' ? '@timestamp' : item.field; return { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts index 72645b4f5eeae..da88bbb213e6b 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts @@ -8,11 +8,10 @@ import { merge } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import { TimelineEventsQueries } from '../../../../../../common/api/search_strategy'; import { EventHit, - TimelineEventsQueries, TimelineEventsDetailsStrategyResponse, - TimelineEventsDetailsRequestOptions, TimelineEventsDetailsItem, } from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -25,7 +24,8 @@ import { import { buildEcsObjects } from '../../helpers/build_ecs_objects'; export const timelineEventsDetails: TimelineFactory = { - buildDsl: ({ authFilter, ...options }: TimelineEventsDetailsRequestOptions) => { + buildDsl: (parsedRequest) => { + const { authFilter, ...options } = parsedRequest; const { indexName, eventId, runtimeMappings = {} } = options; return buildTimelineDetailsQuery({ indexName, @@ -35,7 +35,7 @@ export const timelineEventsDetails: TimelineFactory ): Promise => { const { indexName, eventId, runtimeMappings = {} } = options; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts index 33ec6d02d6a1a..3074cf006ef82 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts @@ -6,7 +6,7 @@ */ import { JsonObject } from '@kbn/utility-types'; -import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { RunTimeMappings } from '../../../../../../common/api/search_strategy/model/runtime_mappings'; export const buildTimelineDetailsQuery = ({ authFilter, @@ -17,7 +17,7 @@ export const buildTimelineDetailsQuery = ({ authFilter?: JsonObject; id: string; indexName: string; - runtimeMappings: MappingRuntimeFields; + runtimeMappings: RunTimeMappings; }) => { const basicFilter = { terms: { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/index.ts index e140fa1038704..cb2935f04c354 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/index.ts @@ -5,10 +5,7 @@ * 2.0. */ -import { - TimelineFactoryQueryTypes, - TimelineEventsQueries, -} from '../../../../../common/search_strategy/timeline'; +import { TimelineEventsQueries } from '../../../../../common/api/search_strategy'; import { TimelineFactory } from '../types'; import { timelineEventsAll } from './all'; @@ -16,10 +13,9 @@ import { timelineEventsDetails } from './details'; import { timelineKpi } from './kpi'; import { timelineEventsLastEventTime } from './last_event_time'; -export const timelineEventsFactory: Record< - TimelineEventsQueries, - TimelineFactory -> = { +export const timelineEventsFactory: { + [K in TimelineEventsQueries]: TimelineFactory; +} = { [TimelineEventsQueries.all]: timelineEventsAll, [TimelineEventsQueries.details]: timelineEventsDetails, [TimelineEventsQueries.kpi]: timelineKpi, diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/index.ts index 0973a96c2672f..e12782a85fb83 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/index.ts @@ -8,19 +8,18 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import { - TimelineEventsQueries, - TimelineRequestBasicOptions, - TimelineKpiStrategyResponse, -} from '../../../../../../common/search_strategy/timeline'; +import { TimelineEventsQueries } from '../../../../../../common/api/search_strategy'; +import { TimelineKpiStrategyResponse } from '../../../../../../common/search_strategy/timeline'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { TimelineFactory } from '../../types'; import { buildTimelineKpiQuery } from './query.kpi.dsl'; export const timelineKpi: TimelineFactory = { - buildDsl: (options: TimelineRequestBasicOptions) => buildTimelineKpiQuery(options), + buildDsl: (options) => { + return buildTimelineKpiQuery(options); + }, parse: async ( - options: TimelineRequestBasicOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts index dd87ef177bfe6..fd98122a12eea 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts @@ -6,19 +6,16 @@ */ import { isEmpty } from 'lodash/fp'; +import { TimelineKpiRequestOptions } from '../../../../../../common/api/search_strategy/timeline/kpi'; -import { - TimerangeFilter, - TimerangeInput, - TimelineRequestBasicOptions, -} from '../../../../../../common/search_strategy'; +import { TimerangeFilter, TimerangeInput } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/filters'; export const buildTimelineKpiQuery = ({ defaultIndex, filterQuery, timerange, -}: TimelineRequestBasicOptions) => { +}: TimelineKpiRequestOptions) => { const filterClause = [...createQueryFilterClauses(filterQuery)]; const getTimerangeFilter = (timerangeOption: TimerangeInput | undefined): TimerangeFilter[] => { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/index.ts index a6425737d6de0..8bd21765f551e 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/index.ts @@ -8,20 +8,16 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import { - TimelineEventsQueries, - TimelineEventsLastEventTimeStrategyResponse, - TimelineEventsLastEventTimeRequestOptions, -} from '../../../../../../common/search_strategy/timeline'; +import { TimelineEventsQueries } from '../../../../../../common/api/search_strategy'; +import { TimelineEventsLastEventTimeStrategyResponse } from '../../../../../../common/search_strategy/timeline'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { TimelineFactory } from '../../types'; import { buildLastEventTimeQuery } from './query.events_last_event_time.dsl'; export const timelineEventsLastEventTime: TimelineFactory = { - buildDsl: (options: TimelineEventsLastEventTimeRequestOptions) => - buildLastEventTimeQuery(options), + buildDsl: (options) => buildLastEventTimeQuery(options), parse: async ( - options: TimelineEventsLastEventTimeRequestOptions, + options, response: IEsSearchResponse ): Promise => { const inspect = { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.test.ts index 5a17afbdd96d5..0a73089aaf321 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.test.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { LastEventIndexKey } from '../../../../../../common/search_strategy'; +import { TimelineEventsQueries } from '../../../../../../common/api/search_strategy'; +import { LastEventIndexKey } from '../../../../../../common/api/search_strategy/timeline/events_last_event_time'; import { buildLastEventTimeQuery } from './query.events_last_event_time.dsl'; describe('buildLastEventTimeQuery', () => { @@ -15,6 +16,7 @@ describe('buildLastEventTimeQuery', () => { indexKey: LastEventIndexKey.ipDetails, details: { ip: '12345567' }, defaultIndex, + factoryQueryType: TimelineEventsQueries.lastEventTime, }); expect(query).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.ts index 24bd1aa6b9971..6d921918f53da 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/last_event_time/query.events_last_event_time.dsl.ts @@ -7,9 +7,9 @@ import type { ISearchRequestParams } from '@kbn/data-plugin/common'; import { - TimelineEventsLastEventTimeRequestOptions, LastEventIndexKey, -} from '../../../../../../common/search_strategy'; + TimelineEventsLastEventTimeRequestOptions, +} from '../../../../../../common/api/search_strategy/timeline/timeline'; import { assertUnreachable } from '../../../../../../common/utility_types'; @@ -23,8 +23,8 @@ export const buildLastEventTimeQuery = ({ defaultIndex, }: TimelineEventsLastEventTimeRequestOptions) => { const indicesToQuery: EventIndices = { - hosts: defaultIndex, - network: defaultIndex, + hosts: defaultIndex || [], + network: defaultIndex || [], }; const getUserDetailsFilter = (userName: string) => [{ term: { 'user.name': userName } }]; const getHostDetailsFilter = (hostName: string) => [{ term: { 'host.name': hostName } }]; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/index.ts index 2ac9c343c843a..4ab3ce6e176ce 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/index.ts @@ -5,13 +5,8 @@ * 2.0. */ -import { TimelineFactoryQueryTypes } from '../../../../common/search_strategy/timeline'; -import { TimelineFactory } from './types'; import { timelineEventsFactory } from './events'; -export const timelineFactory: Record< - TimelineFactoryQueryTypes, - TimelineFactory -> = { +export const timelineFactory = { ...timelineEventsFactory, -}; +} as const; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts index f1a197733baf0..2281109863976 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts @@ -15,43 +15,49 @@ import { import { ENHANCED_ES_SEARCH_STRATEGY, ISearchOptions } from '@kbn/data-plugin/common'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; import { Logger } from '@kbn/logging'; +import { z } from 'zod'; +import { searchStrategyRequestSchema } from '../../../common/api/search_strategy'; import { TimelineFactoryQueryTypes, - TimelineStrategyResponseType, - TimelineStrategyRequestType, EntityType, + TimelineStrategyRequestType, } from '../../../common/search_strategy/timeline'; import { timelineFactory } from './factory'; import { TimelineFactory } from './factory/types'; import { isAggCardinalityAggregate } from './factory/helpers/is_agg_cardinality_aggregate'; -export const timelineSearchStrategyProvider = ( +export const timelineSearchStrategyProvider = ( data: PluginStart, logger: Logger, security?: SecurityPluginSetup -): ISearchStrategy, TimelineStrategyResponseType> => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): ISearchStrategy, any> => { const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); return { search: (request, options, deps) => { - const factoryQueryType = request.factoryQueryType; const entityType = request.entityType; - if (factoryQueryType == null) { - throw new Error('factoryQueryType is required'); - } + const searchStrategyRequest = searchStrategyRequestSchema.parse(request); - const queryFactory: TimelineFactory = timelineFactory[factoryQueryType]; + const queryFactory = timelineFactory[searchStrategyRequest.factoryQueryType]; if (entityType != null && entityType === EntityType.SESSIONS) { return timelineSessionsSearchStrategy({ es, - request, + request: searchStrategyRequest, options, deps, queryFactory, }); } else { - return timelineSearchStrategy({ es, request, options, deps, queryFactory, logger }); + return timelineSearchStrategy({ + es, + request: searchStrategyRequest, + options, + deps, + queryFactory, + logger, + }); } }, cancel: async (id, options, deps) => { @@ -107,7 +113,7 @@ const timelineSessionsSearchStrategy = ({ ...request, defaultIndex: indices, indexName: indices, - }; + } as TimelineStrategyRequestType; const collapse = { field: 'process.entry_leader.entity_id', diff --git a/x-pack/plugins/transform/common/api_schemas/transforms.ts b/x-pack/plugins/transform/common/api_schemas/transforms.ts index 5b300a613be94..65e0433a81264 100644 --- a/x-pack/plugins/transform/common/api_schemas/transforms.ts +++ b/x-pack/plugins/transform/common/api_schemas/transforms.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema, type TypeOf } from '@kbn/config-schema'; import type { ES_FIELD_TYPES } from '@kbn/field-types'; diff --git a/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts b/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts index 459d37414e244..a9d177a341e21 100644 --- a/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts +++ b/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts @@ -13,7 +13,7 @@ import { getTransformsRequestSchema } from './transforms'; export const getTransformsStatsRequestSchema = getTransformsRequestSchema; -export type GetTransformsRequestSchema = TypeOf; +export type GetTransformsStatsRequestSchema = TypeOf; export interface GetTransformsStatsResponseSchema { node_failures?: object; diff --git a/x-pack/plugins/transform/common/api_schemas/type_guards.ts b/x-pack/plugins/transform/common/api_schemas/type_guards.ts deleted file mode 100644 index d5dbad0056c35..0000000000000 --- a/x-pack/plugins/transform/common/api_schemas/type_guards.ts +++ /dev/null @@ -1,147 +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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - -import { isPopulatedObject } from '@kbn/ml-is-populated-object'; - -import type { EsIndex } from '../types/es_index'; -import type { EsIngestPipeline } from '../types/es_ingest_pipeline'; - -// To be able to use the type guards on the client side, we need to make sure we don't import -// the code of '@kbn/config-schema' but just its types, otherwise the client side code will -// fail to build. -import type { FieldHistogramsResponseSchema } from './field_histograms'; -import type { GetTransformsAuditMessagesResponseSchema } from './audit_messages'; -import type { DeleteTransformsResponseSchema } from './delete_transforms'; -import type { ResetTransformsResponseSchema } from './reset_transforms'; -import type { StartTransformsResponseSchema } from './start_transforms'; -import type { StopTransformsResponseSchema } from './stop_transforms'; -import type { ScheduleNowTransformsResponseSchema } from './schedule_now_transforms'; -import type { - GetTransformNodesResponseSchema, - GetTransformsResponseSchema, - PostTransformsPreviewResponseSchema, - PutTransformsResponseSchema, -} from './transforms'; -import type { GetTransformsStatsResponseSchema } from './transforms_stats'; -import type { PostTransformsUpdateResponseSchema } from './update_transforms'; - -const isGenericResponseSchema = (arg: any): arg is T => { - return isPopulatedObject(arg, ['count', 'transforms']) && Array.isArray(arg.transforms); -}; - -export const isGetTransformNodesResponseSchema = ( - arg: unknown -): arg is GetTransformNodesResponseSchema => { - return isPopulatedObject(arg, ['count']) && typeof arg.count === 'number'; -}; - -export const isGetTransformsResponseSchema = (arg: unknown): arg is GetTransformsResponseSchema => { - return isGenericResponseSchema(arg); -}; - -export const isGetTransformsStatsResponseSchema = ( - arg: unknown -): arg is GetTransformsStatsResponseSchema => { - return isGenericResponseSchema(arg); -}; - -export const isDeleteTransformsResponseSchema = ( - arg: unknown -): arg is DeleteTransformsResponseSchema => { - return ( - isPopulatedObject(arg) && - Object.values(arg).every((d) => isPopulatedObject(d, ['transformDeleted'])) - ); -}; - -export const isResetTransformsResponseSchema = ( - arg: unknown -): arg is ResetTransformsResponseSchema => { - return ( - isPopulatedObject(arg) && - Object.values(arg).every((d) => isPopulatedObject(d, ['transformReset'])) - ); -}; - -export const isEsIndices = (arg: unknown): arg is EsIndex[] => { - return Array.isArray(arg); -}; - -export const isEsIngestPipelines = (arg: unknown): arg is EsIngestPipeline[] => { - return Array.isArray(arg) && arg.every((d) => isPopulatedObject(d, ['name'])); -}; - -export const isEsSearchResponse = (arg: unknown): arg is estypes.SearchResponse => { - return isPopulatedObject(arg, ['hits']); -}; - -type SearchResponseWithAggregations = Required> & - estypes.SearchResponse; -export const isEsSearchResponseWithAggregations = ( - arg: unknown -): arg is SearchResponseWithAggregations => { - return isEsSearchResponse(arg) && {}.hasOwnProperty.call(arg, 'aggregations'); -}; - -export const isFieldHistogramsResponseSchema = ( - arg: unknown -): arg is FieldHistogramsResponseSchema => { - return Array.isArray(arg); -}; - -export const isGetTransformsAuditMessagesResponseSchema = ( - arg: unknown -): arg is GetTransformsAuditMessagesResponseSchema => { - return isPopulatedObject(arg, ['messages', 'total']); -}; - -export const isPostTransformsPreviewResponseSchema = ( - arg: unknown -): arg is PostTransformsPreviewResponseSchema => { - return ( - isPopulatedObject(arg, ['generated_dest_index', 'preview']) && - typeof arg.generated_dest_index !== undefined && - Array.isArray(arg.preview) - ); -}; - -export const isPostTransformsUpdateResponseSchema = ( - arg: unknown -): arg is PostTransformsUpdateResponseSchema => { - return isPopulatedObject(arg, ['id']) && typeof arg.id === 'string'; -}; - -export const isPutTransformsResponseSchema = (arg: unknown): arg is PutTransformsResponseSchema => { - return ( - isPopulatedObject(arg, ['transformsCreated', 'errors']) && - Array.isArray(arg.transformsCreated) && - Array.isArray(arg.errors) - ); -}; - -const isGenericSuccessResponseSchema = (arg: unknown) => - isPopulatedObject(arg) && Object.values(arg).every((d) => isPopulatedObject(d, ['success'])); - -export const isStartTransformsResponseSchema = ( - arg: unknown -): arg is StartTransformsResponseSchema => { - return isGenericSuccessResponseSchema(arg); -}; - -export const isStopTransformsResponseSchema = ( - arg: unknown -): arg is StopTransformsResponseSchema => { - return isGenericSuccessResponseSchema(arg); -}; - -export const isScheduleNowTransformsResponseSchema = ( - arg: unknown -): arg is ScheduleNowTransformsResponseSchema => { - return isGenericSuccessResponseSchema(arg); -}; diff --git a/x-pack/plugins/transform/common/constants.ts b/x-pack/plugins/transform/common/constants.ts index 633f0dc7849ac..a1ba2d8277af9 100644 --- a/x-pack/plugins/transform/common/constants.ts +++ b/x-pack/plugins/transform/common/constants.ts @@ -32,6 +32,21 @@ const EXTERNAL_API_BASE_PATH = '/api/transform/'; export const addInternalBasePath = (uri: string): string => `${INTERNAL_API_BASE_PATH}${uri}`; export const addExternalBasePath = (uri: string): string => `${EXTERNAL_API_BASE_PATH}${uri}`; +export const TRANSFORM_REACT_QUERY_KEYS = { + DATA_SEARCH: 'transform.data_search', + DATA_VIEW_EXISTS: 'transform.data_view_exists', + GET_DATA_VIEW_TITLES: 'transform.get_data_view_titles', + GET_ES_INDICES: 'transform.get_es_indices', + GET_ES_INGEST_PIPELINES: 'transform.get_es_ingest_pipelines', + GET_HISTOGRAMS_FOR_FIELDS: 'transform.get_histograms_for_fields', + GET_TRANSFORM: 'transform.get_transform', + GET_TRANSFORM_NODES: 'transform.get_transform_nodes', + GET_TRANSFORM_AUDIT_MESSAGES: 'transform.get_transform_audit_messages', + GET_TRANSFORM_STATS: 'transform.get_transform_stats', + GET_TRANSFORMS: 'transform.get_transforms', + GET_TRANSFORMS_PREVIEW: 'transform.get_transforms_preview', +} as const; + // In order to create a transform, the API requires the following privileges: // - transform_admin (builtin) // - cluster privileges: manage_transform @@ -71,22 +86,6 @@ export const APP_CLUSTER_PRIVILEGES = [ // Minimum privileges required to return transform node count export const NODES_INFO_PRIVILEGES = ['cluster:monitor/transform/get']; -// Equivalent of capabilities.canGetTransform -export const APP_GET_TRANSFORM_CLUSTER_PRIVILEGES = [ - 'cluster.cluster:monitor/transform/get', - 'cluster.cluster:monitor/transform/stats/get', -]; - -// Equivalent of capabilities.canCreateTransform -export const APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES = [ - 'cluster.cluster:monitor/transform/get', - 'cluster.cluster:monitor/transform/stats/get', - 'cluster.cluster:admin/transform/preview', - 'cluster.cluster:admin/transform/put', - 'cluster.cluster:admin/transform/start', - 'cluster.cluster:admin/transform/start_task', -]; - export const APP_INDEX_PRIVILEGES = ['monitor']; // reflects https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformStats.java#L214 diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts b/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts deleted file mode 100644 index e9fa6cd7bc6d7..0000000000000 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { extractMissingPrivileges, getPrivilegesAndCapabilities } from './has_privilege_factory'; - -describe('has_privilege_factory', () => { - const fullClusterPrivileges = { - 'cluster:admin/transform/preview': true, - 'cluster:admin/transform/put': true, - 'cluster:monitor/transform/get': true, - 'cluster:admin/transform/start': true, - 'cluster:admin/transform/delete': true, - 'cluster:admin/transform/reset': true, - 'cluster:admin/transform/stop': true, - 'cluster:admin/transform/start_task': true, - 'cluster:monitor/transform/stats/get': true, - }; - - const monitorOnlyClusterPrivileges = { - 'cluster:admin/transform/preview': false, - 'cluster:admin/transform/put': false, - 'cluster:admin/transform/start': false, - 'cluster:admin/transform/delete': false, - 'cluster:admin/transform/reset': false, - 'cluster:admin/transform/stop': false, - 'cluster:admin/transform/start_task': false, - 'cluster:monitor/transform/get': true, - 'cluster:monitor/transform/stats/get': true, - }; - const noClusterPrivileges = { - 'cluster:admin/transform/preview': false, - 'cluster:admin/transform/put': false, - 'cluster:admin/transform/start': false, - 'cluster:admin/transform/delete': false, - 'cluster:admin/transform/reset': false, - 'cluster:admin/transform/stop': false, - 'cluster:admin/transform/start_task': false, - 'cluster:monitor/transform/get': false, - 'cluster:monitor/transform/stats/get': false, - }; - - const monitorOnlyMissingPrivileges = Object.entries(monitorOnlyClusterPrivileges) - .filter(([, authorized]) => !authorized) - .map(([priv]) => priv); - - describe('extractMissingPrivileges', () => { - it('returns no missing privilege if provided both monitor and admin cluster privileges', () => { - expect(extractMissingPrivileges(fullClusterPrivileges)).toEqual([]); - }); - it('returns missing privilege if provided only monitor cluster privileges', () => { - expect(extractMissingPrivileges(monitorOnlyClusterPrivileges)).toEqual( - monitorOnlyMissingPrivileges - ); - }); - it('returns all missing privilege if provided no cluster privilege', () => { - const allPrivileges = Object.keys(noClusterPrivileges); - const extracted = extractMissingPrivileges(noClusterPrivileges); - expect(extracted).toEqual(allPrivileges); - }); - }); - - describe('getPrivilegesAndCapabilities', () => { - it('returns full capabilities if provided both monitor and admin cluster privileges', () => { - const fullCapabilities = { - canCreateTransform: true, - canCreateTransformAlerts: true, - canDeleteTransform: true, - canGetTransform: true, - canPreviewTransform: true, - canReauthorizeTransform: true, - canResetTransform: true, - canScheduleNowTransform: true, - canStartStopTransform: true, - canUseTransformAlerts: true, - }; - - expect(getPrivilegesAndCapabilities(fullClusterPrivileges, true, true)).toEqual({ - capabilities: fullCapabilities, - privileges: { hasAllPrivileges: true, missingPrivileges: { cluster: [], index: [] } }, - }); - expect(getPrivilegesAndCapabilities(fullClusterPrivileges, false, true)).toEqual({ - capabilities: fullCapabilities, - privileges: { - hasAllPrivileges: true, - missingPrivileges: { cluster: [], index: ['monitor'] }, - }, - }); - }); - it('returns view only capabilities if provided only monitor cluster privileges', () => { - const viewOnlyCapabilities = { - canCreateTransform: false, - canCreateTransformAlerts: false, - canDeleteTransform: false, - canGetTransform: true, - canPreviewTransform: false, - canReauthorizeTransform: false, - canResetTransform: false, - canScheduleNowTransform: false, - canStartStopTransform: false, - canUseTransformAlerts: true, - }; - - const { capabilities, privileges } = getPrivilegesAndCapabilities( - monitorOnlyClusterPrivileges, - true, - false - ); - expect(capabilities).toEqual(viewOnlyCapabilities); - expect(privileges).toEqual({ - hasAllPrivileges: false, - missingPrivileges: { - cluster: monitorOnlyMissingPrivileges, - index: [], - }, - }); - }); - it('returns no capabilities and all the missing privileges if no cluster privileges', () => { - const noCapabilities = { - canCreateTransform: false, - canCreateTransformAlerts: false, - canDeleteTransform: false, - canGetTransform: false, - canPreviewTransform: false, - canResetTransform: false, - canReauthorizeTransform: false, - canScheduleNowTransform: false, - canStartStopTransform: false, - canUseTransformAlerts: false, - }; - - const { capabilities, privileges } = getPrivilegesAndCapabilities( - noClusterPrivileges, - false, - false - ); - expect(capabilities).toEqual(noCapabilities); - expect(privileges).toEqual({ - hasAllPrivileges: false, - missingPrivileges: { - cluster: Object.keys(noClusterPrivileges), - index: ['monitor'], - }, - }); - }); - - it('returns canResetTransform:false if no cluster privilege for transform/reset', () => { - const { capabilities } = getPrivilegesAndCapabilities( - { - ...fullClusterPrivileges, - 'cluster:admin/transform/reset': false, - }, - false, - false - ); - expect(capabilities.canResetTransform).toEqual(false); - }); - }); -}); diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts deleted file mode 100644 index 9dee0c1a73cf1..0000000000000 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts +++ /dev/null @@ -1,217 +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 { i18n } from '@kbn/i18n'; -import { isPopulatedObject } from '@kbn/ml-is-populated-object'; - -import { cloneDeep } from 'lodash'; -import { APP_INDEX_PRIVILEGES } from '../constants'; -import { Privileges } from '../types/privileges'; - -export interface PrivilegesAndCapabilities { - privileges: Privileges; - capabilities: Capabilities; -} - -export interface TransformCapabilities { - canGetTransform: boolean; - canDeleteTransform: boolean; - canPreviewTransform: boolean; - canCreateTransform: boolean; - canReauthorizeTransform: boolean; - canScheduleNowTransform: boolean; - canStartStopTransform: boolean; - canCreateTransformAlerts: boolean; - canUseTransformAlerts: boolean; - canResetTransform: boolean; -} -export type Capabilities = { [k in keyof TransformCapabilities]: boolean }; - -export const INITIAL_CAPABILITIES = Object.freeze({ - canGetTransform: false, - canDeleteTransform: false, - canPreviewTransform: false, - canCreateTransform: false, - canReauthorizeTransform: false, - canScheduleNowTransform: false, - canStartStopTransform: false, - canCreateTransformAlerts: false, - canUseTransformAlerts: false, - canResetTransform: false, -}); - -export type Privilege = [string, string]; - -function isPrivileges(arg: unknown): arg is Privileges { - return ( - isPopulatedObject(arg, ['hasAllPrivileges', 'missingPrivileges']) && - typeof arg.hasAllPrivileges === 'boolean' && - typeof arg.missingPrivileges === 'object' && - arg.missingPrivileges !== null - ); -} - -export const toArray = (value: string | string[]): string[] => - Array.isArray(value) ? value : [value]; - -export const hasPrivilegeFactory = - (privileges: Privileges | undefined | null) => (privilege: Privilege) => { - const [section, requiredPrivilege] = privilege; - if (isPrivileges(privileges) && !privileges.missingPrivileges[section]) { - // if the section does not exist in our missingPrivileges, everything is OK - return true; - } - if (isPrivileges(privileges) && privileges.missingPrivileges[section]!.length === 0) { - return true; - } - if (requiredPrivilege === '*') { - // If length > 0 and we require them all... KO - return false; - } - // If we require _some_ privilege, we make sure that the one - // we require is *not* in the missingPrivilege array - return ( - isPrivileges(privileges) && - !privileges.missingPrivileges[section]!.includes(requiredPrivilege) - ); - }; - -export const extractMissingPrivileges = ( - privilegesObject: { [key: string]: boolean } = {} -): string[] => - Object.keys(privilegesObject).reduce((privileges: string[], privilegeName: string): string[] => { - if (!privilegesObject[privilegeName]) { - privileges.push(privilegeName); - } - return privileges; - }, []); - -export const getPrivilegesAndCapabilities = ( - clusterPrivileges: Record, - hasOneIndexWithAllPrivileges: boolean, - hasAllPrivileges: boolean -): PrivilegesAndCapabilities => { - const privilegesResult: Privileges = { - hasAllPrivileges: true, - missingPrivileges: { - cluster: [], - index: [], - }, - }; - - // Find missing cluster privileges and set overall app privileges - privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(clusterPrivileges); - privilegesResult.hasAllPrivileges = hasAllPrivileges; - - if (!hasOneIndexWithAllPrivileges) { - privilegesResult.missingPrivileges.index = [...APP_INDEX_PRIVILEGES]; - } - - const hasPrivilege = hasPrivilegeFactory(privilegesResult); - - const capabilities = cloneDeep(INITIAL_CAPABILITIES); - capabilities.canGetTransform = - hasPrivilege(['cluster', 'cluster:monitor/transform/get']) && - hasPrivilege(['cluster', 'cluster:monitor/transform/stats/get']); - - capabilities.canCreateTransform = hasPrivilege(['cluster', 'cluster:admin/transform/put']); - - capabilities.canDeleteTransform = hasPrivilege(['cluster', 'cluster:admin/transform/delete']); - - capabilities.canResetTransform = hasPrivilege(['cluster', 'cluster:admin/transform/reset']); - - capabilities.canPreviewTransform = hasPrivilege(['cluster', 'cluster:admin/transform/preview']); - - capabilities.canStartStopTransform = - hasPrivilege(['cluster', 'cluster:admin/transform/start']) && - hasPrivilege(['cluster', 'cluster:admin/transform/start_task']) && - hasPrivilege(['cluster', 'cluster:admin/transform/stop']); - - capabilities.canCreateTransformAlerts = capabilities.canCreateTransform; - - capabilities.canUseTransformAlerts = capabilities.canGetTransform; - - capabilities.canScheduleNowTransform = capabilities.canStartStopTransform; - - capabilities.canReauthorizeTransform = capabilities.canStartStopTransform; - - return { privileges: privilegesResult, capabilities }; -}; -// create the text for button's tooltips if the user -// doesn't have the permission to press that button -export function createCapabilityFailureMessage( - capability: keyof TransformCapabilities | 'noTransformNodes' -) { - let message = ''; - - switch (capability) { - case 'canCreateTransform': - message = i18n.translate('xpack.transform.capability.noPermission.createTransformTooltip', { - defaultMessage: 'You do not have permission to create transforms.', - }); - break; - case 'canCreateTransformAlerts': - message = i18n.translate( - 'xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip', - { - defaultMessage: 'You do not have permission to create transform alert rules.', - } - ); - break; - case 'canScheduleNowTransform': - message = i18n.translate( - 'xpack.transform.capability.noPermission.scheduleNowTransformTooltip', - { - defaultMessage: - 'You do not have permission to schedule transforms to process data instantly.', - } - ); - break; - case 'canStartStopTransform': - message = i18n.translate( - 'xpack.transform.capability.noPermission.startOrStopTransformTooltip', - { - defaultMessage: 'You do not have permission to start or stop transforms.', - } - ); - break; - - case 'canReauthorizeTransform': - message = i18n.translate( - 'xpack.transform.capability.noPermission.reauthorizeTransformTooltip', - { - defaultMessage: 'You do not have permission to reauthorize transforms.', - } - ); - break; - - case 'canDeleteTransform': - message = i18n.translate('xpack.transform.capability.noPermission.deleteTransformTooltip', { - defaultMessage: 'You do not have permission to delete transforms.', - }); - break; - - case 'canResetTransform': - message = i18n.translate('xpack.transform.capability.noPermission.resetTransformTooltip', { - defaultMessage: 'You do not have permission to reset transforms.', - }); - break; - - case 'noTransformNodes': - message = i18n.translate('xpack.transform.capability.noPermission.noTransformNodesTooltip', { - defaultMessage: 'There are no transform nodes available.', - }); - break; - } - - return i18n.translate('xpack.transform.capability.pleaseContactAdministratorTooltip', { - defaultMessage: '{message} Please contact your administrator.', - values: { - message, - }, - }); -} diff --git a/x-pack/plugins/transform/common/types/capabilities.ts b/x-pack/plugins/transform/common/types/capabilities.ts new file mode 100644 index 0000000000000..ae5e241e3b0fe --- /dev/null +++ b/x-pack/plugins/transform/common/types/capabilities.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +export const getInitialTransformCapabilities = (initialSetting = false) => ({ + canCreateTransform: initialSetting, + canCreateTransformAlerts: initialSetting, + canDeleteIndex: initialSetting, + canDeleteTransform: initialSetting, + canGetTransform: initialSetting, + canPreviewTransform: initialSetting, + canReauthorizeTransform: initialSetting, + canResetTransform: initialSetting, + canScheduleNowTransform: initialSetting, + canStartStopTransform: initialSetting, + canUseTransformAlerts: initialSetting, +}); + +export const isTransformCapabilities = (arg: unknown): arg is TransformCapabilities => { + return ( + isPopulatedObject(arg, Object.keys(getInitialTransformCapabilities())) && + Object.values(arg).every((d) => typeof d === 'boolean') + ); +}; + +export type TransformCapabilities = ReturnType; +export type TransformCapability = keyof TransformCapabilities; + +export interface PrivilegesAndCapabilities { + privileges: Privileges; + capabilities: TransformCapabilities; +} + +export type Privilege = [string, string]; + +export interface MissingPrivileges { + [key: string]: string[] | undefined; +} + +export interface Privileges { + hasAllPrivileges: boolean; + missingPrivileges: MissingPrivileges; +} diff --git a/x-pack/plugins/transform/common/types/privileges.ts b/x-pack/plugins/transform/common/types/privileges.ts deleted file mode 100644 index 702b62210d062..0000000000000 --- a/x-pack/plugins/transform/common/types/privileges.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export interface MissingPrivileges { - [key: string]: string[] | undefined; -} - -export interface Privileges { - hasAllPrivileges: boolean; - missingPrivileges: MissingPrivileges; -} diff --git a/x-pack/plugins/transform/common/utils/create_capability_failure_message.ts b/x-pack/plugins/transform/common/utils/create_capability_failure_message.ts new file mode 100644 index 0000000000000..7f0f660bd2514 --- /dev/null +++ b/x-pack/plugins/transform/common/utils/create_capability_failure_message.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import type { TransformCapabilities } from '../types/capabilities'; + +// create the text for button's tooltips if the user +// doesn't have the permission to press that button +export function createCapabilityFailureMessage( + capability: keyof TransformCapabilities | 'noTransformNodes' +) { + let message = ''; + + switch (capability) { + case 'canCreateTransform': + message = i18n.translate('xpack.transform.capability.noPermission.createTransformTooltip', { + defaultMessage: 'You do not have permission to create transforms.', + }); + break; + case 'canCreateTransformAlerts': + message = i18n.translate( + 'xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip', + { + defaultMessage: 'You do not have permission to create transform alert rules.', + } + ); + break; + case 'canScheduleNowTransform': + message = i18n.translate( + 'xpack.transform.capability.noPermission.scheduleNowTransformTooltip', + { + defaultMessage: + 'You do not have permission to schedule transforms to process data instantly.', + } + ); + break; + case 'canStartStopTransform': + message = i18n.translate( + 'xpack.transform.capability.noPermission.startOrStopTransformTooltip', + { + defaultMessage: 'You do not have permission to start or stop transforms.', + } + ); + break; + + case 'canReauthorizeTransform': + message = i18n.translate( + 'xpack.transform.capability.noPermission.reauthorizeTransformTooltip', + { + defaultMessage: 'You do not have permission to reauthorize transforms.', + } + ); + break; + + case 'canDeleteTransform': + message = i18n.translate('xpack.transform.capability.noPermission.deleteTransformTooltip', { + defaultMessage: 'You do not have permission to delete transforms.', + }); + break; + + case 'canResetTransform': + message = i18n.translate('xpack.transform.capability.noPermission.resetTransformTooltip', { + defaultMessage: 'You do not have permission to reset transforms.', + }); + break; + + case 'noTransformNodes': + message = i18n.translate('xpack.transform.capability.noPermission.noTransformNodesTooltip', { + defaultMessage: 'There are no transform nodes available.', + }); + break; + } + + return i18n.translate('xpack.transform.capability.pleaseContactAdministratorTooltip', { + defaultMessage: '{message} Please contact your administrator.', + values: { + message, + }, + }); +} diff --git a/x-pack/plugins/transform/common/utils/to_array.ts b/x-pack/plugins/transform/common/utils/to_array.ts new file mode 100644 index 0000000000000..f41ceea9fc180 --- /dev/null +++ b/x-pack/plugins/transform/common/utils/to_array.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const toArray = (value: T | T[]): T[] => (Array.isArray(value) ? value : [value]); diff --git a/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx b/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx index 56bd9a2db877e..49b8ee1a684fb 100644 --- a/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx +++ b/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx @@ -6,16 +6,15 @@ */ import { EuiForm, EuiSpacer } from '@elastic/eui'; -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { FC, useCallback, useEffect, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import type { TransformHealthRuleParams } from '../../../common/types/alerting'; import { TestsSelectionControl } from './tests_selection_control'; import { TransformSelectorControl } from './transform_selector_control'; -import { useApi } from '../../app/hooks'; +import { useGetTransforms } from '../../app/hooks'; import { useToastNotifications } from '../../app/app_dependencies'; -import { GetTransformsResponseSchema } from '../../../common/api_schemas/transforms'; import { ALL_TRANSFORMS_SELECTION } from '../../../common/constants'; export type TransformHealthRuleTriggerProps = @@ -29,9 +28,12 @@ const TransformHealthRuleTrigger: FC = ({ const formErrors = Object.values(errors).flat(); const isFormInvalid = formErrors.length > 0; - const api = useApi(); const toast = useToastNotifications(); - const [transformOptions, setTransformOptions] = useState([]); + const { error, data } = useGetTransforms(); + const transformOptions = useMemo( + () => data?.transforms.filter((v) => v.config.sync).map((v) => v.id) ?? [], + [data] + ); const onAlertParamChange = useCallback( (param: T) => @@ -41,34 +43,18 @@ const TransformHealthRuleTrigger: FC = ({ [setRuleParams] ); - useEffect( - function fetchTransforms() { - let unmounted = false; - api - .getTransforms() - .then((r) => { - if (!unmounted) { - setTransformOptions( - (r as GetTransformsResponseSchema).transforms.filter((v) => v.sync).map((v) => v.id) - ); + useEffect(() => { + if (error !== null) { + toast.addError(error, { + title: i18n.translate( + 'xpack.transform.alertingRuleTypes.transformHealth.fetchErrorMessage', + { + defaultMessage: 'Unable to fetch transforms', } - }) - .catch((e) => { - toast.addError(e, { - title: i18n.translate( - 'xpack.transform.alertingRuleTypes.transformHealth.fetchErrorMessage', - { - defaultMessage: 'Unable to fetch transforms', - } - ), - }); - }); - return () => { - unmounted = true; - }; - }, - [api, toast] - ); + ), + }); + } + }, [error, toast]); const excludeTransformOptions = useMemo(() => { if (ruleParams.includeTransforms?.some((v) => v === ALL_TRANSFORMS_SELECTION)) { diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index ba4a43bfa0876..2812475f7f87a 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useContext, FC } from 'react'; +import React, { type FC } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -13,68 +13,57 @@ import { EuiErrorBoundary } from '@elastic/eui'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { ScopedHistory } from '@kbn/core/public'; -import { FormattedMessage } from '@kbn/i18n-react'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import { addInternalBasePath } from '../../common/constants'; - -import { SectionError } from './components'; import { SECTION_SLUG } from './common/constants'; -import { AuthorizationContext, AuthorizationProvider } from './lib/authorization'; import { AppDependencies } from './app_dependencies'; import { CloneTransformSection } from './sections/clone_transform'; import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; +import { ServerlessContextProvider } from './serverless_context'; -export const App: FC<{ history: ScopedHistory }> = ({ history }) => { - const { apiError } = useContext(AuthorizationContext); - if (apiError !== null) { - return ( - - } - error={apiError} +export const App: FC<{ history: ScopedHistory }> = ({ history }) => ( + + + - ); - } - - return ( - - - - - - - - ); -}; + + + + +); -export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => { +export const renderApp = ( + element: HTMLElement, + appDependencies: AppDependencies, + isServerless: boolean +) => { const I18nContext = appDependencies.i18n.Context; - const queryClient = new QueryClient(); + + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: Infinity, + retry: false, + }, + }, + }); render( - - + + - - + + diff --git a/x-pack/plugins/transform/public/app/common/index.ts b/x-pack/plugins/transform/public/app/common/index.ts index c7656974ec569..2bea1421277ef 100644 --- a/x-pack/plugins/transform/public/app/common/index.ts +++ b/x-pack/plugins/transform/public/app/common/index.ts @@ -19,12 +19,7 @@ export { toggleSelectedField, } from './fields'; export type { DropDownLabel, DropDownOption, Label } from './dropdown'; -export { - isTransformIdValid, - refreshTransformList$, - useRefreshTransformList, - REFRESH_TRANSFORM_LIST_STATE, -} from './transform'; +export { isTransformIdValid } from './transform'; export type { TransformListAction, TransformListRow } from './transform_list'; export { TRANSFORM_LIST_COLUMN } from './transform_list'; export { getTransformProgress, isCompletedBatchTransform } from './transform_stats'; diff --git a/x-pack/plugins/transform/public/app/common/navigation.tsx b/x-pack/plugins/transform/public/app/common/navigation.tsx index 601e701c7f16d..ef6696d3e757a 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { Redirect } from 'react-router-dom'; import { SECTION_SLUG } from './constants'; diff --git a/x-pack/plugins/transform/public/app/common/transform.ts b/x-pack/plugins/transform/public/app/common/transform.ts index 35ead5691a866..b689ee8096983 100644 --- a/x-pack/plugins/transform/public/app/common/transform.ts +++ b/x-pack/plugins/transform/public/app/common/transform.ts @@ -5,11 +5,8 @@ * 2.0. */ -import { useEffect } from 'react'; -import { BehaviorSubject } from 'rxjs'; -import { filter, distinctUntilChanged } from 'rxjs/operators'; -import { Subscription } from 'rxjs'; import { cloneDeep } from 'lodash'; + import type { TransformConfigUnion, TransformId } from '../../../common/types/transform'; // Via https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/utils/TransformStrings.java#L24 @@ -23,64 +20,6 @@ export function isTransformIdValid(transformId: TransformId) { export const TRANSFORM_ERROR_TYPE = { DANGLING_TASK: 'dangling_task', } as const; -export enum REFRESH_TRANSFORM_LIST_STATE { - ERROR = 'error', - IDLE = 'idle', - LOADING = 'loading', - REFRESH = 'refresh', -} -export const refreshTransformList$ = new BehaviorSubject( - REFRESH_TRANSFORM_LIST_STATE.IDLE -); - -export const useRefreshTransformList = ( - callback: { - isLoading?(d: boolean): void; - onRefresh?(): void; - } = {} -) => { - useEffect(() => { - const distinct$ = refreshTransformList$.pipe(distinctUntilChanged()); - - const subscriptions: Subscription[] = []; - - if (typeof callback.onRefresh === 'function') { - // initial call to refresh - callback.onRefresh(); - - subscriptions.push( - distinct$ - .pipe(filter((state) => state === REFRESH_TRANSFORM_LIST_STATE.REFRESH)) - .subscribe(() => typeof callback.onRefresh === 'function' && callback.onRefresh()) - ); - } - - if (typeof callback.isLoading === 'function') { - subscriptions.push( - distinct$.subscribe( - (state) => - typeof callback.isLoading === 'function' && - callback.isLoading(state === REFRESH_TRANSFORM_LIST_STATE.LOADING) - ) - ); - } - - return () => { - subscriptions.map((sub) => sub.unsubscribe()); - }; - // The effect should only be called once. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return { - refresh: () => { - // A refresh is followed immediately by setting the state to loading - // to trigger data fetching and loading indicators in one go. - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.LOADING); - }, - }; -}; export const overrideTransformForCloning = (originalConfig: TransformConfigUnion) => { // 'Managed' means job is preconfigured and deployed by other solutions diff --git a/x-pack/plugins/transform/public/app/components/capabilities_wrapper.tsx b/x-pack/plugins/transform/public/app/components/capabilities_wrapper.tsx new file mode 100644 index 0000000000000..ce850aa2ffc6c --- /dev/null +++ b/x-pack/plugins/transform/public/app/components/capabilities_wrapper.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 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, { type FC } from 'react'; + +import { EuiFlexItem, EuiFlexGroup, EuiPageTemplate, EuiEmptyPrompt } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { TransformCapability } from '../../../common/types/capabilities'; +import { toArray } from '../../../common/utils/to_array'; + +import { useTransformCapabilities } from '../hooks'; + +interface Props { + title: React.ReactNode; + message: React.ReactNode | string; +} + +export const NotAuthorizedSection = ({ title, message }: Props) => ( + {title}} body={

    {message}

    } /> +); + +const MissingCapabilities: FC = () => ( + + + + + } + message={ + + } + /> + + + +); + +export const CapabilitiesWrapper: FC<{ + requiredCapabilities: TransformCapability | TransformCapability[]; +}> = ({ children, requiredCapabilities }) => { + const capabilities = useTransformCapabilities(); + + const hasCapabilities = toArray(requiredCapabilities).every((c) => capabilities[c]); + + return hasCapabilities ? <>{children} : ; +}; diff --git a/x-pack/plugins/transform/public/app/components/index.ts b/x-pack/plugins/transform/public/app/components/index.ts index 7c3cb454aecb3..2edcf498e5832 100644 --- a/x-pack/plugins/transform/public/app/components/index.ts +++ b/x-pack/plugins/transform/public/app/components/index.ts @@ -5,6 +5,4 @@ * 2.0. */ -export { SectionError } from './section_error'; -export { SectionLoading } from './section_loading'; export { ToastNotificationText } from './toast_notification_text'; diff --git a/x-pack/plugins/transform/public/app/components/job_icon.tsx b/x-pack/plugins/transform/public/app/components/job_icon.tsx index 09db5a1c52377..12a5687156611 100644 --- a/x-pack/plugins/transform/public/app/components/job_icon.tsx +++ b/x-pack/plugins/transform/public/app/components/job_icon.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { AuditMessageBase } from '../../../common/types/messages'; diff --git a/x-pack/plugins/transform/public/app/components/section_error.tsx b/x-pack/plugins/transform/public/app/components/section_error.tsx deleted file mode 100644 index 2a6ddd3174de7..0000000000000 --- a/x-pack/plugins/transform/public/app/components/section_error.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiPageTemplate } from '@elastic/eui'; - -interface Props { - title: React.ReactNode; - error: Error | null; - actions?: JSX.Element; -} - -export const SectionError: React.FunctionComponent = ({ - title, - error, - actions, - ...rest -}) => { - const errorMessage = error?.message ?? JSON.stringify(error, null, 2); - - return ( - {title}} - body={ -

    -

    {errorMessage}
    - {actions ? actions : null} -

    - } - /> - ); -}; diff --git a/x-pack/plugins/transform/public/app/components/section_loading.tsx b/x-pack/plugins/transform/public/app/components/section_loading.tsx deleted file mode 100644 index c1548ad960bb0..0000000000000 --- a/x-pack/plugins/transform/public/app/components/section_loading.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { - EuiEmptyPrompt, - EuiLoadingSpinner, - EuiText, - EuiFlexGroup, - EuiFlexItem, - EuiTextColor, -} from '@elastic/eui'; - -interface Props { - inline?: boolean; - children: React.ReactNode; - [key: string]: any; -} - -export const SectionLoading: React.FunctionComponent = ({ inline, children, ...rest }) => { - if (inline) { - return ( - - - - - - - {children} - - - - ); - } - - return ( - } - body={{children}} - data-test-subj="sectionLoading" - /> - ); -}; diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx index 6409a8fcf3b45..8b2f154e8124e 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiButtonEmpty, @@ -19,41 +19,45 @@ import { import { i18n } from '@kbn/i18n'; -import { CoreStart } from '@kbn/core/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { useAppDependencies } from '../app_dependencies'; const MAX_SIMPLE_MESSAGE_LENGTH = 140; -// Because of the use of `toMountPoint`, `useKibanaContext` doesn't work via `useAppDependencies`. -// That's why we need to pass in `overlays` as a prop cannot get it via context. interface ToastNotificationTextProps { - overlays: CoreStart['overlays']; - theme: CoreStart['theme']; text: any; previewTextLength?: number; + inline?: boolean; + forceModal?: boolean; } export const ToastNotificationText: FC = ({ - overlays, text, - theme, previewTextLength, + inline = false, + forceModal = false, }) => { - if (typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) { + const { overlays, theme, i18n: i18nStart } = useAppDependencies(); + + if (!forceModal && typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) { return text; } if ( + !forceModal && typeof text === 'object' && + text !== null && typeof text.message === 'string' && text.message.length <= MAX_SIMPLE_MESSAGE_LENGTH ) { return text.message; } - const unformattedText = text.message ? text.message : text; - const formattedText = typeof unformattedText === 'object' ? JSON.stringify(text, null, 2) : text; + const unformattedText = + typeof text === 'object' && text !== null && text.message ? text.message : text; + const formattedText = + typeof unformattedText === 'object' ? JSON.stringify(text, null, 2) : unformattedText; const textLength = previewTextLength ?? 140; const previewText = `${formattedText.substring(0, textLength)}${ formattedText.length > textLength ? ' ...' : '' @@ -83,15 +87,19 @@ export const ToastNotificationText: FC = ({ , - { theme$: theme.theme$ } + { theme, i18n: i18nStart } ) ); }; return ( <> -
    {previewText}
    - + {!inline &&
    {previewText}
    } + {i18n.translate('xpack.transform.toastText.openModalButtonText', { defaultMessage: 'View details', })} diff --git a/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts b/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts deleted file mode 100644 index 2e0407b0ac974..0000000000000 --- a/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts +++ /dev/null @@ -1,150 +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 { IHttpFetchError } from '@kbn/core-http-browser'; - -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils'; - -import type { TransformId } from '../../../../common/types/transform'; -import type { FieldHistogramsResponseSchema } from '../../../../common/api_schemas/field_histograms'; -import type { GetTransformsAuditMessagesResponseSchema } from '../../../../common/api_schemas/audit_messages'; -import type { - DeleteTransformsRequestSchema, - DeleteTransformsResponseSchema, -} from '../../../../common/api_schemas/delete_transforms'; -import type { - StartTransformsRequestSchema, - StartTransformsResponseSchema, -} from '../../../../common/api_schemas/start_transforms'; -import type { - StopTransformsRequestSchema, - StopTransformsResponseSchema, -} from '../../../../common/api_schemas/stop_transforms'; -import type { - GetTransformsResponseSchema, - PostTransformsPreviewRequestSchema, - PostTransformsPreviewResponseSchema, - PutTransformsRequestSchema, - PutTransformsResponseSchema, -} from '../../../../common/api_schemas/transforms'; -import type { GetTransformsStatsResponseSchema } from '../../../../common/api_schemas/transforms_stats'; -import type { - PostTransformsUpdateRequestSchema, - PostTransformsUpdateResponseSchema, -} from '../../../../common/api_schemas/update_transforms'; - -import type { EsIndex } from '../../../../common/types/es_index'; - -import type { SavedSearchQuery } from '../use_search_items'; - -export interface FieldHistogramRequestConfig { - fieldName: string; - type?: KBN_FIELD_TYPES; -} - -const apiFactory = () => ({ - async getTransform( - transformId: TransformId - ): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async getTransforms(): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async getTransformStats( - transformId: TransformId - ): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async getTransformsStats(): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async createTransform( - transformId: TransformId, - transformConfig: PutTransformsRequestSchema - ): Promise { - return Promise.resolve({ transformsCreated: [], errors: [] }); - }, - async updateTransform( - transformId: TransformId, - transformConfig: PostTransformsUpdateRequestSchema - ): Promise { - return Promise.resolve({ - id: 'the-test-id', - source: { index: ['the-index-name'], query: { match_all: {} } }, - dest: { index: 'user-the-destination-index-name' }, - frequency: '10m', - pivot: { - group_by: { the_group: { terms: { field: 'the-group-by-field' } } }, - aggregations: { the_agg: { value_count: { field: 'the-agg-field' } } }, - }, - description: 'the-description', - settings: { docs_per_second: null }, - version: '8.0.0', - create_time: 1598860879097, - }); - }, - async deleteTransforms( - reqBody: DeleteTransformsRequestSchema - ): Promise { - return Promise.resolve({}); - }, - async getTransformsPreview( - obj: PostTransformsPreviewRequestSchema - ): Promise { - return Promise.resolve({ - generated_dest_index: { - mappings: { - _meta: { - _transform: { - transform: 'the-transform', - version: { create: 'the-version' }, - creation_date_in_millis: 0, - }, - created_by: 'mock', - }, - properties: {}, - }, - settings: { index: { number_of_shards: '1', auto_expand_replicas: '0-1' } }, - aliases: {}, - }, - preview: [], - }); - }, - async startTransforms( - reqBody: StartTransformsRequestSchema - ): Promise { - return Promise.resolve({}); - }, - async stopTransforms( - transformsInfo: StopTransformsRequestSchema - ): Promise { - return Promise.resolve({}); - }, - async getTransformAuditMessages( - transformId: TransformId - ): Promise { - return Promise.resolve({ messages: [], total: 0 }); - }, - - async getEsIndices(): Promise { - return Promise.resolve([]); - }, - async getHistogramsForFields( - dataViewTitle: string, - fields: FieldHistogramRequestConfig[], - query: string | SavedSearchQuery, - samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE - ): Promise { - return Promise.resolve([]); - }, -}); - -export const useApi = () => { - return apiFactory(); -}; diff --git a/x-pack/plugins/transform/public/app/hooks/index.ts b/x-pack/plugins/transform/public/app/hooks/index.ts index f6a4c72b39a44..749706f97cd70 100644 --- a/x-pack/plugins/transform/public/app/hooks/index.ts +++ b/x-pack/plugins/transform/public/app/hooks/index.ts @@ -5,10 +5,23 @@ * 2.0. */ -export { useApi } from './use_api'; +export { useCreateTransform } from './use_create_transform'; +export { useDocumentationLinks } from './use_documentation_links'; +export { useGetDataViewTitles } from './use_get_data_view_titles'; +export { useGetEsIndices } from './use_get_es_indices'; +export { useGetEsIngestPipelines } from './use_get_es_ingest_pipelines'; +export { useGetTransformAuditMessages } from './use_get_transform_audit_messages'; +export { useGetTransform } from './use_get_transform'; +export { useGetTransformNodes } from './use_get_transform_nodes'; export { useGetTransforms } from './use_get_transforms'; +export { useGetTransformsPreview } from './use_get_transforms_preview'; +export { useGetTransformStats } from './use_get_transform_stats'; export { useDeleteTransforms, useDeleteIndexAndTargetIndex } from './use_delete_transform'; +export { useRefreshTransformList } from './use_refresh_transform_list'; export { useResetTransforms } from './use_reset_transform'; +export { useSearchItems } from './use_search_items'; export { useScheduleNowTransforms } from './use_schedule_now_transform'; export { useStartTransforms } from './use_start_transform'; export { useStopTransforms } from './use_stop_transform'; +export { useTransformCapabilities } from './use_transform_capabilities'; +export { useUpdateTransform } from './use_update_transform'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts deleted file mode 100644 index 066977fb841df..0000000000000 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ /dev/null @@ -1,301 +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 { useMemo } from 'react'; - -import type { IHttpFetchError } from '@kbn/core-http-browser'; - -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils'; - -import { - ReauthorizeTransformsRequestSchema, - ReauthorizeTransformsResponseSchema, -} from '../../../common/api_schemas/reauthorize_transforms'; -import type { GetTransformsAuditMessagesResponseSchema } from '../../../common/api_schemas/audit_messages'; -import type { - DeleteTransformsRequestSchema, - DeleteTransformsResponseSchema, -} from '../../../common/api_schemas/delete_transforms'; -import type { - FieldHistogramsRequestSchema, - FieldHistogramsResponseSchema, -} from '../../../common/api_schemas/field_histograms'; -import type { - ResetTransformsRequestSchema, - ResetTransformsResponseSchema, -} from '../../../common/api_schemas/reset_transforms'; -import type { - StartTransformsRequestSchema, - StartTransformsResponseSchema, -} from '../../../common/api_schemas/start_transforms'; -import type { - StopTransformsRequestSchema, - StopTransformsResponseSchema, -} from '../../../common/api_schemas/stop_transforms'; -import type { - ScheduleNowTransformsRequestSchema, - ScheduleNowTransformsResponseSchema, -} from '../../../common/api_schemas/schedule_now_transforms'; -import type { - GetTransformNodesResponseSchema, - GetTransformsResponseSchema, - PostTransformsPreviewRequestSchema, - PostTransformsPreviewResponseSchema, - PutTransformsRequestSchema, - PutTransformsResponseSchema, -} from '../../../common/api_schemas/transforms'; -import type { - PostTransformsUpdateRequestSchema, - PostTransformsUpdateResponseSchema, -} from '../../../common/api_schemas/update_transforms'; -import type { GetTransformsStatsResponseSchema } from '../../../common/api_schemas/transforms_stats'; -import type { TransformId } from '../../../common/types/transform'; -import { addInternalBasePath } from '../../../common/constants'; -import type { EsIndex } from '../../../common/types/es_index'; -import type { EsIngestPipeline } from '../../../common/types/es_ingest_pipeline'; - -import { useAppDependencies } from '../app_dependencies'; - -import type { SavedSearchQuery } from './use_search_items'; - -export interface FieldHistogramRequestConfig { - fieldName: string; - type?: KBN_FIELD_TYPES; -} - -interface FetchOptions { - asSystemRequest?: boolean; -} - -export const useApi = () => { - const { http } = useAppDependencies(); - - return useMemo( - () => ({ - async getTransformNodes(): Promise { - try { - return await http.get(addInternalBasePath(`transforms/_nodes`), { version: '1' }); - } catch (e) { - return e; - } - }, - async getTransform( - transformId: TransformId - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms/${transformId}`), { - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransforms( - fetchOptions: FetchOptions = {} - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms`), { - ...fetchOptions, - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformStats( - transformId: TransformId - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms/${transformId}/_stats`), { - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformsStats( - fetchOptions: FetchOptions = {} - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms/_stats`), { - ...fetchOptions, - version: '1', - }); - } catch (e) { - return e; - } - }, - async createTransform( - transformId: TransformId, - transformConfig: PutTransformsRequestSchema - ): Promise { - try { - return await http.put(addInternalBasePath(`transforms/${transformId}`), { - body: JSON.stringify(transformConfig), - version: '1', - }); - } catch (e) { - return e; - } - }, - async updateTransform( - transformId: TransformId, - transformConfig: PostTransformsUpdateRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`transforms/${transformId}/_update`), { - body: JSON.stringify(transformConfig), - version: '1', - }); - } catch (e) { - return e; - } - }, - async deleteTransforms( - reqBody: DeleteTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`delete_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformsPreview( - obj: PostTransformsPreviewRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`transforms/_preview`), { - body: JSON.stringify(obj), - version: '1', - }); - } catch (e) { - return e; - } - }, - async reauthorizeTransforms( - reqBody: ReauthorizeTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`reauthorize_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - - async resetTransforms( - reqBody: ResetTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`reset_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - async startTransforms( - reqBody: StartTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`start_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - async stopTransforms( - transformsInfo: StopTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`stop_transforms`), { - body: JSON.stringify(transformsInfo), - version: '1', - }); - } catch (e) { - return e; - } - }, - async scheduleNowTransforms( - transformsInfo: ScheduleNowTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`schedule_now_transforms`), { - body: JSON.stringify(transformsInfo), - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformAuditMessages( - transformId: TransformId, - sortField: string, - sortDirection: 'asc' | 'desc' - ): Promise< - { messages: GetTransformsAuditMessagesResponseSchema; total: number } | IHttpFetchError - > { - try { - return await http.get(addInternalBasePath(`transforms/${transformId}/messages`), { - query: { - sortField, - sortDirection, - }, - version: '1', - }); - } catch (e) { - return e; - } - }, - async getEsIndices(): Promise { - try { - return await http.get(`/api/index_management/indices`, { version: '1' }); - } catch (e) { - return e; - } - }, - async getEsIngestPipelines(): Promise { - try { - return await http.get('/api/ingest_pipelines', { version: '1' }); - } catch (e) { - return e; - } - }, - async getHistogramsForFields( - dataViewTitle: string, - fields: FieldHistogramRequestConfig[], - query: string | SavedSearchQuery, - runtimeMappings?: FieldHistogramsRequestSchema['runtimeMappings'], - samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE - ): Promise { - try { - return await http.post(addInternalBasePath(`field_histograms/${dataViewTitle}`), { - body: JSON.stringify({ - query, - fields, - samplerShardSize, - ...(runtimeMappings !== undefined ? { runtimeMappings } : {}), - }), - version: '1', - }); - } catch (e) { - return e; - } - }, - }), - [http] - ); -}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx new file mode 100644 index 0000000000000..272e815c42eb4 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx @@ -0,0 +1,71 @@ +/* + * Copyright 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 { useMutation } from '@tanstack/react-query'; + +import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/react-kibana-mount'; + +import type { + PutTransformsRequestSchema, + PutTransformsResponseSchema, +} from '../../../common/api_schemas/transforms'; +import { addInternalBasePath } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; +import { getErrorMessage } from '../../../common/utils/errors'; + +import { useAppDependencies, useToastNotifications } from '../app_dependencies'; +import { ToastNotificationText } from '../components'; + +import { useRefreshTransformList } from './use_refresh_transform_list'; + +interface CreateTransformArgs { + transformId: TransformId; + transformConfig: PutTransformsRequestSchema; +} + +export const useCreateTransform = () => { + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); + const toastNotifications = useToastNotifications(); + + function errorToast(error: unknown, { transformId }: CreateTransformArgs) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepCreateForm.createTransformErrorMessage', { + defaultMessage: 'An error occurred creating the transform {transformId}:', + values: { transformId }, + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + + const mutation = useMutation({ + mutationFn: ({ transformId, transformConfig }: CreateTransformArgs) => { + return http.put( + addInternalBasePath(`transforms/${transformId}`), + { + body: JSON.stringify(transformConfig), + version: '1', + } + ); + }, + onError: errorToast, + onSuccess: (resp, options) => { + if (resp.errors.length > 0) { + errorToast(resp.errors.length === 1 ? resp.errors[0] : resp.errors, options); + } + + refreshTransformList(); + }, + }); + + return mutation.mutate; +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_data_search.ts b/x-pack/plugins/transform/public/app/hooks/use_data_search.ts index af4bb440f9e24..f691cb7a0d8b6 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_data_search.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_data_search.ts @@ -5,37 +5,37 @@ * 2.0. */ -import { useCallback } from 'react'; +import { useQuery } from '@tanstack/react-query'; import { lastValueFrom } from 'rxjs'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import type { IKibanaSearchRequest } from '@kbn/data-plugin/common'; +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + import { useAppDependencies } from '../app_dependencies'; -export const useDataSearch = () => { +export const useDataSearch = ( + esSearchRequestParams: IKibanaSearchRequest['params'], + enabled?: boolean +) => { const { data } = useAppDependencies(); - return useCallback( - async (esSearchRequestParams: IKibanaSearchRequest['params'], abortSignal?: AbortSignal) => { - try { - const { rawResponse: resp } = await lastValueFrom( - data.search.search( - { - params: esSearchRequestParams, - }, - { abortSignal } - ) - ); - - return resp; - } catch (error) { - if (error.name === 'AbortError') { - // ignore abort errors - } else { - return error; - } - } + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.DATA_SEARCH, esSearchRequestParams], + async ({ signal }) => { + const { rawResponse: resp } = await lastValueFrom( + data.search.search( + { + params: esSearchRequestParams, + }, + { abortSignal: signal } + ) + ); + + return resp; }, - [data] + { enabled } ); }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts b/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts new file mode 100644 index 0000000000000..d74fa9c909a5d --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { ErrorType } from '@kbn/ml-error-utils'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; +import type { TransformListRow } from '../common'; + +export const useDataViewExists = (items: TransformListRow[]) => { + const { + data: { dataViews: dataViewsContract }, + } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.DATA_VIEW_EXISTS, items], + async () => { + if (items.length !== 1) { + return false; + } + const config = items[0].config; + const indexName = Array.isArray(config.dest.index) ? config.dest.index[0] : config.dest.index; + + if (indexName === undefined) { + return false; + } + + return (await dataViewsContract.find(indexName)).some(({ title }) => title === indexName); + } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index 25318fc9e2903..a7ed779c47cc7 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -6,34 +6,40 @@ */ import React, { useCallback, useEffect, useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; + import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { extractErrorMessage } from '@kbn/ml-error-utils'; + +import { addInternalBasePath } from '../../../common/constants'; import type { - DeleteTransformStatus, DeleteTransformsRequestSchema, + DeleteTransformsResponseSchema, } from '../../../common/api_schemas/delete_transforms'; -import { isDeleteTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; import { getErrorMessage } from '../../../common/utils/errors'; + import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$, TransformListRow } from '../common'; +import { type TransformListRow } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; -import { indexService } from '../services/es_index_service'; + +import { useTransformCapabilities } from './use_transform_capabilities'; +import { useDataViewExists } from './use_data_view_exists'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { const { - http, - data: { dataViews: dataViewsContract }, application: { capabilities }, } = useAppDependencies(); const toastNotifications = useToastNotifications(); + const { canDeleteIndex: userCanDeleteIndex } = useTransformCapabilities(); + + const userCanDeleteDataView = + capabilities.savedObjectsManagement?.delete === true || + capabilities.indexPatterns?.save === true; const [deleteDestIndex, setDeleteDestIndex] = useState(true); - const [deleteDataView, setDeleteDataView] = useState(true); - const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); - const [dataViewExists, setDataViewExists] = useState(false); - const [userCanDeleteDataView, setUserCanDeleteDataView] = useState(false); + const [deleteDataView, setDeleteDataView] = useState(userCanDeleteDataView); const toggleDeleteIndex = useCallback( () => setDeleteDestIndex(!deleteDestIndex), @@ -43,67 +49,31 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { () => setDeleteDataView(!deleteDataView), [deleteDataView] ); - const checkDataViewExists = useCallback( - async (indexName: string) => { - try { - const dvExists = await indexService.dataViewExists(dataViewsContract, indexName); - setDataViewExists(dvExists); - } catch (e) { - const error = extractErrorMessage(e); - toastNotifications.addDanger( - i18n.translate( - 'xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage', - { - defaultMessage: 'An error occurred checking if data view {dataView} exists: {error}', - values: { dataView: indexName, error }, - } - ) - ); - } - }, - [dataViewsContract, toastNotifications] - ); + const { error: dataViewExistsError, data: dataViewExists = items.length !== 1 } = + useDataViewExists(items); + + useEffect(() => { + if (dataViewExistsError !== null && items.length === 1) { + const config = items[0].config; + const indexName = Array.isArray(config.dest.index) ? config.dest.index[0] : config.dest.index; - const checkUserIndexPermission = useCallback(async () => { - try { - const userCanDelete = await indexService.canDeleteIndex(http); - if (userCanDelete) { - setUserCanDeleteIndex(true); - } - const canDeleteDataView = - capabilities.savedObjectsManagement.delete === true || - capabilities.indexPatterns.save === true; - setUserCanDeleteDataView(canDeleteDataView); - if (canDeleteDataView === false) { - setDeleteDataView(false); - } - } catch (e) { toastNotifications.addDanger( i18n.translate( - 'xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage', + 'xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage', { - defaultMessage: 'An error occurred checking if user can delete destination index', + defaultMessage: 'An error occurred checking if data view {dataView} exists: {error}', + values: { + dataView: indexName, + error: extractErrorMessage(dataViewExistsError), + }, } ) ); } - }, [http, toastNotifications, capabilities]); - - useEffect(() => { - checkUserIndexPermission(); - - // if user only deleting one transform - if (items.length === 1) { - const config = items[0].config; - const destinationIndex = Array.isArray(config.dest.index) - ? config.dest.index[0] - : config.dest.index; - checkDataViewExists(destinationIndex); - } else { - setDataViewExists(true); - } - }, [checkDataViewExists, checkUserIndexPermission, items]); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataViewExistsError]); return { userCanDeleteIndex, @@ -116,183 +86,87 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { }; }; -type SuccessCountField = keyof Omit; - export const useDeleteTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - return async (reqBody: DeleteTransformsRequestSchema) => { - const results = await api.deleteTransforms(reqBody); - - if (!isDeleteTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: DeleteTransformsRequestSchema) => + http.post(addInternalBasePath('delete_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate('xpack.transform.transformList.deleteTransformGenericErrorMessage', { defaultMessage: 'An error occurred calling the API endpoint to delete transforms.', }), text: toMountPoint( - , - { theme$: theme.theme$ } + , + { theme, i18n: i18nStart } ), - }); - return; - } - - const isBulk = Object.keys(results).length > 1; - const successCount: Record = { - transformDeleted: 0, - destIndexDeleted: 0, - destDataViewDeleted: 0, - }; - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const status = results[transformId]; - const destinationIndex = status.destinationIndex; - - // if we are only deleting one transform, show the success toast messages - if (!isBulk && status.transformDeleted) { - if (status.transformDeleted?.success) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.deleteTransformSuccessMessage', { - defaultMessage: 'Request to delete transform {transformId} acknowledged.', + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const status = results[transformId]; + const destinationIndex = status.destinationIndex; + + if (status.transformDeleted?.error) { + const error = status.transformDeleted.error.reason; + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.transformList.deleteTransformErrorMessage', { + defaultMessage: 'An error occurred deleting the transform {transformId}', values: { transformId }, - }) - ); + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - if (status.destIndexDeleted?.success) { - toastNotifications.addSuccess( - i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage', + + if (status.destIndexDeleted?.error) { + const error = status.destIndexDeleted.error.reason; + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage', { - defaultMessage: - 'Request to delete destination index {destinationIndex} acknowledged.', + defaultMessage: 'An error occurred deleting destination index {destinationIndex}', values: { destinationIndex }, } - ) - ); + ), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - if (status.destDataViewDeleted?.success) { - toastNotifications.addSuccess( - i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage', + + if (status.destDataViewDeleted?.error) { + const error = status.destDataViewDeleted.error.reason; + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage', { - defaultMessage: 'Request to delete data view {destinationIndex} acknowledged.', + defaultMessage: 'An error occurred deleting data view {destinationIndex}', values: { destinationIndex }, } - ) - ); + ), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - } else { - (Object.keys(successCount) as SuccessCountField[]).forEach((key) => { - if (status[key]?.success) { - successCount[key] = successCount[key] + 1; - } - }); - } - if (status.transformDeleted?.error) { - const error = status.transformDeleted.error.reason; - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.transformList.deleteTransformErrorMessage', { - defaultMessage: 'An error occurred deleting the transform {transformId}', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } - - if (status.destIndexDeleted?.error) { - const error = status.destIndexDeleted.error.reason; - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage', - { - defaultMessage: 'An error occurred deleting destination index {destinationIndex}', - values: { destinationIndex }, - } - ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } - - if (status.destDataViewDeleted?.error) { - const error = status.destDataViewDeleted.error.reason; - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage', - { - defaultMessage: 'An error occurred deleting data view {destinationIndex}', - values: { destinationIndex }, - } - ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); } } - } - // if we are deleting multiple transforms, combine the success messages - if (isBulk) { - if (successCount.transformDeleted > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkDeleteTransformSuccessMessage', { - defaultMessage: - 'Successfully deleted {count} {count, plural, one {transform} other {transforms}}.', - values: { count: successCount.transformDeleted }, - }) - ); - } - - if (successCount.destIndexDeleted > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage', { - defaultMessage: - 'Successfully deleted {count} destination {count, plural, one {index} other {indices}}.', - values: { count: successCount.destIndexDeleted }, - }) - ); - } - if (successCount.destDataViewDeleted > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage', { - defaultMessage: - 'Successfully deleted {count} destination data {count, plural, one {view} other {views}}.', - values: { count: successCount.destDataViewDeleted }, - }) - ); - } - } + refreshTransformList(); + }, + }); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_data_view_titles.ts b/x-pack/plugins/transform/public/app/hooks/use_get_data_view_titles.ts new file mode 100644 index 0000000000000..449ac30b9f513 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_data_view_titles.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetDataViewTitles = () => { + const { data } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_DATA_VIEW_TITLES], + () => data.dataViews.getTitles() + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_es_indices.ts b/x-pack/plugins/transform/public/app/hooks/use_get_es_indices.ts new file mode 100644 index 0000000000000..7da50c155d1be --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_es_indices.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { EsIndex } from '../../../common/types/es_index'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetEsIndices = () => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_ES_INDICES], + ({ signal }) => + http.get('/api/index_management/indices', { + version: '1', + signal, + }) + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_es_ingest_pipelines.ts b/x-pack/plugins/transform/public/app/hooks/use_get_es_ingest_pipelines.ts new file mode 100644 index 0000000000000..3f9784c64b652 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_es_ingest_pipelines.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { EsIngestPipeline } from '../../../common/types/es_ingest_pipeline'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetEsIngestPipelines = () => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_ES_INGEST_PIPELINES], + ({ signal }) => + http.get('/api/ingest_pipelines', { + version: '1', + signal, + }) + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_histograms_for_fields.ts b/x-pack/plugins/transform/public/app/hooks/use_get_histograms_for_fields.ts new file mode 100644 index 0000000000000..63ce23dba6214 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_histograms_for_fields.ts @@ -0,0 +1,66 @@ +/* + * Copyright 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { KBN_FIELD_TYPES } from '@kbn/field-types'; +import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils'; + +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { + FieldHistogramsRequestSchema, + FieldHistogramsResponseSchema, +} from '../../../common/api_schemas/field_histograms'; + +import { useAppDependencies } from '../app_dependencies'; + +import type { SavedSearchQuery } from './use_search_items'; + +export interface FieldHistogramRequestConfig { + fieldName: string; + type?: KBN_FIELD_TYPES; +} + +export const useGetHistogramsForFields = ( + dataViewTitle: string, + fields: FieldHistogramRequestConfig[], + query: string | SavedSearchQuery, + runtimeMappings?: FieldHistogramsRequestSchema['runtimeMappings'], + enabled?: boolean, + samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE +) => { + const { http } = useAppDependencies(); + + return useQuery( + [ + TRANSFORM_REACT_QUERY_KEYS.GET_HISTOGRAMS_FOR_FIELDS, + { + dataViewTitle, + fields, + query, + runtimeMappings, + samplerShardSize, + }, + ], + ({ signal }) => + http.post( + addInternalBasePath(`field_histograms/${dataViewTitle}`), + { + body: JSON.stringify({ + query, + fields, + samplerShardSize, + ...(runtimeMappings !== undefined ? { runtimeMappings } : {}), + }), + version: '1', + signal, + } + ), + { enabled } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_get_transform.tsx new file mode 100644 index 0000000000000..b9e3d977c71bd --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformsResponseSchema } from '../../../common/api_schemas/transforms'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransform = (transformId: TransformId, enabled?: boolean) => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM, transformId], + ({ signal }) => + http.get(addInternalBasePath(`transforms/${transformId}`), { + version: '1', + signal, + }), + { enabled } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform_audit_messages.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transform_audit_messages.ts new file mode 100644 index 0000000000000..3f7559a251275 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform_audit_messages.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformsAuditMessagesResponseSchema } from '../../../common/api_schemas/audit_messages'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import { TransformMessage } from '../../../common/types/messages'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformAuditMessages = ( + transformId: string, + sortField: keyof TransformMessage, + sortDirection: 'asc' | 'desc' +) => { + const { http } = useAppDependencies(); + + const query = { sortField, sortDirection }; + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_AUDIT_MESSAGES, transformId, query], + ({ signal }) => + http.get( + addInternalBasePath(`transforms/${transformId}/messages`), + { + query, + version: '1', + signal, + } + ) + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts new file mode 100644 index 0000000000000..60b31f9187fd6 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts @@ -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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformNodesResponseSchema } from '../../../common/api_schemas/transforms'; +import { + addInternalBasePath, + DEFAULT_REFRESH_INTERVAL_MS, + TRANSFORM_REACT_QUERY_KEYS, +} from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformNodes = ({ enabled } = { enabled: true }) => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_NODES], + async ({ signal }) => { + const transformNodes = await http.get( + addInternalBasePath('transforms/_nodes'), + { + version: '1', + signal, + } + ); + + return transformNodes.count; + }, + { + refetchInterval: DEFAULT_REFRESH_INTERVAL_MS, + enabled, + } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform_stats.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transform_stats.ts new file mode 100644 index 0000000000000..d2b9d32f25853 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform_stats.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformsStatsResponseSchema } from '../../../common/api_schemas/transforms_stats'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformStats = ( + transformId: TransformId, + enabled?: boolean, + refetchInterval?: number | false +) => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_STATS, transformId], + ({ signal }) => + http.get( + addInternalBasePath(`transforms/${transformId}/_stats`), + { + version: '1', + signal, + } + ), + { enabled, refetchInterval } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts index 55e3bc40360cf..f74f7a5774ded 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts @@ -5,75 +5,64 @@ * 2.0. */ +import { useQuery } from '@tanstack/react-query'; + import type { IHttpFetchError } from '@kbn/core-http-browser'; import { isDefined } from '@kbn/ml-is-defined'; -import { - isGetTransformNodesResponseSchema, - isGetTransformsResponseSchema, - isGetTransformsStatsResponseSchema, -} from '../../../common/api_schemas/type_guards'; -import { TRANSFORM_MODE } from '../../../common/constants'; -import { isTransformStats } from '../../../common/types/transform_stats'; +import type { GetTransformsResponseSchema } from '../../../common/api_schemas/transforms'; +import type { GetTransformsStatsResponseSchema } from '../../../common/api_schemas/transforms_stats'; import { - type TransformListRow, - refreshTransformList$, - REFRESH_TRANSFORM_LIST_STATE, -} from '../common'; + addInternalBasePath, + DEFAULT_REFRESH_INTERVAL_MS, + TRANSFORM_REACT_QUERY_KEYS, + TRANSFORM_MODE, +} from '../../../common/constants'; +import { isTransformStats } from '../../../common/types/transform_stats'; -import { useApi } from './use_api'; +import { type TransformListRow } from '../common'; +import { useAppDependencies } from '../app_dependencies'; import { TRANSFORM_ERROR_TYPE } from '../common/transform'; -export type GetTransforms = (forceRefresh?: boolean) => void; - -export const useGetTransforms = ( - setTransforms: React.Dispatch>, - setTransformNodes: React.Dispatch>, - setErrorMessage: React.Dispatch>, - setTransformIdsWithoutConfig: React.Dispatch>, - setIsInitialized: React.Dispatch>, - blockRefresh: boolean -): GetTransforms => { - const api = useApi(); - - let concurrentLoads = 0; - - const getTransforms = async (forceRefresh = false) => { - if (forceRefresh === true || blockRefresh === false) { - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.LOADING); - concurrentLoads++; - - if (concurrentLoads > 1) { - return; - } - - const fetchOptions = { asSystemRequest: true }; - const transformNodes = await api.getTransformNodes(); - const transformConfigs = await api.getTransforms(fetchOptions); - const transformStats = await api.getTransformsStats(fetchOptions); - - if ( - !isGetTransformsResponseSchema(transformConfigs) || - !isGetTransformsStatsResponseSchema(transformStats) || - !isGetTransformNodesResponseSchema(transformNodes) - ) { - // An error is followed immediately by setting the state to idle. - // This way we're able to treat ERROR as a one-time-event like REFRESH. - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.ERROR); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.IDLE); - setTransformNodes(0); - setTransforms([]); - - setIsInitialized(true); - - if (!isGetTransformsResponseSchema(transformConfigs)) { - setErrorMessage(transformConfigs); - } else if (!isGetTransformsStatsResponseSchema(transformStats)) { - setErrorMessage(transformStats); +interface UseGetTransformsResponse { + transforms: TransformListRow[]; + transformIds: string[]; + transformIdsWithoutConfig?: string[]; +} + +const getInitialData = (): UseGetTransformsResponse => ({ + transforms: [], + transformIds: [], +}); + +interface UseGetTransformsOptions { + enabled?: boolean; +} + +export const useGetTransforms = ({ enabled }: UseGetTransformsOptions = {}) => { + const { http } = useAppDependencies(); + + const { data = getInitialData(), ...rest } = useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORMS], + async ({ signal }) => { + const update = getInitialData(); + + const transformConfigs = await http.get( + addInternalBasePath('transforms'), + { + version: '1', + asSystemRequest: true, + signal, } - - return; - } + ); + const transformStats = await http.get( + addInternalBasePath(`transforms/_stats`), + { + version: '1', + asSystemRequest: true, + signal, + } + ); // There might be some errors with fetching certain transforms // For example, when task exists and is running but the config is deleted @@ -87,17 +76,12 @@ export const useGetTransforms = ( }) .filter(isDefined); - setTransformIdsWithoutConfig( - danglingTaskIdMatches.length > 0 ? danglingTaskIdMatches : undefined - ); - } else { - setTransformIdsWithoutConfig(undefined); + update.transformIdsWithoutConfig = + danglingTaskIdMatches.length > 0 ? danglingTaskIdMatches : undefined; } - const tableRows = transformConfigs.transforms.reduce((reducedtableRows, config) => { - const stats = isGetTransformsStatsResponseSchema(transformStats) - ? transformStats.transforms.find((d) => config.id === d.id) - : undefined; + update.transforms = transformConfigs.transforms.reduce((reducedtableRows, config) => { + const stats = transformStats.transforms.find((d) => config.id === d.id); // A newly created transform might not have corresponding stats yet. // If that's the case we just skip the transform and don't add it to the transform list yet. @@ -117,21 +101,15 @@ export const useGetTransforms = ( return reducedtableRows; }, [] as TransformListRow[]); - setTransformNodes(transformNodes.count); - setTransforms(tableRows); - setErrorMessage(undefined); - setIsInitialized(true); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.IDLE); - - concurrentLoads--; + update.transformIds = update.transforms.map(({ id }) => id); - if (concurrentLoads > 0) { - concurrentLoads = 0; - getTransforms(true); - return; - } + return update; + }, + { + enabled, + refetchInterval: DEFAULT_REFRESH_INTERVAL_MS, } - }; + ); - return getTransforms; + return { data, ...rest }; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transforms_preview.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transforms_preview.ts new file mode 100644 index 0000000000000..ae671912b9267 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transforms_preview.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { + PostTransformsPreviewRequestSchema, + PostTransformsPreviewResponseSchema, +} from '../../../common/api_schemas/transforms'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformsPreview = ( + obj: PostTransformsPreviewRequestSchema, + enabled?: boolean +) => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORMS_PREVIEW, obj], + ({ signal }) => + http.post(addInternalBasePath('transforms/_preview'), { + body: JSON.stringify(obj), + version: '1', + signal, + }), + { enabled } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx index 9da9fa8e5e782..ced29e2f8c17c 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React, { FC } from 'react'; -import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; - +import React, { type FC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import '@testing-library/jest-dom/extend-expect'; import { render, screen, waitFor } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { CoreSetup } from '@kbn/core/public'; import { DataGrid, type UseIndexDataReturnType } from '@kbn/ml-data-grid'; import type { RuntimeMappings } from '@kbn/ml-runtime-field-utils'; @@ -25,7 +25,6 @@ import { useIndexData } from './use_index_data'; jest.mock('../../shared_imports'); jest.mock('../app_dependencies'); -jest.mock('./use_api'); import { MlSharedContext } from '../__mocks__/shared_context'; @@ -45,13 +44,17 @@ const runtimeMappings: RuntimeMappings = { }, }; +const queryClient = new QueryClient(); + describe('Transform: useIndexData()', () => { test('dataView set triggers loading', async () => { const mlShared = await getMlSharedImports(); const wrapper: FC = ({ children }) => ( - - {children} - + + + {children} + + ); const { result, waitForNextUpdate } = renderHook( @@ -102,11 +105,13 @@ describe('Transform: with useIndexData()', () => { }; render( - - - - - + + + + + + + ); // Act @@ -142,11 +147,13 @@ describe('Transform: with useIndexData()', () => { }; render( - - - - - + + + + + + + ); // Act diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 3a6781615b70b..4534552f6b405 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { EuiDataGridColumn } from '@elastic/eui'; @@ -28,10 +28,6 @@ import { } from '@kbn/ml-data-grid'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; -import { - isEsSearchResponse, - isFieldHistogramsResponseSchema, -} from '../../../common/api_schemas/type_guards'; import { hasKeywordDuplicate, isKeywordDuplicate, @@ -44,7 +40,7 @@ import { useToastNotifications, useAppDependencies } from '../app_dependencies'; import type { StepDefineExposedState } from '../sections/create_transform/components/step_define/common'; import { SearchItems } from './use_search_items'; -import { useApi } from './use_api'; +import { useGetHistogramsForFields } from './use_get_histograms_for_fields'; import { useDataSearch } from './use_data_search'; export const useIndexData = ( @@ -52,7 +48,7 @@ export const useIndexData = ( query: TransformConfigQuery, combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'], timeRangeMs?: TimeRangeMs, - populatedFields?: Set | null + populatedFields?: string[] ): UseIndexDataReturnType => { const { analytics } = useAppDependencies(); @@ -61,13 +57,8 @@ export const useIndexData = ( const loadIndexDataStartTime = useRef(window.performance.now()); const indexPattern = useMemo(() => dataView.getIndexPattern(), [dataView]); - - const api = useApi(); - const dataSearch = useDataSearch(); const toastNotifications = useToastNotifications(); - const [dataViewFields, setDataViewFields] = useState(); - const baseFilterCriteria = buildBaseFilterCriteria( dataView.timeFieldName, timeRangeMs?.from, @@ -86,73 +77,73 @@ export const useIndexData = ( }, }; - useEffect(() => { - if (dataView.timeFieldName !== undefined && timeRangeMs === undefined) { - return; - } - const abortController = new AbortController(); - - // Fetch 500 random documents to determine populated fields. - // This is a workaround to avoid passing potentially thousands of unpopulated fields - // (for example, as part of filebeat/metricbeat/ECS based indices) - // to the data grid component which would significantly slow down the page. - const fetchDataGridSampleDocuments = async function () { - let populatedDataViewFields = populatedFields ? [...populatedFields] : []; - let isMissingFields = populatedDataViewFields.length === 0; - - // If populatedFields are not provided, make own request to calculate - if (populatedFields === undefined) { - setErrorMessage(''); - setStatus(INDEX_STATUS.LOADING); - - const esSearchRequest = { - index: indexPattern, - body: { - fields: ['*'], - _source: false, - query: { - function_score: { - query: defaultQuery, - random_score: {}, - }, - }, - size: 500, + // Fetch 500 random documents to determine populated fields. + // This is a workaround to avoid passing potentially thousands of unpopulated fields + // (for example, as part of filebeat/metricbeat/ECS based indices) + // to the data grid component which would significantly slow down the page. + const { + error: dataViewFieldsError, + data: dataViewFieldsData, + isError: dataViewFieldsIsError, + isLoading: dataViewFieldsIsLoading, + } = useDataSearch( + { + index: indexPattern, + body: { + fields: ['*'], + _source: false, + query: { + function_score: { + query: defaultQuery, + random_score: {}, }, - }; - - const resp = await dataSearch(esSearchRequest, abortController.signal); - - if (!isEsSearchResponse(resp)) { - setErrorMessage(getErrorMessage(resp)); - setStatus(INDEX_STATUS.ERROR); - return; - } - const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); - isMissingFields = resp.hits.hits.every((d) => typeof d.fields === 'undefined'); + }, + size: 500, + }, + }, + // Check whether fetching should be enabled + // If populatedFields are not provided, make own request to calculate + !Array.isArray(populatedFields) && + !(dataView.timeFieldName !== undefined && timeRangeMs === undefined) + ); - populatedDataViewFields = [...new Set(docs.map(Object.keys).flat(1))]; - } + useEffect(() => { + if (dataViewFieldsIsLoading && !dataViewFieldsIsError) { + setErrorMessage(''); + setStatus(INDEX_STATUS.LOADING); + } else if (dataViewFieldsError !== null) { + setErrorMessage(getErrorMessage(dataViewFieldsError)); + setStatus(INDEX_STATUS.ERROR); + } else if ( + !dataViewFieldsIsLoading && + !dataViewFieldsIsError && + dataViewFieldsData !== undefined + ) { const isCrossClusterSearch = indexPattern.includes(':'); - - // Get all field names for each returned doc and flatten it - // to a list of unique field names used across all docs. - const allDataViewFields = getFieldsFromKibanaIndexPattern(dataView); - const filteredDataViewFields = populatedDataViewFields - .filter((d) => allDataViewFields.includes(d)) - .sort(); + const isMissingFields = dataViewFieldsData.hits.hits.every( + (d) => typeof d.fields === 'undefined' + ); setCcsWarning(isCrossClusterSearch && isMissingFields); setStatus(INDEX_STATUS.LOADED); - setDataViewFields(filteredDataViewFields); - }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataViewFieldsData, dataViewFieldsError, dataViewFieldsIsError, dataViewFieldsIsLoading]); - fetchDataGridSampleDocuments(); + const dataViewFields = useMemo(() => { + let allPopulatedFields = Array.isArray(populatedFields) ? populatedFields : []; - return () => { - abortController.abort(); - }; + if (populatedFields === undefined && dataViewFieldsData) { + // Get all field names for each returned doc and flatten it + // to a list of unique field names used across all docs. + const docs = dataViewFieldsData.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); + allPopulatedFields = [...new Set(docs.map(Object.keys).flat(1))]; + } + + const allDataViewFields = getFieldsFromKibanaIndexPattern(dataView); + return allPopulatedFields.filter((d) => allDataViewFields.includes(d)).sort(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [timeRangeMs, populatedFields?.size]); + }, [dataViewFieldsData, populatedFields]); const columns: EuiDataGridColumn[] = useMemo(() => { if (typeof dataViewFields === 'undefined') { @@ -206,132 +197,113 @@ export const useIndexData = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify([query, timeRangeMs])]); - useEffect(() => { - if (typeof dataViewFields === 'undefined') { - return; - } - const abortController = new AbortController(); + const sort: EsSorting = sortingColumns.reduce((s, column) => { + s[column.id] = { order: column.direction }; + return s; + }, {} as EsSorting); - const fetchDataGridData = async function () { + const { + error: dataGridDataError, + data: dataGridData, + isError: dataGridDataIsError, + isLoading: dataGridDataIsLoading, + } = useDataSearch( + { + index: indexPattern, + body: { + fields: ['*'], + _source: false, + query: isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, + from: pagination.pageIndex * pagination.pageSize, + size: pagination.pageSize, + ...(Object.keys(sort).length > 0 ? { sort } : {}), + ...(isRuntimeMappings(combinedRuntimeMappings) + ? { runtime_mappings: combinedRuntimeMappings } + : {}), + }, + }, + // Check whether fetching should be enabled + dataViewFields !== undefined + ); + + useEffect(() => { + if (dataGridDataIsLoading && !dataGridDataIsError) { setErrorMessage(''); setStatus(INDEX_STATUS.LOADING); - - const sort: EsSorting = sortingColumns.reduce((s, column) => { - s[column.id] = { order: column.direction }; - return s; - }, {} as EsSorting); - - const esSearchRequest = { - index: indexPattern, - body: { - fields: ['*'], - _source: false, - query: isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, - from: pagination.pageIndex * pagination.pageSize, - size: pagination.pageSize, - ...(Object.keys(sort).length > 0 ? { sort } : {}), - ...(isRuntimeMappings(combinedRuntimeMappings) - ? { runtime_mappings: combinedRuntimeMappings } - : {}), - }, - }; - const resp = await dataSearch(esSearchRequest, abortController.signal); - - if (!isEsSearchResponse(resp)) { - setErrorMessage(getErrorMessage(resp)); - setStatus(INDEX_STATUS.ERROR); - return; - } - + } else if (dataGridDataError !== null) { + setErrorMessage(getErrorMessage(dataGridDataError)); + setStatus(INDEX_STATUS.ERROR); + } else if (!dataGridDataIsLoading && !dataGridDataIsError && dataGridData !== undefined) { const isCrossClusterSearch = indexPattern.includes(':'); - const isMissingFields = resp.hits.hits.every((d) => typeof d.fields === 'undefined'); + const isMissingFields = dataGridData.hits.hits.every((d) => typeof d.fields === 'undefined'); - const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); + const docs = dataGridData.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); setCcsWarning(isCrossClusterSearch && isMissingFields); setRowCountInfo({ - rowCount: typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total!.value, + rowCount: + typeof dataGridData.hits.total === 'number' + ? dataGridData.hits.total + : dataGridData.hits.total!.value, rowCountRelation: - typeof resp.hits.total === 'number' + typeof dataGridData.hits.total === 'number' ? ('eq' as estypes.SearchTotalHitsRelation) - : resp.hits.total!.relation, + : dataGridData.hits.total!.relation, }); setTableItems(docs); setStatus(INDEX_STATUS.LOADED); - }; - - fetchDataGridData(); - - return () => { - abortController.abort(); - }; - // custom comparison - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - indexPattern, + } // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify([ - query, - pagination, - sortingColumns, - dataViewFields, + }, [dataGridDataError, dataGridDataIsError, dataGridDataIsLoading]); + + const allDataViewFieldNames = new Set(dataView.fields.map((f) => f.name)); + const { error: histogramsForFieldsError, data: histogramsForFieldsData } = + useGetHistogramsForFields( + indexPattern, + columns + .filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + .map((cT) => { + // If a column field name has a corresponding keyword field, + // fetch the keyword field instead to be able to do aggregations. + const fieldName = cT.id; + return hasKeywordDuplicate(fieldName, allDataViewFieldNames) + ? { + fieldName: `${fieldName}.keyword`, + type: getFieldType(undefined), + } + : { + fieldName, + type: getFieldType(cT.schema), + }; + }), + isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, combinedRuntimeMappings, - timeRangeMs, - ]), - ]); + chartsVisible + ); useEffect(() => { - const fetchColumnChartsData = async function () { - const allDataViewFieldNames = new Set(dataView.fields.map((f) => f.name)); - const columnChartsData = await api.getHistogramsForFields( - indexPattern, - columns - .filter((cT) => dataGrid.visibleColumns.includes(cT.id)) - .map((cT) => { - // If a column field name has a corresponding keyword field, - // fetch the keyword field instead to be able to do aggregations. - const fieldName = cT.id; - return hasKeywordDuplicate(fieldName, allDataViewFieldNames) - ? { - fieldName: `${fieldName}.keyword`, - type: getFieldType(undefined), - } - : { - fieldName, - type: getFieldType(cT.schema), - }; - }), - isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, - combinedRuntimeMappings - ); - - if (!isFieldHistogramsResponseSchema(columnChartsData)) { - showDataGridColumnChartErrorMessageToast(columnChartsData, toastNotifications); - return; - } + if (histogramsForFieldsError !== null) { + showDataGridColumnChartErrorMessageToast(histogramsForFieldsError, toastNotifications); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [histogramsForFieldsError]); + useEffect(() => { + if (histogramsForFieldsData) { setColumnCharts( // revert field names with `.keyword` used to do aggregations to their original column name - columnChartsData.map((d) => ({ + histogramsForFieldsData.map((d) => ({ ...d, ...(isKeywordDuplicate(d.id, allDataViewFieldNames) ? { id: removeKeywordPostfix(d.id) } : {}), })) ); - }; - - if (chartsVisible) { - fetchColumnChartsData(); } // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - chartsVisible, - indexPattern, - // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify([query, dataGrid.visibleColumns, combinedRuntimeMappings, timeRangeMs]), - ]); + }, [histogramsForFieldsData]); const renderCellValue = useRenderCellValue(dataView, pagination, tableItems); diff --git a/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx index af6018c35cecc..9ecd1b8717243 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx @@ -6,31 +6,39 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { StartTransformsRequestSchema } from '../../../common/api_schemas/start_transforms'; -import { isStartTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { addInternalBasePath } from '../../../common/constants'; import { getErrorMessage } from '../../../common/utils/errors'; +import type { + ReauthorizeTransformsRequestSchema, + ReauthorizeTransformsResponseSchema, +} from '../../../common/api_schemas/reauthorize_transforms'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useReauthorizeTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - - return async (transformsInfo: StartTransformsRequestSchema) => { - const results = await api.reauthorizeTransforms(transformsInfo); - if (!isStartTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: ReauthorizeTransformsRequestSchema) => + http.post( + addInternalBasePath('reauthorize_transforms'), + { + body: JSON.stringify(reqBody), + version: '1', + } + ), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.stepCreateForm.reauthorizeTransformResponseSchemaErrorMessage', @@ -38,44 +46,37 @@ export const useReauthorizeTransforms = () => { defaultMessage: 'An error occurred calling the reauthorize transforms request.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const result = results[transformId]; - if (result.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.reauthorizeTransformSuccessMessage', { - defaultMessage: 'Request to reauthorize transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - title: i18n.translate( - 'xpack.transform.transformList.reauthorizeTransformErrorMessage', + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const result = results[transformId]; + if (!result.success) { + toastNotifications.addError( + new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - defaultMessage: 'An error occurred reauthorizing the transform {transformId}', - values: { transformId }, + title: i18n.translate( + 'xpack.transform.transformList.reauthorizeTransformErrorMessage', + { + defaultMessage: 'An error occurred reauthorizing the transform {transformId}', + values: { transformId }, + } + ), + toastMessage: result.error!.reason, } - ), - toastMessage: result.error!.reason, - }); + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_refresh_transform_list.ts b/x-pack/plugins/transform/public/app/hooks/use_refresh_transform_list.ts new file mode 100644 index 0000000000000..651886ba76f7b --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_refresh_transform_list.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQueryClient } from '@tanstack/react-query'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +export const useRefreshTransformList = () => { + const queryClient = useQueryClient(); + + return () => { + queryClient.invalidateQueries([TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_NODES]); + queryClient.invalidateQueries([TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORMS]); + queryClient.invalidateQueries([TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_AUDIT_MESSAGES]); + }; +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx index c33cec3f5b93f..1f415eae1ad20 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx @@ -6,107 +6,72 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; + import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; + import type { - ResetTransformStatus, ResetTransformsRequestSchema, + ResetTransformsResponseSchema, } from '../../../common/api_schemas/reset_transforms'; -import { isResetTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; +import { addInternalBasePath } from '../../../common/constants'; import { getErrorMessage } from '../../../common/utils/errors'; + import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$ } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; -type SuccessCountField = keyof Omit; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useResetTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - - return async (reqBody: ResetTransformsRequestSchema) => { - const results = await api.resetTransforms(reqBody); - if (!isResetTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: ResetTransformsRequestSchema) => + http.post(addInternalBasePath('reset_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate('xpack.transform.transformList.resetTransformGenericErrorMessage', { defaultMessage: 'An error occurred calling the API endpoint to reset transforms.', }), text: toMountPoint( - , - { theme$: theme.theme$ } + , + { + theme, + i18n: i18nStart, + } ), - }); - return; - } - - const isBulk = Object.keys(results).length > 1; - const successCount: Record = { - transformReset: 0, - }; - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const status = results[transformId]; + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const status = results[transformId]; - // if we are only resetting one transform, show the success toast messages - if (!isBulk && status.transformReset) { - if (status.transformReset?.success) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.resetTransformSuccessMessage', { - defaultMessage: 'Request to reset transform {transformId} acknowledged.', + if (status.transformReset?.error) { + const error = status.transformReset.error.reason; + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.transformList.resetTransformErrorMessage', { + defaultMessage: 'An error occurred resetting the transform {transformId}', values: { transformId }, - }) - ); + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - } else { - (Object.keys(successCount) as SuccessCountField[]).forEach((key) => { - if (status[key]?.success) { - successCount[key] = successCount[key] + 1; - } - }); - } - if (status.transformReset?.error) { - const error = status.transformReset.error.reason; - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.transformList.resetTransformErrorMessage', { - defaultMessage: 'An error occurred resetting the transform {transformId}', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); } } - } - // if we are deleting multiple transforms, combine the success messages - if (isBulk) { - if (successCount.transformReset > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkResetTransformSuccessMessage', { - defaultMessage: - 'Successfully reset {count} {count, plural, one {transform} other {transforms}}.', - values: { count: successCount.transformReset }, - }) - ); - } - } + refreshTransformList(); + }, + }); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx index 8454580867391..bb568673a5758 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx @@ -6,31 +6,38 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { ScheduleNowTransformsRequestSchema } from '../../../common/api_schemas/schedule_now_transforms'; -import { isScheduleNowTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; - +import { addInternalBasePath } from '../../../common/constants'; +import type { + ScheduleNowTransformsRequestSchema, + ScheduleNowTransformsResponseSchema, +} from '../../../common/api_schemas/schedule_now_transforms'; import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useScheduleNowTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - return async (transformsInfo: ScheduleNowTransformsRequestSchema) => { - const results = await api.scheduleNowTransforms(transformsInfo); - - if (!isScheduleNowTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: ScheduleNowTransformsRequestSchema) => + http.post( + addInternalBasePath('schedule_now_transforms'), + { + body: JSON.stringify(reqBody), + version: '1', + } + ), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.stepCreateForm.scheduleNowTransformResponseSchemaErrorMessage', @@ -39,46 +46,38 @@ export const useScheduleNowTransforms = () => { 'An error occurred calling the request to schedule the transform to process data instantly.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const result = results[transformId]; - if (result.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.scheduleNowTransformSuccessMessage', { - defaultMessage: - 'Request to schedule transform {transformId} to process data instantly acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - title: i18n.translate( - 'xpack.transform.transformList.scheduleNowTransformErrorMessage', + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const result = results[transformId]; + if (!result.success) { + toastNotifications.addError( + new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - defaultMessage: - 'An error occurred scheduling transform {transformId} to process data instantly.', - values: { transformId }, + title: i18n.translate( + 'xpack.transform.transformList.scheduleNowTransformErrorMessage', + { + defaultMessage: + 'An error occurred scheduling transform {transformId} to process data instantly.', + values: { transformId }, + } + ), + toastMessage: result.error!.reason, } - ), - toastMessage: result.error!.reason, - }); + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx index e4d9bdfe431a9..104c3145fc259 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx @@ -6,31 +6,35 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { StartTransformsRequestSchema } from '../../../common/api_schemas/start_transforms'; -import { isStartTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; - +import { addInternalBasePath } from '../../../common/constants'; +import type { + StartTransformsRequestSchema, + StartTransformsResponseSchema, +} from '../../../common/api_schemas/start_transforms'; import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useStartTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - return async (transformsInfo: StartTransformsRequestSchema) => { - const results = await api.startTransforms(transformsInfo); - - if (!isStartTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: StartTransformsRequestSchema) => + http.post(addInternalBasePath('start_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.stepCreateForm.startTransformResponseSchemaErrorMessage', @@ -38,41 +42,34 @@ export const useStartTransforms = () => { defaultMessage: 'An error occurred calling the start transforms request.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const result = results[transformId]; - if (result.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.startTransformSuccessMessage', { - defaultMessage: 'Request to start transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - title: i18n.translate('xpack.transform.transformList.startTransformErrorMessage', { - defaultMessage: 'An error occurred starting the transform {transformId}', - values: { transformId }, - }), - toastMessage: result.error!.reason, - }); + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const result = results[transformId]; + if (!result.success) { + toastNotifications.addError( + new Error(JSON.stringify(result.error!.caused_by, null, 2)), + { + title: i18n.translate('xpack.transform.transformList.startTransformErrorMessage', { + defaultMessage: 'An error occurred starting the transform {transformId}', + values: { transformId }, + }), + toastMessage: result.error!.reason, + } + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx index 39d009e471180..564b17feac3f9 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx @@ -6,31 +6,36 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { StopTransformsRequestSchema } from '../../../common/api_schemas/stop_transforms'; -import { isStopTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { addInternalBasePath } from '../../../common/constants'; +import type { + StopTransformsRequestSchema, + StopTransformsResponseSchema, +} from '../../../common/api_schemas/stop_transforms'; import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useStopTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - - return async (transformsInfo: StopTransformsRequestSchema) => { - const results = await api.stopTransforms(transformsInfo); - if (!isStopTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: StopTransformsRequestSchema) => + http.post(addInternalBasePath('stop_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.transformList.stopTransformResponseSchemaErrorMessage', @@ -38,39 +43,29 @@ export const useStopTransforms = () => { defaultMessage: 'An error occurred called the stop transforms request.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - if (results[transformId].success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.stopTransformSuccessMessage', { - defaultMessage: 'Request to stop data frame transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addDanger( - i18n.translate('xpack.transform.transformList.stopTransformErrorMessage', { - defaultMessage: 'An error occurred stopping the data frame transform {transformId}', - values: { transformId }, - }) - ); + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + if (!results[transformId].success) { + toastNotifications.addDanger( + i18n.translate('xpack.transform.transformList.stopTransformErrorMessage', { + defaultMessage: 'An error occurred stopping the data frame transform {transformId}', + values: { transformId }, + }) + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_transform_capabilities.ts b/x-pack/plugins/transform/public/app/hooks/use_transform_capabilities.ts new file mode 100644 index 0000000000000..f497da3bd51de --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_transform_capabilities.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + getInitialTransformCapabilities, + isTransformCapabilities, +} from '../../../common/types/capabilities'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useTransformCapabilities = () => { + const { application } = useAppDependencies(); + + if (isTransformCapabilities(application?.capabilities?.transform)) { + return application.capabilities.transform; + } + + return getInitialTransformCapabilities(); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts index f96a0f72194c6..0871dd7877c14 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts @@ -29,14 +29,13 @@ import { } from '@kbn/ml-data-grid'; import type { PreviewMappingsProperties } from '../../../common/api_schemas/transforms'; -import { isPostTransformsPreviewResponseSchema } from '../../../common/api_schemas/type_guards'; import { getErrorMessage } from '../../../common/utils/errors'; import { getPreviewTransformRequestBody, type TransformConfigQuery } from '../common'; import { SearchItems } from './use_search_items'; -import { useApi } from './use_api'; +import { useGetTransformsPreview } from './use_get_transforms_preview'; import { StepDefineExposedState } from '../sections/create_transform/components/step_define'; import { isLatestPartialRequest, @@ -111,7 +110,6 @@ export const useTransformConfigData = ( ): UseIndexDataReturnType => { const [previewMappingsProperties, setPreviewMappingsProperties] = useState({}); - const api = useApi(); // Filters mapping properties of type `object`, which get returned for nested field parents. const columnKeys = Object.keys(previewMappingsProperties).filter( @@ -147,84 +145,100 @@ export const useTransformConfigData = ( tableItems, } = dataGrid; - const getPreviewData = async () => { - if (!validationStatus.isValid) { + const previewRequest = useMemo( + () => + getPreviewTransformRequestBody( + dataView, + query, + requestPayload, + combinedRuntimeMappings, + timeRangeMs + ), + [dataView, query, requestPayload, combinedRuntimeMappings, timeRangeMs] + ); + + const { + error: previewError, + data: previewData, + isError, + isLoading, + } = useGetTransformsPreview(previewRequest, validationStatus.isValid); + + useEffect(() => { + if (isLoading) { + setErrorMessage(''); + setNoDataMessage(''); + setStatus(INDEX_STATUS.LOADING); + } else if (isError) { + setErrorMessage(getErrorMessage(previewError)); setTableItems([]); setRowCountInfo({ rowCount: 0, rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, }); - setNoDataMessage(validationStatus.errorMessage!); - return; + setPreviewMappingsProperties({}); + setStatus(INDEX_STATUS.ERROR); + } else if (!isLoading && !isError && previewData !== undefined) { + // To improve UI performance with a latest configuration for indices with a large number + // of fields, we reduce the number of available columns to those populated with values. + + // 1. Flatten the returned object structure object documents to match mapping properties + const docs = previewData.preview.map(getFlattenedObject); + + // 2. Get all field names for each returned doc and flatten it + // to a list of unique field names used across all docs. + const populatedFields = [...new Set(docs.map(Object.keys).flat(1))]; + + // 3. Filter mapping properties by populated fields + let populatedProperties: PreviewMappingsProperties = Object.entries( + previewData.generated_dest_index.mappings.properties + ) + .filter(([key]) => populatedFields.includes(key)) + .reduce( + (p, [key, value]) => ({ + ...p, + [key]: value, + }), + {} + ); + + populatedProperties = getCombinedProperties(populatedProperties, docs); + + setTableItems(docs); + setRowCountInfo({ + rowCount: docs.length, + rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, + }); + setPreviewMappingsProperties(populatedProperties); + setStatus(INDEX_STATUS.LOADED); + + if (docs.length === 0) { + setNoDataMessage( + i18n.translate('xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', { + defaultMessage: + 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', + }) + ); + } else { + setNoDataMessage(''); + } } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isError, isLoading, previewData]); - setErrorMessage(''); - setNoDataMessage(''); - setStatus(INDEX_STATUS.LOADING); - - const previewRequest = getPreviewTransformRequestBody( - dataView, - query, - requestPayload, - combinedRuntimeMappings, - timeRangeMs - ); - const resp = await api.getTransformsPreview(previewRequest); - - if (!isPostTransformsPreviewResponseSchema(resp)) { - setErrorMessage(getErrorMessage(resp)); + useEffect(() => { + if (!validationStatus.isValid) { setTableItems([]); setRowCountInfo({ rowCount: 0, rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, }); - setPreviewMappingsProperties({}); - setStatus(INDEX_STATUS.ERROR); - return; - } - - // To improve UI performance with a latest configuration for indices with a large number - // of fields, we reduce the number of available columns to those populated with values. - - // 1. Flatten the returned object structure object documents to match mapping properties - const docs = resp.preview.map(getFlattenedObject); - - // 2. Get all field names for each returned doc and flatten it - // to a list of unique field names used across all docs. - const populatedFields = [...new Set(docs.map(Object.keys).flat(1))]; - - // 3. Filter mapping properties by populated fields - let populatedProperties: PreviewMappingsProperties = Object.entries( - resp.generated_dest_index.mappings.properties - ) - .filter(([key]) => populatedFields.includes(key)) - .reduce( - (p, [key, value]) => ({ - ...p, - [key]: value, - }), - {} - ); - - populatedProperties = getCombinedProperties(populatedProperties, docs); - - setTableItems(docs); - setRowCountInfo({ - rowCount: docs.length, - rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, - }); - setPreviewMappingsProperties(populatedProperties); - setStatus(INDEX_STATUS.LOADED); - - if (docs.length === 0) { - setNoDataMessage( - i18n.translate('xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', { - defaultMessage: - 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', - }) - ); + setNoDataMessage(validationStatus.errorMessage!); } - }; + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [validationStatus.isValid]); useEffect(() => { resetPagination(); @@ -232,15 +246,6 @@ export const useTransformConfigData = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(query)]); - useEffect(() => { - getPreviewData(); - // custom comparison - /* eslint-disable react-hooks/exhaustive-deps */ - }, [ - dataView.getIndexPattern(), - JSON.stringify([requestPayload, query, combinedRuntimeMappings, timeRangeMs]), - ]); - if (sortingColumns.length > 0) { const sortingColumnsWithTypes = sortingColumns.map((c) => { // Since items might contain undefined/null values, we want to accurate find the data type @@ -291,13 +296,7 @@ export const useTransformConfigData = ( return cellValue; }; - }, [ - pageData, - pagination.pageIndex, - pagination.pageSize, - previewMappingsProperties, - formatHumanReadableDateTimeSeconds, - ]); + }, [pageData, pagination.pageIndex, pagination.pageSize, previewMappingsProperties]); return { ...dataGrid, diff --git a/x-pack/plugins/transform/public/app/hooks/use_update_transform.ts b/x-pack/plugins/transform/public/app/hooks/use_update_transform.ts new file mode 100644 index 0000000000000..3859f9a8353f0 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_update_transform.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation } from '@tanstack/react-query'; + +import type { + PostTransformsUpdateRequestSchema, + PostTransformsUpdateResponseSchema, +} from '../../../common/api_schemas/update_transforms'; +import { addInternalBasePath } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; + +import { useAppDependencies } from '../app_dependencies'; + +import { useRefreshTransformList } from './use_refresh_transform_list'; + +export const useUpdateTransform = ( + transformId: TransformId, + transformConfig: PostTransformsUpdateRequestSchema +) => { + const { http } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); + + const mutation = useMutation({ + mutationFn: () => + http.post( + addInternalBasePath(`transforms/${transformId}/_update`), + { + body: JSON.stringify(transformConfig), + version: '1', + } + ), + onSuccess: () => refreshTransformList(), + }); + + return mutation.mutate; +}; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx deleted file mode 100644 index 02bbe4e40a969..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx +++ /dev/null @@ -1,83 +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, { createContext } from 'react'; -import { useQuery } from '@tanstack/react-query'; - -import type { IHttpFetchError } from '@kbn/core-http-browser'; - -import type { Privileges } from '../../../../../common/types/privileges'; - -import { - type PrivilegesAndCapabilities, - type TransformCapabilities, - INITIAL_CAPABILITIES, -} from '../../../../../common/privilege/has_privilege_factory'; - -import { useAppDependencies } from '../../../app_dependencies'; - -interface Authorization { - isLoading: boolean; - apiError: Error | null; - privileges: Privileges; - capabilities: TransformCapabilities; -} - -const initialValue: Authorization = { - isLoading: true, - apiError: null, - privileges: { - hasAllPrivileges: false, - missingPrivileges: {}, - }, - capabilities: INITIAL_CAPABILITIES, -}; - -export const AuthorizationContext = createContext({ ...initialValue }); - -interface Props { - privilegesEndpoint: { path: string; version: string }; - children: React.ReactNode; -} - -export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => { - const { http } = useAppDependencies(); - - const { path, version } = privilegesEndpoint; - - const { - isLoading, - error, - data: privilegesData, - } = useQuery( - ['transform-privileges-and-capabilities'], - async ({ signal }) => { - return await http.fetch(path, { - version, - method: 'GET', - signal, - }); - } - ); - - const value = { - isLoading, - privileges: - isLoading || privilegesData === undefined - ? { ...initialValue.privileges } - : privilegesData.privileges, - capabilities: - isLoading || privilegesData === undefined - ? { ...INITIAL_CAPABILITIES } - : privilegesData.capabilities, - apiError: error ? error : null, - }; - - return ( - {children} - ); -}; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/index.ts b/x-pack/plugins/transform/public/app/lib/authorization/components/index.ts deleted file mode 100644 index cb0f248efc165..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { createCapabilityFailureMessage } from '../../../../../common/privilege/has_privilege_factory'; -export { AuthorizationProvider, AuthorizationContext } from './authorization_provider'; -export { PrivilegesWrapper } from './with_privileges'; -export { NotAuthorizedSection } from './not_authorized_section'; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/not_authorized_section.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/not_authorized_section.tsx deleted file mode 100644 index 1ab942d59c787..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/not_authorized_section.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 { EuiPageTemplate } from '@elastic/eui'; - -interface Props { - title: React.ReactNode; - message: React.ReactNode | string; -} - -export const NotAuthorizedSection = ({ title, message }: Props) => ( - {title}} - body={

    {message}

    } - /> -); diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx deleted file mode 100644 index a8069b2156239..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx +++ /dev/null @@ -1,125 +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, { useContext, FC } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { MissingPrivileges } from '../../../../../common/types/privileges'; -import { SectionLoading } from '../../../components'; -import { AuthorizationContext } from './authorization_provider'; -import { NotAuthorizedSection } from './not_authorized_section'; -import { - hasPrivilegeFactory, - toArray, - Privilege, -} from '../../../../../common/privilege/has_privilege_factory'; - -interface Props { - /** - * Each required privilege must have the format "section.privilege". - * To indicate that *all* privileges from a section are required, we can use the asterix - * e.g. "index.*" - */ - privileges: string | string[]; - children: (childrenProps: { - isLoading: boolean; - hasPrivileges: boolean; - privilegesMissing: MissingPrivileges; - }) => JSX.Element; -} - -export const WithPrivileges = ({ privileges: requiredPrivileges, children }: Props) => { - const { isLoading, privileges } = useContext(AuthorizationContext); - - const privilegesToArray: Privilege[] = toArray(requiredPrivileges).map((p) => { - const [section, privilege] = p.split('.'); - if (!privilege) { - // Oh! we forgot to use the dot "." notation. - throw new Error('Required privilege must have the format "section.privilege"'); - } - return [section, privilege]; - }); - - const hasPrivilege = hasPrivilegeFactory(privileges); - const hasPrivileges = isLoading ? false : privilegesToArray.every(hasPrivilege); - - const privilegesMissing = privilegesToArray.reduce((acc, [section, privilege]) => { - if (privilege === '*') { - acc[section] = privileges.missingPrivileges[section] || []; - } else if ( - privileges.missingPrivileges[section] && - privileges.missingPrivileges[section]!.includes(privilege) - ) { - const missing: string[] = acc[section] || []; - acc[section] = [...missing, privilege]; - } - - return acc; - }, {} as MissingPrivileges); - - return children({ isLoading, hasPrivileges, privilegesMissing }); -}; - -interface MissingClusterPrivilegesProps { - missingPrivileges: string; - privilegesCount: number; -} - -const MissingClusterPrivileges: FC = ({ - missingPrivileges, - privilegesCount, -}) => ( - - } - message={ - - } - /> -); - -export const PrivilegesWrapper: FC<{ privileges: string | string[] }> = ({ - children, - privileges, -}) => ( - - {({ isLoading, hasPrivileges, privilegesMissing }) => { - if (isLoading) { - return ( - - - - ); - } - - if (!hasPrivileges) { - return ( - - ); - } - - return <>{children}; - }} - -); diff --git a/x-pack/plugins/transform/public/app/lib/authorization/index.ts b/x-pack/plugins/transform/public/app/lib/authorization/index.ts deleted file mode 100644 index e78d12e73a8ea..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './components'; diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts index cb4032455adf2..86b2a297ed636 100644 --- a/x-pack/plugins/transform/public/app/mount_management_section.ts +++ b/x-pack/plugins/transform/public/app/mount_management_section.ts @@ -22,7 +22,8 @@ const localStorage = new Storage(window.localStorage); export async function mountManagementSection( coreSetup: CoreSetup, - params: ManagementAppMountParams + params: ManagementAppMountParams, + isServerless: boolean ) { const { element, setBreadcrumbs, history } = params; const { http, getStartServices } = coreSetup; @@ -92,7 +93,7 @@ export async function mountManagementSection( contentManagement, }; - const unmountAppCallback = renderApp(element, appDependencies); + const unmountAppCallback = renderApp(element, appDependencies, isServerless); return () => { docTitle.reset(); diff --git a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx index 63964bc422130..47fa44a53d182 100644 --- a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx @@ -13,16 +13,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiCallOut, EuiPageTemplate, EuiSpacer } from '@elastic/eui'; -import { isHttpFetchError } from '@kbn/core-http-browser'; -import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants'; + import { TransformConfigUnion } from '../../../../common/types/transform'; -import { useApi } from '../../hooks/use_api'; +import { useGetTransform } from '../../hooks'; import { useDocumentationLinks } from '../../hooks/use_documentation_links'; import { useSearchItems } from '../../hooks/use_search_items'; import { BREADCRUMB_SECTION, breadcrumbService, docTitleService } from '../../services/navigation'; -import { PrivilegesWrapper } from '../../lib/authorization'; +import { CapabilitiesWrapper } from '../../components/capabilities_wrapper'; import { Wizard } from '../create_transform/components/wizard'; import { overrideTransformForCloning } from '../../common/transform'; @@ -39,8 +38,6 @@ export const CloneTransformSection: FC = ({ match, location }) => { docTitleService.setTitle('createTransform'); }, []); - const api = useApi(); - const { esTransform } = useDocumentationLinks(); const transformId = match.params.transformId; @@ -50,52 +47,55 @@ export const CloneTransformSection: FC = ({ match, location }) => { const [isInitialized, setIsInitialized] = useState(false); const { error: searchItemsError, searchItems, setSavedObjectId } = useSearchItems(undefined); - const fetchTransformConfig = async () => { + useEffect(() => { + if (dataViewId === undefined) { + setErrorMessage( + i18n.translate('xpack.transform.clone.fetchErrorPromptText', { + defaultMessage: 'Could not fetch the Kibana data view ID.', + }) + ); + } else { + setSavedObjectId(dataViewId); + } + }, [dataViewId, setSavedObjectId]); + + useEffect(() => { if (searchItemsError !== undefined) { setTransformConfig(undefined); setErrorMessage(searchItemsError); setIsInitialized(true); - return; } + }, [searchItemsError]); + + const { data: transformConfigs, error } = useGetTransform( + transformId, + searchItemsError === undefined + ); - const transformConfigs = await api.getTransform(transformId); - if (isHttpFetchError(transformConfigs)) { + useEffect(() => { + if (error !== null && error.message !== errorMessage) { setTransformConfig(undefined); - setErrorMessage(transformConfigs.message); + setErrorMessage(error.message); setIsInitialized(true); return; } - try { - if (dataViewId === undefined) { - throw new Error( - i18n.translate('xpack.transform.clone.fetchErrorPromptText', { - defaultMessage: 'Could not fetch the Kibana data view ID.', - }) - ); - } - - setSavedObjectId(dataViewId); - - setTransformConfig(overrideTransformForCloning(transformConfigs.transforms[0])); - setErrorMessage(undefined); - setIsInitialized(true); - } catch (e) { - setTransformConfig(undefined); - if (e.message !== undefined) { - setErrorMessage(e.message); - } else { - setErrorMessage(JSON.stringify(e, null, 2)); + if (transformConfigs !== undefined) { + try { + setTransformConfig(overrideTransformForCloning(transformConfigs.transforms[0])); + setErrorMessage(undefined); + setIsInitialized(true); + } catch (e) { + setTransformConfig(undefined); + if (e.message !== undefined) { + setErrorMessage(e.message); + } else { + setErrorMessage(JSON.stringify(e, null, 2)); + } + setIsInitialized(true); } - setIsInitialized(true); } - }; - - useEffect(() => { - fetchTransformConfig(); - // The effect should only be called once. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [error, errorMessage, transformConfigs]); const docsLink = ( = ({ match, location }) => { ); return ( - + = ({ match, location }) => { )} - + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx index 900af603266b8..a125da52b0cae 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSwitch } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx index 43c6684a5a2bc..cb21e7026f27b 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiSwitch } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx index 2ee8bc9995df6..c53c5dae0b4ad 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SwitchModal } from './switch_modal'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx index ff08ab37bb3e6..37dd6dd01f98e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx index db617690efc5f..8b76b8d7d7e1a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiButton, EuiButtonIcon, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx index 665c9986e6e69..4fea2f43bd5db 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/agg_label_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/agg_label_form.test.tsx.snap deleted file mode 100644 index 09056b8529f16..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/agg_label_form.test.tsx.snap +++ /dev/null @@ -1,72 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Date histogram aggregation 1`] = ` - - - - - the-group-by-agg-name - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_form.test.tsx.snap deleted file mode 100644 index 89b54e6d0a22f..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_form.test.tsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_summary.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_summary.test.tsx.snap deleted file mode 100644 index f08d7fab7c829..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_summary.test.tsx.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - -
    - the-agg -
    -
    - -
    -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/popover_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/popover_form.test.tsx.snap deleted file mode 100644 index f7b4e836ee784..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/popover_form.test.tsx.snap +++ /dev/null @@ -1,48 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Aggregation Minimal initialization 1`] = ` - - - - - - - Apply - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx index fc9f91f96b3ff..b036016314754 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { AggName } from '../../../../../../common/types/aggregations'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -31,8 +31,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toBe('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx index 1525aa1a3320b..205fb93479339 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -29,8 +29,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toBe('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx index 71d728798c8fb..d1078f4d5a87a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -26,8 +26,8 @@ describe('Transform: ', () => { list: { 'the-agg': item }, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toBe('the-agg'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx index 0587d4f0e1c46..88cdad407bd62 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { shallow } from 'enzyme'; import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import { AggName } from '../../../../../../common/types/aggregations'; @@ -29,7 +28,7 @@ describe('Transform: Aggregation ', () => { const otherAggNames: AggName[] = []; const onChange = (item: PivotAggsConfig) => {}; - const wrapper = shallow( + const { getByTestId } = render( ', () => { /> ); - expect(wrapper).toMatchSnapshot(); + const input = getByTestId('transformAggName'); + expect(input).toHaveValue('the-group-by-agg-name'); }); test('preserves the field for unsupported aggs', async () => { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx index a57d83b75aa10..2f5672d3ec592 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiSwitch } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_form.test.tsx.snap deleted file mode 100644 index 0bb11827655e0..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_form.test.tsx.snap +++ /dev/null @@ -1,234 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Date histogram aggregation 1`] = ` - - - - the-group-by-agg-name - - - - - 1m - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformIntervalFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - -`; - -exports[`Transform: Histogram aggregation 1`] = ` - - - - the-group-by-agg-name - - - - - 100 - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformIntervalFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - -`; - -exports[`Transform: Terms aggregation 1`] = ` - - - - the-group-by-agg-name - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformIntervalFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_summary.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_summary.test.tsx.snap deleted file mode 100644 index b1d3afeff412c..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_summary.test.tsx.snap +++ /dev/null @@ -1,77 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Date histogram aggregation 1`] = ` - - - - the-options-data-id - - - - - 1m - - - -`; - -exports[`Transform: Histogram aggregation 1`] = ` - - - - the-options-data-id - - - - - 100 - - - -`; - -exports[`Transform: Terms aggregation 1`] = ` - - - - the-options-data-id - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_form.test.tsx.snap deleted file mode 100644 index da1e9a79680ad..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_form.test.tsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_summary.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_summary.test.tsx.snap deleted file mode 100644 index 724433a80d3a8..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_summary.test.tsx.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap deleted file mode 100644 index 9c9fb59eea4b1..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Group By Minimal initialization 1`] = ` - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx index 09ec34f90d751..2edcfc7a9bd2c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -29,9 +29,9 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); test('Histogram aggregation', () => { @@ -50,9 +50,9 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); test('Terms aggregation', () => { @@ -70,8 +70,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx index 689c27f5f81be..5c03d3bc8f320 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -26,9 +26,9 @@ describe('Transform: ', () => { optionsDataId: 'the-options-data-id', }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); test('Histogram aggregation', () => { @@ -44,9 +44,9 @@ describe('Transform: ', () => { optionsDataId: 'the-options-data-id', }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); test('Terms aggregation', () => { @@ -61,8 +61,8 @@ describe('Transform: ', () => { optionsDataId: 'the-options-data-id', }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx index 61a11a8b551b3..eb942cad63b01 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -27,8 +27,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx index 201b36a41b09c..eadddd8356e42 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -24,8 +24,8 @@ describe('Transform: ', () => { list: { 'the-options-data-id': item }, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx index d5b1139e6cdae..4ef6cbbaf51be 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -101,7 +101,7 @@ describe('Transform: Group By ', () => { appName: 'the-test-app', }; - const wrapper = shallow( + const { getByDisplayValue } = render( ', () => { ); - expect(wrapper.find(PopoverForm)).toMatchSnapshot(); + expect(getByDisplayValue('the-agg-name')).toBeInTheDocument(); + expect(getByDisplayValue('1m')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx index e0a52978c0b4a..42b9d556f9dc2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiCode, EuiInputPopover } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx index 2e785c2d6680b..e2fa8912fc932 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { StepCreateForm, StepCreateFormProps } from './step_create_form'; @@ -16,6 +17,7 @@ jest.mock('../../../../app_dependencies'); describe('Transform: ', () => { test('Minimal initialization', () => { // Arrange + const queryClient = new QueryClient(); const props: StepCreateFormProps = { createDataView: false, transformId: 'the-transform-id', @@ -35,7 +37,11 @@ describe('Transform: ', () => { onChange() {}, }; - const { getByText } = render(); + const { getByText } = render( + + + + ); // Act // Assert diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index d61b18632cef4..3c78757a6f257 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -24,25 +24,19 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; import { DuplicateDataViewError } from '@kbn/data-plugin/public'; import type { RuntimeField } from '@kbn/data-views-plugin/common'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { PutTransformsResponseSchema } from '../../../../../../common/api_schemas/transforms'; -import { - isGetTransformsStatsResponseSchema, - isPutTransformsResponseSchema, - isStartTransformsResponseSchema, -} from '../../../../../../common/api_schemas/type_guards'; import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants'; import { getErrorMessage } from '../../../../../../common/utils/errors'; import { getTransformProgress } from '../../../../common'; -import { useApi } from '../../../../hooks/use_api'; +import { useCreateTransform, useGetTransformStats, useStartTransforms } from '../../../../hooks'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { RedirectToTransformManagement } from '../../../../common/navigation'; import { ToastNotificationText } from '../../../../components'; @@ -92,11 +86,10 @@ export const StepCreateForm: FC = React.memo( ); const [discoverLink, setDiscoverLink] = useState(); - const deps = useAppDependencies(); - const { share } = deps; - const dataViews = deps.data.dataViews; const toastNotifications = useToastNotifications(); - const isDiscoverAvailable = deps.application.capabilities.discover?.show ?? false; + const { application, data, i18n: i18nStart, share, theme } = useAppDependencies(); + const dataViews = data.dataViews; + const isDiscoverAvailable = application.capabilities.discover?.show ?? false; useEffect(() => { let unmounted = false; @@ -128,104 +121,38 @@ export const StepCreateForm: FC = React.memo( // eslint-disable-next-line react-hooks/exhaustive-deps }, [created, started, dataViewId]); - const { overlays, theme } = useAppDependencies(); - const api = useApi(); + const startTransforms = useStartTransforms(); + const createTransform = useCreateTransform(); - async function createTransform() { + function createTransformHandler(startAfterCreation = false) { setLoading(true); - const resp = await api.createTransform(transformId, transformConfig); - - if (!isPutTransformsResponseSchema(resp) || resp.errors.length > 0) { - let respErrors: - | PutTransformsResponseSchema['errors'] - | PutTransformsResponseSchema['errors'][number] - | undefined; - - if (isPutTransformsResponseSchema(resp) && resp.errors.length > 0) { - respErrors = resp.errors.length === 1 ? resp.errors[0] : resp.errors; + createTransform( + { transformId, transformConfig }, + { + onError: () => setCreated(false), + onSuccess: () => { + setCreated(true); + if (createDataView) { + createKibanaDataView(); + } + if (startAfterCreation) { + startTransform(); + } + }, + onSettled: () => setLoading(false), } - - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepCreateForm.createTransformErrorMessage', { - defaultMessage: 'An error occurred creating the transform {transformId}:', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - setCreated(false); - setLoading(false); - return false; - } - - toastNotifications.addSuccess( - i18n.translate('xpack.transform.stepCreateForm.createTransformSuccessMessage', { - defaultMessage: 'Request to create transform {transformId} acknowledged.', - values: { transformId }, - }) ); - setCreated(true); - setLoading(false); - - if (createDataView) { - createKibanaDataView(); - } - - return true; } - async function startTransform() { + function startTransform() { setLoading(true); - const resp = await api.startTransforms([{ id: transformId }]); - - if (isStartTransformsResponseSchema(resp) && resp[transformId]?.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.stepCreateForm.startTransformSuccessMessage', { - defaultMessage: 'Request to start transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - setStarted(true); - setLoading(false); - return; - } - - const errorMessage = - isStartTransformsResponseSchema(resp) && resp[transformId]?.success === false - ? resp[transformId].error - : resp; - - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepCreateForm.startTransformErrorMessage', { - defaultMessage: 'An error occurred starting the transform {transformId}:', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), + startTransforms([{ id: transformId }], { + onError: () => setStarted(false), + onSuccess: (resp) => setStarted(resp[transformId]?.success === true), + onSettled: () => setLoading(false), }); - setStarted(false); - setLoading(false); - } - - async function createAndStartTransform() { - const acknowledged = await createTransform(); - if (acknowledged) { - await startTransform(); - } } const createKibanaDataView = async () => { @@ -250,13 +177,6 @@ export const StepCreateForm: FC = React.memo( true ); - toastNotifications.addSuccess( - i18n.translate('xpack.transform.stepCreateForm.createDataViewSuccessMessage', { - defaultMessage: 'Kibana data view {dataViewName} created successfully.', - values: { dataViewName }, - }) - ); - setDataViewId(newDataView.id); setLoading(false); return true; @@ -275,10 +195,10 @@ export const StepCreateForm: FC = React.memo( defaultMessage: 'An error occurred creating the Kibana data view {dataViewName}:', values: { dataViewName }, }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), }); setLoading(false); return false; @@ -288,57 +208,59 @@ export const StepCreateForm: FC = React.memo( const isBatchTransform = typeof transformConfig.sync === 'undefined'; - if ( - loading === false && - started === true && - progressPercentComplete === undefined && - isBatchTransform - ) { - // wrapping in function so we can keep the interval id in local scope - function startProgressBar() { - const interval = setInterval(async () => { - const stats = await api.getTransformStats(transformId); - - if ( - isGetTransformsStatsResponseSchema(stats) && - Array.isArray(stats.transforms) && - stats.transforms.length > 0 - ) { - const percent = - getTransformProgress({ - id: transformId, - config: { - ...transformConfig, - id: transformId, - }, - stats: stats.transforms[0], - }) || 0; - setProgressPercentComplete(percent); - if (percent >= 100) { - clearInterval(interval); - } - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepCreateForm.progressErrorMessage', { - defaultMessage: 'An error occurred getting the progress percentage:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - clearInterval(interval); - } - }, PROGRESS_REFRESH_INTERVAL_MS); + useEffect(() => { + if ( + loading === false && + started === true && + progressPercentComplete === undefined && + isBatchTransform + ) { setProgressPercentComplete(0); } + }, [loading, started, progressPercentComplete, isBatchTransform]); + + const progressBarRefetchEnabled = + isBatchTransform && + typeof progressPercentComplete === 'number' && + progressPercentComplete < 100; + const progressBarRefetchInterval = progressBarRefetchEnabled + ? PROGRESS_REFRESH_INTERVAL_MS + : false; + + const { data: stats } = useGetTransformStats( + transformId, + progressBarRefetchEnabled, + progressBarRefetchInterval + ); + + useEffect(() => { + if (stats === undefined) { + return; + } - startProgressBar(); - } + if (stats && Array.isArray(stats.transforms) && stats.transforms.length > 0) { + const percent = + getTransformProgress({ + id: transformId, + config: { + ...transformConfig, + id: transformId, + }, + stats: stats.transforms[0], + }) || 0; + setProgressPercentComplete(percent); + } else { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepCreateForm.progressErrorMessage', { + defaultMessage: 'An error occurred getting the progress percentage:', + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + }, [i18nStart, stats, theme, toastNotifications, transformConfig, transformId]); function getTransformConfigDevConsoleStatement() { return `PUT _transform/${transformId}\n${JSON.stringify(transformConfig, null, 2)}\n\n`; @@ -362,7 +284,7 @@ export const StepCreateForm: FC = React.memo( createTransformHandler(true)} data-test-subj="transformWizardCreateAndStartButton" > {i18n.translate('xpack.transform.stepCreateForm.createAndStartTransformButton', { @@ -436,7 +358,7 @@ export const StepCreateForm: FC = React.memo( createTransformHandler()} data-test-subj="transformWizardCreateButton" > {i18n.translate('xpack.transform.stepCreateForm.createTransformButton', { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx index b7704044fe0b3..2be067c777a1e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; export const StepCreateSummary: FC = React.memo(() => { return null; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx index 0c6fae30fd1f0..c35cca74c72ac 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx @@ -6,7 +6,7 @@ */ import { debounce } from 'lodash'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -18,7 +18,6 @@ import { i18n } from '@kbn/i18n'; import { isMultiBucketAggregate } from '@kbn/ml-agg-utils'; import { useDataSearch } from '../../../../../../../hooks/use_data_search'; -import { isEsSearchResponseWithAggregations } from '../../../../../../../../../common/api_schemas/type_guards'; import { CreateTransformWizardContext } from '../../../../wizard/wizard'; import { useToastNotifications } from '../../../../../../../app_dependencies'; @@ -33,16 +32,22 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm selectedField, }) => { const { dataView, runtimeMappings } = useContext(CreateTransformWizardContext); - const dataSearch = useDataSearch(); const toastNotifications = useToastNotifications(); - const [options, setOptions] = useState([]); - const [isLoading, setIsLoading] = useState(true); const [searchValue, setSearchValue] = useState(''); + const debouncedOnSearchChange = useMemo( + () => debounce((d: string) => setSearchValue(d), 600), + [] + ); - const onSearchChange = (newSearchValue: string) => { - setSearchValue(newSearchValue); - }; + useEffect(() => { + // Simulate initial load. + debouncedOnSearchChange(''); + // Cancel debouncing when unmounting + return () => debouncedOnSearchChange.cancel(); + // Only call on mount + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, []); const updateConfig = useCallback( (update) => { @@ -56,80 +61,53 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm [config, onChange] ); - useEffect(() => { - const abortController = new AbortController(); - - const fetchOptions = debounce(async () => { - if (selectedField === undefined) return; - - setIsLoading(true); - setOptions([]); - - const esSearchRequest = { - index: dataView!.title, - body: { - ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), - query: { - wildcard: { - [selectedField!]: { - value: `*${searchValue}*`, - }, + const { data, isError, isLoading } = useDataSearch( + { + index: dataView!.title, + body: { + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), + query: { + wildcard: { + [selectedField!]: { + value: `*${searchValue}*`, }, }, - aggs: { - field_values: { - terms: { - field: selectedField, - size: 10, - }, + }, + aggs: { + field_values: { + terms: { + field: selectedField, + size: 10, }, }, - size: 0, }, - }; - - const response = await dataSearch(esSearchRequest, abortController.signal); - - setIsLoading(false); - - if ( - !( - isEsSearchResponseWithAggregations(response) && - isMultiBucketAggregate( - response.aggregations.field_values - ) - ) - ) { - toastNotifications.addWarning( - i18n.translate('xpack.transform.agg.popoverForm.filerAgg.term.errorFetchSuggestions', { - defaultMessage: 'Unable to fetch suggestions', - }) - ); - return; - } + size: 0, + }, + }, + // Check whether fetching should be enabled + selectedField !== undefined + ); - setOptions( - ( - response.aggregations.field_values - .buckets as estypes.AggregationsSignificantLongTermsBucket[] - ).map((value) => ({ label: value.key + '' })) + useEffect(() => { + if (isError) { + toastNotifications.addWarning( + i18n.translate('xpack.transform.agg.popoverForm.filerAgg.term.errorFetchSuggestions', { + defaultMessage: 'Unable to fetch suggestions', + }) ); - }, 600); - - fetchOptions(); - - return () => { - // make sure the ongoing request is canceled - fetchOptions.cancel(); - abortController.abort(); - }; + } /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [selectedField]); - - useEffect(() => { - // Simulate initial load. - onSearchChange(''); - }, []); + }, [isError]); + + const options: EuiComboBoxOptionOption[] = + isMultiBucketAggregate( + data?.aggregations?.field_values + ) + ? ( + data?.aggregations?.field_values + .buckets as estypes.AggregationsSignificantLongTermsBucket[] + ).map((value) => ({ label: value.key + '' })) + : []; useUpdateEffect(() => { // Reset value control on field change @@ -168,7 +146,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm onCreateOption={(value) => { updateConfig({ value }); }} - onSearchChange={onSearchChange} + onSearchChange={debouncedOnSearchChange} data-test-subj="transformFilterTermValueSelector" /> diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx index a83c7c7a5871c..ba0d9e93e2cce 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButtonIcon, EuiCallOut, EuiComboBox, EuiCopy, EuiFormRow } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx index efa28de596a18..861aeb778af74 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiButton, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index 6d5d6d0ea6fc9..3470cf5706a2e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { I18nProvider } from '@kbn/i18n-react'; import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; @@ -66,6 +67,7 @@ const createMockStorage = () => ({ describe('Transform: ', () => { test('Minimal initialization', async () => { // Arrange + const queryClient = new QueryClient(); const mlSharedImports = await getMlSharedImports(); const searchItems = { @@ -87,13 +89,15 @@ describe('Transform: ', () => { const { getByText } = render( - - - - - - - + + + + + + + + + ); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 81bdb47735a37..246460d11d3ee 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -78,6 +78,9 @@ const ALLOW_TIME_RANGE_ON_TRANSFORM_CONFIG = false; const advancedEditorsSidebarWidth = '220px'; +type PopulatedFields = Set; +const isPopulatedFields = (arg: unknown): arg is PopulatedFields => arg instanceof Set; + export const ConfigSectionTitle: FC<{ title: string }> = ({ title }) => ( <> @@ -132,7 +135,9 @@ export const StepDefineForm: FC = React.memo((props) => { transformConfigQuery, runtimeMappings, timeRangeMs, - fieldStatsContext?.populatedFields ?? null + isPopulatedFields(fieldStatsContext?.populatedFields) + ? [...fieldStatsContext.populatedFields] + : [] ), dataTestSubj: 'transformIndexPreview', toastNotifications, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx index 85cef1fb2958e..4f378d4394da0 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -30,6 +31,7 @@ describe('Transform: ', () => { // Using the async/await wait()/done() pattern to avoid act() errors. test('Minimal initialization', async () => { // Arrange + const queryClient = new QueryClient(); const mlSharedImports = await getMlSharedImports(); const searchItems = { @@ -78,9 +80,11 @@ describe('Transform: ', () => { }; const { queryByText } = render( - - - + + + + + ); // Act diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx index 3aec137a3adf8..441e18ebb43d2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; import { TRANSFORM_FUNCTION, TransformFunction } from '../../../../../../common/constants'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 4e8104a7645ee..da9ebbf9b2ef7 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -25,15 +25,9 @@ import { } from '@elastic/eui'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { isHttpFetchError } from '@kbn/core-http-browser'; import { retentionPolicyMaxAgeInvalidErrorMessage } from '../../../../common/constants/validation_messages'; -import { - isEsIndices, - isEsIngestPipelines, - isPostTransformsPreviewResponseSchema, -} from '../../../../../../common/api_schemas/type_guards'; import { DEFAULT_TRANSFORM_FREQUENCY } from '../../../../../../common/constants'; import { TransformId } from '../../../../../../common/types/transform'; import { isValidIndexName } from '../../../../../../common/utils/es_utils'; @@ -42,16 +36,22 @@ import { getErrorMessage } from '../../../../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { ToastNotificationText } from '../../../../components'; -import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; +import { + useDocumentationLinks, + useGetDataViewTitles, + useGetEsIndices, + useGetEsIngestPipelines, + useGetTransforms, + useGetTransformsPreview, +} from '../../../../hooks'; import { SearchItems } from '../../../../hooks/use_search_items'; -import { useApi } from '../../../../hooks/use_api'; import { StepDetailsTimeField } from './step_details_time_field'; import { getTransformConfigQuery, getPreviewTransformRequestBody, isTransformIdValid, } from '../../../../common'; -import { EsIndexName, DataViewTitle } from './common'; +import { EsIndexName } from './common'; import { continuousModeDelayValidator, integerRangeMinus1To100Validator, @@ -73,8 +73,8 @@ interface StepDetailsFormProps { export const StepDetailsForm: FC = React.memo( ({ overrides = {}, onChange, searchItems, stepDefineState }) => { - const deps = useAppDependencies(); - const { capabilities } = deps.application; + const { application, i18n: i18nStart, theme } = useAppDependencies(); + const { capabilities } = application; const toastNotifications = useToastNotifications(); const { esIndicesCreateIndex } = useDocumentationLinks(); @@ -90,19 +90,15 @@ export const StepDetailsForm: FC = React.memo( const [destinationIngestPipeline, setDestinationIngestPipeline] = useState( defaults.destinationIngestPipeline ); - const [transformIds, setTransformIds] = useState([]); - const [indexNames, setIndexNames] = useState([]); - const [ingestPipelineNames, setIngestPipelineNames] = useState([]); const canCreateDataView = useMemo( () => - capabilities.savedObjectsManagement.edit === true || - capabilities.indexPatterns.save === true, + capabilities.savedObjectsManagement?.edit === true || + capabilities.indexPatterns?.save === true, [capabilities] ); // Index pattern state - const [dataViewTitles, setDataViewTitles] = useState([]); const [createDataView, setCreateDataView] = useState( canCreateDataView === false ? false : defaults.createDataView ); @@ -125,126 +121,122 @@ export const StepDetailsForm: FC = React.memo( [setDataViewTimeField, dataViewAvailableTimeFields] ); - const { overlays, theme } = useAppDependencies(); - const api = useApi(); + const { + error: transformsError, + data: { transformIds }, + } = useGetTransforms(); + + useEffect(() => { + if (transformsError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { + defaultMessage: 'An error occurred getting the existing transform IDs:', + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [transformsError]); + + const previewRequest = useMemo(() => { + const { searchQuery, previewRequest: partialPreviewRequest } = stepDefineState; + const transformConfigQuery = getTransformConfigQuery(searchQuery); + return getPreviewTransformRequestBody( + searchItems.dataView, + transformConfigQuery, + partialPreviewRequest, + stepDefineState.runtimeMappings + ); + }, [searchItems.dataView, stepDefineState]); + const { error: transformsPreviewError, data: transformPreview } = + useGetTransformsPreview(previewRequest); - // fetch existing transform IDs and indices once for form validation useEffect(() => { - // use an IIFE to avoid returning a Promise to useEffect. - (async function () { - const { searchQuery, previewRequest: partialPreviewRequest } = stepDefineState; - const transformConfigQuery = getTransformConfigQuery(searchQuery); - const previewRequest = getPreviewTransformRequestBody( - searchItems.dataView, - transformConfigQuery, - partialPreviewRequest, - stepDefineState.runtimeMappings + if (transformPreview) { + const properties = transformPreview.generated_dest_index.mappings.properties; + const timeFields: string[] = Object.keys(properties).filter( + (col) => properties[col].type === 'date' ); - const transformPreview = await api.getTransformsPreview(previewRequest); - - if (isPostTransformsPreviewResponseSchema(transformPreview)) { - const properties = transformPreview.generated_dest_index.mappings.properties; - const timeFields: string[] = Object.keys(properties).filter( - (col) => properties[col].type === 'date' - ); - - setDataViewAvailableTimeFields(timeFields); - setDataViewTimeField(timeFields[0]); - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformPreview', { - defaultMessage: 'An error occurred fetching the transform preview', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } + setDataViewAvailableTimeFields(timeFields); + setDataViewTimeField(timeFields[0]); + } + }, [transformPreview]); - const resp = await api.getTransforms(); - - if (isHttpFetchError(resp)) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { - defaultMessage: 'An error occurred getting the existing transform IDs:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } else { - setTransformIds(resp.transforms.map((transform) => transform.id)); - } + useEffect(() => { + if (transformsPreviewError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformPreview', { + defaultMessage: 'An error occurred fetching the transform preview', + }), + text: toMountPoint( + , + { theme, i18n: i18nStart } + ), + }); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [transformsPreviewError]); - const [indices, ingestPipelines] = await Promise.all([ - api.getEsIndices(), - api.getEsIngestPipelines(), - ]); - - if (isEsIndices(indices)) { - setIndexNames(indices.map((index) => index.name)); - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { - defaultMessage: 'An error occurred getting the existing index names:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } + const { error: esIndicesError, data: esIndicesData } = useGetEsIndices(); + const indexNames = esIndicesData?.map((index) => index.name) ?? []; - if (isEsIngestPipelines(ingestPipelines)) { - setIngestPipelineNames(ingestPipelines.map(({ name }) => name)); - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIngestPipelines', { - defaultMessage: 'An error occurred getting the existing ingest pipeline names:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } + useEffect(() => { + if (esIndicesError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { + defaultMessage: 'An error occurred getting the existing index names:', + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ + }, [esIndicesError]); - try { - setDataViewTitles(await deps.data.dataViews.getTitles()); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingDataViewTitles', { - defaultMessage: 'An error occurred getting the existing data view titles:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } - })(); - // run once - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { error: esIngestPipelinesError, data: esIngestPipelinesData } = + useGetEsIngestPipelines(); + const ingestPipelineNames = esIngestPipelinesData?.map(({ name }) => name) ?? []; + + useEffect(() => { + if (esIngestPipelinesError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIngestPipelines', { + defaultMessage: 'An error occurred getting the existing ingest pipeline names:', + }), + text: toMountPoint( + , + { theme, i18n: i18nStart } + ), + }); + } + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ + }, [esIngestPipelinesError]); + + const { error: dataViewTitlesError, data: dataViewTitles } = useGetDataViewTitles(); + + useEffect(() => { + if (dataViewTitlesError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingDataViewTitles', { + defaultMessage: 'An error occurred getting the existing data view titles:', + }), + text: toMountPoint( + , + { theme, i18n: i18nStart } + ), + }); + } + }, [dataViewTitlesError]); const dateFieldNames = searchItems.dataView.fields .filter((f) => f.type === KBN_FIELD_TYPES.DATE) @@ -284,7 +276,6 @@ export const StepDetailsForm: FC = React.memo( ); setRetentionPolicyMaxAge(''); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isRetentionPolicyEnabled]); const transformIdExists = transformIds.some((id) => transformId === id); @@ -294,7 +285,7 @@ export const StepDetailsForm: FC = React.memo( const indexNameExists = indexNames.some((name) => destinationIndex === name); const indexNameEmpty = destinationIndex === ''; const indexNameValid = isValidIndexName(destinationIndex); - const dataViewTitleExists = dataViewTitles.some((name) => destinationIndex === name); + const dataViewTitleExists = dataViewTitles?.some((name) => destinationIndex === name) ?? false; const [transformFrequency, setTransformFrequency] = useState(defaults.transformFrequency); const isTransformFrequencyValid = transformFrequencyValidator(transformFrequency); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx index 80203af34e105..b3b11a6e4764e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx index d750bf6c7e1fd..76ce803bdb415 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx index 2d2a5b1fcad93..cbc53383541ed 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiConfirmModal } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx index 103b8378cef7f..2ffb8437d4861 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx index e8780ba180e8d..6ee4d5c812f73 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx @@ -8,12 +8,14 @@ import React, { FC, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; + import { EuiButtonEmpty, EuiCallOut, EuiPageTemplate, EuiSpacer } from '@elastic/eui'; -import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants'; + import { useDocumentationLinks } from '../../hooks/use_documentation_links'; import { useSearchItems } from '../../hooks/use_search_items'; -import { BREADCRUMB_SECTION, breadcrumbService, docTitleService } from '../../services/navigation'; -import { PrivilegesWrapper } from '../../lib/authorization'; +import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; +import { CapabilitiesWrapper } from '../../components/capabilities_wrapper'; + import { Wizard } from './components/wizard'; type Props = RouteComponentProps<{ savedObjectId: string }>; @@ -43,7 +45,14 @@ export const CreateTransformSection: FC = ({ match }) => { ); return ( - + = ({ match }) => { )} {searchItems !== undefined && } - + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/__snapshots__/transform_management_section.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/__snapshots__/transform_management_section.test.tsx.snap deleted file mode 100644 index 8348f93b32140..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/__snapshots__/transform_management_section.test.tsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx index e6f3eeb4a9064..21c33361e398b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx @@ -5,11 +5,12 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; export const cloneActionNameText = i18n.translate( 'xpack.transform.transformList.cloneActionNameText', diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx index 35d5e9f05c315..d78211e19bdb4 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { AuthorizationContext } from '../../../../lib/authorization'; import { TransformListAction, TransformListRow } from '../../../../common'; import { SECTION_SLUG } from '../../../../common/constants'; -import { useSearchItems } from '../../../../hooks/use_search_items'; +import { useTransformCapabilities, useSearchItems } from '../../../../hooks'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { cloneActionNameText, CloneActionName } from './clone_action_name'; @@ -26,7 +25,7 @@ export const useCloneAction = (forceDisable: boolean, transformNodes: number) => const { getDataViewIdByTitle, loadDataViews } = useSearchItems(undefined); - const { canCreateTransform } = useContext(AuthorizationContext).capabilities; + const { canCreateTransform } = useTransformCapabilities(); const clickHandler = useCallback( async (item: TransformListRow) => { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx index c8d67a86d579a..fec35288d80b5 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; + +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; interface CreateAlertRuleActionProps { disabled: boolean; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx index 070f1eb08ac60..af75222fa85e7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo } from 'react'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import React, { useCallback, useMemo } from 'react'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListAction, TransformListRow } from '../../../../common'; import { crateAlertRuleActionNameText, @@ -17,7 +17,7 @@ import { isContinuousTransform } from '../../../../../../common/types/transform' export type CreateAlertRuleAction = ReturnType; export const useCreateAlertRuleAction = (forceDisable: boolean) => { - const { canCreateTransformAlerts } = useContext(AuthorizationContext).capabilities; + const { canCreateTransformAlerts } = useTransformCapabilities(); const { setCreateAlertRule } = useAlertRuleFlyout(); const clickHandler = useCallback( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap deleted file mode 100644 index fe1e813ca9843..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - Delete - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx index 1fb0d69682cda..37daeeb75e138 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { DeleteActionName, DeleteActionNameProps } from './delete_action_name'; @@ -21,7 +21,7 @@ describe('Transform: Transform List Actions ', () => { isBulkAction: false, }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { container } = render(); + expect(container.textContent).toBe('Delete'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx index fa1142938efe7..93e346e6c7a1d 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx @@ -5,11 +5,15 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; + import { EuiToolTip } from '@elastic/eui'; + import { TransformState, TRANSFORM_STATE } from '../../../../../../common/constants'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + import { TransformListRow } from '../../../../common'; export const deleteActionNameText = i18n.translate( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx index 357809b54746b..5996e271604e3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx @@ -5,13 +5,16 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useDeleteIndexAndTargetIndex, useDeleteTransforms } from '../../../../hooks'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { + useDeleteIndexAndTargetIndex, + useDeleteTransforms, + useTransformCapabilities, +} from '../../../../hooks'; import { deleteActionNameText, @@ -21,7 +24,7 @@ import { export type DeleteAction = ReturnType; export const useDeleteAction = (forceDisable: boolean) => { - const { canDeleteTransform } = useContext(AuthorizationContext).capabilities; + const { canDeleteTransform } = useTransformCapabilities(); const deleteTransforms = useDeleteTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx index f7cc72c2236b0..c3d359e648c28 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx index 3bf5ee8e611f6..7fc16ebfff278 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx @@ -5,16 +5,15 @@ * 2.0. */ -import React, { useContext, FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + +import { useTransformCapabilities } from '../../../../hooks'; export const editActionNameText = i18n.translate( 'xpack.transform.transformList.editActionNameText', @@ -24,7 +23,7 @@ export const editActionNameText = i18n.translate( ); export const EditActionName: FC = () => { - const { canCreateTransform } = useContext(AuthorizationContext).capabilities; + const { canCreateTransform } = useTransformCapabilities(); if (!canCreateTransform) { return ( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx index 19d60b8b64381..40e7334197b0c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { editActionNameText, EditActionName } from './edit_action_name'; import { useSearchItems } from '../../../../hooks/use_search_items'; @@ -19,7 +19,7 @@ import { TransformConfigUnion } from '../../../../../../common/types/transform'; export type EditAction = ReturnType; export const useEditAction = (forceDisable: boolean, transformNodes: number) => { - const { canCreateTransform } = useContext(AuthorizationContext).capabilities; + const { canCreateTransform } = useTransformCapabilities(); const [config, setConfig] = useState(); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx index 9e848fdd8323e..edfde117abbd7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal } from '@elastic/eui'; import type { ReauthorizeAction } from './use_reauthorize_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx index e07ae03ec46b0..ee883a3e7e77b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx @@ -5,14 +5,16 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { type FC } from 'react'; + import { EuiToolTip, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + import { needsReauthorization } from '../../../../common/reauthorization_utils'; -import { - AuthorizationContext, - createCapabilityFailureMessage, -} from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListRow } from '../../../../common'; export const reauthorizeActionNameText = i18n.translate( @@ -45,7 +47,7 @@ export const ReauthorizeActionName: FC = ({ forceDisable, transformNodes, }) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); // Disable start for batch transforms which have completed. const someNeedsReauthorization = items.some(needsReauthorization); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx index 086f5451d53be..67e618765e42e 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { sortTransformsToReauthorize } from './sort_transforms_to_reauthorize'; import { needsReauthorization } from '../../../../common/reauthorization_utils'; @@ -17,11 +17,11 @@ import { } from './reauthorize_action_name'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; export type ReauthorizeAction = ReturnType; export const useReauthorizeAction = (forceDisable: boolean, transformNodes: number) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const reauthorizeTransforms = useReauthorizeTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx index c6ea0f5f7270d..6d5f56d3e7297 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx @@ -5,11 +5,15 @@ * 2.0. */ -import React, { FC } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { type FC } from 'react'; + import { EuiToolTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + import { TransformState, TRANSFORM_STATE } from '../../../../../../common/constants'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + import { TransformListRow } from '../../../../common'; export const resetActionNameText = i18n.translate( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx index 70164bc22a63c..23a399fdb9086 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx @@ -5,19 +5,18 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useResetTransforms } from '../../../../hooks'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities, useResetTransforms } from '../../../../hooks'; import { resetActionNameText, isResetActionDisabled, ResetActionName } from './reset_action_name'; export type ResetAction = ReturnType; export const useResetAction = (forceDisable: boolean) => { - const { canResetTransform } = useContext(AuthorizationContext).capabilities; + const { canResetTransform } = useTransformCapabilities(); const resetTransforms = useResetTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx index 71b6a055c0d94..0c3be1cdad70b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx @@ -5,14 +5,15 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { type FC } from 'react'; + import { EuiToolTip } from '@elastic/eui'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { i18n } from '@kbn/i18n'; + +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListRow, isCompletedBatchTransform } from '../../../../common'; export const scheduleNowActionNameText = i18n.translate( @@ -48,7 +49,7 @@ export const ScheduleNowActionName: FC = ({ forceDisable, transformNodes, }) => { - const { canScheduleNowTransform } = useContext(AuthorizationContext).capabilities; + const { canScheduleNowTransform } = useTransformCapabilities(); const isBulkAction = items.length > 1; // Disable schedule-now for batch transforms which have completed. diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx index dda70deb225a6..a13d3da89f677 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import React, { useContext, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListAction, TransformListRow } from '../../../../common'; import { useScheduleNowTransforms } from '../../../../hooks'; @@ -21,8 +21,7 @@ import { export type ScheduleNowAction = ReturnType; export const useScheduleNowAction = (forceDisable: boolean, transformNodes: number) => { - const { canScheduleNowTransform } = useContext(AuthorizationContext).capabilities; - + const { canScheduleNowTransform } = useTransformCapabilities(); const scheduleNowTransforms = useScheduleNowTransforms(); const action: TransformListAction = useMemo( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap deleted file mode 100644 index 20b0691b55bf9..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - Start - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx index 7ad6897034e10..ab98f36222957 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { StartAction } from './use_start_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx index ba4619b022620..6ef0a995186c7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { TransformListRow } from '../../../../common'; import { StartActionName, StartActionNameProps } from './start_action_name'; @@ -16,6 +17,8 @@ import transformListRow from '../../../../common/__mocks__/transform_list_row.js jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); +const queryClient = new QueryClient(); + describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { // @ts-expect-error mock data is too loosely typed @@ -26,8 +29,11 @@ describe('Transform: Transform List Actions ', () => { transformNodes: 1, }; - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render( + + + + ); + expect(container.textContent).toBe('Start'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx index 844d9755d7073..c50c83d25edc5 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx @@ -5,16 +5,14 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListRow, isCompletedBatchTransform } from '../../../../common'; export const startActionNameText = i18n.translate( @@ -55,7 +53,7 @@ export const StartActionName: FC = ({ forceDisable, transformNodes, }) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const isBulkAction = items.length > 1; // Disable start for batch transforms which have completed. diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx index 20910cf5fa0a5..168174730b706 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx @@ -5,19 +5,18 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; -import { AuthorizationContext } from '../../../../lib/authorization'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useStartTransforms } from '../../../../hooks'; +import { useTransformCapabilities, useStartTransforms } from '../../../../hooks'; import { isStartActionDisabled, startActionNameText, StartActionName } from './start_action_name'; export type StartAction = ReturnType; export const useStartAction = (forceDisable: boolean, transformNodes: number) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const startTransforms = useStartTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap deleted file mode 100644 index fd97412fa1875..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - Stop - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx index 9496dbd82c70d..e32bf043a2221 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { TransformListRow } from '../../../../common'; import { StopActionName, StopActionNameProps } from './stop_action_name'; @@ -16,6 +17,8 @@ import transformListRow from '../../../../common/__mocks__/transform_list_row.js jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); +const queryClient = new QueryClient(); + describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { // @ts-expect-error mock data is too loosely typed @@ -25,8 +28,11 @@ describe('Transform: Transform List Actions ', () => { items: [item], }; - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render( + + + + ); + expect(container.textContent).toBe('Stop'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx index 1c729555f7df3..e5bc1425cdd99 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx @@ -5,17 +5,15 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; import { TransformListRow } from '../../../../common'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; export const stopActionNameText = i18n.translate( 'xpack.transform.transformList.stopActionNameText', @@ -43,7 +41,7 @@ export interface StopActionNameProps { } export const StopActionName: FC = ({ items, forceDisable }) => { const isBulkAction = items.length > 1; - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); // Disable stop action if one of the transforms is stopped already const stoppedTransform = items.some( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx index ac53ee83f6f65..e410704341177 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx @@ -5,32 +5,28 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; -import { AuthorizationContext } from '../../../../lib/authorization'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useStopTransforms } from '../../../../hooks'; +import { useTransformCapabilities, useStopTransforms } from '../../../../hooks'; import { isStopActionDisabled, stopActionNameText, StopActionName } from './stop_action_name'; import { isManagedTransform } from '../../../../common/managed_transforms_utils'; export type StopAction = ReturnType; export const useStopAction = (forceDisable: boolean) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; - + const { canStartStopTransform } = useTransformCapabilities(); const stopTransforms = useStopTransforms(); const [isModalVisible, setModalVisible] = useState(false); const [items, setItems] = useState([]); const closeModal = () => setModalVisible(false); - const openModal = (newItems: TransformListRow[]) => { if (Array.isArray(newItems)) { setItems(newItems); setModalVisible(true); } }; - const stopAndCloseModal = useCallback( (transformSelection: TransformListRow[]) => { setModalVisible(false); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap deleted file mode 100644 index a529ef04f9230..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Minimal initialization 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx index 0a7324fd09ffc..da84ef2faa8e3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx @@ -5,17 +5,23 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { CreateTransformButton } from './create_transform_button'; jest.mock('../../../../../shared_imports'); +const queryClient = new QueryClient(); + describe('Transform: Transform List ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render( + + + + ); + expect(container.textContent).toBe('Create a transform'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx index 14697f9af4080..9c1fa4be8c101 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx @@ -5,16 +5,15 @@ * 2.0. */ -import React, { useContext, FC, MouseEventHandler } from 'react'; +import React, { type FC, type MouseEventHandler } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + +import { useTransformCapabilities } from '../../../../hooks'; interface CreateTransformButtonProps { onClick: MouseEventHandler; @@ -25,7 +24,7 @@ export const CreateTransformButton: FC = ({ onClick, transformNodes, }) => { - const { capabilities } = useContext(AuthorizationContext); + const capabilities = useTransformCapabilities(); const disabled = !capabilities.canCreateTransform || diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx index 75f333960fa54..cdaabb3a3b200 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx index a337d73ca54b3..d9310762ef3e0 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiFieldText, EuiFormRow } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx index b5bb7f3fb258f..519bdc94011e1 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx @@ -5,15 +5,13 @@ * 2.0. */ -import React, { useEffect, useState, type FC } from 'react'; +import React, { type FC } from 'react'; import { useEuiTheme, EuiComboBox, EuiFormRow, EuiSkeletonRectangle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEsIngestPipelines } from '../../../../../../common/api_schemas/type_guards'; - -import { useApi } from '../../../../hooks/use_api'; +import { useGetEsIngestPipelines } from '../../../../hooks'; import { EditTransformFlyoutFormTextInput } from './edit_transform_flyout_form_text_input'; import { useEditTransformFlyout } from './use_edit_transform_flyout'; @@ -30,35 +28,8 @@ export const EditTransformIngestPipeline: FC = () => { const { errorMessages, value } = useEditTransformFlyout('destinationIngestPipeline'); const { formField } = useEditTransformFlyout('actions'); - const api = useApi(); - - const [ingestPipelineNames, setIngestPipelineNames] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - useEffect(function fetchPipelinesOnMount() { - let unmounted = false; - - async function getIngestPipelineNames() { - try { - const ingestPipelines = await api.getEsIngestPipelines(); - - if (!unmounted && isEsIngestPipelines(ingestPipelines)) { - setIngestPipelineNames(ingestPipelines.map(({ name }) => name)); - } - } finally { - if (!unmounted) { - setIsLoading(false); - } - } - } - - getIngestPipelineNames(); - - return () => { - unmounted = true; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { data: esIngestPipelinesData, isLoading } = useGetEsIngestPipelines(); + const ingestPipelineNames = esIngestPipelinesData?.map(({ name }) => name) ?? []; return ( <> diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx index 06e30d584de46..b55b6f90a0aa3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx @@ -11,12 +11,9 @@ import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isPostTransformsUpdateResponseSchema } from '../../../../../../common/api_schemas/type_guards'; import { getErrorMessage } from '../../../../../../common/utils/errors'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common'; -import { useToastNotifications } from '../../../../app_dependencies'; -import { useApi } from '../../../../hooks/use_api'; +import { useUpdateTransform } from '../../../../hooks'; import { useEditTransformFlyout } from './use_edit_transform_flyout'; @@ -25,33 +22,20 @@ interface EditTransformUpdateButtonProps { } export const EditTransformUpdateButton: FC = ({ closeFlyout }) => { - const api = useApi(); - const toastNotifications = useToastNotifications(); - const requestConfig = useEditTransformFlyout('requestConfig'); const isUpdateButtonDisabled = useEditTransformFlyout('isUpdateButtonDisabled'); const config = useEditTransformFlyout('config'); const { apiError } = useEditTransformFlyout('actions'); + const updateTransfrom = useUpdateTransform(config.id, requestConfig); + async function submitFormHandler() { apiError(undefined); - const transformId = config.id; - - const resp = await api.updateTransform(transformId, requestConfig); - - if (!isPostTransformsUpdateResponseSchema(resp)) { - apiError(getErrorMessage(resp)); - return; - } - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.editTransformSuccessMessage', { - defaultMessage: 'Transform {transformId} updated.', - values: { transformId }, - }) - ); - closeFlyout(); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); + updateTransfrom(undefined, { + onError: (error) => apiError(getErrorMessage(error)), + onSuccess: () => closeFlyout(), + }); } return ( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx index d0ea13ec11df1..efd4fb1d500af 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx index 5647060ee7a72..3f2bd7fb70c0c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx @@ -8,7 +8,8 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useAppDependencies } from '../../../../app_dependencies'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx index 810d60f3b8fd7..cfd0d112ba90b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; export interface StatsBarStat { label: string; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx index 21674ae24301c..58f9cd2a455e2 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { useMemo, type FC } from 'react'; + import { Stat, StatsBarStat } from './stat'; interface Stats { @@ -17,10 +18,10 @@ export interface TransformStatsBarStats extends Stats { batch: StatsBarStat; continuous: StatsBarStat; started: StatsBarStat; + nodes?: StatsBarStat; } type StatsBarStats = TransformStatsBarStats; -type StatsKey = keyof StatsBarStats; interface StatsBarProps { stats: StatsBarStats; @@ -28,7 +29,8 @@ interface StatsBarProps { } export const StatsBar: FC = ({ stats, dataTestSub }) => { - const statsList = Object.keys(stats).map((k) => stats[k as StatsKey]); + const statsList = useMemo(() => Object.values(stats), [stats]); + return (
    {statsList diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_details_pane.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_details_pane.test.tsx.snap deleted file mode 100644 index 39964399f66db..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_details_pane.test.tsx.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Job List Expanded Row Minimal initialization 1`] = ` -
    - - - -
    - - - -
    -`; - -exports[`Transform: Job List Expanded Row
    Minimal initialization 1`] = ` - - - - the-section-title - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap deleted file mode 100644 index 19a5055954d84..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Expanded Row Minimal initialization 1`] = ` -
    - - - - - { - "id": "fq_date_histogram_1m_1441", - "source": { - "index": [ - "farequote-2019" - ], - "query": { - "match_all": {} - } - }, - "dest": { - "index": "fq_date_histogram_1m_1441" - }, - "pivot": { - "group_by": { - "@timestamp": { - "date_histogram": { - "field": "@timestamp", - "calendar_interval": "1m" - } - } - }, - "aggregations": { - "responsetime.avg": { - "avg": { - "field": "responsetime" - } - } - } - }, - "version": "8.0.0", - "create_time": 1564388146667 -} - - - -   - - -
    -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap deleted file mode 100644 index 77df5dc76f68a..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Minimal initialization 1`] = ` -<_EuiPageEmptyPrompt - actions={ - Array [ - - Create your first transform - , - ] - } - color="subdued" - data-test-subj="transformNoTransformsFound" - title={ -

    - No transforms found -

    - } -/> -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx index d756c4a459e6a..f4766da492f67 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx @@ -17,6 +17,7 @@ import { formatHumanReadableDateTimeSeconds } from '@kbn/ml-date-utils'; import { stringHash } from '@kbn/ml-string-hash'; import { isDefined } from '@kbn/ml-is-defined'; +import { useIsServerless } from '../../../../serverless_context'; import { TransformHealthAlertRule } from '../../../../../../common/types/alerting'; import { TransformListRow } from '../../../../common'; @@ -46,6 +47,8 @@ interface Props { type StateValues = Optional; export const ExpandedRow: FC = ({ item, onAlertEdit }) => { + const hideNodeInfo = useIsServerless(); + const stateValues: StateValues = { ...item.stats }; delete stateValues.stats; delete stateValues.checkpointing; @@ -61,7 +64,7 @@ export const ExpandedRow: FC = ({ item, onAlertEdit }) => { description: item.stats.state, } ); - if (item.stats.node !== undefined) { + if (!hideNodeInfo && item.stats.node !== undefined) { stateItems.push({ title: 'node.name', description: item.stats.node.name, diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx index a4c8a497d2202..ad375d47ce4cc 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { ExpandedRowDetailsPane, Section, SectionConfig } from './expanded_row_details_pane'; @@ -23,16 +23,20 @@ const section: SectionConfig = { describe('Transform: Job List Expanded Row ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-section-title'); + expect(container.textContent).toContain('the-item-title'); + expect(container.textContent).toContain('the-item-description'); }); }); describe('Transform: Job List Expanded Row
    ', () => { test('Minimal initialization', () => { - const wrapper = shallow(
    ); + const { container } = render(
    ); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-section-title'); + expect(container.textContent).toContain('the-item-title'); + expect(container.textContent).toContain('the-item-description'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx index 7b1a5de79fae5..ff69aab3aa3eb 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; @@ -14,8 +14,7 @@ import { ExpandedRowJsonPane } from './expanded_row_json_pane'; describe('Transform: Transform List Expanded Row ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render(); + expect(container.textContent).toContain(JSON.stringify(transformListRow.config, null, 2)); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx index c68a57ea12109..48e17d9157226 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx index 4cf387ad2973b..10c1a4f01dfb2 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx @@ -15,17 +15,16 @@ import { EuiToolTip, EuiButtonIcon, } from '@elastic/eui'; -import { euiLightVars as theme } from '@kbn/ui-theme'; +import { euiLightVars as theme } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; +import { useIsServerless } from '../../../../serverless_context'; import { DEFAULT_MAX_AUDIT_MESSAGE_SIZE, TIME_FORMAT } from '../../../../../../common/constants'; -import { isGetTransformsAuditMessagesResponseSchema } from '../../../../../../common/api_schemas/type_guards'; import { TransformMessage } from '../../../../../../common/types/messages'; -import { useApi } from '../../../../hooks/use_api'; import { JobIcon } from '../../../../components/job_icon'; -import { useRefreshTransformList } from '../../../../common'; +import { useGetTransformAuditMessages, useRefreshTransformList } from '../../../../hooks'; interface ExpandedRowMessagesPaneProps { transformId: string; @@ -37,11 +36,7 @@ interface Sorting { } export const ExpandedRowMessagesPane: FC = ({ transformId }) => { - const [messages, setMessages] = useState([]); - const [msgCount, setMsgCount] = useState(0); - - const [isLoading, setIsLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); + const hideNodeInfo = useIsServerless(); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -52,58 +47,24 @@ export const ExpandedRowMessagesPane: FC = ({ tran }, }); - const api = useApi(); - - const getMessagesFactory = ( - sortField: keyof TransformMessage = 'timestamp', - sortDirection: 'asc' | 'desc' = 'desc' - ) => { - let concurrentLoads = 0; - - return async function getMessages() { - concurrentLoads++; - - if (concurrentLoads > 1) { - return; - } - - setIsLoading(true); - const messagesResp = await api.getTransformAuditMessages( - transformId, - sortField, - sortDirection - ); - - if (!isGetTransformsAuditMessagesResponseSchema(messagesResp)) { - setIsLoading(false); - setErrorMessage( - i18n.translate( - 'xpack.transform.transformList.transformDetails.messagesPane.errorMessage', - { - defaultMessage: 'Messages could not be loaded', - } - ) - ); - return; - } - - setIsLoading(false); - setMessages(messagesResp.messages); - setMsgCount(messagesResp.total); - - concurrentLoads--; - - if (concurrentLoads > 0) { - concurrentLoads = 0; - getMessages(); - } - }; - }; - const { refresh: refreshMessage } = useRefreshTransformList({ onRefresh: getMessagesFactory() }); + const { + isLoading, + error, + data = { messages: [], total: 0 }, + } = useGetTransformAuditMessages(transformId, sorting.sort.field, sorting.sort.direction); + const { messages, total } = data; + const errorMessage = + error !== null + ? i18n.translate('xpack.transform.transformList.transformDetails.messagesPane.errorMessage', { + defaultMessage: 'Messages could not be loaded', + }) + : ''; + + const refreshTransformList = useRefreshTransformList(); const columns = [ { - name: refreshMessage ? ( + name: refreshTransformList ? ( = ({ tran // TODO: Replace this with ML's blurButtonOnClick when it's moved to a shared package onClick={(e: MouseEvent) => { (e.currentTarget as HTMLButtonElement).blur(); - refreshMessage(); + refreshTransformList(); }} iconType="refresh" aria-label={i18n.translate('xpack.transform.transformList.refreshAriaLabel', { @@ -138,16 +99,20 @@ export const ExpandedRowMessagesPane: FC = ({ tran render: (timestamp: number) => formatDate(timestamp, TIME_FORMAT), sortable: true, }, - { - field: 'node_name', - name: i18n.translate( - 'xpack.transform.transformList.transformDetails.messagesPane.nodeLabel', - { - defaultMessage: 'Node', - } - ), - sortable: true, - }, + ...(!hideNodeInfo + ? [ + { + field: 'node_name', + name: i18n.translate( + 'xpack.transform.transformList.transformDetails.messagesPane.nodeLabel', + { + defaultMessage: 'Node', + } + ), + sortable: true, + }, + ] + : []), { field: 'message', name: i18n.translate( @@ -156,13 +121,13 @@ export const ExpandedRowMessagesPane: FC = ({ tran defaultMessage: 'Message', } ), - width: '50%', + width: !hideNodeInfo ? '50%' : '70%', }, ]; const getPageOfMessages = ({ index, size }: { index: number; size: number }) => { let list = messages; - if (msgCount <= DEFAULT_MAX_AUDIT_MESSAGE_SIZE) { + if (total <= DEFAULT_MAX_AUDIT_MESSAGE_SIZE) { const sortField = sorting.sort.field ?? 'timestamp'; list = messages.sort((a: TransformMessage, b: TransformMessage) => { const prev = a[sortField] as any; @@ -192,12 +157,6 @@ export const ExpandedRowMessagesPane: FC = ({ tran setPageSize(size); if (sort) { setSorting({ sort }); - - // Since we only show 500 messages, if user wants oldest messages first - // we need to make sure we fetch them from elasticsearch - if (msgCount > DEFAULT_MAX_AUDIT_MESSAGE_SIZE) { - getMessagesFactory(sort.field, sort.direction)(); - } } }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx index 430fa9478f60a..fc4bf0e24d14d 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx @@ -5,25 +5,48 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider, type UseQueryResult } from '@tanstack/react-query'; +import * as ReactQuery from '@tanstack/react-query'; + +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { TransformList } from './transform_list'; +const useQueryMock = jest.spyOn(ReactQuery, 'useQuery').mockImplementation((queryKey) => { + switch (queryKey[0]) { + case 'transform.data_view_exists': + return { error: null, data: true } as UseQueryResult; + } + + return { error: null, data: undefined } as UseQueryResult; +}); + +const queryClient = new QueryClient(); + jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); describe('Transform: Transform List ', () => { - test('Minimal initialization', () => { - const wrapper = shallow( - + test('Minimal initialization', async () => { + const { container } = render( + + + + + ); - expect(wrapper).toMatchSnapshot(); + await waitFor(() => { + expect(useQueryMock).toHaveBeenCalledTimes(4); + expect(container.textContent).toContain('Create your first transform'); + }); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx index dc831e996b309..2e0106afc0e92 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC, MouseEventHandler, useContext, useState } from 'react'; +import React, { type FC, type MouseEventHandler, useState } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiButton, @@ -26,12 +27,10 @@ import { useReauthorizeAction, } from '../action_reauthorize'; import type { TransformId } from '../../../../../../common/types/transform'; -import { - TRANSFORM_LIST_COLUMN, - TransformListRow, - useRefreshTransformList, -} from '../../../../common'; -import { AuthorizationContext } from '../../../../lib/authorization'; + +import { type TransformListRow, TRANSFORM_LIST_COLUMN } from '../../../../common'; +import { useRefreshTransformList, useTransformCapabilities } from '../../../../hooks'; + import { CreateTransformButton } from '../create_transform_button'; import { RefreshTransformListButton } from '../refresh_transform_list_button'; import { @@ -83,6 +82,7 @@ function getItemIdToExpandedRowMap( } interface TransformListProps { + isLoading: boolean; onCreateTransform: MouseEventHandler; transformNodes: number; transforms: TransformListRow[]; @@ -90,13 +90,13 @@ interface TransformListProps { } export const TransformList: FC = ({ + isLoading, onCreateTransform, transformNodes, transforms, transformsLoading, }) => { - const [isLoading, setIsLoading] = useState(false); - const { refresh } = useRefreshTransformList({ isLoading: setIsLoading }); + const refreshTransformList = useRefreshTransformList(); const { setEditAlertRule } = useAlertRuleFlyout(); const [query, setQuery] = useState>[0]>(); @@ -111,7 +111,7 @@ export const TransformList: FC = ({ const bulkStopAction = useStopAction(false); const bulkScheduleNowAction = useScheduleNowAction(false, transformNodes); - const { capabilities } = useContext(AuthorizationContext); + const capabilities = useTransformCapabilities(); const disabled = !capabilities.canCreateTransform || !capabilities.canPreviewTransform || @@ -134,10 +134,6 @@ export const TransformList: FC = ({ const filteredTransforms = clauses.length > 0 ? filterTransforms(transforms, clauses) : transforms; - if (transforms.length === 0 && transformNodes === 0) { - return null; - } - if (transforms.length === 0) { return ( = ({ const toolsRight = ( - + diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx index 8ad53c64c2f1e..79f6321c2419f 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx @@ -5,23 +5,28 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; -import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useIsServerless } from '../../../../serverless_context'; import { TRANSFORM_MODE, TRANSFORM_STATE } from '../../../../../../common/constants'; import { TransformListRow } from '../../../../common'; -import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; +import { useDocumentationLinks, useRefreshTransformList } from '../../../../hooks'; import { StatsBar, TransformStatsBarStats } from '../stats_bar'; -function createTranformStats(transformNodes: number, transformsList: TransformListRow[]) { - const transformStats = { +function createTranformStats( + transformNodes: number, + transformsList: TransformListRow[], + hideNodeInfo: boolean +): TransformStatsBarStats { + const transformStats: TransformStatsBarStats = { total: { label: i18n.translate('xpack.transform.statsBar.totalTransformsLabel', { defaultMessage: 'Total transforms', @@ -57,14 +62,17 @@ function createTranformStats(transformNodes: number, transformsList: TransformLi value: 0, show: true, }, - nodes: { + }; + + if (!hideNodeInfo) { + transformStats.nodes = { label: i18n.translate('xpack.transform.statsBar.transformNodesLabel', { defaultMessage: 'Nodes', }), value: transformNodes, show: true, - }, - }; + }; + } if (transformsList === undefined) { return transformStats; @@ -74,9 +82,15 @@ function createTranformStats(transformNodes: number, transformsList: TransformLi let startedTransforms = 0; transformsList.forEach((transform) => { - if (transform.mode === TRANSFORM_MODE.CONTINUOUS) { + if ( + transform.mode === TRANSFORM_MODE.CONTINUOUS && + typeof transformStats.continuous.value === 'number' + ) { transformStats.continuous.value++; - } else if (transform.mode === TRANSFORM_MODE.BATCH) { + } else if ( + transform.mode === TRANSFORM_MODE.BATCH && + typeof transformStats.batch.value === 'number' + ) { transformStats.batch.value++; } @@ -109,19 +123,20 @@ export const TransformStatsBar: FC = ({ transformNodes, transformsList, }) => { + const hideNodeInfo = useIsServerless(); + const refreshTransformList = useRefreshTransformList(); const { esNodeRoles } = useDocumentationLinks(); const transformStats: TransformStatsBarStats = createTranformStats( transformNodes, - transformsList + transformsList, + hideNodeInfo ); return ( <> - - {transformNodes === 0 && ( + {!hideNodeInfo && transformNodes === 0 && ( <> - = ({ }} />

    + refreshTransformList()} size="s"> + +
    + )} + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx index d620a60e7a861..34b69e45f087a 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import React, { type FC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; jest.mock('../../../../../shared_imports'); @@ -14,8 +16,13 @@ import { useActions } from './use_actions'; describe('Transform: Transform List Actions', () => { test('useActions()', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useActions({ forceDisable: false, transformNodes: 1 }) + const queryClient = new QueryClient(); + const wrapper: FC = ({ children }) => ( + {children} + ); + const { result, waitForNextUpdate } = renderHook( + () => useActions({ forceDisable: false, transformNodes: 1 }), + { wrapper } ); await waitForNextUpdate(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx index 521bd745e8692..87f550e433bc2 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import React, { type FC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; import { useColumns } from './use_columns'; @@ -14,7 +16,13 @@ jest.mock('../../../../app_dependencies'); describe('Transform: Job List Columns', () => { test('useColumns()', async () => { - const { result, waitForNextUpdate } = renderHook(() => useColumns([], () => {}, 1, [])); + const queryClient = new QueryClient(); + const wrapper: FC = ({ children }) => ( + {children} + ); + const { result, waitForNextUpdate } = renderHook(() => useColumns([], () => {}, 1, []), { + wrapper, + }); await waitForNextUpdate(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx index db77b3e306da2..2ae8edf30a1de 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useContext } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -24,7 +24,7 @@ import { EuiIcon, } from '@elastic/eui'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { needsReauthorization } from '../../../../common/reauthorization_utils'; import { isLatestTransform, @@ -52,7 +52,7 @@ export const useColumns = ( transformNodes: number, transformSelection: TransformListRow[] ) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const { actions, modals } = useActions({ forceDisable: transformSelection.length > 0, diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts deleted file mode 100644 index c1f4c3d4f7d5c..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useEffect } from 'react'; - -import { DEFAULT_REFRESH_INTERVAL_MS } from '../../../../../../common/constants'; - -import { useRefreshTransformList } from '../../../../common'; - -export const useRefreshInterval = ( - setBlockRefresh: React.Dispatch> -) => { - const { refresh } = useRefreshTransformList(); - useEffect(() => { - const interval = setInterval(refresh, DEFAULT_REFRESH_INTERVAL_MS); - - // useEffect cleanup - return () => { - clearInterval(interval); - }; - // custom comparison - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); // [] as comparator makes sure this only runs once -}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx index 3e4cee609c75b..3782bc9e9dfb3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx @@ -5,17 +5,25 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { TransformManagementSection } from './transform_management_section'; jest.mock('../../../shared_imports'); +jest.mock('../../services/navigation'); + +const queryClient = new QueryClient(); describe('Transform: ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); + const { container } = render( + + + + ); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('Missing permission'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx index 093930aeb1b7f..cce3128b3f3d0 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { FC, useContext, useEffect, useMemo, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; +import React, { type FC, useEffect, useMemo, useState } from 'react'; + import { EuiButton, EuiButtonEmpty, @@ -16,19 +16,27 @@ import { EuiSkeletonText, EuiSpacer, } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { useIsServerless } from '../../serverless_context'; import { needsReauthorization } from '../../common/reauthorization_utils'; +import { TRANSFORM_STATE } from '../../../../common/constants'; + import { - APP_GET_TRANSFORM_CLUSTER_PRIVILEGES, - TRANSFORM_STATE, -} from '../../../../common/constants'; -import { TransformListRow, useRefreshTransformList } from '../../common'; -import { useDocumentationLinks } from '../../hooks/use_documentation_links'; -import { useDeleteTransforms, useGetTransforms } from '../../hooks'; + useDocumentationLinks, + useDeleteTransforms, + useTransformCapabilities, + useGetTransforms, + useGetTransformNodes, +} from '../../hooks'; import { RedirectToCreateTransform } from '../../common/navigation'; -import { AuthorizationContext, PrivilegesWrapper } from '../../lib/authorization'; -import { BREADCRUMB_SECTION, breadcrumbService, docTitleService } from '../../services/navigation'; -import { useRefreshInterval } from './components/transform_list/use_refresh_interval'; +import { CapabilitiesWrapper } from '../../components/capabilities_wrapper'; +import { ToastNotificationText } from '../../components/toast_notification_text'; +import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; + import { SearchSelection } from './components/search_selection'; import { TransformList } from './components/transform_list'; import { TransformStatsBar } from './components/transform_list/transforms_stats_bar'; @@ -38,39 +46,55 @@ import { TransformAlertFlyoutWrapper, } from '../../../alerting/transform_alerting_flyout'; +const ErrorMessageCallout: FC<{ + text: JSX.Element; + errorMessage: IHttpFetchError | null; +}> = ({ text, errorMessage }) => { + return ( + <> + + + {text}{' '} + {errorMessage !== null && ( + + )} + + } + color="danger" + iconType="error" + /> + + ); +}; + export const TransformManagement: FC = () => { const { esTransform } = useDocumentationLinks(); - - const [transformsLoading, setTransformsLoading] = useState(false); - const [isInitialized, setIsInitialized] = useState(false); - const [blockRefresh, setBlockRefresh] = useState(false); - const [transforms, setTransforms] = useState([]); - const [transformNodes, setTransformNodes] = useState(0); - const [errorMessage, setErrorMessage] = useState(undefined); - const [transformIdsWithoutConfig, setTransformIdsWithoutConfig] = useState< - string[] | undefined - >(); + const hideNodeInfo = useIsServerless(); const deleteTransforms = useDeleteTransforms(); - const getTransforms = useGetTransforms( - setTransforms, - setTransformNodes, - setErrorMessage, - setTransformIdsWithoutConfig, - setIsInitialized, - blockRefresh - ); + const { + isInitialLoading: transformNodesInitialLoading, + error: transformNodesErrorMessage, + data: transformNodesData = 0, + } = useGetTransformNodes({ enabled: true }); + const transformNodes = transformNodesErrorMessage === null ? transformNodesData : 0; - // Subscribe to the refresh observable to trigger reloading the transform list. - useRefreshTransformList({ - isLoading: setTransformsLoading, - onRefresh: () => getTransforms(true), + const { + isInitialLoading: transformsInitialLoading, + isLoading: transformsLoading, + error: transformsErrorMessage, + data: { transforms, transformIdsWithoutConfig }, + } = useGetTransforms({ + enabled: !transformNodesInitialLoading && (transformNodes > 0 || hideNodeInfo), }); - // Call useRefreshInterval() after the subscription above is set up. - useRefreshInterval(setBlockRefresh); - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const isInitialLoading = transformNodesInitialLoading || transformsInitialLoading; + + const { canStartStopTransform } = useTransformCapabilities(); const unauthorizedTransformsWarning = useMemo(() => { const unauthorizedCnt = transforms.filter((t) => needsReauthorization(t)).length; @@ -162,93 +186,101 @@ export const TransformManagement: FC = () => { paddingSize={'none'} /> - - - {typeof errorMessage !== 'undefined' && ( - - - - } - body={ -

    -

    {JSON.stringify(errorMessage)}
    -

    - } - /> - )} - - {!isInitialized && } - {isInitialized && ( + {isInitialLoading && ( + <> + + + + )} + {!isInitialLoading && ( <> {unauthorizedTransformsWarning} + {!hideNodeInfo && transformNodesErrorMessage !== null && ( + + } + errorMessage={transformNodesErrorMessage} + /> + )} + {transformsErrorMessage !== null && ( + + } + errorMessage={transformsErrorMessage} + /> + )} + + - {typeof errorMessage === 'undefined' && ( - - {transformIdsWithoutConfig ? ( - <> - -

    - -

    - { - await deleteTransforms( - // If transform task doesn't have any corresponding config - // we won't know what the destination index or data view would be - // and should be force deleted - { - transformsInfo: transformIdsWithoutConfig.map((id) => ({ - id, - state: TRANSFORM_STATE.FAILED, - })), - deleteDestIndex: false, - deleteDestDataView: false, - forceDelete: true, - } - ); + + {transformIdsWithoutConfig ? ( + <> + +

    + - - - - - - ) : null} + /> +

    + + deleteTransforms( + // If transform task doesn't have any corresponding config + // we won't know what the destination index or data view would be + // and should be force deleted + { + transformsInfo: transformIdsWithoutConfig.map((id) => ({ + id, + state: TRANSFORM_STATE.FAILED, + })), + deleteDestIndex: false, + deleteDestDataView: false, + forceDelete: true, + } + ) + } + > + + +
    + + + ) : null} + {(transformNodes > 0 || transforms.length > 0) && ( - -
    - )} + )} + +
    )}
    @@ -274,8 +306,8 @@ export const TransformManagementSection: FC = () => { }, []); return ( - + - + ); }; diff --git a/x-pack/plugins/transform/public/app/serverless_context.tsx b/x-pack/plugins/transform/public/app/serverless_context.tsx new file mode 100644 index 0000000000000..d6113957aff73 --- /dev/null +++ b/x-pack/plugins/transform/public/app/serverless_context.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, FC, useContext, useMemo } from 'react'; + +export const ServerlessContext = createContext({ + isServerless: false, +}); + +export const ServerlessContextProvider: FC<{ isServerless: boolean }> = (props) => { + const { children, isServerless } = props; + return ( + {children} + ); +}; + +export function useIsServerless() { + const context = useContext(ServerlessContext); + return useMemo(() => { + return context.isServerless; + }, [context]); +} diff --git a/x-pack/plugins/transform/public/app/services/es_index_service.ts b/x-pack/plugins/transform/public/app/services/es_index_service.ts deleted file mode 100644 index d8d058b731a74..0000000000000 --- a/x-pack/plugins/transform/public/app/services/es_index_service.ts +++ /dev/null @@ -1,29 +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 { HttpSetup } from '@kbn/core/public'; -import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { addInternalBasePath } from '../../../common/constants'; - -export class IndexService { - async canDeleteIndex(http: HttpSetup) { - const privilege = await http.get<{ hasAllPrivileges: boolean }>( - addInternalBasePath(`privileges`), - { version: '1' } - ); - if (!privilege) { - return false; - } - return privilege.hasAllPrivileges; - } - - async dataViewExists(dataViewsContract: DataViewsContract, indexName: string) { - return (await dataViewsContract.find(indexName)).some(({ title }) => title === indexName); - } -} - -export const indexService = new IndexService(); diff --git a/x-pack/plugins/transform/public/index.ts b/x-pack/plugins/transform/public/index.ts index ebe43aea75440..0b185da7f6900 100644 --- a/x-pack/plugins/transform/public/index.ts +++ b/x-pack/plugins/transform/public/index.ts @@ -6,11 +6,12 @@ */ import './app/index.scss'; +import type { PluginInitializerContext } from '@kbn/core-plugins-server'; import { TransformUiPlugin } from './plugin'; /** @public */ -export const plugin = () => { - return new TransformUiPlugin(); +export const plugin = (ctx: PluginInitializerContext) => { + return new TransformUiPlugin(ctx); }; export { getTransformHealthRuleType } from './alerting'; diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index 789d76901aeaa..03b99ab85ad27 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -18,11 +18,12 @@ import type { SpacesApi } from '@kbn/spaces-plugin/public'; import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { ChartsPluginStart } from '@kbn/charts-plugin/public'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public/plugin'; -import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; -import { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; +import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public/plugin'; +import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; +import type { PluginInitializerContext } from '@kbn/core/public'; import { registerFeature } from './register_feature'; import { getTransformHealthRuleType } from './alerting'; @@ -45,6 +46,11 @@ export interface PluginsDependencies { } export class TransformUiPlugin { + private isServerless: boolean = false; + constructor(initializerContext: PluginInitializerContext) { + this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless'; + } + public setup(coreSetup: CoreSetup, pluginsSetup: PluginsDependencies): void { const { management, home, triggersActionsUi } = pluginsSetup; @@ -58,7 +64,7 @@ export class TransformUiPlugin { order: 5, mount: async (params) => { const { mountManagementSection } = await import('./app/mount_management_section'); - return mountManagementSection(coreSetup, params); + return mountManagementSection(coreSetup, params, this.isServerless); }, }); registerFeature(home); diff --git a/x-pack/plugins/transform/server/capabilities.test.ts b/x-pack/plugins/transform/server/capabilities.test.ts new file mode 100644 index 0000000000000..5cf194d6e84e3 --- /dev/null +++ b/x-pack/plugins/transform/server/capabilities.test.ts @@ -0,0 +1,167 @@ +/* + * Copyright 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 TransformCapabilities } from '../common/types/capabilities'; + +import { extractMissingPrivileges, getPrivilegesAndCapabilities } from './capabilities'; + +describe('has_privilege_factory', () => { + const fullClusterPrivileges = { + 'cluster:admin/transform/preview': true, + 'cluster:admin/transform/put': true, + 'cluster:monitor/transform/get': true, + 'cluster:admin/transform/start': true, + 'cluster:admin/transform/delete': true, + 'cluster:admin/transform/reset': true, + 'cluster:admin/transform/stop': true, + 'cluster:admin/transform/start_task': true, + 'cluster:monitor/transform/stats/get': true, + }; + + const monitorOnlyClusterPrivileges = { + 'cluster:admin/transform/preview': false, + 'cluster:admin/transform/put': false, + 'cluster:admin/transform/start': false, + 'cluster:admin/transform/delete': false, + 'cluster:admin/transform/reset': false, + 'cluster:admin/transform/stop': false, + 'cluster:admin/transform/start_task': false, + 'cluster:monitor/transform/get': true, + 'cluster:monitor/transform/stats/get': true, + }; + const noClusterPrivileges = { + 'cluster:admin/transform/preview': false, + 'cluster:admin/transform/put': false, + 'cluster:admin/transform/start': false, + 'cluster:admin/transform/delete': false, + 'cluster:admin/transform/reset': false, + 'cluster:admin/transform/stop': false, + 'cluster:admin/transform/start_task': false, + 'cluster:monitor/transform/get': false, + 'cluster:monitor/transform/stats/get': false, + }; + + const monitorOnlyMissingPrivileges = Object.entries(monitorOnlyClusterPrivileges) + .filter(([, authorized]) => !authorized) + .map(([priv]) => priv); + + describe('extractMissingPrivileges', () => { + it('returns no missing privilege if provided both monitor and admin cluster privileges', () => { + expect(extractMissingPrivileges(fullClusterPrivileges)).toEqual([]); + }); + it('returns missing privilege if provided only monitor cluster privileges', () => { + expect(extractMissingPrivileges(monitorOnlyClusterPrivileges)).toEqual( + monitorOnlyMissingPrivileges + ); + }); + it('returns all missing privilege if provided no cluster privilege', () => { + const allPrivileges = Object.keys(noClusterPrivileges); + const extracted = extractMissingPrivileges(noClusterPrivileges); + expect(extracted).toEqual(allPrivileges); + }); + }); + + describe('getPrivilegesAndCapabilities', () => { + it('returns full capabilities if provided both monitor and admin cluster privileges', () => { + const fullCapabilities: TransformCapabilities = { + canCreateTransform: true, + canCreateTransformAlerts: true, + canDeleteIndex: true, + canDeleteTransform: true, + canGetTransform: true, + canPreviewTransform: true, + canReauthorizeTransform: true, + canResetTransform: true, + canScheduleNowTransform: true, + canStartStopTransform: true, + canUseTransformAlerts: true, + }; + + expect(getPrivilegesAndCapabilities(fullClusterPrivileges, true, true)).toEqual({ + capabilities: fullCapabilities, + privileges: { hasAllPrivileges: true, missingPrivileges: { cluster: [], index: [] } }, + }); + expect(getPrivilegesAndCapabilities(fullClusterPrivileges, false, true)).toEqual({ + capabilities: fullCapabilities, + privileges: { + hasAllPrivileges: true, + missingPrivileges: { cluster: [], index: ['monitor'] }, + }, + }); + }); + it('returns view only capabilities if provided only monitor cluster privileges', () => { + const viewOnlyCapabilities: TransformCapabilities = { + canCreateTransform: false, + canCreateTransformAlerts: false, + canDeleteIndex: false, + canDeleteTransform: false, + canGetTransform: true, + canPreviewTransform: false, + canReauthorizeTransform: false, + canResetTransform: false, + canScheduleNowTransform: false, + canStartStopTransform: false, + canUseTransformAlerts: true, + }; + + const { capabilities, privileges } = getPrivilegesAndCapabilities( + monitorOnlyClusterPrivileges, + true, + false + ); + expect(capabilities).toEqual(viewOnlyCapabilities); + expect(privileges).toEqual({ + hasAllPrivileges: false, + missingPrivileges: { + cluster: monitorOnlyMissingPrivileges, + index: [], + }, + }); + }); + it('returns no capabilities and all the missing privileges if no cluster privileges', () => { + const noCapabilities: TransformCapabilities = { + canCreateTransform: false, + canCreateTransformAlerts: false, + canDeleteIndex: false, + canDeleteTransform: false, + canGetTransform: false, + canPreviewTransform: false, + canResetTransform: false, + canReauthorizeTransform: false, + canScheduleNowTransform: false, + canStartStopTransform: false, + canUseTransformAlerts: false, + }; + + const { capabilities, privileges } = getPrivilegesAndCapabilities( + noClusterPrivileges, + false, + false + ); + expect(capabilities).toEqual(noCapabilities); + expect(privileges).toEqual({ + hasAllPrivileges: false, + missingPrivileges: { + cluster: Object.keys(noClusterPrivileges), + index: ['monitor'], + }, + }); + }); + + it('returns canResetTransform:false if no cluster privilege for transform/reset', () => { + const { capabilities } = getPrivilegesAndCapabilities( + { + ...fullClusterPrivileges, + 'cluster:admin/transform/reset': false, + }, + false, + false + ); + expect(capabilities.canResetTransform).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/transform/server/capabilities.ts b/x-pack/plugins/transform/server/capabilities.ts index 73889a0808359..5fd542d428a70 100644 --- a/x-pack/plugins/transform/server/capabilities.ts +++ b/x-pack/plugins/transform/server/capabilities.ts @@ -7,22 +7,123 @@ import type { CoreSetup } from '@kbn/core-lifecycle-server'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + import { - getPrivilegesAndCapabilities, - INITIAL_CAPABILITIES, -} from '../common/privilege/has_privilege_factory'; -import { APP_CLUSTER_PRIVILEGES } from '../common/constants'; + getInitialTransformCapabilities, + type Privilege, + type Privileges, + type PrivilegesAndCapabilities, +} from '../common/types/capabilities'; +import { APP_CLUSTER_PRIVILEGES, APP_INDEX_PRIVILEGES } from '../common/constants'; + import type { PluginStartDependencies } from './types'; export const TRANSFORM_PLUGIN_ID = 'transform' as const; +function isPrivileges(arg: unknown): arg is Privileges { + return ( + isPopulatedObject(arg, ['hasAllPrivileges', 'missingPrivileges']) && + typeof arg.hasAllPrivileges === 'boolean' && + typeof arg.missingPrivileges === 'object' && + arg.missingPrivileges !== null + ); +} + +export const hasPrivilegeFactory = + (privileges: Privileges | undefined | null) => (privilege: Privilege) => { + const [section, requiredPrivilege] = privilege; + if (isPrivileges(privileges) && !privileges.missingPrivileges[section]) { + // if the section does not exist in our missingPrivileges, everything is OK + return true; + } + if (isPrivileges(privileges) && privileges.missingPrivileges[section]!.length === 0) { + return true; + } + if (requiredPrivilege === '*') { + // If length > 0 and we require them all... KO + return false; + } + // If we require _some_ privilege, we make sure that the one + // we require is *not* in the missingPrivilege array + return ( + isPrivileges(privileges) && + !privileges.missingPrivileges[section]!.includes(requiredPrivilege) + ); + }; + +export const extractMissingPrivileges = ( + privilegesObject: { [key: string]: boolean } = {} +): string[] => + Object.keys(privilegesObject).reduce((privileges: string[], privilegeName: string): string[] => { + if (!privilegesObject[privilegeName]) { + privileges.push(privilegeName); + } + return privileges; + }, []); + +export const getPrivilegesAndCapabilities = ( + clusterPrivileges: Record, + hasOneIndexWithAllPrivileges: boolean, + hasAllPrivileges: boolean +): PrivilegesAndCapabilities => { + const privilegesResult: Privileges = { + hasAllPrivileges: true, + missingPrivileges: { + cluster: [], + index: [], + }, + }; + + // Find missing cluster privileges and set overall app privileges + privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(clusterPrivileges); + privilegesResult.hasAllPrivileges = hasAllPrivileges; + + if (!hasOneIndexWithAllPrivileges) { + privilegesResult.missingPrivileges.index = [...APP_INDEX_PRIVILEGES]; + } + + const hasPrivilege = hasPrivilegeFactory(privilegesResult); + + const capabilities = getInitialTransformCapabilities(); + + capabilities.canGetTransform = + hasPrivilege(['cluster', 'cluster:monitor/transform/get']) && + hasPrivilege(['cluster', 'cluster:monitor/transform/stats/get']); + + capabilities.canCreateTransform = hasPrivilege(['cluster', 'cluster:admin/transform/put']); + + capabilities.canDeleteTransform = hasPrivilege(['cluster', 'cluster:admin/transform/delete']); + + capabilities.canResetTransform = hasPrivilege(['cluster', 'cluster:admin/transform/reset']); + + capabilities.canPreviewTransform = hasPrivilege(['cluster', 'cluster:admin/transform/preview']); + + capabilities.canStartStopTransform = + hasPrivilege(['cluster', 'cluster:admin/transform/start']) && + hasPrivilege(['cluster', 'cluster:admin/transform/start_task']) && + hasPrivilege(['cluster', 'cluster:admin/transform/stop']); + + capabilities.canCreateTransformAlerts = capabilities.canCreateTransform; + + capabilities.canUseTransformAlerts = capabilities.canGetTransform; + + capabilities.canScheduleNowTransform = capabilities.canStartStopTransform; + + capabilities.canReauthorizeTransform = capabilities.canStartStopTransform; + + capabilities.canDeleteIndex = hasAllPrivileges; + + return { privileges: privilegesResult, capabilities }; +}; + export const setupCapabilities = ( core: Pick, 'capabilities' | 'getStartServices'>, securitySetup?: SecurityPluginSetup ) => { core.capabilities.registerProvider(() => { return { - transform: INITIAL_CAPABILITIES, + transform: getInitialTransformCapabilities(), }; }); @@ -38,10 +139,7 @@ export const setupCapabilities = ( // If security is not enabled or not available, transform should have full permission if (!isSecurityPluginEnabled || !securityStart) { return { - transform: Object.keys(INITIAL_CAPABILITIES).reduce>((acc, p) => { - acc[p] = true; - return acc; - }, {}), + transform: getInitialTransformCapabilities(true), }; } @@ -71,6 +169,8 @@ export const setupCapabilities = ( hasAllRequested ).capabilities; - return { transform: transformCapabilities }; + return { + transform: transformCapabilities as Record>, + }; }); }; diff --git a/x-pack/plugins/transform/server/routes/api/privileges.ts b/x-pack/plugins/transform/server/routes/api/privileges.ts deleted file mode 100644 index 0b93c8e503fc6..0000000000000 --- a/x-pack/plugins/transform/server/routes/api/privileges.ts +++ /dev/null @@ -1,84 +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 { IKibanaResponse, IScopedClusterClient } from '@kbn/core/server'; -import type { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/types'; -import { - getPrivilegesAndCapabilities, - type PrivilegesAndCapabilities, -} from '../../../common/privilege/has_privilege_factory'; -import { - addInternalBasePath, - APP_CLUSTER_PRIVILEGES, - APP_INDEX_PRIVILEGES, -} from '../../../common/constants'; - -import type { RouteDependencies } from '../../types'; - -export function registerPrivilegesRoute({ router, license }: RouteDependencies) { - router.versioned - .get({ - path: addInternalBasePath('privileges'), - access: 'internal', - }) - .addVersion( - { - version: '1', - validate: false, - }, - license.guardApiRoute( - async (ctx, req, res): Promise> => { - if (license.getStatus().isSecurityEnabled === false) { - // If security isn't enabled, let the user use app. - return res.ok({ - body: getPrivilegesAndCapabilities({}, true, true), - }); - } - - const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client; - - const esClusterPrivilegesReq: Promise = - esClient.asCurrentUser.security.hasPrivileges({ - body: { - cluster: APP_CLUSTER_PRIVILEGES, - }, - }); - const [esClusterPrivileges, userPrivileges] = await Promise.all([ - // Get cluster privileges - esClusterPrivilegesReq, - // // Get all index privileges the user has - esClient.asCurrentUser.security.getUserPrivileges(), - ]); - - const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges; - const { indices } = userPrivileges; - - // Check if they have all the required index privileges for at least one index - const hasOneIndexWithAllPrivileges = - indices.find(({ privileges }: { privileges: string[] }) => { - if (privileges.includes('all')) { - return true; - } - - const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) => - privileges.includes(privilege) - ); - - return indexHasAllPrivileges; - }) !== undefined; - - return res.ok({ - body: getPrivilegesAndCapabilities( - cluster, - hasOneIndexWithAllPrivileges, - hasAllPrivileges - ), - }); - } - ) - ); -} diff --git a/x-pack/plugins/transform/server/routes/index.ts b/x-pack/plugins/transform/server/routes/index.ts index 0a8539c7683de..aec4417f2a421 100644 --- a/x-pack/plugins/transform/server/routes/index.ts +++ b/x-pack/plugins/transform/server/routes/index.ts @@ -8,11 +8,9 @@ import { RouteDependencies } from '../types'; import { registerFieldHistogramsRoutes } from './api/field_histograms'; -import { registerPrivilegesRoute } from './api/privileges'; import { registerTransformsRoutes } from './api/transforms'; export function registerRoutes(dependencies: RouteDependencies) { registerFieldHistogramsRoutes(dependencies); - registerPrivilegesRoute(dependencies); registerTransformsRoutes(dependencies); } diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json index 9c9ef2299c60d..52e0747cc1fb9 100644 --- a/x-pack/plugins/transform/tsconfig.json +++ b/x-pack/plugins/transform/tsconfig.json @@ -67,7 +67,9 @@ "@kbn/saved-search-plugin", "@kbn/unified-field-list", "@kbn/ebt-tools", - "@kbn/content-management-plugin" + "@kbn/content-management-plugin", + "@kbn/react-kibana-mount", + "@kbn/core-plugins-server" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 374cea2026289..2f13b6f419260 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -651,7 +651,6 @@ "core.deprecations.elasticsearchUsername.manualSteps2": "Ajoutez le paramètre \"elasticsearch.serviceAccountToken\" à kibana.yml.", "core.deprecations.elasticsearchUsername.manualSteps3": "Supprimez \"elasticsearch.username\" et \"elasticsearch.password\" de kibana.yml.", "core.deprecations.noCorrectiveAction": "Ce déclassement ne peut pas être résolu automatiquement.", - "core.euiAccordion.isLoading": "Chargement", "core.euiAutoRefresh.autoRefreshLabel": "Actualisation automatique", "core.euiAutoRefresh.buttonLabelOff": "L'actualisation automatique est désactivée", "core.euiBasicTable.noItemsMessage": "Aucun élément n'a été trouvé", @@ -1317,7 +1316,6 @@ "data.search.aggs.rareTerms.aggTypesLabel": "Termes rares de {fieldName}", "data.search.es_search.queryTimeValue": "{queryTime} ms", "data.search.functions.geoBoundingBox.arguments.error": "Au moins un des groupes de paramètres suivants doit être fourni : {parameters}.", - "data.search.searchSource.fetch.shardsFailedNotificationMessage": "Échec de {shardsFailed} des {shardsTotal} partitions", "data.search.searchSource.indexPatternIdDescription": "ID dans l'index {kibanaIndexPattern}.", "data.search.searchSource.queryTimeValue": "{queryTime} ms", "data.search.searchSource.requestTimeValue": "{requestTime} ms", @@ -2064,15 +2062,7 @@ "data.search.searchSource.dataViewDescription": "La vue de données qui a été interrogée.", "data.search.searchSource.dataViewIdLabel": "ID de vue de données", "data.search.searchSource.dataViewLabel": "Vue de données", - "data.search.searchSource.fetch.requestTimedOutNotificationMessage": "Les données peuvent être incomplètes parce que votre requête est arrivée à échéance.", - "data.search.searchSource.fetch.shardsFailedModal.close": "Fermer", - "data.search.searchSource.fetch.shardsFailedModal.copyToClipboard": "Copier la réponse dans le presse-papiers", - "data.search.searchSource.fetch.shardsFailedModal.showDetails": "Afficher les détails", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest": "Requête", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse": "Réponse", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures": "Échecs de partition", "data.search.searchSource.fetch.shardsFailedModal.tableColReason": "Raison", - "data.search.searchSource.fetch.shardsFailedNotificationDescription": "Les données peuvent être incomplètes ou erronées.", "data.search.searchSource.hitsDescription": "Le nombre de documents renvoyés par la requête.", "data.search.searchSource.hitsLabel": "Résultats", "data.search.searchSource.hitsTotalDescription": "Le nombre de documents correspondant à la requête.", @@ -2155,7 +2145,7 @@ "discover.advancedSettings.disableDocumentExplorerDescription": "Désactivez cette option pour utiliser le nouveau {documentExplorerDocs} au lieu de la vue classique. l'explorateur de documents offre un meilleur tri des données, des colonnes redimensionnables et une vue en plein écran.", "discover.advancedSettings.discover.showFieldStatisticsDescription": "Activez le {fieldStatisticsDocs} pour afficher des détails tels que les valeurs minimale et maximale d'un champ numérique ou une carte d'un champ géographique. Cette fonctionnalité est en version bêta et susceptible d'être modifiée.", "discover.advancedSettings.discover.showMultifieldsDescription": "Détermine si les {multiFields} doivent s'afficher dans la fenêtre de document étendue. Dans la plupart des cas, les champs multiples sont les mêmes que les champs d'origine. Cette option est uniquement disponible lorsque le paramètre ''searchFieldsFromSource'' est désactivé.", - "discover.advancedSettings.enableSQLDescription": "{technicalPreviewLabel} Cette fonctionnalité en préversion technique est à un stade hautement expérimental ; ne pas s'y fier pour les recherches enregistrées, ni pour les visualisations ou les tableaux de bord en production. Ce paramètre active SQL comme langage de requête à base de texte dans Discover et Lens. Si vous avez des commentaires sur cette expérience, contactez-nous via {link}", + "discover.advancedSettings.enableESQLDescription": "{technicalPreviewLabel} Cette fonctionnalité en préversion technique est à un stade hautement expérimental ; ne pas s'y fier pour les recherches enregistrées, ni pour les visualisations ou les tableaux de bord en production. Ce paramètre active SQL comme langage de requête à base de texte dans Discover et Lens. Si vous avez des commentaires sur cette expérience, contactez-nous via {link}", "discover.context.contextOfTitle": "Documents relatifs à #{anchorId}", "discover.context.newerDocumentsWarning": "Seuls {docCount} documents plus récents que le document ancré ont été trouvés.", "discover.context.olderDocumentsWarning": "Seuls {docCount} documents plus anciens que le document ancré ont été trouvés.", @@ -2178,11 +2168,6 @@ "discover.dscTour.stepAddFields.description": "Cliquez sur {plusIcon} pour ajouter les champs qui vous intéressent.", "discover.dscTour.stepExpand.description": "Cliquez sur {expandIcon} pour afficher, comparer et filtrer les documents.", "discover.errorCalloutFormattedTitle": "{title} : {errorMessage}", - "discover.grid.copyClipboardButtonTitle": "Copier la valeur de {column}", - "discover.grid.copyColumnValuesToClipboard.toastTitle": "Valeurs de la colonne \"{column}\" copiées dans le presse-papiers", - "discover.grid.filterForAria": "Filtrer sur cette {value}", - "discover.grid.filterOutAria": "Exclure cette {value}", - "discover.gridSampleSize.limitDescription": "Les résultats de recherche sont limités à {sampleSize} documents Ajoutez d'autres termes pour affiner votre recherche.", "discover.howToSeeOtherMatchingDocumentsDescription": "Voici les {sampleSize} premiers documents correspondant à votre recherche. Veuillez affiner cette dernière pour en voir davantage.", "discover.noMatchRoute.bannerText": "L'application Discover ne reconnaît pas cet itinéraire : {route}", "discover.noResults.kqlExamples.kqlDescription": "En savoir plus sur {kqlLink}", @@ -2192,9 +2177,6 @@ "discover.pageTitleWithSavedSearch": "Discover –{savedSearchTitle}", "discover.savedSearchAliasMatchRedirect.objectNoun": "Recherche {savedSearch}", "discover.savedSearchURLConflictCallout.objectNoun": "Recherche {savedSearch}", - "discover.searchGenerationWithDescription": "Tableau généré par la recherche {searchTitle}", - "discover.searchGenerationWithDescriptionGrid": "Tableau généré par la recherche {searchTitle} ({searchDescription})", - "discover.selectedDocumentsNumber": "{nr} documents sélectionnés", "discover.showingDefaultDataViewWarningDescription": "Affichage de la vue de données par défaut : \"{loadedDataViewTitle}\" ({loadedDataViewId})", "discover.showingSavedDataViewWarningDescription": "Affichage de la vue de données enregistrée : \"{ownDataViewTitle}\" ({ownDataViewId})", "discover.singleDocRoute.errorMessage": "Aucune vue de données correspondante pour l'ID {dataViewId}", @@ -2220,8 +2202,8 @@ "discover.advancedSettings.docTableHideTimeColumnText": "Permet de masquer la colonne ''Time'' dans Discover et dans toutes les recherches enregistrées des tableaux de bord.", "discover.advancedSettings.docTableHideTimeColumnTitle": "Masquer la colonne ''Time''", "discover.advancedSettings.documentExplorerLinkText": "Explorateur de documents", - "discover.advancedSettings.enableSQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", - "discover.advancedSettings.enableSQLTitle": "Activer SQL", + "discover.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", + "discover.advancedSettings.enableESQLTitle": "Activer SQL", "discover.advancedSettings.fieldsPopularLimitText": "Les N champs les plus populaires à afficher", "discover.advancedSettings.fieldsPopularLimitTitle": "Limite de champs populaires", "discover.advancedSettings.maxDocFieldsDisplayedText": "Le nombre maximal de champs renvoyés dans le résumé du document", @@ -2247,7 +2229,6 @@ "discover.backToTopLinkText": "Revenir en haut de la page.", "discover.badge.readOnly.text": "Lecture seule", "discover.badge.readOnly.tooltip": "Impossible d’enregistrer les recherches", - "discover.clearSelection": "Effacer la sélection", "discover.confirmDataViewSave.cancel": "Annuler", "discover.confirmDataViewSave.message": "L'action que vous avez choisie requiert une vue de données enregistrée.", "discover.confirmDataViewSave.saveAndContinue": "Enregistrer et continuer", @@ -2268,8 +2249,6 @@ "discover.context.unableToLoadAnchorDocumentDescription": "Impossible de charger le document ancré", "discover.context.unableToLoadDocumentDescription": "Impossible de charger les documents", "discover.contextViewRoute.errorTitle": "Une erreur s'est produite", - "discover.controlColumnHeader": "Colonne de commande", - "discover.copyToClipboardJSON": "Copier les documents dans le presse-papiers (JSON)", "discover.discoverBreadcrumbTitle": "Discover", "discover.discoverDefaultSearchSessionName": "Discover", "discover.discoverDescription": "Explorez vos données de manière interactive en interrogeant et en filtrant des documents bruts.", @@ -2310,36 +2289,7 @@ "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "Afficher les documents alentour", "discover.documentsAriaLabel": "Documents", "discover.documentsErrorTitle": "Erreur lors de la recherche", - "unifiedDocViewer.docView.table.actions.label": "Actions", - "unifiedDocViewer.docView.table.actions.open": "Actions ouvertes", - "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "Une ou plusieurs valeurs dans ce champ sont trop longues et ne peuvent pas être recherchées ni filtrées.", - "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "Ce champ comporte une ou plusieurs valeurs mal formées qui ne peuvent pas être recherchées ni filtrées.", - "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "Une ou plusieurs valeurs dans ce champ ont été ignorées par Elasticsearch et ne peuvent pas être recherchées ni filtrées.", - "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "La valeur dans ce champ est trop longue et ne peut pas être recherchée ni filtrée.", - "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "La valeur dans ce champ est mal formée et ne peut pas être recherchée ni filtrée.", - "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "La valeur dans ce champ a été ignorée par Elasticsearch et ne peut pas être recherchée ni filtrée.", - "unifiedDocViewer.docView.table.searchPlaceHolder": "Rechercher les noms de champs", - "unifiedDocViewer.docViews.json.jsonTitle": "JSON", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "Filtrer sur le champ", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "Filtrer sur le champ", - "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "Filtrer sur la valeur", - "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "Filtrer sur la valeur", - "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "Exclure la valeur", - "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "Exclure la valeur", - "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "Contient des valeurs ignorées", - "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "Valeur ignorée", - "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "Épingler le champ", - "unifiedDocViewer.docViews.table.pinFieldLabel": "Épingler le champ", "discover.docViews.table.scoreSortWarningTooltip": "Filtrez sur _score pour pouvoir récupérer les valeurs correspondantes.", - "unifiedDocViewer.docViews.table.tableTitle": "Tableau", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "Afficher/Masquer la colonne dans le tableau", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "Afficher/Masquer la colonne dans le tableau", - "unifiedDocViewer.fieldChooser.discoverField.name": "Afficher/Masquer les détails du champ", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "Impossible de filtrer sur les champs méta", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "Impossible de filtrer sur les champs scriptés", - "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "Les champs non indexés ou les valeurs ignorées ne peuvent pas être recherchés", - "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "Désépingler le champ", - "unifiedDocViewer.docViews.table.unpinFieldLabel": "Désépingler le champ", "discover.dropZoneTableLabel": "Abandonner la zone pour ajouter un champ en tant que colonne dans la table", "discover.dscTour.stepAddFields.imageAltText": "Dans la liste Champs disponibles, cliquez sur l'icône Plus pour afficher/masquer un champ dans le tableau de documents.", "discover.dscTour.stepAddFields.title": "Ajouter des champs dans le tableau", @@ -2360,46 +2310,24 @@ "discover.embeddable.search.displayName": "rechercher", "discover.errorCalloutShowErrorMessage": "Afficher les détails", "discover.fieldChooser.availableFieldsTooltip": "Champs disponibles pour l'affichage dans le tableau.", - "unifiedDocViewer.fieldChooser.discoverField.actions": "Actions", "discover.fieldChooser.discoverField.addFieldTooltip": "Ajouter le champ en tant que colonne", - "unifiedDocViewer.fieldChooser.discoverField.multiField": "champ multiple", - "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "Les champs multiples peuvent avoir plusieurs valeurs.", - "unifiedDocViewer.fieldChooser.discoverField.name": "Champ", "discover.fieldChooser.discoverField.removeFieldTooltip": "Supprimer le champ du tableau", - "unifiedDocViewer.fieldChooser.discoverField.value": "Valeur", "discover.goToDiscoverButtonText": "Aller à Discover", - "discover.grid.closePopover": "Fermer la fenêtre contextuelle", - "discover.grid.copyCellValueButton": "Copier la valeur", - "discover.grid.copyColumnNameToClipboard.toastTitle": "Copié dans le presse-papiers", - "discover.grid.copyColumnNameToClipBoardButton": "Copier le nom", - "discover.grid.copyColumnValuesToClipBoardButton": "Copier la colonne", - "discover.grid.copyEscapedValueWithFormulasToClipboardWarningText": "Les valeurs peuvent contenir des formules avec échappement.", - "discover.grid.copyFailedErrorText": "Impossible de copier dans le presse-papiers avec ce navigateur", - "discover.grid.copyValueToClipboard.toastTitle": "Copié dans le presse-papiers", - "discover.grid.documentHeader": "Document", - "discover.grid.editFieldButton": "Modifier le champ de la vue de données", - "discover.grid.filterFor": "Filtrer sur", - "discover.grid.filterOut": "Exclure", "discover.grid.flyout.documentNavigation": "Navigation dans le document", "discover.grid.flyout.toastColumnAdded": "La colonne \"{columnName}\" a été ajoutée.", "discover.grid.flyout.toastColumnRemoved": "La colonne \"{columnName}\" a été supprimée.", - "discover.grid.selectDoc": "Sélectionner le document \"{rowNumber}\"", "discover.grid.tableRow.detailHeading": "Document développé", "discover.grid.tableRow.textBasedDetailHeading": "Ligne développée", "discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "Document unique", "discover.grid.tableRow.viewSurroundingDocumentsHover": "Inspectez des documents qui ont été créés avant et après ce document. Seuls les filtres épinglés restent actifs dans la vue Documents relatifs.", "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "Documents relatifs", "discover.grid.tableRow.viewText": "Afficher :", - "discover.grid.viewDoc": "Afficher/Masquer les détails de la boîte de dialogue", "discover.helpMenu.appName": "Découverte", "discover.inspectorRequestDataTitleDocuments": "Documents", "discover.inspectorRequestDescriptionDocument": "Cette requête interroge Elasticsearch afin de récupérer les documents.", "discover.invalidFiltersWarnToast.description": "Les références d'ID de la vue de données dans certains filtres appliqués diffèrent de la vue de données actuelle.", "discover.invalidFiltersWarnToast.title": "Références d'index différentes", - "unifiedDocViewer.json.codeEditorAriaLabel": "Affichage JSON en lecture seule d’un document Elasticsearch", - "unifiedDocViewer.json.copyToClipboardLabel": "Copier dans le presse-papiers", "discover.loadingDocuments": "Chargement des documents", - "unifiedDocViewer.loadingJSON": "Chargement de JSON", "discover.loadingResults": "Chargement des résultats", "discover.localMenu.alertsDescription": "Alertes", "discover.localMenu.fallbackReportTitle": "Recherche Discover sans titre", @@ -2443,29 +2371,20 @@ "discover.noResults.suggestion.syntaxPopoverExampleHeader": "Exemple", "discover.noResults.suggestion.tryText": "Voici quelques solutions à essayer :", "discover.noResults.suggestion.viewAllMatchesButtonText": "Afficher toutes les correspondances", - "discover.noResultsFound": "Résultat introuvable", "discover.notifications.invalidTimeRangeText": "La plage temporelle spécifiée n'est pas valide (de : \"{from}\" à \"{to}\").", "discover.notifications.invalidTimeRangeTitle": "Plage temporelle non valide", "discover.notifications.notSavedSearchTitle": "La recherche \"{savedSearchTitle}\" n'a pas été enregistrée.", "discover.notifications.savedSearchTitle": "La recherche \"{savedSearchTitle}\" a été enregistrée.", "discover.pageTitleWithoutSavedSearch": "Discover - Recherche non encore enregistrée", "discover.reloadSavedSearchButton": "Réinitialiser la recherche", - "discover.removeColumnLabel": "Supprimer la colonne", "discover.rootBreadcrumb": "Découverte", "discover.sampleData.viewLinkLabel": "Découverte", "discover.savedSearch.savedObjectName": "Recherche enregistrée", "discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "Ouvrir dans Discover", "discover.searchingTitle": "Recherche", - "discover.selectColumnHeader": "Sélectionner la colonne", "discover.serverLocatorExtension.titleFromLocatorUnknown": "Recherche inconnue", - "discover.showAllDocuments": "Afficher tous les documents", - "discover.showSelectedDocumentsOnly": "Afficher uniquement les documents sélectionnés", "discover.singleDocRoute.errorTitle": "Une erreur s'est produite", "discover.skipToBottomButtonLabel": "Atteindre la fin du tableau", - "unifiedDocViewer.sourceViewer.errorMessage": "Impossible de récupérer les données pour le moment. Actualisez l'onglet et réessayez.", - "unifiedDocViewer.sourceViewer.errorMessageTitle": "Une erreur s'est produite.", - "unifiedDocViewer.sourceViewer.refresh": "Actualiser", - "discover.toggleSidebarAriaLabel": "Activer/Désactiver la barre latérale", "discover.topNav.openSearchPanel.manageSearchesButtonLabel": "Gérer les recherches", "discover.topNav.openSearchPanel.noSearchesFoundDescription": "Aucune recherche correspondante trouvée.", "discover.topNav.openSearchPanel.openSearchTitle": "Ouvrir une recherche", @@ -2481,6 +2400,73 @@ "discover.viewAlert.searchSourceErrorTitle": "Erreur lors de la récupération de la source de recherche", "discover.viewModes.document.label": "Documents", "discover.viewModes.fieldStatistics.label": "Statistiques de champ", + "discover.fieldNameIcons.binaryAriaLabel": "Binaire", + "discover.fieldNameIcons.booleanAriaLabel": "Booléen", + "discover.fieldNameIcons.conflictFieldAriaLabel": "Conflit", + "discover.fieldNameIcons.counterFieldAriaLabel": "Indicateur de compteur", + "discover.fieldNameIcons.dateFieldAriaLabel": "Date", + "discover.fieldNameIcons.dateRangeFieldAriaLabel": "Plage de dates", + "discover.fieldNameIcons.denseVectorFieldAriaLabel": "Vecteur dense", + "discover.fieldNameIcons.flattenedFieldAriaLabel": "Lissé", + "discover.fieldNameIcons.gaugeFieldAriaLabel": "Indicateur de jauge", + "discover.fieldNameIcons.geoPointFieldAriaLabel": "Point géographique", + "discover.fieldNameIcons.geoShapeFieldAriaLabel": "Forme géométrique", + "discover.fieldNameIcons.histogramFieldAriaLabel": "Histogramme", + "discover.fieldNameIcons.ipAddressFieldAriaLabel": "Adresse IP", + "discover.fieldNameIcons.ipRangeFieldAriaLabel": "Plage d'IP", + "discover.fieldNameIcons.keywordFieldAriaLabel": "Mot-clé", + "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3", + "discover.fieldNameIcons.nestedFieldAriaLabel": "Imbriqué", + "discover.fieldNameIcons.numberFieldAriaLabel": "Nombre", + "discover.fieldNameIcons.pointFieldAriaLabel": "Point", + "discover.fieldNameIcons.rankFeatureFieldAriaLabel": "Fonctionnalité de rang", + "discover.fieldNameIcons.rankFeaturesFieldAriaLabel": "Fonctionnalités de rang", + "discover.fieldNameIcons.recordAriaLabel": "Enregistrements", + "discover.fieldNameIcons.shapeFieldAriaLabel": "Forme", + "discover.fieldNameIcons.sourceFieldAriaLabel": "Champ source", + "discover.fieldNameIcons.stringFieldAriaLabel": "Chaîne", + "discover.fieldNameIcons.textFieldAriaLabel": "Texte", + "discover.fieldNameIcons.unknownFieldAriaLabel": "Champ inconnu", + "discover.fieldNameIcons.versionFieldAriaLabel": "Version", + "unifiedDocViewer.docView.table.actions.label": "Actions", + "unifiedDocViewer.docView.table.actions.open": "Actions ouvertes", + "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "Une ou plusieurs valeurs dans ce champ sont trop longues et ne peuvent pas être recherchées ni filtrées.", + "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "Ce champ comporte une ou plusieurs valeurs mal formées qui ne peuvent pas être recherchées ni filtrées.", + "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "Une ou plusieurs valeurs dans ce champ ont été ignorées par Elasticsearch et ne peuvent pas être recherchées ni filtrées.", + "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "La valeur dans ce champ est trop longue et ne peut pas être recherchée ni filtrée.", + "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "La valeur dans ce champ est mal formée et ne peut pas être recherchée ni filtrée.", + "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "La valeur dans ce champ a été ignorée par Elasticsearch et ne peut pas être recherchée ni filtrée.", + "unifiedDocViewer.docView.table.searchPlaceHolder": "Rechercher les noms de champs", + "unifiedDocViewer.docViews.json.jsonTitle": "JSON", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "Filtrer sur le champ", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "Filtrer sur le champ", + "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "Filtrer sur la valeur", + "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "Filtrer sur la valeur", + "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "Exclure la valeur", + "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "Exclure la valeur", + "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "Contient des valeurs ignorées", + "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "Valeur ignorée", + "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "Épingler le champ", + "unifiedDocViewer.docViews.table.pinFieldLabel": "Épingler le champ", + "unifiedDocViewer.docViews.table.tableTitle": "Tableau", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "Afficher/Masquer la colonne dans le tableau", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "Afficher/Masquer la colonne dans le tableau", + "unifiedDocViewer.fieldChooser.discoverField.name": "Champ", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "Impossible de filtrer sur les champs méta", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "Impossible de filtrer sur les champs scriptés", + "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "Les champs non indexés ou les valeurs ignorées ne peuvent pas être recherchés", + "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "Désépingler le champ", + "unifiedDocViewer.docViews.table.unpinFieldLabel": "Désépingler le champ", + "unifiedDocViewer.fieldChooser.discoverField.actions": "Actions", + "unifiedDocViewer.fieldChooser.discoverField.multiField": "champ multiple", + "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "Les champs multiples peuvent avoir plusieurs valeurs.", + "unifiedDocViewer.fieldChooser.discoverField.value": "Valeur", + "unifiedDocViewer.json.codeEditorAriaLabel": "Affichage JSON en lecture seule d’un document Elasticsearch", + "unifiedDocViewer.json.copyToClipboardLabel": "Copier dans le presse-papiers", + "unifiedDocViewer.loadingJSON": "Chargement de JSON", + "unifiedDocViewer.sourceViewer.errorMessage": "Impossible de récupérer les données pour le moment. Actualisez l'onglet et réessayez.", + "unifiedDocViewer.sourceViewer.errorMessageTitle": "Une erreur s'est produite.", + "unifiedDocViewer.sourceViewer.refresh": "Actualiser", "domDragDrop.announce.cancelled": "Mouvement annulé. {label} revenu à sa position initiale", "domDragDrop.announce.cancelledItem": "Mouvement annulé. {label} revenu au groupe {groupLabel} à la position {position}", "domDragDrop.announce.dropped.combineCompatible": "{label} combiné dans le groupe {groupLabel} en {dropLabel} dans le groupe {dropGroupLabel} à la position {dropPosition} dans le calque {dropLayerNumber}", @@ -3226,7 +3212,7 @@ "guidedOnboardingPackage.gettingStarted.cards.progressLabel": "{numberCompleteSteps} étape(s) terminée(s) sur {numberSteps}", "guidedOnboardingPackage.gettingStarted.cards.siemSecurity.title": "Détecter les menaces dans {lineBreak} mes données avec SIEM", "guidedOnboardingPackage.gettingStarted.cards.completeLabel": "Guide terminé", - "guidedOnboardingPackage.gettingStarted.cards.esreSearch.title": "Créer une expérience de recherche sémantique", + "guidedOnboardingPackage.gettingStarted.cards.aiSearch.title": "Créer une expérience de recherche sémantique", "guidedOnboardingPackage.gettingStarted.cards.hostsObservability.title": "Monitorer mes indicateurs d'hôte", "guidedOnboardingPackage.gettingStarted.cards.kubernetesObservability.title": "Monitorer les clusters Kubernetes", "guidedOnboardingPackage.gettingStarted.cards.logsObservability.title": "Collecter et analyser mes logs", @@ -4154,7 +4140,7 @@ "indexPatternEditor.rollupDataView.warning.textParagraphOne": "Kibana propose un support bêta pour les vues de données basées sur les cumuls. Vous pourriez rencontrer des problèmes lors de l'utilisation de ces vues dans les recherches enregistrées, les visualisations et les tableaux de bord. Ils ne sont pas compatibles avec certaines fonctionnalités avancées, telles que Timelion et le Machine Learning.", "indexPatternEditor.rollupDataView.warning.textParagraphTwo": "Vous pouvez mettre une vue de données de cumul en correspondance avec un index de cumul et zéro index régulier ou plus. Une vue de données de cumul dispose d'indicateurs, de champs, d'intervalles et d'agrégations limités. Un index de cumul se limite aux index disposant d'une configuration de tâche ou de plusieurs tâches avec des configurations compatibles.", "indexPatternEditor.rollupIndexPattern.warning.title": "Fonctionnalité bêta", - "indexPatternEditor.saved": "'{indexPatternName}' enregistré", + "indexPatternEditor.saved": "Enregistré", "indexPatternEditor.status.noSystemIndicesLabel": "Aucun flux de données, index ni alias d'index ne correspond à votre modèle d'indexation.", "indexPatternEditor.status.noSystemIndicesWithPromptLabel": "Aucun flux de données, index ni alias d'index ne correspond à votre modèle d'indexation.", "indexPatternEditor.status.notMatchLabel.notMatchNoIndicesDetail": "Le modèle d'indexation spécifié ne correspond à aucun flux de données, index ni alias d'index.", @@ -5697,34 +5683,6 @@ "unifiedFieldList.fieldNameDescription.textField": "Texte intégral tel que le corps d'un e-mail ou la description d'un produit.", "unifiedFieldList.fieldNameDescription.unknownField": "Champ inconnu", "unifiedFieldList.fieldNameDescription.versionField": "Versions des logiciels. Prend en charge les règles de priorité de la Gestion sémantique des versions.", - "discover.fieldNameIcons.binaryAriaLabel": "Binaire", - "discover.fieldNameIcons.booleanAriaLabel": "Booléen", - "discover.fieldNameIcons.conflictFieldAriaLabel": "Conflit", - "discover.fieldNameIcons.counterFieldAriaLabel": "Indicateur de compteur", - "discover.fieldNameIcons.dateFieldAriaLabel": "Date", - "discover.fieldNameIcons.dateRangeFieldAriaLabel": "Plage de dates", - "discover.fieldNameIcons.denseVectorFieldAriaLabel": "Vecteur dense", - "discover.fieldNameIcons.flattenedFieldAriaLabel": "Lissé", - "discover.fieldNameIcons.gaugeFieldAriaLabel": "Indicateur de jauge", - "discover.fieldNameIcons.geoPointFieldAriaLabel": "Point géographique", - "discover.fieldNameIcons.geoShapeFieldAriaLabel": "Forme géométrique", - "discover.fieldNameIcons.histogramFieldAriaLabel": "Histogramme", - "discover.fieldNameIcons.ipAddressFieldAriaLabel": "Adresse IP", - "discover.fieldNameIcons.ipRangeFieldAriaLabel": "Plage d'IP", - "discover.fieldNameIcons.keywordFieldAriaLabel": "Mot-clé", - "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3", - "discover.fieldNameIcons.nestedFieldAriaLabel": "Imbriqué", - "discover.fieldNameIcons.numberFieldAriaLabel": "Nombre", - "discover.fieldNameIcons.pointFieldAriaLabel": "Point", - "discover.fieldNameIcons.rankFeatureFieldAriaLabel": "Fonctionnalité de rang", - "discover.fieldNameIcons.rankFeaturesFieldAriaLabel": "Fonctionnalités de rang", - "discover.fieldNameIcons.recordAriaLabel": "Enregistrements", - "discover.fieldNameIcons.shapeFieldAriaLabel": "Forme", - "discover.fieldNameIcons.sourceFieldAriaLabel": "Champ source", - "discover.fieldNameIcons.stringFieldAriaLabel": "Chaîne", - "discover.fieldNameIcons.textFieldAriaLabel": "Texte", - "discover.fieldNameIcons.unknownFieldAriaLabel": "Champ inconnu", - "discover.fieldNameIcons.versionFieldAriaLabel": "Version", "unifiedFieldList.fieldNameSearch.filterByNameLabel": "Rechercher les noms de champs", "unifiedFieldList.fieldPopover.addExistsFilterLabel": "Filtrer sur le champ", "unifiedFieldList.fieldPopover.deleteFieldLabel": "Supprimer le champ de la vue de données", @@ -5925,9 +5883,6 @@ "unifiedSearch.query.queryBar.indexPattern.findFilterSet": "Trouver une requête", "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "Gérer cette vue de données", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "Temporaire", - "unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "Un changement de vue de données supprime la requête SQL en cours. Sauvegardez cette recherche pour ne pas perdre de travail.", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesLabel": "Langages de requête à base de texte", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "Un changement de vue de données supprime la requête SQL en cours. Sauvegardez cette recherche pour ne pas perdre de travail.", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "Basculer sans sauvegarder", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "Ne plus afficher cet avertissement", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "Sauvegarder et basculer", @@ -7074,7 +7029,6 @@ "visualizationUiComponents.dimensionButtonIcon.colorIndicatorLabel": "Couleur de cette dimension : {hex}", "visualizationUiComponents.queryInput.queryPlaceholderKql": "{example}", "visualizationUiComponents.queryInput.queryPlaceholderLucene": "{example}", - "visualizationUiComponents.colorPicker.seriesColor.auto": "Auto", "visualizationUiComponents.colorPicker.seriesColor.label": "Couleur de la série", "visualizationUiComponents.colorPicker.tooltip.auto": "Lens choisit automatiquement des couleurs à votre place sauf si vous spécifiez une couleur personnalisée.", "visualizationUiComponents.colorPicker.tooltip.custom": "Effacez la couleur personnalisée pour revenir au mode \"Auto\".", @@ -7617,7 +7571,7 @@ "xpack.apm.tutorial.windowsServerInstructions.textPre": "1. Téléchargez le fichier .zip APM Server pour Windows via la [page de téléchargement]({downloadPageLink}).\n2. Extrayez le contenu du fichier compressé (ZIP) dans {zipFileExtractFolder}.\n3. Renommez le répertoire {apmServerDirectory} en \"APM-Server\".\n4. Ouvrez une invite PowerShell en tant qu'administrateur (faites un clic droit sur l'icône PowerShell et sélectionnez **Exécuter en tant qu'administrateur**). Si vous exécutez Windows XP, vous devrez peut-être télécharger et installer PowerShell.\n5. Dans l'invite PowerShell, exécutez les commandes suivantes pour installer le serveur APM en tant que service Windows :", "xpack.apm.unifiedSearchBar.placeholder": "Rechercher {event, select, transaction {transactions} metric {indicateurs} error {erreurs} other {transactions, erreurs et indicateurs}} (par exemple {queryExample})", "xpack.apm.waterfall.errorCount": "{errorCount, plural, one {Voir l'erreur associée} many {Voir les # erreurs associées} other {Voir les # erreurs associées}}", - "xpack.apm.waterfall.exceedsMax": "Le nombre d'éléments dans cette trace est de {traceItemCount}, ce qui est supérieur à la limite actuelle de {maxTraceItems}. Veuillez augmenter la limite pour afficher la trace complète.", + "xpack.apm.waterfall.exceedsMax": "Le nombre d'éléments dans cette trace est de {traceDocsTotal}, ce qui est supérieur à la limite actuelle de {maxTraceItems}. Veuillez augmenter la limite pour afficher la trace complète.", "xpack.apm.waterfall.spanLinks.badge": "{total} {total, plural, one {Lien d'intervalle} many {Liens d'intervalle} other {Liens d'intervalle}}", "xpack.apm.waterfall.spanLinks.tooltip.linkedChildren": "{linkedChildren} entrants", "xpack.apm.waterfall.spanLinks.tooltip.linkedParents": "{linkedParents} sortants", @@ -8315,7 +8269,6 @@ "xpack.apm.mobile.location.metrics.http.requests.title": "Le plus utilisé dans", "xpack.apm.mobile.location.metrics.launches": "La plupart des lancements", "xpack.apm.mobile.location.metrics.sessions": "La plupart des sessions", - "xpack.apm.mobile.metrics.crash.rate": "Taux de panne (pannes par minute)", "xpack.apm.mobile.metrics.http.requests": "Requêtes HTTP", "xpack.apm.mobile.metrics.load.time": "Temps de chargement de l'application le plus lent", "xpack.apm.mobile.metrics.sessions": "Sessions", @@ -11345,11 +11298,8 @@ "xpack.csp.awsIntegration.sharedCredentialsDescription": "Si vous utilisez différentes informations d'identification AWS pour différents outils ou applications, vous pouvez utiliser les profils pour définir plusieurs clés d'accès dans le même fichier de configuration.", "xpack.csp.awsIntegration.temporaryKeysDescription": "Vous pouvez configurer dans AWS des informations d'identification de sécurité temporaires pour une durée spécifiée. Elles comprennent un ID de clé d'accès, une clé d'accès secrète et un jeton de sécurité, qui peut généralement être récupéré à l'aide de GetSessionToken.", "xpack.csp.awsIntegration.temporaryKeysLabel": "Clés temporaires", - "xpack.csp.benchmarks.benchmarkEmptyState.integrationsNotFoundTitle": "Aucune intégration Benchmark trouvée", - "xpack.csp.benchmarks.benchmarkEmptyState.integrationsNotFoundWithFiltersTitle": "Nous n'avons trouvé aucune intégration Benchmark avec les filtres ci-dessus.", "xpack.csp.benchmarks.benchmarkSearchField.searchPlaceholder": "Rechercher par nom d'intégration", "xpack.csp.benchmarks.benchmarksPageHeader.addIntegrationButtonLabel": "Ajouter une intégration", - "xpack.csp.benchmarks.benchmarksPageHeader.benchmarkIntegrationsTitle": "Intégrations Benchmark", "xpack.csp.benchmarks.benchmarksTable.agentPolicyColumnTitle": "Politique d'agent", "xpack.csp.benchmarks.benchmarksTable.createdAtColumnTitle": "Créé", "xpack.csp.benchmarks.benchmarksTable.createdByColumnTitle": "Créé par", @@ -11452,13 +11402,11 @@ "xpack.csp.findings.findingsByResourceTable.postureScoreColumnLabel": "Score du niveau", "xpack.csp.findings.findingsErrorToast.searchFailedTitle": "Échec de la recherche", "xpack.csp.findings.findingsFlyout.jsonTabTitle": "JSON", - "xpack.csp.findings.findingsFlyout.overviewTab.actualTitle": "Réel", "xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle": "Section CIS", "xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle": "Valeur par défaut", "xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle": "Détails", "xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle": "Évalué à", "xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle": "Preuve", - "xpack.csp.findings.findingsFlyout.overviewTab.expectedTitle": "Attendus", "xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle": "Sources du framework", "xpack.csp.findings.findingsFlyout.overviewTab.impactTitle": "Impact", "xpack.csp.findings.findingsFlyout.overviewTab.indexTitle": "Index", @@ -11563,7 +11511,6 @@ "xpack.csp.rules.manageIntegrationButtonLabel": "Gérer l'intégration", "xpack.csp.rules.ruleFlyout.overviewTabLabel": "Aperçu", "xpack.csp.rules.ruleFlyout.remediationTabLabel": "Résolution", - "xpack.csp.rules.rulesPageHeader.benchmarkIntegrationsButtonLabel": "Intégrations Benchmark", "xpack.csp.rules.rulesPageSharedValues.benchmarkTitle": "Benchmark", "xpack.csp.rules.rulesPageSharedValues.deploymentTypeTitle": "Type de déploiement", "xpack.csp.rules.rulesPageSharedValues.integrationTitle": "Intégration", @@ -12167,19 +12114,19 @@ "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "Est-ce que des nœuds Enterprise Search sont en cours d'exécution dans votre déploiement cloud ? {deploymentSettingsLink}", "xpack.enterpriseSearch.errorConnectingState.description1": "Impossible d'établir une connexion à Enterprise Search au niveau de l'URL hôte {enterpriseSearchUrl} en raison de l'erreur suivante :", "xpack.enterpriseSearch.errorConnectingState.description2": "Vérifiez que l'URL hôte est correctement configurée dans {configFile}.", - "xpack.enterpriseSearch.esre.elser.description": "Déployez le {elser} sans effort pour des capacités de recherche sémantique de texte instantanées en quelques clics seulement. Ce modèle développe votre document et votre texte de requête utilisant le champ \"text_expansion\", ce qui vous permet immédiatement de faire des recherches transparentes.", - "xpack.enterpriseSearch.esre.elserPanel.step2.description": "Après avoir créé un index, sélectionnez-le et cliquez sur l’onglet intitulé \"{pipelinesName}\".", - "xpack.enterpriseSearch.esre.esreDocsSection.description": "Pour en savoir plus sur comment débuter avec ESRE et tester ces outils avec des exemples concrets, consultez la {esreDocumentation}.", - "xpack.enterpriseSearch.esre.esreDocsSection.faq.description": "Découvrez ce qu’est (et n’est pas) ESRE avec ces {frequentlyAskedQuestions}.", - "xpack.enterpriseSearch.esre.esreDocsSection.help.description": "Besoin d'aide ? Consultez {discussForum}.", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.description": "Ces sujets sont complexes, c’est pourquoi nous avons choisi quelques {learningTopics} pour vous aider à démarrer.", - "xpack.enterpriseSearch.esre.measurePerformanceSection.description": "Utilisez les tableaux de bord et outils de {behavioralAnalytics} pour visualiser le comportement des utilisateurs et mesurer l’impact de vos modifications.", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.description": "Consultez les {behavioralAnalytics} et créez votre première collection", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.description": "Utilisez des outils de traitement du langage naturel (NLP) tels que l’analyse des sentiments, la synthèse ou la reconnaissance d’entités nommées pour améliorer la pertinence de vos résultats de recherche. NLP utilise plusieurs {supportedMlModels} que vous pouvez charger pour analyser et enrichir intelligemment des documents avec des champs supplémentaires.", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.description": "Accédez à l’onglet {pipelinesName} de votre index pour créer un pipeline d’inférence qui utilise votre modèle déployé.", - "xpack.enterpriseSearch.esre.rrfRankingPanel.description": "Utilisez {rrf} pour combiner des classements de plusieurs ensembles de résultats avec différents indicateurs de pertinence, sans avoir besoin d’ajustement.", - "xpack.enterpriseSearch.esre.vectorSearchPanel.description": "Utilisez des {vectorDbCapabilities} en ajoutant des incorporations de vos modèles ML. Déployez des modèles entraînés sur des nœuds de ML Elastic et configurez des pipelines d’inférence pour ajouter automatiquement des incorporations quand vous ingérez des documents, afin de pouvoir utiliser la méthode de recherche vectorielle kNN dans _search.", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.description": "Accédez à l’onglet {pipelinesName} de votre index pour créer un pipeline d’inférence qui utilise votre modèle déployé.", + "xpack.enterpriseSearch.aiSearch.elser.description": "Déployez le {elser} sans effort pour des capacités de recherche sémantique de texte instantanées en quelques clics seulement. Ce modèle développe votre document et votre texte de requête utilisant le champ \"text_expansion\", ce qui vous permet immédiatement de faire des recherches transparentes.", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description": "Après avoir créé un index, sélectionnez-le et cliquez sur l’onglet intitulé \"{pipelinesName}\".", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.description": "Pour en savoir plus sur la façon de démarrer et de tester ces outils avec des exemples concrets, visitez le {searchLab}.", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.description": "Visitez la {aiSearchDoc}.", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.description": "Besoin d'aide ? Consultez {discussForum}.", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.description": "Le {searchLabsRepo} contient de nombreuses resources comme des notebooks ou des exemples d'application", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.description": "Utilisez les tableaux de bord et outils de {behavioralAnalytics} pour visualiser le comportement des utilisateurs et mesurer l’impact de vos modifications.", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.description": "Consultez les {behavioralAnalytics} et créez votre première collection", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.description": "Utilisez des outils de traitement du langage naturel (NLP) tels que l’analyse des sentiments, la synthèse ou la reconnaissance d’entités nommées pour améliorer la pertinence de vos résultats de recherche. NLP utilise plusieurs {supportedMlModels} que vous pouvez charger pour analyser et enrichir intelligemment des documents avec des champs supplémentaires.", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.description": "Accédez à l’onglet {pipelinesName} de votre index pour créer un pipeline d’inférence qui utilise votre modèle déployé.", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.description": "Utilisez {rrf} pour combiner des classements de plusieurs ensembles de résultats avec différents indicateurs de pertinence, sans avoir besoin d’ajustement.", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description": "Utilisez des {vectorDbCapabilities} en ajoutant des incorporations de vos modèles ML. Déployez des modèles entraînés sur des nœuds de ML Elastic et configurez des pipelines d’inférence pour ajouter automatiquement des incorporations quand vous ingérez des documents, afin de pouvoir utiliser la méthode de recherche vectorielle kNN dans _search.", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description": "Accédez à l’onglet {pipelinesName} de votre index pour créer un pipeline d’inférence qui utilise votre modèle déployé.", "xpack.enterpriseSearch.index.connector.syncRules.description": "Inclure ou exclure les éléments de haut niveau, les types de fichier et les chemins (de fichier ou de répertoire) pour\n effectuer la synchronisation à partir de {indexName}. Tout est inclus par défaut. Chaque document est\n testé par rapport aux règles ci-dessous, et la première règle qui correspond est appliquée.", "xpack.enterpriseSearch.inferencePipelineCard.action.delete.disabledDescription": "Ce pipeline d'inférence ne peut pas être supprimé, car il est utilisé dans plusieurs pipelines [{indexReferences}]. Pour le supprimer, vous devez le détacher des autres pipelines pour ne garder qu'un seul pipeline d'ingestion.", "xpack.enterpriseSearch.inferencePipelineCard.deleteConfirm.description": "Vous êtes en train de retirer le pipeline \"{pipelineName}\" du pipeline d'inférence de Machine Learning pour le supprimer.", @@ -13670,19 +13617,6 @@ "xpack.enterpriseSearch.content.ml_inference.text_embedding": "Incorporation de texte vectoriel dense", "xpack.enterpriseSearch.content.ml_inference.text_expansion": "Expansion de texte ELSER", "xpack.enterpriseSearch.content.ml_inference.zero_shot_classification": "Classification de texte Zero-Shot", - "xpack.enterpriseSearch.content.nativeConnectors.azureBlob.name": "Stockage Blob Azure", - "xpack.enterpriseSearch.content.nativeConnectors.confluence.name": "Confluence Cloud & Server", - "xpack.enterpriseSearch.content.nativeConnectors.customConnector.name": "Connecteur personnalisé", - "xpack.enterpriseSearch.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", - "xpack.enterpriseSearch.content.nativeConnectors.jira.name": "Jira Cloud & Server", - "xpack.enterpriseSearch.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", - "xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB", - "xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL", - "xpack.enterpriseSearch.content.nativeConnectors.networkDrive.name": "Lecteur réseau", - "xpack.enterpriseSearch.content.nativeConnectors.oracle.name": "Oracle", - "xpack.enterpriseSearch.content.nativeConnectors.postgresql.name": "PostgreSQL", - "xpack.enterpriseSearch.content.nativeConnectors.s3.name": "S3", - "xpack.enterpriseSearch.content.nativeConnectors.sharepoint_online.name": "SharePoint en ligne", "xpack.enterpriseSearch.content.navTitle": "Contenu", "xpack.enterpriseSearch.content.new_index.apiDescription": "Utilisez l’API pour ajouter des documents par programme à un index Elasticsearch. Commencez par créer votre index.", "xpack.enterpriseSearch.content.new_index.apiTitle": "Nouvel index de recherche", @@ -14159,75 +14093,75 @@ "xpack.enterpriseSearch.errorConnectingState.troubleshootAuth": "Vérifiez votre authentification utilisateur :", "xpack.enterpriseSearch.errorConnectingState.troubleshootAuthNative": "Vous devez vous authentifier à l'aide d'une authentification native d'Elasticsearch, de SSO/SAML ou d'OpenID Connect.", "xpack.enterpriseSearch.errorConnectingState.troubleshootAuthSAML": "Si vous utilisez un fournisseur de SSO externe, tel que SAML ou OpenID Connect, votre domaine SAML/OIDC doit également être configuré sur Enterprise Search.", - "xpack.enterpriseSearch.esre.description": "Le kit d’outils permettant aux développeurs de créer des applications d’IA optimisées pour la recherche à l’aide de la plateforme Elastic.", - "xpack.enterpriseSearch.esre.elser.description.elserLinkText": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.esre.elserAccordion.description": "Fonctionnalités de recherche sémantique instantanée", - "xpack.enterpriseSearch.esre.elserAccordion.title": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.esre.elserPanel.step1.buttonLabel": "Créer un index", - "xpack.enterpriseSearch.esre.elserPanel.step1.title": "Créer un index", - "xpack.enterpriseSearch.esre.elserPanel.step2.description.pipelinesName": "Pipelines", - "xpack.enterpriseSearch.esre.elserPanel.step2.title": "Accédez à l’onglet Pipelines d’un index", - "xpack.enterpriseSearch.esre.elserPanel.step3.description": "Localisez le panneau qui vous permet de déployer ELSER en un clic et créez un pipeline d’inférence à l’aide de ce modèle.", - "xpack.enterpriseSearch.esre.elserPanel.step3.title": "Suivez les instructions à l’écran pour déployer ELSER", - "xpack.enterpriseSearch.esre.esreDocsSection.description.esreLinkText": "Documentation ESRE", - "xpack.enterpriseSearch.esre.esreDocsSection.faq.title": "FAQ", - "xpack.enterpriseSearch.esre.esreDocsSection.help.title": "Aide", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.discussForumLinkText": "Forum de discussion ESRE", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.frequentlyAskedQuestionsLinkText": "questions fréquentes", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.learningTopicsLinkText": "thèmes de formation", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.title": "Apprendre", - "xpack.enterpriseSearch.esre.esreDocsSection.title": "Approfondissez avec les documents ESRE", - "xpack.enterpriseSearch.esre.guide.description": "L’Elasticsearch Relevance Engine™ (ESRE) permet aux développeurs de créer des applications d’IA optimisées pour la recherche à l’aide de la plateforme Elastic. ESRE est un ensemble d'outils et de fonctionnalités qui comprennent notre modèle ML entraîné ELSER, notre recherche vectorielle et nos capacités d'intégration, ainsi que le classement RRF pour combiner la recherche vectorielle et la recherche textuelle.", - "xpack.enterpriseSearch.esre.guide.pageTitle": "Améliorez vos recherches avec ESRE", - "xpack.enterpriseSearch.esre.linearCombinationAccordion.description": "Résultats pondérés de plusieurs classements", - "xpack.enterpriseSearch.esre.linearCombinationAccordion.title": "Combinaison linéaire", - "xpack.enterpriseSearch.esre.linearCombinationPanel.description": "Utilisée pour calculer un score de similarité ou une distance entre des points de données. Combine les attributs ou fonctionnalités à l’aide de pondérations, ce qui permet de personnaliser les facteurs de pertinence.", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step1.knnSearchCombineLinkText": "Combiner les kNN (k plus proches voisins) approximatifs avec d'autres caractéristiques", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step1.title": "Découvrez comment utiliser la combinaison linéaire dans les requêtes _search", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step2.buttonLabel": "Ouvrir Console", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step2.title": "Essayez-le dès maintenant dans Console", - "xpack.enterpriseSearch.esre.measurePerformanceSection.behavioralAnalyticsLinkText": "Behavioral Analytics", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.behavioralAnalyticsLinkText": "Behavioral Analytics", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.title": "Créer une collection", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step2.description": "Après avoir créé une collection, suivez les directives concernant l’intégration de notre outil de suivi dans votre application ou site web.", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step2.title": "Intégrer l’outil de suivi d’analyse", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step3.description": "Nos tableaux de bord et outils vous aident à visualiser le comportement de vos utilisateurs finaux et à mesurer les performances de vos applications de recherche.", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step3.title": "Consulter votre tableau de bord", - "xpack.enterpriseSearch.esre.measurePerformanceSection.title": "Mesurer vos performances", - "xpack.enterpriseSearch.esre.navTitle": "ESRE", - "xpack.enterpriseSearch.esre.nlpEnrichmentAccordion.description": "Enrichissement des données pertinentes avec les modèles de ML entraînés", - "xpack.enterpriseSearch.esre.nlpEnrichmentAccordion.title": "Enrichissement de NLP", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.description.supportedMlModelsLinkText": "Modèles de ML compatibles", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.buttonLabel": "Affichez les modèles entraînés", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.guideToTrainedModelsLinkText": "Guide sur les modèles entraînés", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.supportedNlpModelsLinkText": "Modèles NLP compatibles", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.title": "Apprenez à charger des modèles de ML", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step2.buttonLabel": "Créez un index", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step2.title": "Créez un index", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.description.pipelinesName": "Pipelines", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.title": "Créez un pipeline d’inférence de ML", - "xpack.enterpriseSearch.esre.productName": "ESRE", - "xpack.enterpriseSearch.esre.rankAggregationSection.description": "Méthodes facultatives pour fusionner ou combiner différents classements pour obtenir une meilleure performance générale de classement.", - "xpack.enterpriseSearch.esre.rankAggregationSection.title": "Utiliser une méthode d’agrégation de classement", - "xpack.enterpriseSearch.esre.rrfRankingAccordion.description": "Ceci combine intelligemment les classements sans configuration", - "xpack.enterpriseSearch.esre.rrfRankingAccordion.title": "Classement hybride avec la RRF", - "xpack.enterpriseSearch.esre.rrfRankingPanel.rrfLinkText": "Fusion des rangs réciproques (RRF)", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step1.rrfDocsLinkText": "Documentations sur la fusion des rangs réciproques (RRF)", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step1.title": "Découvrez des exemples de l’utilisation de la RRF dans les requêtes _search", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step2.buttonLabel": "Ouvrir Console", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step2.title": "Essayez-le dès maintenant dans Console", - "xpack.enterpriseSearch.esre.semanticSearch.description": "ESRE combine votre choix parmi ces outils de récupération d'informations.", - "xpack.enterpriseSearch.esre.semanticSearch.title": "Configurer une recherche sémantique", - "xpack.enterpriseSearch.esre.vectorSearchAccordion.description": "Des recherches de similarités puissantes pour les données non structurées", - "xpack.enterpriseSearch.esre.vectorSearchAccordion.title": "Recherche vectorielle", - "xpack.enterpriseSearch.esre.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "Fonctionnalités de bases de données vectorielles d’Elasticsearch", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.buttonLabel": "Affichez les modèles entraînés", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "Guide sur les modèles entraînés", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.title": "Apprenez à charger des modèles de ML", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step2.buttonLabel": "Créez un index", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step2.title": "Créez un index", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.description.pipelinesName": "Pipelines", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.title": "Créez un pipeline d’inférence de ML", + "xpack.enterpriseSearch.aiSearch.description": "Le kit d’outils permettant aux développeurs de créer des applications d’IA optimisées pour la recherche à l’aide de la plateforme Elastic.", + "xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText": "Elastic Learned Sparse Encoder v2", + "xpack.enterpriseSearch.aiSearch.elserAccordion.description": "Fonctionnalités de recherche sémantique instantanée", + "xpack.enterpriseSearch.aiSearch.elserAccordion.title": "Elastic Learned Sparse Encoder", + "xpack.enterpriseSearch.aiSearch.elserPanel.step1.buttonLabel": "Créer un index", + "xpack.enterpriseSearch.aiSearch.elserPanel.step1.title": "Créer un index", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description.pipelinesName": "Pipelines", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.title": "Accédez à l’onglet Pipelines d’un index", + "xpack.enterpriseSearch.aiSearch.elserPanel.step3.description": "Localisez le panneau qui vous permet de déployer ELSER en un clic et créez un pipeline d’inférence à l’aide de ce modèle.", + "xpack.enterpriseSearch.aiSearch.elserPanel.step3.title": "Suivez les instructions à l’écran pour déployer ELSER", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.description.searchLabsLinkText": "Search Labs", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.title": "Documentation", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.title": "Aide", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.helpLinkText": "notre forum de discussion ou le Slack communautaire Elastic", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.aiSearchDocLinkText": "documetation Elastic", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.searchLabsRepoLinkText": "dépôt GitHub du Search Labs", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.title": "Apprendre", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.title": "En savoir plus sur la recherche basée sur l'IA", + "xpack.enterpriseSearch.aiSearch.guide.description": "Créez des applications de recherche basées IA à l'aide de la plateforme Elastic, en utilisant notre modèle ML exclusif ELSER, nos capacités de recherche vectorielle, ainsi que le modèle de ranking RRF pour combiner la recherche vectorielle et la recherche textuelle.", + "xpack.enterpriseSearch.aiSearch.guide.pageTitle": "Améliorez vos recherches avec l'IA", + "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.description": "Résultats pondérés de plusieurs classements", + "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.title": "Combinaison linéaire", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.description": "Utilisée pour calculer un score de similarité ou une distance entre des points de données. Combine les attributs ou fonctionnalités à l’aide de pondérations, ce qui permet de personnaliser les facteurs de pertinence.", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step1.knnSearchCombineLinkText": "Combiner les kNN (k plus proches voisins) approximatifs avec d'autres caractéristiques", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step1.title": "Découvrez comment utiliser la combinaison linéaire dans les requêtes _search", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step2.buttonLabel": "Ouvrir Console", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step2.title": "Essayez-le dès maintenant dans Console", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.behavioralAnalyticsLinkText": "Behavioral Analytics", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.behavioralAnalyticsLinkText": "Behavioral Analytics", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.title": "Créer une collection", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step2.description": "Après avoir créé une collection, suivez les directives concernant l’intégration de notre outil de suivi dans votre application ou site web.", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step2.title": "Intégrer l’outil de suivi d’analyse", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step3.description": "Nos tableaux de bord et outils vous aident à visualiser le comportement de vos utilisateurs finaux et à mesurer les performances de vos applications de recherche.", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step3.title": "Consulter votre tableau de bord", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.title": "Mesurer vos performances", + "xpack.enterpriseSearch.aiSearch.navTitle": "AI Search", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentAccordion.description": "Enrichissement des données pertinentes avec les modèles de ML entraînés", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentAccordion.title": "Enrichissement de NLP", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.description.supportedMlModelsLinkText": "Modèles de ML compatibles", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.buttonLabel": "Affichez les modèles entraînés", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.guideToTrainedModelsLinkText": "Guide sur les modèles entraînés", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.supportedNlpModelsLinkText": "Modèles NLP compatibles", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.title": "Apprenez à charger des modèles de ML", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step2.buttonLabel": "Créez un index", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step2.title": "Créez un index", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.description.pipelinesName": "Pipelines", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.title": "Créez un pipeline d’inférence de ML", + "xpack.enterpriseSearch.aiSearch.productName": "AI Search", + "xpack.enterpriseSearch.aiSearch.rankAggregationSection.description": "Méthodes facultatives pour fusionner ou combiner différents classements pour obtenir une meilleure performance générale de classement.", + "xpack.enterpriseSearch.aiSearch.rankAggregationSection.title": "Utiliser une méthode d’agrégation de classement", + "xpack.enterpriseSearch.aiSearch.rrfRankingAccordion.description": "Ceci combine intelligemment les classements sans configuration", + "xpack.enterpriseSearch.aiSearch.rrfRankingAccordion.title": "Classement hybride avec la RRF", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.rrfLinkText": "Fusion des rangs réciproques (RRF)", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step1.rrfDocsLinkText": "Documentations sur la fusion des rangs réciproques (RRF)", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step1.title": "Découvrez des exemples de l’utilisation de la RRF dans les requêtes _search", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.buttonLabel": "Ouvrir Console", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.title": "Essayez-le dès maintenant dans Console", + "xpack.enterpriseSearch.aiSearch.semanticSearch.description": "ESRE combine votre choix parmi ces outils de récupération d'informations.", + "xpack.enterpriseSearch.aiSearch.semanticSearch.title": "Configurer une recherche sémantique", + "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.description": "Des recherches de similarités puissantes pour les données non structurées", + "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.title": "Recherche vectorielle", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "Fonctionnalités de bases de données vectorielles d’Elasticsearch", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.buttonLabel": "Affichez les modèles entraînés", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "Guide sur les modèles entraînés", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.title": "Apprenez à charger des modèles de ML", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.buttonLabel": "Créez un index", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.title": "Créez un index", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description.pipelinesName": "Pipelines", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.title": "Créez un pipeline d’inférence de ML", "xpack.enterpriseSearch.guideConfig.addDataStep.description": "Ingérez vos données, créez un index et enrichissez vos données avec des pipelines d'ingestion et d'inférence personnalisables.", "xpack.enterpriseSearch.guideConfig.addDataStep.title": "Ajouter des données", "xpack.enterpriseSearch.guideConfig.description": "Nous vous aiderons à créer une expérience de recherche avec vos données à l'aide du robot d'indexation, des connecteurs et des API d'Elastic.", @@ -14293,40 +14227,6 @@ "xpack.enterpriseSearch.licenseDocumentationLink": "En savoir plus sur les fonctionnalités incluses dans la licence", "xpack.enterpriseSearch.licenseManagementLink": "Gérer votre licence", "xpack.enterpriseSearch.nameLabel": "Nom", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.collectionLabel": "Collection", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.directConnectionLabel": "Connexion directe", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.hostLabel": "Nom d'hôte du serveur", - "xpack.enterpriseSearch.nativeConnectors.mongodb.name": "MongoDB", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.hostLabel": "Hôte", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.passwordLabel": "Mot de passe", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.retriesLabel": "Nouvelles tentatives par requête", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.rowsFetchedLabel": "Lignes extraites par requête", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.schemaLabel": "Schéma", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.tablesLabel": "Liste de tables séparées par des virgules", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.usernameLabel": "Nom d'utilisateur", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.validateHostLabel": "Valider l’hôte", - "xpack.enterpriseSearch.nativeConnectors.mssql.name": "Microsoft SQL", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.hostLabel": "Hôte", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.passwordLabel": "Mot de passe", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.retriesLabel": "Nouvelles tentatives par requête", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.rowsFetchedLabel": "Lignes extraites par requête", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.tablesLabel": "Liste de tables séparées par des virgules", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.usernameLabel": "Nom d'utilisateur", - "xpack.enterpriseSearch.nativeConnectors.mysql.name": "MySQL", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.hostLabel": "Hôte", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.retriesLabel": "Nouvelles tentatives par requête", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.rowsFetchedLabel": "Lignes extraites par requête", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.tablesLabel": "Liste de tables séparées par des virgules", - "xpack.enterpriseSearch.nativeConnectors.postgresql.name": "PostgreSQL", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.clientIdLabel": "ID client", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.secretValueLabel": "Valeur secrète", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.siteCollectionsLabel": "Liste de sites séparées par des virgules", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.siteCollectionsTooltip": "Une liste de sites séparés par des virgules dont les données doivent être ingérées. Utilisez \"*\" pour inclure tous les sites disponibles.", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.tenantIdLabel": "ID locataire", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.tenantNameLabel": "Nom du locataire", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.textExtractionServiceLabel": "Utiliser un service d’extraction de texte", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.textExtractionServiceTooltip": "Nécessite un déploiement distinct du service d’extraction de données d’Elastic. Nécessite également que les paramètres de pipeline désactivent l’extraction de texte.", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.name": "SharePoint en ligne", "xpack.enterpriseSearch.nativeLabel": "Natif", "xpack.enterpriseSearch.nav.analyticsCollections.explorerTitle": "Explorer", "xpack.enterpriseSearch.nav.analyticsCollections.integrationTitle": "Intégration", @@ -14338,8 +14238,8 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "Paramètres", "xpack.enterpriseSearch.nav.contentTitle": "Contenu", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "Aperçu", - "xpack.enterpriseSearch.nav.esreTitle": "ESRE", + "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "Guide de démarrage", + "xpack.enterpriseSearch.nav.aiSearchTitle": "AI Search", "xpack.enterpriseSearch.nav.searchApplicationsTitle": "Applications de recherche", "xpack.enterpriseSearch.nav.searchIndicesTitle": "Index", "xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search", @@ -14512,9 +14412,6 @@ "xpack.enterpriseSearch.searchExperiences.navTitle": "Expériences de recherche", "xpack.enterpriseSearch.searchExperiences.productDescription": "Construisez une expérience de recherche attrayante et intuitive sans perdre votre temps à tout réinventer.", "xpack.enterpriseSearch.searchExperiences.productName": "Expériences de recherche", - "xpack.enterpriseSearch.server.connectors.configuration.error": "Connecteur introuvable", - "xpack.enterpriseSearch.server.connectors.scheduling.error": "Document introuvable", - "xpack.enterpriseSearch.server.connectors.serviceType.error": "Document introuvable", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionExistsError": "Le nom de collection existe déjà. Choisissez un autre nom.", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionNotFoundErrorMessage": "Collection d'analyses introuvable", "xpack.enterpriseSearch.server.routes.addConnector.connectorExistsError": "Le connecteur ou l'index existe déjà", @@ -16648,8 +16545,6 @@ "xpack.fleet.policyDetailsPackagePolicies.createFirstTitle": "Ajouter votre première intégration", "xpack.fleet.policyForm.deletePolicyActionText": "Supprimer la stratégie", "xpack.fleet.policyForm.deletePolicyActionText.disabled": "La politique d'agent avec les politiques de package géré ne peut pas être supprimée.", - "xpack.fleet.policyForm.deletePolicyGroupDescription": "Les données existantes ne sont pas supprimées.", - "xpack.fleet.policyForm.deletePolicyGroupTitle": "Supprimer la stratégie", "xpack.fleet.policyForm.generalSettingsGroupDescription": "Attribuez un nom et ajoutez une description à votre stratégie d'agent.", "xpack.fleet.policyForm.generalSettingsGroupTitle": "Paramètres généraux", "xpack.fleet.renameAgentTags.errorNotificationTitle": "La balise n’a pas pu être renommée", @@ -17326,10 +17221,7 @@ "xpack.idxMgmt.breadcrumb.editTemplateLabel": "Modifier le modèle", "xpack.idxMgmt.breadcrumb.homeLabel": "Gestion des index", "xpack.idxMgmt.breadcrumb.templatesLabel": "Modèles", - "xpack.idxMgmt.componentTemplate.breadcrumb.componentTemplatesLabel": "Modèles de composants", - "xpack.idxMgmt.componentTemplate.breadcrumb.createComponentTemplateLabel": "Créer un modèle de composant", "xpack.idxMgmt.componentTemplate.breadcrumb.editComponentTemplateLabel": "Modifier le modèle de composant", - "xpack.idxMgmt.componentTemplate.breadcrumb.homeLabel": "Gestion des index", "xpack.idxMgmt.componentTemplateClone.loadComponentTemplateTitle": "Erreur lors du chargement du modèle de composant \"{sourceComponentTemplateName}\".", "xpack.idxMgmt.componentTemplateDetails.aliasesTabTitle": "Alias", "xpack.idxMgmt.componentTemplateDetails.cloneActionLabel": "Cloner", @@ -17447,7 +17339,6 @@ "xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "Nombre cumulatif d'index de sauvegarde créés pour le flux de données", "xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "Intégrité", "xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "Intégrité des index de sauvegarde actuels du flux de données", - "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "Aucun", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "Stratégie de cycle de vie des index", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "Stratégie de cycle de vie de l'index qui gère les données du flux de données", "xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "Modèle d'index", @@ -18645,7 +18536,7 @@ "xpack.infra.logSourceConfiguration.missingDataViewsLabel": "Type de données manquant {indexPatternId}", "xpack.infra.logSourceConfiguration.missingMessageFieldErrorMessage": "La vue de données doit contenir un champ {messageField}.", "xpack.infra.logSourceErrorPage.savedObjectNotFoundErrorMessage": "Impossible de localiser ce {savedObjectType} : {savedObjectId}", - "xpack.infra.metadataEmbeddable.errorMessage": "Une erreur s'est produite lors du chargement des données. Essayez de {reload} et d'ouvrir à nouveau les détails de l'hôte.", + "xpack.infra.metadataEmbeddable.errorMessage": "Une erreur s'est produite lors du chargement des données. Essayez de {refetch} et d'ouvrir à nouveau les détails de l'hôte.", "xpack.infra.metrics.alertDetailsAppSection.criterion.subtitle": "Dernière {lookback} {timeLabel}", "xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError": "Il est possible que cette règle signale {matchedGroups} moins que prévu, car la requête de filtre comporte une correspondance pour {groupCount, plural, one {ce champ} many {ces champs} other {ces champs}}. Pour en savoir plus, consultez notre {filteringAndGroupingLink}.", "xpack.infra.metrics.alertFlyout.customEquationEditor.aggregationLabel": "Agrégation {name}", @@ -18747,7 +18638,6 @@ "xpack.infra.assetDetailsEmbeddable.displayName": "Détails de ressource", "xpack.infra.assetDetailsEmbeddable.title": "Détails de ressource", "xpack.infra.assetDetailsEmbeddable.overview.kpi.cpuUsage.title": "Utilisation CPU", - "xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title": "Utilisation de l’espace disque", "xpack.infra.assetDetailsEmbeddable.overview.kpi.memoryUsage.title": "Utilisation mémoire", "xpack.infra.assetDetailsEmbeddable.overview.kpi.normalizedLoad1m.title": "Charge normalisée", "xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average": "Moyenne", @@ -18854,7 +18744,6 @@ "xpack.infra.hostsViewPage.metrics.tooltip.tx": "Nombre d'octets envoyés par seconde sur les interfaces publiques des hôtes.", "xpack.infra.hostsViewPage.table.addFilter": "Ajouter un filtre", "xpack.infra.hostsViewPage.table.cpuUsageColumnHeader": "Utilisation CPU (moy.)", - "xpack.infra.hostsViewPage.table.diskSpaceUsageColumnHeader": "Utilisation de l’espace disque (moy.)", "xpack.infra.hostsViewPage.table.memoryFreeColumnHeader": "Sans mémoire (moy.)", "xpack.infra.hostsViewPage.table.memoryUsageColumnHeader": "Utilisation de la mémoire (moy.)", "xpack.infra.hostsViewPage.table.nameColumnHeader": "Nom", @@ -18880,7 +18769,6 @@ "xpack.infra.hostsViewPage.tabs.metricsCharts.diskIOWrite": "Entrées et sorties par seconde en écriture sur le disque", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskReadThroughput": "Rendement de lecture du disque", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceAvailable": "Espace disque disponible", - "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed": "Utilisation de l’espace disque", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskWriteThroughput": "Rendement d’écriture du disque", "xpack.infra.hostsViewPage.tabs.metricsCharts.memoryFree": "Sans mémoire", "xpack.infra.hostsViewPage.tabs.metricsCharts.memoryUsage": "Utilisation mémoire", @@ -21140,7 +21028,6 @@ "xpack.lens.indexPattern.percentFormatLabel": "Pourcent", "xpack.lens.indexPattern.percentile": "Centile", "xpack.lens.indexPattern.percentile.documentation.quick": "\n La plus grande valeur qui est inférieure à n pour cent des valeurs présentes dans tous les documents.\n ", - "xpack.lens.indexPattern.percentile.errorMessage": "Le centile doit être un entier compris entre 1 et 99", "xpack.lens.indexPattern.percentile.percentileRanksValue": "Valeur des rangs centiles", "xpack.lens.indexPattern.percentile.percentileValue": "Centile", "xpack.lens.indexPattern.percentile.signature": "champ : chaîne, [percentile] : nombre", @@ -24014,17 +23901,11 @@ "xpack.ml.entityFilter.addFilterTooltip": "Ajouter un filtre", "xpack.ml.entityFilter.removeFilterTooltip": "Supprimer le filtre", "xpack.ml.logRateAnalysis.pageHeader": "Expliquer les pics de taux de log", - "xpack.ml.explorer.addToDashboard.anomalyCharts.dashboardsTitle": "Ajouter des graphiques d'anomalies aux tableaux de bord", "xpack.ml.explorer.addToDashboard.anomalyCharts.maxSeriesToPlotLabel": "Nombre maximal de séries à tracer", - "xpack.ml.explorer.addToDashboard.cancelButtonLabel": "Annuler", - "xpack.ml.explorer.addToDashboard.selectDashboardsLabel": "Sélectionner les tableaux de bord :", - "xpack.ml.explorer.addToDashboard.swimlanes.dashboardsTitle": "Ajouter un couloir à un tableau de bord", - "xpack.ml.explorer.addToDashboard.swimlanes.selectSwimlanesLabel": "Sélectionner la vue de couloir :", "xpack.ml.explorer.addToDashboardLabel": "Ajouter au tableau de bord", "xpack.ml.explorer.annotationsErrorCallOutTitle": "Une erreur s'est produite lors du chargement des annotations :", "xpack.ml.explorer.annotationsErrorTitle": "Annotations", "xpack.ml.explorer.anomalies.actionsAriaLabel": "Actions", - "xpack.ml.explorer.anomalies.actionsPopoverLabel": "Graphiques d'anomalies", "xpack.ml.explorer.anomalies.addToDashboardLabel": "Ajouter au tableau de bord", "xpack.ml.explorer.anomaliesTitle": "Anomalies", "xpack.ml.explorer.anomalyTimelinePopoverAdvancedExplanation": "Les scores d'anomalies affichés dans chaque section d'Anomaly Explorer (Explorateur d'anomalies) peuvent varier légèrement. Cette disparité s'explique par le fait que, pour chaque tâche, sont consignés les résultats de groupe, les résultats de groupe généraux, les résultats d'influenceur et les résultats d'enregistrements. Les scores d'anomalies sont générés pour chaque type de résultat. Le couloir général affiche le score maximal des groupes globaux pour chaque bloc. Lorsque vous affichez un couloir par tâche, il montre le score maximal du groupe dans chaque bloc. Lorsque vous choisissez l'affichage par influenceur, il montre le score maximal d'influenceur dans chaque bloc.", @@ -24046,10 +23927,6 @@ "xpack.ml.explorer.charts.viewInMapsLabel": "Afficher", "xpack.ml.explorer.charts.viewLabel": "Afficher", "xpack.ml.explorer.clearSelectionLabel": "Effacer la sélection", - "xpack.ml.explorer.dashboardsTable.actionsHeader": "Actions", - "xpack.ml.explorer.dashboardsTable.descriptionColumnHeader": "Description", - "xpack.ml.explorer.dashboardsTable.editActionName": "Ajouter au tableau de bord", - "xpack.ml.explorer.dashboardsTable.titleColumnHeader": "Titre", "xpack.ml.explorer.distributionChart.anomalyScoreLabel": "score d'anomalies", "xpack.ml.explorer.distributionChart.entityLabel": "entité", "xpack.ml.explorer.distributionChart.typicalLabel": "typique", @@ -24216,13 +24093,7 @@ "xpack.ml.jobSelector.noResultsForJobLabel": "Aucun résultat", "xpack.ml.jobSelector.selectAllGroupLabel": "Tout sélectionner", "xpack.ml.jobSelector.selectAllOptionLabel": "*", - "xpack.ml.jobService.activeDatafeedsLabel": "Flux de données actifs", - "xpack.ml.jobService.activeMLNodesLabel": "Nœuds de ML actifs", - "xpack.ml.jobService.closedJobsLabel": "Tâches fermées", - "xpack.ml.jobService.failedJobsLabel": "Tâches échouées", "xpack.ml.jobService.jobAuditMessagesErrorTitle": "Erreur lors du chargement des messages liés à la tâche", - "xpack.ml.jobService.openJobsLabel": "Ouvrir les tâches", - "xpack.ml.jobService.totalJobsLabel": "Total de tâches", "xpack.ml.jobService.validateJobErrorTitle": "Erreur de validation de tâche", "xpack.ml.jobsHealthAlertingRule.actionGroupName": "Problème détecté", "xpack.ml.jobsHealthAlertingRule.name": "Intégrité des tâches de détection des anomalies", @@ -27144,9 +27015,6 @@ "xpack.observability_onboarding.card.systemLogs.title": "Collecter des logs système", "xpack.observability_onboarding.configureLogs.advancedSettings": "Paramètres avancés", "xpack.observability_onboarding.configureLogs.customConfig": "Configurations personnalisées", - "xpack.observability_onboarding.configureLogs.dataset.helper": "Choisissez un nom pour vos logs. Tout en minuscules, 100 caractères maximum, les caractères spéciaux seront remplacés par \"_\".", - "xpack.observability_onboarding.configureLogs.dataset.name": "Nom de l’ensemble de données", - "xpack.observability_onboarding.configureLogs.dataset.placeholder": "Nom de l’ensemble de données", "xpack.observability_onboarding.configureLogs.description": "Remplissez les chemins d’accès aux fichiers log sur vos hôtes.", "xpack.observability_onboarding.configureLogs.learnMore": "En savoir plus", "xpack.observability_onboarding.configureLogs.logFile.addRow": "Ajouter une ligne", @@ -27206,9 +27074,7 @@ "xpack.observability_onboarding.selectLogs.useOwnShipper": "Obtenir une clé d’API", "xpack.observability_onboarding.selectLogs.useOwnShipper.description": "Utilisez votre propre agent de transfert pour collecter des données de logs en générant une clé d’API.", "xpack.observability_onboarding.steps.back": "Retour", - "xpack.observability_onboarding.steps.continue": "Continuer", "xpack.observability_onboarding.steps.exploreLogs": "Explorer les logs", - "xpack.observability_onboarding.steps.inspect": "Inspecter", "xpack.observability_onboarding.title.collectCustomLogs": "Collectez des logs personnalisés", "xpack.observability.apmEnableContinuousRollupsDescription": "{betaLabel} Lorsque les cumuls continus sont activés, l'interface utilisateur sélectionne des indicateurs ayant la résolution appropriée. Sur des plages temporelles plus larges, des indicateurs de résolution inférieure sont utilisés, ce qui améliore les temps de chargement.", "xpack.observability.apmEnableServiceMetricsDescription": "{betaLabel} Permet l'utilisation d'indicateurs de transaction de service. Il s'agit d'indicateurs à faible cardinalité qui peuvent être utilisés par certaines vues, comme l'inventaire de service, pour accélérer le chargement.", @@ -27221,7 +27087,6 @@ "xpack.observability.inspector.stats.queryTimeValue": "{queryTime} ms", "xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {détecté} Inventory {seuil dépassé} other {dépassé}}", "xpack.observability.slo.alerting.burnRate.reason": "{actionGroupName} : Le taux d'avancement pour le (les) dernier(s) {longWindowDuration} est de {longWindowBurnRate} et pour le (les) dernier(s) {shortWindowDuration} est de {shortWindowBurnRate}. Alerter si supérieur à {burnRateThreshold} pour les deux fenêtres", - "xpack.observability.slo.burnRateWindow.thresholdTip": "Le seuil est {target}x", "xpack.observability.slo.clone.errorNotification": "Échec du clonage de {name}", "xpack.observability.slo.clone.successNotification": "{name} créé avec succès", "xpack.observability.slo.create.errorNotification": "Un problème est survenu lors de la création de {name}", @@ -27239,8 +27104,6 @@ "xpack.observability.slo.slo.activeAlertsBadge.label": "{count, plural, one {# alerte} many {# alertes} other {Alertes #}}", "xpack.observability.slo.slo.delete.errorNotification": "Impossible de supprimer {name}", "xpack.observability.slo.slo.delete.successNotification": "{name} supprimé", - "xpack.observability.slo.slo.deleteConfirmationModal.deleteButtonLabel": "Supprimer {name}", - "xpack.observability.slo.slo.deleteConfirmationModal.descriptionText": "Vous ne pouvez pas récupérer {name} après l'avoir supprimé.", "xpack.observability.slo.slo.stats.objective": "Objectif {objective}", "xpack.observability.slo.slo.timeWindow.calendar": "{elapsed}/{total} jours", "xpack.observability.slo.sloDetails.errorBudgetChartPanel.duration": "{duration}", @@ -27254,18 +27117,6 @@ "xpack.observability.slo.update.errorNotification": "Un problème est survenu lors de la mise à jour de {name}", "xpack.observability.slo.update.successNotification": "Mise à jour réussie de {name}", "xpack.observability.syntheticsThrottlingEnabledExperimentDescription": "Activez les paramètres de régulation dans les configurations du moniteur Synthetics. Notez qu’il est possible que la régulation ne soit pas toujours disponible pour vos moniteurs, même si le paramètre est actif. Destiné à un usage interne uniquement. {link}", - "xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "Dernière {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "Il est possible que cette règle signale {matchedGroups} moins que prévu, car la requête de filtre comporte une correspondance pour {groupCount, plural, one {ce champ} many {ces champs} other {ces champs}}. Pour en savoir plus, consultez notre {filteringAndGroupingLink}.", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "Agrégation {name}", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "Filtre KQL {name}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "Dernière {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "Dernières {lookback} {timeLabel} de données pour {id}", - "xpack.observability.threshold.rule.threshold.errorAlertReason": "Elasticsearch a échoué lors de l'interrogation des données pour {metric}", - "xpack.observability.threshold.rule.threshold.firedAlertReason": "{metric} est {currentValue} dans les dernières {duration}{group}. Alerte lorsque {comparator} {threshold}.", - "xpack.observability.threshold.rule.threshold.noDataAlertReason": "{metric} n'a signalé aucune donnée dans les dernières {interval} {group}", - "xpack.observability.threshold.rule.threshold.recoveredAlertReason": "{metric} est maintenant {comparator} un seuil de {threshold} (la valeur actuelle est {currentValue}) pour {group}", - "xpack.observability.threshold.rule.threshold.thresholdRange": "{a} et {b}", - "xpack.observability.threshold.rule.thresholdExtraTitle": "Alerte lorsque {comparator} {threshold}.", "xpack.observability.transactionRateLabel": "{value} tpm", "xpack.observability.ux.coreVitals.averageMessage": " et inférieur à {bad}", "xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd} ({ranksInd} %)", @@ -27506,18 +27357,6 @@ "xpack.observability.slo.alerting.windowDescription": "Durée de fenêtre avec la valeur du taux d'avancement associée.", "xpack.observability.slo.budgetingMethod.occurrences": "Occurrences", "xpack.observability.slo.budgetingMethod.timeslices": "Intervalles de temps", - "xpack.observability.slo.burnRate.criticalLongLabel": "1 heure", - "xpack.observability.slo.burnRate.criticalShortLabel": "5 minutes", - "xpack.observability.slo.burnRate.criticalTitle": "Taux d’avancement critique", - "xpack.observability.slo.burnRate.highLongLabel": "6 heures", - "xpack.observability.slo.burnRate.highShortLabel": "30 minutes", - "xpack.observability.slo.burnRate.highTitle": "Taux d’avancement élevé", - "xpack.observability.slo.burnRate.lowLongLabel": "3 jours", - "xpack.observability.slo.burnRate.lowShortLabel": "6 heures", - "xpack.observability.slo.burnRate.lowTitle": "Taux d'avancement bas", - "xpack.observability.slo.burnRate.mediumLongLabel": "24 heures", - "xpack.observability.slo.burnRate.mediumShortLabel": "2 heures", - "xpack.observability.slo.burnRate.mediumTitle": "Taux d’avancement moyen", "xpack.observability.slo.burnRate.technicalPreviewBadgeDescription": "Cette fonctionnalité est en préversion technique et est susceptible d’être changée, ou elle peut-être supprimée dans les versions futures. La conception et le code sont moins matures que les fonctionnalités officielles en disponibilité générale et sont fournis tels quels sans aucune garantie. Les fonctionnalités de la version d’évaluation technique ne sont pas soumises à l'accord de niveau de service des fonctionnalités officielles en disponibilité générale.", "xpack.observability.slo.burnRate.technicalPreviewBadgeTitle": "Version d'évaluation technique", "xpack.observability.slo.burnRate.title": "Fenêtres du taux d’avancement", @@ -27559,8 +27398,7 @@ "xpack.observability.slo.rules.sloSelector.placeholder": "Sélectionner un SLO", "xpack.observability.slo.rules.sloSelector.rowLabel": "SLO", "xpack.observability.slo.slo.activeAlertsBadge.ariaLabel": "badge alertes actives", - "xpack.observability.slo.slo.deleteConfirmationModal.cancelButtonLabel": "Annuler", - "xpack.observability.slo.slo.deleteConfirmationModal.title": "Voulez-vous vraiment continuer ?", + "xpack.observability.slo.deleteConfirmationModal.cancelButtonLabel": "Annuler", "xpack.observability.slo.slo.item.actions.clone": "Cloner", "xpack.observability.slo.slo.item.actions.delete": "Supprimer", "xpack.observability.slo.slo.rulesBadge.popover": "Il n'y a pas encore de règles configurées pour ce SLO. Vous ne recevrez pas d'alertes lorsque le SLO est dépassé.", @@ -27640,8 +27478,6 @@ "xpack.observability.slo.sloEdit.sliType.customKql.goodQuery.tooltip": "Cette requête KQL doit renvoyer un sous-ensemble d'événements considérés comme \"bons\" ou \"réussis\" aux fins du calcul du SLO. La requête doit filtrer les événements en fonction de certains critères pertinents, tels que les codes de statut, les messages d'erreur ou d'autres champs pertinents.", "xpack.observability.slo.sloEdit.sliType.customKql.goodQueryPlaceholder": "Définir les bons événements", "xpack.observability.slo.sloEdit.sliType.customKql.queryFilter": "Filtre de requête", - "xpack.observability.slo.sloEdit.sliType.customKql.timestampField.label": "Champ d'horodatage", - "xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder": "Sélectionner un champ d'horodatage", "xpack.observability.slo.sloEdit.sliType.customKql.totalQuery": "Total de la requête", "xpack.observability.slo.sloEdit.sliType.customKql.totalQuery.tooltip": "Cette requête KQL doit renvoyer tous les événements pertinents pour le calcul du SLO, y compris les bons et les mauvais événements.", "xpack.observability.slo.sloEdit.sliType.customKql.totalQueryPlaceholder": "Définir le total d'événements", @@ -27655,8 +27491,6 @@ "xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "Sélectionner un champ d’indicateur", "xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "Filtre de requête", "xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "Somme de", - "xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.label": "Champ d'horodatage", - "xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder": "Sélectionner un champ d'horodatage", "xpack.observability.slo.sloEdit.tags.label": "Balises", "xpack.observability.slo.sloEdit.tags.placeholder": "Ajouter des balises", "xpack.observability.slo.sloEdit.targetSlo.label": "Cible/SLO (%)", @@ -27728,85 +27562,6 @@ "xpack.observability.statusVisualization.ux.link": "Ajouter des données", "xpack.observability.statusVisualization.ux.title": "Expérience utilisateur", "xpack.observability.syntheticsThrottlingEnabledExperimentName": "Activer la régulation Synthetics (Expérimentale)", - "xpack.observability.threshold.rule..charts.errorMessage": "Oups, un problème est survenu", - "xpack.observability.threshold.rule..charts.noDataMessage": "Aucune donnée graphique disponible", - "xpack.observability.threshold.rule..timeLabels.days": "jours", - "xpack.observability.threshold.rule..timeLabels.hours": "heures", - "xpack.observability.threshold.rule..timeLabels.minutes": "minutes", - "xpack.observability.threshold.rule..timeLabels.seconds": "secondes", - "xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule": "Règle", - "xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle": "Seuil dépassé", - "xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription": "Lien vers l’affichage de résolution des problèmes d’alerte pour voir plus de contextes et de détails. La chaîne sera vide si server.publicBaseUrl n'est pas configuré.", - "xpack.observability.threshold.rule.alertDropdownTitle": "Alertes et règles", - "xpack.observability.threshold.rule.alertFlyout.addCondition": "Ajouter une condition", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.avg": "Moyenne", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.cardinality": "Cardinalité", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.count": "Compte du document", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.max": "Max", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.min": "Min", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p95": "95e centile", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p99": "99e centile", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.rate": "Taux", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.sum": "Somme", - "xpack.observability.threshold.rule.alertFlyout.alertDescription": "Alerter quand un type de données Observability atteint ou dépasse une valeur donnée.", - "xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear": "Me prévenir si un groupe cesse de signaler les données", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink": "les documents", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerHelpText": "Créer une alerte pour chaque valeur unique. Par exemple : \"host.id\" ou \"cloud.region\".", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerText": "Regrouper les alertes par (facultatif)", - "xpack.observability.threshold.rule.alertFlyout.customEquation": "Équation personnalisée", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.addCustomRow": "Ajouter une agrégation/un champ", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton": "Supprimer", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage": "Prend en charge les expressions mathématiques de base", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage": "L'étiquette personnalisée s'affichera sur le graphique d'alerte et dans le titre de raison/d'alerte", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel": "Étiquette (facultatif)", - "xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[Ce paramètre n’est pas applicable à l’agrégateur du nombre de documents.]", - "xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired": "L'agrégation est requise.", - "xpack.observability.threshold.rule.alertFlyout.error.customMetrics.aggTypeRequired": "L'agrégation est requise", - "xpack.observability.threshold.rule.alertFlyout.error.customMetrics.fieldRequired": "Le champ est obligatoire", - "xpack.observability.threshold.rule.alertFlyout.error.customMetricsError": "Vous devez définir au moins 1 indicateur personnalisé", - "xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters": "Le champ d'équation prend en charge uniquement les caractères suivants : A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =", - "xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery": "La requête de filtre n'est pas valide.", - "xpack.observability.threshold.rule.alertFlyout.error.metricRequired": "L'indicateur est requis.", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "Le seuil est requis.", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "Les seuils doivent contenir un nombre valide.", - "xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "La taille de temps est requise.", - "xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "Activez cette option pour déclencher l’action si un groupe précédemment détecté cesse de signaler des résultats. Ce n’est pas recommandé pour les infrastructures à montée en charge dynamique qui peuvent rapidement lancer ou stopper des nœuds automatiquement.", - "xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "N'est pas entre", - "xpack.observability.threshold.rule.alertFlyout.removeCondition": "Retirer la condition", - "xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[AUCUNE DONNÉE]", - "xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage": "\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\} est à l'état \\{\\{context.alertState\\}\\}\n\n Raison :\n \\{\\{context.reason\\}\\}\n ", - "xpack.observability.threshold.rule.alerting.threshold.fired": "Alerte", - "xpack.observability.threshold.rule.alerting.threshold.nodata": "Aucune donnée", - "xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue": "[AUCUNE DONNÉE]", - "xpack.observability.threshold.rule.alertsButton": "Alertes et règles", - "xpack.observability.threshold.rule.cloudActionVariableDescription": "Objet cloud défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.containerActionVariableDescription": "Objet conteneur défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.createInventoryRuleButton": "Créer une règle d'inventaire", - "xpack.observability.threshold.rule.createThresholdRuleButton": "Créer une règle de seuil", - "xpack.observability.threshold.rule.groupByKeysActionVariableDescription": "Objet contenant les groupes qui fournissent les données", - "xpack.observability.threshold.rule.homePage.toolbar.kqlSearchFieldPlaceholder": "Rechercher des données d'infrastructure… (par exemple host.name:host-1)", - "xpack.observability.threshold.rule.hostActionVariableDescription": "Objet hôte défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.infrastructureDropdownMenu": "Infrastructure", - "xpack.observability.threshold.rule.infrastructureDropdownTitle": "Règles d'infrastructure", - "xpack.observability.threshold.rule.labelsActionVariableDescription": "Liste d'étiquettes associées avec l'entité sur laquelle l'alerte s'est déclenchée.", - "xpack.observability.threshold.rule.manageRules": "Gérer les règles", - "xpack.observability.threshold.rule.metricsDropdownMenu": "Indicateurs", - "xpack.observability.threshold.rule.metricsDropdownTitle": "Règles d'indicateurs", - "xpack.observability.threshold.rule.orchestratorActionVariableDescription": "Objet orchestrateur défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.reasonActionVariableDescription": "Une description concise de la raison du signalement", - "xpack.observability.threshold.rule.sourceConfiguration.missingHttp": "Échec de chargement de la source : Aucun client HTTP disponible.", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody": "Nous n'avons pas pu appliquer les modifications à la configuration des indicateurs. Réessayez plus tard.", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle": "La mise à jour de la configuration a échoué", - "xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle": "Les paramètres d'indicateurs ont bien été mis à jour", - "xpack.observability.threshold.rule.tagsActionVariableDescription": "Liste de balises associées avec l'entité sur laquelle l'alerte s'est déclenchée.", - "xpack.observability.threshold.rule.threshold.aboveRecovery": "supérieur à", - "xpack.observability.threshold.rule.threshold.belowRecovery": "inférieur à", - "xpack.observability.threshold.rule.threshold.betweenRecovery": "entre", - "xpack.observability.threshold.rule.threshold.customEquation": "Équation personnalisée", - "xpack.observability.threshold.rule.threshold.documentCount": "Nombre de documents", - "xpack.observability.threshold.rule.timestampDescription": "Horodatage du moment où l'alerte a été détectée.", - "xpack.observability.threshold.rule.valueActionVariableDescription": "Valeur de l'indicateur dans la condition spécifiée. Utilisation : (ctx.value.condition0, ctx.value.condition1, etc...).", - "xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription": "Lien vers la source de l’alerte", "xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "Graphique par", "xpack.observability.threshold.ruleExplorer.groupByLabel": "Tout", "xpack.observability.threshold.ruleName": "Seuil (Version d'évaluation technique)", @@ -30013,8 +29768,6 @@ "xpack.securitySolution.exceptions.viewer.lastUpdated": "Mis à jour {updated}", "xpack.securitySolution.exceptions.viewer.paginationDetails": "Affichage de {partOne} sur {partTwo}", "xpack.securitySolution.fieldBrowser.descriptionForScreenReaderOnly": "Description pour le champ {field} :", - "xpack.securitySolution.flyout.errorMessage": "Une erreur est survenue lors de l'affichage de {message}", - "xpack.securitySolution.flyout.errorTitle": "Impossible d'afficher {title}", "xpack.securitySolution.footer.autoRefreshActiveTooltip": "Lorsque le rafraîchissement automatique est activé, la chronologie vous montre les {numberOfItems} derniers événements qui correspondent à votre requête.", "xpack.securitySolution.formattedNumber.countsLabel": "{mantissa}{scale}{hasRemainder}", "xpack.securitySolution.header.editableTitle.editButtonAria": "Vous pouvez modifier {title} en cliquant", @@ -31629,7 +31382,6 @@ "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootkitDescription": "Bootkit (T1067)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonAutostartExecutionDescription": "Exécution de démarrage ou de démarrage automatique de connexion (T1547)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonInitializationScriptsDescription": "Scripts de démarrage ou d'initialisation de connexion (T1037)", - "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription": "Découverte de favoris de navigateur (T1217)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserExtensionsDescription": "Extensions de navigateur (T1176)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserSessionHijackingDescription": "Détournement de session de navigateur (T1185)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bruteForceDescription": "Force brute (T1110)", @@ -33231,71 +32983,9 @@ "xpack.securitySolution.fleetIntegration.assets.name": "Hôtes", "xpack.securitySolution.fleetIntegration.elasticDefend.eventFilter.nonInteractiveSessions.description": "Filtre d'événement pour Cloud Security. Créé par l'intégration Elastic Defend.", "xpack.securitySolution.fleetIntegration.elasticDefend.eventFilter.nonInteractiveSessions.name": "Sessions non interactives", - "xpack.securitySolution.flyout.analyzerErrorMessage": "analyseur", "xpack.securitySolution.flyout.button.timeline": "chronologie", - "xpack.securitySolution.flyout.correlations.caseNameColumnTitle": "Nom", - "xpack.securitySolution.flyout.correlations.reasonColumnTitle": "Raison", - "xpack.securitySolution.flyout.correlations.ruleColumnTitle": "Règle", - "xpack.securitySolution.flyout.correlations.severityColumnTitle": "Sévérité", - "xpack.securitySolution.flyout.correlations.statusColumnTitle": "Statut", - "xpack.securitySolution.flyout.correlations.timestampColumnTitle": "Horodatage", - "xpack.securitySolution.flyout.documentDetails.alertReasonTitle": "Raison d'alerte", - "xpack.securitySolution.flyout.documentDetails.analyzerGraphButton": "Graph Analyseur", - "xpack.securitySolution.flyout.documentDetails.analyzerPreviewTitle": "Aperçu de l'analyseur", - "xpack.securitySolution.flyout.documentDetails.collapseDetailButton": "Réduire les détails de l'alerte", - "xpack.securitySolution.flyout.documentDetails.correlationsButton": "Corrélations", - "xpack.securitySolution.flyout.documentDetails.correlationsTitle": "Corrélations", - "xpack.securitySolution.flyout.documentDetails.documentDescriptionTitle": "Description du document", - "xpack.securitySolution.flyout.documentDetails.documentReasonTitle": "Motif du document", - "xpack.securitySolution.flyout.documentDetails.entitiesButton": "Entités", - "xpack.securitySolution.flyout.documentDetails.entitiesTitle": "Entités", - "xpack.securitySolution.flyout.documentDetails.expandDetailButton": "Développer les détails de l'alerte", - "xpack.securitySolution.flyout.documentDetails.highlightedFieldsTitle": "Champs en surbrillance", - "xpack.securitySolution.flyout.documentDetails.insightsOptions": "Options des informations exploitables", - "xpack.securitySolution.flyout.documentDetails.insightsTab": "Informations exploitables", - "xpack.securitySolution.flyout.documentDetails.insightsTitle": "Informations exploitables", - "xpack.securitySolution.flyout.documentDetails.investigationSectionTitle": "Investigation", - "xpack.securitySolution.flyout.documentDetails.investigationsTab": "Investigation", - "xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON", - "xpack.securitySolution.flyout.documentDetails.overviewTab": "Aperçu", - "xpack.securitySolution.flyout.documentDetails.overviewTab.prevalenceRowText": "est inhabituel", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichment": "champ enrichi avec la Threat Intelligence", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichments": "champs enrichis avec la Threat Intelligence", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatch": "correspondance de menace détectée", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatches": "correspondances de menaces détectées", - "xpack.securitySolution.flyout.documentDetails.prevalenceButton": "Prévalence", - "xpack.securitySolution.flyout.documentDetails.prevalenceTitle": "Prévalence", - "xpack.securitySolution.flyout.documentDetails.riskScoreTitle": "Score de risque", - "xpack.securitySolution.flyout.documentDetails.ruleDescriptionTitle": "Description de la règle", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.commandText": "par", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.processText": "démarré", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.ruleText": "avec la règle", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.timeText": "à", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.title": "Aperçu du visualiseur de session", - "xpack.securitySolution.flyout.documentDetails.sessionViewButton": "Vue de session", - "xpack.securitySolution.flyout.documentDetails.severityTitle": "Sévérité", - "xpack.securitySolution.flyout.documentDetails.share": "Partager l'alerte", - "xpack.securitySolution.flyout.documentDetails.tableTab": "Tableau", - "xpack.securitySolution.flyout.documentDetails.threatIntelligenceButton": "Threat Intelligence", - "xpack.securitySolution.flyout.documentDetails.threatIntelligenceTitle": "Threat Intelligence", - "xpack.securitySolution.flyout.documentDetails.visualizationsTitle": "Visualisations", - "xpack.securitySolution.flyout.documentDetails.visualizeOptions": "Options Visualize", - "xpack.securitySolution.flyout.documentDetails.visualizeTab": "Visualiser", - "xpack.securitySolution.flyout.documentErrorMessage": "les valeurs et champs du document", - "xpack.securitySolution.flyout.documentErrorTitle": "informations du document", "xpack.securitySolution.flyout.entities.failRelatedHostsDescription": "Impossible de lancer la recherche sur les hôtes associés", "xpack.securitySolution.flyout.entities.failRelatedUsersDescription": "Impossible de lancer la recherche sur les utilisateurs associés", - "xpack.securitySolution.flyout.entities.hostsInfoTitle": "Informations sur l’hôte", - "xpack.securitySolution.flyout.entities.relatedEntitiesIpColumn": "Adresses IP", - "xpack.securitySolution.flyout.entities.relatedEntitiesNameColumn": "Nom", - "xpack.securitySolution.flyout.entities.relatedHostsTitle": "Hôtes associés", - "xpack.securitySolution.flyout.entities.relatedUsersTitle": "Utilisateurs associés", - "xpack.securitySolution.flyout.entities.usersInfoTitle": "Informations sur l’utilisateur", - "xpack.securitySolution.flyout.prevalenceErrorMessage": "prévalence", - "xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitle": "Nombre d'alertes", - "xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitle": "Compte du document", - "xpack.securitySolution.flyout.response.title": "Réponses", - "xpack.securitySolution.flyout.sessionViewErrorMessage": "vue de session", "xpack.securitySolution.footer.autoRefreshActiveDescription": "Actualisation automatique active", "xpack.securitySolution.footer.cancel": "Annuler", "xpack.securitySolution.footer.data": "données", @@ -34620,10 +34310,6 @@ "xpack.serverlessSearch.apiKey.roleDescriptorsLinkLabel": "Découvrir comment structurer les descripteurs de rôles", "xpack.serverlessSearch.apiKey.setup.description": "Les détails de la configuration de base pour créer votre clé d’API.", "xpack.serverlessSearch.apiKey.setup.title": "Configuration", - "xpack.serverlessSearch.apiKey.stepOneDescription": "Identifiant unique pour l’authentification et l’autorisation. ", - "xpack.serverlessSearch.apiKey.stepOneTitle": "Générer et stocker vos clés d’API", - "xpack.serverlessSearch.apiKey.stepTwoDescription": "Identifiant unique pour un projet spécifique. ", - "xpack.serverlessSearch.apiKey.stepTwoTitle": "Stocker votre identifiant unique du cloud", "xpack.serverlessSearch.apiKey.title": "Stocker votre clé d’API et votre identifiant du cloud", "xpack.serverlessSearch.apiKey.userFieldHelpText": "Identifiant de l’utilisateur créant la clé d’API.", "xpack.serverlessSearch.apiKey.userFieldLabel": "Utilisateur", @@ -35748,7 +35434,6 @@ "xpack.stackAlerts.geoContainment.boundariesFetchError": "Impossible de récupérer les limites du suivi de l’endiguement, erreur : {error}", "xpack.stackAlerts.geoContainment.entityContainmentFetchError": "Impossible de récupérer l’endiguement des entités, erreur : {error}", "xpack.stackAlerts.geoContainment.noBoundariesError": "Aucune limite de suivi de l’endiguement trouvée. Assurez-vous que l’index \"{index}\" a des documents.", - "xpack.stackAlerts.geoContainment.noGeoFieldInIndexPattern.message": "La vue de données ne contient aucun champ géospatial autorisé. Il doit en contenir un de type {geoFields}.", "xpack.stackAlerts.indexThreshold.alertTypeContextSubjectTitle": "le groupe {group} de l'alerte {name} a atteint le seuil", "xpack.stackAlerts.indexThreshold.alertTypeRecoveryContextSubjectTitle": "groupe {group} de l'alerte {name} récupéré", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "thresholdComparator spécifié non valide : {comparator}", @@ -35839,12 +35524,8 @@ "xpack.stackAlerts.geoContainment.actionVariableContextFromEntityDocumentIdLabel": "ID du document de l'entité contenue", "xpack.stackAlerts.geoContainment.actionVariableContextFromEntityLocationLabel": "Emplacement de l'entité", "xpack.stackAlerts.geoContainment.alertTypeTitle": "Suivi de l'endiguement", - "xpack.stackAlerts.geoContainment.boundaryNameSelect": "Sélectionner un nom de limite", "xpack.stackAlerts.geoContainment.boundaryNameSelectLabel": "Nom de limite lisible par l'utilisateur (facultatif)", "xpack.stackAlerts.geoContainment.descriptionText": "Alerte lorsqu'une entité est contenue dans une limite géographique.", - "xpack.stackAlerts.geoContainment.entityByLabel": "par", - "xpack.stackAlerts.geoContainment.entityIndexLabel": "index", - "xpack.stackAlerts.geoContainment.entityIndexSelect": "Sélectionner une vue de données et un champ de point géographique", "xpack.stackAlerts.geoContainment.error.requiredBoundaryGeoFieldText": "Le champ de limite géographique est requis.", "xpack.stackAlerts.geoContainment.error.requiredBoundaryIndexTitleText": "Le titre de la vue de données de limite est requis.", "xpack.stackAlerts.geoContainment.error.requiredBoundaryTypeText": "Le type de limite est requis.", @@ -35852,25 +35533,12 @@ "xpack.stackAlerts.geoContainment.error.requiredEntityText": "L'entité est requise.", "xpack.stackAlerts.geoContainment.error.requiredGeoFieldText": "Le champ géographique est requis.", "xpack.stackAlerts.geoContainment.error.requiredIndexTitleText": "La vue de données est requise.", - "xpack.stackAlerts.geoContainment.fixErrorInExpressionBelowValidationMessage": "L'expression contient des erreurs.", "xpack.stackAlerts.geoContainment.geofieldLabel": "Champ géospatial", - "xpack.stackAlerts.geoContainment.indexLabel": "index", - "xpack.stackAlerts.geoContainment.indexPatternSelectLabel": "Vue de données", - "xpack.stackAlerts.geoContainment.indexPatternSelectPlaceholder": "Sélectionner la vue de données", - "xpack.stackAlerts.geoContainment.noIndexPattern.doThisLinkTextDescription": "Créez une vue de données.", - "xpack.stackAlerts.geoContainment.noIndexPattern.doThisPrefixDescription": "Vous devrez ", - "xpack.stackAlerts.geoContainment.noIndexPattern.getStartedLinkText": "Commencez avec des échantillons d'ensembles de données.", - "xpack.stackAlerts.geoContainment.noIndexPattern.hintDescription": "Vous n'avez aucune donnée ? ", - "xpack.stackAlerts.geoContainment.noIndexPattern.messageTitle": "Aucune vue de données n'a été trouvée", "xpack.stackAlerts.geoContainment.notGeoContained": "Plus contenu", - "xpack.stackAlerts.geoContainment.selectBoundaryIndex": "Sélectionner une limite", - "xpack.stackAlerts.geoContainment.selectEntity": "Sélectionner une entité", "xpack.stackAlerts.geoContainment.selectGeoLabel": "Sélectionner un champ géographique", - "xpack.stackAlerts.geoContainment.selectLabel": "Sélectionner un champ géographique", "xpack.stackAlerts.geoContainment.selectTimeLabel": "Sélectionner un champ temporel", "xpack.stackAlerts.geoContainment.timeFieldLabel": "Champ temporel", "xpack.stackAlerts.geoContainment.topHitsSplitFieldSelectPlaceholder": "Sélectionner un champ d'entité", - "xpack.stackAlerts.geoContainment.ui.expressionPopover.closePopoverLabel": "Fermer", "xpack.stackAlerts.indexThreshold.actionGroupThresholdMetTitle": "Seuil atteint", "xpack.stackAlerts.indexThreshold.actionVariableContextConditionsLabel": "Chaîne décrivant le comparateur de seuil et le seuil", "xpack.stackAlerts.indexThreshold.actionVariableContextDateLabel": "Date à laquelle l'alerte a dépassé le seuil.", @@ -37710,26 +37378,19 @@ "xpack.transform.alertTypes.transformHealth.healthCheckRecoveryMessage": "La/les {count, plural, one {Transformer} many {Transformations} other {Transformations}} {transformsString} {count, plural, one {est} many {sont} other {sont}} saine(s).", "xpack.transform.alertTypes.transformHealth.notStartedMessage": "La/les {count, plural, one {Transformer} many {Transformer} other {Transformer}} {transformsString} {count, plural, one {est} many {sont} other {ne sont pas démarrées}}.", "xpack.transform.alertTypes.transformHealth.notStartedRecoveryMessage": "La/les {count, plural, one {Transformer} many {Transformer} other {Transformer}} {transformsString} {count, plural, one {est} many {sont} other {sont}} lancée(s).", - "xpack.transform.app.deniedPrivilegeDescription": "Pour utiliser cette section des transformations, vous devez avoir {privilegesCount, plural, one {ce privilège de cluster} many {ces privilèges de cluster} other {ces privilèges de cluster}} : {missingPrivileges}.", "xpack.transform.capability.pleaseContactAdministratorTooltip": "{message} Veuillez contacter votre administrateur.", "xpack.transform.clone.noDataViewErrorPromptText": "Impossible de cloner la transformation {transformId}. Il n'existe aucune vue de données pour {dataViewTitle}.", "xpack.transform.danglingTasksError": "{count} {count, plural, one {transformation n'a pas de} many {transformations n'ont pas de} other {transformations n'ont pas de}} détails de configuration : [{transformIds}] {count, plural, one {Elle ne peut pas être récupérée et doit être supprimée} many {Elles ne peuvent pas être récupérées et doivent être supprimées} other {Elles ne peuvent pas être récupérées et doivent être supprimées}}.", "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage": "Une erreur est survenue lors de la suppression de la vue de données {destinationIndex}", - "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage": "Requête de suppression de la vue de données {destinationIndex} reconnue.", "xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "Une erreur s'est produite lors de la suppression de l'index de destination {destinationIndex}", - "xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage": "Requête de suppression de l'index de destination {destinationIndex} reconnue.", "xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage": "Une erreur s'est produite lors de la vérification de l'existence de la vue de données {dataView} : {error}", "xpack.transform.edit.noDataViewErrorPromptText": "Impossible d'obtenir la vue de données pour la transformation {transformId}. Il n'existe aucune vue de données pour {dataViewTitle}.", "xpack.transform.forceDeleteTransformMessage": "Supprimer {count} {count, plural, one {transformation} many {transformations} other {transformations}}", "xpack.transform.managedTransformsWarningCallout": "{count, plural, one {Cette transformation} many {Au moins l'une de ces transformations} other {Au moins l'une de ces transformations}} est préconfigurée par Elastic. Le fait de {action} {count, plural, one {la} many {les} other {les}} avec une heure de fin spécifique peut avoir un impact sur d'autres éléments du produit.", "xpack.transform.multiTransformActionsMenu.transformsCount": "Sélection effectuée de {count} {count, plural, one {transformation} many {transformations} other {transformations}}", "xpack.transform.stepCreateForm.createDataViewErrorMessage": "Une erreur est survenue lors de la création de la vue de données Kibana {dataViewName} :", - "xpack.transform.stepCreateForm.createDataViewSuccessMessage": "La vue de données Kibana {dataViewName} a bien été créée.", "xpack.transform.stepCreateForm.createTransformErrorMessage": "Une erreur s'est produite lors de la création de la transformation {transformId} :", - "xpack.transform.stepCreateForm.createTransformSuccessMessage": "La requête pour créer la transformation {transformId} a été reconnue.", "xpack.transform.stepCreateForm.duplicateDataViewErrorMessage": "Une erreur est survenue lors de la création de la vue de données Kibana {dataViewName} : La vue de données existe déjà.", - "xpack.transform.stepCreateForm.startTransformErrorMessage": "Une erreur s'est produite lors du démarrage de la transformation {transformId} :", - "xpack.transform.stepCreateForm.startTransformSuccessMessage": "La requête pour démarrer la transformation {transformId} a été reconnue.", "xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar": "Requête non valide : {queryErrorMessage}", "xpack.transform.stepDefineForm.queryPlaceholderKql": "Par exemple, {example}", "xpack.transform.stepDefineForm.queryPlaceholderLucene": "Par exemple, {example}", @@ -37741,41 +37402,30 @@ "xpack.transform.stepDetailsForm.retentionPolicyMaxAgePlaceholderText": "max_age, par exemple {exampleValue}", "xpack.transform.transformForm.sizeNotationPlaceholder": "Exemples : {example1}, {example2}, {example3}, {example4}", "xpack.transform.transformList.alertingRules.tooltipContent": "La transformation a {rulesCount} {rulesCount, plural, one { règle} many { règles} other { règles}} d'alerte associée(s)", - "xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage": "Suppression réussie de {count} {count, plural, one {vue} many {vues} other {vues}} de données de destination.", - "xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "Suppression réussie de {count} {count, plural, one {index} many {index système non migrés} other {index}} de destination.", "xpack.transform.transformList.bulkDeleteModalTitle": "Supprimer {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", - "xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "Suppression réussie de {count} {count, plural, one {transformation} many {transformations} other {transformations}}.", "xpack.transform.transformList.bulkReauthorizeModalTitle": "Réautoriser {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", "xpack.transform.transformList.bulkResetModalTitle": "Réinitialiser {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", - "xpack.transform.transformList.bulkResetTransformSuccessMessage": "Réinitialisation effectuée avec succès de {count} {count, plural, one {transformation} many {transformations} other {transformations}}.", "xpack.transform.transformList.bulkStartModalTitle": "Démarrer {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", "xpack.transform.transformList.bulkStopModalTitle": "Arrêter {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", "xpack.transform.transformList.cannotRestartCompleteBatchTransformToolTip": "{transformId} est une transformation par lots terminée et ne peut pas être redémarrée.", "xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformToolTip": "{transformId} est une transformation par lots terminée qui ne peut pas être planifiée pour traiter les données instantanément.", "xpack.transform.transformList.deleteModalTitle": "Supprimer {transformId} ?", "xpack.transform.transformList.deleteTransformErrorMessage": "Une erreur s'est produite lors de la suppression de la transformation {transformId}", - "xpack.transform.transformList.deleteTransformSuccessMessage": "La requête pour supprimer la transformation {transformId} a été reconnue.", "xpack.transform.transformList.editFlyoutFormPlaceholderText": "Par défaut : {defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "Modifier {transformId}", - "xpack.transform.transformList.editTransformSuccessMessage": "Transformation {transformId} mise à jour.", "xpack.transform.transformList.reauthorizeModalTitle": "Réautoriser {transformId} ?", "xpack.transform.transformList.reauthorizeTransformErrorMessage": "Une erreur s'est produite lors de la réautorisation de la transformation {transformId}", - "xpack.transform.transformList.reauthorizeTransformSuccessMessage": "La requête pour réautoriser la transformation {transformId} a été reconnue.", "xpack.transform.transformList.resetModalTitle": "Réinitialiser {transformId} ?", "xpack.transform.transformList.resetTransformErrorMessage": "Une erreur s'est produite lors de la réinitialisation de la transformation {transformId}", - "xpack.transform.transformList.resetTransformSuccessMessage": "La requête pour réinitialiser la transformation {transformId} a été reconnue.", "xpack.transform.transformList.rowCollapse": "Masquer les détails pour {transformId}", "xpack.transform.transformList.rowExpand": "Afficher les détails pour {transformId}", "xpack.transform.transformList.scheduleNowTransformErrorMessage": "Une erreur s'est produite lors de la planification de la transformation {transformId} pour traiter les données instantanément.", - "xpack.transform.transformList.scheduleNowTransformSuccessMessage": "La demande de planification de transformation {transformId} pour traiter les données immédiatement a été reconnue.", "xpack.transform.transformList.startedTransformToolTip": "{transformId} a déjà démarré.", "xpack.transform.transformList.startModalTitle": "Démarrer {transformId} ?", "xpack.transform.transformList.startTransformErrorMessage": "Une erreur s'est produite lors du démarrage de la transformation {transformId}", - "xpack.transform.transformList.startTransformSuccessMessage": "La requête pour démarrer la transformation {transformId} a été reconnue.", "xpack.transform.transformList.stopModalTitle": "Arrêter {transformId} ?", "xpack.transform.transformList.stoppedTransformToolTip": "{transformId} est déjà arrêtée.", "xpack.transform.transformList.stopTransformErrorMessage": "Une erreur s'est produite lors de l'arrêt de la transformation du cadre de données {transformId}", - "xpack.transform.transformList.stopTransformSuccessMessage": "La requête pour arrêter la transformation du cadre de données {transformId} a été reconnue.", "xpack.transform.transformList.unauthorizedTransformsCallout.insufficientPermissionsMsg": "{unauthorizedCnt, plural, one {Une transformation a été créée avec des autorisations insuffisantes.} many {# transformations ont été créées avec des autorisations insuffisantes.} other {# transformations ont été créées avec des autorisations insuffisantes.}}", "xpack.transform.transformList.unauthorizedTransformsCallout.reauthorizeMsg": "Réautorisez pour démarrer {unauthorizedCnt, plural, one {la transformation} many {# transformations} other {# transformations}}.", "xpack.transform.transformNodes.noTransformNodesCallOutBody": "Vous ne pourrez ni créer ni exécuter de transformations. {learnMoreLink}", @@ -37821,9 +37471,6 @@ "xpack.transform.alertTypes.transformHealth.notStartedCheckName": "La transformation n'a pas démarré", "xpack.transform.alertTypes.transformHealth.testsConfigTransforms.errorMessage": "Au moins une vérification d'intégrité doit être sélectionnée", "xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel": "Activer", - "xpack.transform.app.checkingPrivilegesDescription": "Vérification des privilèges…", - "xpack.transform.app.checkingPrivilegesErrorMessage": "Erreur lors de la récupération des privilèges utilisateur depuis le serveur", - "xpack.transform.app.deniedPrivilegeTitle": "Vous ne disposez pas de privilèges de cluster", "xpack.transform.appName": "Transformations", "xpack.transform.appTitle": "Transformations", "xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip": "Vous ne disposez pas d'autorisation pour créer des règles d'alerte de transformation.", @@ -37869,7 +37516,6 @@ "xpack.transform.licenseCheckErrorMessage": "La vérification de la licence a échoué", "xpack.transform.list.emptyPromptButtonText": "Créez votre première transformation", "xpack.transform.list.emptyPromptTitle": "Aucune transformation n'a été trouvée", - "xpack.transform.list.errorPromptTitle": "Une erreur s'est produite lors de l'obtention de la liste de transformations", "xpack.transform.mode": "Mode", "xpack.transform.modeFilter": "Mode", "xpack.transform.models.transformService.allOtherRequestsCancelledDescription": "Toutes les autres requêtes ont été annulées.", @@ -38101,7 +37747,6 @@ "xpack.transform.transformList.editFlyoutUpdateButtonText": "Mettre à jour", "xpack.transform.transformList.editManagedTransformsDescription": "modification", "xpack.transform.transformList.editTransformGenericErrorMessage": "Une erreur s'est produite lors de l'appel du point de terminaison de l'API pour mettre à jour les transformations.", - "xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "Une erreur s'est produite lors de la vérification de la possibilité pour un utilisateur de supprimer l'index de destination", "xpack.transform.transformList.managedBadgeLabel": "Géré", "xpack.transform.transformList.managedBadgeTooltip": "Cette transformation est préconfigurée et gérée par Elastic. Les autres éléments du produit peuvent dépendre de ce comportement.", "xpack.transform.transformList.needsReauthorizationBadge.contactAdminTooltip": "Contactez votre administrateur pour demander les autorisations requises.", @@ -39776,7 +39421,6 @@ "eventAnnotation.group.args.annotationConfigs.ignoreGlobalFilters.help": "Basculer pour ignorer les filtres globaux pour l'annotation", "eventAnnotation.group.args.annotationGroups": "Groupe d'annotations", "eventAnnotation.group.description": "Groupe d'annotations d'événement", - "eventAnnotation.listingViewTitle": "Groupes d'annotations", "eventAnnotation.manualAnnotation.args.color": "Couleur de la ligne", "eventAnnotation.manualAnnotation.args.icon": "Icône facultative utilisée pour les lignes d'annotation", "eventAnnotation.manualAnnotation.args.id": "ID de l'annotation", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 29500521e970e..ef312936eb961 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -665,7 +665,6 @@ "core.deprecations.elasticsearchUsername.manualSteps2": "「elasticsearch.serviceAccountToken」設定をkibana.ymlに追加します。", "core.deprecations.elasticsearchUsername.manualSteps3": "kibana.ymlから「elasticsearch.username」と「elasticsearch.password」を削除します。", "core.deprecations.noCorrectiveAction": "この廃止予定は自動的に解決できません。", - "core.euiAccordion.isLoading": "読み込み中", "core.euiAutoRefresh.autoRefreshLabel": "自動更新", "core.euiAutoRefresh.buttonLabelOff": "自動更新はオフです", "core.euiBasicTable.noItemsMessage": "項目が見つかりません", @@ -1331,7 +1330,6 @@ "data.search.aggs.rareTerms.aggTypesLabel": "{fieldName}の希少な用語", "data.search.es_search.queryTimeValue": "{queryTime}ms", "data.search.functions.geoBoundingBox.arguments.error": "次のパラメーターのグループの1つ以上を指定する必要があります:{parameters}。", - "data.search.searchSource.fetch.shardsFailedNotificationMessage": "{shardsTotal}件中{shardsFailed}件のシャードでエラーが発生しました", "data.search.searchSource.indexPatternIdDescription": "{kibanaIndexPattern}インデックス内のIDです。", "data.search.searchSource.queryTimeValue": "{queryTime}ms", "data.search.searchSource.requestTimeValue": "{requestTime}ms", @@ -2078,15 +2076,7 @@ "data.search.searchSource.dataViewDescription": "照会されたデータビュー。", "data.search.searchSource.dataViewIdLabel": "データビューID", "data.search.searchSource.dataViewLabel": "データビュー", - "data.search.searchSource.fetch.requestTimedOutNotificationMessage": "リクエストがタイムアウトしたため、データが不完全な可能性があります", - "data.search.searchSource.fetch.shardsFailedModal.close": "閉じる", - "data.search.searchSource.fetch.shardsFailedModal.copyToClipboard": "応答をクリップボードにコピー", - "data.search.searchSource.fetch.shardsFailedModal.showDetails": "詳細を表示", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest": "リクエスト", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse": "応答", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures": "シャードエラー", "data.search.searchSource.fetch.shardsFailedModal.tableColReason": "理由", - "data.search.searchSource.fetch.shardsFailedNotificationDescription": "データが不完全か誤りの可能性があります。", "data.search.searchSource.hitsDescription": "クエリにより返されたドキュメントの数です。", "data.search.searchSource.hitsLabel": "ヒット数", "data.search.searchSource.hitsTotalDescription": "クエリに一致するドキュメントの数です。", @@ -2170,7 +2160,7 @@ "discover.advancedSettings.disableDocumentExplorerDescription": "クラシックビューではなく、{documentExplorerDocs}を使用するには、このオプションをオフにします。ドキュメントエクスプローラーでは、データの並べ替え、列のサイズ変更、全画面表示といった優れた機能を使用できます。", "discover.advancedSettings.discover.showFieldStatisticsDescription": "{fieldStatisticsDocs}を有効にすると、数値フィールドの最大/最小値やジオフィールドの地図といった詳細が表示されます。この機能はベータ段階で、変更される可能性があります。", "discover.advancedSettings.discover.showMultifieldsDescription": "拡張ドキュメントビューに{multiFields}が表示されるかどうかを制御します。ほとんどの場合、マルチフィールドは元のフィールドと同じです。「searchFieldsFromSource」がオフのときにのみこのオプションを使用できます。", - "discover.advancedSettings.enableSQLDescription": "{technicalPreviewLabel}このパッチプレビュー機能は実験段階です。本番の保存された検索、可視化、またはダッシュボードでは、この機能を信頼しないでください。この設定により、DiscoverとLensでテキストベースのクエリ言語としてSQLを使用できます。このエクスペリエンスに関するフィードバックがございましたら、{link}からお問い合わせください", + "discover.advancedSettings.enableESQLDescription": "{technicalPreviewLabel}このパッチプレビュー機能は実験段階です。本番の保存された検索、可視化、またはダッシュボードでは、この機能を信頼しないでください。この設定により、DiscoverとLensでテキストベースのクエリ言語としてSQLを使用できます。このエクスペリエンスに関するフィードバックがございましたら、{link}からお問い合わせください", "discover.context.contextOfTitle": "#{anchorId}の周りのドキュメント", "discover.context.newerDocumentsWarning": "アンカーよりも新しいドキュメントは{docCount}件しか見つかりませんでした。", "discover.context.olderDocumentsWarning": "アンカーよりも古いドキュメントは{docCount}件しか見つかりませんでした。", @@ -2193,11 +2183,6 @@ "discover.dscTour.stepAddFields.description": "{plusIcon}をクリックして、関心があるフィールドを追加します。", "discover.dscTour.stepExpand.description": "{expandIcon}をクリックすると、ドキュメントを表示、比較、フィルタリングできます。", "discover.errorCalloutFormattedTitle": "{title}: {errorMessage}", - "discover.grid.copyClipboardButtonTitle": "{column}の値をコピー", - "discover.grid.copyColumnValuesToClipboard.toastTitle": "\"{column}\"列の値がクリップボードにコピーされました", - "discover.grid.filterForAria": "この{value}でフィルターを適用", - "discover.grid.filterOutAria": "この{value}を除外", - "discover.gridSampleSize.limitDescription": "検索結果は{sampleSize}ドキュメントに制限されています。検索を絞り込むには、その他の検索用語を追加してください。", "discover.howToSeeOtherMatchingDocumentsDescription": "これらは検索条件に一致した初めの{sampleSize}件のドキュメントです。他の結果を表示するには検索条件を絞ってください。", "discover.noMatchRoute.bannerText": "Discoverアプリケーションはこのルートを認識できません:{route}", "discover.noResults.kqlExamples.kqlDescription": "{kqlLink}の詳細", @@ -2207,9 +2192,6 @@ "discover.pageTitleWithSavedSearch": "Discover - {savedSearchTitle}", "discover.savedSearchAliasMatchRedirect.objectNoun": "{savedSearch}検索", "discover.savedSearchURLConflictCallout.objectNoun": "{savedSearch}検索", - "discover.searchGenerationWithDescription": "検索{searchTitle}で生成されたテーブル", - "discover.searchGenerationWithDescriptionGrid": "検索{searchTitle}で生成されたテーブル({searchDescription})", - "discover.selectedDocumentsNumber": "{nr}個のドキュメントが選択されました", "discover.showingDefaultDataViewWarningDescription": "デフォルトのデータビューを表示しています:\"{loadedDataViewTitle}\"({loadedDataViewId})", "discover.showingSavedDataViewWarningDescription": "保存されたデータビューを表示しています:\"{ownDataViewTitle}\"({ownDataViewId})", "discover.singleDocRoute.errorMessage": "ID {dataViewId}の一致するデータビューが見つかりません", @@ -2235,8 +2217,8 @@ "discover.advancedSettings.docTableHideTimeColumnText": "Discover と、ダッシュボードのすべての保存された検索で、「時刻」列を非表示にします。", "discover.advancedSettings.docTableHideTimeColumnTitle": "「時刻」列を非表示", "discover.advancedSettings.documentExplorerLinkText": "ドキュメントエクスプローラー", - "discover.advancedSettings.enableSQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", - "discover.advancedSettings.enableSQLTitle": "SQLを有効にする", + "discover.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", + "discover.advancedSettings.enableESQLTitle": "SQLを有効にする", "discover.advancedSettings.fieldsPopularLimitText": "最も頻繁に使用されるフィールドのトップNを表示します", "discover.advancedSettings.fieldsPopularLimitTitle": "頻繁に使用されるフィールドの制限", "discover.advancedSettings.maxDocFieldsDisplayedText": "ドキュメント概要でレンダリングされるフィールドの最大数", @@ -2262,7 +2244,6 @@ "discover.backToTopLinkText": "最上部へ戻る。", "discover.badge.readOnly.text": "読み取り専用", "discover.badge.readOnly.tooltip": "検索を保存できません", - "discover.clearSelection": "選択した項目をクリア", "discover.confirmDataViewSave.cancel": "キャンセル", "discover.confirmDataViewSave.message": "選択したアクションでは、保存されたデータビューが必要です。", "discover.confirmDataViewSave.saveAndContinue": "保存して続行", @@ -2283,8 +2264,6 @@ "discover.context.unableToLoadAnchorDocumentDescription": "アンカードキュメントを読み込めません", "discover.context.unableToLoadDocumentDescription": "ドキュメントを読み込めません", "discover.contextViewRoute.errorTitle": "エラーが発生しました", - "discover.controlColumnHeader": "列の制御", - "discover.copyToClipboardJSON": "ドキュメントをクリップボードにコピー(JSON)", "discover.discoverBreadcrumbTitle": "Discover", "discover.discoverDefaultSearchSessionName": "Discover", "discover.discoverDescription": "ドキュメントにクエリをかけたりフィルターを適用することで、データをインタラクティブに閲覧できます。", @@ -2325,36 +2304,7 @@ "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "周りのドキュメントを表示", "discover.documentsAriaLabel": "ドキュメント", "discover.documentsErrorTitle": "検索エラー", - "unifiedDocViewer.docView.table.actions.label": "アクション", - "unifiedDocViewer.docView.table.actions.open": "アクションを開く", - "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "このフィールドの1つ以上の値が長すぎるため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "このフィールドは、検索またはフィルタリングできない正しくない形式の値が1つ以上あります。", - "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "このフィールドの1つ以上の値がElasticsearchによって無視されたため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "このフィールドの値が長すぎるため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "このフィールドの値の形式が正しくないため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "このフィールドの値はElasticsearchによって無視されたため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.searchPlaceHolder": "検索フィールド名", - "unifiedDocViewer.docViews.json.jsonTitle": "JSON", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "フィールド表示のフィルター", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "フィールド表示のフィルター", - "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "値でフィルター", - "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "値でフィルター", - "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "値を除外", - "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "値を除外", - "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "無視された値を含む", - "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "無視された値", - "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "フィールドを固定", - "unifiedDocViewer.docViews.table.pinFieldLabel": "フィールドを固定", "discover.docViews.table.scoreSortWarningTooltip": "_scoreの値を取得するには、並べ替える必要があります。", - "unifiedDocViewer.docViews.table.tableTitle": "表", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "表の列を切り替える", - "unifiedDocViewer.fieldChooser.discoverField.name": "フィールド詳細を切り替える", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", - "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスがないフィールドまたは無視された値は検索できません", - "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "フィールドを固定解除", - "unifiedDocViewer.docViews.table.unpinFieldLabel": "フィールドを固定解除", "discover.dropZoneTableLabel": "フィールドを列として表に追加するには、ゾーンをドロップします", "discover.dscTour.stepAddFields.imageAltText": "[使用可能なフィールド]リストで、プラスアイコンをクリックし、フィールドをドキュメントテーブルに切り替えます。", "discover.dscTour.stepAddFields.title": "フィールドをテーブルに追加", @@ -2375,46 +2325,24 @@ "discover.embeddable.search.displayName": "検索", "discover.errorCalloutShowErrorMessage": "詳細を表示", "discover.fieldChooser.availableFieldsTooltip": "フィールドをテーブルに表示できます。", - "unifiedDocViewer.fieldChooser.discoverField.actions": "アクション", "discover.fieldChooser.discoverField.addFieldTooltip": "フィールドを列として追加", - "unifiedDocViewer.fieldChooser.discoverField.multiField": "複数フィールド", - "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "複数フィールドにはフィールドごとに複数の値を入力できます", - "unifiedDocViewer.fieldChooser.discoverField.name": "フィールド", "discover.fieldChooser.discoverField.removeFieldTooltip": "フィールドを表から削除", - "unifiedDocViewer.fieldChooser.discoverField.value": "値", "discover.goToDiscoverButtonText": "Discoverに移動", - "discover.grid.closePopover": "ポップオーバーを閉じる", - "discover.grid.copyCellValueButton": "値をコピー", - "discover.grid.copyColumnNameToClipboard.toastTitle": "クリップボードにコピーされました", - "discover.grid.copyColumnNameToClipBoardButton": "名前をコピー", - "discover.grid.copyColumnValuesToClipBoardButton": "列をコピー", - "discover.grid.copyEscapedValueWithFormulasToClipboardWarningText": "値にはエスケープされた式を含めることができます。", - "discover.grid.copyFailedErrorText": "このブラウザーではクリップボードにコピーできません", - "discover.grid.copyValueToClipboard.toastTitle": "クリップボードにコピーされました", - "discover.grid.documentHeader": "ドキュメント", - "discover.grid.editFieldButton": "データビューフィールドを編集", - "discover.grid.filterFor": "フィルター", - "discover.grid.filterOut": "除外", "discover.grid.flyout.documentNavigation": "ドキュメントナビゲーション", "discover.grid.flyout.toastColumnAdded": "列'{columnName}'が追加されました", "discover.grid.flyout.toastColumnRemoved": "列'{columnName}'が削除されました", - "discover.grid.selectDoc": "ドキュメント'{rowNumber}'を選択", "discover.grid.tableRow.detailHeading": "拡張ドキュメント", "discover.grid.tableRow.textBasedDetailHeading": "展開された行", "discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "1つのドキュメント", "discover.grid.tableRow.viewSurroundingDocumentsHover": "このドキュメントの前後に出現したドキュメントを検査します。周りのドキュメントビューでは、固定されたフィルターのみがアクティブのままです。", "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周りのドキュメント", "discover.grid.tableRow.viewText": "表示:", - "discover.grid.viewDoc": "詳細ダイアログを切り替え", "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "ドキュメント", "discover.inspectorRequestDescriptionDocument": "このリクエストはElasticsearchにクエリをかけ、ドキュメントを取得します。", "discover.invalidFiltersWarnToast.description": "一部の適用されたフィルターのデータビューID参照は、現在のデータビューとは異なります。", "discover.invalidFiltersWarnToast.title": "別のインデックス参照", - "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", - "unifiedDocViewer.json.copyToClipboardLabel": "クリップボードにコピー", "discover.loadingDocuments": "ドキュメントを読み込み中", - "unifiedDocViewer.loadingJSON": "JSONを読み込んでいます", "discover.loadingResults": "結果を読み込み中", "discover.localMenu.alertsDescription": "アラート", "discover.localMenu.fallbackReportTitle": "無題のDiscover検索", @@ -2458,29 +2386,20 @@ "discover.noResults.suggestion.syntaxPopoverExampleHeader": "例", "discover.noResults.suggestion.tryText": "次の方法を試してください:", "discover.noResults.suggestion.viewAllMatchesButtonText": "すべての一致を表示", - "discover.noResultsFound": "結果が見つかりませんでした", "discover.notifications.invalidTimeRangeText": "指定された時間範囲が無効です。(開始:'{from}'、終了:'{to}')", "discover.notifications.invalidTimeRangeTitle": "無効な時間範囲", "discover.notifications.notSavedSearchTitle": "検索「{savedSearchTitle}」は保存されませんでした。", "discover.notifications.savedSearchTitle": "検索「{savedSearchTitle}」が保存されました。", "discover.pageTitleWithoutSavedSearch": "Discover - 検索は保存されていません", "discover.reloadSavedSearchButton": "検索をリセット", - "discover.removeColumnLabel": "列を削除", "discover.rootBreadcrumb": "Discover", "discover.sampleData.viewLinkLabel": "Discover", "discover.savedSearch.savedObjectName": "保存検索", "discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "Discoverで開く", "discover.searchingTitle": "検索中", - "discover.selectColumnHeader": "列を選択", "discover.serverLocatorExtension.titleFromLocatorUnknown": "不明な検索", - "discover.showAllDocuments": "すべてのドキュメントを表示", - "discover.showSelectedDocumentsOnly": "選択したドキュメントのみを表示", "discover.singleDocRoute.errorTitle": "エラーが発生しました", "discover.skipToBottomButtonLabel": "テーブルの最後に移動", - "unifiedDocViewer.sourceViewer.errorMessage": "現在データを取得できませんでした。タブを更新して、再試行してください。", - "unifiedDocViewer.sourceViewer.errorMessageTitle": "エラーが発生しました", - "unifiedDocViewer.sourceViewer.refresh": "更新", - "discover.toggleSidebarAriaLabel": "サイドバーを切り替える", "discover.topNav.openSearchPanel.manageSearchesButtonLabel": "検索の管理", "discover.topNav.openSearchPanel.noSearchesFoundDescription": "一致する検索が見つかりませんでした。", "discover.topNav.openSearchPanel.openSearchTitle": "検索を開く", @@ -2496,6 +2415,73 @@ "discover.viewAlert.searchSourceErrorTitle": "検索ソースの取得エラー", "discover.viewModes.document.label": "ドキュメント", "discover.viewModes.fieldStatistics.label": "フィールド統計情報", + "discover.fieldNameIcons.binaryAriaLabel": "バイナリー", + "discover.fieldNameIcons.booleanAriaLabel": "ブール", + "discover.fieldNameIcons.conflictFieldAriaLabel": "競合", + "discover.fieldNameIcons.counterFieldAriaLabel": "カウンターメトリック", + "discover.fieldNameIcons.dateFieldAriaLabel": "日付", + "discover.fieldNameIcons.dateRangeFieldAriaLabel": "日付範囲", + "discover.fieldNameIcons.denseVectorFieldAriaLabel": "密集ベクトル", + "discover.fieldNameIcons.flattenedFieldAriaLabel": "平坦化済み", + "discover.fieldNameIcons.gaugeFieldAriaLabel": "ゲージメトリック", + "discover.fieldNameIcons.geoPointFieldAriaLabel": "地理ポイント", + "discover.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報図形", + "discover.fieldNameIcons.histogramFieldAriaLabel": "ヒストグラム", + "discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP アドレス", + "discover.fieldNameIcons.ipRangeFieldAriaLabel": "IP範囲", + "discover.fieldNameIcons.keywordFieldAriaLabel": "キーワード", + "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3", + "discover.fieldNameIcons.nestedFieldAriaLabel": "ネスト済み", + "discover.fieldNameIcons.numberFieldAriaLabel": "数字", + "discover.fieldNameIcons.pointFieldAriaLabel": "点", + "discover.fieldNameIcons.rankFeatureFieldAriaLabel": "ランク特性", + "discover.fieldNameIcons.rankFeaturesFieldAriaLabel": "ランク特性", + "discover.fieldNameIcons.recordAriaLabel": "記録", + "discover.fieldNameIcons.shapeFieldAriaLabel": "形状", + "discover.fieldNameIcons.sourceFieldAriaLabel": "ソースフィールド", + "discover.fieldNameIcons.stringFieldAriaLabel": "文字列", + "discover.fieldNameIcons.textFieldAriaLabel": "テキスト", + "discover.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", + "discover.fieldNameIcons.versionFieldAriaLabel": "バージョン", + "unifiedDocViewer.docView.table.actions.label": "アクション", + "unifiedDocViewer.docView.table.actions.open": "アクションを開く", + "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "このフィールドの1つ以上の値が長すぎるため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "このフィールドは、検索またはフィルタリングできない正しくない形式の値が1つ以上あります。", + "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "このフィールドの1つ以上の値がElasticsearchによって無視されたため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "このフィールドの値が長すぎるため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "このフィールドの値の形式が正しくないため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "このフィールドの値はElasticsearchによって無視されたため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.searchPlaceHolder": "検索フィールド名", + "unifiedDocViewer.docViews.json.jsonTitle": "JSON", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "フィールド表示のフィルター", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "フィールド表示のフィルター", + "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "値でフィルター", + "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "値でフィルター", + "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "値を除外", + "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "値を除外", + "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "無視された値を含む", + "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "無視された値", + "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "フィールドを固定", + "unifiedDocViewer.docViews.table.pinFieldLabel": "フィールドを固定", + "unifiedDocViewer.docViews.table.tableTitle": "表", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "表の列を切り替える", + "unifiedDocViewer.fieldChooser.discoverField.name": "フィールド", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", + "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスがないフィールドまたは無視された値は検索できません", + "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "フィールドを固定解除", + "unifiedDocViewer.docViews.table.unpinFieldLabel": "フィールドを固定解除", + "unifiedDocViewer.fieldChooser.discoverField.actions": "アクション", + "unifiedDocViewer.fieldChooser.discoverField.multiField": "複数フィールド", + "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "複数フィールドにはフィールドごとに複数の値を入力できます", + "unifiedDocViewer.fieldChooser.discoverField.value": "値", + "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", + "unifiedDocViewer.json.copyToClipboardLabel": "クリップボードにコピー", + "unifiedDocViewer.loadingJSON": "JSONを読み込んでいます", + "unifiedDocViewer.sourceViewer.errorMessage": "現在データを取得できませんでした。タブを更新して、再試行してください。", + "unifiedDocViewer.sourceViewer.errorMessageTitle": "エラーが発生しました", + "unifiedDocViewer.sourceViewer.refresh": "更新", "domDragDrop.announce.cancelled": "移動がキャンセルされました。{label}は初期位置に戻りました", "domDragDrop.announce.cancelledItem": "移動がキャンセルされました。{label}は位置{position}の{groupLabel}グループに戻りました", "domDragDrop.announce.dropped.combineCompatible": "レイヤー{dropLayerNumber}の位置{dropPosition}でグループ{dropGroupLabel}の{dropLabel}にグループ{groupLabel}の{label}を結合しました。", @@ -3241,7 +3227,7 @@ "guidedOnboardingPackage.gettingStarted.cards.progressLabel": "{numberSteps}ステップ中{numberCompleteSteps}ステップ完了", "guidedOnboardingPackage.gettingStarted.cards.siemSecurity.title": "SIEMで{lineBreak}データの脅威を検出", "guidedOnboardingPackage.gettingStarted.cards.completeLabel": "ガイド完了", - "guidedOnboardingPackage.gettingStarted.cards.esreSearch.title": "セマンティック検索エクスペリエンスを構築", + "guidedOnboardingPackage.gettingStarted.cards.aiSearch.title": "セマンティック検索エクスペリエンスを構築", "guidedOnboardingPackage.gettingStarted.cards.hostsObservability.title": "ホストメトリックを監視", "guidedOnboardingPackage.gettingStarted.cards.kubernetesObservability.title": "Kubernetesクラスターの監視", "guidedOnboardingPackage.gettingStarted.cards.logsObservability.title": "ログを収集して分析", @@ -4169,7 +4155,7 @@ "indexPatternEditor.rollupDataView.warning.textParagraphOne": "Kibanaではロールアップに基づいてデータビューのデータサポートを提供します。保存された検索、可視化、ダッシュボードでこれらを使用すると問題が発生する場合があります。Timelionや機械学習などの一部の高度な機能ではサポートされていません。", "indexPatternEditor.rollupDataView.warning.textParagraphTwo": "ロールアップデータビューは、1つのロールアップインデックスとゼロ以上の標準インデックスと一致させることができます。ロールアップデータビューでは、メトリック、フィールド、間隔、アグリゲーションが制限されています。ロールアップインデックスは、1つのジョブ構成があるインデックス、または複数のジョブと互換する構成があるインデックスに制限されています。", "indexPatternEditor.rollupIndexPattern.warning.title": "ベータ機能", - "indexPatternEditor.saved": "'{indexPatternName}'が保存されました", + "indexPatternEditor.saved": "が保存されました", "indexPatternEditor.status.noSystemIndicesLabel": "データストリーム、インデックス、またはインデックスエイリアスがインデックスパターンと一致しません。", "indexPatternEditor.status.noSystemIndicesWithPromptLabel": "データストリーム、インデックス、またはインデックスエイリアスがインデックスパターンと一致しません。", "indexPatternEditor.status.notMatchLabel.notMatchNoIndicesDetail": "入力したインデックスパターンはデータストリーム、インデックス、またはインデックスエイリアスと一致しません。", @@ -5713,34 +5699,6 @@ "unifiedFieldList.fieldNameDescription.textField": "電子メール本文や製品説明などの全文テキスト。", "unifiedFieldList.fieldNameDescription.unknownField": "不明なフィールド", "unifiedFieldList.fieldNameDescription.versionField": "ソフトウェアバージョン。「セマンティックバージョニング」優先度ルールをサポートします。", - "discover.fieldNameIcons.binaryAriaLabel": "バイナリー", - "discover.fieldNameIcons.booleanAriaLabel": "ブール", - "discover.fieldNameIcons.conflictFieldAriaLabel": "競合", - "discover.fieldNameIcons.counterFieldAriaLabel": "カウンターメトリック", - "discover.fieldNameIcons.dateFieldAriaLabel": "日付", - "discover.fieldNameIcons.dateRangeFieldAriaLabel": "日付範囲", - "discover.fieldNameIcons.denseVectorFieldAriaLabel": "密集ベクトル", - "discover.fieldNameIcons.flattenedFieldAriaLabel": "平坦化済み", - "discover.fieldNameIcons.gaugeFieldAriaLabel": "ゲージメトリック", - "discover.fieldNameIcons.geoPointFieldAriaLabel": "地理ポイント", - "discover.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報図形", - "discover.fieldNameIcons.histogramFieldAriaLabel": "ヒストグラム", - "discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP アドレス", - "discover.fieldNameIcons.ipRangeFieldAriaLabel": "IP範囲", - "discover.fieldNameIcons.keywordFieldAriaLabel": "キーワード", - "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3", - "discover.fieldNameIcons.nestedFieldAriaLabel": "ネスト済み", - "discover.fieldNameIcons.numberFieldAriaLabel": "数字", - "discover.fieldNameIcons.pointFieldAriaLabel": "点", - "discover.fieldNameIcons.rankFeatureFieldAriaLabel": "ランク特性", - "discover.fieldNameIcons.rankFeaturesFieldAriaLabel": "ランク特性", - "discover.fieldNameIcons.recordAriaLabel": "記録", - "discover.fieldNameIcons.shapeFieldAriaLabel": "形状", - "discover.fieldNameIcons.sourceFieldAriaLabel": "ソースフィールド", - "discover.fieldNameIcons.stringFieldAriaLabel": "文字列", - "discover.fieldNameIcons.textFieldAriaLabel": "テキスト", - "discover.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", - "discover.fieldNameIcons.versionFieldAriaLabel": "バージョン", "unifiedFieldList.fieldNameSearch.filterByNameLabel": "検索フィールド名", "unifiedFieldList.fieldPopover.addExistsFilterLabel": "フィールド表示のフィルター", "unifiedFieldList.fieldPopover.deleteFieldLabel": "データビューフィールドを削除", @@ -5941,9 +5899,6 @@ "unifiedSearch.query.queryBar.indexPattern.findFilterSet": "クエリを検索", "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "このデータビューを管理", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "一時", - "unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "データビューを切り替えると、現在のSQLクエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesLabel": "テキストベースのクエリ言語", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "データビューを切り替えると、現在のSQLクエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "保存せずに切り替え", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "次回以降この警告を表示しない", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "保存して切り替え", @@ -7090,7 +7045,6 @@ "visualizationUiComponents.dimensionButtonIcon.colorIndicatorLabel": "このディメンションの色:{hex}", "visualizationUiComponents.queryInput.queryPlaceholderKql": "{example}", "visualizationUiComponents.queryInput.queryPlaceholderLucene": "{example}", - "visualizationUiComponents.colorPicker.seriesColor.auto": "自動", "visualizationUiComponents.colorPicker.seriesColor.label": "系列色", "visualizationUiComponents.colorPicker.tooltip.auto": "カスタム色を指定しない場合、Lensは自動的に色を選択します。", "visualizationUiComponents.colorPicker.tooltip.custom": "[自動]モードに戻すには、カスタム色をオフにしてください。", @@ -7633,7 +7587,7 @@ "xpack.apm.tutorial.windowsServerInstructions.textPre": "1.[ダウンロードページ]({downloadPageLink})からAPM Server Windows zipファイルをダウンロードします。\n2.zipファイルのコンテンツを{zipFileExtractFolder}に解凍します。\n3.「{apmServerDirectory}」ディレクトリの名前を「APM-Server」に変更します。\n4.管理者としてPowerShellプロンプトを開きます(PowerShellアイコンを右クリックして「管理者として実行」を選択します)。Windows XPをご使用の場合、PowerShellのダウンロードとインストールが必要な場合があります。\n5.PowerShellプロンプトで次のコマンドを実行し、APM ServerをWindowsサービスとしてインストールします。", "xpack.apm.unifiedSearchBar.placeholder": "{event, select, transaction {トランザクション} metric {メトリック} error {エラー} other {トランザクション、エラー、およびメトリック}}を検索(例:{queryExample})", "xpack.apm.waterfall.errorCount": "{errorCount, plural, other {#件の関連エラーを表示}}", - "xpack.apm.waterfall.exceedsMax": "このトレースの項目数は{traceItemCount}であり、現在の制限値{maxTraceItems}より大きくなっています。トレース全体を表示するには、制限を大きくしてください", + "xpack.apm.waterfall.exceedsMax": "このトレースの項目数は{traceDocsTotal}であり、現在の制限値{maxTraceItems}より大きくなっています。トレース全体を表示するには、制限を大きくしてください", "xpack.apm.waterfall.spanLinks.badge": "{total} {total, plural, other {スパンリンク}}", "xpack.apm.waterfall.spanLinks.tooltip.linkedChildren": "{linkedChildren}受信", "xpack.apm.waterfall.spanLinks.tooltip.linkedParents": "{linkedParents}送信", @@ -8331,7 +8285,6 @@ "xpack.apm.mobile.location.metrics.http.requests.title": "最も使用されている", "xpack.apm.mobile.location.metrics.launches": "最も多い起動", "xpack.apm.mobile.location.metrics.sessions": "最も多いセッション", - "xpack.apm.mobile.metrics.crash.rate": "クラッシュ率(毎分のクラッシュ数)", "xpack.apm.mobile.metrics.http.requests": "HTTPリクエスト", "xpack.apm.mobile.metrics.load.time": "最も遅いアプリ読み込み時間", "xpack.apm.mobile.metrics.sessions": "セッション", @@ -11360,11 +11313,8 @@ "xpack.csp.awsIntegration.sharedCredentialsDescription": "ツールやアプリケーションごとに異なるAWS認証情報を使用する場合、プロファイルを使用して、同じ設定ファイルに複数のアクセスキーを定義することができます。", "xpack.csp.awsIntegration.temporaryKeysDescription": "AWSの一時的なセキュリティ認証情報は、指定した期間だけ存続するように設定することができます。アクセスキーID、シークレットアクセスキー、セキュリティトークンから構成され、通常GetSessionTokenで確認することができます。", "xpack.csp.awsIntegration.temporaryKeysLabel": "一時キー", - "xpack.csp.benchmarks.benchmarkEmptyState.integrationsNotFoundTitle": "ベンチマーク統合が見つかりません", - "xpack.csp.benchmarks.benchmarkEmptyState.integrationsNotFoundWithFiltersTitle": "上記のフィルターでベンチマーク統合が見つかりませんでした。", "xpack.csp.benchmarks.benchmarkSearchField.searchPlaceholder": "統合名で検索", "xpack.csp.benchmarks.benchmarksPageHeader.addIntegrationButtonLabel": "統合の追加", - "xpack.csp.benchmarks.benchmarksPageHeader.benchmarkIntegrationsTitle": "ベンチマーク統合", "xpack.csp.benchmarks.benchmarksTable.agentPolicyColumnTitle": "エージェントポリシー", "xpack.csp.benchmarks.benchmarksTable.createdAtColumnTitle": "作成済み", "xpack.csp.benchmarks.benchmarksTable.createdByColumnTitle": "作成者", @@ -11467,13 +11417,11 @@ "xpack.csp.findings.findingsByResourceTable.postureScoreColumnLabel": "態勢スコア", "xpack.csp.findings.findingsErrorToast.searchFailedTitle": "検索失敗", "xpack.csp.findings.findingsFlyout.jsonTabTitle": "JSON", - "xpack.csp.findings.findingsFlyout.overviewTab.actualTitle": "実際", "xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle": "CISセクション", "xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle": "デフォルト値", "xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle": "詳細", "xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle": "評価日", "xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle": "証拠", - "xpack.csp.findings.findingsFlyout.overviewTab.expectedTitle": "期待値", "xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle": "フレームワークソース", "xpack.csp.findings.findingsFlyout.overviewTab.impactTitle": "インパクト", "xpack.csp.findings.findingsFlyout.overviewTab.indexTitle": "インデックス", @@ -11578,7 +11526,6 @@ "xpack.csp.rules.manageIntegrationButtonLabel": "統合を管理", "xpack.csp.rules.ruleFlyout.overviewTabLabel": "概要", "xpack.csp.rules.ruleFlyout.remediationTabLabel": "修正", - "xpack.csp.rules.rulesPageHeader.benchmarkIntegrationsButtonLabel": "ベンチマーク統合", "xpack.csp.rules.rulesPageSharedValues.benchmarkTitle": "ベンチマーク", "xpack.csp.rules.rulesPageSharedValues.deploymentTypeTitle": "デプロイタイプ", "xpack.csp.rules.rulesPageSharedValues.integrationTitle": "統合", @@ -12181,19 +12128,19 @@ "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "クラウドデプロイのエンタープライズ サーチノードが実行中ですか?{deploymentSettingsLink}", "xpack.enterpriseSearch.errorConnectingState.description1": "次のエラーのため、ホストURL {enterpriseSearchUrl}では、エンタープライズ サーチへの接続を確立できません:", "xpack.enterpriseSearch.errorConnectingState.description2": "ホストURLが{configFile}で正しく構成されていることを確認してください。", - "xpack.enterpriseSearch.esre.elser.description": "わずか数回のクリック操作で、簡単に{elser}をデプロイし、即時テキストセマンティック検索機能を実現できます。このモデルは、「text_expansion」フィールドを使用してドキュメントとクエリテキストを拡張し、すぐに使えるシームレスな検索を提供します。", - "xpack.enterpriseSearch.esre.elserPanel.step2.description": "インデックスを作成した後は、インデックスを選択し、{pipelinesName}タブをクリックします。", - "xpack.enterpriseSearch.esre.esreDocsSection.description": "ESREの基本操作と、具体的な例でこれらのツールをテストする方法の詳細については、{esreDocumentation}をご覧ください。", - "xpack.enterpriseSearch.esre.esreDocsSection.faq.description": "これらの{frequentlyAskedQuestions}でESREとは何かをご覧ください。", - "xpack.enterpriseSearch.esre.esreDocsSection.help.description": "ヘルプが必要な場合{discussForum}をご確認ください。", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.description": "これらは複雑なトピックであるため、導入しやすいように、いくつかの{learningTopics}をまとめました。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.description": "{behavioralAnalytics}ダッシュボードとツールを使用して、ユーザーの行動を可視化し、変更の影響を測定します。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.description": "{behavioralAnalytics}にアクセスし、最初のコレクションを作成", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.description": "セマンティック分析、要約、固有表現抽出などの自然言語処理(NLP)ツールを使用し、検索結果の関連性を高めます。NLPは、ユーザーが読み込むことができる複数の{supportedMlModels}を使用して、ドキュメントをインテリジェントに分析し、フィールドを追加することでドキュメントを強化します。", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.description": "インデックスの{pipelinesName}タブに移動し、デプロイされたモデルで使用する推論パイプラインを作成します。", - "xpack.enterpriseSearch.esre.rrfRankingPanel.description": "{rrf}を使用して、複数の結果セットの評価を異なる関連性指標と組み合わせます。微調整は必要ありません。", - "xpack.enterpriseSearch.esre.vectorSearchPanel.description": "MLモデルから埋め込みを追加して、{vectorDbCapabilities}を使用します。Elastic MLノードに学習済みモデルをデプロイし、推論パイプラインを設定して、ドキュメントをインジェストしたときに自動的に埋め込みが追加されるようにします。これにより、_searchでkNNベクトル検索方法を使用できます。", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.description": "インデックスの{pipelinesName}タブに移動し、デプロイされたモデルで使用する推論パイプラインを作成します。", + "xpack.enterpriseSearch.aiSearch.elser.description": "わずか数回のクリック操作で、簡単に{elser}をデプロイし、即時テキストセマンティック検索機能を実現できます。このモデルは、「text_expansion」フィールドを使用してドキュメントとクエリテキストを拡張し、すぐに使えるシームレスな検索を提供します。", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description": "インデックスを作成した後は、インデックスを選択し、{pipelinesName}タブをクリックします。", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.description": "これらのツールを開始し、具体的な例でテストする方法の詳細については、{searchLab} にアクセスしてください。", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.description": "訪問する{aiSearchDoc}。", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.description": "ヘルプが必要な場合{discussForum}をご確認ください。", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.description": "{searchLabsRepo} には、ノートブック、サンプル アプリ、リソースが含まれています。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.description": "{behavioralAnalytics}ダッシュボードとツールを使用して、ユーザーの行動を可視化し、変更の影響を測定します。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.description": "{behavioralAnalytics}にアクセスし、最初のコレクションを作成", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.description": "セマンティック分析、要約、固有表現抽出などの自然言語処理(NLP)ツールを使用し、検索結果の関連性を高めます。NLPは、ユーザーが読み込むことができる複数の{supportedMlModels}を使用して、ドキュメントをインテリジェントに分析し、フィールドを追加することでドキュメントを強化します。", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.description": "インデックスの{pipelinesName}タブに移動し、デプロイされたモデルで使用する推論パイプラインを作成します。", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.description": "{rrf}を使用して、複数の結果セットの評価を異なる関連性指標と組み合わせます。微調整は必要ありません。", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description": "MLモデルから埋め込みを追加して、{vectorDbCapabilities}を使用します。Elastic MLノードに学習済みモデルをデプロイし、推論パイプラインを設定して、ドキュメントをインジェストしたときに自動的に埋め込みが追加されるようにします。これにより、_searchでkNNベクトル検索方法を使用できます。", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description": "インデックスの{pipelinesName}タブに移動し、デプロイされたモデルで使用する推論パイプラインを作成します。", "xpack.enterpriseSearch.index.connector.syncRules.description": "上位のアイテム、ファイルタイプ、(ファイルまたはフォルダー)パスを追加または除外\n {indexName}から同期します。デフォルトではすべてが含まれています。各ドキュメントは\n 以下のルールに対してテストされ、最初の一致するルールが適用されます。", "xpack.enterpriseSearch.inferencePipelineCard.action.delete.disabledDescription": "この推論パイプラインは削除できません。複数のパイプライン[{indexReferences}]で使用されています。削除する前に、1つのインジェストパイプライン以外のすべてからこのパイプラインをデタッチする必要があります。", "xpack.enterpriseSearch.inferencePipelineCard.deleteConfirm.description": "パイプライン\"{pipelineName}\"を機械学習推論パイプラインから切り離し、削除しています。", @@ -13684,19 +13631,6 @@ "xpack.enterpriseSearch.content.ml_inference.text_embedding": "密ベクトルテキスト埋め込み", "xpack.enterpriseSearch.content.ml_inference.text_expansion": "ELSERテキスト拡張", "xpack.enterpriseSearch.content.ml_inference.zero_shot_classification": "ゼロショットテキスト分類", - "xpack.enterpriseSearch.content.nativeConnectors.azureBlob.name": "Azure Blob Storage", - "xpack.enterpriseSearch.content.nativeConnectors.confluence.name": "Confluence Cloud & Server", - "xpack.enterpriseSearch.content.nativeConnectors.customConnector.name": "カスタマイズされたコネクター", - "xpack.enterpriseSearch.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", - "xpack.enterpriseSearch.content.nativeConnectors.jira.name": "Jira Cloud & Server", - "xpack.enterpriseSearch.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", - "xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB", - "xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL", - "xpack.enterpriseSearch.content.nativeConnectors.networkDrive.name": "ネットワークドライブ", - "xpack.enterpriseSearch.content.nativeConnectors.oracle.name": "Oracle", - "xpack.enterpriseSearch.content.nativeConnectors.postgresql.name": "PostgreSQL", - "xpack.enterpriseSearch.content.nativeConnectors.s3.name": "S3", - "xpack.enterpriseSearch.content.nativeConnectors.sharepoint_online.name": "Sharepoint Online", "xpack.enterpriseSearch.content.navTitle": "コンテンツ", "xpack.enterpriseSearch.content.new_index.apiDescription": "プログラム的にElasticsearchインデックスにドキュメントを追加するにはAPIを使用します。パイプラインを作成して開始", "xpack.enterpriseSearch.content.new_index.apiTitle": "新しい検索インデックス", @@ -14173,75 +14107,75 @@ "xpack.enterpriseSearch.errorConnectingState.troubleshootAuth": "ユーザー認証を確認してください。", "xpack.enterpriseSearch.errorConnectingState.troubleshootAuthNative": "Elasticsearchネイティブ認証、SSO/SAML、またはOpenID Connectを使用して認証する必要があります。", "xpack.enterpriseSearch.errorConnectingState.troubleshootAuthSAML": "SAMLやOpenID Connectなどの外部SSOプロバイダーを使用している場合は、エンタープライズ サーチでSAML/OIDCレルムを設定できる必要があります。", - "xpack.enterpriseSearch.esre.description": "開発者がElasticプラットフォームを使ってAI検索エンジンを搭載したアプリケーションを構築するためのツールキット。", - "xpack.enterpriseSearch.esre.elser.description.elserLinkText": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.esre.elserAccordion.description": "即時セマンティック検索機能", - "xpack.enterpriseSearch.esre.elserAccordion.title": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.esre.elserPanel.step1.buttonLabel": "インデックスを作成", - "xpack.enterpriseSearch.esre.elserPanel.step1.title": "インデックスを作成", - "xpack.enterpriseSearch.esre.elserPanel.step2.description.pipelinesName": "パイプライン", - "xpack.enterpriseSearch.esre.elserPanel.step2.title": "インデックスのパイプラインタブに移動", - "xpack.enterpriseSearch.esre.elserPanel.step3.description": "ELSERをワンクリックでデプロイし、そのモデルを使った推論パイプラインを作成できるパネルを探します。", - "xpack.enterpriseSearch.esre.elserPanel.step3.title": "画面の指示に従い、ELSERをデプロイ", - "xpack.enterpriseSearch.esre.esreDocsSection.description.esreLinkText": "ESREドキュメント", - "xpack.enterpriseSearch.esre.esreDocsSection.faq.title": "FAQ", - "xpack.enterpriseSearch.esre.esreDocsSection.help.title": "ヘルプ", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.discussForumLinkText": "ESREディスカッションフォーラム", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.frequentlyAskedQuestionsLinkText": "FAQ", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.learningTopicsLinkText": "学習トピック", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.title": "学習", - "xpack.enterpriseSearch.esre.esreDocsSection.title": "ESREドキュメントでさらに深く", - "xpack.enterpriseSearch.esre.guide.description": "Elasticsearch Relevance Engine™(ESRE)により、開発者はElasticプラットフォームを使ってAI検索エンジンを搭載したアプリケーションを構築できます。ESREは、当社独自の学習済みMLモデルELSER、ベクトル検索と埋め込み機能、ベクトル検索とテキスト検索を組み合わせたRRFランキングを含むツールと機能のセットです。", - "xpack.enterpriseSearch.esre.guide.pageTitle": "ESREで検索を強化", - "xpack.enterpriseSearch.esre.linearCombinationAccordion.description": "複数のランキングから重み付けがされた結果", - "xpack.enterpriseSearch.esre.linearCombinationAccordion.title": "線形結合", - "xpack.enterpriseSearch.esre.linearCombinationPanel.description": "データポイント間の類似度スコアまたは距離を計算するために使用します。重みを使って属性や特徴量を組み合わせることで、関連性係数をカスタマイズできます。", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step1.knnSearchCombineLinkText": "近似kNNと他の特徴量を組み合わせる", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step1.title": "_searchクエリで線形結合を使用する方法を見る", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step2.buttonLabel": "コンソールを開く", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step2.title": "今すぐコンソールで試す", - "xpack.enterpriseSearch.esre.measurePerformanceSection.behavioralAnalyticsLinkText": "Behavioral Analytics", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.behavioralAnalyticsLinkText": "Behavioral Analytics", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.title": "コレクションの作成", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step2.description": "コレクションを作成した後、手順に従って、トラッカーをあなたのアプリケーションやWebサイトに統合してください。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step2.title": "分析トラッカーを統合", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step3.description": "Elasticのダッシュボードとツールでは、エンドユーザーの行動を可視化し、検索アプリケーションのパフォーマンスを測定できます。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step3.title": "ダッシュボードを確認", - "xpack.enterpriseSearch.esre.measurePerformanceSection.title": "パフォーマンスを測定", - "xpack.enterpriseSearch.esre.navTitle": "ESRE", - "xpack.enterpriseSearch.esre.nlpEnrichmentAccordion.description": "学習済みMLモデルを使用した、インサイトが豊富なデータエンリッチメント", - "xpack.enterpriseSearch.esre.nlpEnrichmentAccordion.title": "NLPエンリッチメント", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.description.supportedMlModelsLinkText": "サポートされているMLモデル", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.buttonLabel": "学習済みモデルを表示", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.guideToTrainedModelsLinkText": "学習済みモデルのガイド", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.supportedNlpModelsLinkText": "サポートされているNLPモデル", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.title": "MLモデルのアップロード方法", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step2.buttonLabel": "インデックスを作成", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step2.title": "インデックスを作成", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.description.pipelinesName": "パイプライン", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.title": "ML推論パイプラインを作成", - "xpack.enterpriseSearch.esre.productName": "ESRE", - "xpack.enterpriseSearch.esre.rankAggregationSection.description": "全体的なランキングパフォーマンスを向上させるために、異なるランキングを融合または組み合わせるオプションの方法。", - "xpack.enterpriseSearch.esre.rankAggregationSection.title": "ランク集約方法を使用", - "xpack.enterpriseSearch.esre.rrfRankingAccordion.description": "構成なしで、インテリジェントにランキングを結合", - "xpack.enterpriseSearch.esre.rrfRankingAccordion.title": "RRFハイブリッドランキング", - "xpack.enterpriseSearch.esre.rrfRankingPanel.rrfLinkText": "逆順位融合(RRF)", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step1.rrfDocsLinkText": "逆順位融合(RRF)ドキュメント", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step1.title": "_searchクエリにおけるRRFの使用例を見る", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step2.buttonLabel": "コンソールを開く", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step2.title": "今すぐコンソールで試す", - "xpack.enterpriseSearch.esre.semanticSearch.description": "ESREでは、これらの情報検索ツールの中から任意のツールを組み合わせることができます。", - "xpack.enterpriseSearch.esre.semanticSearch.title": "セマンティック検索を設定", - "xpack.enterpriseSearch.esre.vectorSearchAccordion.description": "非構造化データ用の強力な類似度検索", - "xpack.enterpriseSearch.esre.vectorSearchAccordion.title": "ベクトル検索", - "xpack.enterpriseSearch.esre.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "ElasticsearchのベクトルDB機能", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.buttonLabel": "学習済みモデルを表示", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "学習済みモデルのガイド", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.title": "MLモデルのアップロード方法", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step2.buttonLabel": "インデックスを作成", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step2.title": "インデックスを作成", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.description.pipelinesName": "パイプライン", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.title": "ML推論パイプラインを作成", + "xpack.enterpriseSearch.aiSearch.description": "開発者がElasticプラットフォームを使ってAI検索エンジンを搭載したアプリケーションを構築するためのツールキット。", + "xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText": "Elastic Learned Sparse Encoder v2", + "xpack.enterpriseSearch.aiSearch.elserAccordion.description": "即時セマンティック検索機能", + "xpack.enterpriseSearch.aiSearch.elserAccordion.title": "Elastic Learned Sparse Encoder", + "xpack.enterpriseSearch.aiSearch.elserPanel.step1.buttonLabel": "インデックスを作成", + "xpack.enterpriseSearch.aiSearch.elserPanel.step1.title": "インデックスを作成", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description.pipelinesName": "パイプライン", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.title": "インデックスのパイプラインタブに移動", + "xpack.enterpriseSearch.aiSearch.elserPanel.step3.description": "ELSERをワンクリックでデプロイし、そのモデルを使った推論パイプラインを作成できるパネルを探します。", + "xpack.enterpriseSearch.aiSearch.elserPanel.step3.title": "画面の指示に従い、ELSERをデプロイ", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.description.searchLabsLinkText": "Search Labs", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.title": "ドキュメンテーション", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.title": "ヘルプ", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.helpLinkText": "フォーラムまたは Elastic コミュニティ Slack について議論します", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.aiSearchDocLinkText": "Elastic ドキュメンテーション", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.searchLabsRepoLinkText": "SearchLabs リポジトリ ", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.title": "学習", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.title": "AI 検索でさらに深く掘り下げる", + "xpack.enterpriseSearch.aiSearch.guide.description": "Elastic プラットフォームを使用して、AI 検索を活用したアプリケーションを構築します。これには、独自のトレーニング済み ML モデル ELSER、ベクトル検索と埋め込み機能、ベクトル検索とテキスト検索を組み合わせるための RRF ランキングが含まれます。", + "xpack.enterpriseSearch.aiSearch.guide.pageTitle": "AI で検索を強化する", + "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.description": "複数のランキングから重み付けがされた結果", + "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.title": "線形結合", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.description": "データポイント間の類似度スコアまたは距離を計算するために使用します。重みを使って属性や特徴量を組み合わせることで、関連性係数をカスタマイズできます。", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step1.knnSearchCombineLinkText": "近似kNNと他の特徴量を組み合わせる", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step1.title": "_searchクエリで線形結合を使用する方法を見る", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step2.buttonLabel": "コンソールを開く", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step2.title": "今すぐコンソールで試す", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.behavioralAnalyticsLinkText": "Behavioral Analytics", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.behavioralAnalyticsLinkText": "Behavioral Analytics", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.title": "コレクションの作成", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step2.description": "コレクションを作成した後、手順に従って、トラッカーをあなたのアプリケーションやWebサイトに統合してください。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step2.title": "分析トラッカーを統合", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step3.description": "Elasticのダッシュボードとツールでは、エンドユーザーの行動を可視化し、検索アプリケーションのパフォーマンスを測定できます。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step3.title": "ダッシュボードを確認", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.title": "パフォーマンスを測定", + "xpack.enterpriseSearch.aiSearch.navTitle": "AI検索", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentAccordion.description": "学習済みMLモデルを使用した、インサイトが豊富なデータエンリッチメント", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentAccordion.title": "NLPエンリッチメント", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.description.supportedMlModelsLinkText": "サポートされているMLモデル", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.buttonLabel": "学習済みモデルを表示", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.guideToTrainedModelsLinkText": "学習済みモデルのガイド", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.supportedNlpModelsLinkText": "サポートされているNLPモデル", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.title": "MLモデルのアップロード方法", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step2.buttonLabel": "インデックスを作成", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step2.title": "インデックスを作成", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.description.pipelinesName": "パイプライン", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.title": "ML推論パイプラインを作成", + "xpack.enterpriseSearch.aiSearch.productName": "AI検索", + "xpack.enterpriseSearch.aiSearch.rankAggregationSection.description": "全体的なランキングパフォーマンスを向上させるために、異なるランキングを融合または組み合わせるオプションの方法。", + "xpack.enterpriseSearch.aiSearch.rankAggregationSection.title": "ランク集約方法を使用", + "xpack.enterpriseSearch.aiSearch.rrfRankingAccordion.description": "構成なしで、インテリジェントにランキングを結合", + "xpack.enterpriseSearch.aiSearch.rrfRankingAccordion.title": "RRFハイブリッドランキング", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.rrfLinkText": "逆順位融合(RRF)", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step1.rrfDocsLinkText": "逆順位融合(RRF)ドキュメント", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step1.title": "_searchクエリにおけるRRFの使用例を見る", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.buttonLabel": "コンソールを開く", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.title": "今すぐコンソールで試す", + "xpack.enterpriseSearch.aiSearch.semanticSearch.description": "ESREでは、これらの情報検索ツールの中から任意のツールを組み合わせることができます。", + "xpack.enterpriseSearch.aiSearch.semanticSearch.title": "セマンティック検索を設定", + "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.description": "非構造化データ用の強力な類似度検索", + "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.title": "ベクトル検索", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "ElasticsearchのベクトルDB機能", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.buttonLabel": "学習済みモデルを表示", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "学習済みモデルのガイド", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.title": "MLモデルのアップロード方法", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.buttonLabel": "インデックスを作成", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.title": "インデックスを作成", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description.pipelinesName": "パイプライン", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.title": "ML推論パイプラインを作成", "xpack.enterpriseSearch.guideConfig.addDataStep.description": "カスタマイズ可能なインジェストパイプラインと推論パイプラインにより、データのインジェスト、インデックスの作成、データのリッチ化を実現します。", "xpack.enterpriseSearch.guideConfig.addDataStep.title": "データの追加", "xpack.enterpriseSearch.guideConfig.description": "Elasticのウェブクローラー、コネクター、APIを使って、お客様のデータで検索体験を構築するお手伝いをします。", @@ -14307,40 +14241,6 @@ "xpack.enterpriseSearch.licenseDocumentationLink": "ライセンス機能の詳細", "xpack.enterpriseSearch.licenseManagementLink": "ライセンスを更新", "xpack.enterpriseSearch.nameLabel": "名前", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.collectionLabel": "収集", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.directConnectionLabel": "直接接続", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.hostLabel": "サーバーホスト名", - "xpack.enterpriseSearch.nativeConnectors.mongodb.name": "MongoDB", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.hostLabel": "ホスト", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.passwordLabel": "パスワード", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.retriesLabel": "リクエストごとの再試行回数", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.rowsFetchedLabel": "リクエストごとに取得された行数", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.schemaLabel": "スキーマ", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.tablesLabel": "カンマ区切りのテーブルのリスト", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.usernameLabel": "ユーザー名", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.validateHostLabel": "ホストを検証", - "xpack.enterpriseSearch.nativeConnectors.mssql.name": "Microsoft SQL", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.hostLabel": "ホスト", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.passwordLabel": "パスワード", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.retriesLabel": "リクエストごとの再試行回数", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.rowsFetchedLabel": "リクエストごとに取得された行数", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.tablesLabel": "カンマ区切りのテーブルのリスト", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.usernameLabel": "ユーザー名", - "xpack.enterpriseSearch.nativeConnectors.mysql.name": "MySQL", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.hostLabel": "ホスト", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.retriesLabel": "リクエストごとの再試行回数", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.rowsFetchedLabel": "リクエストごとに取得された行数", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.tablesLabel": "カンマ区切りのテーブルのリスト", - "xpack.enterpriseSearch.nativeConnectors.postgresql.name": "PostgreSQL", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.clientIdLabel": "クライアントID", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.secretValueLabel": "シークレット値", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.siteCollectionsLabel": "カンマ区切りのサイトのリスト", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.siteCollectionsTooltip": "データのインジェスト元のサイトのリスト(カンマ区切り)。すべての使用可能なサイトを含めるには、*を使用します。", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.tenantIdLabel": "テナントID", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.tenantNameLabel": "テナント名", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.textExtractionServiceLabel": "テキスト抽出サービスを使用", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.textExtractionServiceTooltip": "Elastic Data Extraction Serviceを別途デプロイする必要があります。また、パイプラインの設定でテキスト抽出を無効にする必要があります。", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.name": "Sharepoint Online", "xpack.enterpriseSearch.nativeLabel": "ネイティブ", "xpack.enterpriseSearch.nav.analyticsCollections.explorerTitle": "エクスプローラー", "xpack.enterpriseSearch.nav.analyticsCollections.integrationTitle": "統合", @@ -14352,8 +14252,8 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "設定", "xpack.enterpriseSearch.nav.contentTitle": "コンテンツ", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概要", - "xpack.enterpriseSearch.nav.esreTitle": "ESRE", + "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "はじめる", + "xpack.enterpriseSearch.nav.aiSearchTitle": "AI Search", "xpack.enterpriseSearch.nav.searchApplicationsTitle": "検索アプリケーション", "xpack.enterpriseSearch.nav.searchIndicesTitle": "インデックス", "xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search", @@ -14526,9 +14426,6 @@ "xpack.enterpriseSearch.searchExperiences.navTitle": "検索エクスペリエンス", "xpack.enterpriseSearch.searchExperiences.productDescription": "直感的で、魅力的な検索エクスペリエンスを簡単に構築できます。", "xpack.enterpriseSearch.searchExperiences.productName": "検索エクスペリエンス", - "xpack.enterpriseSearch.server.connectors.configuration.error": "コネクターが見つかりませんでした", - "xpack.enterpriseSearch.server.connectors.scheduling.error": "ドキュメントが見つかりませんでした", - "xpack.enterpriseSearch.server.connectors.serviceType.error": "ドキュメントが見つかりませんでした", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionExistsError": "コレクション名はすでに存在します。別の名前を選択してください。", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionNotFoundErrorMessage": "分析コレクションが見つかりません", "xpack.enterpriseSearch.server.routes.addConnector.connectorExistsError": "コネクターまたはインデックスはすでに存在します", @@ -16662,8 +16559,6 @@ "xpack.fleet.policyDetailsPackagePolicies.createFirstTitle": "最初の統合を追加", "xpack.fleet.policyForm.deletePolicyActionText": "ポリシーを削除", "xpack.fleet.policyForm.deletePolicyActionText.disabled": "管理されたパッケージポリシーのエージェントポリシーは削除できません。", - "xpack.fleet.policyForm.deletePolicyGroupDescription": "既存のデータは削除されません。", - "xpack.fleet.policyForm.deletePolicyGroupTitle": "ポリシーを削除", "xpack.fleet.policyForm.generalSettingsGroupDescription": "エージェントポリシーの名前と説明を選択してください。", "xpack.fleet.policyForm.generalSettingsGroupTitle": "一般設定", "xpack.fleet.renameAgentTags.errorNotificationTitle": "タグ名の変更が失敗しました", @@ -17340,10 +17235,7 @@ "xpack.idxMgmt.breadcrumb.editTemplateLabel": "テンプレートを編集", "xpack.idxMgmt.breadcrumb.homeLabel": "インデックス管理", "xpack.idxMgmt.breadcrumb.templatesLabel": "テンプレート", - "xpack.idxMgmt.componentTemplate.breadcrumb.componentTemplatesLabel": "コンポーネントテンプレート", - "xpack.idxMgmt.componentTemplate.breadcrumb.createComponentTemplateLabel": "コンポーネントテンプレートの作成", "xpack.idxMgmt.componentTemplate.breadcrumb.editComponentTemplateLabel": "コンポーネントテンプレートの編集", - "xpack.idxMgmt.componentTemplate.breadcrumb.homeLabel": "インデックス管理", "xpack.idxMgmt.componentTemplateClone.loadComponentTemplateTitle": "コンポーネントテンプレート「{sourceComponentTemplateName}」の読み込みエラー", "xpack.idxMgmt.componentTemplateDetails.aliasesTabTitle": "エイリアス", "xpack.idxMgmt.componentTemplateDetails.cloneActionLabel": "クローンを作成", @@ -17461,7 +17353,6 @@ "xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "データストリームに作成されたバッキングインデックスの累積数", "xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "ヘルス", "xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "データストリームの現在のバッキングインデックスのヘルス", - "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "なし", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "インデックスライフサイクルポリシー", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "データストリームのデータを管理するインデックスライフサイクルポリシー", "xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "インデックステンプレート", @@ -18659,7 +18550,7 @@ "xpack.infra.logSourceConfiguration.missingDataViewsLabel": "データビュー{indexPatternId}が見つかりません", "xpack.infra.logSourceConfiguration.missingMessageFieldErrorMessage": "データビューには{messageField}フィールドが必要です。", "xpack.infra.logSourceErrorPage.savedObjectNotFoundErrorMessage": "{savedObjectType}が見つかりませんでした:{savedObjectId}", - "xpack.infra.metadataEmbeddable.errorMessage": "データの読み込みエラーが発生しました。{reload}し、ホスト詳細をもう一度開いてください。", + "xpack.infra.metadataEmbeddable.errorMessage": "データの読み込みエラーが発生しました。{refetch}し、ホスト詳細をもう一度開いてください。", "xpack.infra.metrics.alertDetailsAppSection.criterion.subtitle": "最後の{lookback} {timeLabel}", "xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError": "フィルタークエリには{groupCount, plural, other {これらのフィールド}}に対する一致が含まれているため、このルールによって、想定を下回る{matchedGroups}に関するアラートが発行される場合があります。詳細については、{filteringAndGroupingLink}を参照してください。", "xpack.infra.metrics.alertFlyout.customEquationEditor.aggregationLabel": "アグリゲーション{name}", @@ -18761,7 +18652,6 @@ "xpack.infra.assetDetailsEmbeddable.displayName": "アセット詳細", "xpack.infra.assetDetailsEmbeddable.title": "アセット詳細", "xpack.infra.assetDetailsEmbeddable.overview.kpi.cpuUsage.title": "CPU使用状況", - "xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title": "ディスク容量使用状況", "xpack.infra.assetDetailsEmbeddable.overview.kpi.memoryUsage.title": "メモリー使用状況", "xpack.infra.assetDetailsEmbeddable.overview.kpi.normalizedLoad1m.title": "正規化された負荷", "xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average": "平均", @@ -18868,7 +18758,6 @@ "xpack.infra.hostsViewPage.metrics.tooltip.tx": "ホストのパブリックインターフェースで1秒間に送信したバイト数。", "xpack.infra.hostsViewPage.table.addFilter": "フィルターを追加します", "xpack.infra.hostsViewPage.table.cpuUsageColumnHeader": "CPU使用状況(平均)", - "xpack.infra.hostsViewPage.table.diskSpaceUsageColumnHeader": "ディスク容量使用状況(平均)", "xpack.infra.hostsViewPage.table.memoryFreeColumnHeader": "空きメモリー(平均)", "xpack.infra.hostsViewPage.table.memoryUsageColumnHeader": "メモリー使用状況(平均)", "xpack.infra.hostsViewPage.table.nameColumnHeader": "名前", @@ -18894,7 +18783,6 @@ "xpack.infra.hostsViewPage.tabs.metricsCharts.diskIOWrite": "ディスク書き込みIOPS", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskReadThroughput": "ディスク読み取りスループット", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceAvailable": "空きディスク容量", - "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed": "ディスク容量使用状況", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskWriteThroughput": "ディスク書き込みスループット", "xpack.infra.hostsViewPage.tabs.metricsCharts.memoryFree": "空きメモリー", "xpack.infra.hostsViewPage.tabs.metricsCharts.memoryUsage": "メモリー使用状況", @@ -21155,7 +21043,6 @@ "xpack.lens.indexPattern.percentFormatLabel": "割合(%)", "xpack.lens.indexPattern.percentile": "パーセンタイル", "xpack.lens.indexPattern.percentile.documentation.quick": "\n すべてのドキュメントで発生する値のnパーセントよりも小さい最大値。\n ", - "xpack.lens.indexPattern.percentile.errorMessage": "パーセンタイルは1~99の範囲の整数でなければなりません。", "xpack.lens.indexPattern.percentile.percentileRanksValue": "パーセンタイル順位値", "xpack.lens.indexPattern.percentile.percentileValue": "パーセンタイル", "xpack.lens.indexPattern.percentile.signature": "フィールド:文字列、[percentile]:数値", @@ -24014,17 +23901,11 @@ "xpack.ml.entityFilter.addFilterTooltip": "フィルターを追加します", "xpack.ml.entityFilter.removeFilterTooltip": "フィルターを削除", "xpack.ml.logRateAnalysis.pageHeader": "ログレートスパイクを説明", - "xpack.ml.explorer.addToDashboard.anomalyCharts.dashboardsTitle": "異常グラフをダッシュボードに追加", "xpack.ml.explorer.addToDashboard.anomalyCharts.maxSeriesToPlotLabel": "プロットする最大系列数", - "xpack.ml.explorer.addToDashboard.cancelButtonLabel": "キャンセル", - "xpack.ml.explorer.addToDashboard.selectDashboardsLabel": "ダッシュボードを選択:", - "xpack.ml.explorer.addToDashboard.swimlanes.dashboardsTitle": "スイムレーンをダッシュボードに追加", - "xpack.ml.explorer.addToDashboard.swimlanes.selectSwimlanesLabel": "スイムレーンビューを選択:", "xpack.ml.explorer.addToDashboardLabel": "ダッシュボードに追加", "xpack.ml.explorer.annotationsErrorCallOutTitle": "注釈の読み込み中にエラーが発生しました。", "xpack.ml.explorer.annotationsErrorTitle": "注釈", "xpack.ml.explorer.anomalies.actionsAriaLabel": "アクション", - "xpack.ml.explorer.anomalies.actionsPopoverLabel": "異常グラフ", "xpack.ml.explorer.anomalies.addToDashboardLabel": "ダッシュボードに追加", "xpack.ml.explorer.anomaliesTitle": "異常", "xpack.ml.explorer.anomalyTimelinePopoverAdvancedExplanation": "異常エクスプローラーの各セクションに表示される異常スコアは少し異なる場合があります。各ジョブではバケット結果、全体的なバケット結果、影響因子結果、レコード結果があるため、このような不一致が発生します。各タイプの結果の異常スコアが生成されます。全体的なスイムレーンは、各ブロックの最大全体バケットスコアの最大値を示します。ジョブでスイムレーンを表示するときには、各ブロックに最大バケットスコアが表示されます。影響因子別に表示するときには、各ブロックに最大影響因子スコアが表示されます。", @@ -24046,10 +23927,6 @@ "xpack.ml.explorer.charts.viewInMapsLabel": "表示", "xpack.ml.explorer.charts.viewLabel": "表示", "xpack.ml.explorer.clearSelectionLabel": "選択した項目をクリア", - "xpack.ml.explorer.dashboardsTable.actionsHeader": "アクション", - "xpack.ml.explorer.dashboardsTable.descriptionColumnHeader": "説明", - "xpack.ml.explorer.dashboardsTable.editActionName": "ダッシュボードに追加", - "xpack.ml.explorer.dashboardsTable.titleColumnHeader": "タイトル", "xpack.ml.explorer.distributionChart.anomalyScoreLabel": "異常スコア", "xpack.ml.explorer.distributionChart.entityLabel": "エンティティ", "xpack.ml.explorer.distributionChart.typicalLabel": "通常", @@ -24216,13 +24093,7 @@ "xpack.ml.jobSelector.noResultsForJobLabel": "成果がありません", "xpack.ml.jobSelector.selectAllGroupLabel": "すべて選択", "xpack.ml.jobSelector.selectAllOptionLabel": "*", - "xpack.ml.jobService.activeDatafeedsLabel": "アクティブなデータフィード", - "xpack.ml.jobService.activeMLNodesLabel": "アクティブな ML ノード", - "xpack.ml.jobService.closedJobsLabel": "ジョブを作成", - "xpack.ml.jobService.failedJobsLabel": "失敗したジョブ", "xpack.ml.jobService.jobAuditMessagesErrorTitle": "ジョブメッセージの読み込みエラー", - "xpack.ml.jobService.openJobsLabel": "ジョブを開く", - "xpack.ml.jobService.totalJobsLabel": "合計ジョブ数", "xpack.ml.jobService.validateJobErrorTitle": "ジョブ検証エラー", "xpack.ml.jobsHealthAlertingRule.actionGroupName": "問題が検出されました", "xpack.ml.jobsHealthAlertingRule.name": "異常検知ジョブヘルス", @@ -27144,9 +27015,6 @@ "xpack.observability_onboarding.card.systemLogs.title": "システムログを収集", "xpack.observability_onboarding.configureLogs.advancedSettings": "高度な設定", "xpack.observability_onboarding.configureLogs.customConfig": "カスタム構成", - "xpack.observability_onboarding.configureLogs.dataset.helper": "ログの名前を設定します。すべて小文字、最大100文字、特殊文字は「_」に置き換えられます。", - "xpack.observability_onboarding.configureLogs.dataset.name": "データセット名", - "xpack.observability_onboarding.configureLogs.dataset.placeholder": "データセット名", "xpack.observability_onboarding.configureLogs.description": "ホスト上のログファイルへのパスを入力します。", "xpack.observability_onboarding.configureLogs.learnMore": "詳細", "xpack.observability_onboarding.configureLogs.logFile.addRow": "行の追加", @@ -27206,9 +27074,7 @@ "xpack.observability_onboarding.selectLogs.useOwnShipper": "APIキーを取得", "xpack.observability_onboarding.selectLogs.useOwnShipper.description": "APIキーを生成し、ログデータを収集するために独自のシッパーを使用します。", "xpack.observability_onboarding.steps.back": "戻る", - "xpack.observability_onboarding.steps.continue": "続行", "xpack.observability_onboarding.steps.exploreLogs": "ログを探索", - "xpack.observability_onboarding.steps.inspect": "検査", "xpack.observability_onboarding.title.collectCustomLogs": "カスタムログを収集", "xpack.observability.apmEnableContinuousRollupsDescription": "{betaLabel}連続ロールアップが有効な場合、UIは適切な解像度でメトリックを選択します。より大きな時間範囲では、より低い解像度の測定基準が使用され、読み込み時間が改善されます。", "xpack.observability.apmEnableServiceMetricsDescription": "{betaLabel}サービストランザクションメトリックの使用を有効にします。これは、サービスインベントリなどの特定のビューで使用できる低カーディナリティのメトリックで、読み込み時間を短縮します。", @@ -27221,7 +27087,6 @@ "xpack.observability.inspector.stats.queryTimeValue": "{queryTime}ms", "xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {が検出されました} Inventory {しきい値を超えました} other {を超えました}}", "xpack.observability.slo.alerting.burnRate.reason": "{actionGroupName}:過去{longWindowDuration}のバーンレートは{longWindowBurnRate}で、過去{shortWindowDuration}のバーンレートは{shortWindowBurnRate}です。両期間とも{burnRateThreshold}を超えたらアラート", - "xpack.observability.slo.burnRateWindow.thresholdTip": "しきい値は{target}xです", "xpack.observability.slo.clone.errorNotification": "{name}を複製できませんでした", "xpack.observability.slo.clone.successNotification": "{name}の作成が正常に完了しました", "xpack.observability.slo.create.errorNotification": "{name}の作成中に問題が発生しました", @@ -27239,8 +27104,6 @@ "xpack.observability.slo.slo.activeAlertsBadge.label": "{count, plural, other {#件のアラート}}", "xpack.observability.slo.slo.delete.errorNotification": "{name}の削除に失敗しました", "xpack.observability.slo.slo.delete.successNotification": "{name}が削除されました", - "xpack.observability.slo.slo.deleteConfirmationModal.deleteButtonLabel": "{name}削除", - "xpack.observability.slo.slo.deleteConfirmationModal.descriptionText": "削除された{name}は復元できません。", "xpack.observability.slo.slo.stats.objective": "{objective}目標", "xpack.observability.slo.slo.timeWindow.calendar": "{elapsed}/{total}日", "xpack.observability.slo.sloDetails.errorBudgetChartPanel.duration": "過去{duration}", @@ -27254,18 +27117,6 @@ "xpack.observability.slo.update.errorNotification": "{name}の更新中にエラーが発生しました", "xpack.observability.slo.update.successNotification": "正常に{name}を更新しました", "xpack.observability.syntheticsThrottlingEnabledExperimentDescription": "シンセティック監視構成で調整設定を有効にします。設定が有効でも、モニターで調整を使用できない場合があります。内部使用専用です。{link}", - "xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "最後の{lookback} {timeLabel}", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "フィルタークエリには{groupCount, plural, other {これらのフィールド}}に対する一致が含まれているため、このルールによって、想定を下回る{matchedGroups}に関するアラートが発行される場合があります。詳細については、{filteringAndGroupingLink}を参照してください。", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "アグリゲーション{name}", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "KQLフィルター{name}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "最後の{lookback} {timeLabel}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id}のデータの最後の{lookback} {timeLabel}", - "xpack.observability.threshold.rule.threshold.errorAlertReason": "{metric}のデータのクエリを試行しているときに、Elasticsearchが失敗しました", - "xpack.observability.threshold.rule.threshold.firedAlertReason": "{metric}は最後の{duration}{group}の{currentValue}です。{comparator} {threshold}のときにアラートを通知します。", - "xpack.observability.threshold.rule.threshold.noDataAlertReason": "{metric}は最後の{interval}{group}でデータがないことを報告しました", - "xpack.observability.threshold.rule.threshold.recoveredAlertReason": "{metric} が {comparator} に、{group} が {threshold} のしきい値(現在の値は {currentValue})になりました", - "xpack.observability.threshold.rule.threshold.thresholdRange": "{a}および{b}", - "xpack.observability.threshold.rule.thresholdExtraTitle": "{comparator} {threshold}のときにアラートを通知", "xpack.observability.transactionRateLabel": "{value} tpm", "xpack.observability.ux.coreVitals.averageMessage": " {bad}未満", "xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd}({ranksInd}%)", @@ -27506,18 +27357,6 @@ "xpack.observability.slo.alerting.windowDescription": "関連付けられたバーンレート値の期間。", "xpack.observability.slo.budgetingMethod.occurrences": "オカレンス", "xpack.observability.slo.budgetingMethod.timeslices": "タイムスライス", - "xpack.observability.slo.burnRate.criticalLongLabel": "1時間", - "xpack.observability.slo.burnRate.criticalShortLabel": "5分", - "xpack.observability.slo.burnRate.criticalTitle": "重大バーンレート", - "xpack.observability.slo.burnRate.highLongLabel": "6時間", - "xpack.observability.slo.burnRate.highShortLabel": "30分", - "xpack.observability.slo.burnRate.highTitle": "高バーンレート", - "xpack.observability.slo.burnRate.lowLongLabel": "3日", - "xpack.observability.slo.burnRate.lowShortLabel": "6時間", - "xpack.observability.slo.burnRate.lowTitle": "低バーンレート", - "xpack.observability.slo.burnRate.mediumLongLabel": "24時間", - "xpack.observability.slo.burnRate.mediumShortLabel": "2時間", - "xpack.observability.slo.burnRate.mediumTitle": "中バーンレート", "xpack.observability.slo.burnRate.technicalPreviewBadgeDescription": "この機能はテクニカルプレビュー中であり、将来のバージョンで変更または削除される可能性があります。デザインとコードは正式に一般公開された機能より完成度が低く、現状のまま保証なしで提供されています。テクニカルプレビュー機能は、正式に一般公開された機能に適用されるサポートサービスレベル契約の対象外です。", "xpack.observability.slo.burnRate.technicalPreviewBadgeTitle": "テクニカルプレビュー", "xpack.observability.slo.burnRate.title": "バーンレート時間枠", @@ -27559,8 +27398,7 @@ "xpack.observability.slo.rules.sloSelector.placeholder": "SLOを選択", "xpack.observability.slo.rules.sloSelector.rowLabel": "SLO", "xpack.observability.slo.slo.activeAlertsBadge.ariaLabel": "アクティブアラートバッジ", - "xpack.observability.slo.slo.deleteConfirmationModal.cancelButtonLabel": "キャンセル", - "xpack.observability.slo.slo.deleteConfirmationModal.title": "よろしいですか?", + "xpack.observability.slo.deleteConfirmationModal.cancelButtonLabel": "キャンセル", "xpack.observability.slo.slo.item.actions.clone": "クローンを作成", "xpack.observability.slo.slo.item.actions.delete": "削除", "xpack.observability.slo.slo.rulesBadge.popover": "このSLOではまだルールが構成されていません。SLOに違反したときにアラートを受信しません。", @@ -27640,8 +27478,6 @@ "xpack.observability.slo.sloEdit.sliType.customKql.goodQuery.tooltip": "このKQLクエリは、SLOを計算する目的で、「良好」または「成功」と見なされるイベントのサブセットを返します。このクエリは、ステータスコード、エラー、メッセージ、または他の関連するフィールドなどの一部の関連する条件に基づいて、イベントをフィルタリングします。", "xpack.observability.slo.sloEdit.sliType.customKql.goodQueryPlaceholder": "良いイベントを定義", "xpack.observability.slo.sloEdit.sliType.customKql.queryFilter": "クエリのフィルター", - "xpack.observability.slo.sloEdit.sliType.customKql.timestampField.label": "タイムスタンプフィールド", - "xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder": "タイムスタンプフィールドを選択", "xpack.observability.slo.sloEdit.sliType.customKql.totalQuery": "合計クエリ", "xpack.observability.slo.sloEdit.sliType.customKql.totalQuery.tooltip": "このKQLクエリは、良好なイベントと問題があるイベントの両方を含む、SLO計算に関連するすべてのイベントを返します。", "xpack.observability.slo.sloEdit.sliType.customKql.totalQueryPlaceholder": "合計イベントを定義", @@ -27655,8 +27491,6 @@ "xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "メトリックフィールドを選択", "xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "クエリのフィルター", "xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "の合計", - "xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.label": "タイムスタンプフィールド", - "xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder": "タイムスタンプフィールドを選択", "xpack.observability.slo.sloEdit.tags.label": "タグ", "xpack.observability.slo.sloEdit.tags.placeholder": "タグを追加", "xpack.observability.slo.sloEdit.targetSlo.label": "目標 / SLO(%)", @@ -27728,85 +27562,6 @@ "xpack.observability.statusVisualization.ux.link": "データの追加", "xpack.observability.statusVisualization.ux.title": "ユーザーエクスペリエンス", "xpack.observability.syntheticsThrottlingEnabledExperimentName": "シンセティック調整を有効にする(実験)", - "xpack.observability.threshold.rule..charts.errorMessage": "問題が発生しました", - "xpack.observability.threshold.rule..charts.noDataMessage": "グラフデータがありません", - "xpack.observability.threshold.rule..timeLabels.days": "日", - "xpack.observability.threshold.rule..timeLabels.hours": "時間", - "xpack.observability.threshold.rule..timeLabels.minutes": "分", - "xpack.observability.threshold.rule..timeLabels.seconds": "秒", - "xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule": "ルール", - "xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle": "しきい値を超えました", - "xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription": "アラートトラブルシューティングビューにリンクして、さらに詳しい状況や詳細を確認できます。server.publicBaseUrlが構成されていない場合は、空の文字列になります。", - "xpack.observability.threshold.rule.alertDropdownTitle": "アラートとルール", - "xpack.observability.threshold.rule.alertFlyout.addCondition": "条件を追加", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.avg": "平均", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.cardinality": "基数", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.count": "ドキュメントカウント", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.max": "最高", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.min": "最低", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p95": "95パーセンタイル", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p99": "99パーセンタイル", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.rate": "レート", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.sum": "合計", - "xpack.observability.threshold.rule.alertFlyout.alertDescription": "オブザーバビリティデータタイプが特定の値以上になったときにアラートを送信します。", - "xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear": "グループがデータのレポートを停止する場合にアラートで通知する", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink": "ドキュメント", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerHelpText": "すべての一意の値についてアラートを作成します。例:「host.id」または「cloud.region」。", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerText": "アラートのグループ化条件(オプション)", - "xpack.observability.threshold.rule.alertFlyout.customEquation": "カスタム等式", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.addCustomRow": "集約/フィールドを追加", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton": "削除", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage": "基本数学式をサポートします", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage": "カスタムラベルにはアラートグラフと理由/アラートタイトルに表示されます", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel": "ラベル(任意)", - "xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[この設定は、ドキュメントカウントアグリゲーターには適用されません。]", - "xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired": "集約が必要です。", - "xpack.observability.threshold.rule.alertFlyout.error.customMetrics.aggTypeRequired": "集約が必要です", - "xpack.observability.threshold.rule.alertFlyout.error.customMetrics.fieldRequired": "フィールドが必要です", - "xpack.observability.threshold.rule.alertFlyout.error.customMetricsError": "1つ以上のカスタムメトリックを定義する必要があります", - "xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters": "等式フィールドでは次の文字のみを使用できます:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", - "xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery": "フィルタークエリは無効です。", - "xpack.observability.threshold.rule.alertFlyout.error.metricRequired": "メトリックが必要です。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "しきい値が必要です。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "しきい値には有効な数値を含める必要があります。", - "xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "ページサイズが必要です。", - "xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "以前に検出されたグループが結果を報告しなくなった場合は、これを有効にすると、アクションがトリガーされます。自動的に急速にノードを開始および停止することがある動的に拡張するインフラストラクチャーでは、これは推奨されません。", - "xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "is not between", - "xpack.observability.threshold.rule.alertFlyout.removeCondition": "条件を削除", - "xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[データなし]", - "xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage": "\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\}は状態\\{\\{context.alertState\\}\\}です\n\n 理由:\n \\{\\{context.reason\\}\\}\n ", - "xpack.observability.threshold.rule.alerting.threshold.fired": "アラート", - "xpack.observability.threshold.rule.alerting.threshold.nodata": "データなし", - "xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue": "[データなし]", - "xpack.observability.threshold.rule.alertsButton": "アラートとルール", - "xpack.observability.threshold.rule.cloudActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたクラウドオブジェクト。", - "xpack.observability.threshold.rule.containerActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたコンテナーオブジェクト。", - "xpack.observability.threshold.rule.createInventoryRuleButton": "インベントリルールの作成", - "xpack.observability.threshold.rule.createThresholdRuleButton": "しきい値ルールを作成", - "xpack.observability.threshold.rule.groupByKeysActionVariableDescription": "データを報告しているグループを含むオブジェクト", - "xpack.observability.threshold.rule.homePage.toolbar.kqlSearchFieldPlaceholder": "インフラストラクチャーデータを検索…(例:host.name:host-1)", - "xpack.observability.threshold.rule.hostActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたホストオブジェクト。", - "xpack.observability.threshold.rule.infrastructureDropdownMenu": "インフラストラクチャー", - "xpack.observability.threshold.rule.infrastructureDropdownTitle": "インフラストラクチャールール", - "xpack.observability.threshold.rule.labelsActionVariableDescription": "このアラートがトリガーされたエンティティに関連付けられたラベルのリスト。", - "xpack.observability.threshold.rule.manageRules": "ルールの管理", - "xpack.observability.threshold.rule.metricsDropdownMenu": "メトリック", - "xpack.observability.threshold.rule.metricsDropdownTitle": "メトリックルール", - "xpack.observability.threshold.rule.orchestratorActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたオーケストレーターオブジェクト。", - "xpack.observability.threshold.rule.reasonActionVariableDescription": "アラートの理由の簡潔な説明", - "xpack.observability.threshold.rule.sourceConfiguration.missingHttp": "ソースの読み込みに失敗しました:HTTPクライアントがありません。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody": "変更をメトリック構成に適用できませんでした。しばらくたってから再試行してください。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle": "構成の更新が失敗しました", - "xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle": "メトリック設定は正常に更新されました", - "xpack.observability.threshold.rule.tagsActionVariableDescription": "このアラートがトリガーされたエンティティに関連付けられたタグのリスト。", - "xpack.observability.threshold.rule.threshold.aboveRecovery": "より大", - "xpack.observability.threshold.rule.threshold.belowRecovery": "より小", - "xpack.observability.threshold.rule.threshold.betweenRecovery": "の間", - "xpack.observability.threshold.rule.threshold.customEquation": "カスタム等式", - "xpack.observability.threshold.rule.threshold.documentCount": "ドキュメントカウント", - "xpack.observability.threshold.rule.timestampDescription": "アラートが検出された時点のタイムスタンプ。", - "xpack.observability.threshold.rule.valueActionVariableDescription": "指定された条件のメトリックの値。使用方法:(ctx.value.condition0、ctx.value.condition1など)。", - "xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription": "アラートソースにリンク", "xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "graph/", "xpack.observability.threshold.ruleExplorer.groupByLabel": "すべて", "xpack.observability.threshold.ruleName": "しきい値(テクニカルプレビュー)", @@ -30012,8 +29767,6 @@ "xpack.securitySolution.exceptions.viewer.lastUpdated": "{updated}を更新しました", "xpack.securitySolution.exceptions.viewer.paginationDetails": "{partOne}/{partTwo}ページを表示中", "xpack.securitySolution.fieldBrowser.descriptionForScreenReaderOnly": "フィールド{field}の説明:", - "xpack.securitySolution.flyout.errorMessage": "{message}の表示中にエラーが発生しました", - "xpack.securitySolution.flyout.errorTitle": "{title}を表示できません", "xpack.securitySolution.footer.autoRefreshActiveTooltip": "自動更新が有効な間、タイムラインはクエリに一致する直近{numberOfItems}件のイベントを表示します。", "xpack.securitySolution.formattedNumber.countsLabel": "{mantissa}{scale}{hasRemainder}", "xpack.securitySolution.header.editableTitle.editButtonAria": "クリックすると{title}を編集できます", @@ -31628,7 +31381,6 @@ "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootkitDescription": "Bootkit (T1067)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonAutostartExecutionDescription": "ブートまたはログオン自動起動実行(T1547)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonInitializationScriptsDescription": "ブートまたはログオン初期化スクリプト(T1037)", - "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription": "ブラウザーブックマーク検出(T1217)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserExtensionsDescription": "ブラウザー拡張(T1176)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserSessionHijackingDescription": "ブラウザーセッションハイジャック(T1185)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bruteForceDescription": "Brute Force(T1110)", @@ -33230,71 +32982,9 @@ "xpack.securitySolution.fleetIntegration.assets.name": "ホスト", "xpack.securitySolution.fleetIntegration.elasticDefend.eventFilter.nonInteractiveSessions.description": "クラウドセキュリティのイベントフィルター。Elastic Defend統合によって作成。", "xpack.securitySolution.fleetIntegration.elasticDefend.eventFilter.nonInteractiveSessions.name": "非インタラクティブセッション", - "xpack.securitySolution.flyout.analyzerErrorMessage": "アナライザー", "xpack.securitySolution.flyout.button.timeline": "タイムライン", - "xpack.securitySolution.flyout.correlations.caseNameColumnTitle": "名前", - "xpack.securitySolution.flyout.correlations.reasonColumnTitle": "理由", - "xpack.securitySolution.flyout.correlations.ruleColumnTitle": "ルール", - "xpack.securitySolution.flyout.correlations.severityColumnTitle": "深刻度", - "xpack.securitySolution.flyout.correlations.statusColumnTitle": "ステータス", - "xpack.securitySolution.flyout.correlations.timestampColumnTitle": "タイムスタンプ", - "xpack.securitySolution.flyout.documentDetails.alertReasonTitle": "アラートの理由", - "xpack.securitySolution.flyout.documentDetails.analyzerGraphButton": "アナライザーグラフ", - "xpack.securitySolution.flyout.documentDetails.analyzerPreviewTitle": "アナライザープレビュー", - "xpack.securitySolution.flyout.documentDetails.collapseDetailButton": "アラート詳細を折りたたむ", - "xpack.securitySolution.flyout.documentDetails.correlationsButton": "相関関係", - "xpack.securitySolution.flyout.documentDetails.correlationsTitle": "相関関係", - "xpack.securitySolution.flyout.documentDetails.documentDescriptionTitle": "ドキュメント説明", - "xpack.securitySolution.flyout.documentDetails.documentReasonTitle": "ドキュメント理由", - "xpack.securitySolution.flyout.documentDetails.entitiesButton": "エンティティ", - "xpack.securitySolution.flyout.documentDetails.entitiesTitle": "エンティティ", - "xpack.securitySolution.flyout.documentDetails.expandDetailButton": "アラート詳細を展開", - "xpack.securitySolution.flyout.documentDetails.highlightedFieldsTitle": "ハイライトされたフィールド", - "xpack.securitySolution.flyout.documentDetails.insightsOptions": "インサイトオプション", - "xpack.securitySolution.flyout.documentDetails.insightsTab": "インサイト", - "xpack.securitySolution.flyout.documentDetails.insightsTitle": "インサイト", - "xpack.securitySolution.flyout.documentDetails.investigationSectionTitle": "調査", - "xpack.securitySolution.flyout.documentDetails.investigationsTab": "調査", - "xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON", - "xpack.securitySolution.flyout.documentDetails.overviewTab": "概要", - "xpack.securitySolution.flyout.documentDetails.overviewTab.prevalenceRowText": "共通しない", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichment": "脅威インテリジェンスで拡張されたフィールド", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichments": "脅威インテリジェンスで拡張されたフィールド", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatch": "脅威一致が検出されました", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatches": "脅威一致が検出されました", - "xpack.securitySolution.flyout.documentDetails.prevalenceButton": "発生率", - "xpack.securitySolution.flyout.documentDetails.prevalenceTitle": "発生率", - "xpack.securitySolution.flyout.documentDetails.riskScoreTitle": "リスクスコア", - "xpack.securitySolution.flyout.documentDetails.ruleDescriptionTitle": "ルールの説明", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.commandText": "グループ基準", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.processText": "開始済み", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.ruleText": "ルールがある", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.timeText": "に", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.title": "セッションビューアープレビュー", - "xpack.securitySolution.flyout.documentDetails.sessionViewButton": "セッションビュー", - "xpack.securitySolution.flyout.documentDetails.severityTitle": "深刻度", - "xpack.securitySolution.flyout.documentDetails.share": "アラートを共有", - "xpack.securitySolution.flyout.documentDetails.tableTab": "表", - "xpack.securitySolution.flyout.documentDetails.threatIntelligenceButton": "脅威インテリジェンス", - "xpack.securitySolution.flyout.documentDetails.threatIntelligenceTitle": "脅威インテリジェンス", - "xpack.securitySolution.flyout.documentDetails.visualizationsTitle": "ビジュアライゼーション", - "xpack.securitySolution.flyout.documentDetails.visualizeOptions": "Visualizeオプション", - "xpack.securitySolution.flyout.documentDetails.visualizeTab": "可視化", - "xpack.securitySolution.flyout.documentErrorMessage": "ドキュメントフィールドおよび値", - "xpack.securitySolution.flyout.documentErrorTitle": "ドキュメント情報", "xpack.securitySolution.flyout.entities.failRelatedHostsDescription": "関連するホストで検索を実行できませんでした", "xpack.securitySolution.flyout.entities.failRelatedUsersDescription": "関連するユーザーで検索を実行できませんでした", - "xpack.securitySolution.flyout.entities.hostsInfoTitle": "ホスト情報", - "xpack.securitySolution.flyout.entities.relatedEntitiesIpColumn": "IPアドレス", - "xpack.securitySolution.flyout.entities.relatedEntitiesNameColumn": "名前", - "xpack.securitySolution.flyout.entities.relatedHostsTitle": "関連するホスト", - "xpack.securitySolution.flyout.entities.relatedUsersTitle": "関連するユーザー", - "xpack.securitySolution.flyout.entities.usersInfoTitle": "ユーザー情報", - "xpack.securitySolution.flyout.prevalenceErrorMessage": "発生率", - "xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitle": "アラート件数", - "xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitle": "ドキュメントカウント", - "xpack.securitySolution.flyout.response.title": "対応", - "xpack.securitySolution.flyout.sessionViewErrorMessage": "セッションビュー", "xpack.securitySolution.footer.autoRefreshActiveDescription": "自動更新アクション", "xpack.securitySolution.footer.cancel": "キャンセル", "xpack.securitySolution.footer.data": "データ", @@ -34619,10 +34309,6 @@ "xpack.serverlessSearch.apiKey.roleDescriptorsLinkLabel": "ロール記述子を構造化する方法をご覧ください", "xpack.serverlessSearch.apiKey.setup.description": "APIキーを作成するための基本構成詳細情報。", "xpack.serverlessSearch.apiKey.setup.title": "セットアップ", - "xpack.serverlessSearch.apiKey.stepOneDescription": "認証と認可のための一意の識別子。", - "xpack.serverlessSearch.apiKey.stepOneTitle": "APIキーを生成して保存", - "xpack.serverlessSearch.apiKey.stepTwoDescription": "特定のプロジェクトの一意の識別子。", - "xpack.serverlessSearch.apiKey.stepTwoTitle": "一意のCloud IDを保存", "xpack.serverlessSearch.apiKey.title": "APIキーとCloud IDを保存", "xpack.serverlessSearch.apiKey.userFieldHelpText": "APIキーを作成するユーザーのID。", "xpack.serverlessSearch.apiKey.userFieldLabel": "ユーザー", @@ -35747,7 +35433,6 @@ "xpack.stackAlerts.geoContainment.boundariesFetchError": "追跡包含境界を取得できません。エラー:{error}", "xpack.stackAlerts.geoContainment.entityContainmentFetchError": "エンティティコンテインメントを取得できません。エラー:{error}", "xpack.stackAlerts.geoContainment.noBoundariesError": "追跡包含境界が見つかりません。インデックス\"{index}\"にドキュメントがあることを確認します。", - "xpack.stackAlerts.geoContainment.noGeoFieldInIndexPattern.message": "データビューには許可された地理空間フィールドが含まれていません。{geoFields}型のいずれかが必要です。", "xpack.stackAlerts.indexThreshold.alertTypeContextSubjectTitle": "アラート{name}グループ{group}がしきい値を満たしました", "xpack.stackAlerts.indexThreshold.alertTypeRecoveryContextSubjectTitle": "アラート{name}グループ{group}が回復されました", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "無効なthresholdComparatorが指定されました:{comparator}", @@ -35838,12 +35523,8 @@ "xpack.stackAlerts.geoContainment.actionVariableContextFromEntityDocumentIdLabel": "含まれるエンティティドキュメントの ID", "xpack.stackAlerts.geoContainment.actionVariableContextFromEntityLocationLabel": "エンティティの場所", "xpack.stackAlerts.geoContainment.alertTypeTitle": "追跡包含", - "xpack.stackAlerts.geoContainment.boundaryNameSelect": "境界名を選択", "xpack.stackAlerts.geoContainment.boundaryNameSelectLabel": "人間が読み取れる境界名(任意)", "xpack.stackAlerts.geoContainment.descriptionText": "エンティティが地理的境界に含まれるときにアラートを発行します。", - "xpack.stackAlerts.geoContainment.entityByLabel": "グループ基準", - "xpack.stackAlerts.geoContainment.entityIndexLabel": "インデックス", - "xpack.stackAlerts.geoContainment.entityIndexSelect": "データビューと地理ポイントフィールドを選択", "xpack.stackAlerts.geoContainment.error.requiredBoundaryGeoFieldText": "境界地理フィールドは必須です。", "xpack.stackAlerts.geoContainment.error.requiredBoundaryIndexTitleText": "境界データビュータイトルが必要です。", "xpack.stackAlerts.geoContainment.error.requiredBoundaryTypeText": "境界タイプは必須です。", @@ -35851,25 +35532,12 @@ "xpack.stackAlerts.geoContainment.error.requiredEntityText": "エンティティは必須です。", "xpack.stackAlerts.geoContainment.error.requiredGeoFieldText": "地理フィールドは必須です。", "xpack.stackAlerts.geoContainment.error.requiredIndexTitleText": "データビューが必要です。", - "xpack.stackAlerts.geoContainment.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", "xpack.stackAlerts.geoContainment.geofieldLabel": "地理空間フィールド", - "xpack.stackAlerts.geoContainment.indexLabel": "インデックス", - "xpack.stackAlerts.geoContainment.indexPatternSelectLabel": "データビュー", - "xpack.stackAlerts.geoContainment.indexPatternSelectPlaceholder": "データビューを選択", - "xpack.stackAlerts.geoContainment.noIndexPattern.doThisLinkTextDescription": "データビューを作成します。", - "xpack.stackAlerts.geoContainment.noIndexPattern.doThisPrefixDescription": "次のことが必要です ", - "xpack.stackAlerts.geoContainment.noIndexPattern.getStartedLinkText": "サンプルデータセットで始めましょう。", - "xpack.stackAlerts.geoContainment.noIndexPattern.hintDescription": "データがない場合", - "xpack.stackAlerts.geoContainment.noIndexPattern.messageTitle": "データビューが見つかりませんでした", "xpack.stackAlerts.geoContainment.notGeoContained": "含まれていません", - "xpack.stackAlerts.geoContainment.selectBoundaryIndex": "境界を選択", - "xpack.stackAlerts.geoContainment.selectEntity": "エンティティを選択", "xpack.stackAlerts.geoContainment.selectGeoLabel": "ジオフィールドを選択", - "xpack.stackAlerts.geoContainment.selectLabel": "ジオフィールドを選択", "xpack.stackAlerts.geoContainment.selectTimeLabel": "時刻フィールドを選択", "xpack.stackAlerts.geoContainment.timeFieldLabel": "時間フィールド", "xpack.stackAlerts.geoContainment.topHitsSplitFieldSelectPlaceholder": "エンティティフィールドを選択", - "xpack.stackAlerts.geoContainment.ui.expressionPopover.closePopoverLabel": "閉じる", "xpack.stackAlerts.indexThreshold.actionGroupThresholdMetTitle": "しきい値一致", "xpack.stackAlerts.indexThreshold.actionVariableContextConditionsLabel": "しきい値比較基準としきい値を説明する文字列", "xpack.stackAlerts.indexThreshold.actionVariableContextDateLabel": "アラートがしきい値を超えた日付。", @@ -37709,26 +37377,19 @@ "xpack.transform.alertTypes.transformHealth.healthCheckRecoveryMessage": "{count, plural, other {トランスフォーム}} {transformsString} {count, plural, other {あります}}は正常です。", "xpack.transform.alertTypes.transformHealth.notStartedMessage": "{count, plural, other {トランスフォーム}}\"{transformsString}\"{count, plural, other {あります}}開始されていません。", "xpack.transform.alertTypes.transformHealth.notStartedRecoveryMessage": "{count, plural, other {トランスフォーム}}\"{transformsString}\"{count, plural, other {あります}}開始しました。", - "xpack.transform.app.deniedPrivilegeDescription": "変換のこのセクションを使用するには、{privilegesCount, plural, other {これらのクラスター権限}}が必要です:{missingPrivileges}", "xpack.transform.capability.pleaseContactAdministratorTooltip": "{message} 管理者にお問い合わせください。", "xpack.transform.clone.noDataViewErrorPromptText": "トランスフォーム{transformId}を複製できません。{dataViewTitle}のデータビューは存在しません。", "xpack.transform.danglingTasksError": "{count}個の{count, plural, other {変換}}に構成の詳細がありません:[{transformIds}]。{count, plural, other {それら}}回復できないため、削除してください。", "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage": "データビュー{destinationIndex}の削除中にエラーが発生しました", - "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage": "データビュー {destinationIndex} の削除リクエストが受け付けられました。", "xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "ディスティネーションインデックス{destinationIndex}の削除中にエラーが発生しました", - "xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage": "ディスティネーションインデックス{destinationIndex}を削除する要求が確認されました。", "xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage": "データビュー{dataView}が存在するかどうかを確認するときにエラーが発生しました:{error}", "xpack.transform.edit.noDataViewErrorPromptText": "変換{transformId}のデータビューを取得できません。{dataViewTitle}のデータビューは存在しません。", "xpack.transform.forceDeleteTransformMessage": "{count}{count, plural, other {変換}}の削除", "xpack.transform.managedTransformsWarningCallout": "{count, plural, other {これらの変換のうちの少なくとも1個の変換}}はElasticによってあらかじめ構成されています。{count, plural, other {それらを}}{action}すると、製品の他の部分に影響する可能性があります。", "xpack.transform.multiTransformActionsMenu.transformsCount": "{count}個の{count, plural, other {変換}}を選択済み", "xpack.transform.stepCreateForm.createDataViewErrorMessage": "Kibanaデータビュー{dataViewName}の作成中にエラーが発生しました:", - "xpack.transform.stepCreateForm.createDataViewSuccessMessage": "Kibanaデータビュー{dataViewName}が正常に作成されました。", "xpack.transform.stepCreateForm.createTransformErrorMessage": "変換 {transformId} の取得中にエラーが発生しました。", - "xpack.transform.stepCreateForm.createTransformSuccessMessage": "変換 {transformId} の作成リクエストが受け付けられました。", "xpack.transform.stepCreateForm.duplicateDataViewErrorMessage": "Kibanaデータビュー{dataViewName}の作成中にエラーが発生しました:データビューはすでに存在します。", - "xpack.transform.stepCreateForm.startTransformErrorMessage": "変換 {transformId} の開始中にエラーが発生しました。", - "xpack.transform.stepCreateForm.startTransformSuccessMessage": "変換 {transformId} の開始リクエストが受け付けられました。", "xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar": "無効なクエリ:{queryErrorMessage}", "xpack.transform.stepDefineForm.queryPlaceholderKql": "例: {example}.", "xpack.transform.stepDefineForm.queryPlaceholderLucene": "例: {example}.", @@ -37740,41 +37401,30 @@ "xpack.transform.stepDetailsForm.retentionPolicyMaxAgePlaceholderText": "max_age 例:{exampleValue}", "xpack.transform.transformForm.sizeNotationPlaceholder": "例:{example1}, {example2}, {example3}, {example4}", "xpack.transform.transformList.alertingRules.tooltipContent": "変換には{rulesCount}個の関連付けられたアラート{rulesCount, plural, other { ルール}}があります", - "xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage": "{count}個のディスティネーションデータ{count, plural, other {ビュー}}が正常に削除されました。", - "xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "{count}個のディスティネーション{count, plural, other {インデックス}}が正常に削除されました。", "xpack.transform.transformList.bulkDeleteModalTitle": "{count}{count, plural, other {変換}}を削除しますか?", - "xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "{count}個の{count, plural, other {トランスフォーム}}が正常に削除されました。", "xpack.transform.transformList.bulkReauthorizeModalTitle": "{count} {count, plural, other {トランスフォーム}}をもう一度認可しますか?", "xpack.transform.transformList.bulkResetModalTitle": "{count}個の{count, plural, other {変換}}をリセットしますか?", - "xpack.transform.transformList.bulkResetTransformSuccessMessage": "{count}個の{count, plural, other {変換}}が正常にリセットされました。", "xpack.transform.transformList.bulkStartModalTitle": "{count}個の{count, plural, other {変換}}を開始しますか?", "xpack.transform.transformList.bulkStopModalTitle": "{count}個のを{count, plural, other {変換}}停止しますか?", "xpack.transform.transformList.cannotRestartCompleteBatchTransformToolTip": "{transformId} は完了済みの一斉変換で、再度開始できません。", "xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformToolTip": "{transformId}は完了済みのバッチ変換であるため、データの即時処理のスケジュールを設定できません。", "xpack.transform.transformList.deleteModalTitle": "{transformId}を削除しますか?", "xpack.transform.transformList.deleteTransformErrorMessage": "変換 {transformId} の削除中にエラーが発生しました", - "xpack.transform.transformList.deleteTransformSuccessMessage": "変換 {transformId} の削除リクエストが受け付けられました。", "xpack.transform.transformList.editFlyoutFormPlaceholderText": "デフォルト:{defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "{transformId}の編集", - "xpack.transform.transformList.editTransformSuccessMessage": "変換{transformId}が更新されました。", "xpack.transform.transformList.reauthorizeModalTitle": "{transformId}をもう一度許可しますか?", "xpack.transform.transformList.reauthorizeTransformErrorMessage": "変換{transformId}の再認可中にエラーが発生しました", - "xpack.transform.transformList.reauthorizeTransformSuccessMessage": "変換{transformId}の再認可リクエストが受け付けられました。", "xpack.transform.transformList.resetModalTitle": "{transformId}をリセットしますか?", "xpack.transform.transformList.resetTransformErrorMessage": "変換{transformId}のリセット中にエラーが発生しました", - "xpack.transform.transformList.resetTransformSuccessMessage": "変換{transformId}のリセットリクエストが受け付けられました。", "xpack.transform.transformList.rowCollapse": "{transformId} の詳細を非表示", "xpack.transform.transformList.rowExpand": "{transformId} の詳細を表示", "xpack.transform.transformList.scheduleNowTransformErrorMessage": "変換{transformId}でデータの即時処理のスケジュールの設定中にエラーが発生しました。", - "xpack.transform.transformList.scheduleNowTransformSuccessMessage": "変換{transformId}でデータの即時処理のスケジュールを設定するリクエストが受け付けられました。", "xpack.transform.transformList.startedTransformToolTip": "{transformId} はすでに開始済みです。", "xpack.transform.transformList.startModalTitle": "{transformId}を開始しますか?", "xpack.transform.transformList.startTransformErrorMessage": "変換 {transformId} の開始中にエラーが発生しました", - "xpack.transform.transformList.startTransformSuccessMessage": "変換 {transformId} の開始リクエストが受け付けられました。", "xpack.transform.transformList.stopModalTitle": "{transformId}を終了しますか?", "xpack.transform.transformList.stoppedTransformToolTip": "{transformId} はすでに停止済みです。", "xpack.transform.transformList.stopTransformErrorMessage": "データフレーム変換 {transformId} の停止中にエラーが発生しました", - "xpack.transform.transformList.stopTransformSuccessMessage": "データフレーム変換 {transformId} の停止リクエストが受け付けられました。", "xpack.transform.transformList.unauthorizedTransformsCallout.insufficientPermissionsMsg": "{unauthorizedCnt, plural, other {十分な権限がない状態で#個の変換が作成されました。}}", "xpack.transform.transformList.unauthorizedTransformsCallout.reauthorizeMsg": "もう一度認可して、{unauthorizedCnt, plural, other {#個の変換}}を開始してください。", "xpack.transform.transformNodes.noTransformNodesCallOutBody": "変換の作成または実行はできません。{learnMoreLink}。", @@ -37820,9 +37470,6 @@ "xpack.transform.alertTypes.transformHealth.notStartedCheckName": "トランスフォームが開始していません", "xpack.transform.alertTypes.transformHealth.testsConfigTransforms.errorMessage": "1つ以上の正常性チェックを選択する必要があります", "xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel": "有効にする", - "xpack.transform.app.checkingPrivilegesDescription": "権限を確認中…", - "xpack.transform.app.checkingPrivilegesErrorMessage": "サーバーからユーザー特権を取得しているときにエラーが発生しました", - "xpack.transform.app.deniedPrivilegeTitle": "クラスター特権が足りません", "xpack.transform.appName": "トランスフォーム", "xpack.transform.appTitle": "トランスフォーム", "xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip": "トランスフォームアラートルールを作成するアクセス権がありません。", @@ -37868,7 +37515,6 @@ "xpack.transform.licenseCheckErrorMessage": "ライセンス確認失敗", "xpack.transform.list.emptyPromptButtonText": "初めてのトランスフォームを作成", "xpack.transform.list.emptyPromptTitle": "トランスフォームが見つかりません", - "xpack.transform.list.errorPromptTitle": "トランスフォームリストの取得中にエラーが発生しました", "xpack.transform.mode": "モード", "xpack.transform.modeFilter": "モード", "xpack.transform.models.transformService.allOtherRequestsCancelledDescription": "他のすべてのリクエストはキャンセルされました。", @@ -38100,7 +37746,6 @@ "xpack.transform.transformList.editFlyoutUpdateButtonText": "更新", "xpack.transform.transformList.editManagedTransformsDescription": "編集中", "xpack.transform.transformList.editTransformGenericErrorMessage": "トランスフォームを削除するためのAPIエンドポイントの呼び出し中にエラーが発生しました。", - "xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "ユーザーがディスティネーションインデックスを削除できるかどうかを確認するときにエラーが発生しました。", "xpack.transform.transformList.managedBadgeLabel": "管理中", "xpack.transform.transformList.managedBadgeTooltip": "このトランスフォームはElasticによってあらかじめ構成および管理されています。製品の他の部分はその動作に依存関係が存在している場合があります。", "xpack.transform.transformList.needsReauthorizationBadge.contactAdminTooltip": "必要な権限をリクエストするには、管理者に問い合わせてください。", @@ -39767,7 +39412,6 @@ "eventAnnotation.group.args.annotationConfigs.ignoreGlobalFilters.help": "注釈のグローバルフィルターを無視するスイッチ", "eventAnnotation.group.args.annotationGroups": "注釈グループ", "eventAnnotation.group.description": "イベント注釈グループ", - "eventAnnotation.listingViewTitle": "注釈グループ", "eventAnnotation.manualAnnotation.args.color": "行の色", "eventAnnotation.manualAnnotation.args.icon": "注釈行で使用される任意のアイコン", "eventAnnotation.manualAnnotation.args.id": "注釈のID", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e562115194f19..f297fa99a38e2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -665,7 +665,6 @@ "core.deprecations.elasticsearchUsername.manualSteps2": "将“elasticsearch.serviceAccountToken”设置添加到 kibana.yml。", "core.deprecations.elasticsearchUsername.manualSteps3": "从 kibana.yml 中移除“elasticsearch.username”和“elasticsearch.password”。", "core.deprecations.noCorrectiveAction": "无法自动解决此弃用。", - "core.euiAccordion.isLoading": "正在加载", "core.euiAutoRefresh.autoRefreshLabel": "自动刷新", "core.euiAutoRefresh.buttonLabelOff": "自动刷新已关闭", "core.euiBasicTable.noItemsMessage": "找不到项目", @@ -1331,7 +1330,6 @@ "data.search.aggs.rareTerms.aggTypesLabel": "{fieldName} 的稀有词", "data.search.es_search.queryTimeValue": "{queryTime}ms", "data.search.functions.geoBoundingBox.arguments.error": "必须至少提供一个以下参数组:{parameters}。", - "data.search.searchSource.fetch.shardsFailedNotificationMessage": "{shardsTotal} 个分片有 {shardsFailed} 个失败", "data.search.searchSource.indexPatternIdDescription": "{kibanaIndexPattern} 索引中的 ID。", "data.search.searchSource.queryTimeValue": "{queryTime}ms", "data.search.searchSource.requestTimeValue": "{requestTime}ms", @@ -2078,15 +2076,7 @@ "data.search.searchSource.dataViewDescription": "被查询的数据视图。", "data.search.searchSource.dataViewIdLabel": "数据视图 ID", "data.search.searchSource.dataViewLabel": "数据视图", - "data.search.searchSource.fetch.requestTimedOutNotificationMessage": "由于您的请求超时,数据可能不完整", - "data.search.searchSource.fetch.shardsFailedModal.close": "关闭", - "data.search.searchSource.fetch.shardsFailedModal.copyToClipboard": "将响应复制到剪贴板", - "data.search.searchSource.fetch.shardsFailedModal.showDetails": "显示详情", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest": "请求", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse": "响应", - "data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures": "分片错误", "data.search.searchSource.fetch.shardsFailedModal.tableColReason": "原因", - "data.search.searchSource.fetch.shardsFailedNotificationDescription": "数据可能不完整或有错误。", "data.search.searchSource.hitsDescription": "查询返回的文档数目。", "data.search.searchSource.hitsLabel": "命中数", "data.search.searchSource.hitsTotalDescription": "与查询匹配的文档数目。", @@ -2170,7 +2160,7 @@ "discover.advancedSettings.disableDocumentExplorerDescription": "要使用新的 {documentExplorerDocs},而非经典视图,请关闭此选项。Document Explorer 提供了更合理的数据排序、可调整大小的列和全屏视图。", "discover.advancedSettings.discover.showFieldStatisticsDescription": "启用 {fieldStatisticsDocs} 以显示详细信息,如数字字段的最小和最大值,或地理字段的地图。此功能为公测版,可能会进行更改。", "discover.advancedSettings.discover.showMultifieldsDescription": "控制 {multiFields} 是否显示在展开的文档视图中。多数情况下,多字段与原始字段相同。此选项仅在 `searchFieldsFromSource` 关闭时可用。", - "discover.advancedSettings.enableSQLDescription": "{technicalPreviewLabel} 此技术预览功能为高度实验性功能 -- 请勿在生产已保存搜索、可视化或仪表板中依赖此功能。此设置在 Discover 和 Lens 中将 SQL 用作基于文本的查询语言。如果具有与此体验有关的反馈,请通过 {link} 联系我们", + "discover.advancedSettings.enableESQLDescription": "{technicalPreviewLabel} 此技术预览功能为高度实验性功能 -- 请勿在生产已保存搜索、可视化或仪表板中依赖此功能。此设置在 Discover 和 Lens 中将 SQL 用作基于文本的查询语言。如果具有与此体验有关的反馈,请通过 {link} 联系我们", "discover.context.contextOfTitle": "#{anchorId} 周围的文档", "discover.context.newerDocumentsWarning": "仅可以找到 {docCount} 个比定位标记新的文档。", "discover.context.olderDocumentsWarning": "仅可以找到 {docCount} 个比定位标记旧的文档。", @@ -2193,11 +2183,6 @@ "discover.dscTour.stepAddFields.description": "单击 {plusIcon} 以添加您感兴趣的字段。", "discover.dscTour.stepExpand.description": "单击 {expandIcon} 以查看、比较和筛选文档。", "discover.errorCalloutFormattedTitle": "{title}: {errorMessage}", - "discover.grid.copyClipboardButtonTitle": "复制 {column} 的值", - "discover.grid.copyColumnValuesToClipboard.toastTitle": "“{column}”列的值已复制到剪贴板", - "discover.grid.filterForAria": "筛留此 {value}", - "discover.grid.filterOutAria": "筛除此 {value}", - "discover.gridSampleSize.limitDescription": "搜索结果被限定为 {sampleSize} 个文档。添加更多搜索词以缩小搜索范围。", "discover.howToSeeOtherMatchingDocumentsDescription": "以下是匹配您的搜索的前 {sampleSize} 个文档,请优化您的搜索以查看其他文档。", "discover.noMatchRoute.bannerText": "Discover 应用程序无法识别此路由:{route}", "discover.noResults.kqlExamples.kqlDescription": "详细了解 {kqlLink}", @@ -2207,9 +2192,6 @@ "discover.pageTitleWithSavedSearch": "Discover - {savedSearchTitle}", "discover.savedSearchAliasMatchRedirect.objectNoun": "{savedSearch} 搜索", "discover.savedSearchURLConflictCallout.objectNoun": "{savedSearch} 搜索", - "discover.searchGenerationWithDescription": "搜索 {searchTitle} 生成的表", - "discover.searchGenerationWithDescriptionGrid": "搜索 {searchTitle} 生成的表({searchDescription})", - "discover.selectedDocumentsNumber": "{nr} 个文档已选择", "discover.showingDefaultDataViewWarningDescription": "正在显示默认数据视图:“{loadedDataViewTitle}”({loadedDataViewId})", "discover.showingSavedDataViewWarningDescription": "正在显示已保存数据视图:“{ownDataViewTitle}”({ownDataViewId})", "discover.singleDocRoute.errorMessage": "没有与 ID {dataViewId} 相匹配的数据视图", @@ -2235,8 +2217,8 @@ "discover.advancedSettings.docTableHideTimeColumnText": "在 Discover 中和仪表板上的所有已保存搜索中隐藏“时间”列。", "discover.advancedSettings.docTableHideTimeColumnTitle": "隐藏“时间”列", "discover.advancedSettings.documentExplorerLinkText": "Document Explorer", - "discover.advancedSettings.enableSQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", - "discover.advancedSettings.enableSQLTitle": "启用 SQL", + "discover.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", + "discover.advancedSettings.enableESQLTitle": "启用 SQL", "discover.advancedSettings.fieldsPopularLimitText": "要显示的排名前 N 最常见字段", "discover.advancedSettings.fieldsPopularLimitTitle": "常见字段限制", "discover.advancedSettings.maxDocFieldsDisplayedText": "在文档摘要中渲染的最大字段数目", @@ -2262,7 +2244,6 @@ "discover.backToTopLinkText": "返回顶部。", "discover.badge.readOnly.text": "只读", "discover.badge.readOnly.tooltip": "无法保存搜索", - "discover.clearSelection": "清除所选内容", "discover.confirmDataViewSave.cancel": "取消", "discover.confirmDataViewSave.message": "您选择的操作需要已保存的数据视图。", "discover.confirmDataViewSave.saveAndContinue": "保存并继续", @@ -2283,8 +2264,6 @@ "discover.context.unableToLoadAnchorDocumentDescription": "无法加载该定位点文档", "discover.context.unableToLoadDocumentDescription": "无法加载文档", "discover.contextViewRoute.errorTitle": "发生错误", - "discover.controlColumnHeader": "控制列", - "discover.copyToClipboardJSON": "将文档复制到剪贴板 (JSON)", "discover.discoverBreadcrumbTitle": "Discover", "discover.discoverDefaultSearchSessionName": "发现", "discover.discoverDescription": "通过查询和筛选原始文档来以交互方式浏览您的数据。", @@ -2325,36 +2304,7 @@ "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "查看周围文档", "discover.documentsAriaLabel": "文档", "discover.documentsErrorTitle": "搜索错误", - "unifiedDocViewer.docView.table.actions.label": "操作", - "unifiedDocViewer.docView.table.actions.open": "打开操作", - "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "此字段中的一个或多个值过长,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "此字段包含一个或多个格式错误的值,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "此字段中的一个或多个值被 Elasticsearch 忽略,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "此字段中的值过长,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "此字段中的值格式错误,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "此字段中的值被 Elasticsearch 忽略,无法搜索或筛选。", - "unifiedDocViewer.docView.table.searchPlaceHolder": "搜索字段名称", - "unifiedDocViewer.docViews.json.jsonTitle": "JSON", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "字段是否存在筛选", - "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "筛留值", - "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "筛留值", - "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "筛除值", - "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "筛除值", - "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "包含被忽略的值", - "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "被忽略的值", - "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "固定字段", - "unifiedDocViewer.docViews.table.pinFieldLabel": "固定字段", "discover.docViews.table.scoreSortWarningTooltip": "要检索 _score 的值,必须按其筛选。", - "unifiedDocViewer.docViews.table.tableTitle": "表", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "在表中切换列", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "在表中切换列", - "unifiedDocViewer.fieldChooser.discoverField.name": "切换字段详细信息", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "无法筛选元数据字段是否存在", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "无法筛选脚本字段是否存在", - "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "无法搜索未编入索引的字段或被忽略的值", - "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "取消固定字段", - "unifiedDocViewer.docViews.table.unpinFieldLabel": "取消固定字段", "discover.dropZoneTableLabel": "放置区域以将字段作为列添加到表中", "discover.dscTour.stepAddFields.imageAltText": "在可用字段列表中,单击加号图标将字段切换为文档表。", "discover.dscTour.stepAddFields.title": "将字段添加到表中", @@ -2375,46 +2325,24 @@ "discover.embeddable.search.displayName": "搜索", "discover.errorCalloutShowErrorMessage": "显示详情", "discover.fieldChooser.availableFieldsTooltip": "适用于在表中显示的字段。", - "unifiedDocViewer.fieldChooser.discoverField.actions": "操作", "discover.fieldChooser.discoverField.addFieldTooltip": "将字段添加为列", - "unifiedDocViewer.fieldChooser.discoverField.multiField": "多字段", - "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "多字段的每个字段可以有多个值", - "unifiedDocViewer.fieldChooser.discoverField.name": "字段", "discover.fieldChooser.discoverField.removeFieldTooltip": "从表中移除字段", - "unifiedDocViewer.fieldChooser.discoverField.value": "值", "discover.goToDiscoverButtonText": "前往 Discover", - "discover.grid.closePopover": "关闭弹出框", - "discover.grid.copyCellValueButton": "复制值", - "discover.grid.copyColumnNameToClipboard.toastTitle": "已复制到剪贴板", - "discover.grid.copyColumnNameToClipBoardButton": "复制名称", - "discover.grid.copyColumnValuesToClipBoardButton": "复制列", - "discover.grid.copyEscapedValueWithFormulasToClipboardWarningText": "值可能包含已转义的公式。", - "discover.grid.copyFailedErrorText": "无法在此浏览器中复制到剪贴板", - "discover.grid.copyValueToClipboard.toastTitle": "已复制到剪贴板", - "discover.grid.documentHeader": "文档", - "discover.grid.editFieldButton": "编辑数据视图字段", - "discover.grid.filterFor": "筛留", - "discover.grid.filterOut": "筛除", "discover.grid.flyout.documentNavigation": "文档导航", "discover.grid.flyout.toastColumnAdded": "已添加列“{columnName}”", "discover.grid.flyout.toastColumnRemoved": "已移除列“{columnName}”", - "discover.grid.selectDoc": "选择文档“{rowNumber}”", "discover.grid.tableRow.detailHeading": "已展开文档", "discover.grid.tableRow.textBasedDetailHeading": "已展开行", "discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "单个文档", "discover.grid.tableRow.viewSurroundingDocumentsHover": "检查在此文档之前和之后出现的文档。在周围文档视图中,仅已固定筛选仍处于活动状态。", "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周围文档", "discover.grid.tableRow.viewText": "视图:", - "discover.grid.viewDoc": "切换具有详情的对话框", "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "文档", "discover.inspectorRequestDescriptionDocument": "此请求将查询 Elasticsearch 以获取文档。", "discover.invalidFiltersWarnToast.description": "某些应用的筛选中的数据视图 ID 引用与当前数据视图不同。", "discover.invalidFiltersWarnToast.title": "不同的索引引用", - "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", - "unifiedDocViewer.json.copyToClipboardLabel": "复制到剪贴板", "discover.loadingDocuments": "正在加载文档", - "unifiedDocViewer.loadingJSON": "正在加载 JSON", "discover.loadingResults": "正在加载结果", "discover.localMenu.alertsDescription": "告警", "discover.localMenu.fallbackReportTitle": "未命名 Discover 搜索", @@ -2458,29 +2386,20 @@ "discover.noResults.suggestion.syntaxPopoverExampleHeader": "示例", "discover.noResults.suggestion.tryText": "这里是要尝试的一些内容:", "discover.noResults.suggestion.viewAllMatchesButtonText": "查看所有匹配项", - "discover.noResultsFound": "找不到结果", "discover.notifications.invalidTimeRangeText": "提供的时间范围无效。(自:“{from}”,至:“{to}”)", "discover.notifications.invalidTimeRangeTitle": "时间范围无效", "discover.notifications.notSavedSearchTitle": "搜索“{savedSearchTitle}”未保存。", "discover.notifications.savedSearchTitle": "搜索“{savedSearchTitle}”已保存", "discover.pageTitleWithoutSavedSearch": "Discover - 尚未保存搜索", "discover.reloadSavedSearchButton": "重置搜索", - "discover.removeColumnLabel": "移除列", "discover.rootBreadcrumb": "Discover", "discover.sampleData.viewLinkLabel": "Discover", "discover.savedSearch.savedObjectName": "已保存搜索", "discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "在 Discover 中打开", "discover.searchingTitle": "正在搜索", - "discover.selectColumnHeader": "选择列", "discover.serverLocatorExtension.titleFromLocatorUnknown": "未知搜索", - "discover.showAllDocuments": "显示所有文档", - "discover.showSelectedDocumentsOnly": "仅显示选定的文档", "discover.singleDocRoute.errorTitle": "发生错误", "discover.skipToBottomButtonLabel": "转到表尾", - "unifiedDocViewer.sourceViewer.errorMessage": "当前无法获取数据。请刷新选项卡以重试。", - "unifiedDocViewer.sourceViewer.errorMessageTitle": "发生错误", - "unifiedDocViewer.sourceViewer.refresh": "刷新", - "discover.toggleSidebarAriaLabel": "切换侧边栏", "discover.topNav.openSearchPanel.manageSearchesButtonLabel": "管理搜索", "discover.topNav.openSearchPanel.noSearchesFoundDescription": "未找到匹配的搜索。", "discover.topNav.openSearchPanel.openSearchTitle": "打开搜索", @@ -2496,6 +2415,73 @@ "discover.viewAlert.searchSourceErrorTitle": "提取搜索源时出错", "discover.viewModes.document.label": "文档", "discover.viewModes.fieldStatistics.label": "字段统计信息", + "discover.fieldNameIcons.binaryAriaLabel": "二进制", + "discover.fieldNameIcons.booleanAriaLabel": "布尔型", + "discover.fieldNameIcons.conflictFieldAriaLabel": "冲突", + "discover.fieldNameIcons.counterFieldAriaLabel": "计数器指标", + "discover.fieldNameIcons.dateFieldAriaLabel": "日期", + "discover.fieldNameIcons.dateRangeFieldAriaLabel": "日期范围", + "discover.fieldNameIcons.denseVectorFieldAriaLabel": "密集向量", + "discover.fieldNameIcons.flattenedFieldAriaLabel": "扁平", + "discover.fieldNameIcons.gaugeFieldAriaLabel": "仪表盘指标", + "discover.fieldNameIcons.geoPointFieldAriaLabel": "地理点", + "discover.fieldNameIcons.geoShapeFieldAriaLabel": "几何形状", + "discover.fieldNameIcons.histogramFieldAriaLabel": "直方图", + "discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP 地址", + "discover.fieldNameIcons.ipRangeFieldAriaLabel": "IP 范围", + "discover.fieldNameIcons.keywordFieldAriaLabel": "关键字", + "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3", + "discover.fieldNameIcons.nestedFieldAriaLabel": "嵌套", + "discover.fieldNameIcons.numberFieldAriaLabel": "数字", + "discover.fieldNameIcons.pointFieldAriaLabel": "点", + "discover.fieldNameIcons.rankFeatureFieldAriaLabel": "排名特征", + "discover.fieldNameIcons.rankFeaturesFieldAriaLabel": "排名特征", + "discover.fieldNameIcons.recordAriaLabel": "记录", + "discover.fieldNameIcons.shapeFieldAriaLabel": "形状", + "discover.fieldNameIcons.sourceFieldAriaLabel": "源字段", + "discover.fieldNameIcons.stringFieldAriaLabel": "字符串", + "discover.fieldNameIcons.textFieldAriaLabel": "文本", + "discover.fieldNameIcons.unknownFieldAriaLabel": "未知字段", + "discover.fieldNameIcons.versionFieldAriaLabel": "版本", + "unifiedDocViewer.docView.table.actions.label": "操作", + "unifiedDocViewer.docView.table.actions.open": "打开操作", + "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "此字段中的一个或多个值过长,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "此字段包含一个或多个格式错误的值,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "此字段中的一个或多个值被 Elasticsearch 忽略,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "此字段中的值过长,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "此字段中的值格式错误,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "此字段中的值被 Elasticsearch 忽略,无法搜索或筛选。", + "unifiedDocViewer.docView.table.searchPlaceHolder": "搜索字段名称", + "unifiedDocViewer.docViews.json.jsonTitle": "JSON", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "字段是否存在筛选", + "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "筛留值", + "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "筛留值", + "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "筛除值", + "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "筛除值", + "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "包含被忽略的值", + "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "被忽略的值", + "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "固定字段", + "unifiedDocViewer.docViews.table.pinFieldLabel": "固定字段", + "unifiedDocViewer.docViews.table.tableTitle": "表", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "在表中切换列", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "在表中切换列", + "unifiedDocViewer.fieldChooser.discoverField.name": "字段", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "无法筛选元数据字段是否存在", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "无法筛选脚本字段是否存在", + "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "无法搜索未编入索引的字段或被忽略的值", + "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "取消固定字段", + "unifiedDocViewer.docViews.table.unpinFieldLabel": "取消固定字段", + "unifiedDocViewer.fieldChooser.discoverField.actions": "操作", + "unifiedDocViewer.fieldChooser.discoverField.multiField": "多字段", + "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "多字段的每个字段可以有多个值", + "unifiedDocViewer.fieldChooser.discoverField.value": "值", + "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", + "unifiedDocViewer.json.copyToClipboardLabel": "复制到剪贴板", + "unifiedDocViewer.loadingJSON": "正在加载 JSON", + "unifiedDocViewer.sourceViewer.errorMessage": "当前无法获取数据。请刷新选项卡以重试。", + "unifiedDocViewer.sourceViewer.errorMessageTitle": "发生错误", + "unifiedDocViewer.sourceViewer.refresh": "刷新", "domDragDrop.announce.cancelled": "移动已取消。{label} 已返回至其初始位置", "domDragDrop.announce.cancelledItem": "移动已取消。{label} 返回至 {groupLabel} 组中的位置 {position}", "domDragDrop.announce.dropped.combineCompatible": "已将组 {groupLabel} 中的 {label} 组合到图层 {dropLayerNumber} 的组 {dropGroupLabel} 中的位置 {dropPosition} 上的 {dropLabel}", @@ -3240,7 +3226,7 @@ "guidedOnboardingPackage.gettingStarted.cards.progressLabel": "已完成 {numberCompleteSteps} 个(共 {numberSteps} 个)步骤", "guidedOnboardingPackage.gettingStarted.cards.siemSecurity.title": "通过 SIEM{lineBreak}在我的数据中检测威胁", "guidedOnboardingPackage.gettingStarted.cards.completeLabel": "指南完成", - "guidedOnboardingPackage.gettingStarted.cards.esreSearch.title": "构建语义搜索体验", + "guidedOnboardingPackage.gettingStarted.cards.aiSearch.title": "构建语义搜索体验", "guidedOnboardingPackage.gettingStarted.cards.hostsObservability.title": "监测我的主机指标", "guidedOnboardingPackage.gettingStarted.cards.kubernetesObservability.title": "监测 Kubernetes 集群", "guidedOnboardingPackage.gettingStarted.cards.logsObservability.title": "收集并分析我的日志", @@ -4168,7 +4154,7 @@ "indexPatternEditor.rollupDataView.warning.textParagraphOne": "Kibana 基于汇总/打包为数据视图提供公测版支持。将这些视图用于已保存搜索、可视化以及仪表板可能会遇到问题。某些高级功能,如 Timelion 和 Machine Learning,不支持这些模式。", "indexPatternEditor.rollupDataView.warning.textParagraphTwo": "可以根据一个汇总/打包索引和零个或更多常规索引匹配汇总/打包数据视图。汇总/打包数据视图的指标、字段、时间间隔和聚合有限。汇总/打包索引仅限于具有一个作业配置或多个作业配置兼容的索引。", "indexPatternEditor.rollupIndexPattern.warning.title": "公测版功能", - "indexPatternEditor.saved": "已保存“{indexPatternName}”", + "indexPatternEditor.saved": "已保存", "indexPatternEditor.status.noSystemIndicesLabel": "没有数据流、索引或索引别名匹配您的索引模式。", "indexPatternEditor.status.noSystemIndicesWithPromptLabel": "没有数据流、索引或索引别名匹配您的索引模式。", "indexPatternEditor.status.notMatchLabel.notMatchNoIndicesDetail": "输入的索引模式不匹配任何数据流、索引或索引别名。", @@ -5712,34 +5698,6 @@ "unifiedFieldList.fieldNameDescription.textField": "全文本,如电子邮件正文或产品描述。", "unifiedFieldList.fieldNameDescription.unknownField": "未知字段", "unifiedFieldList.fieldNameDescription.versionField": "软件版本。支持“语义版本控制”优先规则。", - "discover.fieldNameIcons.binaryAriaLabel": "二进制", - "discover.fieldNameIcons.booleanAriaLabel": "布尔型", - "discover.fieldNameIcons.conflictFieldAriaLabel": "冲突", - "discover.fieldNameIcons.counterFieldAriaLabel": "计数器指标", - "discover.fieldNameIcons.dateFieldAriaLabel": "日期", - "discover.fieldNameIcons.dateRangeFieldAriaLabel": "日期范围", - "discover.fieldNameIcons.denseVectorFieldAriaLabel": "密集向量", - "discover.fieldNameIcons.flattenedFieldAriaLabel": "扁平", - "discover.fieldNameIcons.gaugeFieldAriaLabel": "仪表盘指标", - "discover.fieldNameIcons.geoPointFieldAriaLabel": "地理点", - "discover.fieldNameIcons.geoShapeFieldAriaLabel": "几何形状", - "discover.fieldNameIcons.histogramFieldAriaLabel": "直方图", - "discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP 地址", - "discover.fieldNameIcons.ipRangeFieldAriaLabel": "IP 范围", - "discover.fieldNameIcons.keywordFieldAriaLabel": "关键字", - "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3", - "discover.fieldNameIcons.nestedFieldAriaLabel": "嵌套", - "discover.fieldNameIcons.numberFieldAriaLabel": "数字", - "discover.fieldNameIcons.pointFieldAriaLabel": "点", - "discover.fieldNameIcons.rankFeatureFieldAriaLabel": "排名特征", - "discover.fieldNameIcons.rankFeaturesFieldAriaLabel": "排名特征", - "discover.fieldNameIcons.recordAriaLabel": "记录", - "discover.fieldNameIcons.shapeFieldAriaLabel": "形状", - "discover.fieldNameIcons.sourceFieldAriaLabel": "源字段", - "discover.fieldNameIcons.stringFieldAriaLabel": "字符串", - "discover.fieldNameIcons.textFieldAriaLabel": "文本", - "discover.fieldNameIcons.unknownFieldAriaLabel": "未知字段", - "discover.fieldNameIcons.versionFieldAriaLabel": "版本", "unifiedFieldList.fieldNameSearch.filterByNameLabel": "搜索字段名称", "unifiedFieldList.fieldPopover.addExistsFilterLabel": "筛留存在的字段", "unifiedFieldList.fieldPopover.deleteFieldLabel": "删除数据视图字段", @@ -5940,9 +5898,6 @@ "unifiedSearch.query.queryBar.indexPattern.findFilterSet": "查找查询", "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "管理此数据视图", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "临时", - "unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "切换数据视图会移除当前的 SQL 查询。保存此搜索以确保不会丢失工作。", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesLabel": "基于文本的查询语言", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "切换数据视图会移除当前的 SQL 查询。保存此搜索以确保不会丢失工作。", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "切换而不保存", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "不再显示此警告", "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "保存并切换", @@ -7089,7 +7044,6 @@ "visualizationUiComponents.dimensionButtonIcon.colorIndicatorLabel": "此维度的颜色:{hex}", "visualizationUiComponents.queryInput.queryPlaceholderKql": "{example}", "visualizationUiComponents.queryInput.queryPlaceholderLucene": "{example}", - "visualizationUiComponents.colorPicker.seriesColor.auto": "自动", "visualizationUiComponents.colorPicker.seriesColor.label": "系列颜色", "visualizationUiComponents.colorPicker.tooltip.auto": "Lens 自动为您选取颜色,除非您指定定制颜色。", "visualizationUiComponents.colorPicker.tooltip.custom": "清除定制颜色以返回到“自动”模式。", @@ -7632,7 +7586,7 @@ "xpack.apm.tutorial.windowsServerInstructions.textPre": "1.从[下载页面]({downloadPageLink}) 下载 APM Server Windows zip 文件。\n2.将 zip 文件的内容解压缩到 {zipFileExtractFolder}。\n3.将 {apmServerDirectory} 目录重命名为 `APM-Server`。\n4.以管理员身份打开 PowerShell 提示符(右键单击 PowerShell 图标,然后选择**以管理员身份运行**)。如果运行的是 Windows XP,则可能需要下载并安装 PowerShell。\n5.从 PowerShell 提示符处,运行以下命令以将 APM Server 安装为 Windows 服务:", "xpack.apm.unifiedSearchBar.placeholder": "搜索{event, select, transaction {事务} metric {指标} error {错误} other {事务、错误和指标}}(例如 {queryExample})", "xpack.apm.waterfall.errorCount": "{errorCount, plural, other {查看 # 个相关错误}}", - "xpack.apm.waterfall.exceedsMax": "此跟踪中的项目数为 {traceItemCount},这高于 {maxTraceItems} 的当前限值。请增加该限值以查看完整追溯信息", + "xpack.apm.waterfall.exceedsMax": "此跟踪中的项目数为 {traceDocsTotal},这高于 {maxTraceItems} 的当前限值。请增加该限值以查看完整追溯信息", "xpack.apm.waterfall.spanLinks.badge": "{total} {total, plural, other {跨度链接}}", "xpack.apm.waterfall.spanLinks.tooltip.linkedChildren": "{linkedChildren} 传入", "xpack.apm.waterfall.spanLinks.tooltip.linkedParents": "{linkedParents} 传出", @@ -8330,7 +8284,6 @@ "xpack.apm.mobile.location.metrics.http.requests.title": "最常用于", "xpack.apm.mobile.location.metrics.launches": "大多数启动", "xpack.apm.mobile.location.metrics.sessions": "大多数会话", - "xpack.apm.mobile.metrics.crash.rate": "崩溃速率(每分钟崩溃数)", "xpack.apm.mobile.metrics.http.requests": "HTTP 请求", "xpack.apm.mobile.metrics.load.time": "最慢应用加载时间", "xpack.apm.mobile.metrics.sessions": "会话", @@ -11360,11 +11313,8 @@ "xpack.csp.awsIntegration.sharedCredentialsDescription": "如果对不同工具或应用程序使用不同的 AWS 凭据,可以使用配置文件在同一配置文件中定义多个访问密钥。", "xpack.csp.awsIntegration.temporaryKeysDescription": "您可以在 AWS 中配置在指定持续时间内有效的临时安全凭据。它们包括访问密钥 ID、机密访问密钥和安全令牌(通常使用 GetSessionToken 查找)。", "xpack.csp.awsIntegration.temporaryKeysLabel": "临时密钥", - "xpack.csp.benchmarks.benchmarkEmptyState.integrationsNotFoundTitle": "找不到基准集成", - "xpack.csp.benchmarks.benchmarkEmptyState.integrationsNotFoundWithFiltersTitle": "使用上述筛选,我们无法找到任何基准集成。", "xpack.csp.benchmarks.benchmarkSearchField.searchPlaceholder": "按集成名称搜索", "xpack.csp.benchmarks.benchmarksPageHeader.addIntegrationButtonLabel": "添加集成", - "xpack.csp.benchmarks.benchmarksPageHeader.benchmarkIntegrationsTitle": "基准集成", "xpack.csp.benchmarks.benchmarksTable.agentPolicyColumnTitle": "代理策略", "xpack.csp.benchmarks.benchmarksTable.createdAtColumnTitle": "创建时间", "xpack.csp.benchmarks.benchmarksTable.createdByColumnTitle": "创建者", @@ -11467,13 +11417,11 @@ "xpack.csp.findings.findingsByResourceTable.postureScoreColumnLabel": "态势分数", "xpack.csp.findings.findingsErrorToast.searchFailedTitle": "搜索失败", "xpack.csp.findings.findingsFlyout.jsonTabTitle": "JSON", - "xpack.csp.findings.findingsFlyout.overviewTab.actualTitle": "实际", "xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle": "CIS 部分", "xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle": "默认值", "xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle": "详情", "xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle": "评估时间", "xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle": "证据", - "xpack.csp.findings.findingsFlyout.overviewTab.expectedTitle": "预期", "xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle": "框架源", "xpack.csp.findings.findingsFlyout.overviewTab.impactTitle": "影响", "xpack.csp.findings.findingsFlyout.overviewTab.indexTitle": "索引", @@ -11578,7 +11526,6 @@ "xpack.csp.rules.manageIntegrationButtonLabel": "管理集成", "xpack.csp.rules.ruleFlyout.overviewTabLabel": "概览", "xpack.csp.rules.ruleFlyout.remediationTabLabel": "补救", - "xpack.csp.rules.rulesPageHeader.benchmarkIntegrationsButtonLabel": "基准集成", "xpack.csp.rules.rulesPageSharedValues.benchmarkTitle": "基准", "xpack.csp.rules.rulesPageSharedValues.deploymentTypeTitle": "部署类型", "xpack.csp.rules.rulesPageSharedValues.integrationTitle": "集成", @@ -12181,19 +12128,19 @@ "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "您的云部署是否正在运行 Enterprise Search 节点?{deploymentSettingsLink}", "xpack.enterpriseSearch.errorConnectingState.description1": "由于以下错误,我们无法与主机 URL {enterpriseSearchUrl} 的 Enterprise Search 建立连接:", "xpack.enterpriseSearch.errorConnectingState.description2": "确保在 {configFile} 中已正确配置主机 URL。", - "xpack.enterpriseSearch.esre.elser.description": "轻松部署 {elser},以便只需单击几下即可实现即时文本语义搜索功能。此模型将使用“text_expansion”字段扩充您的文档和查询文本,提供开箱可用的无缝搜索功能。", - "xpack.enterpriseSearch.esre.elserPanel.step2.description": "创建索引后,请选中该索引,然后单击 {pipelinesName} 选项卡。", - "xpack.enterpriseSearch.esre.esreDocsSection.description": "要详细了解如何开始使用 ESRE 并使用具体的示例来测试这些工具,请访问 {esreDocumentation}。", - "xpack.enterpriseSearch.esre.esreDocsSection.faq.description": "通过这些{frequentlyAskedQuestions}了解 ESRE 是(不是)什么。", - "xpack.enterpriseSearch.esre.esreDocsSection.help.description": "需要帮助?请访问{discussForum}!", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.description": "这些是复杂的主题,我们已汇编了一些{learningTopics}以帮助您开始使用。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.description": "使用{behavioralAnalytics}仪表板和工具以可视化用户行为并评估您所做更改的影响。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.description": "访问{behavioralAnalytics}并创建您的首个集合", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.description": "使用情绪分析、汇总或已命名实体识别等自然语言处理 (NLP) 工具来增强搜索结果的相关性。NLP 使用几个可加载的 {supportedMlModels},以通过附加字段智能分析并扩充文档。", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.description": "导航到您索引的 {pipelinesName} 选项卡,以创建使用已部署模型的推理管道。", - "xpack.enterpriseSearch.esre.rrfRankingPanel.description": "使用 {rrf} 将来自多个结果集的排名与不同相关性指标组合起来,而无需进行微调。", - "xpack.enterpriseSearch.esre.vectorSearchPanel.description": "通过添加来自 ML 模型的嵌入来使用{vectorDbCapabilities}。在 Elastic ML 节点上部署已训练模型并设置推理管道,以在采集文档时自动添加嵌入,便于您在 _search 中使用 kNN 矢量搜索方法。", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.description": "导航到您索引的 {pipelinesName} 选项卡,以创建使用已部署模型的推理管道。", + "xpack.enterpriseSearch.aiSearch.elser.description": "轻松部署 {elser},以便只需单击几下即可实现即时文本语义搜索功能。此模型将使用“text_expansion”字段扩充您的文档和查询文本,提供开箱可用的无缝搜索功能。", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description": "创建索引后,请选中该索引,然后单击 {pipelinesName} 选项卡。", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.description": "要详细了解如何开始使用并通过具体示例测试这些工具,请访问 {searchLab}。", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.description": "请访问{aiSearchDoc}。", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.description": "需要帮助?请访问{discussForum}!", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.description": "{searchLabsRepo} 包含笔记本、示例应用程序和资源。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.description": "使用{behavioralAnalytics}仪表板和工具以可视化用户行为并评估您所做更改的影响。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.description": "访问{behavioralAnalytics}并创建您的首个集合", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.description": "使用情绪分析、汇总或已命名实体识别等自然语言处理 (NLP) 工具来增强搜索结果的相关性。NLP 使用几个可加载的 {supportedMlModels},以通过附加字段智能分析并扩充文档。", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.description": "导航到您索引的 {pipelinesName} 选项卡,以创建使用已部署模型的推理管道。", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.description": "使用 {rrf} 将来自多个结果集的排名与不同相关性指标组合起来,而无需进行微调。", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description": "通过添加来自 ML 模型的嵌入来使用{vectorDbCapabilities}。在 Elastic ML 节点上部署已训练模型并设置推理管道,以在采集文档时自动添加嵌入,便于您在 _search 中使用 kNN 矢量搜索方法。", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description": "导航到您索引的 {pipelinesName} 选项卡,以创建使用已部署模型的推理管道。", "xpack.enterpriseSearch.index.connector.syncRules.description": "包括或排除高级别项目、文件类型和(文件或文件夹)路径\n 从 {indexName} 中同步。默认情况下包括所有内容。每个文档会\n 根据以下规则进行测试,并将应用第一个匹配的规则。", "xpack.enterpriseSearch.inferencePipelineCard.action.delete.disabledDescription": "无法删除此推理管道,因为它已用在多个管道中 [{indexReferences}]。您必须将此管道从所有管道(一个采集管道除外)分离,然后才能将其删除。", "xpack.enterpriseSearch.inferencePipelineCard.deleteConfirm.description": "您正从 Machine Learning 推理管道中移除并删除管道“{pipelineName}”。", @@ -13684,19 +13631,6 @@ "xpack.enterpriseSearch.content.ml_inference.text_embedding": "密集向量文本嵌入", "xpack.enterpriseSearch.content.ml_inference.text_expansion": "ELSER 文本扩展", "xpack.enterpriseSearch.content.ml_inference.zero_shot_classification": "Zero-Shot 文本分类", - "xpack.enterpriseSearch.content.nativeConnectors.azureBlob.name": "Azure Blob 存储", - "xpack.enterpriseSearch.content.nativeConnectors.confluence.name": "Confluence Cloud 和 Confluence Server", - "xpack.enterpriseSearch.content.nativeConnectors.customConnector.name": "定制连接器", - "xpack.enterpriseSearch.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", - "xpack.enterpriseSearch.content.nativeConnectors.jira.name": "Jira Cloud 和 Jira Server", - "xpack.enterpriseSearch.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", - "xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB", - "xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL", - "xpack.enterpriseSearch.content.nativeConnectors.networkDrive.name": "网络驱动器", - "xpack.enterpriseSearch.content.nativeConnectors.oracle.name": "Oracle", - "xpack.enterpriseSearch.content.nativeConnectors.postgresql.name": "PostgreSQL", - "xpack.enterpriseSearch.content.nativeConnectors.s3.name": "S3", - "xpack.enterpriseSearch.content.nativeConnectors.sharepoint_online.name": "Sharepoint", "xpack.enterpriseSearch.content.navTitle": "内容", "xpack.enterpriseSearch.content.new_index.apiDescription": "使用 API 以编程方式将文档添加到 Elasticsearch 索引。首先创建索引。", "xpack.enterpriseSearch.content.new_index.apiTitle": "新搜索索引", @@ -14173,75 +14107,75 @@ "xpack.enterpriseSearch.errorConnectingState.troubleshootAuth": "检查您的用户身份验证:", "xpack.enterpriseSearch.errorConnectingState.troubleshootAuthNative": "必须使用 Elasticsearch 本机身份验证、SSO/SAML 或 OpenID Connect 执行身份验证。", "xpack.enterpriseSearch.errorConnectingState.troubleshootAuthSAML": "如果使用外部 SSO 提供程序,如 SAML 或 OpenID Connect,还必须在 Enterprise Search 上设置 SAML/OIDC Realm。", - "xpack.enterpriseSearch.esre.description": "工具套件,供开发者使用 Elastic 平台构建 AI 搜索驱动型应用程序。", - "xpack.enterpriseSearch.esre.elser.description.elserLinkText": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.esre.elserAccordion.description": "即时语义搜索功能", - "xpack.enterpriseSearch.esre.elserAccordion.title": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.esre.elserPanel.step1.buttonLabel": "创建索引", - "xpack.enterpriseSearch.esre.elserPanel.step1.title": "创建索引", - "xpack.enterpriseSearch.esre.elserPanel.step2.description.pipelinesName": "管道", - "xpack.enterpriseSearch.esre.elserPanel.step2.title": "导航到索引的“管道”选项卡", - "xpack.enterpriseSearch.esre.elserPanel.step3.description": "查找允许您一键部署 ELSER 并使用该模型创建推理管道的面板。", - "xpack.enterpriseSearch.esre.elserPanel.step3.title": "按照屏幕上显示的说明部署 ELSER", - "xpack.enterpriseSearch.esre.esreDocsSection.description.esreLinkText": "ESRE 文档", - "xpack.enterpriseSearch.esre.esreDocsSection.faq.title": "常见问题解答", - "xpack.enterpriseSearch.esre.esreDocsSection.help.title": "帮助", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.discussForumLinkText": "ESRE 讨论论坛", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.frequentlyAskedQuestionsLinkText": "常见问题解答", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.learningTopicsLinkText": "学习主题", - "xpack.enterpriseSearch.esre.esreDocsSection.learn.title": "学习", - "xpack.enterpriseSearch.esre.esreDocsSection.title": "更深入地了解 ESRE 文档", - "xpack.enterpriseSearch.esre.guide.description": "借助 Elasticsearch Relevance Engine™ (ESRE),开发者可以使用 Elastic 平台构建 AI 搜索驱动型应用程序。ESRE 提供了一组工具和功能,其中包括我们专有的已训练 ML 模型 ELSER、我们的矢量搜索和嵌入功能,以及用于组合矢量和文本搜索的 RRF 排名。", - "xpack.enterpriseSearch.esre.guide.pageTitle": "利用 ESRE 增强您的搜索功能", - "xpack.enterpriseSearch.esre.linearCombinationAccordion.description": "来自多个排名的加权结果", - "xpack.enterpriseSearch.esre.linearCombinationAccordion.title": "线性组合", - "xpack.enterpriseSearch.esre.linearCombinationPanel.description": "用于计算相似度分数或数据点之间的距离。使用权重组合属性或功能,这将启用定制相关性因子。", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step1.knnSearchCombineLinkText": "组合近似 kNN 与其他功能", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step1.title": "了解如何在 _search 查询中使用线性组合", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step2.buttonLabel": "打开 Console", - "xpack.enterpriseSearch.esre.linearCombinationPanel.step2.title": "立即在 Console 中试用", - "xpack.enterpriseSearch.esre.measurePerformanceSection.behavioralAnalyticsLinkText": "行为分析", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.behavioralAnalyticsLinkText": "行为分析", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step1.title": "创建集合", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step2.description": "创建集合后,请按照有关如何将跟踪器集成到应用程序或网站的指示操作。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step2.title": "集成分析跟踪器", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step3.description": "我们的仪表板和工具将帮助您对最终用户行为进行可视化并评估搜索应用程序的性能。", - "xpack.enterpriseSearch.esre.measurePerformanceSection.step3.title": "查看仪表板", - "xpack.enterpriseSearch.esre.measurePerformanceSection.title": "评估性能", - "xpack.enterpriseSearch.esre.navTitle": "ESRE", - "xpack.enterpriseSearch.esre.nlpEnrichmentAccordion.description": "利用已训练 ML 模型进行富有洞察力的数据扩充", - "xpack.enterpriseSearch.esre.nlpEnrichmentAccordion.title": "NLP 扩充", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.description.supportedMlModelsLinkText": "受支持的 ML 模型", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.buttonLabel": "查看已训练模型", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.guideToTrainedModelsLinkText": "已训练模型指南", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.supportedNlpModelsLinkText": "受支持的 NLP 模型", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step1.title": "了解如何上传 ML 模型", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step2.buttonLabel": "创建索引", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step2.title": "创建索引", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.description.pipelinesName": "管道", - "xpack.enterpriseSearch.esre.nlpEnrichmentPanel.step3.title": "创建 ML 推理管道", - "xpack.enterpriseSearch.esre.productName": "ESRE", - "xpack.enterpriseSearch.esre.rankAggregationSection.description": "可选方法,用于融合或组合不同排名,以实现更高的总体排名性能。", - "xpack.enterpriseSearch.esre.rankAggregationSection.title": "使用排名聚合方法", - "xpack.enterpriseSearch.esre.rrfRankingAccordion.description": "智能组合排名,而无需配置", - "xpack.enterpriseSearch.esre.rrfRankingAccordion.title": "RRF 混合排名", - "xpack.enterpriseSearch.esre.rrfRankingPanel.rrfLinkText": "倒数排名融合 (RRF)", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step1.rrfDocsLinkText": "倒数排名融合文档", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step1.title": "发现在 _search 查询中使用 RRF 的示例", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step2.buttonLabel": "打开 Console", - "xpack.enterpriseSearch.esre.rrfRankingPanel.step2.title": "立即在 Console 中试用", - "xpack.enterpriseSearch.esre.semanticSearch.description": "ESRE 组合了您选择的这些信息检索工具。", - "xpack.enterpriseSearch.esre.semanticSearch.title": "设置语义搜索", - "xpack.enterpriseSearch.esre.vectorSearchAccordion.description": "用于非结构化数据的强大相似度搜索", - "xpack.enterpriseSearch.esre.vectorSearchAccordion.title": "矢量搜索", - "xpack.enterpriseSearch.esre.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "Elasticsearch 的矢量 DB 功能", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.buttonLabel": "查看已训练模型", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "已训练模型指南", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step1.title": "了解如何上传 ML 模型", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step2.buttonLabel": "创建索引", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step2.title": "创建索引", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.description.pipelinesName": "管道", - "xpack.enterpriseSearch.esre.vectorSearchPanel.step3.title": "创建 ML 推理管道", + "xpack.enterpriseSearch.aiSearch.description": "工具套件,供开发者使用 Elastic 平台构建 AI 搜索驱动型应用程序。", + "xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText": "Elastic Learned Sparse Encoder v2", + "xpack.enterpriseSearch.aiSearch.elserAccordion.description": "即时语义搜索功能", + "xpack.enterpriseSearch.aiSearch.elserAccordion.title": "Elastic Learned Sparse Encoder", + "xpack.enterpriseSearch.aiSearch.elserPanel.step1.buttonLabel": "创建索引", + "xpack.enterpriseSearch.aiSearch.elserPanel.step1.title": "创建索引", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description.pipelinesName": "管道", + "xpack.enterpriseSearch.aiSearch.elserPanel.step2.title": "导航到索引的“管道”选项卡", + "xpack.enterpriseSearch.aiSearch.elserPanel.step3.description": "查找允许您一键部署 ELSER 并使用该模型创建推理管道的面板。", + "xpack.enterpriseSearch.aiSearch.elserPanel.step3.title": "按照屏幕上显示的说明部署 ELSER", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.description.searchLabsLinkText": "Search Labs", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.title": "ドキュメンテーション", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.title": "帮助", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.help.helpLinkText": "讨论论坛或 Elastic 社区 Slack", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.doc.aiSearchDocLinkText": "Elastic 文档", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.searchLabsRepoLinkText": "SearchLabs 存储库", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.learn.title": "学习", + "xpack.enterpriseSearch.aiSearch.aiSearchDocsSection.title": "通过人工智能搜索进行更深入的挖掘", + "xpack.enterpriseSearch.aiSearch.guide.description": "使用 Elastic 平台构建人工智能搜索驱动的应用程序,包括我们经过专有训练的 ML 模型 ELSER、矢量搜索和嵌入功能以及用于组合矢量和文本搜索的 RRF 排名。", + "xpack.enterpriseSearch.aiSearch.guide.pageTitle": "利用 AI 增强您的搜索功能", + "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.description": "来自多个排名的加权结果", + "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.title": "线性组合", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.description": "用于计算相似度分数或数据点之间的距离。使用权重组合属性或功能,这将启用定制相关性因子。", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step1.knnSearchCombineLinkText": "组合近似 kNN 与其他功能", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step1.title": "了解如何在 _search 查询中使用线性组合", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step2.buttonLabel": "打开 Console", + "xpack.enterpriseSearch.aiSearch.linearCombinationPanel.step2.title": "立即在 Console 中试用", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.behavioralAnalyticsLinkText": "行为分析", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.behavioralAnalyticsLinkText": "行为分析", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step1.title": "创建集合", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step2.description": "创建集合后,请按照有关如何将跟踪器集成到应用程序或网站的指示操作。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step2.title": "集成分析跟踪器", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step3.description": "我们的仪表板和工具将帮助您对最终用户行为进行可视化并评估搜索应用程序的性能。", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.step3.title": "查看仪表板", + "xpack.enterpriseSearch.aiSearch.measurePerformanceSection.title": "评估性能", + "xpack.enterpriseSearch.aiSearch.navTitle": "人工智能搜索", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentAccordion.description": "利用已训练 ML 模型进行富有洞察力的数据扩充", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentAccordion.title": "NLP 扩充", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.description.supportedMlModelsLinkText": "受支持的 ML 模型", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.buttonLabel": "查看已训练模型", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.guideToTrainedModelsLinkText": "已训练模型指南", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.supportedNlpModelsLinkText": "受支持的 NLP 模型", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step1.title": "了解如何上传 ML 模型", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step2.buttonLabel": "创建索引", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step2.title": "创建索引", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.description.pipelinesName": "管道", + "xpack.enterpriseSearch.aiSearch.nlpEnrichmentPanel.step3.title": "创建 ML 推理管道", + "xpack.enterpriseSearch.aiSearch.productName": "人工智能搜索", + "xpack.enterpriseSearch.aiSearch.rankAggregationSection.description": "可选方法,用于融合或组合不同排名,以实现更高的总体排名性能。", + "xpack.enterpriseSearch.aiSearch.rankAggregationSection.title": "使用排名聚合方法", + "xpack.enterpriseSearch.aiSearch.rrfRankingAccordion.description": "智能组合排名,而无需配置", + "xpack.enterpriseSearch.aiSearch.rrfRankingAccordion.title": "RRF 混合排名", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.rrfLinkText": "倒数排名融合 (RRF)", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step1.rrfDocsLinkText": "倒数排名融合文档", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step1.title": "发现在 _search 查询中使用 RRF 的示例", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.buttonLabel": "打开 Console", + "xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.title": "立即在 Console 中试用", + "xpack.enterpriseSearch.aiSearch.semanticSearch.description": "ESRE 组合了您选择的这些信息检索工具。", + "xpack.enterpriseSearch.aiSearch.semanticSearch.title": "设置语义搜索", + "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.description": "用于非结构化数据的强大相似度搜索", + "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.title": "矢量搜索", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "Elasticsearch 的矢量 DB 功能", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.buttonLabel": "查看已训练模型", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "已训练模型指南", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.title": "了解如何上传 ML 模型", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.buttonLabel": "创建索引", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.title": "创建索引", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description.pipelinesName": "管道", + "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.title": "创建 ML 推理管道", "xpack.enterpriseSearch.guideConfig.addDataStep.description": "采集您的数据,创建索引,并使用可定制采集和推理管道扩充您的数据。", "xpack.enterpriseSearch.guideConfig.addDataStep.title": "添加数据", "xpack.enterpriseSearch.guideConfig.description": "我们将帮助您使用 Elastic 的网络爬虫、连接器和 API,利用您的数据构建搜索体验。", @@ -14307,40 +14241,6 @@ "xpack.enterpriseSearch.licenseDocumentationLink": "详细了解许可证功能", "xpack.enterpriseSearch.licenseManagementLink": "管理您的许可", "xpack.enterpriseSearch.nameLabel": "名称", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.collectionLabel": "集合", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.directConnectionLabel": "直接连接", - "xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.hostLabel": "服务器主机名", - "xpack.enterpriseSearch.nativeConnectors.mongodb.name": "MongoDB", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.hostLabel": "主机", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.passwordLabel": "密码", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.retriesLabel": "根据请求重试", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.rowsFetchedLabel": "根据请求提取的行", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.schemaLabel": "架构", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.tablesLabel": "表的逗号分隔列表", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.usernameLabel": "用户名", - "xpack.enterpriseSearch.nativeConnectors.mssql.configuration.validateHostLabel": "验证主机", - "xpack.enterpriseSearch.nativeConnectors.mssql.name": "Microsoft SQL", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.hostLabel": "主机", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.passwordLabel": "密码", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.retriesLabel": "根据请求重试", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.rowsFetchedLabel": "根据请求提取的行", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.tablesLabel": "表的逗号分隔列表", - "xpack.enterpriseSearch.nativeConnectors.mysql.configuration.usernameLabel": "用户名", - "xpack.enterpriseSearch.nativeConnectors.mysql.name": "MySQL", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.hostLabel": "主机", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.retriesLabel": "根据请求重试", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.rowsFetchedLabel": "根据请求提取的行", - "xpack.enterpriseSearch.nativeConnectors.postgresql.configuration.tablesLabel": "表的逗号分隔列表", - "xpack.enterpriseSearch.nativeConnectors.postgresql.name": "PostgreSQL", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.clientIdLabel": "客户端 ID", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.secretValueLabel": "机密值", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.siteCollectionsLabel": "网站的逗号分隔列表", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.siteCollectionsTooltip": "要从中采集数据的网站的逗号分隔列表。使用 * 以包括所有可用网站。", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.tenantIdLabel": "租户 ID", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.tenantNameLabel": "租户名称", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.textExtractionServiceLabel": "使用文本提取服务", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.configuration.textExtractionServiceTooltip": "需要独立部署的 Elastic 数据提取服务。还需要管道设置禁用文本提取。", - "xpack.enterpriseSearch.nativeConnectors.sharepoint_online.name": "Sharepoint", "xpack.enterpriseSearch.nativeLabel": "原生", "xpack.enterpriseSearch.nav.analyticsCollections.explorerTitle": "浏览器", "xpack.enterpriseSearch.nav.analyticsCollections.integrationTitle": "集成", @@ -14352,8 +14252,8 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "设置", "xpack.enterpriseSearch.nav.contentTitle": "内容", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概览", - "xpack.enterpriseSearch.nav.esreTitle": "ESRE", + "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "入门", + "xpack.enterpriseSearch.nav.aiSearchTitle": "AI Search", "xpack.enterpriseSearch.nav.searchApplicationsTitle": "搜索应用程序", "xpack.enterpriseSearch.nav.searchIndicesTitle": "索引", "xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search", @@ -14526,9 +14426,6 @@ "xpack.enterpriseSearch.searchExperiences.navTitle": "搜索体验", "xpack.enterpriseSearch.searchExperiences.productDescription": "构建直观、具有吸引力的搜索体验,而无需浪费时间进行重复工作。", "xpack.enterpriseSearch.searchExperiences.productName": "搜索体验", - "xpack.enterpriseSearch.server.connectors.configuration.error": "找不到连接器", - "xpack.enterpriseSearch.server.connectors.scheduling.error": "无法找到文档", - "xpack.enterpriseSearch.server.connectors.serviceType.error": "无法找到文档", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionExistsError": "集合名称已存在。请选择其他名称。", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionNotFoundErrorMessage": "未找到分析集合", "xpack.enterpriseSearch.server.routes.addConnector.connectorExistsError": "连接器或索引已存在", @@ -16662,8 +16559,6 @@ "xpack.fleet.policyDetailsPackagePolicies.createFirstTitle": "添加您的首个集成", "xpack.fleet.policyForm.deletePolicyActionText": "删除策略", "xpack.fleet.policyForm.deletePolicyActionText.disabled": "无法删除包含托管软件包策略的代理策略。", - "xpack.fleet.policyForm.deletePolicyGroupDescription": "现有数据将不会删除。", - "xpack.fleet.policyForm.deletePolicyGroupTitle": "删除策略", "xpack.fleet.policyForm.generalSettingsGroupDescription": "为您的代理策略选择名称和描述。", "xpack.fleet.policyForm.generalSettingsGroupTitle": "常规设置", "xpack.fleet.renameAgentTags.errorNotificationTitle": "标签重命名失败", @@ -17340,10 +17235,7 @@ "xpack.idxMgmt.breadcrumb.editTemplateLabel": "编辑模板", "xpack.idxMgmt.breadcrumb.homeLabel": "索引管理", "xpack.idxMgmt.breadcrumb.templatesLabel": "模板", - "xpack.idxMgmt.componentTemplate.breadcrumb.componentTemplatesLabel": "组件模板", - "xpack.idxMgmt.componentTemplate.breadcrumb.createComponentTemplateLabel": "创建组件模板", "xpack.idxMgmt.componentTemplate.breadcrumb.editComponentTemplateLabel": "编辑组件模板", - "xpack.idxMgmt.componentTemplate.breadcrumb.homeLabel": "索引管理", "xpack.idxMgmt.componentTemplateClone.loadComponentTemplateTitle": "加载组件模板“{sourceComponentTemplateName}”时出错。", "xpack.idxMgmt.componentTemplateDetails.aliasesTabTitle": "别名", "xpack.idxMgmt.componentTemplateDetails.cloneActionLabel": "克隆", @@ -17461,7 +17353,6 @@ "xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "为数据流创建的后备索引的累积计数", "xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "运行状况", "xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "数据流的当前后备索引的运行状况", - "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "无", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "索引生命周期策略", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "用于管理数据流数据的索引生命周期策略", "xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "索引模板", @@ -18659,7 +18550,7 @@ "xpack.infra.logSourceConfiguration.missingDataViewsLabel": "缺少数据视图 {indexPatternId}", "xpack.infra.logSourceConfiguration.missingMessageFieldErrorMessage": "数据视图必须包含 {messageField} 字段。", "xpack.infra.logSourceErrorPage.savedObjectNotFoundErrorMessage": "无法找到该{savedObjectType}:{savedObjectId}", - "xpack.infra.metadataEmbeddable.errorMessage": "加载数据时出错。尝试{reload}并再次打开主机详情。", + "xpack.infra.metadataEmbeddable.errorMessage": "加载数据时出错。尝试{refetch}并再次打开主机详情。", "xpack.infra.metrics.alertDetailsAppSection.criterion.subtitle": "过去 {lookback} {timeLabel}", "xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError": "此规则可能针对低于预期的 {matchedGroups} 告警,因为筛选查询包含{groupCount, plural, other {这些字段}}的匹配项。有关更多信息,请参阅 {filteringAndGroupingLink}。", "xpack.infra.metrics.alertFlyout.customEquationEditor.aggregationLabel": "聚合 {name}", @@ -18761,7 +18652,6 @@ "xpack.infra.assetDetailsEmbeddable.displayName": "资产详情", "xpack.infra.assetDetailsEmbeddable.title": "资产详情", "xpack.infra.assetDetailsEmbeddable.overview.kpi.cpuUsage.title": "CPU 使用率", - "xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title": "磁盘空间使用率", "xpack.infra.assetDetailsEmbeddable.overview.kpi.memoryUsage.title": "内存利用率", "xpack.infra.assetDetailsEmbeddable.overview.kpi.normalizedLoad1m.title": "标准化负载", "xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average": "平均值", @@ -18868,7 +18758,6 @@ "xpack.infra.hostsViewPage.metrics.tooltip.tx": "主机的公共接口上每秒发送的字节数。", "xpack.infra.hostsViewPage.table.addFilter": "添加筛选", "xpack.infra.hostsViewPage.table.cpuUsageColumnHeader": "CPU 使用率(平均值)", - "xpack.infra.hostsViewPage.table.diskSpaceUsageColumnHeader": "磁盘空间使用率(平均值)", "xpack.infra.hostsViewPage.table.memoryFreeColumnHeader": "可用内存(平均值)", "xpack.infra.hostsViewPage.table.memoryUsageColumnHeader": "内存使用率(平均值)", "xpack.infra.hostsViewPage.table.nameColumnHeader": "名称", @@ -18894,7 +18783,6 @@ "xpack.infra.hostsViewPage.tabs.metricsCharts.diskIOWrite": "磁盘写入 IOPS", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskReadThroughput": "磁盘读取吞吐量", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceAvailable": "可用磁盘空间", - "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed": "磁盘空间使用率", "xpack.infra.hostsViewPage.tabs.metricsCharts.diskWriteThroughput": "磁盘写入吞吐量", "xpack.infra.hostsViewPage.tabs.metricsCharts.memoryFree": "可用内存", "xpack.infra.hostsViewPage.tabs.metricsCharts.memoryUsage": "内存利用率", @@ -21155,7 +21043,6 @@ "xpack.lens.indexPattern.percentFormatLabel": "百分比", "xpack.lens.indexPattern.percentile": "百分位数", "xpack.lens.indexPattern.percentile.documentation.quick": "\n 小于所有文档中出现值的 n% 的最大值。\n ", - "xpack.lens.indexPattern.percentile.errorMessage": "百分位数必须是介于 1 到 99 之间的整数", "xpack.lens.indexPattern.percentile.percentileRanksValue": "百分位等级值", "xpack.lens.indexPattern.percentile.percentileValue": "百分位数", "xpack.lens.indexPattern.percentile.signature": "field: string, [percentile]: number", @@ -24013,17 +23900,11 @@ "xpack.ml.entityFilter.addFilterTooltip": "添加筛选", "xpack.ml.entityFilter.removeFilterTooltip": "移除筛选", "xpack.ml.logRateAnalysis.pageHeader": "解释日志速率峰值", - "xpack.ml.explorer.addToDashboard.anomalyCharts.dashboardsTitle": "将异常图表添加到仪表板", "xpack.ml.explorer.addToDashboard.anomalyCharts.maxSeriesToPlotLabel": "要绘制的最大序列数目", - "xpack.ml.explorer.addToDashboard.cancelButtonLabel": "取消", - "xpack.ml.explorer.addToDashboard.selectDashboardsLabel": "选择仪表板:", - "xpack.ml.explorer.addToDashboard.swimlanes.dashboardsTitle": "将泳道添加到仪表板", - "xpack.ml.explorer.addToDashboard.swimlanes.selectSwimlanesLabel": "选择泳道视图:", "xpack.ml.explorer.addToDashboardLabel": "添加到仪表板", "xpack.ml.explorer.annotationsErrorCallOutTitle": "加载注释时发生错误:", "xpack.ml.explorer.annotationsErrorTitle": "标注", "xpack.ml.explorer.anomalies.actionsAriaLabel": "操作", - "xpack.ml.explorer.anomalies.actionsPopoverLabel": "异常图表", "xpack.ml.explorer.anomalies.addToDashboardLabel": "添加到仪表板", "xpack.ml.explorer.anomaliesTitle": "异常", "xpack.ml.explorer.anomalyTimelinePopoverAdvancedExplanation": "在 Anomaly Explorer 的每个部分中看到的异常分数可能略微不同。这种差异之所以发生,是因为每个作业都有存储桶结果、总体存储桶结果、影响因素结果和记录结果。每个结果类型都会生成异常分数。总体泳道显示每个块的最大总体存储桶分数。按作业查看泳道时,其在每个块中显示最大存储桶分数。按影响因素查看泳道时,其在每个块中显示最大影响因素分数。", @@ -24045,10 +23926,6 @@ "xpack.ml.explorer.charts.viewInMapsLabel": "查看", "xpack.ml.explorer.charts.viewLabel": "查看", "xpack.ml.explorer.clearSelectionLabel": "清除所选内容", - "xpack.ml.explorer.dashboardsTable.actionsHeader": "操作", - "xpack.ml.explorer.dashboardsTable.descriptionColumnHeader": "描述", - "xpack.ml.explorer.dashboardsTable.editActionName": "添加到仪表板", - "xpack.ml.explorer.dashboardsTable.titleColumnHeader": "标题", "xpack.ml.explorer.distributionChart.anomalyScoreLabel": "异常分数", "xpack.ml.explorer.distributionChart.entityLabel": "实体", "xpack.ml.explorer.distributionChart.typicalLabel": "典型", @@ -24215,13 +24092,7 @@ "xpack.ml.jobSelector.noResultsForJobLabel": "无结果", "xpack.ml.jobSelector.selectAllGroupLabel": "全选", "xpack.ml.jobSelector.selectAllOptionLabel": "*", - "xpack.ml.jobService.activeDatafeedsLabel": "活动数据馈送", - "xpack.ml.jobService.activeMLNodesLabel": "活动 ML 节点", - "xpack.ml.jobService.closedJobsLabel": "已关闭的作业", - "xpack.ml.jobService.failedJobsLabel": "失败的作业", "xpack.ml.jobService.jobAuditMessagesErrorTitle": "加载作业消息时出错", - "xpack.ml.jobService.openJobsLabel": "打开的作业", - "xpack.ml.jobService.totalJobsLabel": "总计作业数", "xpack.ml.jobService.validateJobErrorTitle": "作业验证错误", "xpack.ml.jobsHealthAlertingRule.actionGroupName": "检测到问题", "xpack.ml.jobsHealthAlertingRule.name": "异常检测作业运行状况", @@ -27142,9 +27013,6 @@ "xpack.observability_onboarding.card.systemLogs.title": "收集系统日志", "xpack.observability_onboarding.configureLogs.advancedSettings": "高级设置", "xpack.observability_onboarding.configureLogs.customConfig": "定制配置", - "xpack.observability_onboarding.configureLogs.dataset.helper": "选取日志的名称。全部小写,最多 100 个字符,将用“_”替代特殊字符。", - "xpack.observability_onboarding.configureLogs.dataset.name": "数据集名称", - "xpack.observability_onboarding.configureLogs.dataset.placeholder": "数据集名称", "xpack.observability_onboarding.configureLogs.description": "填写日志文件在主机上的路径。", "xpack.observability_onboarding.configureLogs.learnMore": "了解详情", "xpack.observability_onboarding.configureLogs.logFile.addRow": "添加行", @@ -27204,9 +27072,7 @@ "xpack.observability_onboarding.selectLogs.useOwnShipper": "获取 API 密钥", "xpack.observability_onboarding.selectLogs.useOwnShipper.description": "通过生成 API 密钥使用您自己的采集器来收集日志数据。", "xpack.observability_onboarding.steps.back": "返回", - "xpack.observability_onboarding.steps.continue": "继续", "xpack.observability_onboarding.steps.exploreLogs": "浏览日志", - "xpack.observability_onboarding.steps.inspect": "检查", "xpack.observability_onboarding.title.collectCustomLogs": "收集定制日志", "xpack.observability.apmEnableContinuousRollupsDescription": "{betaLabel} 启用连续汇总/打包时,UI 将以适当分辨率选择指标。在更大时间范围内,将使用分辨率较低的指标,这会缩短加载时间。", "xpack.observability.apmEnableServiceMetricsDescription": "{betaLabel} 启用服务事务指标,这种是低基数指标,可供某些视图(如服务库存)使用来加快加载速度。", @@ -27219,7 +27085,6 @@ "xpack.observability.inspector.stats.queryTimeValue": "{queryTime}ms", "xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {已检测到} Inventory {超出阈值} other {超出}}", "xpack.observability.slo.alerting.burnRate.reason": "{actionGroupName}:过去 {longWindowDuration} 的消耗速度为 {longWindowBurnRate} 且过去 {shortWindowDuration} 为 {shortWindowBurnRate}。两个窗口超出 {burnRateThreshold} 时告警", - "xpack.observability.slo.burnRateWindow.thresholdTip": "阈值为 {target}x", "xpack.observability.slo.clone.errorNotification": "无法克隆 {name}", "xpack.observability.slo.clone.successNotification": "已成功创建 {name}", "xpack.observability.slo.create.errorNotification": "创建 {name} 时出现问题", @@ -27237,8 +27102,6 @@ "xpack.observability.slo.slo.activeAlertsBadge.label": "{count, plural, other {# 个告警}}", "xpack.observability.slo.slo.delete.errorNotification": "无法删除{name}", "xpack.observability.slo.slo.delete.successNotification": "已删除 {name}", - "xpack.observability.slo.slo.deleteConfirmationModal.deleteButtonLabel": "删除{name}", - "xpack.observability.slo.slo.deleteConfirmationModal.descriptionText": "您无法恢复删除的{name}。", "xpack.observability.slo.slo.stats.objective": "{objective} 目标", "xpack.observability.slo.slo.timeWindow.calendar": "{elapsed}/{total} 天", "xpack.observability.slo.sloDetails.errorBudgetChartPanel.duration": "过去 {duration}", @@ -27252,18 +27115,6 @@ "xpack.observability.slo.update.errorNotification": "更新 {name} 时出现问题", "xpack.observability.slo.update.successNotification": "成功更新 {name}", "xpack.observability.syntheticsThrottlingEnabledExperimentDescription": "在 Synthetics 监测配置中启用限制设置。请注意,即使该设置处于活动状态,可能仍然无法对您的监测应用限制。仅限内部使用。{link}", - "xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "过去 {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "此规则可能针对低于预期的 {matchedGroups} 告警,因为筛选查询包含{groupCount, plural, other {这些字段}}的匹配项。有关更多信息,请参阅 {filteringAndGroupingLink}。", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "聚合 {name}", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "KQL 筛选 {name}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "过去 {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id} 过去 {lookback} {timeLabel}的数据", - "xpack.observability.threshold.rule.threshold.errorAlertReason": "Elasticsearch 尝试查询 {metric} 的数据时出现故障", - "xpack.observability.threshold.rule.threshold.firedAlertReason": "过去 {duration}{group},{metric} 为 {currentValue}。{comparator} {threshold} 时告警。", - "xpack.observability.threshold.rule.threshold.noDataAlertReason": "对于 {group},{metric} 在过去 {interval}中未报告数据", - "xpack.observability.threshold.rule.threshold.recoveredAlertReason": "对于 {group},{metric} 现在{comparator}阈值 {threshold}(当前值为 {currentValue})", - "xpack.observability.threshold.rule.threshold.thresholdRange": "{a} 和 {b}", - "xpack.observability.threshold.rule.thresholdExtraTitle": "{comparator} {threshold} 时告警", "xpack.observability.transactionRateLabel": "{value} tpm", "xpack.observability.ux.coreVitals.averageMessage": " 且小于 {bad}", "xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd} ({ranksInd}%)", @@ -27504,18 +27355,6 @@ "xpack.observability.slo.alerting.windowDescription": "带有关联的消耗速度值的窗口持续时间。", "xpack.observability.slo.budgetingMethod.occurrences": "发生次数", "xpack.observability.slo.budgetingMethod.timeslices": "时间片", - "xpack.observability.slo.burnRate.criticalLongLabel": "1 小时", - "xpack.observability.slo.burnRate.criticalShortLabel": "5 分钟", - "xpack.observability.slo.burnRate.criticalTitle": "临界消耗速度", - "xpack.observability.slo.burnRate.highLongLabel": "6 小时", - "xpack.observability.slo.burnRate.highShortLabel": "30 分钟", - "xpack.observability.slo.burnRate.highTitle": "高消耗速度", - "xpack.observability.slo.burnRate.lowLongLabel": "3 天", - "xpack.observability.slo.burnRate.lowShortLabel": "6 小时", - "xpack.observability.slo.burnRate.lowTitle": "低消耗速度", - "xpack.observability.slo.burnRate.mediumLongLabel": "24 小时", - "xpack.observability.slo.burnRate.mediumShortLabel": "2 小时", - "xpack.observability.slo.burnRate.mediumTitle": "中等消耗速度", "xpack.observability.slo.burnRate.technicalPreviewBadgeDescription": "此功能处于技术预览状态,可能会有所更改,或在未来版本中移除。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。技术预览功能不受正式发行版功能的支持服务水平协议约束。", "xpack.observability.slo.burnRate.technicalPreviewBadgeTitle": "技术预览", "xpack.observability.slo.burnRate.title": "消耗速度窗口", @@ -27557,8 +27396,7 @@ "xpack.observability.slo.rules.sloSelector.placeholder": "选择 SLO", "xpack.observability.slo.rules.sloSelector.rowLabel": "SLO", "xpack.observability.slo.slo.activeAlertsBadge.ariaLabel": "活动告警徽章", - "xpack.observability.slo.slo.deleteConfirmationModal.cancelButtonLabel": "取消", - "xpack.observability.slo.slo.deleteConfirmationModal.title": "是否确定?", + "xpack.observability.slo.deleteConfirmationModal.cancelButtonLabel": "取消", "xpack.observability.slo.slo.item.actions.clone": "克隆", "xpack.observability.slo.slo.item.actions.delete": "删除", "xpack.observability.slo.slo.rulesBadge.popover": "尚未为此 SLO 配置任何规则。超出 SLO 时,您不会收到告警。", @@ -27638,8 +27476,6 @@ "xpack.observability.slo.sloEdit.sliType.customKql.goodQuery.tooltip": "此 KQL 查询应返回用于计算 SLO 时被视为“良好”或“成功”的事件的子集。此查询应基于某些相关条件(如状态代码、错误消息或其他相关字段)筛选事件。", "xpack.observability.slo.sloEdit.sliType.customKql.goodQueryPlaceholder": "定义良好事件", "xpack.observability.slo.sloEdit.sliType.customKql.queryFilter": "查询筛选", - "xpack.observability.slo.sloEdit.sliType.customKql.timestampField.label": "时间戳字段", - "xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder": "选择时间戳字段", "xpack.observability.slo.sloEdit.sliType.customKql.totalQuery": "查询总数", "xpack.observability.slo.sloEdit.sliType.customKql.totalQuery.tooltip": "此 KQL 查询应返回与 SLO 计算相关的所有事件,包括良好和不良事件。", "xpack.observability.slo.sloEdit.sliType.customKql.totalQueryPlaceholder": "定义事件总数", @@ -27653,8 +27489,6 @@ "xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "选择指标字段", "xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "查询筛选", "xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "求和", - "xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.label": "时间戳字段", - "xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder": "选择时间戳字段", "xpack.observability.slo.sloEdit.tags.label": "标签", "xpack.observability.slo.sloEdit.tags.placeholder": "添加标签", "xpack.observability.slo.sloEdit.targetSlo.label": "目标/SLO (%)", @@ -27726,85 +27560,6 @@ "xpack.observability.statusVisualization.ux.link": "添加数据", "xpack.observability.statusVisualization.ux.title": "用户体验", "xpack.observability.syntheticsThrottlingEnabledExperimentName": "启用 Synthetics 限制(实验性)", - "xpack.observability.threshold.rule..charts.errorMessage": "哇哦,出问题了", - "xpack.observability.threshold.rule..charts.noDataMessage": "没有可用图表数据", - "xpack.observability.threshold.rule..timeLabels.days": "天", - "xpack.observability.threshold.rule..timeLabels.hours": "小时", - "xpack.observability.threshold.rule..timeLabels.minutes": "分钟", - "xpack.observability.threshold.rule..timeLabels.seconds": "秒", - "xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule": "规则", - "xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle": "超出阈值", - "xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription": "链接到告警故障排除视图获取进一步的上下文和详情。如果未配置 server.publicBaseUrl,这将为空字符串。", - "xpack.observability.threshold.rule.alertDropdownTitle": "告警和规则", - "xpack.observability.threshold.rule.alertFlyout.addCondition": "添加条件", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.avg": "平均值", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.cardinality": "基数", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.count": "文档计数", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.max": "最大值", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.min": "最小值", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p95": "第 95 个百分位", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p99": "第 99 个百分位", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.rate": "比率", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.sum": "求和", - "xpack.observability.threshold.rule.alertFlyout.alertDescription": "任何 Observability 数据类型到达或超出给定值时告警。", - "xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear": "组停止报告数据时提醒我", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink": "文档", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerHelpText": "为每个唯一值创建告警。例如:“host.id”或“cloud.region”。", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerText": "告警分组依据(可选)", - "xpack.observability.threshold.rule.alertFlyout.customEquation": "定制方程", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.addCustomRow": "添加聚合/字段", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton": "删除", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage": "支持基本匹配表达式", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage": "定制标签将在告警图表上以及原因/告警标题中显示", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel": "标签(可选)", - "xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[此设置不适用于文档计数聚合器。]", - "xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired": "“聚合”必填。", - "xpack.observability.threshold.rule.alertFlyout.error.customMetrics.aggTypeRequired": "“聚合”必填", - "xpack.observability.threshold.rule.alertFlyout.error.customMetrics.fieldRequired": "“字段”必填", - "xpack.observability.threshold.rule.alertFlyout.error.customMetricsError": "必须至少定义 1 个定制指标", - "xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters": "方程字段仅支持以下字符:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", - "xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery": "筛选查询无效。", - "xpack.observability.threshold.rule.alertFlyout.error.metricRequired": "“指标”必填。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "“阈值”必填。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "阈值必须包含有效数字。", - "xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "“时间大小”必填。", - "xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "启用此选项可在之前检测的组开始不报告任何数据时触发操作。不建议将此选项用于可能会快速自动启动和停止节点的动态扩展基础架构。", - "xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "不介于", - "xpack.observability.threshold.rule.alertFlyout.removeCondition": "删除条件", - "xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[无数据]", - "xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage": "\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\} 处于 \\{\\{context.alertState\\}\\} 状态\n\n 原因:\n \\{\\{context.reason\\}\\}\n ", - "xpack.observability.threshold.rule.alerting.threshold.fired": "告警", - "xpack.observability.threshold.rule.alerting.threshold.nodata": "无数据", - "xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue": "[无数据]", - "xpack.observability.threshold.rule.alertsButton": "告警和规则", - "xpack.observability.threshold.rule.cloudActionVariableDescription": "ECS 定义的云对象(如果在源中可用)。", - "xpack.observability.threshold.rule.containerActionVariableDescription": "ECS 定义的容器对象(如果在源中可用)。", - "xpack.observability.threshold.rule.createInventoryRuleButton": "创建库存规则", - "xpack.observability.threshold.rule.createThresholdRuleButton": "创建阈值规则", - "xpack.observability.threshold.rule.groupByKeysActionVariableDescription": "包含正报告数据的组的对象", - "xpack.observability.threshold.rule.homePage.toolbar.kqlSearchFieldPlaceholder": "搜索基础设施数据……(例如 host.name:host-1)", - "xpack.observability.threshold.rule.hostActionVariableDescription": "ECS 定义的主机对象(如果在源中可用)。", - "xpack.observability.threshold.rule.infrastructureDropdownMenu": "基础设施", - "xpack.observability.threshold.rule.infrastructureDropdownTitle": "基础设施规则", - "xpack.observability.threshold.rule.labelsActionVariableDescription": "与在其上触发此告警的实体关联的标签列表。", - "xpack.observability.threshold.rule.manageRules": "管理规则", - "xpack.observability.threshold.rule.metricsDropdownMenu": "指标", - "xpack.observability.threshold.rule.metricsDropdownTitle": "指标规则", - "xpack.observability.threshold.rule.orchestratorActionVariableDescription": "ECS 定义的 Orchestrator 对象(如果在源中可用)。", - "xpack.observability.threshold.rule.reasonActionVariableDescription": "告警原因的简洁描述", - "xpack.observability.threshold.rule.sourceConfiguration.missingHttp": "无法加载源:无 HTTP 客户端可用。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody": "无法对指标配置应用更改。请稍后重试。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle": "配置更新失败", - "xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle": "已成功更新指标设置", - "xpack.observability.threshold.rule.tagsActionVariableDescription": "与在其上触发此告警的实体关联的标记列表。", - "xpack.observability.threshold.rule.threshold.aboveRecovery": "高于", - "xpack.observability.threshold.rule.threshold.belowRecovery": "低于", - "xpack.observability.threshold.rule.threshold.betweenRecovery": "介于", - "xpack.observability.threshold.rule.threshold.customEquation": "定制方程", - "xpack.observability.threshold.rule.threshold.documentCount": "文档计数", - "xpack.observability.threshold.rule.timestampDescription": "检测到告警时的时间戳。", - "xpack.observability.threshold.rule.valueActionVariableDescription": "指定条件中的指标值。用法:(ctx.value.condition0, ctx.value.condition1, 诸如此类)。", - "xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription": "链接到告警源", "xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "图表绘制依据", "xpack.observability.threshold.ruleExplorer.groupByLabel": "所有内容", "xpack.observability.threshold.ruleName": "阈值(技术预览)", @@ -30008,8 +29763,6 @@ "xpack.securitySolution.exceptions.viewer.lastUpdated": "已更新 {updated}", "xpack.securitySolution.exceptions.viewer.paginationDetails": "正在显示 {partOne} 个,共 {partTwo} 个", "xpack.securitySolution.fieldBrowser.descriptionForScreenReaderOnly": "{field} 字段的描述:", - "xpack.securitySolution.flyout.errorMessage": "显示 {message} 时出现错误", - "xpack.securitySolution.flyout.errorTitle": "无法显示 {title}", "xpack.securitySolution.footer.autoRefreshActiveTooltip": "自动刷新已启用时,时间线将显示匹配查询的最近 {numberOfItems} 个事件。", "xpack.securitySolution.formattedNumber.countsLabel": "{mantissa}{scale}{hasRemainder}", "xpack.securitySolution.header.editableTitle.editButtonAria": "通过单击,可以编辑 {title}", @@ -31624,7 +31377,6 @@ "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootkitDescription": "Bootkit (T1067)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonAutostartExecutionDescription": "Boot or Logon Autostart Execution (T1547)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootOrLogonInitializationScriptsDescription": "Boot or Logon Initialization Scripts (T1037)", - "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription": "Browser Bookmark Discovery (T1217)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserExtensionsDescription": "Browser Extensions (T1176)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.browserSessionHijackingDescription": "Browser Session Hijacking (T1185)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.bruteForceDescription": "Brute Force (T1110)", @@ -33226,71 +32978,9 @@ "xpack.securitySolution.fleetIntegration.assets.name": "主机", "xpack.securitySolution.fleetIntegration.elasticDefend.eventFilter.nonInteractiveSessions.description": "云安全事件筛选。已由 Elastic Defend 集成创建。", "xpack.securitySolution.fleetIntegration.elasticDefend.eventFilter.nonInteractiveSessions.name": "非交互式会话", - "xpack.securitySolution.flyout.analyzerErrorMessage": "分析器", "xpack.securitySolution.flyout.button.timeline": "时间线", - "xpack.securitySolution.flyout.correlations.caseNameColumnTitle": "名称", - "xpack.securitySolution.flyout.correlations.reasonColumnTitle": "原因", - "xpack.securitySolution.flyout.correlations.ruleColumnTitle": "规则", - "xpack.securitySolution.flyout.correlations.severityColumnTitle": "严重性", - "xpack.securitySolution.flyout.correlations.statusColumnTitle": "状态", - "xpack.securitySolution.flyout.correlations.timestampColumnTitle": "时间戳", - "xpack.securitySolution.flyout.documentDetails.alertReasonTitle": "告警原因", - "xpack.securitySolution.flyout.documentDetails.analyzerGraphButton": "分析器图表", - "xpack.securitySolution.flyout.documentDetails.analyzerPreviewTitle": "分析器预览", - "xpack.securitySolution.flyout.documentDetails.collapseDetailButton": "折叠告警详情", - "xpack.securitySolution.flyout.documentDetails.correlationsButton": "相关性", - "xpack.securitySolution.flyout.documentDetails.correlationsTitle": "相关性", - "xpack.securitySolution.flyout.documentDetails.documentDescriptionTitle": "文档描述", - "xpack.securitySolution.flyout.documentDetails.documentReasonTitle": "文档原因", - "xpack.securitySolution.flyout.documentDetails.entitiesButton": "实体", - "xpack.securitySolution.flyout.documentDetails.entitiesTitle": "实体", - "xpack.securitySolution.flyout.documentDetails.expandDetailButton": "展开告警详情", - "xpack.securitySolution.flyout.documentDetails.highlightedFieldsTitle": "突出显示的字段", - "xpack.securitySolution.flyout.documentDetails.insightsOptions": "洞见选项", - "xpack.securitySolution.flyout.documentDetails.insightsTab": "洞见", - "xpack.securitySolution.flyout.documentDetails.insightsTitle": "洞见", - "xpack.securitySolution.flyout.documentDetails.investigationSectionTitle": "调查", - "xpack.securitySolution.flyout.documentDetails.investigationsTab": "调查", - "xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON", - "xpack.securitySolution.flyout.documentDetails.overviewTab": "概览", - "xpack.securitySolution.flyout.documentDetails.overviewTab.prevalenceRowText": "不常见", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichment": "已使用威胁情报扩充字段", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichments": "已使用威胁情报扩充字段", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatch": "检测到威胁匹配", - "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatMatches": "检测到威胁匹配", - "xpack.securitySolution.flyout.documentDetails.prevalenceButton": "普及率", - "xpack.securitySolution.flyout.documentDetails.prevalenceTitle": "普及率", - "xpack.securitySolution.flyout.documentDetails.riskScoreTitle": "风险分数", - "xpack.securitySolution.flyout.documentDetails.ruleDescriptionTitle": "规则描述", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.commandText": "依据", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.processText": "已启动", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.ruleText": "具有规则", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.timeText": "处于", - "xpack.securitySolution.flyout.documentDetails.sessionPreview.title": "会话查看器预览", - "xpack.securitySolution.flyout.documentDetails.sessionViewButton": "会话视图", - "xpack.securitySolution.flyout.documentDetails.severityTitle": "严重性", - "xpack.securitySolution.flyout.documentDetails.share": "共享告警", - "xpack.securitySolution.flyout.documentDetails.tableTab": "表", - "xpack.securitySolution.flyout.documentDetails.threatIntelligenceButton": "威胁情报", - "xpack.securitySolution.flyout.documentDetails.threatIntelligenceTitle": "威胁情报", - "xpack.securitySolution.flyout.documentDetails.visualizationsTitle": "可视化", - "xpack.securitySolution.flyout.documentDetails.visualizeOptions": "Visualize 选项", - "xpack.securitySolution.flyout.documentDetails.visualizeTab": "Visualize", - "xpack.securitySolution.flyout.documentErrorMessage": "文档字段和值", - "xpack.securitySolution.flyout.documentErrorTitle": "文档信息", "xpack.securitySolution.flyout.entities.failRelatedHostsDescription": "无法对相关主机执行搜索", "xpack.securitySolution.flyout.entities.failRelatedUsersDescription": "无法对相关用户执行搜索", - "xpack.securitySolution.flyout.entities.hostsInfoTitle": "主机信息", - "xpack.securitySolution.flyout.entities.relatedEntitiesIpColumn": "IP 地址", - "xpack.securitySolution.flyout.entities.relatedEntitiesNameColumn": "名称", - "xpack.securitySolution.flyout.entities.relatedHostsTitle": "相关主机", - "xpack.securitySolution.flyout.entities.relatedUsersTitle": "相关用户", - "xpack.securitySolution.flyout.entities.usersInfoTitle": "用户信息", - "xpack.securitySolution.flyout.prevalenceErrorMessage": "普及率", - "xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitle": "告警计数", - "xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitle": "文档计数", - "xpack.securitySolution.flyout.response.title": "响应", - "xpack.securitySolution.flyout.sessionViewErrorMessage": "会话视图", "xpack.securitySolution.footer.autoRefreshActiveDescription": "自动刷新已启用", "xpack.securitySolution.footer.cancel": "取消", "xpack.securitySolution.footer.data": "数据", @@ -34615,10 +34305,6 @@ "xpack.serverlessSearch.apiKey.roleDescriptorsLinkLabel": "了解如何构造角色描述符", "xpack.serverlessSearch.apiKey.setup.description": "用于创建 API 密钥的基本配置详情。", "xpack.serverlessSearch.apiKey.setup.title": "设置", - "xpack.serverlessSearch.apiKey.stepOneDescription": "用于身份验证和授权的唯一标识符。", - "xpack.serverlessSearch.apiKey.stepOneTitle": "生成并存储 API 密钥", - "xpack.serverlessSearch.apiKey.stepTwoDescription": "用于特定项目的唯一标识符。", - "xpack.serverlessSearch.apiKey.stepTwoTitle": "存储您的唯一云 ID", "xpack.serverlessSearch.apiKey.title": "存储您的 API 密钥和云 ID", "xpack.serverlessSearch.apiKey.userFieldHelpText": "创建 API 密钥的用户的 ID。", "xpack.serverlessSearch.apiKey.userFieldLabel": "用户", @@ -35741,7 +35427,6 @@ "xpack.stackAlerts.geoContainment.boundariesFetchError": "无法提取跟踪限制边界,错误:{error}", "xpack.stackAlerts.geoContainment.entityContainmentFetchError": "无法提取实体限制,错误:{error}", "xpack.stackAlerts.geoContainment.noBoundariesError": "找不到跟踪限制边界。确保索引“{index}”包含文档。", - "xpack.stackAlerts.geoContainment.noGeoFieldInIndexPattern.message": "数据视图不包含任何允许的地理空间字段。必须具有一个类型 {geoFields}。", "xpack.stackAlerts.indexThreshold.alertTypeContextSubjectTitle": "告警 {name} 组 {group} 达到阈值", "xpack.stackAlerts.indexThreshold.alertTypeRecoveryContextSubjectTitle": "告警 {name} 组 {group} 已恢复", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "指定的 thresholdComparator 无效:{comparator}", @@ -35832,12 +35517,8 @@ "xpack.stackAlerts.geoContainment.actionVariableContextFromEntityDocumentIdLabel": "所包含实体文档的 ID", "xpack.stackAlerts.geoContainment.actionVariableContextFromEntityLocationLabel": "实体的位置", "xpack.stackAlerts.geoContainment.alertTypeTitle": "跟踪限制", - "xpack.stackAlerts.geoContainment.boundaryNameSelect": "选择边界名称", "xpack.stackAlerts.geoContainment.boundaryNameSelectLabel": "可人工读取的边界名称(可选)", "xpack.stackAlerts.geoContainment.descriptionText": "实体包含在地理边界内时告警。", - "xpack.stackAlerts.geoContainment.entityByLabel": "依据", - "xpack.stackAlerts.geoContainment.entityIndexLabel": "索引", - "xpack.stackAlerts.geoContainment.entityIndexSelect": "选择数据视图和地理点字段", "xpack.stackAlerts.geoContainment.error.requiredBoundaryGeoFieldText": "边界地理字段必填。", "xpack.stackAlerts.geoContainment.error.requiredBoundaryIndexTitleText": "边界数据视图标题必填。", "xpack.stackAlerts.geoContainment.error.requiredBoundaryTypeText": "“边界类型”必填。", @@ -35845,25 +35526,12 @@ "xpack.stackAlerts.geoContainment.error.requiredEntityText": "“实体”必填。", "xpack.stackAlerts.geoContainment.error.requiredGeoFieldText": "“地理”字段必填。", "xpack.stackAlerts.geoContainment.error.requiredIndexTitleText": "需要数据视图。", - "xpack.stackAlerts.geoContainment.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", "xpack.stackAlerts.geoContainment.geofieldLabel": "地理空间字段", - "xpack.stackAlerts.geoContainment.indexLabel": "索引", - "xpack.stackAlerts.geoContainment.indexPatternSelectLabel": "数据视图", - "xpack.stackAlerts.geoContainment.indexPatternSelectPlaceholder": "选择数据视图", - "xpack.stackAlerts.geoContainment.noIndexPattern.doThisLinkTextDescription": "创建数据视图。", - "xpack.stackAlerts.geoContainment.noIndexPattern.doThisPrefixDescription": "您将需要 ", - "xpack.stackAlerts.geoContainment.noIndexPattern.getStartedLinkText": "开始使用一些样例数据集。", - "xpack.stackAlerts.geoContainment.noIndexPattern.hintDescription": "没有任何数据?", - "xpack.stackAlerts.geoContainment.noIndexPattern.messageTitle": "找不到任何数据视图", "xpack.stackAlerts.geoContainment.notGeoContained": "不再包含", - "xpack.stackAlerts.geoContainment.selectBoundaryIndex": "选择边界", - "xpack.stackAlerts.geoContainment.selectEntity": "选择实体", "xpack.stackAlerts.geoContainment.selectGeoLabel": "选择地理字段", - "xpack.stackAlerts.geoContainment.selectLabel": "选择地理字段", "xpack.stackAlerts.geoContainment.selectTimeLabel": "选择时间字段", "xpack.stackAlerts.geoContainment.timeFieldLabel": "时间字段", "xpack.stackAlerts.geoContainment.topHitsSplitFieldSelectPlaceholder": "选择实体字段", - "xpack.stackAlerts.geoContainment.ui.expressionPopover.closePopoverLabel": "关闭", "xpack.stackAlerts.indexThreshold.actionGroupThresholdMetTitle": "已达到阈值", "xpack.stackAlerts.indexThreshold.actionVariableContextConditionsLabel": "描述阈值比较运算符和阈值的字符串", "xpack.stackAlerts.indexThreshold.actionVariableContextDateLabel": "告警超过阈值的日期。", @@ -37703,26 +37371,19 @@ "xpack.transform.alertTypes.transformHealth.healthCheckRecoveryMessage": "{count, plural, other {转换}} {transformsString}{count, plural, other {有}}运行正常。", "xpack.transform.alertTypes.transformHealth.notStartedMessage": "{count, plural, other {转换}} {transformsString}{count, plural, other {有}}未启动。", "xpack.transform.alertTypes.transformHealth.notStartedRecoveryMessage": "{count, plural, other {转换}} {transformsString}{count, plural, other {有}}已启动。", - "xpack.transform.app.deniedPrivilegeDescription": "要使用“转换”部分,必须具有{privilegesCount, plural, other {以下集群权限}}:{missingPrivileges}。", "xpack.transform.capability.pleaseContactAdministratorTooltip": "{message}请联系您的管理员。", "xpack.transform.clone.noDataViewErrorPromptText": "无法克隆转换 {transformId}。对于 {dataViewTitle},不存在数据视图。", "xpack.transform.danglingTasksError": "{count} 个{count, plural, other {转换}}缺少配置详情:[{transformIds}] 无法将{count, plural, other {其}}恢复,应予以删除。", "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage": "删除数据视图 {destinationIndex} 时出错", - "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage": "删除数据视图 {destinationIndex} 的请求已确认。", "xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "删除目标索引 {destinationIndex} 时发生错误", - "xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage": "删除目标索引 {destinationIndex} 的请求已确认。", "xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage": "检查数据视图 {dataView} 是否存在时发生错误:{error}", "xpack.transform.edit.noDataViewErrorPromptText": "无法获取转换 {transformId} 的数据视图。对于 {dataViewTitle},不存在数据视图。", "xpack.transform.forceDeleteTransformMessage": "删除 {count} {count, plural, other {转换}}", "xpack.transform.managedTransformsWarningCallout": "{count, plural, other {至少一个此类转换}}由 Elastic 预配置;{action} {count, plural, other {这些转换}}可能会影响该产品的其他部分。", "xpack.transform.multiTransformActionsMenu.transformsCount": "已选定 {count} 个{count, plural, other {转换}}", "xpack.transform.stepCreateForm.createDataViewErrorMessage": "创建 Kibana 数据视图 {dataViewName} 时发生错误:", - "xpack.transform.stepCreateForm.createDataViewSuccessMessage": "已成功创建 Kibana 数据视图 {dataViewName}。", "xpack.transform.stepCreateForm.createTransformErrorMessage": "创建转换 {transformId} 时出错:", - "xpack.transform.stepCreateForm.createTransformSuccessMessage": "创建转换 {transformId} 的请求已确认。", "xpack.transform.stepCreateForm.duplicateDataViewErrorMessage": "创建 Kibana 数据视图 {dataViewName} 时发生错误:数据视图已存在。", - "xpack.transform.stepCreateForm.startTransformErrorMessage": "启动转换 {transformId} 时发生错误:", - "xpack.transform.stepCreateForm.startTransformSuccessMessage": "启动转换 {transformId} 的请求已确认。", "xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar": "无效查询:{queryErrorMessage}", "xpack.transform.stepDefineForm.queryPlaceholderKql": "例如,{example}", "xpack.transform.stepDefineForm.queryPlaceholderLucene": "例如,{example}", @@ -37734,41 +37395,30 @@ "xpack.transform.stepDetailsForm.retentionPolicyMaxAgePlaceholderText": "max_age,例如 {exampleValue}", "xpack.transform.transformForm.sizeNotationPlaceholder": "示例:{example1}、{example2}、{example3}、{example4}", "xpack.transform.transformList.alertingRules.tooltipContent": "转换具有 {rulesCount} 个关联的告警{rulesCount, plural, other { 规则}}", - "xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage": "已成功删除 {count} 个目标数据{count, plural, other {视图}}。", - "xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "已成功删除 {count} 个目标数据{count, plural, other {索引}}。", "xpack.transform.transformList.bulkDeleteModalTitle": "删除 {count} 个 {count, plural, other {转换}}?", - "xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "已成功删除 {count} 个{count, plural, other {转换}}。", "xpack.transform.transformList.bulkReauthorizeModalTitle": "重新授权 {count} 个{count, plural, other {转换}}?", "xpack.transform.transformList.bulkResetModalTitle": "重置 {count} 个{count, plural, other {转换}}?", - "xpack.transform.transformList.bulkResetTransformSuccessMessage": "已成功重置 {count} 个{count, plural, other {转换}}。", "xpack.transform.transformList.bulkStartModalTitle": "启动 {count} 个{count, plural, other {转换}}?", "xpack.transform.transformList.bulkStopModalTitle": "停止 {count} 个{count, plural, other {转换}}?", "xpack.transform.transformList.cannotRestartCompleteBatchTransformToolTip": "{transformId} 为已完成批量转换,无法重新启动。", "xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformToolTip": "{transformId} 为已完成的批量转换,无法安排其立即处理数据。", "xpack.transform.transformList.deleteModalTitle": "删除 {transformId}?", "xpack.transform.transformList.deleteTransformErrorMessage": "删除转换 {transformId} 时发生错误", - "xpack.transform.transformList.deleteTransformSuccessMessage": "删除转换 {transformId} 的请求已确认。", "xpack.transform.transformList.editFlyoutFormPlaceholderText": "默认值:{defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "编辑 {transformId}", - "xpack.transform.transformList.editTransformSuccessMessage": "转换 {transformId} 已更新。", "xpack.transform.transformList.reauthorizeModalTitle": "重新授权 {transformId}?", "xpack.transform.transformList.reauthorizeTransformErrorMessage": "重新授权转换 {transformId} 时出错", - "xpack.transform.transformList.reauthorizeTransformSuccessMessage": "重新授权转换 {transformId} 的请求已确认。", "xpack.transform.transformList.resetModalTitle": "重置 {transformId}?", "xpack.transform.transformList.resetTransformErrorMessage": "重置转换 {transformId} 时出错", - "xpack.transform.transformList.resetTransformSuccessMessage": "重置转换 {transformId} 的请求已确认。", "xpack.transform.transformList.rowCollapse": "隐藏 {transformId} 的详情", "xpack.transform.transformList.rowExpand": "显示 {transformId} 的详情", "xpack.transform.transformList.scheduleNowTransformErrorMessage": "计划转换 {transformId} 以立即处理数据时出错。", - "xpack.transform.transformList.scheduleNowTransformSuccessMessage": "计划转换 {transformId} 以立即处理数据的请求已确认。", "xpack.transform.transformList.startedTransformToolTip": "{transformId} 已启动。", "xpack.transform.transformList.startModalTitle": "启动 {transformId}?", "xpack.transform.transformList.startTransformErrorMessage": "启动转换 {transformId} 时发生错误", - "xpack.transform.transformList.startTransformSuccessMessage": "启动转换 {transformId} 的请求已确认。", "xpack.transform.transformList.stopModalTitle": "停止 {transformId}?", "xpack.transform.transformList.stoppedTransformToolTip": "{transformId} 已停止。", "xpack.transform.transformList.stopTransformErrorMessage": "停止数据帧转换 {transformId} 时发生错误", - "xpack.transform.transformList.stopTransformSuccessMessage": "停止数据帧转换 {transformId} 的请求已确认。", "xpack.transform.transformList.unauthorizedTransformsCallout.insufficientPermissionsMsg": "{unauthorizedCnt, plural, other {已创建 # 个转换,但权限不足。}}", "xpack.transform.transformList.unauthorizedTransformsCallout.reauthorizeMsg": "重新授权以启动{unauthorizedCnt, plural, other {# 个转换}}。", "xpack.transform.transformNodes.noTransformNodesCallOutBody": "您将无法创建或运行转换。{learnMoreLink}", @@ -37814,9 +37464,6 @@ "xpack.transform.alertTypes.transformHealth.notStartedCheckName": "转换未启动", "xpack.transform.alertTypes.transformHealth.testsConfigTransforms.errorMessage": "必须至少选择一次运行状况检查", "xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel": "启用", - "xpack.transform.app.checkingPrivilegesDescription": "正在检查权限……", - "xpack.transform.app.checkingPrivilegesErrorMessage": "从服务器获取用户权限时出错", - "xpack.transform.app.deniedPrivilegeTitle": "您缺少集群权限", "xpack.transform.appName": "转换", "xpack.transform.appTitle": "转换", "xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip": "您无权创建转换告警规则。", @@ -37862,7 +37509,6 @@ "xpack.transform.licenseCheckErrorMessage": "许可证检查失败", "xpack.transform.list.emptyPromptButtonText": "创建您的首个转换", "xpack.transform.list.emptyPromptTitle": "找不到转换", - "xpack.transform.list.errorPromptTitle": "获取转换列表时发生错误", "xpack.transform.mode": "模式", "xpack.transform.modeFilter": "模式", "xpack.transform.models.transformService.allOtherRequestsCancelledDescription": "所有其他请求已取消。", @@ -38094,7 +37740,6 @@ "xpack.transform.transformList.editFlyoutUpdateButtonText": "更新", "xpack.transform.transformList.editManagedTransformsDescription": "正在编辑", "xpack.transform.transformList.editTransformGenericErrorMessage": "调用用于更新转换的 API 终端时发生错误。", - "xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "检查用户是否可以删除目标索引时发生错误", "xpack.transform.transformList.managedBadgeLabel": "托管", "xpack.transform.transformList.managedBadgeTooltip": "此转换由 Elastic 预配置和管理;该产品的其他部分可能依赖于其行为。", "xpack.transform.transformList.needsReauthorizationBadge.contactAdminTooltip": "请联系管理员请求所需权限。", @@ -39761,7 +39406,6 @@ "eventAnnotation.group.args.annotationConfigs.ignoreGlobalFilters.help": "进行切换以忽略标注的全局筛选", "eventAnnotation.group.args.annotationGroups": "标注组", "eventAnnotation.group.description": "事件标注组", - "eventAnnotation.listingViewTitle": "标注组", "eventAnnotation.manualAnnotation.args.color": "线条的颜色", "eventAnnotation.manualAnnotation.args.icon": "用于标注线条的可选图标", "eventAnnotation.manualAnnotation.args.id": "标注的 ID", diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index c65ac1eff0347..4678324fdbef4 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -535,10 +535,10 @@ Props definition: interface GroupByExpressionProps { groupBy: string; termSize?: number; - termField?: string; + termField?: string | string[]; errors: { [key: string]: string[] }; onChangeSelectedTermSize: (selectedTermSize?: number) => void; - onChangeSelectedTermField: (selectedTermField?: string) => void; + onChangeSelectedTermField: (selectedTermField?: string | string[]) => void; onChangeSelectedGroupBy: (selectedGroupBy?: string) => void; fields: Record; customGroupByTypes?: { @@ -555,9 +555,9 @@ interface GroupByExpressionProps { | termSize | Selected term size that will be set as the alert type property. | | termField | Selected term field that will be set as the alert type property. | | errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `termSize` and `termField`. | -| onChangeSelectedTermSize | Event handler that will be excuted if selected term size is changed. | -| onChangeSelectedTermField | Event handler that will be excuted if selected term field is changed. | -| onChangeSelectedGroupBy | Event handler that will be excuted if selected group by is changed. | +| onChangeSelectedTermSize | Event handler that will be executed if selected term size is changed. | +| onChangeSelectedTermField | Event handler that will be executed if selected term field is changed. | +| onChangeSelectedGroupBy | Event handler that will be executed if selected group by is changed. | | fields | Fields list with options for the `termField` dropdown. | | customGroupByTypes | (Optional) List of group by types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/group_by_types.ts`. | | popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | diff --git a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts index 42278057d124c..1925954ea7bb9 100644 --- a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts +++ b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts @@ -247,6 +247,67 @@ describe('buildAgg', () => { }); }); + it('should create correct aggregation when condition params are defined and timeSeries is defined and multi terms selected', async () => { + expect( + buildAggregation({ + timeSeries: { + timeField: 'time-field', + timeWindowSize: 5, + timeWindowUnit: 'm', + dateStart: '2021-04-22T15:19:31Z', + dateEnd: '2021-04-22T15:20:31Z', + interval: '1m', + }, + aggType: 'count', + aggField: undefined, + termField: ['the-term', 'second-term'], + termSize: 10, + condition: { + resultLimit: 1000, + conditionScript: `params.compareValue > 1`, + }, + }) + ).toEqual({ + groupAgg: { + multi_terms: { + size: 10, + terms: [{ field: 'the-term' }, { field: 'second-term' }], + }, + aggs: { + conditionSelector: { + bucket_selector: { + buckets_path: { + compareValue: '_count', + }, + script: `params.compareValue > 1`, + }, + }, + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + }, + }, + }, + groupAggCount: { + stats_bucket: { + buckets_path: 'groupAgg._count', + }, + }, + }); + }); + it('should create correct aggregation when condition params are defined and timeSeries is undefined', async () => { expect( buildAggregation({ diff --git a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts index 5362682e0b848..2c6ccac04c638 100644 --- a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts +++ b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts @@ -19,7 +19,7 @@ export interface BuildAggregationOpts { aggType: string; aggField?: string; termSize?: number; - termField?: string; + termField?: string | string[]; topHitsSize?: number; condition?: { resultLimit?: number; @@ -32,7 +32,7 @@ export const BUCKET_SELECTOR_FIELD = `params.${BUCKET_SELECTOR_PATH_NAME}`; export const DEFAULT_GROUPS = 100; export const isCountAggregation = (aggType: string) => aggType === 'count'; -export const isGroupAggregation = (termField?: string) => !!termField; +export const isGroupAggregation = (termField?: string | string[]) => !!termField; export const buildAggregation = ({ timeSeries, @@ -48,6 +48,7 @@ export const buildAggregation = ({ }; const isCountAgg = isCountAggregation(aggType); const isGroupAgg = isGroupAggregation(termField); + const isMultiTerms = Array.isArray(termField); const isDateAgg = !!timeSeries; const includeConditionInQuery = !!condition; @@ -82,10 +83,19 @@ export const buildAggregation = ({ if (isGroupAgg) { aggParent.aggs = { groupAgg: { - terms: { - field: termField, - size: terms, - }, + ...(isMultiTerms + ? { + multi_terms: { + terms: termField.map((field) => ({ field })), + size: terms, + }, + } + : { + terms: { + field: termField, + size: terms, + }, + }), }, ...(includeConditionInQuery ? { diff --git a/x-pack/plugins/triggers_actions_ui/kibana.jsonc b/x-pack/plugins/triggers_actions_ui/kibana.jsonc index 653c105772711..eb660c0bbe383 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.jsonc +++ b/x-pack/plugins/triggers_actions_ui/kibana.jsonc @@ -24,6 +24,7 @@ "actions", "dashboard", "licensing", + "expressions" ], "optionalPlugins": [ "cloud", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 263c13ccd4981..a8d58e2e627f3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -30,6 +30,7 @@ import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { ruleDetailsRoute } from '@kbn/rule-data-utils'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { DashboardStart } from '@kbn/dashboard-plugin/public'; +import { ExpressionsStart } from '@kbn/expressions-plugin/public'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; import { ActionTypeRegistryContract, @@ -70,6 +71,7 @@ export interface TriggersAndActionsUiServices extends CoreStart { theme$: Observable; unifiedSearch: UnifiedSearchPublicPluginStart; licensing: LicensingPluginStart; + expressions: ExpressionsStart; } export const renderApp = (deps: TriggersAndActionsUiServices) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss index 85d161d5c978a..d554b876faa90 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss @@ -15,9 +15,9 @@ padding-left: $euiSizeL; } -.actAccordionActionForm .euiAccordion__iconButton { +.actAccordionActionForm .euiAccordion__arrow { transform: translateX($euiSizeM) rotate(0deg) !important; } -.actAccordionActionForm .euiAccordion__iconButton.euiAccordion__iconButton-isOpen { +.actAccordionActionForm .euiAccordion__arrow[aria-expanded='true'] { transform: translateX($euiSizeM) rotate(90deg) !important; } \ No newline at end of file diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts index 6e2465e58c2fd..a2f3e1eb80392 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts @@ -5,7 +5,7 @@ * 2.0. */ import { AsApiContract } from '@kbn/actions-plugin/common'; -import { RuleAggregationFormattedResult } from '@kbn/alerting-plugin/common'; +import { AggregateRulesResponseBody } from '@kbn/alerting-plugin/common/routes/rule/apis/aggregate'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; import { mapFiltersToKql } from './map_filters_to_kql'; import { @@ -14,6 +14,7 @@ import { rewriteBodyRes, rewriteTagsBodyRes, GetRuleTagsResponse, + AggregateRulesResponse, } from './aggregate_helpers'; export async function loadRuleTags({ @@ -43,7 +44,7 @@ export async function loadRuleAggregations({ ruleExecutionStatusesFilter, ruleStatusesFilter, tagsFilter, -}: LoadRuleAggregationsProps): Promise { +}: LoadRuleAggregationsProps): Promise { const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, @@ -51,7 +52,7 @@ export async function loadRuleAggregations({ ruleStatusesFilter, tagsFilter, }); - const res = await http.post>( + const res = await http.post( `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, { body: JSON.stringify({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts index 61fff1e95a451..d093649f73740 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts @@ -7,19 +7,34 @@ import { HttpSetup } from '@kbn/core/public'; import { RewriteRequestCase } from '@kbn/actions-plugin/common'; -import { RuleAggregationFormattedResult } from '@kbn/alerting-plugin/common'; +import { AggregateRulesResponseBody } from '@kbn/alerting-plugin/common/routes/rule/apis/aggregate'; import { RuleStatus } from '../../../types'; -export const rewriteBodyRes: RewriteRequestCase = ({ +export interface AggregateRulesResponse { + ruleExecutionStatus: Record; + ruleLastRunOutcome: Record; + ruleEnabledStatus: { + enabled: number; + disabled: number; + }; + ruleMutedStatus: { + muted: number; + unmuted: number; + }; + ruleSnoozedStatus: { + snoozed: number; + }; + ruleTags: string[]; +} + +export const rewriteBodyRes = ({ rule_execution_status: ruleExecutionStatus, rule_last_run_outcome: ruleLastRunOutcome, rule_enabled_status: ruleEnabledStatus, rule_muted_status: ruleMutedStatus, rule_snoozed_status: ruleSnoozedStatus, rule_tags: ruleTags, - ...rest -}: any) => ({ - ...rest, +}: AggregateRulesResponseBody): AggregateRulesResponse => ({ ruleExecutionStatus, ruleEnabledStatus, ruleMutedStatus, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts index 20d1fc9281b48..23941ae36ccc5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts @@ -4,10 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { AsApiContract } from '@kbn/actions-plugin/common'; -import { RuleAggregationFormattedResult } from '@kbn/alerting-plugin/common'; +import { AggregateRulesResponseBody } from '@kbn/alerting-plugin/common/routes/rule/apis/aggregate'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; -import { LoadRuleAggregationsProps, rewriteBodyRes } from './aggregate_helpers'; +import { + AggregateRulesResponse, + LoadRuleAggregationsProps, + rewriteBodyRes, +} from './aggregate_helpers'; import { mapFiltersToKueryNode } from './map_filters_to_kuery_node'; export async function loadRuleAggregationsWithKueryFilter({ @@ -18,7 +21,7 @@ export async function loadRuleAggregationsWithKueryFilter({ ruleExecutionStatusesFilter, ruleStatusesFilter, tagsFilter, -}: LoadRuleAggregationsProps): Promise { +}: LoadRuleAggregationsProps): Promise { const filtersKueryNode = mapFiltersToKueryNode({ typesFilter, actionTypesFilter, @@ -28,7 +31,7 @@ export async function loadRuleAggregationsWithKueryFilter({ searchText, }); - const res = await http.post>( + const res = await http.post( `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, { body: JSON.stringify({ @@ -37,5 +40,6 @@ export async function loadRuleAggregationsWithKueryFilter({ }), } ); + return rewriteBodyRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.tsx index f64c531519d5e..e13284f1048b2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.tsx @@ -22,26 +22,11 @@ import numeral from '@elastic/numeral'; import { ReactNode } from 'react'; import React from 'react'; -import { euiStyled, EuiTheme } from '@kbn/kibana-react-plugin/common'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { isEmpty } from 'lodash'; import { GetInspectQuery } from '../../../../../../types'; import * as i18n from './translations'; -const DescriptionListStyled = euiStyled(EuiDescriptionList)` - @media only screen and (min-width: ${({ theme }: { theme: EuiTheme }) => - theme.eui.euiBreakpoints.s}) { - .euiDescriptionList__title { - width: 30% !important; - } - - .euiDescriptionList__description { - width: 70% !important; - } - } -`; - -DescriptionListStyled.displayName = 'DescriptionListStyled'; - export interface ModalInspectProps { closeModal: () => void; getInspectQuery: GetInspectQuery; @@ -154,7 +139,11 @@ const ModalInspectQueryComponent = ({ closeModal, getInspectQuery, title }: Moda content: ( <> - + ), }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_data_grid.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_data_grid.tsx index 43199b924ba2d..08c9a45358f3e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_data_grid.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_data_grid.tsx @@ -66,6 +66,7 @@ export interface EventLogDataGrid { onFlyoutOpen?: (runLog: IExecutionLog) => void; setVisibleColumns: (visibleColumns: string[]) => void; setSortingColumns: (sortingColumns: EuiDataGridSorting['columns']) => void; + getRuleDetailsRoute?: (ruleId: string) => string; } export const numTriggeredActionsDisplay = i18n.translate( @@ -167,6 +168,7 @@ export const EventLogDataGrid = (props: EventLogDataGrid) => { onChangeItemsPerPage, onChangePage, onFlyoutOpen, + getRuleDetailsRoute, } = props; const { euiTheme } = useEuiTheme(); @@ -343,6 +345,7 @@ export const EventLogDataGrid = (props: EventLogDataGrid) => { ruleId={ruleId} spaceIds={spaceIds} useExecutionStatus={isRuleUsingExecutionStatus} + getRuleDetailsRoute={getRuleDetailsRoute} />
    diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_list_cell_renderer.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_list_cell_renderer.tsx index f11c38a9dfe11..a35e963b6ce11 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_list_cell_renderer.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/event_log/event_log_list_cell_renderer.tsx @@ -10,7 +10,7 @@ import moment from 'moment'; import { EuiLink } from '@elastic/eui'; import { RuleAlertingOutcome } from '@kbn/alerting-plugin/common'; import { useHistory } from 'react-router-dom'; -import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; +import { getRuleDetailsRoute as internalGetRuleDetailsRoute } from '@kbn/rule-data-utils'; import { formatRuleAlertCount } from '../../../../../common/lib/format_rule_alert_count'; import { useKibana, useSpacesData } from '../../../../../common/lib/kibana'; import { EventLogListStatus } from './event_log_list_status'; @@ -36,6 +36,7 @@ interface EventLogListCellRendererProps { ruleId?: string; spaceIds?: string[]; useExecutionStatus?: boolean; + getRuleDetailsRoute?: (ruleId: string) => string; } export const EventLogListCellRenderer = (props: EventLogListCellRendererProps) => { @@ -47,6 +48,7 @@ export const EventLogListCellRenderer = (props: EventLogListCellRendererProps) = ruleId, spaceIds, useExecutionStatus = true, + getRuleDetailsRoute, } = props; const spacesData = useSpacesData(); const { http } = useKibana().services; @@ -66,7 +68,9 @@ export const EventLogListCellRenderer = (props: EventLogListCellRendererProps) = const ruleNamePathname = useMemo(() => { if (!ruleId) return ''; - const ruleRoute = getRuleDetailsRoute(ruleId); + const ruleRoute = getRuleDetailsRoute + ? getRuleDetailsRoute(ruleId) + : internalGetRuleDetailsRoute(ruleId); if (ruleOnDifferentSpace) { const [linkedSpaceId] = spaceIds ?? []; @@ -82,7 +86,7 @@ export const EventLogListCellRenderer = (props: EventLogListCellRendererProps) = return newPathname; } return ruleRoute; - }, [ruleId, ruleOnDifferentSpace, history, activeSpace, http, spaceIds]); + }, [ruleId, ruleOnDifferentSpace, history, activeSpace, http, spaceIds, getRuleDetailsRoute]); const onClickRuleName = useCallback(() => { if (!ruleId) return; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/global_rule_event_log_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/global_rule_event_log_list.tsx index bb939e3e26180..c7d7f442beab1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/global_rule_event_log_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/global_rule_event_log_list.tsx @@ -16,6 +16,7 @@ export interface GlobalRuleEventLogListProps { setHeaderActions?: RuleEventLogListCommonProps['setHeaderActions']; localStorageKey?: RuleEventLogListCommonProps['localStorageKey']; filteredRuleTypes?: RuleEventLogListCommonProps['filteredRuleTypes']; + getRuleDetailsRoute?: RuleEventLogListCommonProps['getRuleDetailsRoute']; } const GLOBAL_EVENT_LOG_LIST_STORAGE_KEY = @@ -31,7 +32,7 @@ const REFRESH_TOKEN = { }; export const GlobalRuleEventLogList = (props: GlobalRuleEventLogListProps) => { - const { setHeaderActions, localStorageKey, filteredRuleTypes } = props; + const { setHeaderActions, localStorageKey, filteredRuleTypes, getRuleDetailsRoute } = props; const { spaces } = useKibana().services; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -51,6 +52,7 @@ export const GlobalRuleEventLogList = (props: GlobalRuleEventLogListProps) => { localStorageKey={localStorageKey || GLOBAL_EVENT_LOG_LIST_STORAGE_KEY} filteredRuleTypes={filteredRuleTypes} setHeaderActions={setHeaderActions} + getRuleDetailsRoute={getRuleDetailsRoute} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index d672fc3287fcd..6bae4f720c032 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -14,7 +14,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiBadge, - EuiPageContentBody_Deprecated as EuiPageContentBody, + EuiPageSection, EuiCallOut, EuiSpacer, EuiButtonEmpty, @@ -426,8 +426,7 @@ export const RuleDetails: React.FunctionComponent = ({ , ]} /> - - + {rule.enabled && rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License ? ( @@ -519,7 +518,7 @@ export const RuleDetails: React.FunctionComponent = ({ /> - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx index 32db0a686bff5..73eb9f2c5c536 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx @@ -95,6 +95,7 @@ export interface RuleEventLogListCommonProps { hasAllSpaceSwitch?: boolean; filteredRuleTypes?: string[]; setHeaderActions?: (components?: React.ReactNode[]) => void; + getRuleDetailsRoute?: (ruleId: string) => string; } export type RuleEventLogListTableProps = @@ -116,6 +117,7 @@ export const RuleEventLogListTable = ( hasAllSpaceSwitch = false, setHeaderActions, filteredRuleTypes, + getRuleDetailsRoute, } = props; const { uiSettings, notifications } = useKibana().services; @@ -629,6 +631,7 @@ export const RuleEventLogListTable = ( onFlyoutOpen={onFlyoutOpen} setVisibleColumns={setVisibleColumns} setSortingColumns={setSortingColumns} + getRuleDetailsRoute={getRuleDetailsRoute} /> ); @@ -668,7 +671,7 @@ export const RuleEventLogListTable = ( - + { - it('renders with builtin group by types', () => { + configure({ testIdAttribute: 'data-test-subj' }); + it('renders with builtin group by types', async () => { const onChangeSelectedTermField = jest.fn(); const onChangeSelectedGroupBy = jest.fn(); const onChangeSelectedTermSize = jest.fn(); - const wrapper = shallow( - + render( + + + ); - expect(wrapper.find('[data-test-subj="overExpressionSelect"]')).toMatchInlineSnapshot(` - - `); + fireEvent.click(screen.getByTestId('groupByExpression')); + + expect(await screen.findByRole('dialog')).toBeInTheDocument(); + + expect(screen.getByRole('option', { name: 'all documents' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'top' })).toBeInTheDocument(); }); - it('renders with aggregation type fields', () => { + it('clears selected agg field if fields does not contain current selection', async () => { const onChangeSelectedTermField = jest.fn(); - const onChangeSelectedGroupBy = jest.fn(); - const onChangeSelectedTermSize = jest.fn(); - const wrapper = shallow( - + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} + /> + ); + expect(onChangeSelectedTermField).toHaveBeenCalledTimes(1); + expect(onChangeSelectedTermField).toHaveBeenCalledWith(undefined); + }); - expect(wrapper.find('[data-test-subj="fieldsExpressionSelect"]')).toMatchInlineSnapshot(` - { + const onChangeSelectedTermField = jest.fn(); + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} /> - `); + + ); + expect(onChangeSelectedTermField).toHaveBeenCalledTimes(1); + expect(onChangeSelectedTermField).toHaveBeenCalledWith(undefined); }); - it('renders with default aggreagation type preselected if no aggType was set', () => { + it('clears selected agg field if groupBy field is all', async () => { const onChangeSelectedTermField = jest.fn(); - const onChangeSelectedGroupBy = jest.fn(); - const onChangeSelectedTermSize = jest.fn(); - const wrapper = shallow( - + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} + /> + ); - wrapper.simulate('click'); - expect(wrapper.find('[value="all"]').length > 0).toBeTruthy(); - expect( - wrapper.contains( - { + const onChangeSelectedTermField = jest.fn(); + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} /> - ) - ).toBeTruthy(); + + ); + + expect(onChangeSelectedTermField).not.toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId('groupByExpression')); + + expect(await screen.findByText(/You are in a dialog/)).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('comboBoxToggleListButton')); + + const option1 = screen.getByText('field1'); + expect(option1).toBeInTheDocument(); + fireEvent.click(option1); + expect(onChangeSelectedTermField).toHaveBeenCalledWith('field1'); + + const option2 = screen.getByText('field2'); + expect(option2).toBeInTheDocument(); + fireEvent.click(option2); + expect(onChangeSelectedTermField).toHaveBeenCalledWith('field2'); }); - it('clears selected agg field if fields does not contain current selection', async () => { + it('calls onChangeSelectedTermField when multiple termFields are selected', async () => { const onChangeSelectedTermField = jest.fn(); - const wrapper = mountWithIntl( - {}} - onChangeSelectedTermSize={() => {}} - onChangeSelectedTermField={onChangeSelectedTermField} - /> + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} + canSelectMultiTerms={true} + /> + ); + expect(onChangeSelectedTermField).not.toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId('groupByExpression')); + + expect(await screen.findByText(/You are in a dialog/)).toBeInTheDocument(); + + // dropdown is closed + expect(screen.queryByText('field1')).not.toBeInTheDocument(); + fireEvent.click(screen.getByTestId('comboBoxToggleListButton')); - await act(async () => { - await nextTick(); - wrapper.update(); - }); + // dropdown is open + expect(screen.getByText('field1')).toBeInTheDocument(); + fireEvent.click(screen.getByText('field1')); + expect(onChangeSelectedTermField).toHaveBeenCalledWith('field1'); - expect(onChangeSelectedTermField).toHaveBeenCalledWith(''); + fireEvent.click(screen.getByText('field2')); + expect(onChangeSelectedTermField).toHaveBeenCalledTimes(2); + expect(onChangeSelectedTermField).toHaveBeenCalledWith(['field1', 'field2']); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index 0819f7541d1cc..1a6901ffb5dec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; @@ -17,6 +17,8 @@ import { EuiFormRow, EuiSelect, EuiFieldNumber, + EuiComboBoxOptionOption, + EuiComboBox, } from '@elastic/eui'; import { builtInGroupByTypes } from '../constants'; import { FieldOption, GroupByType } from '../types'; @@ -24,18 +26,17 @@ import { ClosablePopoverTitle } from './components'; import { IErrorObject } from '../../types'; interface GroupByOverFieldOption { - text: string; - value: string; + label: string; } export interface GroupByExpressionProps { groupBy: string; errors: IErrorObject; onChangeSelectedTermSize: (selectedTermSize?: number) => void; - onChangeSelectedTermField: (selectedTermField?: string) => void; + onChangeSelectedTermField: (selectedTermField?: string | string[]) => void; onChangeSelectedGroupBy: (selectedGroupBy?: string) => void; fields: FieldOption[]; termSize?: number; - termField?: string; + termField?: string | string[]; customGroupByTypes?: { [key: string]: GroupByType; }; @@ -53,6 +54,7 @@ export interface GroupByExpressionProps { | 'rightUp' | 'rightDown'; display?: 'fullWidth' | 'inline'; + canSelectMultiTerms?: boolean; } export const GroupByExpression = ({ @@ -67,45 +69,55 @@ export const GroupByExpression = ({ termField, customGroupByTypes, popupPosition, + canSelectMultiTerms, }: GroupByExpressionProps) => { const groupByTypes = customGroupByTypes ?? builtInGroupByTypes; const [groupByPopoverOpen, setGroupByPopoverOpen] = useState(false); const MIN_TERM_SIZE = 1; const MAX_TERM_SIZE = 1000; - const firstFieldOption: GroupByOverFieldOption = { - text: i18n.translate( - 'xpack.triggersActionsUI.common.expressionItems.groupByType.timeFieldOptionLabel', - { - defaultMessage: 'Select a field', - } - ), - value: '', - }; - const availableFieldOptions: GroupByOverFieldOption[] = fields.reduce( - (options: GroupByOverFieldOption[], field: FieldOption) => { - if (groupByTypes[groupBy].validNormalizedTypes.includes(field.normalizedType)) { - options.push({ - text: field.name, - value: field.name, - }); - } - return options; - }, - [firstFieldOption] + const availableFieldOptions: GroupByOverFieldOption[] = useMemo( + () => + fields.reduce((options: GroupByOverFieldOption[], field: FieldOption) => { + if (groupByTypes[groupBy].validNormalizedTypes.includes(field.normalizedType)) { + options.push({ label: field.name }); + } + return options; + }, []), + [groupByTypes, fields, groupBy] ); + const initialTermFieldOptions = useMemo(() => { + let initialFields: string[] = []; + + if (!!termField) { + initialFields = Array.isArray(termField) ? termField : [termField]; + } + return initialFields.map((field: string) => ({ + label: field, + })); + }, [termField]); + + const [selectedTermsFieldsOptions, setSelectedTermsFieldsOptions] = + useState(initialTermFieldOptions); + + useEffect(() => { + if (groupBy === builtInGroupByTypes.all.value && selectedTermsFieldsOptions.length > 0) { + setSelectedTermsFieldsOptions([]); + onChangeSelectedTermField(undefined); + } + }, [selectedTermsFieldsOptions, groupBy, onChangeSelectedTermField]); + useEffect(() => { // if current field set doesn't contain selected field, clear selection - if ( - termField && - termField.length > 0 && - fields.length > 0 && - !fields.find((field: FieldOption) => field.name === termField) - ) { - onChangeSelectedTermField(''); + const hasUnknownField = selectedTermsFieldsOptions.some( + (fieldOption) => !fields.some((field) => field.name === fieldOption.label) + ); + if (hasUnknownField) { + setSelectedTermsFieldsOptions([]); + onChangeSelectedTermField(undefined); } - }, [termField, fields, onChangeSelectedTermField]); + }, [selectedTermsFieldsOptions, fields, onChangeSelectedTermField]); return ( 0))} /> } isOpen={groupByPopoverOpen} @@ -157,7 +169,7 @@ export const GroupByExpression = ({ /> - + - + 0} error={errors.termSize}> - - 0 && termField !== undefined} - error={errors.termField} - > - + 0} error={errors.termField}> + 0 && termField !== undefined} - onChange={(e) => { - onChangeSelectedTermField(e.target.value); + isInvalid={errors.termField.length > 0} + selectedOptions={selectedTermsFieldsOptions} + onChange={( + selectedOptions: Array> + ) => { + const selectedTermFields = selectedOptions.map((option) => option.label); + + const termsToSave = + Array.isArray(selectedTermFields) && selectedTermFields.length > 1 + ? selectedTermFields + : selectedTermFields[0]; + + onChangeSelectedTermField(termsToSave); + setSelectedTermsFieldsOptions(selectedOptions); }} options={availableFieldOptions} - onBlur={() => { - if (termField === undefined) { - onChangeSelectedTermField(''); - } - }} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_global_rule_event_log_list.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_global_rule_event_log_list.tsx index 546a5a31d9916..5f1ccaf07c8e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_global_rule_event_log_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_global_rule_event_log_list.tsx @@ -15,7 +15,7 @@ const queryClient = new QueryClient(); export const getGlobalRuleEventLogListLazy = (props: GlobalRuleEventLogListProps) => { return ( - ; + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_rule_event_log_list.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_event_log_list.tsx index 1d2e2cf580411..168f3b6d7ad38 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_rule_event_log_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_event_log_list.tsx @@ -20,7 +20,7 @@ export const getRuleEventLogListLazy = { const core = coreMock.createStart(); @@ -70,6 +71,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => { } as unknown as HTMLElement, theme$: themeServiceMock.createTheme$(), licensing: licensingPluginMock, + expressions: expressionsPluginMock.createStartContract(), } as TriggersAndActionsUiServices; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 3bc8ef4b9f75b..a13df899ec3cf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -116,6 +116,7 @@ export { getIndexOptions, firstFieldOption, getTimeFieldOptions, + getTimeOptions, GroupByExpression, COMPARATORS, connectorDeprecatedMessage, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 6f66f38e8541a..689d841b9c1f5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -26,6 +26,7 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import { triggersActionsRoute } from '@kbn/rule-data-utils'; import { DashboardStart } from '@kbn/dashboard-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; +import { ExpressionsStart } from '@kbn/expressions-plugin/public'; import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar'; import { TypeRegistry } from './application/type_registry'; @@ -161,6 +162,7 @@ interface PluginsStart { spaces?: SpacesPluginStart; navigateToApp: CoreStart['application']['navigateToApp']; features: FeaturesPluginStart; + expressions: ExpressionsStart; unifiedSearch: UnifiedSearchPublicPluginStart; licensing: LicensingPluginStart; } @@ -287,6 +289,7 @@ export class Plugin alertsTableConfigurationRegistry, kibanaFeatures, licensing: pluginsStart.licensing, + expressions: pluginsStart.expressions, }); }, }); diff --git a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts index 371ee93a6dfa0..e6a81ddbfd1ca 100644 --- a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts @@ -51,6 +51,7 @@ describe('createConfigRoute', () => { baseRoute: `/internal/triggers_actions_ui`, alertingConfig: () => ({ isUsingSecurity: true, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, }), getRulesClientWithRequest: () => mockRulesClient, @@ -64,7 +65,11 @@ describe('createConfigRoute', () => { expect(mockResponse.ok).toBeCalled(); expect(mockResponse.ok.mock.calls[0][0]).toEqual({ - body: { isUsingSecurity: true, minimumScheduleInterval: { value: '1m', enforce: false } }, + body: { + isUsingSecurity: true, + maxScheduledPerMinute: 10000, + minimumScheduleInterval: { value: '1m', enforce: false }, + }, }); }); @@ -80,6 +85,7 @@ describe('createConfigRoute', () => { baseRoute: `/internal/triggers_actions_ui`, alertingConfig: () => ({ isUsingSecurity: true, + maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, }), getRulesClientWithRequest: () => mockRulesClient, diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index 42e53b4f313b2..3bfe7239fac2c 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -54,6 +54,7 @@ "@kbn/core-ui-settings-common", "@kbn/dashboard-plugin", "@kbn/licensing-plugin", + "@kbn/expressions-plugin", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/uptime/common/config.ts b/x-pack/plugins/uptime/common/config.ts index fb4f94ed33807..238d53dfeebfa 100644 --- a/x-pack/plugins/uptime/common/config.ts +++ b/x-pack/plugins/uptime/common/config.ts @@ -7,22 +7,9 @@ import type { PluginConfigDescriptor } from '@kbn/core/server'; import { schema, TypeOf } from '@kbn/config-schema'; -import { sslSchema } from '@kbn/server-http-tools'; - -const serviceConfig = schema.object({ - username: schema.maybe(schema.string()), - password: schema.maybe(schema.string()), - manifestUrl: schema.maybe(schema.string()), - hosts: schema.maybe(schema.arrayOf(schema.string())), - syncInterval: schema.maybe(schema.string()), - tls: schema.maybe(sslSchema), - devUrl: schema.maybe(schema.string()), - showExperimentalLocations: schema.maybe(schema.boolean()), -}); const uptimeConfig = schema.object({ index: schema.maybe(schema.string()), - service: schema.maybe(serviceConfig), enabled: schema.boolean({ defaultValue: true }), }); diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap index 254b265e63ecd..4a4a5a5255cb3 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap @@ -150,7 +150,7 @@ exports[`PingListExpandedRow renders link to docs if body is not recorded but it data-type="row" >
    Response Body
    diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/status_details/__snapshots__/monitor_status.bar.test.tsx.snap b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/status_details/__snapshots__/monitor_status.bar.test.tsx.snap index cae8a771e1696..5ebde1d72dd0d 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/status_details/__snapshots__/monitor_status.bar.test.tsx.snap +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/status_details/__snapshots__/monitor_status.bar.test.tsx.snap @@ -23,47 +23,47 @@ Array [ }
    Overall availability
    0.00 %
    Url
    --
    Monitor ID
    Tags
    TLS Certificate , @@ -63,7 +63,7 @@ Array [ }
    TLS Certificate
    , diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap index 619096189b084..5ec1d2011e72e 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap @@ -6,7 +6,7 @@ exports[`MostRecentError component renders properly with mock data 1`] = ` data-type="row" >
    Most recent error (5 days ago)
    diff --git a/x-pack/plugins/uptime/public/plugin.ts b/x-pack/plugins/uptime/public/plugin.ts index 8162e6991d26f..27ba81c692723 100644 --- a/x-pack/plugins/uptime/public/plugin.ts +++ b/x-pack/plugins/uptime/public/plugin.ts @@ -226,7 +226,7 @@ export class UptimePlugin function registerUptimeRoutesWithNavigation(coreStart: CoreStart, plugins: ClientPluginsStart) { async function getUptimeSections() { - if (coreStart.application.capabilities.uptime.show) { + if (coreStart.application.capabilities.uptime?.show) { return [ { label: 'Uptime', diff --git a/x-pack/plugins/uptime/tsconfig.json b/x-pack/plugins/uptime/tsconfig.json index ddfe50db65940..dc93878a42464 100644 --- a/x-pack/plugins/uptime/tsconfig.json +++ b/x-pack/plugins/uptime/tsconfig.json @@ -15,7 +15,6 @@ "kbn_references": [ "@kbn/core", "@kbn/config-schema", - "@kbn/server-http-tools", "@kbn/i18n", "@kbn/fleet-plugin", "@kbn/alerting-plugin", diff --git a/x-pack/plugins/ux/kibana.jsonc b/x-pack/plugins/ux/kibana.jsonc index 26a2ab78a926a..22d912cbe4ab3 100644 --- a/x-pack/plugins/ux/kibana.jsonc +++ b/x-pack/plugins/ux/kibana.jsonc @@ -17,7 +17,6 @@ "observabilityShared", "observabilityAIAssistant", "embeddable", - "infra", "inspector", "apm" ], diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx index d334bd160a5cc..0b138e663f102 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx @@ -33,6 +33,7 @@ export function ResetPercentileZoom({
    Average page load duration
    , ] diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit_page/components/threshold_watch_edit/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit_page/components/threshold_watch_edit/threshold_watch_action_accordion.tsx index f162941461ce8..dd564f2b719e2 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit_page/components/threshold_watch_edit/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit_page/components/threshold_watch_edit/threshold_watch_action_accordion.tsx @@ -6,6 +6,7 @@ */ import React, { Fragment, useContext, useState } from 'react'; +import { css } from '@emotion/react'; import { EuiAccordion, @@ -20,6 +21,8 @@ import { EuiLink, EuiText, EuiSpacer, + useEuiTheme, + euiCanAnimate, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -75,6 +78,7 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ settings, actionErrors, }) => { + const { euiTheme } = useEuiTheme(); const { links: { watchActionsConfigurationMap }, toasts, @@ -96,8 +100,15 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ initialIsOpen={action.isNew || hasErrors} // If an action contains errors in edit mode, we want the accordion open so the user is aware key={action.id} id={action.id} - className="euiAccordionForm" - buttonContentClassName="euiAccordionForm__button" + borders="horizontal" + buttonProps={{ + paddingSize: 'm', + css: css` + &:hover { + text-decoration: none; + } + `, + }} data-test-subj="watchActionAccordion" buttonContent={ @@ -105,7 +116,7 @@ export const WatchActionsAccordion: React.FunctionComponent = ({
    - +
    {action.typeName}
    @@ -115,7 +126,18 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/canvas/default'); - await common.navigateToApp('canvas'); + await canvas.goToListingPage(); }); it('loads workpads', async function () { diff --git a/x-pack/test/accessibility/apps/enterprise_search.ts b/x-pack/test/accessibility/apps/enterprise_search.ts index 1e526695485d3..b610130d99e0e 100644 --- a/x-pack/test/accessibility/apps/enterprise_search.ts +++ b/x-pack/test/accessibility/apps/enterprise_search.ts @@ -152,15 +152,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); }); - describe('ESRE', () => { + describe('AI Search', () => { before(async () => { - await common.navigateToApp('enterprise_search/esre'); + await common.navigateToApp('enterprise_search/ai_search'); }); - it('loads ESRE page', async function () { + it('loads AI Search page', async function () { await retry.waitFor( - 'esre header description', - async () => await testSubjects.exists('esre-description-text') + 'ai search page header description', + async () => await testSubjects.exists('ai-search-description-text') ); await a11y.testAppSnapshot(); }); diff --git a/x-pack/test/accessibility/apps/security_solution.ts b/x-pack/test/accessibility/apps/security_solution.ts index ba7d22fd2d39d..cda47540f5d0f 100644 --- a/x-pack/test/accessibility/apps/security_solution.ts +++ b/x-pack/test/accessibility/apps/security_solution.ts @@ -14,7 +14,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const toasts = getService('toasts'); const testSubjects = getService('testSubjects'); - describe('Security Solution Accessibility', () => { + // Failing: See https://github.com/elastic/kibana/issues/166102 + // Failing: See https://github.com/elastic/kibana/issues/166105 + describe.skip('Security Solution Accessibility', () => { before(async () => { await security.testUser.setRoles(['superuser'], { skipBrowserRefresh: true }); await common.navigateToApp('security'); diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 3ba6caf872159..823ef92ea32ca 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -28,6 +28,7 @@ interface CreateTestConfigOptions { reportName?: string; useDedicatedTaskRunner: boolean; enableFooterInEmail?: boolean; + maxScheduledPerMinute?: number; } // test.not-enabled is specifically not enabled @@ -82,6 +83,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) reportName = undefined, useDedicatedTaskRunner, enableFooterInEmail = true, + maxScheduledPerMinute, } = options; return async ({ readConfigFile }: FtrConfigProviderContext) => { @@ -151,6 +153,11 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ? [`--xpack.actions.email.domain_allowlist=${JSON.stringify(emailDomainsAllowed)}`] : []; + const maxScheduledPerMinuteSettings = + typeof maxScheduledPerMinute === 'number' + ? [`--xpack.alerting.rules.maxScheduledPerMinute=${maxScheduledPerMinute}`] + : []; + return { testFiles: testFiles ? testFiles : [require.resolve(`../${name}/tests/`)], servers, @@ -199,6 +206,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ...actionsProxyUrl, ...customHostSettings, ...emailSettings, + ...maxScheduledPerMinuteSettings, '--xpack.eventLog.logEntries=true', '--xpack.task_manager.ephemeral_tasks.enabled=false', `--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify([ @@ -335,6 +343,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) '--notifications.connectors.default.email=notification-email', '--xpack.task_manager.allow_reading_invalid_state=false', '--xpack.task_manager.requeue_invalid_tasks.enabled=true', + '--xpack.actions.queued.max=500', ], }, }; diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index 480c4baaab198..5b4aeb496b125 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -29,6 +29,7 @@ export interface CreateAlertWithActionOpts { summary?: boolean; throttle?: string | null; alertsFilter?: AlertsFilter; + messageTemplate?: string; } export interface CreateNoopAlertOpts { objectRemover?: ObjectRemover; @@ -264,7 +265,7 @@ export class AlertUtils { return response; } - public async createAlwaysFiringSummaryAction({ + public async createAlwaysFiringRuleWithSummaryAction({ objectRemover, overwrites = {}, indexRecordActionId, @@ -272,6 +273,7 @@ export class AlertUtils { notifyWhen, throttle, alertsFilter, + messageTemplate, }: CreateAlertWithActionOpts) { const objRemover = objectRemover || this.objectRemover; const actionId = indexRecordActionId || this.indexRecordActionId; @@ -294,7 +296,52 @@ export class AlertUtils { actionId, notifyWhen, throttle, - alertsFilter + alertsFilter, + messageTemplate + ); + + const response = await request.send({ ...rule, ...overwrites }); + if (response.statusCode === 200) { + objRemover.add(this.space.id, response.body.id, 'rule', 'alerting'); + } + return response; + } + + public async createPatternFiringRuleWithSummaryAction({ + objectRemover, + overwrites = {}, + indexRecordActionId, + reference, + notifyWhen, + throttle, + alertsFilter, + messageTemplate, + pattern, + }: CreateAlertWithActionOpts & { pattern: object }) { + const objRemover = objectRemover || this.objectRemover; + const actionId = indexRecordActionId || this.indexRecordActionId; + + if (!objRemover) { + throw new Error('objectRemover is required'); + } + if (!actionId) { + throw new Error('indexRecordActionId is required '); + } + + let request = this.supertestWithoutAuth + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo'); + if (this.user) { + request = request.auth(this.user.username, this.user.password); + } + const rule = getPatternFiringRuleWithSummaryAction( + reference, + pattern, + actionId, + notifyWhen, + throttle, + alertsFilter, + messageTemplate ); const response = await request.send({ ...rule, ...overwrites }); @@ -409,6 +456,17 @@ export class AlertUtils { } return response; } + + public async runSoon(ruleId: string) { + let request = this.supertestWithoutAuth + .post(`${getUrlPrefix(this.space.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + + if (this.user) { + request = request.auth(this.user.username, this.user.password); + } + return await request.send(); + } } export function getConsumerUnauthorizedErrorMessage( @@ -521,14 +579,16 @@ function getAlwaysFiringRuleWithSummaryAction( actionId: string, notifyWhen = 'onActiveAlert', throttle: string | null = '1m', - alertsFilter?: AlertsFilter + alertsFilter?: AlertsFilter, + messageTemplate?: string ) { - const messageTemplate = + const message = + messageTemplate ?? `Alerts, ` + - `all:{{alerts.all.count}}, ` + - `new:{{alerts.new.count}} IDs:[{{#alerts.new.data}}{{kibana.alert.instance.id}},{{/alerts.new.data}}], ` + - `ongoing:{{alerts.ongoing.count}} IDs:[{{#alerts.ongoing.data}}{{kibana.alert.instance.id}},{{/alerts.ongoing.data}}], ` + - `recovered:{{alerts.recovered.count}} IDs:[{{#alerts.recovered.data}}{{kibana.alert.instance.id}},{{/alerts.recovered.data}}]`.trim(); + `all:{{alerts.all.count}}, ` + + `new:{{alerts.new.count}} IDs:[{{#alerts.new.data}}{{kibana.alert.instance.id}},{{/alerts.new.data}}], ` + + `ongoing:{{alerts.ongoing.count}} IDs:[{{#alerts.ongoing.data}}{{kibana.alert.instance.id}},{{/alerts.ongoing.data}}], ` + + `recovered:{{alerts.recovered.count}} IDs:[{{#alerts.recovered.data}}{{kibana.alert.instance.id}},{{/alerts.recovered.data}}]`.trim(); return { enabled: true, @@ -548,7 +608,54 @@ function getAlwaysFiringRuleWithSummaryAction( params: { index: ES_TEST_INDEX_NAME, reference, - message: messageTemplate, + message, + }, + frequency: { + summary: true, + notify_when: notifyWhen, + throttle, + }, + ...(alertsFilter && { alerts_filter: alertsFilter }), + }, + ], + }; +} + +function getPatternFiringRuleWithSummaryAction( + reference: string, + pattern: object, + actionId: string, + notifyWhen = 'onActiveAlert', + throttle: string | null = null, + alertsFilter?: AlertsFilter, + messageTemplate?: string +) { + const message = + messageTemplate ?? + `Alerts, ` + + `all:{{alerts.all.count}}, ` + + `new:{{alerts.new.count}} IDs:[{{#alerts.new.data}}{{kibana.alert.instance.id}},{{/alerts.new.data}}], ` + + `ongoing:{{alerts.ongoing.count}} IDs:[{{#alerts.ongoing.data}}{{kibana.alert.instance.id}},{{/alerts.ongoing.data}}], ` + + `recovered:{{alerts.recovered.count}} IDs:[{{#alerts.recovered.data}}{{kibana.alert.instance.id}},{{/alerts.recovered.data}}]`.trim(); + + return { + enabled: true, + name: 'pattern-firing-rule-aad', + schedule: { interval: '1m' }, + tags: ['tag-A', 'tag-B'], + rule_type_id: 'test.patternFiringAad', + consumer: 'alertsFixture', + params: { + pattern, + }, + actions: [ + { + group: 'default', + id: actionId, + params: { + index: ES_TEST_INDEX_NAME, + reference, + message, }, frequency: { summary: true, diff --git a/x-pack/test/alerting_api_integration/common/plugins/alerts/server/plugin.ts b/x-pack/test/alerting_api_integration/common/plugins/alerts/server/plugin.ts index 807bd82d5f917..0809a4a5b71c7 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/alerts/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/alerts/server/plugin.ts @@ -87,6 +87,7 @@ export class FixturePlugin implements Plugin { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts new file mode 100644 index 0000000000000..d57398def6381 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -0,0 +1,185 @@ +/* + * Copyright 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 { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; + +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + + describe('Custom Threshold rule - AVG - PCT - NoData', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id-no-data'; + let actionId: string; + let ruleId: string; + + before(async () => { + await createDataView({ + supertest, + name: 'no-data-pattern', + id: DATA_VIEW_ID, + title: 'no-data-pattern', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.nodata' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + index: 'data-view-id-no-data', + query: { query: '', language: 'kuery' }, + }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts similarity index 90% rename from x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts index 65db9101c04e8..b4570771b9f2f 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts @@ -8,8 +8,11 @@ import moment from 'moment'; import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { format } from 'url'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -33,8 +36,8 @@ export default function ({ getService }: FtrProviderContext) { const kibanaUrl = format(kibanaServerConfig); const supertest = getService('supertest'); - describe('Threshold rule - AVG - US - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe('Custom Threshold rule - AVG - US - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; @@ -60,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); await esDeleteAllIndices([ALERT_ACTION_INDEX]); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -96,7 +99,7 @@ export default function ({ getService }: FtrProviderContext) { threshold: [7500000], timeSize: 5, timeUnit: 'm', - customMetrics: [ + metrics: [ { name: 'A', field: 'span.self_time.sum.us', aggType: Aggregators.AVERAGE }, ], }, @@ -149,7 +152,7 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await waitForAlertInIndex({ esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; @@ -157,7 +160,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -165,14 +168,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); @@ -189,7 +195,7 @@ export default function ({ getService }: FtrProviderContext) { threshold: [7500000], timeSize: 5, timeUnit: 'm', - customMetrics: [{ name: 'A', field: 'span.self_time.sum.us', aggType: 'avg' }], + metrics: [{ name: 'A', field: 'span.self_time.sum.us', aggType: 'avg' }], }, ], alertOnNoData: true, @@ -210,7 +216,7 @@ export default function ({ getService }: FtrProviderContext) { indexName: ALERT_ACTION_INDEX, }); - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold'); + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` ); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts new file mode 100644 index 0000000000000..7602c9454a55e --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + + describe('Custom Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.9], + timeSize: 1, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, + { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, + ], + equation: '(A + A) / (B + B)', + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.9], + timeSize: 1, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, + { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, + ], + equation: '(A + A) / (B + B)', + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts new file mode 100644 index 0000000000000..99f313960fa6b --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts @@ -0,0 +1,186 @@ +/* + * Copyright 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 { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + + describe('Custom Threshold rule - DOCUMENTS_COUNT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [2], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [2], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: 'count' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts new file mode 100644 index 0000000000000..5925907b471b6 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts @@ -0,0 +1,243 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { + waitForAlertInIndex, + waitForDocumentInIndex, + waitForRuleStatus, +} from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + let alertId: string; + let startedAt: string; + + describe('Custom Threshold rule - GROUP_BY - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT_OR_EQ, + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.cpu.total.norm.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + groupBy: ['host.name'], + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + reason: '{{context.reason}}', + value: '{{context.value}}', + host: '{{context.host}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; + startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start']; + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source).property('host.name', 'host-0'); + expect(resp.hits.hits[0]._source) + .property('host.mac') + .eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']); + expect(resp.hits.hits[0]._source).property('container.id', 'container-0'); + expect(resp.hits.hits[0]._source).property('container.name', 'container-name'); + expect(resp.hits.hits[0]._source).not.property('container.cpu'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>=', + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + groupBy: ['host.name'], + }); + }); + + it('should set correct action variables', async () => { + const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString(); + const resp = await waitForDocumentInIndex<{ + ruleType: string; + alertDetailsUrl: string; + reason: string; + value: string; + host: string; + }>({ + esClient, + indexName: ALERT_ACTION_INDEX, + }); + + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); + expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( + `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` + ); + expect(resp.hits.hits[0]._source?.reason).eql( + 'Custom equation is 0.8 in the last 1 min for host-0. Alert when >= 0.2.' + ); + expect(resp.hits.hits[0]._source?.value).eql('0.8'); + expect(resp.hits.hits[0]._source?.host).eql( + '{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}' + ); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts new file mode 100644 index 0000000000000..3119a0ae46e63 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; + +import { FtrProviderContext } from '../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover } from '../common/lib'; +import { createRule } from './helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from './helpers/data_view'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); + const es = getService('es'); + + describe('Custom Threshold rule data view >', () => { + const DATA_VIEW_ID = 'data-view-id'; + + let ruleId: string; + + const searchRule = () => + es.search<{ references: unknown; alert: { params: any } }>({ + index: '.kibana*', + query: { + bool: { + filter: [ + { + term: { + _id: `alert:${ruleId}`, + }, + }, + ], + }, + }, + fields: ['alert.params', 'references'], + }); + + before(async () => { + await createDataView({ + supertest, + name: 'test-data-view', + id: DATA_VIEW_ID, + title: 'random-index*', + }); + }); + + after(async () => { + objectRemover.removeAll(); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + }); + + describe('save data view in rule correctly', () => { + it('create a threshold rule', async () => { + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [7500000], + timeSize: 5, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'span.self_time.sum.us', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should have correct data view reference before and after edit', async () => { + const { + hits: { hits: alertHitsV1 }, + } = await searchRule(); + + await supertest + .post(`${getUrlPrefix('default')}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send({ + ids: [ruleId], + operations: [{ operation: 'set', field: 'apiKey' }], + }) + .expect(200); + objectRemover.add('default', ruleId, 'rule', 'alerting'); + + const { + hits: { hits: alertHitsV2 }, + } = await searchRule(); + + expect(alertHitsV1[0]?._source?.references).to.eql([ + { + name: 'param:kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: 'data-view-id', + }, + ]); + expect(alertHitsV1[0]?._source?.alert?.params?.searchConfiguration).to.eql({ + query: { query: '', language: 'kuery' }, + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + }); + expect(alertHitsV1[0].fields).to.eql(alertHitsV2[0].fields); + expect(alertHitsV1[0]?._source?.references ?? true).to.eql( + alertHitsV2[0]?._source?.references ?? false + ); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts b/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts index a50e1b4e85c14..4bde235e97dd2 100644 --- a/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts +++ b/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts @@ -6,7 +6,7 @@ */ import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; -import { ThresholdParams } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import type { SuperTest, Test } from 'supertest'; export async function createIndexConnector({ diff --git a/x-pack/test/alerting_api_integration/observability/index.ts b/x-pack/test/alerting_api_integration/observability/index.ts index 7bc5a5caab0b2..884c17d2abfd1 100644 --- a/x-pack/test/alerting_api_integration/observability/index.ts +++ b/x-pack/test/alerting_api_integration/observability/index.ts @@ -10,13 +10,13 @@ export default function ({ loadTestFile }: any) { describe('Observability Rules', () => { describe('Rules Endpoints', () => { loadTestFile(require.resolve('./metric_threshold_rule')); - loadTestFile(require.resolve('./threshold_rule/avg_pct_fired')); - loadTestFile(require.resolve('./threshold_rule/avg_pct_no_data')); - loadTestFile(require.resolve('./threshold_rule/avg_us_fired')); - loadTestFile(require.resolve('./threshold_rule/custom_eq_avg_bytes_fired')); - loadTestFile(require.resolve('./threshold_rule/documents_count_fired')); - loadTestFile(require.resolve('./threshold_rule/group_by_fired')); - loadTestFile(require.resolve('./threshold_rule_data_view')); + loadTestFile(require.resolve('./custom_threshold_rule/avg_pct_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/avg_pct_no_data')); + loadTestFile(require.resolve('./custom_threshold_rule/avg_us_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/custom_eq_avg_bytes_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/documents_count_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/group_by_fired')); + loadTestFile(require.resolve('./custom_threshold_rule_data_view')); }); describe('Synthetics', () => { loadTestFile(require.resolve('./synthetics_rule')); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts deleted file mode 100644 index 5c48eb3571ec2..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts +++ /dev/null @@ -1,182 +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 { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - - describe('Threshold rule - AVG - PCT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await createDataView({ - supertest, - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await createRule({ - supertest, - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>', - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - }); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts deleted file mode 100644 index 400d5ad55e730..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts +++ /dev/null @@ -1,179 +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 { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; - -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - - describe('Threshold rule - AVG - PCT - NoData', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id-no-data'; - let actionId: string; - let ruleId: string; - - before(async () => { - await createDataView({ - supertest, - name: 'no-data-pattern', - id: DATA_VIEW_ID, - title: 'no-data-pattern', - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await createRule({ - supertest, - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.nodata'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>', - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - index: 'data-view-id-no-data', - query: { query: '', language: 'kuery' }, - }, - }); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts deleted file mode 100644 index b05d60ca7f750..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts +++ /dev/null @@ -1,194 +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. - */ -/* - * Copyright 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 { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - - describe('Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await createDataView({ - supertest, - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await createRule({ - supertest, - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.9], - timeSize: 1, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, - { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, - ], - equation: 'A / B ', - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.9], - timeSize: 1, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, - { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, - ], - equation: 'A / B ', - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - }); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts deleted file mode 100644 index 0ccff07f20828..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts +++ /dev/null @@ -1,180 +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 { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - - describe('Threshold rule - DOCUMENTS_COUNT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await createDataView({ - supertest, - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await createRule({ - supertest, - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>', - threshold: [2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [{ name: 'A', filter: '', aggType: 'count' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - }); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts deleted file mode 100644 index 8b2db8b13ca05..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts +++ /dev/null @@ -1,237 +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. - */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import moment from 'moment'; -import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - let alertId: string; - let startedAt: string; - - describe('Threshold rule - GROUP_BY - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await createDataView({ - supertest, - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await createRule({ - supertest, - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT_OR_EQ, - threshold: [0.2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.cpu.total.norm.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - groupBy: ['host.name'], - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - alertDetailsUrl: '{{context.alertDetailsUrl}}', - reason: '{{context.reason}}', - value: '{{context.value}}', - host: '{{context.host}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; - startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start']; - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source).property('host.name', 'host-0'); - expect(resp.hits.hits[0]._source) - .property('host.mac') - .eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']); - expect(resp.hits.hits[0]._source).property('container.id', 'container-0'); - expect(resp.hits.hits[0]._source).property('container.name', 'container-name'); - expect(resp.hits.hits[0]._source).not.property('container.cpu'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>=', - threshold: [0.2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - groupBy: ['host.name'], - }); - }); - - it('should set correct action variables', async () => { - const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString(); - const resp = await waitForDocumentInIndex<{ - ruleType: string; - alertDetailsUrl: string; - reason: string; - value: string; - host: string; - }>({ - esClient, - indexName: ALERT_ACTION_INDEX, - }); - - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold'); - expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` - ); - expect(resp.hits.hits[0]._source?.reason).eql( - 'Custom equation is 0.8 in the last 1 min for host-0. Alert when >= 0.2.' - ); - expect(resp.hits.hits[0]._source?.value).eql('0.8'); - expect(resp.hits.hits[0]._source?.host).eql( - '{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}' - ); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule_data_view.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule_data_view.ts deleted file mode 100644 index ba846751ef7b4..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule_data_view.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; - -import { FtrProviderContext } from '../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover } from '../common/lib'; -import { createRule } from './helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from './helpers/data_view'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const objectRemover = new ObjectRemover(supertest); - const es = getService('es'); - - describe('Threshold rule data view >', () => { - const DATA_VIEW_ID = 'data-view-id'; - - let ruleId: string; - - const searchRule = () => - es.search<{ references: unknown; alert: { params: any } }>({ - index: '.kibana*', - query: { - bool: { - filter: [ - { - term: { - _id: `alert:${ruleId}`, - }, - }, - ], - }, - }, - fields: ['alert.params', 'references'], - }); - - before(async () => { - await createDataView({ - supertest, - name: 'test-data-view', - id: DATA_VIEW_ID, - title: 'random-index*', - }); - }); - - after(async () => { - objectRemover.removeAll(); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - }); - }); - - describe('save data view in rule correctly', () => { - it('create a threshold rule', async () => { - const createdRule = await createRule({ - supertest, - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [7500000], - timeSize: 5, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'span.self_time.sum.us', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should have correct data view reference before and after edit', async () => { - const { - hits: { hits: alertHitsV1 }, - } = await searchRule(); - - await supertest - .post(`${getUrlPrefix('default')}/internal/alerting/rules/_bulk_edit`) - .set('kbn-xsrf', 'foo') - .send({ - ids: [ruleId], - operations: [{ operation: 'set', field: 'apiKey' }], - }) - .expect(200); - objectRemover.add('default', ruleId, 'rule', 'alerting'); - - const { - hits: { hits: alertHitsV2 }, - } = await searchRule(); - - expect(alertHitsV1[0]?._source?.references).to.eql([ - { - name: 'param:kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - id: 'data-view-id', - }, - ]); - expect(alertHitsV1[0]?._source?.alert?.params?.searchConfiguration).to.eql({ - query: { query: '', language: 'kuery' }, - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', - }); - expect(alertHitsV1[0].fields).to.eql(alertHitsV2[0].fields); - expect(alertHitsV1[0]?._source?.references ?? true).to.eql( - alertHitsV2[0]?._source?.references ?? false - ); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts index ecf565bd60d5d..de1bb08f62772 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { expect as expectExpect } from 'expect'; import { omit, padStart } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; @@ -1235,7 +1236,7 @@ instanceStateValue: true it('should schedule actions for summary of alerts per rule run', async () => { const reference = alertUtils.generateReference(); - const response = await alertUtils.createAlwaysFiringSummaryAction({ + const response = await alertUtils.createAlwaysFiringRuleWithSummaryAction({ reference, overwrites: { schedule: { interval: '1s' }, @@ -1297,7 +1298,7 @@ instanceStateValue: true it('should filter alerts by kql', async () => { const reference = alertUtils.generateReference(); - const response = await alertUtils.createAlwaysFiringSummaryAction({ + const response = await alertUtils.createAlwaysFiringRuleWithSummaryAction({ reference, overwrites: { schedule: { interval: '1s' }, @@ -1367,7 +1368,7 @@ instanceStateValue: true const end = `${hour}:${minutes}`; const reference = alertUtils.generateReference(); - const response = await alertUtils.createAlwaysFiringSummaryAction({ + const response = await alertUtils.createAlwaysFiringRuleWithSummaryAction({ reference, overwrites: { schedule: { interval: '1s' }, @@ -1428,7 +1429,7 @@ instanceStateValue: true it('should schedule actions for summary of alerts on a custom interval', async () => { const reference = alertUtils.generateReference(); - const response = await alertUtils.createAlwaysFiringSummaryAction({ + const response = await alertUtils.createAlwaysFiringRuleWithSummaryAction({ reference, overwrites: { schedule: { interval: '1s' }, @@ -1436,6 +1437,7 @@ instanceStateValue: true notifyWhen: 'onThrottleInterval', throttle: '10s', summary: true, + messageTemplate: `{{alerts}}`, }); switch (scenario.id) { @@ -1474,21 +1476,394 @@ instanceStateValue: true ); // @ts-expect-error doesnt handle total: number expect(searchResult.body.hits.total.value).to.eql(2); - // Summary action is executed on first rule run then skipped 4 times (throttle is 5s and schedule.interval is 1s) - // @ts-expect-error _source: unknown - expect(searchResult.body.hits.hits[0]._source.params.message).to.eql( - 'Alerts, all:2, new:2 IDs:[1,2,], ongoing:0 IDs:[], recovered:0 IDs:[]' + expectExpect( + // Summary action is executed on first rule run then skipped 4 times (throttle is 5s and schedule.interval is 1s) + // @ts-expect-error _source: unknown + JSON.parse(searchResult.body.hits.hits[0]._source.params.message) + ).toEqual( + expectExpect.objectContaining({ + new: { + count: 2, + data: [ + { + _id: expectExpect.any(String), + _index: '.internal.alerts-observability.test.alerts.alerts-default-000001', + kibana: { + alert: { + rule: { + parameters: { + index: '.kibana-alerting-test-data', + reference, + }, + category: 'Test: Always Firing Alert As Data', + consumer: 'alertsFixture', + execution: { uuid: expectExpect.any(String) }, + name: 'abc', + producer: 'alertsFixture', + revision: 0, + rule_type_id: 'test.always-firing-alert-as-data', + uuid: expectExpect.any(String), + tags: ['tag-A', 'tag-B'], + }, + duration: { us: 0 }, + time_range: { gte: expectExpect.any(String) }, + instance: { id: '1' }, + start: expectExpect.any(String), + uuid: expectExpect.any(String), + status: 'active', + workflow_status: 'open', + flapping: false, + }, + space_ids: ['space1'], + version: expectExpect.any(String), + }, + '@timestamp': expectExpect.any(String), + event: { kind: 'signal', action: 'open' }, + tags: ['tag-A', 'tag-B'], + }, + { + _id: expectExpect.any(String), + _index: '.internal.alerts-observability.test.alerts.alerts-default-000001', + kibana: { + alert: { + rule: { + parameters: { + index: '.kibana-alerting-test-data', + reference, + }, + category: 'Test: Always Firing Alert As Data', + consumer: 'alertsFixture', + execution: { uuid: expectExpect.any(String) }, + name: 'abc', + producer: 'alertsFixture', + revision: 0, + rule_type_id: 'test.always-firing-alert-as-data', + uuid: expectExpect.any(String), + tags: ['tag-A', 'tag-B'], + }, + duration: { us: 0 }, + time_range: { gte: expectExpect.any(String) }, + instance: { id: '2' }, + start: expectExpect.any(String), + uuid: expectExpect.any(String), + status: 'active', + workflow_status: 'open', + flapping: false, + }, + space_ids: ['space1'], + version: expectExpect.any(String), + }, + '@timestamp': expectExpect.any(String), + event: { kind: 'signal', action: 'open' }, + tags: ['tag-A', 'tag-B'], + }, + ], + }, + ongoing: { count: 0, data: [] }, + recovered: { count: 0, data: [] }, + }) ); - // @ts-expect-error _source: unknown - // Summary action is executed on the fifth rule run. The new alerts in the first execution become ongoing - expect(searchResult.body.hits.hits[1]._source.params.message).to.eql( - 'Alerts, all:2, new:0 IDs:[], ongoing:2 IDs:[1,2,], recovered:0 IDs:[]' + expectExpect( + // @ts-expect-error _source: unknown + // Summary action is executed on the fifth rule run. The new alerts in the first execution become ongoing + JSON.parse(searchResult.body.hits.hits[1]._source.params.message) + ).toEqual( + expectExpect.objectContaining({ + new: { count: 0, data: [] }, + ongoing: { + count: 2, + data: [ + { + _id: expectExpect.any(String), + _index: '.internal.alerts-observability.test.alerts.alerts-default-000001', + kibana: { + alert: { + rule: { + parameters: { + index: '.kibana-alerting-test-data', + reference, + }, + category: 'Test: Always Firing Alert As Data', + consumer: 'alertsFixture', + execution: { uuid: expectExpect.any(String) }, + name: 'abc', + producer: 'alertsFixture', + revision: 0, + rule_type_id: 'test.always-firing-alert-as-data', + uuid: expectExpect.any(String), + tags: ['tag-A', 'tag-B'], + }, + duration: { us: expectExpect.any(Number) }, + time_range: { gte: expectExpect.any(String) }, + instance: { id: '1' }, + start: expectExpect.any(String), + uuid: expectExpect.any(String), + status: 'active', + workflow_status: 'open', + flapping: false, + }, + space_ids: ['space1'], + version: expectExpect.any(String), + }, + '@timestamp': expectExpect.any(String), + event: { kind: 'signal', action: 'active' }, + tags: ['tag-A', 'tag-B'], + }, + { + _id: expectExpect.any(String), + _index: '.internal.alerts-observability.test.alerts.alerts-default-000001', + kibana: { + alert: { + rule: { + parameters: { + index: '.kibana-alerting-test-data', + reference, + }, + category: 'Test: Always Firing Alert As Data', + consumer: 'alertsFixture', + execution: { uuid: expectExpect.any(String) }, + name: 'abc', + producer: 'alertsFixture', + revision: 0, + rule_type_id: 'test.always-firing-alert-as-data', + uuid: expectExpect.any(String), + tags: ['tag-A', 'tag-B'], + }, + duration: { us: expectExpect.any(Number) }, + time_range: { gte: expectExpect.any(String) }, + instance: { id: '2' }, + start: expectExpect.any(String), + uuid: expectExpect.any(String), + status: 'active', + workflow_status: 'open', + flapping: false, + }, + space_ids: ['space1'], + version: expectExpect.any(String), + }, + '@timestamp': expectExpect.any(String), + event: { kind: 'signal', action: 'active' }, + tags: ['tag-A', 'tag-B'], + }, + ], + }, + recovered: { count: 0, data: [] }, + }) ); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); } }); + + it('should pass summarized alerts to actions', async () => { + const reference = alertUtils.generateReference(); + const response = await alertUtils.createAlwaysFiringRuleWithSummaryAction({ + reference, + overwrites: { + schedule: { interval: '1s' }, + }, + notifyWhen: 'onActiveAlert', + throttle: null, + summary: true, + messageTemplate: `[{{alerts.all.data}}]`, + }); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + expect(response.statusCode).to.eql(403); + expect(response.body).to.eql({ + error: 'Forbidden', + message: getConsumerUnauthorizedErrorMessage( + 'create', + 'test.always-firing-alert-as-data', + 'alertsFixture' + ), + statusCode: 403, + }); + break; + case 'space_1_all_alerts_none_actions at space1': + expect(response.statusCode).to.eql(403); + expect(response.body).to.eql({ + error: 'Forbidden', + message: `Unauthorized to get actions`, + statusCode: 403, + }); + break; + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + case 'superuser at space1': + expect(response.statusCode).to.eql(200); + + await esTestIndexTool.waitForDocs('rule:test.always-firing-alert-as-data', reference); + await esTestIndexTool.waitForDocs('action:test.index-record', reference); + const searchResult = await esTestIndexTool.search( + 'action:test.index-record', + reference + ); + // @ts-expect-error doesnt handle total: number + expect(searchResult.body.hits.total.value).to.eql(1); + expectExpect( + // @ts-expect-error _source: unknown + JSON.parse(searchResult.body.hits.hits[0]._source.params.message) + ).toEqual([ + expectExpect.objectContaining({ + _id: expectExpect.any(String), + _index: '.internal.alerts-observability.test.alerts.alerts-default-000001', + kibana: { + alert: { + rule: { + parameters: { + index: '.kibana-alerting-test-data', + reference, + }, + category: 'Test: Always Firing Alert As Data', + consumer: 'alertsFixture', + execution: { uuid: expectExpect.any(String) }, + name: 'abc', + producer: 'alertsFixture', + revision: 0, + rule_type_id: 'test.always-firing-alert-as-data', + uuid: expectExpect.any(String), + tags: ['tag-A', 'tag-B'], + }, + duration: { us: 0 }, + time_range: { gte: expectExpect.any(String) }, + instance: { id: '1' }, + start: expectExpect.any(String), + uuid: expectExpect.any(String), + status: 'active', + workflow_status: 'open', + flapping: false, + }, + space_ids: ['space1'], + version: expectExpect.any(String), + }, + '@timestamp': expectExpect.any(String), + event: { kind: 'signal', action: 'open' }, + tags: ['tag-A', 'tag-B'], + }), + expectExpect.objectContaining({ + _id: expectExpect.any(String), + _index: '.internal.alerts-observability.test.alerts.alerts-default-000001', + kibana: { + alert: { + rule: { + parameters: { + index: '.kibana-alerting-test-data', + reference, + }, + category: 'Test: Always Firing Alert As Data', + consumer: 'alertsFixture', + execution: { uuid: expectExpect.any(String) }, + name: 'abc', + producer: 'alertsFixture', + revision: 0, + rule_type_id: 'test.always-firing-alert-as-data', + uuid: expectExpect.any(String), + tags: ['tag-A', 'tag-B'], + }, + duration: { us: 0 }, + time_range: { gte: expectExpect.any(String) }, + instance: { id: '2' }, + start: expectExpect.any(String), + uuid: expectExpect.any(String), + status: 'active', + workflow_status: 'open', + flapping: false, + }, + space_ids: ['space1'], + version: expectExpect.any(String), + }, + '@timestamp': expectExpect.any(String), + event: { kind: 'signal', action: 'open' }, + tags: ['tag-A', 'tag-B'], + }), + ]); + + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should create new, ongoing and recovered alerts', async () => { + const reference = alertUtils.generateReference(); + const createdRule = await alertUtils.createPatternFiringRuleWithSummaryAction({ + reference, + overwrites: { + // set the schedule long so we can use "runSoon" to specify rule runs + schedule: { interval: '1d' }, + }, + pattern: { alertA: [true, true, false, false, false, false] }, + }); + + const ruleId = createdRule.body.id; + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'space_1_all at space2': + case 'global_read at space1': + case 'space_1_all_alerts_none_actions at space1': + expect(createdRule.statusCode).to.eql(403); + break; + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + break; + case 'superuser at space1': + expect(createdRule.statusCode).to.eql(200); + + // ##################################### + // first run (new alerts) + // ##################################### + const searchResult = await esTestIndexTool.waitForDocs( + 'action:test.index-record', + reference, + 1 + ); // action execution + + expect(searchResult[0]._source.params.message).to.be( + 'Alerts, all:1, new:1 IDs:[alertA,], ongoing:0 IDs:[], recovered:0 IDs:[]' + ); + + // ##################################### + // second run (ongoing alerts) + // ##################################### + await alertUtils.runSoon(ruleId); + + const secondSearchResult = await esTestIndexTool.waitForDocs( + 'action:test.index-record', + reference, + 2 + ); + + expect(secondSearchResult[1]._source.params.message).to.be( + 'Alerts, all:1, new:0 IDs:[], ongoing:1 IDs:[alertA,], recovered:0 IDs:[]' + ); + + // ##################################### + // third run (recovered alerts) + // ##################################### + await alertUtils.runSoon(ruleId); + + const thirdSearchResult = await esTestIndexTool.waitForDocs( + 'action:test.index-record', + reference, + 3 + ); + + expect(thirdSearchResult[2]._source.params.message).to.be( + 'Alerts, all:1, new:0 IDs:[], ongoing:0 IDs:[], recovered:1 IDs:[alertA,]' + ); + + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); }); } }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts index 02a41b6de7afa..ddc90ec10e9b6 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts @@ -26,7 +26,8 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F const esTestIndexTool = new ESTestIndexTool(es, retry); const supertestWithoutAuth = getService('supertestWithoutAuth'); - describe('telemetry', () => { + // FLAKY: https://github.com/elastic/kibana/issues/140973 + describe.skip('telemetry', () => { const objectRemover = new ObjectRemover(supertest); const alwaysFiringRuleId: { [key: string]: string } = {}; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/config_with_schedule_circuit_breaker.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/config_with_schedule_circuit_breaker.ts new file mode 100644 index 0000000000000..e6441be2e7226 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/config_with_schedule_circuit_breaker.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('security_and_spaces', { + disabledPlugins: [], + license: 'trial', + ssl: true, + enableActionsProxy: true, + publicBaseUrl: true, + testFiles: [require.resolve('./tests/alerting/schedule_circuit_breaker')], + useDedicatedTaskRunner: true, + maxScheduledPerMinute: 10, +}); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/bulk_edit_with_circuit_breaker.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/bulk_edit_with_circuit_breaker.ts new file mode 100644 index 0000000000000..d878eb7404238 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/bulk_edit_with_circuit_breaker.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function bulkEditWithCircuitBreakerTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); + + describe('Bulk edit with circuit breaker', () => { + afterEach(async () => { + await objectRemover.removeAll(); + }); + + it('should prevent rules from being bulk edited if max schedules have been reached', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '20s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '20s' } })) + .expect(200); + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + + const { body: createdRule3 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '20s' } })) + .expect(200); + objectRemover.add('space1', createdRule3.id, 'rule', 'alerting'); + + const payload = { + ids: [createdRule2.id, createdRule3.id], + operations: [ + { + operation: 'set', + field: 'schedule', + value: { + interval: '10s', + }, + }, + ], + }; + + const { body } = await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send(payload) + .expect(200); + + expect(body.errors.length).eql(2); + expect(body.errors[0].message).eql( + 'Failed to bulk edit rule - Run limit reached: The rule has 12 runs per minute; there are only 1 runs per minute available.' + ); + }); + + it('should allow disabled rules to go over the circuit breaker', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '20s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + schedule: { interval: '20s' }, + }) + ) + .expect(200); + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + + const { body: createdRule3 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + schedule: { interval: '20s' }, + }) + ) + .expect(200); + objectRemover.add('space1', createdRule3.id, 'rule', 'alerting'); + + const payload = { + ids: [createdRule2.id, createdRule3.id], + operations: [ + { + operation: 'set', + field: 'schedule', + value: { + interval: '10s', + }, + }, + ], + }; + + const { body } = await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send(payload) + .expect(200); + + expect(body.rules.length).eql(2); + expect(body.errors.length).eql(0); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/bulk_enable_with_circuit_breaker.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/bulk_enable_with_circuit_breaker.ts new file mode 100644 index 0000000000000..d60409223b2b3 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/bulk_enable_with_circuit_breaker.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function bulkEnableWithCircuitBreakerTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); + + describe('Bulk enable with circuit breaker', () => { + afterEach(async () => { + await objectRemover.removeAll(); + }); + + it('should prevent rules from being bulk enabled if max schedules have been reached', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '10s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + schedule: { interval: '20s' }, + }) + ) + .expect(200); + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + + const { body: createdRule3 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + schedule: { interval: '10s' }, + }) + ) + .expect(200); + objectRemover.add('space1', createdRule3.id, 'rule', 'alerting'); + + const { body } = await supertest + .patch(`${getUrlPrefix('space1')}/internal/alerting/rules/_bulk_enable`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule2.id, createdRule3.id] }) + .expect(200); + + expect(body.errors.length).eql(2); + expect(body.errors[0].message).eql( + 'Error validating enable rule data - Run limit reached: The rule has 9 runs per minute; there are only 4 runs per minute available.' + ); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/create_with_circuit_breaker.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/create_with_circuit_breaker.ts new file mode 100644 index 0000000000000..8183f6b48f4ed --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/create_with_circuit_breaker.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function createWithCircuitBreakerTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); + + describe('Create with circuit breaker', () => { + afterEach(async () => { + await objectRemover.removeAll(); + }); + + it('should prevent rules from being created if max schedules have been reached', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '10s' } })) + .expect(200); + objectRemover.add('space1', createdRule.id, 'rule', 'alerting'); + + const { body } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '10s' } })) + .expect(400); + }); + + it('should prevent rules from being created across spaces', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '10s' } })) + .expect(200); + objectRemover.add('space1', createdRule.id, 'rule', 'alerting'); + + const { body } = await supertest + .post(`${getUrlPrefix('space2')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '10s' } })) + .expect(400); + }); + + it('should allow disabled rules to go over the circuit breaker', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '10s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + schedule: { interval: '10s' }, + }) + ) + .expect(200); + + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/enable_with_circuit_breaker.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/enable_with_circuit_breaker.ts new file mode 100644 index 0000000000000..89a90952ed6a7 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/enable_with_circuit_breaker.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function enableWithCircuitBreakerTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); + + describe('Enable with circuit breaker', () => { + afterEach(async () => { + await objectRemover.removeAll(); + }); + + it('should prevent rules from being enabled if max schedules have been reached', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '10s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + schedule: { interval: '5s' }, + }) + ) + .expect(200); + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + + const { body } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule/${createdRule2.id}/_enable`) + .set('kbn-xsrf', 'foo') + .expect(400); + + expect(body.message).eql( + 'Error validating enable rule data - Run limit reached: The rule has 12 runs per minute; there are only 4 runs per minute available.' + ); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/get_schedule_frequency.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/get_schedule_frequency.ts new file mode 100644 index 0000000000000..97cf3a74282a5 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/get_schedule_frequency.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { UserAtSpaceScenarios } from '../../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function getScheduleFrequencyTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const objectRemover = new ObjectRemover(supertest); + + describe('getScheduleFrequency', () => { + before(async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '30s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '1m' } })) + .expect(200); + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + + const { body: createdRule3 } = await supertest + .post(`${getUrlPrefix('space2')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '2m' } })) + .expect(200); + objectRemover.add('space2', createdRule3.id, 'rule', 'alerting'); + + const { body: createdRule4 } = await supertest + .post(`${getUrlPrefix('space2')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '30s' } })) + .expect(200); + objectRemover.add('space2', createdRule4.id, 'rule', 'alerting'); + }); + + after(async () => { + await objectRemover.removeAll(); + }); + + for (const scenario of UserAtSpaceScenarios) { + const { user, space } = scenario; + + describe(scenario.id, () => { + it('should get the total and remaining schedule frequency', async () => { + const { body } = await supertestWithoutAuth + .get(`${getUrlPrefix(space.id)}/internal/alerting/rules/_schedule_frequency`) + .set('kbn-xsrf', 'foo') + .send() + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(body.total_scheduled_per_minute).eql(5.5); + expect(body.remaining_schedules_per_minute).eql(4.5); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/index.ts new file mode 100644 index 0000000000000..963b1bda33245 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/index.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { setupSpacesAndUsers, tearDown } from '../../../../setup'; + +// eslint-disable-next-line import/no-default-export +export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Alerts - Group 3 - schedule circuit breaker', () => { + describe('alerts', () => { + before(async () => { + await setupSpacesAndUsers(getService); + }); + + after(async () => { + await tearDown(getService); + }); + + loadTestFile(require.resolve('./get_schedule_frequency')); + loadTestFile(require.resolve('./create_with_circuit_breaker')); + loadTestFile(require.resolve('./update_with_circuit_breaker')); + loadTestFile(require.resolve('./enable_with_circuit_breaker')); + loadTestFile(require.resolve('./bulk_enable_with_circuit_breaker')); + loadTestFile(require.resolve('./bulk_edit_with_circuit_breaker')); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/update_with_circuit_breaker.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/update_with_circuit_breaker.ts new file mode 100644 index 0000000000000..2b1b8e749def9 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/update_with_circuit_breaker.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function updateWithCircuitBreakerTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); + + describe('Update with circuit breaker', () => { + afterEach(async () => { + await objectRemover.removeAll(); + }); + + it('should prevent rules from being updated if max schedules have been reached', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '20s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '20s' } })) + .expect(200); + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + + const updatedData = { + name: 'bcd', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '5s' }, + actions: [], + throttle: '1m', + notify_when: 'onThrottleInterval', + }; + + const { body } = await supertest + .put(`${getUrlPrefix('space1')}/api/alerting/rule/${createdRule2.id}`) + .set('kbn-xsrf', 'foo') + .send(updatedData) + .expect(400); + + expect(body.message).eql( + 'Error validating update data - Run limit reached: The rule has 12 runs per minute; there are only 7 runs per minute available.' + ); + }); + + it('should allow disabled rules to go over the circuit breaker', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ schedule: { interval: '20s' } })) + .expect(200); + objectRemover.add('space1', createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + enabled: false, + schedule: { interval: '20s' }, + }) + ) + .expect(200); + objectRemover.add('space1', createdRule2.id, 'rule', 'alerting'); + + const updatedData = { + name: 'bcd', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '5s' }, + actions: [], + throttle: '1m', + notify_when: 'onThrottleInterval', + }; + + await supertest + .put(`${getUrlPrefix('space1')}/api/alerting/rule/${createdRule2.id}`) + .set('kbn-xsrf', 'foo') + .send(updatedData) + .expect(200); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index 7a77ebcb1a432..1c735f75f5001 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -31,6 +31,7 @@ export default function actionsTests({ loadTestFile, getService }: FtrProviderCo loadTestFile(require.resolve('./type_not_enabled')); loadTestFile(require.resolve('./schedule_unsecured_action')); loadTestFile(require.resolve('./check_registered_connector_types')); + loadTestFile(require.resolve('./max_queued_actions_circuit_breaker')); // note that this test will destroy existing spaces loadTestFile(require.resolve('./migrations')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/max_queued_actions_circuit_breaker.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/max_queued_actions_circuit_breaker.ts new file mode 100644 index 0000000000000..02231a58a83c3 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/max_queued_actions_circuit_breaker.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; +import { getEventLog, ObjectRemover } from '../../../common/lib'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function createActionTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('max queued actions circuit breaker', () => { + const objectRemover = new ObjectRemover(supertest); + const retry = getService('retry'); + const es = getService('es'); + const esTestIndexTool = new ESTestIndexTool(es, retry); + + beforeEach(async () => { + await esTestIndexTool.destroy(); + await esTestIndexTool.setup(); + }); + + afterEach(async () => { + objectRemover.removeAll(); + await esTestIndexTool.destroy(); + }); + + it('completes execution and reports back whether it reached the limit', async () => { + const response = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }); + + expect(response.status).to.eql(200); + const actionId = response.body.id; + objectRemover.add('default', actionId, 'action', 'actions'); + + const actions = []; + for (let i = 0; i < 510; i++) { + actions.push({ + id: actionId, + group: 'query matched', + params: { + index: ES_TEST_INDEX_NAME, + reference: 'test', + message: '', + }, + frequency: { + summary: false, + throttle: null, + notify_when: 'onActiveAlert', + }, + }); + } + + const resp = await supertest + .post('/api/alerting/rule') + .set('kbn-xsrf', 'foo') + .send({ + name: 'abc', + consumer: 'alertsFixture', + enabled: true, + rule_type_id: '.es-query', + schedule: { interval: '1h' }, + actions, + notify_when: undefined, + params: { + size: 100, + timeWindowSize: 20, + timeWindowUnit: 's', + thresholdComparator: '>', + threshold: [-1], + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeField: 'date', + index: [ES_TEST_INDEX_NAME], + }, + }); + + expect(resp.status).to.eql(200); + const ruleId = resp.body.id; + objectRemover.add('default', ruleId, 'rule', 'alerting'); + + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: 'default', + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([['execute', { gte: 1 }]]), + }); + }); + + // check that there's a warning in the execute event + const executeEvent = events[0]; + expect(executeEvent?.event?.outcome).to.eql('success'); + expect(executeEvent?.event?.reason).to.eql('maxQueuedActions'); + expect(executeEvent?.kibana?.alerting?.status).to.eql('warning'); + expect(executeEvent?.message).to.eql( + 'The maximum number of queued actions was reached; excess actions were not triggered.' + ); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts deleted file mode 100644 index 63fdfa8d498fa..0000000000000 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { Spaces } from '../../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function createAggregateTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const retry = getService('retry'); - - const getEventLogWithRetry = async (id: string) => { - await retry.try(async () => { - return await getEventLog({ - getService, - spaceId: Spaces.space1.id, - type: 'alert', - id, - provider: 'alerting', - actions: new Map([['execute', { equal: 1 }]]), - }); - }); - }; - - describe('aggregate', () => { - const objectRemover = new ObjectRemover(supertest); - - afterEach(() => objectRemover.removeAll()); - - it('should aggregate when there are no alerts', async () => { - const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate` - ); - expect(response.status).to.eql(200); - expect(response.body).to.eql({ - rule_enabled_status: { - disabled: 0, - enabled: 0, - }, - rule_execution_status: { - ok: 0, - active: 0, - error: 0, - pending: 0, - unknown: 0, - warning: 0, - }, - rule_last_run_outcome: { - succeeded: 0, - warning: 0, - failed: 0, - }, - rule_muted_status: { - muted: 0, - unmuted: 0, - }, - rule_snoozed_status: { - snoozed: 0, - }, - rule_tags: [], - }); - }); - - it('should aggregate alert status totals', async () => { - const NumOkAlerts = 4; - const NumActiveAlerts = 1; - const NumErrorAlerts = 2; - - const okAlertIds: string[] = []; - const activeAlertIds: string[] = []; - const errorAlertIds: string[] = []; - - await Promise.all( - [...Array(NumOkAlerts)].map(async () => { - const okAlertId = await createTestAlert({ - rule_type_id: 'test.noop', - schedule: { interval: '24h' }, - }); - okAlertIds.push(okAlertId); - objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting'); - }) - ); - - await Promise.all(okAlertIds.map((id) => getEventLogWithRetry(id))); - - await Promise.all( - [...Array(NumActiveAlerts)].map(async () => { - const activeAlertId = await createTestAlert({ - rule_type_id: 'test.patternFiring', - schedule: { interval: '24h' }, - params: { - pattern: { instance: new Array(100).fill(true) }, - }, - }); - activeAlertIds.push(activeAlertId); - objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting'); - }) - ); - - await Promise.all(activeAlertIds.map((id) => getEventLogWithRetry(id))); - - await Promise.all( - [...Array(NumErrorAlerts)].map(async () => { - const errorAlertId = await createTestAlert({ - rule_type_id: 'test.throw', - schedule: { interval: '24h' }, - }); - errorAlertIds.push(errorAlertId); - objectRemover.add(Spaces.space1.id, errorAlertId, 'rule', 'alerting'); - }) - ); - - await Promise.all(errorAlertIds.map((id) => getEventLogWithRetry(id))); - - await retry.try(async () => { - const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate` - ); - - expect(response.status).to.eql(200); - expect(response.body).to.eql({ - rule_enabled_status: { - disabled: 0, - enabled: 7, - }, - rule_execution_status: { - ok: NumOkAlerts, - active: NumActiveAlerts, - error: NumErrorAlerts, - pending: 0, - unknown: 0, - warning: 0, - }, - rule_last_run_outcome: { - succeeded: 5, - warning: 0, - failed: 2, - }, - rule_muted_status: { - muted: 0, - unmuted: 7, - }, - rule_snoozed_status: { - snoozed: 0, - }, - rule_tags: ['foo'], - }); - }); - }); - - describe('tags limit', () => { - it('should be 50 be default', async () => { - const numOfAlerts = 3; - const numOfTagsPerAlert = 30; - - await Promise.all( - [...Array(numOfAlerts)].map(async (_, alertIndex) => { - const okAlertId = await createTestAlert({ - rule_type_id: 'test.noop', - schedule: { interval: '24h' }, - tags: [...Array(numOfTagsPerAlert)].map( - (__, i) => `tag-${i + numOfTagsPerAlert * alertIndex}` - ), - }); - objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting'); - }) - ); - - const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate` - ); - - expect(response.body.rule_tags.length).to.eql(50); - }); - }); - - describe('legacy', () => { - it('should aggregate alert status totals', async () => { - const NumOkAlerts = 4; - const NumActiveAlerts = 1; - const NumErrorAlerts = 2; - - const okAlertIds: string[] = []; - const activeAlertIds: string[] = []; - const errorAlertIds: string[] = []; - - await Promise.all( - [...Array(NumOkAlerts)].map(async () => { - const okAlertId = await createTestAlert({ - rule_type_id: 'test.noop', - schedule: { interval: '24h' }, - tags: ['a', 'b'], - }); - okAlertIds.push(okAlertId); - objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting'); - }) - ); - await Promise.all(okAlertIds.map((id) => getEventLogWithRetry(id))); - - await Promise.all( - [...Array(NumActiveAlerts)].map(async () => { - const activeAlertId = await createTestAlert({ - rule_type_id: 'test.patternFiring', - schedule: { interval: '24h' }, - params: { - pattern: { instance: new Array(100).fill(true) }, - }, - tags: ['a', 'c', 'f'], - }); - activeAlertIds.push(activeAlertId); - objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting'); - }) - ); - await Promise.all(activeAlertIds.map((id) => getEventLogWithRetry(id))); - - await Promise.all( - [...Array(NumErrorAlerts)].map(async () => { - const errorAlertId = await createTestAlert({ - rule_type_id: 'test.throw', - schedule: { interval: '24h' }, - tags: ['b', 'c', 'd'], - }); - errorAlertIds.push(errorAlertId); - objectRemover.add(Spaces.space1.id, errorAlertId, 'rule', 'alerting'); - }) - ); - await Promise.all(errorAlertIds.map((id) => getEventLogWithRetry(id))); - - await retry.try(async () => { - const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/_aggregate` - ); - - expect(response.status).to.eql(200); - expect(response.body).to.eql({ - alertExecutionStatus: { - ok: NumOkAlerts, - active: NumActiveAlerts, - error: NumErrorAlerts, - pending: 0, - unknown: 0, - warning: 0, - }, - ruleEnabledStatus: { - disabled: 0, - enabled: 7, - }, - ruleLastRunOutcome: { - succeeded: 5, - warning: 0, - failed: 2, - }, - ruleMutedStatus: { - muted: 0, - unmuted: 7, - }, - ruleSnoozedStatus: { - snoozed: 0, - }, - ruleTags: ['a', 'b', 'c', 'd', 'f'], - }); - }); - }); - }); - }); - - async function createTestAlert(testAlertOverrides = {}) { - const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send(getTestRuleData(testAlertOverrides)) - .expect(200); - return createdAlert.id; - } -} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 2fe26f2bdb927..2aff79b8997b8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -725,6 +725,114 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(flapping).to.eql(result); }); + it('should generate expected events for flapping alerts that settle on active where the action notifyWhen is set to "on status change"', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) + .set('kbn-xsrf', 'foo') + .auth('superuser', 'superuser') + .send({ + enabled: true, + look_back_window: 6, + status_change_threshold: 4, + }) + .expect(200); + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); + const pattern = { + instance, + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: null, + notify_when: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + ], + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 6 }], + ['execute', { gte: 6 }], + ['execute-action', { equal: 6 }], + ['new-instance', { equal: 3 }], + ['active-instance', { gte: 6 }], + ['recovered-instance', { equal: 3 }], + ]), + }); + }); + + const flapping = events + .filter( + (event) => + event?.event?.action === 'active-instance' || + event?.event?.action === 'recovered-instance' + ) + .map((event) => event?.kibana?.alert?.flapping); + const result = [false, false, false, false, false].concat( + new Array(9).fill(true), + false, + false, + false + ); + expect(flapping).to.eql(result); + }); + it('should generate expected events for flapping alerts settle on recovered', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) @@ -818,6 +926,109 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ); }); + it('should generate expected events for flapping alerts settle on recovered where the action notifyWhen is set to "on status change"', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) + .set('kbn-xsrf', 'foo') + .auth('superuser', 'superuser') + .send({ + enabled: true, + look_back_window: 6, + status_change_threshold: 4, + }) + .expect(200); + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const instance = [true, false, false, true, false, true, false, true, false, true].concat( + new Array(11).fill(false) + ); + const pattern = { + instance, + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: null, + notify_when: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + ], + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 6 }], + ['execute', { gte: 6 }], + ['execute-action', { equal: 6 }], + ['new-instance', { equal: 3 }], + ['active-instance', { gte: 3 }], + ['recovered-instance', { equal: 3 }], + ]), + }); + }); + + const flapping = events + .filter( + (event) => + event?.event?.action === 'active-instance' || + event?.event?.action === 'recovered-instance' + ) + .map((event) => event?.kibana?.alert?.flapping); + expect(flapping).to.eql( + [false, false, false, false, false].concat(new Array(8).fill(true)) + ); + }); + it('should generate expected events for flapping alerts over a period of time longer than the look back', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) @@ -917,7 +1128,104 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(flapping).to.eql(result); }); - it('should generate expected events for flapping alerts that settle on active where notifyWhen is not set to "on status change"', async () => { + it('should generate expected events for flapping alerts that settle on active where notifyWhen is NOT set to "on status change"', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) + .set('kbn-xsrf', 'foo') + .auth('superuser', 'superuser') + .send({ + enabled: true, + look_back_window: 6, + status_change_threshold: 4, + }) + .expect(200); + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); + const pattern = { + instance, + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: '1s', + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, + ], + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 15 }], + ['execute', { gte: 15 }], + ['execute-action', { equal: 15 }], + ['new-instance', { equal: 3 }], + ['active-instance', { gte: 6 }], + ['recovered-instance', { equal: 3 }], + ]), + }); + }); + + const flapping = events + .filter( + (event) => + event?.event?.action === 'active-instance' || + event?.event?.action === 'recovered-instance' + ) + .map((event) => event?.kibana?.alert?.flapping); + const result = [false, false, false, false, false].concat( + new Array(7).fill(true), + false, + false, + false + ); + expect(flapping).to.eql(result); + }); + + it('should generate expected events for flapping alerts that settle on active where the action notifyWhen is NOT set to "on status change"', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') @@ -956,6 +1264,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, throttle: null, + notify_when: null, params: { pattern, }, @@ -964,11 +1273,21 @@ export default function eventLogTests({ getService }: FtrProviderContext) { id: createdAction.id, group: 'default', params: {}, + frequency: { + summary: false, + throttle: '1s', + notify_when: RuleNotifyWhen.THROTTLE, + }, }, { id: createdAction.id, group: 'recovered', params: {}, + frequency: { + summary: false, + throttle: '1s', + notify_when: RuleNotifyWhen.THROTTLE, + }, }, ], }) @@ -1014,7 +1333,97 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(flapping).to.eql(result); }); - it('should generate expected events for flapping alerts that settle on recovered where notifyWhen is not set to "on status change"', async () => { + it('should generate expected events for flapping alerts that settle on recovered where notifyWhen is NOT set to "on status change"', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) + .set('kbn-xsrf', 'foo') + .auth('superuser', 'superuser') + .send({ + enabled: true, + look_back_window: 6, + status_change_threshold: 4, + }) + .expect(200); + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const instance = [true, false, false, true, false, true, false, true, false, true].concat( + new Array(11).fill(false) + ); + const pattern = { + instance, + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: '1s', + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, + ], + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 8 }], + ['execute', { gte: 8 }], + ['execute-action', { equal: 8 }], + ['new-instance', { equal: 3 }], + ['active-instance', { gte: 3 }], + ['recovered-instance', { equal: 3 }], + ]), + }); + }); + + const flapping = events + .filter( + (event) => + event?.event?.action === 'active-instance' || + event?.event?.action === 'recovered-instance' + ) + .map((event) => event?.kibana?.alert?.flapping); + expect(flapping).to.eql([false, false, false, false, false, true, true, true]); + }); + + it('should generate expected events for flapping alerts that settle on recovered where the action notifyWhen is NOT set to "on status change"', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') @@ -1052,6 +1461,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, throttle: null, + notify_when: null, params: { pattern, }, @@ -1060,11 +1470,21 @@ export default function eventLogTests({ getService }: FtrProviderContext) { id: createdAction.id, group: 'default', params: {}, + frequency: { + summary: false, + throttle: '1s', + notify_when: RuleNotifyWhen.THROTTLE, + }, }, { id: createdAction.id, group: 'recovered', params: {}, + frequency: { + summary: false, + throttle: '1s', + notify_when: RuleNotifyWhen.THROTTLE, + }, }, ], }) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/index.ts index 6dacd17642a10..7a0c24878d07f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/index.ts @@ -14,7 +14,6 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC before(async () => await buildUp(getService)); after(async () => await tearDown(getService)); - loadTestFile(require.resolve('./aggregate')); loadTestFile(require.resolve('./aggregate_post')); loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts new file mode 100644 index 0000000000000..eee79e38a2dac --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts @@ -0,0 +1,340 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover } from '../../../../../../common/lib'; +import { + createConnector, + ES_GROUPS_TO_WRITE, + ES_TEST_DATA_STREAM_NAME, + ES_TEST_INDEX_REFERENCE, + ES_TEST_INDEX_SOURCE, + ES_TEST_OUTPUT_INDEX_NAME, + getRuleServices, + RULE_INTERVALS_TO_WRITE, + RULE_INTERVAL_MILLIS, + RULE_INTERVAL_SECONDS, + RULE_TYPE_ID, +} from './common'; +import { createDataStream, deleteDataStream } from '../../../create_test_data'; + +// eslint-disable-next-line import/no-default-export +export default function ruleTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const { + es, + esTestIndexTool, + esTestIndexToolOutput, + esTestIndexToolDataStream, + createEsDocumentsInGroups, + removeAllAADDocs, + getAllAADDocs, + } = getRuleServices(getService); + + describe('rule', async () => { + let endDate: string; + let connectorId: string; + const objectRemover = new ObjectRemover(supertest); + + beforeEach(async () => { + await esTestIndexTool.destroy(); + await esTestIndexTool.setup(); + + await esTestIndexToolOutput.destroy(); + await esTestIndexToolOutput.setup(); + + connectorId = await createConnector(supertest, objectRemover, ES_TEST_OUTPUT_INDEX_NAME); + + // write documents in the future, figure out the end date + const endDateMillis = Date.now() + (RULE_INTERVALS_TO_WRITE - 1) * RULE_INTERVAL_MILLIS; + endDate = new Date(endDateMillis).toISOString(); + + await createDataStream(es, ES_TEST_DATA_STREAM_NAME); + }); + + afterEach(async () => { + await objectRemover.removeAll(); + await esTestIndexTool.destroy(); + await esTestIndexToolOutput.destroy(); + await deleteDataStream(es, ES_TEST_DATA_STREAM_NAME); + await removeAllAADDocs(); + }); + + it('runs correctly: threshold on ungrouped hit count < >', async () => { + // write documents from now to the future end date in groups + await createEsDocumentsInGroups(ES_GROUPS_TO_WRITE, endDate); + await createRule({ + name: 'never fire', + esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) | where c < 0', + size: 100, + }); + await createRule({ + name: 'always fire', + esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) | where c > -1', + size: 100, + }); + + const docs = await waitForDocs(2); + const messagePattern = + /rule 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Query matched documents over 20s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\n- Link:/; + + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`rule 'always fire' matched query`); + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + } + + const aadDocs = await getAllAADDocs(1); + + const alertDoc = aadDocs.body.hits.hits[0]._source.kibana.alert; + expect(alertDoc.reason).to.match(messagePattern); + expect(alertDoc.title).to.be("rule 'always fire' matched query"); + expect(alertDoc.evaluation.conditions).to.be('Query matched documents'); + expect(alertDoc.evaluation.value).greaterThan(0); + expect(alertDoc.url).to.contain('/s/space1/app/'); + }); + + it('runs correctly: use epoch millis - threshold on hit count < >', async () => { + // write documents from now to the future end date in groups + const endDateMillis = Date.now() + (RULE_INTERVALS_TO_WRITE - 1) * RULE_INTERVAL_MILLIS; + endDate = new Date(endDateMillis).toISOString(); + await createEsDocumentsInGroups(ES_GROUPS_TO_WRITE, endDate); + await createRule({ + name: 'never fire', + esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) | where c < 0', + size: 100, + timeField: 'date_epoch_millis', + }); + await createRule({ + name: 'always fire', + esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) | where c > -1', + size: 100, + timeField: 'date_epoch_millis', + }); + + const docs = await waitForDocs(2); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`rule 'always fire' matched query`); + const messagePattern = + /rule 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Query matched documents over 20s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\n- Link:/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + } + }); + + it('runs correctly: no matches', async () => { + await createRule({ + name: 'always fire', + esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) | where c < 1', + size: 100, + }); + + const docs = await waitForDocs(1); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`rule 'always fire' matched query`); + const messagePattern = + /rule 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Query matched documents over 20s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\n- Link:/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + } + }); + + it('runs correctly and populates recovery context', async () => { + // This rule should be active initially when the number of documents is below the threshold + // and then recover when we add more documents. + await createRule({ + name: 'fire then recovers', + esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) | where c < 1', + size: 100, + notifyWhen: 'onActionGroupChange', + timeWindowSize: RULE_INTERVAL_SECONDS, + }); + + let docs = await waitForDocs(1); + const activeDoc = docs[0]; + const { + name: activeName, + title: activeTitle, + value: activeValue, + message: activeMessage, + } = activeDoc._source.params; + + expect(activeName).to.be('fire then recovers'); + expect(activeTitle).to.be(`rule 'fire then recovers' matched query`); + expect(activeValue).to.be('1'); + expect(activeMessage).to.match( + /rule 'fire then recovers' is active:\n\n- Value: \d+\n- Conditions Met: Query matched documents over 4s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\n- Link:/ + ); + await createEsDocumentsInGroups(1, endDate); + docs = await waitForDocs(2); + const recoveredDoc = docs[1]; + const { + name: recoveredName, + title: recoveredTitle, + message: recoveredMessage, + } = recoveredDoc._source.params; + + expect(recoveredName).to.be('fire then recovers'); + expect(recoveredTitle).to.be(`rule 'fire then recovers' recovered`); + expect(recoveredMessage).to.match( + /rule 'fire then recovers' is recovered:\n\n- Value: \d+\n- Conditions Met: Query did NOT match documents over 4s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\n- Link:/ + ); + }); + + it('runs correctly over a data stream: threshold on hit count < >', async () => { + // write documents from now to the future end date in groups + await createEsDocumentsInGroups( + ES_GROUPS_TO_WRITE, + endDate, + esTestIndexToolDataStream, + ES_TEST_DATA_STREAM_NAME + ); + await createRule({ + name: 'never fire', + esqlQuery: 'from test-data-stream | stats c = count(@timestamp) | where c < 0', + size: 100, + }); + await createRule({ + name: 'always fire', + esqlQuery: 'from test-data-stream | stats c = count(@timestamp) | where c > -1', + size: 100, + }); + + const docs = await waitForDocs(2); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`rule 'always fire' matched query`); + const messagePattern = + /rule 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Query matched documents over 20s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\n- Link:/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + } + }); + + async function waitForDocs(count: number): Promise { + return await esTestIndexToolOutput.waitForDocs( + ES_TEST_INDEX_SOURCE, + ES_TEST_INDEX_REFERENCE, + count + ); + } + + interface CreateRuleParams { + name: string; + size: number; + esqlQuery: string; + timeWindowSize?: number; + timeField?: string; + notifyWhen?: string; + aggType?: string; + aggField?: string; + groupBy?: string; + termField?: string; + termSize?: number; + } + + async function createRule(params: CreateRuleParams): Promise { + const action = { + id: connectorId, + group: 'query matched', + params: { + documents: [ + { + source: ES_TEST_INDEX_SOURCE, + reference: ES_TEST_INDEX_REFERENCE, + params: { + name: '{{{rule.name}}}', + value: '{{{context.value}}}', + title: '{{{context.title}}}', + message: '{{{context.message}}}', + }, + hits: '{{context.hits}}', + date: '{{{context.date}}}', + previousTimestamp: '{{{state.latestTimestamp}}}', + }, + ], + }, + }; + + const recoveryAction = { + id: connectorId, + group: 'recovered', + params: { + documents: [ + { + source: ES_TEST_INDEX_SOURCE, + reference: ES_TEST_INDEX_REFERENCE, + params: { + name: '{{{rule.name}}}', + value: '{{{context.value}}}', + title: '{{{context.title}}}', + message: '{{{context.message}}}', + }, + hits: '{{context.hits}}', + date: '{{{context.date}}}', + }, + ], + }, + }; + + const { body: createdRule } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send({ + name: params.name, + consumer: 'alerts', + enabled: true, + rule_type_id: RULE_TYPE_ID, + schedule: { interval: `${RULE_INTERVAL_SECONDS}s` }, + actions: [action, recoveryAction], + notify_when: params.notifyWhen || 'onActiveAlert', + params: { + size: params.size, + timeWindowSize: params.timeWindowSize || RULE_INTERVAL_SECONDS * 5, + timeWindowUnit: 's', + thresholdComparator: '>', + threshold: [0], + searchType: 'esqlQuery', + aggType: params.aggType, + groupBy: params.groupBy, + aggField: params.aggField, + termField: params.termField, + termSize: params.termSize, + timeField: params.timeField || 'date', + esqlQuery: { esql: params.esqlQuery }, + }, + }) + .expect(200); + + const ruleId = createdRule.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); + + return ruleId; + } + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/index.ts index a584753db7c25..758737d9749b6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/index.ts @@ -12,5 +12,6 @@ export default function alertingTests({ loadTestFile }: FtrProviderContext) { describe('es_query', () => { loadTestFile(require.resolve('./rule')); loadTestFile(require.resolve('./query_dsl_only')); + loadTestFile(require.resolve('./esql_only')); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts index c0b9113fa6143..25ca53c52e429 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts @@ -368,6 +368,75 @@ export default function ruleTests({ getService }: FtrProviderContext) { }) ); + [ + [ + 'esQuery', + async () => { + await createRule({ + name: 'always fire', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + thresholdComparator: '>', + threshold: [-1], + groupBy: 'top', + termField: ['group', 'testedValue'], + termSize: 2, + }); + }, + ] as const, + [ + 'searchSource', + async () => { + const esTestDataView = await indexPatterns.create( + { title: ES_TEST_INDEX_NAME, timeFieldName: 'date' }, + { override: true }, + getUrlPrefix(Spaces.space1.id) + ); + await createRule({ + name: 'always fire', + size: 100, + thresholdComparator: '>', + threshold: [-1], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + groupBy: 'top', + termField: ['group', 'testedValue'], + termSize: 2, + }); + }, + ] as const, + ].forEach(([searchType, initData]) => + it(`runs correctly: threshold on grouped with multi term hit count < > for ${searchType} search type`, async () => { + // write documents from now to the future end date in groups + await createGroupedEsDocumentsInGroups(ES_GROUPS_TO_WRITE, endDate); + await initData(); + + const docs = await waitForDocs(2); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { previousTimestamp, hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + const titlePattern = /rule 'always fire' matched query for group group-\d/; + expect(title).to.match(titlePattern); + const messagePattern = + /rule 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents for group \"group-\d,\d{1,2}\" is greater than -1 over 20s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + + expect(previousTimestamp).to.be.empty(); + } + }) + ); + [ [ 'esQuery', @@ -1044,7 +1113,7 @@ export default function ruleTests({ getService }: FtrProviderContext) { aggType?: string; aggField?: string; groupBy?: string; - termField?: string; + termField?: string | string[]; termSize?: number; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/alert.ts index 46f8a0dc955f3..3de5e761b2622 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/alert.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; +import { STACK_AAD_INDEX_NAME } from '@kbn/stack-alerts-plugin/server/rule_types'; import { Spaces } from '../../../../../scenarios'; import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../../common/lib'; @@ -33,6 +34,11 @@ export default function ruleTests({ getService }: FtrProviderContext) { const es = getService('es'); const esTestIndexTool = new ESTestIndexTool(es, retry); const esTestIndexToolOutput = new ESTestIndexTool(es, retry, ES_TEST_OUTPUT_INDEX_NAME); + const esTestIndexToolAAD = new ESTestIndexTool( + es, + retry, + `.internal.alerts-${STACK_AAD_INDEX_NAME}.alerts-default-000001` + ); describe('rule', async () => { let endDate: string; @@ -60,6 +66,7 @@ export default function ruleTests({ getService }: FtrProviderContext) { await esTestIndexTool.destroy(); await esTestIndexToolOutput.destroy(); await deleteDataStream(es, ES_TEST_DATA_STREAM_NAME); + await esTestIndexToolAAD.removeAll(); }); // The tests below create two alerts, one that will fire, one that will @@ -86,6 +93,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); const docs = await waitForDocs(2); + const messagePattern = + /alert 'always fire' is active for group \'all documents\':\n\n- Value: \d+\n- Conditions Met: count is greater than -1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + for (const doc of docs) { const { group } = doc._source; const { name, title, message } = doc._source.params; @@ -96,10 +106,17 @@ export default function ruleTests({ getService }: FtrProviderContext) { // we'll check title and message in this test, but not subsequent ones expect(title).to.be('alert always fire group all documents met threshold'); - const messagePattern = - /alert 'always fire' is active for group \'all documents\':\n\n- Value: \d+\n- Conditions Met: count is greater than -1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; expect(message).to.match(messagePattern); } + + const aadDocs = await esTestIndexToolAAD.getAll(1); + + // @ts-ignore + const alertDoc = aadDocs.body.hits.hits[0]._source.kibana.alert; + expect(alertDoc.reason).to.match(messagePattern); + expect(alertDoc.title).to.be('alert always fire group all documents met threshold'); + expect(alertDoc.evaluation.conditions).to.be('count is greater than -1'); + expect(alertDoc.evaluation.value).greaterThan(0); }); it('runs correctly: count grouped <= =>', async () => { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts index 62d45f50a07c9..3bff92d2470c9 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts @@ -298,6 +298,63 @@ export default function createAlertsAsDataInstallResourcesTest({ getService }: F expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(true); }); + it('Should not fail when an alert is flapping and recovered for a rule with notify_when: onThrottleInterval', async () => { + await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`) + .set('kbn-xsrf', 'foo') + .auth('superuser', 'superuser') + .send({ + enabled: true, + look_back_window: 5, + status_change_threshold: 3, + }) + .expect(200); + + const pattern = { + alertA: [true, false, true, false, false, false, false, false, false], + }; + const ruleParameters = { pattern }; + const createdRule = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + // notify_when is not RuleNotifyWhen.CHANGE, so it's not added to activeCurrent + getTestRuleData({ + rule_type_id: 'test.patternFiringAad', + // set the schedule long so we can use "runSoon" to specify rule runs + schedule: { interval: '1d' }, + throttle: null, + params: ruleParameters, + actions: [], + }) + ); + + expect(createdRule.status).to.eql(200); + const ruleId = createdRule.body.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); + + // Wait for the rule to run once + let run = 1; + await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 1 }]])); + // Run the rule 10 more times + for (let i = 0; i < 5; i++) { + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(response.status).to.eql(204); + await waitForEventLogDocs(ruleId, new Map([['execute', { equal: ++run }]])); + } + + const alertDocs = await queryForAlertDocs(); + const state = await getRuleState(ruleId); + + expect(alertDocs.length).to.equal(2); + + // Alert is recovered and flapping + expect(alertDocs[0]._source!.kibana.alert.flapping).to.equal(true); + expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(true); + }); + it('should set flapping and flapping_history for flapping alerts over a period of time longer than the lookback', async () => { await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/install_resources.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/install_resources.ts index b6c86b49c7fba..e80a8f94d93b6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/install_resources.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/install_resources.ts @@ -163,6 +163,7 @@ export default function createAlertsAsDataInstallResourcesTest({ getService }: F rollover_alias: '.alerts-test.patternfiring.alerts-default', }, mapping: { + ignore_malformed: 'true', total_fields: { limit: '2500', }, @@ -196,6 +197,7 @@ export default function createAlertsAsDataInstallResourcesTest({ getService }: F }); expect(contextIndex[indexName].settings?.index?.mapping).to.eql({ + ignore_malformed: 'true', total_fields: { limit: '2500', }, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts index 40197f1e18783..e748478b18432 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts @@ -63,7 +63,7 @@ export default function createRegisteredRuleTypeTests({ getService }: FtrProvide 'monitoring_alert_elasticsearch_version_mismatch', 'monitoring_ccr_read_exceptions', 'monitoring_shard_size', - 'observability.rules.threshold', + 'observability.rules.custom_threshold', 'apm.transaction_duration', 'apm.anomaly', 'apm.error_rate', diff --git a/x-pack/test/api_integration/apis/management/index_management/create_enrich_policy.ts b/x-pack/test/api_integration/apis/management/index_management/create_enrich_policy.ts new file mode 100644 index 0000000000000..e2bfa9cdb35ef --- /dev/null +++ b/x-pack/test/api_integration/apis/management/index_management/create_enrich_policy.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const INTERNAL_API_BASE_PATH = '/internal/index_management'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + + describe('Create enrich policy', function () { + const INDEX_A_NAME = `index-${Math.random()}`; + const INDEX_B_NAME = `index-${Math.random()}`; + const POLICY_NAME = `policy-${Math.random()}`; + + before(async () => { + try { + await es.indices.create({ + index: INDEX_A_NAME, + body: { + mappings: { + properties: { + email: { + type: 'text', + }, + firstName: { + type: 'text', + }, + }, + }, + }, + }); + await es.indices.create({ + index: INDEX_B_NAME, + body: { + mappings: { + properties: { + email: { + type: 'text', + }, + age: { + type: 'long', + }, + }, + }, + }, + }); + } catch (err) { + log.debug('[Setup error] Error creating test index'); + throw err; + } + }); + + after(async () => { + try { + await es.indices.delete({ index: INDEX_A_NAME }); + await es.indices.delete({ index: INDEX_B_NAME }); + } catch (err) { + log.debug('[Cleanup error] Error deleting test index'); + throw err; + } + }); + + it('Allows to create an enrich policy', async () => { + const { body } = await supertest + .post(`${INTERNAL_API_BASE_PATH}/enrich_policies`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ + policy: { + name: POLICY_NAME, + type: 'match', + matchField: 'email', + enrichFields: ['firstName'], + sourceIndices: [INDEX_A_NAME], + }, + }) + .expect(200); + + expect(body).toStrictEqual({ acknowledged: true }); + }); + + it('Can retrieve fields from indices', async () => { + const { body } = await supertest + .post(`${INTERNAL_API_BASE_PATH}/enrich_policies/get_fields_from_indices`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ indices: [INDEX_A_NAME, INDEX_B_NAME] }) + .expect(200); + + expect(body).toStrictEqual({ + commonFields: [{ name: 'email', type: 'text', normalizedType: 'text' }], + indices: [ + { + index: INDEX_A_NAME, + fields: [ + { name: 'email', type: 'text', normalizedType: 'text' }, + { name: 'firstName', type: 'text', normalizedType: 'text' }, + ], + }, + { + index: INDEX_B_NAME, + fields: [ + { name: 'age', type: 'long', normalizedType: 'number' }, + { name: 'email', type: 'text', normalizedType: 'text' }, + ], + }, + ], + }); + }); + + it('Can retrieve matching indices', async () => { + const { body } = await supertest + .post(`${INTERNAL_API_BASE_PATH}/enrich_policies/get_matching_indices`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ pattern: 'index-' }) + .expect(200); + + expect( + body.indices.every((value: string) => [INDEX_A_NAME, INDEX_B_NAME].includes(value)) + ).toBe(true); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/management/index_management/create_index.ts b/x-pack/test/api_integration/apis/management/index_management/create_index.ts new file mode 100644 index 0000000000000..b5e0527e2a196 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/index_management/create_index.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { INTERNAL_API_BASE_PATH } from '@kbn/index-management-plugin/common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + + describe('create index', async () => { + const testIndices = ['my-test-index-001', 'my-test-index-002']; + before(async () => { + await esDeleteAllIndices(testIndices); + }); + after(async () => { + await esDeleteAllIndices(testIndices); + }); + + it('can create an index', async () => { + const indexName = testIndices[0]; + await supertest + .put(`${INTERNAL_API_BASE_PATH}/indices/create`) + .set('kbn-xsrf', 'xxx') + .send({ + indexName, + }) + .expect(200); + + // Make sure the index is created + const { + body: [cat1], + } = await es.cat.indices({ index: indexName, format: 'json' }, { meta: true }); + expect(cat1.status).to.be('open'); + }); + + it(`throws 400 when index already created`, async () => { + const indexName = testIndices[1]; + await supertest + .put(`${INTERNAL_API_BASE_PATH}/indices/create`) + .set('kbn-xsrf', 'xxx') + .send({ + indexName, + }) + .expect(200); + + // Make sure the index is created + const { + body: [cat1], + } = await es.cat.indices({ index: indexName, format: 'json' }, { meta: true }); + expect(cat1.status).to.be('open'); + + await supertest + .put(`${INTERNAL_API_BASE_PATH}/indices/create`) + .set('kbn-xsrf', 'xxx') + .send({ + indexName, + }) + .expect(400); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index 0117204343285..520396ad46283 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -112,6 +112,9 @@ export default function ({ getService }: FtrProviderContext) { expect(testDataStream).to.eql({ name: testDataStreamName, + lifecycle: { + enabled: true, + }, privileges: { delete_index: true, }, @@ -166,6 +169,9 @@ export default function ({ getService }: FtrProviderContext) { indexTemplateName: testDataStreamName, maxTimeStamp: 0, hidden: false, + lifecycle: { + enabled: true, + }, }); }); @@ -197,6 +203,9 @@ export default function ({ getService }: FtrProviderContext) { indexTemplateName: testDataStreamName, maxTimeStamp: 0, hidden: false, + lifecycle: { + enabled: true, + }, }); }); }); diff --git a/x-pack/test/api_integration/apis/management/index_management/index.js b/x-pack/test/api_integration/apis/management/index_management/index.js index 15d043826ddda..ae74d0fdcd91c 100644 --- a/x-pack/test/api_integration/apis/management/index_management/index.js +++ b/x-pack/test/api_integration/apis/management/index_management/index.js @@ -16,5 +16,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./component_templates')); loadTestFile(require.resolve('./cluster_nodes')); loadTestFile(require.resolve('./index_details')); + loadTestFile(require.resolve('./create_enrich_policy')); }); } diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts index 1e185b88b7587..04d157e79fe65 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts @@ -55,8 +55,14 @@ export function IngestPipelinesAPIProvider({ getService }: FtrProviderContext) { }, async createIndex(index: { index: string; id: string; body: object }) { - log.debug(`Creating index: '${index.index}'`); + const indexExists = await es.indices.exists({ index: index.index }); + + // Index should not exist, but in the case that it already does, we bypass the create request + if (indexExists) { + return; + } + log.debug(`Creating index: '${index.index}'`); return await es.index(index); }, diff --git a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts index 8c2ec686fdb4a..fc91d64e534bc 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts @@ -153,7 +153,7 @@ export default function ({ getService }: FtrProviderContext) { { name: 'A', aggType: 'count', filter: 'event.dataset: "apache2.error"' }, { name: 'B', aggType: 'count' }, ], - equation: '(A / B) * 100', + equation: '((A + A) / (B + B)) * 100', label: 'apache2 error ratio', } as CustomMetricExpressionParams, ], @@ -187,7 +187,7 @@ export default function ({ getService }: FtrProviderContext) { { name: 'A', aggType: 'count', filter: 'event.dataset: "apache2.error"' }, { name: 'B', aggType: 'count' }, ], - equation: '(A / B) * 100', + equation: '((A + A) / (B + B)) * 100', currentValue: 36.195262024407754, timestamp: '2021-10-19T00:53:59.997Z', shouldFire: true, diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts index 84c555f751a53..eb3cd1677d2ba 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts @@ -134,7 +134,7 @@ export default function ({ getService }: FtrProviderContext) { { name: 'A', aggregation: 'avg', field: 'system.cpu.user.pct' }, { name: 'B', aggregation: 'avg', field: 'system.cpu.user.pct' }, ], - equation: '(A + B) * 100', + equation: '((A + A + B + B) / 2) * 100', }, ], }; diff --git a/x-pack/test/api_integration/apis/ml/modules/get_module.ts b/x-pack/test/api_integration/apis/ml/modules/get_module.ts index 7c72f18f918e3..488714f13c399 100644 --- a/x-pack/test/api_integration/apis/ml/modules/get_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/get_module.ts @@ -49,7 +49,8 @@ export default ({ getService }: FtrProviderContext) => { return body; } - describe('get_module', function () { + // FLAKY: https://github.com/elastic/kibana/issues/164420 + describe.skip('get_module', function () { before(async () => { await ml.testResources.setKibanaTimeZoneToUTC(); }); diff --git a/x-pack/test/api_integration/apis/search/search.ts b/x-pack/test/api_integration/apis/search/search.ts index a7bf10ea7dc6c..48ff19e51623d 100644 --- a/x-pack/test/api_integration/apis/search/search.ts +++ b/x-pack/test/api_integration/apis/search/search.ts @@ -455,8 +455,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(404); }); - // FLAKY: https://github.com/elastic/kibana/issues/164856 - it.skip('should delete a completed search', async function () { + it('should delete a completed search', async function () { await markRequiresShardDelayAgg(this); const resp = await supertest @@ -483,7 +482,7 @@ export default function ({ getService }: FtrProviderContext) { await new Promise((resolve) => setTimeout(resolve, 3000)); - await retry.tryForTime(10000, async () => { + await retry.tryForTime(30000, async () => { const resp2 = await supertest .post(`/internal/search/ese/${id}`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index d49df52bfcd1c..c786a41411a5b 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -55,7 +55,7 @@ export default function ({ getService }: FtrProviderContext) { 'file_operations_all', 'execute_operations_all', ], - uptime: ['all', 'read', 'minimal_all', 'minimal_read'], + uptime: ['all', 'read', 'minimal_all', 'minimal_read', 'elastic_managed_locations_enabled'], securitySolutionAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index c6982b3c6d53e..6c6d32c1cb1e9 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -130,7 +130,13 @@ export default function ({ getService }: FtrProviderContext) { 'file_operations_all', 'execute_operations_all', ], - uptime: ['all', 'read', 'minimal_all', 'minimal_read'], + uptime: [ + 'all', + 'elastic_managed_locations_enabled', + 'read', + 'minimal_all', + 'minimal_read', + ], securitySolutionAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index e1e730376482d..79a2267cbadef 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -17,7 +17,10 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from './helper/get_fixture_json'; import { comparePolicies, getTestSyntheticsPolicy } from './sample_data/test_policy'; -import { PrivateLocationTestService } from './services/private_location_test_service'; +import { + INSTALLED_VERSION, + PrivateLocationTestService, +} from './services/private_location_test_service'; export default function ({ getService }: FtrProviderContext) { describe('PrivateLocationAddMonitor', function () { @@ -537,7 +540,7 @@ export default function ({ getService }: FtrProviderContext) { pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default` ); - expect(packagePolicy.package.version).eql('1.0.4'); + expect(packagePolicy.package.version).eql(INSTALLED_VERSION); await supertestAPI.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); const policyResponseAfterUpgrade = await supertestAPI.get( @@ -547,7 +550,7 @@ export default function ({ getService }: FtrProviderContext) { (pkgPolicy: PackagePolicy) => pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default` ); - expect(semver.gte(packagePolicyAfterUpgrade.package.version, '1.0.4')).eql(true); + expect(semver.gte(packagePolicyAfterUpgrade.package.version, INSTALLED_VERSION)).eql(true); } finally { await supertestAPI .delete(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) diff --git a/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts b/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts index 4b9346adadc2a..59130c700ede7 100644 --- a/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts @@ -201,6 +201,7 @@ export default function ({ getService }: FtrProviderContext) { schedule: '@every 5m', timeout: '3ms', max_redirects: 3, + max_attempts: 2, proxy_url: 'http://proxy.com', tags: ['tag1', 'tag2'], username: 'test-username', diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts index 5914b116f909a..efbeca6161d56 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts @@ -8,6 +8,8 @@ import { omit, sortBy } from 'lodash'; import expect from '@kbn/expect'; import { PackagePolicy, PackagePolicyConfigRecord } from '@kbn/fleet-plugin/common'; +import { INSTALLED_VERSION } from '../services/private_location_test_service'; +import { commonVars } from './test_project_monitor_policy'; interface PolicyProps { name?: string; @@ -29,7 +31,7 @@ export const getTestSyntheticsPolicy = (props: PolicyProps): PackagePolicy => { version: 'WzE2MjYsMV0=', name: 'test-monitor-name-Test private location 0-default', namespace: namespace ?? 'testnamespace', - package: { name: 'synthetics', title: 'Elastic Synthetics', version: '1.0.4' }, + package: { name: 'synthetics', title: 'Elastic Synthetics', version: INSTALLED_VERSION }, enabled: true, policy_id: '5347cd10-0368-11ed-8df7-a7424c6f5167', inputs: [ @@ -166,6 +168,7 @@ export const getHttpInput = ({ 'ssl.supported_protocols': { type: 'yaml' }, location_id: { value: 'fleet_managed', type: 'text' }, location_name: { value: 'Fleet managed', type: 'text' }, + ...commonVars, id: { type: 'text' }, origin: { type: 'text' }, ipv4: { type: 'bool', value: true }, @@ -241,6 +244,7 @@ export const getHttpInput = ({ value: JSON.stringify(location.name) ?? '"Test private location 0"', type: 'text', }, + ...commonVars, id: { value: JSON.stringify(id), type: 'text' }, origin: { value: projectId ? 'project' : 'ui', type: 'text' }, ipv4: { type: 'bool', value: true }, @@ -261,6 +265,7 @@ export const getHttpInput = ({ schedule: '@every 5m', timeout: '3ms', max_redirects: 3, + max_attempts: 2, proxy_url: proxyUrl ?? 'http://proxy.com', tags: ['tag1', 'tag2'], username: 'test-username', @@ -471,30 +476,14 @@ export const getBrowserInput = ({ id, params, isBrowser, projectId }: PolicyProp streams: [ { enabled: true, - data_stream: { - type: 'synthetics', - dataset: 'browser', - elasticsearch: { - privileges: { - indices: ['auto_configure', 'create_doc', 'read'], - }, - }, - }, + data_stream: getDataStream('browser'), vars: browserVars, id: 'synthetics/browser-browser-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: compiledBrowser, }, { enabled: true, - data_stream: { - type: 'synthetics', - dataset: 'browser.network', - elasticsearch: { - privileges: { - indices: ['auto_configure', 'create_doc', 'read'], - }, - }, - }, + data_stream: getDataStream('browser.network'), id: 'synthetics/browser-browser.network-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: { processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], @@ -502,15 +491,7 @@ export const getBrowserInput = ({ id, params, isBrowser, projectId }: PolicyProp }, { enabled: true, - data_stream: { - type: 'synthetics', - dataset: 'browser.screenshot', - elasticsearch: { - privileges: { - indices: ['auto_configure', 'create_doc', 'read'], - }, - }, - }, + data_stream: getDataStream('browser.screenshot'), id: 'synthetics/browser-browser.screenshot-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: { processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], @@ -520,6 +501,16 @@ export const getBrowserInput = ({ id, params, isBrowser, projectId }: PolicyProp }; }; +export const getDataStream = (dataset: string) => ({ + dataset, + type: 'synthetics', + elasticsearch: { + privileges: { + indices: ['auto_configure', 'create_doc', 'read'], + }, + }, +}); + export const omitIds = (policy: PackagePolicy) => { policy.inputs = sortBy(policy.inputs, 'type'); diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts index 779e13d5b6cfe..73171abe24435 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts @@ -6,6 +6,15 @@ */ import { PackagePolicy } from '@kbn/fleet-plugin/common'; +import { INSTALLED_VERSION } from '../services/private_location_test_service'; +import { getDataStream } from './test_policy'; + +export const commonVars = { + max_attempts: { + type: 'integer', + value: 2, + }, +}; export const getTestProjectSyntheticsPolicyLightweight = ( { @@ -37,7 +46,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( version: 'WzEzMDksMV0=', name: `4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-Test private location 0`, namespace: 'default', - package: { name: 'synthetics', title: 'Elastic Synthetics', version: '1.0.4' }, + package: { name: 'synthetics', title: 'Elastic Synthetics', version: INSTALLED_VERSION }, enabled: true, policy_id: '46034710-0ba6-11ed-ba04-5f123b9faa8b', inputs: [ @@ -122,6 +131,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( type: 'integer', value: '0', }, + ...commonVars, mode: { type: 'text', value: 'any', @@ -240,6 +250,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( schedule: '@every 60m', timeout: '80s', max_redirects: 0, + max_attempts: 2, tags: ['tag2', 'tag2'], proxy_url: 'testGlobalParamOverwrite', 'run_from.geo.name': locationName ?? 'Test private location 0', @@ -314,6 +325,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( 'ssl.supported_protocols': { type: 'yaml' }, location_id: { value: 'fleet_managed', type: 'text' }, location_name: { value: 'Fleet managed', type: 'text' }, + ...commonVars, id: { type: 'text' }, origin: { type: 'text' }, ipv4: { type: 'bool', value: true }, @@ -348,6 +360,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( tags: { type: 'yaml' }, location_id: { value: 'fleet_managed', type: 'text' }, location_name: { value: 'Fleet managed', type: 'text' }, + ...commonVars, id: { type: 'text' }, origin: { type: 'text' }, ipv4: { type: 'bool', value: true }, @@ -416,6 +429,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( 'source.zip_url.proxy_url': { type: 'text' }, location_id: { value: 'fleet_managed', type: 'text' }, location_name: { value: 'Fleet managed', type: 'text' }, + ...commonVars, id: { type: 'text' }, origin: { type: 'text' }, ...inputs, @@ -520,7 +534,7 @@ export const getTestProjectSyntheticsPolicy = ( version: 'WzEzMDksMV0=', name: `4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-Test private location 0`, namespace: 'default', - package: { name: 'synthetics', title: 'Elastic Synthetics', version: '1.0.4' }, + package: { name: 'synthetics', title: 'Elastic Synthetics', version: INSTALLED_VERSION }, enabled: true, policy_id: '46034710-0ba6-11ed-ba04-5f123b9faa8b', inputs: [ @@ -570,6 +584,7 @@ export const getTestProjectSyntheticsPolicy = ( 'ssl.supported_protocols': { type: 'yaml' }, location_id: { value: 'fleet_managed', type: 'text' }, location_name: { value: 'Fleet managed', type: 'text' }, + ...commonVars, id: { type: 'text' }, origin: { type: 'text' }, ipv4: { type: 'bool', value: true }, @@ -612,6 +627,7 @@ export const getTestProjectSyntheticsPolicy = ( 'ssl.verification_mode': { type: 'text' }, 'ssl.supported_protocols': { type: 'yaml' }, location_name: { value: 'Fleet managed', type: 'text' }, + ...commonVars, id: { type: 'text' }, origin: { type: 'text' }, ipv4: { type: 'bool', value: true }, @@ -645,6 +661,10 @@ export const getTestProjectSyntheticsPolicy = ( timeout: { type: 'text' }, tags: { type: 'yaml' }, location_name: { value: 'Fleet managed', type: 'text' }, + max_attempts: { + type: 'integer', + value: 2, + }, id: { type: 'text' }, origin: { type: 'text' }, ipv4: { type: 'bool', value: true }, @@ -662,15 +682,7 @@ export const getTestProjectSyntheticsPolicy = ( streams: [ { enabled: true, - data_stream: { - type: 'synthetics', - dataset: 'browser', - elasticsearch: { - privileges: { - indices: ['auto_configure', 'create_doc', 'read'], - }, - }, - }, + data_stream: getDataStream('browser'), vars: { __ui: { value: '{"script_source":{"is_generated_script":false,"file_name":""}}', @@ -719,6 +731,7 @@ export const getTestProjectSyntheticsPolicy = ( 'source.zip_url.ssl.supported_protocols': { type: 'yaml' }, 'source.zip_url.proxy_url': { type: 'text' }, location_name: { value: 'Test private location 0', type: 'text' }, + ...commonVars, location_id: { value: 'fleet_managed', type: 'text' }, id: { value: id, type: 'text' }, origin: { value: 'project', type: 'text' }, @@ -756,15 +769,7 @@ export const getTestProjectSyntheticsPolicy = ( }, { enabled: true, - data_stream: { - type: 'synthetics', - dataset: 'browser.network', - elasticsearch: { - privileges: { - indices: ['auto_configure', 'create_doc', 'read'], - }, - }, - }, + data_stream: getDataStream('browser.network'), id: `synthetics/browser-browser.network-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, compiled_stream: { processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], @@ -772,15 +777,7 @@ export const getTestProjectSyntheticsPolicy = ( }, { enabled: true, - data_stream: { - type: 'synthetics', - dataset: 'browser.screenshot', - elasticsearch: { - privileges: { - indices: ['auto_configure', 'create_doc', 'read'], - }, - }, - }, + data_stream: getDataStream('browser.screenshot'), id: `synthetics/browser-browser.screenshot-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, compiled_stream: { processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], diff --git a/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts index 6846f16e190b6..60ee844e11ab8 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts @@ -10,6 +10,8 @@ import { privateLocationsSavedObjectId } from '@kbn/synthetics-plugin/server/sav import { FtrProviderContext } from '../../../ftr_provider_context'; import { KibanaSupertestProvider } from '../../../../../../test/api_integration/services/supertest'; +export const INSTALLED_VERSION = '1.0.7'; + export class PrivateLocationTestService { private supertest: ReturnType; private readonly getService: FtrProviderContext['getService']; @@ -22,12 +24,12 @@ export class PrivateLocationTestService { async installSyntheticsPackage() { await this.supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); const response = await this.supertest - .get('/api/fleet/epm/packages/synthetics/1.0.4') + .get(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`) .set('kbn-xsrf', 'true') .expect(200); if (response.body.item.status !== 'installed') { await this.supertest - .post('/api/fleet/epm/packages/synthetics/1.0.4') + .post(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`) .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); diff --git a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts index e34caa20fcdbc..765138034f772 100644 --- a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts +++ b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts @@ -23,7 +23,8 @@ import { comparePolicies, getTestSyntheticsPolicy } from './sample_data/test_pol export default function ({ getService }: FtrProviderContext) { // FLAKY: https://github.com/elastic/kibana/issues/162594 - describe.skip('SyncGlobalParams', function () { + // Failing: See https://github.com/elastic/kibana/issues/162594 + describe('SyncGlobalParams', function () { this.tags('skipCloud'); const supertestAPI = getService('supertest'); const kServer = getService('kibanaServer'); @@ -42,6 +43,7 @@ export default function ({ getService }: FtrProviderContext) { const params: Record = {}; before(async () => { + await kServer.savedObjects.cleanStandardList(); await testPrivateLocations.installSyntheticsPackage(); _browserMonitorJson = getFixtureJson('browser_monitor'); diff --git a/x-pack/test/api_integration/apis/transform/stop_transforms.ts b/x-pack/test/api_integration/apis/transform/stop_transforms.ts index 7811432e7417a..c982c6b45f1c9 100644 --- a/x-pack/test/api_integration/apis/transform/stop_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/stop_transforms.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import type { PutTransformsRequestSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; import type { StopTransformsRequestSchema } from '@kbn/transform-plugin/common/api_schemas/stop_transforms'; -import { isStopTransformsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; @@ -76,7 +75,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body[transformId].success).to.eql(true); expect(typeof body[transformId].error).to.eql('undefined'); await transform.api.waitForTransformState(transformId, TRANSFORM_STATE.STOPPED); @@ -97,7 +95,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body[transformId].success).to.eql(false); expect(typeof body[transformId].error).to.eql('object'); @@ -121,7 +118,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body.invalid_transform_id.success).to.eql(false); expect(body.invalid_transform_id).to.have.property('error'); }); @@ -158,8 +154,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); - await asyncForEach(reqBody, async ({ id: transformId }: { id: string }, idx: number) => { expect(body[transformId].success).to.eql(true); await transform.api.waitForTransformState(transformId, TRANSFORM_STATE.STOPPED); @@ -183,8 +177,6 @@ export default ({ getService }: FtrProviderContext) => { ]); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); - await asyncForEach(reqBody, async ({ id: transformId }: { id: string }, idx: number) => { expect(body[transformId].success).to.eql(true); await transform.api.waitForTransformState(transformId, TRANSFORM_STATE.STOPPED); diff --git a/x-pack/test/api_integration/apis/transform/transforms.ts b/x-pack/test/api_integration/apis/transform/transforms.ts index 4c3d1641d6d5f..2016876ba93a6 100644 --- a/x-pack/test/api_integration/apis/transform/transforms.ts +++ b/x-pack/test/api_integration/apis/transform/transforms.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import type { GetTransformsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; -import { isGetTransformsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { getCommonRequestHeader } from '../../../functional/services/ml/common_api'; import { USER } from '../../../functional/services/transform/security_common'; @@ -43,8 +42,6 @@ export default ({ getService }: FtrProviderContext) => { } function assertTransformsResponseBody(body: GetTransformsResponseSchema) { - expect(isGetTransformsResponseSchema(body)).to.eql(true); - expect(body.count).to.eql(expected.apiTransformTransforms.count); expect(body.transforms).to.have.length(expected.apiTransformTransforms.count); @@ -62,8 +59,6 @@ export default ({ getService }: FtrProviderContext) => { } function assertSingleTransformResponseBody(body: GetTransformsResponseSchema) { - expect(isGetTransformsResponseSchema(body)).to.eql(true); - expect(body.count).to.eql(expected.apiTransformTransformsTransformId.count); expect(body.transforms).to.have.length(expected.apiTransformTransformsTransformId.count); diff --git a/x-pack/test/api_integration/apis/transform/transforms_nodes.ts b/x-pack/test/api_integration/apis/transform/transforms_nodes.ts index bf3e91df69684..10f4413a8f85f 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_nodes.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_nodes.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import type { GetTransformNodesResponseSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; -import { isGetTransformNodesResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { getCommonRequestHeader } from '../../../functional/services/ml/common_api'; import { USER } from '../../../functional/services/transform/security_common'; @@ -25,8 +24,6 @@ export default ({ getService }: FtrProviderContext) => { }; function assertTransformsNodesResponseBody(body: GetTransformNodesResponseSchema) { - expect(isGetTransformNodesResponseSchema(body)).to.eql(true); - expect(body.count).to.not.be.lessThan(expected.apiTransformTransformsNodes.minCount); } diff --git a/x-pack/test/api_integration/apis/transform/transforms_stats.ts b/x-pack/test/api_integration/apis/transform/transforms_stats.ts index 41cdafd0c058f..aea5c049343e9 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_stats.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_stats.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import type { GetTransformsStatsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/transforms_stats'; -import { isGetTransformsStatsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; import { getCommonRequestHeader } from '../../../functional/services/ml/common_api'; @@ -39,7 +38,6 @@ export default ({ getService }: FtrProviderContext) => { } function assertTransformsStatsResponseBody(body: GetTransformsStatsResponseSchema) { - expect(isGetTransformsStatsResponseSchema(body)).to.eql(true); expect(body.count).to.eql(expected.apiTransformTransforms.count); expect(body.transforms).to.have.length(expected.apiTransformTransforms.count); diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts index 447aebf727767..94193f2946ece 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts @@ -219,6 +219,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); }); }); @@ -253,6 +256,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { location } = response.currentPeriod.mostRequests; expect(location).to.be('China'); }); + + it('returns location for most crashes', () => { + const { location } = response.currentPeriod.mostCrashes; + expect(location).to.be('China'); + }); }); describe('when filters are applied', () => { @@ -265,6 +273,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostSessions.value).to.eql(0); expect(response.currentPeriod.mostRequests.value).to.eql(0); + expect(response.currentPeriod.mostCrashes.value).to.eql(0); expect(response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0)).to.eql( true @@ -272,6 +281,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); it('returns the correct values when single filter is applied', async () => { @@ -283,6 +295,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostSessions.value).to.eql(3); expect(response.currentPeriod.mostRequests.value).to.eql(3); + expect(response.currentPeriod.mostCrashes.value).to.eql(3); }); it('returns the correct values when multiple filters are applied', async () => { @@ -293,6 +306,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostSessions.value).to.eql(3); expect(response.currentPeriod.mostRequests.value).to.eql(3); + expect(response.currentPeriod.mostCrashes.value).to.eql(3); }); }); }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts index 477d5456315cf..edc852d97ad2a 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts @@ -10,7 +10,7 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { sumBy } from 'lodash'; +import { sumBy, meanBy } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>; @@ -103,7 +103,7 @@ async function generateData({ return [ galaxy10 .transaction('Start View - View Appearing', 'Android Activity') - .errors(galaxy10.crash({ message: 'error' }).timestamp(timestamp)) + .errors(galaxy10.crash({ message: 'error C' }).timestamp(timestamp)) .timestamp(timestamp) .duration(500) .success() @@ -120,7 +120,11 @@ async function generateData({ ), huaweiP2 .transaction('Start View - View Appearing', 'huaweiP2 Activity') - .errors(huaweiP2.crash({ message: 'error' }).timestamp(timestamp)) + .errors( + huaweiP2.crash({ message: 'error A' }).timestamp(timestamp), + huaweiP2.crash({ message: 'error B' }).timestamp(timestamp), + huaweiP2.crash({ message: 'error D' }).timestamp(timestamp) + ) .timestamp(timestamp) .duration(20) .success(), @@ -211,6 +215,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { const timeseriesTotal = sumBy(timeseries, 'y'); expect(value).to.be(timeseriesTotal); }); + + it('returns same crashes', () => { + const { value, timeseries } = response.currentPeriod.crashRate; + const timeseriesMean = meanBy( + timeseries.filter((bucket) => bucket.y !== 0), + 'y' + ); + expect(value).to.be(timeseriesMean); + }); }); describe('when filters are applied', () => { @@ -223,6 +236,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.sessions.value).to.eql(0); expect(response.currentPeriod.requests.value).to.eql(0); + expect(response.currentPeriod.crashRate.value).to.eql(0); expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql( true @@ -230,6 +244,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.crashRate.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); it('returns the correct values when single filter is applied', async () => { @@ -241,6 +258,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.sessions.value).to.eql(3); expect(response.currentPeriod.requests.value).to.eql(0); + expect(response.currentPeriod.crashRate.value).to.eql(3); }); it('returns the correct values when multiple filters are applied', async () => { @@ -248,9 +266,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { serviceName: 'synth-android', kuery: `service.version:"1.2" and service.environment: "production"`, }); - expect(response.currentPeriod.sessions.value).to.eql(3); expect(response.currentPeriod.requests.value).to.eql(3); + expect(response.currentPeriod.crashRate.value).to.eql(1); }); }); }); diff --git a/x-pack/test/apm_api_integration/tests/sourcemaps/sourcemaps.ts b/x-pack/test/apm_api_integration/tests/sourcemaps/sourcemaps.ts index 769041847bb3f..2b02096f57d39 100644 --- a/x-pack/test/apm_api_integration/tests/sourcemaps/sourcemaps.ts +++ b/x-pack/test/apm_api_integration/tests/sourcemaps/sourcemaps.ts @@ -27,16 +27,6 @@ const SAMPLE_SOURCEMAP = { mappings: 'A,AAAB;;ABCDE;', }; -const SAMPLE_ANDROID_MAP = `# compiler: R8 -# compiler_version: 3.2.47 -# min_api: 26 -# common_typos_disable -# {"id":"com.android.tools.r8.mapping","version":"2.0"} -# pg_map_id: 127b14c -# pg_map_hash: SHA-256 127b14c0be5dd1b55beee544a8d0e7c9414b432868ed8bc54ca5cc43cba12435 -a1.TableInfo$ForeignKey$$ExternalSyntheticOutline0 -> a1.e: -# {"id":"sourceFile","fileName":"R8$$SyntheticClass"}`; - export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); const apmApiClient = getService('apmApiClient'); @@ -109,28 +99,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { return response.body; } - async function uploadAndroidMap({ - serviceName, - serviceVersion, - androidMap, - }: { - serviceName: string; - serviceVersion: string; - androidMap: string; - }) { - const response = await apmApiClient.writeUser({ - endpoint: 'POST /api/apm/androidmaps 2023-10-31', - type: 'form-data', - params: { - body: { - service_name: serviceName, - service_version: serviceVersion, - map_file: androidMap, - }, - }, - }); - return response.body; - } async function runSourceMapMigration() { await apmApiClient.writeUser({ endpoint: 'POST /internal/apm/sourcemaps/migrate_fleet_artifacts', @@ -160,12 +128,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { await Promise.all([deleteAllFleetSourceMaps(), deleteAllApmSourceMaps()]); }); - async function getDecodedMapContent(encodedContent?: string): Promise { - if (encodedContent) { - return (await unzip(Buffer.from(encodedContent, 'base64'))).toString(); - } - } - async function getDecodedSourceMapContent( encodedContent?: string ): Promise { @@ -281,111 +243,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - let androidResp: APIReturnType<'POST /api/apm/androidmaps 2023-10-31'>; - describe('upload android map', () => { - after(async () => { - await apmApiClient.writeUser({ - endpoint: 'DELETE /api/apm/sourcemaps/{id} 2023-10-31', - params: { path: { id: androidResp.id } }, - }); - }); - - before(async () => { - androidResp = await uploadAndroidMap({ - serviceName: 'uploading-test', - serviceVersion: '1.0.0', - androidMap: SAMPLE_ANDROID_MAP, - }); - - await waitForSourceMapCount(1); - }); - - it('is uploaded as a fleet artifact', async () => { - const res = await es.search({ - index: '.fleet-artifacts', - size: 1, - query: { - bool: { - filter: [{ term: { type: 'sourcemap' } }, { term: { package_name: 'apm' } }], - }, - }, - }); - - // @ts-expect-error - expect(res.hits.hits[0]._source.identifier).to.be('uploading-test-1.0.0-android'); - }); - - it('is added to .apm-source-map index', async () => { - const res = await es.search({ - index: '.apm-source-map', - }); - - const source = res.hits.hits[0]._source; - const decodedSourceMap = await getDecodedMapContent(source?.content); - expect(decodedSourceMap).to.eql(SAMPLE_ANDROID_MAP); - expect(source?.content_sha256).to.be( - '702e07279b0fbed47fdbf5e71528dff845b4f07a16ca79cab0c1b06eb71be966' - ); - expect(source?.file.path).to.be('android'); - expect(source?.service.name).to.be('uploading-test'); - expect(source?.service.version).to.be('1.0.0'); - }); - - describe('when uploading a new android map with the same service.name and service.version', () => { - let resBefore: GetResponse; - let resAfter: GetResponse; - - before(async () => { - async function getSourceMapDocFromApmIndex() { - await es.indices.refresh({ index: '.apm-source-map' }); - return await es.get({ - index: '.apm-source-map', - id: 'uploading-test-1.0.0-android', - }); - } - - resBefore = await getSourceMapDocFromApmIndex(); - - await uploadAndroidMap({ - serviceName: 'uploading-test', - serviceVersion: '1.0.0', - androidMap: '# compiler: R8\n# ANOTHER MAP', - }); - - resAfter = await getSourceMapDocFromApmIndex(); - }); - - after(async () => { - await deleteAllApmSourceMaps(); - await deleteAllFleetSourceMaps(); - }); - - it('creates one document in the .apm-source-map index', async () => { - const res = await es.search({ index: '.apm-source-map', size: 0 }); - - // @ts-expect-error - expect(res.hits.total.value).to.be(1); - }); - - it('creates two documents in the .fleet-artifacts index', async () => { - const res = await listSourcemaps({ page: 1, perPage: 10 }); - expect(res.total).to.be(2); - }); - - it('updates the content', async () => { - const contentBefore = await getDecodedMapContent(resBefore._source?.content); - const contentAfter = await getDecodedMapContent(resAfter._source?.content); - - expect(contentBefore).to.be(SAMPLE_ANDROID_MAP); - expect(contentAfter).to.be('# compiler: R8\n# ANOTHER MAP'); - }); - - it('updates the content hash', async () => { - expect(resBefore._source?.content_sha256).to.not.be(resAfter._source?.content_sha256); - }); - }); - }); - describe('list source maps', async () => { before(async () => { const totalCount = 6; diff --git a/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts b/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts new file mode 100644 index 0000000000000..3cd580a6e7a96 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable @typescript-eslint/no-shadow */ + +import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { apm, timerange, DistributedTrace } from '@kbn/apm-synthtrace-client'; + +const RATE_PER_MINUTE = 1; + +export function generateLargeTrace({ + start, + end, + rootTransactionName, + synthtraceEsClient, + repeaterFactor, + environment, +}: { + start: number; + end: number; + rootTransactionName: string; + synthtraceEsClient: ApmSynthtraceEsClient; + repeaterFactor: number; + environment: string; +}) { + const range = timerange(start, end); + + const synthRum = apm + .service({ name: 'synth-rum', environment, agentName: 'rum-js' }) + .instance('my-instance'); + + const synthNode = apm + .service({ name: 'synth-node', environment, agentName: 'nodejs' }) + .instance('my-instance'); + + const synthGo = apm + .service({ name: 'synth-go', environment, agentName: 'go' }) + .instance('my-instance'); + + const synthDotnet = apm + .service({ name: 'synth-dotnet', environment, agentName: 'dotnet' }) + .instance('my-instance'); + + const synthJava = apm + .service({ name: 'synth-java', environment, agentName: 'java' }) + .instance('my-instance'); + + const traces = range.ratePerMinute(RATE_PER_MINUTE).generator((timestamp) => { + return new DistributedTrace({ + serviceInstance: synthRum, + transactionName: rootTransactionName, + timestamp, + children: (_) => { + _.service({ + repeat: 5 * repeaterFactor, + serviceInstance: synthNode, + transactionName: 'GET /nodejs/products', + latency: 100, + + children: (_) => { + _.service({ + serviceInstance: synthGo, + transactionName: 'GET /go', + children: (_) => { + _.service({ + repeat: 5 * repeaterFactor, + serviceInstance: synthJava, + transactionName: 'GET /java', + children: (_) => { + _.external({ + name: 'GET telemetry.elastic.co', + url: 'https://telemetry.elastic.co/ping', + duration: 50, + }); + }, + }); + }, + }); + _.db({ + name: 'GET apm-*/_search', + type: 'elasticsearch', + duration: 400, + }); + _.db({ name: 'GET', type: 'redis', duration: 500 }); + _.db({ + name: 'SELECT * FROM users', + type: 'sqlite', + duration: 600, + }); + }, + }); + + _.service({ + serviceInstance: synthNode, + transactionName: 'GET /nodejs/users', + latency: 100, + repeat: 5 * repeaterFactor, + children: (_) => { + _.service({ + serviceInstance: synthGo, + transactionName: 'GET /go/security', + latency: 50, + children: (_) => { + _.service({ + repeat: 5 * repeaterFactor, + serviceInstance: synthDotnet, + transactionName: 'GET /dotnet/cases/4', + latency: 50, + children: (_) => + _.db({ + name: 'GET apm-*/_search', + type: 'elasticsearch', + duration: 600, + statement: JSON.stringify( + { + query: { + query_string: { + query: '(new york city) OR (big apple)', + default_field: 'content', + }, + }, + }, + null, + 2 + ), + }), + }); + }, + }); + }, + }); + }, + }).getTransaction(); + }); + + return synthtraceEsClient.index(traces); +} diff --git a/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts b/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts new file mode 100644 index 0000000000000..013a9e3668e04 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts @@ -0,0 +1,153 @@ +/* + * Copyright 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 { + PROCESSOR_EVENT, + TRACE_ID, + SERVICE_ENVIRONMENT, + TRANSACTION_ID, + PARENT_ID, +} from '@kbn/apm-plugin/common/es_fields/apm'; +import type { Client } from '@elastic/elasticsearch'; +import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import { ApmApiClient } from '../../../common/config'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { generateLargeTrace } from './generate_large_trace'; + +const start = new Date('2023-01-01T00:00:00.000Z').getTime(); +const end = new Date('2023-01-01T00:01:00.000Z').getTime() - 1; +const rootTransactionName = 'Long trace'; +const environment = 'long_trace_scenario'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + const es = getService('es'); + + registry.when('Large trace', { config: 'basic', archives: [] }, () => { + describe('when the trace is large (>15.000 items)', () => { + before(async () => { + await synthtraceEsClient.clean(); + await generateLargeTrace({ + start, + end, + rootTransactionName, + synthtraceEsClient, + repeaterFactor: 10, + environment, + }); + }); + + after(async () => { + await synthtraceEsClient.clean(); + }); + + describe('when maxTraceItems is 5000 (default)', () => { + let trace: APIReturnType<'GET /internal/apm/traces/{traceId}'>; + before(async () => { + trace = await getTrace({ es, apmApiClient, maxTraceItems: 5000 }); + }); + + it('and traceDocsTotal is correct', () => { + expect(trace.traceItems.traceDocsTotal).to.be(15551); + }); + + it('and traceDocs is correct', () => { + expect(trace.traceItems.traceDocs.length).to.be(5000); + }); + + it('and maxTraceItems is correct', () => { + expect(trace.traceItems.maxTraceItems).to.be(5000); + }); + + it('and exceedsMax is correct', () => { + expect(trace.traceItems.exceedsMax).to.be(true); + }); + }); + + describe('when maxTraceItems is 20000', () => { + let trace: APIReturnType<'GET /internal/apm/traces/{traceId}'>; + before(async () => { + trace = await getTrace({ es, apmApiClient, maxTraceItems: 20000 }); + }); + + it('and traceDocsTotal is correct', () => { + expect(trace.traceItems.traceDocsTotal).to.be(15551); + }); + + it('and traceDocs is correct', () => { + expect(trace.traceItems.traceDocs.length).to.be(15551); + }); + + it('and maxTraceItems is correct', () => { + expect(trace.traceItems.maxTraceItems).to.be(20000); + }); + + it('and exceedsMax is correct', () => { + expect(trace.traceItems.exceedsMax).to.be(false); + }); + }); + }); + }); +} + +async function getRootTransaction(es: Client) { + const params = { + index: 'traces-apm*', + _source: [TRACE_ID, TRANSACTION_ID], + body: { + query: { + bool: { + filter: [ + { term: { [PROCESSOR_EVENT]: 'transaction' } }, + { term: { [SERVICE_ENVIRONMENT]: environment } }, + ], + must_not: [{ exists: { field: PARENT_ID } }], + }, + }, + }, + }; + + interface Hit { + trace: { id: string }; + transaction: { id: string }; + } + + const res = await es.search(params); + + return { + traceId: res.hits.hits[0]?._source?.trace.id as string, + transactionId: res.hits.hits[0]?._source?.transaction.id as string, + }; +} + +async function getTrace({ + es, + apmApiClient, + maxTraceItems, +}: { + es: Client; + apmApiClient: ApmApiClient; + maxTraceItems?: number; +}) { + const rootTransaction = await getRootTransaction(es); + const res = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}`, + params: { + path: { traceId: rootTransaction.traceId }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + entryTransactionId: rootTransaction.transactionId, + maxTraceItems, + }, + }, + }); + + return res.body; +} diff --git a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts index b055bcf276438..6df809b594416 100644 --- a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts +++ b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; import { Readable } from 'stream'; @@ -17,30 +18,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { const start = new Date('2022-01-01T00:00:00.000Z').getTime(); const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; - async function fetchTraces({ - traceId, - query, - }: { - traceId: string; - query: { start: string; end: string; entryTransactionId: string }; - }) { - return await apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces/{traceId}`, - params: { - path: { traceId }, - query, - }, - }); - } - registry.when('Trace does not exist', { config: 'basic', archives: [] }, () => { it('handles empty state', async () => { - const response = await fetchTraces({ - traceId: 'foo', - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - entryTransactionId: 'foo', + const response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}`, + params: { + path: { traceId: 'foo' }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + entryTransactionId: 'foo', + }, }, }); @@ -51,7 +39,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { traceDocs: [], errorDocs: [], spanLinksCountById: {}, - traceItemCount: 0, + traceDocsTotal: 0, maxTraceItems: 5000, }, }); @@ -61,6 +49,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Trace exists', { config: 'basic', archives: [] }, () => { let entryTransactionId: string; let serviceATraceId: string; + before(async () => { const instanceJava = apm .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) @@ -106,19 +95,24 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(() => synthtraceEsClient.clean()); describe('return trace', () => { - let traces: Awaited>['body']; + let traces: APIReturnType<'GET /internal/apm/traces/{traceId}'>; before(async () => { - const response = await fetchTraces({ - traceId: serviceATraceId, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - entryTransactionId, + const response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}`, + params: { + path: { traceId: serviceATraceId }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + entryTransactionId, + }, }, }); + expect(response.status).to.eql(200); traces = response.body; }); + it('returns some errors', () => { expect(traces.traceItems.errorDocs.length).to.be.greaterThan(0); expect(traces.traceItems.errorDocs[0].error.exception?.[0].message).to.eql( diff --git a/x-pack/test/cases_api_integration/common/lib/api/index.ts b/x-pack/test/cases_api_integration/common/lib/api/index.ts index f32a578103584..6a539457849e6 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/index.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/index.ts @@ -433,6 +433,7 @@ export const updateCase = async ({ const { body: cases } = await apiCall .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') .set(headers) .send(params) .expect(expectedHttpCode); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/attachments_framework/registered_persistable_state_basic.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/attachments_framework/registered_persistable_state_basic.ts new file mode 100644 index 0000000000000..bb7ae7d04c418 --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/attachments_framework/registered_persistable_state_basic.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + + /** + * Attachment types are being registered in + * x-pack/test/cases_api_integration/common/plugins/cases/server/plugin.ts + */ + describe('Persistable state attachments', () => { + // This test is intended to fail when new persistable state attachment types are registered. + // To resolve, add the new persistable state attachment types ID to this list. This will trigger + // a CODEOWNERS review by Response Ops. + describe('check registered persistable state attachment types', () => { + const getRegisteredTypes = () => { + return supertest + .get('/api/cases_fixture/registered_persistable_state_attachments') + .expect(200) + .then((response) => response.body); + }; + + it('should check changes on all registered persistable state attachment types', async () => { + const types = await getRegisteredTypes(); + + expect(types).to.eql({ + '.lens': '78559fd806809ac3a1008942ead2a079864054f5', + '.test': 'ab2204830c67f5cf992c9aa2f7e3ead752cc60a1', + aiopsChangePointChart: 'a1212d71947ec34487b374cecc47ab9941b5d91c', + }); + }); + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts index 6fc840f873ba1..2750559ca3fb5 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts @@ -30,6 +30,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => { loadTestFile(require.resolve('./cases/assignees')); loadTestFile(require.resolve('./cases/push_case')); loadTestFile(require.resolve('./configure/get_connectors')); + loadTestFile(require.resolve('./attachments_framework/registered_persistable_state_basic')); // Internal routes loadTestFile(require.resolve('./internal/suggest_user_profiles')); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/persistable_state.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/persistable_state.ts index 8c9701ed58e33..24d9cc5132c64 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/persistable_state.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/persistable_state.ts @@ -273,29 +273,5 @@ export default ({ getService }: FtrProviderContext): void => { }); }); }); - - // This test is intended to fail when new persistable state attachment types are registered. - // To resolve, add the new persistable state attachment types ID to this list. This will trigger - // a CODEOWNERS review by Response Ops. - describe('check registered persistable state attachment types', () => { - const getRegisteredTypes = () => { - return supertest - .get('/api/cases_fixture/registered_persistable_state_attachments') - .expect(200) - .then((response) => response.body); - }; - - it('should check changes on all registered persistable state attachment types', async () => { - const types = await getRegisteredTypes(); - - expect(types).to.eql({ - '.lens': '78559fd806809ac3a1008942ead2a079864054f5', - '.test': 'ab2204830c67f5cf992c9aa2f7e3ead752cc60a1', - aiopsChangePointChart: 'a1212d71947ec34487b374cecc47ab9941b5d91c', - ml_anomaly_charts: '23e92e824af9db6e8b8bb1d63c222e04f57d2147', - ml_anomaly_swimlane: 'a3517f3e53fb041e9cbb150477fb6ef0f731bd5f', - }); - }); - }); }); }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts index 2cc6d249ef130..d4032bf7ea909 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts @@ -54,7 +54,8 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - describe('delete_comment', () => { + // Failing: See https://github.com/elastic/kibana/issues/157589 + describe.skip('delete_comment', () => { afterEach(async () => { await deleteCasesByESQuery(es); await deleteComments(es); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/attachments_framework/registered_persistable_state_trial.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/attachments_framework/registered_persistable_state_trial.ts new file mode 100644 index 0000000000000..3b2b536b3c88d --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/attachments_framework/registered_persistable_state_trial.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + + /** + * Attachment types are being registered in + * x-pack/test/cases_api_integration/common/plugins/cases/server/plugin.ts + */ + describe('Persistable state attachments', () => { + // This test is intended to fail when new persistable state attachment types are registered. + // To resolve, add the new persistable state attachment types ID to this list. This will trigger + // a CODEOWNERS review by Response Ops. + describe('check registered persistable state attachment types', () => { + const getRegisteredTypes = () => { + return supertest + .get('/api/cases_fixture/registered_persistable_state_attachments') + .expect(200) + .then((response) => response.body); + }; + + it('should check changes on all registered persistable state attachment types', async () => { + const types = await getRegisteredTypes(); + + expect(types).to.eql({ + '.lens': '78559fd806809ac3a1008942ead2a079864054f5', + '.test': 'ab2204830c67f5cf992c9aa2f7e3ead752cc60a1', + aiopsChangePointChart: 'a1212d71947ec34487b374cecc47ab9941b5d91c', + ml_anomaly_charts: '23e92e824af9db6e8b8bb1d63c222e04f57d2147', + ml_anomaly_swimlane: 'a3517f3e53fb041e9cbb150477fb6ef0f731bd5f', + }); + }); + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts index 9e68a8379b702..f6ef4d3ede478 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts @@ -33,6 +33,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => { loadTestFile(require.resolve('./cases/assignees')); loadTestFile(require.resolve('./cases/find_cases')); loadTestFile(require.resolve('./configure')); + loadTestFile(require.resolve('./attachments_framework/registered_persistable_state_trial')); // sub privileges are only available with a license above basic loadTestFile(require.resolve('./delete_sub_privilege')); loadTestFile(require.resolve('./user_profiles/get_current')); diff --git a/x-pack/test/defend_workflows_cypress/endpoint_serverless_config.ts b/x-pack/test/defend_workflows_cypress/endpoint_serverless_config.ts new file mode 100644 index 0000000000000..ef7bda4d01d03 --- /dev/null +++ b/x-pack/test/defend_workflows_cypress/endpoint_serverless_config.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getLocalhostRealIp } from '@kbn/security-solution-plugin/scripts/endpoint/common/localhost_services'; +import { FtrConfigProviderContext } from '@kbn/test'; + +import { ExperimentalFeatures } from '@kbn/security-solution-plugin/common/experimental_features'; +import { DefendWorkflowsCypressCliTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const defendWorkflowsCypressConfig = await readConfigFile(require.resolve('./config.ts')); + const svlSharedConfig = await readConfigFile( + require.resolve('../../test_serverless/shared/config.base.ts') + ); + + const hostIp = getLocalhostRealIp(); + + const enabledFeatureFlags: Array = []; + + return { + ...svlSharedConfig.getAll(), + esTestCluster: { + ...svlSharedConfig.get('esTestCluster'), + serverArgs: [ + ...svlSharedConfig.get('esTestCluster.serverArgs'), + // define custom es server here + // API Keys is enabled at the top level + ], + }, + servers: { + ...svlSharedConfig.get('servers'), + fleetserver: { + protocol: 'https', + hostname: hostIp, + port: 8220, + }, + }, + kbnTestServer: { + ...svlSharedConfig.get('kbnTestServer'), + serverArgs: [ + ...svlSharedConfig.get('kbnTestServer.serverArgs'), + '--csp.strict=false', + '--csp.warnLegacyBrowsers=false', + '--serverless=security', + '--xpack.encryptedSavedObjects.encryptionKey="abcdefghijklmnopqrstuvwxyz123456"', + + '--xpack.security.enabled=true', + `--xpack.fleet.agents.fleet_server.hosts=["https://${hostIp}:8220"]`, + `--xpack.fleet.agents.elasticsearch.host=http://${hostIp}:${defendWorkflowsCypressConfig.get( + 'servers.elasticsearch.port' + )}`, + + // set the packagerTaskInterval to 5s in order to speed up test executions when checking fleet artifacts + '--xpack.securitySolution.packagerTaskInterval=5s', + + `--xpack.securitySolution.enableExperimental=${JSON.stringify(enabledFeatureFlags)}`, + ], + }, + testRunner: DefendWorkflowsCypressCliTestRunner, + }; +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts index 5fb1f0d87a585..3a016fe68618d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts @@ -82,7 +82,7 @@ export default ({ getService }: FtrProviderContext) => { // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe expect(body?.execution_summary?.last_execution.message).to.eql( - `This rule may not have the required read privileges to the following indices/index patterns: ["${index[0]}"]` + `This rule may not have the required read privileges to the following index patterns: ["${index[0]}"]` ); await deleteUserAndRole(getService, ROLES.detections_admin); @@ -121,7 +121,7 @@ export default ({ getService }: FtrProviderContext) => { // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe expect(body?.execution_summary?.last_execution.message).to.eql( - `This rule may not have the required read privileges to the following indices/index patterns: ["${index[0]}"]` + `This rule may not have the required read privileges to the following index patterns: ["${index[0]}"]` ); await deleteUserAndRole(getService, ROLES.detections_admin); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts index d8fc2ec1439be..7df1ae742640f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts @@ -165,7 +165,7 @@ export default ({ getService }: FtrProviderContext) => { // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe expect(rule?.execution_summary?.last_execution.status).to.eql('partial failure'); expect(rule?.execution_summary?.last_execution.message).to.eql( - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["does-not-exist-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.' + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["does-not-exist-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.' ); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts index 44ac35cf9569d..80719b25ef295 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts @@ -7,7 +7,10 @@ import expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + DETECTION_ENGINE_RULES_URL, + UPDATE_OR_CREATE_LEGACY_ACTIONS, +} from '@kbn/security-solution-plugin/common/constants'; import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { @@ -427,8 +430,9 @@ export default ({ getService }: FtrProviderContext): void => { // attach the legacy notification await supertest - .post(`/internal/api/detection/legacy/notifications?alert_id=${rule.id}`) + .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}?alert_id=${rule.id}`) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .send({ name: 'Legacy notification with one action', interval: '1h', @@ -496,8 +500,9 @@ export default ({ getService }: FtrProviderContext): void => { // attach the legacy notification with actions await supertest - .post(`/internal/api/detection/legacy/notifications?alert_id=${rule.id}`) + .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}?alert_id=${rule.id}`) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .send({ name: 'Legacy notification with one action', interval: '1h', @@ -585,8 +590,9 @@ export default ({ getService }: FtrProviderContext): void => { // attach the legacy notification with actions to the first rule await supertest - .post(`/internal/api/detection/legacy/notifications?alert_id=${rule1.id}`) + .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}?alert_id=${rule1.id}`) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .send({ name: 'Legacy notification with one action', interval: '1h', @@ -615,8 +621,9 @@ export default ({ getService }: FtrProviderContext): void => { // attach the legacy notification with actions to the 2nd rule await supertest - .post(`/internal/api/detection/legacy/notifications?alert_id=${rule2.id}`) + .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}?alert_id=${rule2.id}`) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .send({ name: 'Legacy notification with one action', interval: '1h', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts index 831c41dc07063..dc63651aafc14 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts @@ -67,6 +67,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: references } = await supertest .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .query({ ids: `${exceptionList.id}`, list_ids: `${exceptionList.list_id}`, @@ -120,6 +121,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: references } = await supertest .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .query({ ids: `1234`, list_ids: `i_dont_exist`, @@ -166,6 +168,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: references } = await supertest .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .query({ ids: `${exceptionList.id},${exceptionList2.id}`, list_ids: `${exceptionList.list_id},${exceptionList2.list_id}`, @@ -214,6 +217,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: references } = await supertest .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .query({ namespace_types: 'single,agnostic', }) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts index 5e5d193d55d8a..81a7c8c749172 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts @@ -7,7 +7,10 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + DETECTION_ENGINE_RULES_URL, + UPDATE_OR_CREATE_LEGACY_ACTIONS, +} from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, @@ -211,8 +214,9 @@ export default ({ getService }: FtrProviderContext): void => { // attach the legacy notification await supertest - .post(`/internal/api/detection/legacy/notifications?alert_id=${createRuleBody.id}`) + .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}?alert_id=${createRuleBody.id}`) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .send({ name: 'Legacy notification with one action', interval: '1h', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts index b7cca9fd005df..acb8043ab3a25 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts @@ -126,7 +126,7 @@ export default ({ getService }: FtrProviderContext) => { expect(response.body.events[0].security_status).to.eql('partial failure'); expect( response.body.events[0].security_message.startsWith( - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["no-name-index"] was found.' + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["no-name-index"] was found.' ) ).to.eql(true); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_rules.ts index 87fee66b44767..364731d4864b4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_rules.ts @@ -7,7 +7,10 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + DETECTION_ENGINE_RULES_URL, + UPDATE_OR_CREATE_LEGACY_ACTIONS, +} from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, @@ -216,8 +219,9 @@ export default ({ getService }: FtrProviderContext) => { // attach the legacy notification await supertest - .post(`/internal/api/detection/legacy/notifications?alert_id=${createRuleBody.id}`) + .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}?alert_id=${createRuleBody.id}`) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .send({ name: 'Legacy notification with one action', interval: '1h', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/init_and_status_apis.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/init_and_status_apis.ts index 0babe434c7f90..5f6363ada6a29 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/init_and_status_apis.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/init_and_status_apis.ts @@ -104,6 +104,7 @@ export default ({ getService }: FtrProviderContext) => { dynamic: 'strict', properties: { '@timestamp': { + ignore_malformed: false, type: 'date', }, host: { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts index 7d95f6c9ec6bc..ff3611b4ab583 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts @@ -154,8 +154,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - // @ts-expect-error ts upgrade v4.7.4 - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -283,7 +282,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -345,7 +344,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -525,7 +524,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -695,7 +694,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts index 32ae758b20807..ca9c047209b53 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts @@ -56,7 +56,6 @@ export default ({ getService }: FtrProviderContext) => { }; }; - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154277 describe('Non ECS fields in alert document source', () => { before(async () => { await esArchiver.load( @@ -257,9 +256,10 @@ export default ({ getService }: FtrProviderContext) => { expect(alertSource).toHaveProperty('client.nat.port', '3000'); }); - // we don't validate it because geo_point is very complex type with many various representations: array, different object, string with few valid patterns - // more on geo_point type https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html - it('should fail creating alert when ECS field mapping is geo_point', async () => { + // We don't validate it because geo_point is very complex type with many various representations: array, + // different object, string with few valid patterns. + // More on geo_point type https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html + it('should not fail creating alert when ECS field mapping is geo_point', async () => { const document = { client: { geo: { @@ -269,12 +269,10 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { errors } = await indexAndCreatePreviewAlert(document); + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); - expect(errors[0]).toContain('Bulk Indexing of signals failed'); - expect(errors[0]).toContain( - 'failed to parse field [client.geo.location] of type [geo_point]' - ); + expect(errors).toEqual([]); + expect(alertSource).toHaveProperty('client.geo.location', 'test test'); }); it('should strip invalid boolean values and left valid ones', async () => { diff --git a/x-pack/test/detection_engine_api_integration/utils/create_legacy_rule_action.ts b/x-pack/test/detection_engine_api_integration/utils/create_legacy_rule_action.ts index 6428b41cdeba8..8e0cb59d5ee90 100644 --- a/x-pack/test/detection_engine_api_integration/utils/create_legacy_rule_action.ts +++ b/x-pack/test/detection_engine_api_integration/utils/create_legacy_rule_action.ts @@ -15,8 +15,9 @@ export const createLegacyRuleAction = async ( connectorId: string ): Promise => supertest - .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}`) + .post(UPDATE_OR_CREATE_LEGACY_ACTIONS) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .query({ alert_id: alertId }) .send({ name: 'Legacy notification with one action', diff --git a/x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts b/x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts index 5f490f2dc7c60..59fd8828e667f 100644 --- a/x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts @@ -22,7 +22,11 @@ export const createSignalsIndex = async ( ): Promise => { await countDownTest( async () => { - await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send(); + await supertest + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); return { passed: true, }; diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts index d1b45b0c33a44..8a4447e931120 100644 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts +++ b/x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts @@ -25,7 +25,11 @@ export const deleteAllAlerts = async ( ): Promise => { await countDownTest( async () => { - await supertest.delete(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send(); + await supertest + .delete(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); await es.deleteByQuery({ index, body: { diff --git a/x-pack/test/detection_engine_api_integration/utils/get_security_telemetry_stats.ts b/x-pack/test/detection_engine_api_integration/utils/get_security_telemetry_stats.ts index dd243dd3ca581..7eb00b7ff3138 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_security_telemetry_stats.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_security_telemetry_stats.ts @@ -23,6 +23,7 @@ export const getSecurityTelemetryStats = async ( const response = await supertest .get(SECURITY_TELEMETRY_URL) .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') .send({ unencrypted: true, refreshCache: true }); if (response.status !== 200) { log.error( diff --git a/x-pack/test/disable_ems/README.md b/x-pack/test/disable_ems/README.md new file mode 100644 index 0000000000000..22e9e59db4d48 --- /dev/null +++ b/x-pack/test/disable_ems/README.md @@ -0,0 +1,3 @@ +# FTR tests for map.includeElasticMapsService: false + +Verify Kibana functionallity when connection to Elastic Maps Service is disabled diff --git a/x-pack/test/disable_ems/config.ts b/x-pack/test/disable_ems/config.ts new file mode 100644 index 0000000000000..e09bf1ed0dda2 --- /dev/null +++ b/x-pack/test/disable_ems/config.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; +import { services, pageObjects } from './ftr_provider_context'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaFunctionalConfig = await readConfigFile( + require.resolve('../functional/config.base.js') + ); + + return { + ...kibanaFunctionalConfig.getAll(), + testFiles: [require.resolve('./tests')], + services, + pageObjects, + junit: { + reportName: `Kibana Maps without access to Elastic Maps Service`, + }, + kbnTestServer: { + ...kibanaFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'), + `--map.includeElasticMapsService=false`, + ], + }, + }; +} diff --git a/x-pack/test/disable_ems/ftr_provider_context.ts b/x-pack/test/disable_ems/ftr_provider_context.ts new file mode 100644 index 0000000000000..c641b4efcc493 --- /dev/null +++ b/x-pack/test/disable_ems/ftr_provider_context.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; +import { services } from '../functional/services'; +import { pageObjects } from '../functional/page_objects'; + +export type FtrProviderContext = GenericFtrProviderContext; +export { services, pageObjects }; diff --git a/x-pack/test/disable_ems/kbn_archive.json b/x-pack/test/disable_ems/kbn_archive.json new file mode 100644 index 0000000000000..d272371b4be77 --- /dev/null +++ b/x-pack/test/disable_ems/kbn_archive.json @@ -0,0 +1,50 @@ +{ + "attributes": { + "fieldAttrs":"{}", + "fieldFormatMap":"{}", + "fields":"[]", + "name":"logstash-*", + "runtimeFieldMap":"{}", + "sourceFilters":"[]", + "timeFieldName":"@timestamp", + "title":"logstash-*", + "typeMeta":"{}" + }, + "coreMigrationVersion":"8.8.0", + "created_at":"2023-09-07T14:49:04.891Z", + "id":"fd405dbb-002b-4caa-aae1-0893e5ffb75b", + "managed":false, + "references":[], + "type":"index-pattern", + "typeMigrationVersion":"8.0.0", + "updated_at":"2023-09-07T14:49:04.891Z", + "version":"WzEwLDFd" +} + +{ + "id": "ee65a3b0-4d8d-11ee-a8ed-97fb2d02a957", + "type": "map", + "namespaces": [ + "default" + ], + "updated_at": "2023-09-07T14:50:48.043Z", + "created_at": "2023-09-07T14:50:48.043Z", + "version": "WzE1LDFd", + "attributes": { + "title": "mvt documents with labels", + "description": "", + "layerListJSON": "[{\"sourceDescriptor\":{\"geoField\":\"geo.coordinates\",\"scalingType\":\"MVT\",\"id\":\"57798aca-8a4e-4c35-9225-e8d4133ae8a7\",\"type\":\"ES_SEARCH\",\"applyGlobalQuery\":true,\"applyGlobalTime\":true,\"applyForceRefresh\":true,\"filterByMapBounds\":true,\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"topHitsGroupByTimeseries\":false,\"topHitsSplitField\":\"\",\"topHitsSize\":1,\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"id\":\"00c8d672-dc32-42cc-9e11-1c43d3e9a3be\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#54B399\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":0}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\",\"type\":\"number\",\"supportsAutoDomain\":true,\"isUnsupported\":false}}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelZoomRange\":{\"options\":{\"useLayerZoomRange\":true,\"minZoom\":0,\"maxZoom\":24}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}},\"labelPosition\":{\"options\":{\"position\":\"CENTER\"}}},\"isTimeAware\":true},\"includeInFitToBounds\":true,\"type\":\"MVT_VECTOR\",\"joins\":[],\"disableTooltips\":false}]", + "mapStateJSON": "{\"adHocDataViews\":[],\"zoom\":2.19,\"center\":{\"lon\":-116.75537,\"lat\":55.05932},\"timeFilters\":{\"from\":\"2015-09-19T21:21:45.309Z\",\"to\":\"2015-09-23T01:33:44.867Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"00c8d672-dc32-42cc-9e11-1c43d3e9a3be\"]}" + }, + "references": [ + { + "name": "layer_0_source_index_pattern", + "type": "index-pattern", + "id": "fd405dbb-002b-4caa-aae1-0893e5ffb75b" + } + ], + "managed": false, + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "8.4.0" +} \ No newline at end of file diff --git a/x-pack/test/disable_ems/tests/fonts.ts b/x-pack/test/disable_ems/tests/fonts.ts new file mode 100644 index 0000000000000..aec0b0a6d6802 --- /dev/null +++ b/x-pack/test/disable_ems/tests/fonts.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['maps']); + const security = getService('security'); + + describe('Fonts', function () { + before(async () => { + await security.testUser.setRoles(['test_logstash_reader', 'global_maps_all']); + await PageObjects.maps.loadSavedMap('mvt documents with labels'); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + }); + + it('should load map with labels', async () => { + const doesLayerExist = await PageObjects.maps.doesLayerExist('logstash-*'); + expect(doesLayerExist).to.equal(true); + const tooltipText = await PageObjects.maps.getLayerTocTooltipMsg('logstash-*'); + expect(tooltipText).to.equal( + 'logstash-*\nFound 14,000 documents.\nResults narrowed by global time' + ); + }); + }); +} diff --git a/x-pack/test/disable_ems/tests/index.ts b/x-pack/test/disable_ems/tests/index.ts new file mode 100644 index 0000000000000..f4a87f7b99da9 --- /dev/null +++ b/x-pack/test/disable_ems/tests/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('disable Elastic Maps Service', () => { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.load('x-pack/test/disable_ems/kbn_archive.json'); + await browser.setWindowSize(1600, 1000); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/maps/data'); + await kibanaServer.importExport.unload('x-pack/test/disable_ems/kbn_archive.json'); + }); + + loadTestFile(require.resolve('./fonts')); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index 4c52547e67aef..6cf131939807c 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -19,6 +19,22 @@ export default function (providerContext: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const es = getService('es'); + const getPackage = async (pkgName: string) => { + const getPkgRes = await supertest + .get(`/api/fleet/epm/packages/${pkgName}`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + return getPkgRes; + }; + const epmInstall = async (pkgName: string, pkgVersion: string) => { + const getPkgRes = await supertest + .post(`/api/fleet/epm/packages/${pkgName}/${pkgVersion}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + return getPkgRes; + }; + describe('fleet_agent_policies', () => { skipIfNoDockerRegistry(providerContext); @@ -89,7 +105,7 @@ export default function (providerContext: FtrProviderContext) { let packagePoliciesToDeleteIds: string[] = []; after(async () => { if (systemPkgVersion) { - await supertest.delete(`/api/fleet/epm/packages/system-${systemPkgVersion}`); + await supertest.delete(`/api/fleet/epm/packages/system/${systemPkgVersion}`); } if (packagePoliciesToDeleteIds.length > 0) { await kibanaServer.savedObjects.bulkDelete({ @@ -297,21 +313,14 @@ export default function (providerContext: FtrProviderContext) { .expect(409); }); - it('should allow to create policy with the system integration policy and increment correctly the name if there is more than 10 package policy', async () => { + it('should allow to create policy with the system integration policy and increment correctly the name if package policies are more than 10', async () => { // load a bunch of fake system integration policy const policyIds = new Array(10).fill(null).map((_, i) => `package-policy-test-${i}`); packagePoliciesToDeleteIds = packagePoliciesToDeleteIds.concat(policyIds); - const getPkRes = await supertest - .get(`/api/fleet/epm/packages/system`) - .set('kbn-xsrf', 'xxxx') - .expect(200); + const getPkRes = await getPackage('system'); systemPkgVersion = getPkRes.body.item.version; // we must first force install the system package to override package verification error on policy create - const installPromise = supertest - .post(`/api/fleet/epm/packages/system-${systemPkgVersion}`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); + const installPromise = await epmInstall('system', `${systemPkgVersion}`); await Promise.all([ installPromise, @@ -419,7 +428,7 @@ export default function (providerContext: FtrProviderContext) { await Promise.all(deletedPromises); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents'); if (systemPkgVersion) { - await supertest.delete(`/api/fleet/epm/packages/system-${systemPkgVersion}`); + await supertest.delete(`/api/fleet/epm/packages/system/${systemPkgVersion}`); } if (packagePoliciesToDeleteIds.length > 0) { await kibanaServer.savedObjects.bulkDelete({ @@ -591,17 +600,10 @@ export default function (providerContext: FtrProviderContext) { const policyId = 'package-policy-test-'; packagePoliciesToDeleteIds.push(policyId); - const getPkRes = await supertest - .get(`/api/fleet/epm/packages/system`) - .set('kbn-xsrf', 'xxxx') - .expect(200); + const getPkRes = await getPackage('system'); systemPkgVersion = getPkRes.body.item.version; // we must first force install the system package to override package verification error on policy create - const installPromise = supertest - .post(`/api/fleet/epm/packages/system-${systemPkgVersion}`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); + const installPromise = await epmInstall('system', `${systemPkgVersion}`); await Promise.all([ installPromise, @@ -682,17 +684,10 @@ export default function (providerContext: FtrProviderContext) { it('should work with package policy with space in name', async () => { const policyId = 'package-policy-test-1'; packagePoliciesToDeleteIds.push(policyId); - const getPkRes = await supertest - .get(`/api/fleet/epm/packages/system`) - .set('kbn-xsrf', 'xxxx') - .expect(200); + const getPkRes = await getPackage('system'); systemPkgVersion = getPkRes.body.item.version; // we must first force install the system package to override package verification error on policy create - const installPromise = supertest - .post(`/api/fleet/epm/packages/system-${systemPkgVersion}`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); + const installPromise = await epmInstall('system', `${systemPkgVersion}`); await Promise.all([ installPromise, @@ -1192,17 +1187,10 @@ export default function (providerContext: FtrProviderContext) { }); setupFleetAndAgents(providerContext); before(async () => { - const getPkRes = await supertest - .get(`/api/fleet/epm/packages/system`) - .set('kbn-xsrf', 'xxxx') - .expect(200); + const getPkRes = await getPackage('system'); // we must first force install the system package to override package verification error on policy create - await supertest - .post(`/api/fleet/epm/packages/system-${getPkRes.body.item.version}`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); + await epmInstall('system', `${getPkRes.body.item.version}`); const { body: { item: createdPolicy }, diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index 328e4a240e77c..845a0b1a83659 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -216,6 +216,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return a status summary if getStatusSummary provided', async () => { const { body: apiResponse } = await supertest .get('/api/fleet/agents?getStatusSummary=true&perPage=0') + .set('kbn-xsrf', 'xxxx') .expect(200); expect(apiResponse.items).to.eql([]); diff --git a/x-pack/test/fleet_api_integration/apis/agents/status.ts b/x-pack/test/fleet_api_integration/apis/agents/status.ts index 498fbe7c42bce..b0879afc5ccb7 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/status.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/status.ts @@ -8,6 +8,8 @@ import expect from '@kbn/expect'; import { INGEST_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import { API_VERSIONS } from '@kbn/fleet-plugin/common/constants'; + import { AGENTS_INDEX } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { testUsers } from '../test_users'; @@ -230,7 +232,11 @@ export default function ({ getService }: FtrProviderContext) { }); it('should work with deprecated api', async () => { - await supertest.get(`/api/fleet/agent-status`).expect(200); + await supertest + .get(`/api/fleet/agent-status`) + .set('kbn-xsrf', 'xxxx') + .set('Elastic-Api-Version', `${API_VERSIONS.internal.v1}`) + .expect(200); }); it('should work with adequate package privileges', async () => { diff --git a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts index d717c6e285c04..47c4d7ecd9f7b 100644 --- a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; +import { API_VERSIONS } from '@kbn/fleet-plugin/common/constants'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { setupFleetAndAgents, getEsClientForAPIKey } from '../agents/services'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -324,6 +325,7 @@ export default function (providerContext: FtrProviderContext) { const { body: apiResponse } = await supertest .post(`/api/fleet/enrollment-api-keys`) .set('kbn-xsrf', 'xxx') + .set('Elastic-Api-Version', `${API_VERSIONS.internal.v1}`) .send({ policy_id: 'policy1', }) @@ -332,11 +334,20 @@ export default function (providerContext: FtrProviderContext) { }); it('should get and delete with deprecated API', async () => { - await supertest.get(`/api/fleet/enrollment-api-keys`).expect(200); - await supertest.get(`/api/fleet/enrollment-api-keys/${ENROLLMENT_KEY_ID}`).expect(200); + await supertest + .get(`/api/fleet/enrollment-api-keys`) + .set('Elastic-Api-Version', `${API_VERSIONS.internal.v1}`) + .set('kbn-xsrf', 'xxx') + .expect(200); + await supertest + .get(`/api/fleet/enrollment-api-keys/${ENROLLMENT_KEY_ID}`) + .set('Elastic-Api-Version', `${API_VERSIONS.internal.v1}`) + .set('kbn-xsrf', 'xxx') + .expect(200); await supertest .delete(`/api/fleet/enrollment-api-keys/${keyId}`) + .set('Elastic-Api-Version', `${API_VERSIONS.internal.v1}`) .set('kbn-xsrf', 'xxx') .expect(200); }); diff --git a/x-pack/test/fleet_api_integration/apis/epm/get.ts b/x-pack/test/fleet_api_integration/apis/epm/get.ts index fb15de6847952..2ca984ceb67dd 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/get.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/get.ts @@ -200,7 +200,10 @@ export default function (providerContext: FtrProviderContext) { it('returns package info in item field when calling without version', async function () { // this will install through the registry by default await installPackage(testPkgName, testPkgVersion); - const res = await supertest.get(`/api/fleet/epm/packages/${testPkgName}`).expect(200); + const res = await supertest + .get(`/api/fleet/epm/packages/${testPkgName}`) + .set('kbn-xsrf', 'xxxx') + .expect(200); const packageInfo = res.body.item; // the uploaded version will have this description expect(packageInfo.name).to.equal('apache'); diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts b/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts index 88d71edb74d47..63dfd7690e887 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts @@ -39,9 +39,9 @@ export default function (providerContext: FtrProviderContext) { force: true, integrationName: INTEGRATION_NAME, datasets: [ - { name: 'access', type: 'logs' }, - { name: 'error', type: 'metrics' }, - { name: 'warning', type: 'logs' }, + { name: `${INTEGRATION_NAME}.access`, type: 'logs' }, + { name: `${INTEGRATION_NAME}.error`, type: 'metrics' }, + { name: `${INTEGRATION_NAME}.warning`, type: 'logs' }, ], }) .expect(200); @@ -108,9 +108,9 @@ export default function (providerContext: FtrProviderContext) { force: true, integrationName: INTEGRATION_NAME, datasets: [ - { name: 'access', type: 'logs' }, - { name: 'error', type: 'metrics' }, - { name: 'warning', type: 'logs' }, + { name: `${INTEGRATION_NAME}.access`, type: 'logs' }, + { name: `${INTEGRATION_NAME}.error`, type: 'metrics' }, + { name: `${INTEGRATION_NAME}.warning`, type: 'logs' }, ], }) .expect(200); @@ -123,9 +123,9 @@ export default function (providerContext: FtrProviderContext) { force: true, integrationName: INTEGRATION_NAME, datasets: [ - { name: 'access', type: 'logs' }, - { name: 'error', type: 'metrics' }, - { name: 'warning', type: 'logs' }, + { name: `${INTEGRATION_NAME}.access`, type: 'logs' }, + { name: `${INTEGRATION_NAME}.error`, type: 'metrics' }, + { name: `${INTEGRATION_NAME}.warning`, type: 'logs' }, ], }) .expect(409); @@ -145,7 +145,7 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true, integrationName: pkgName, - datasets: [{ name: 'error', type: 'logs' }], + datasets: [{ name: `${INTEGRATION_NAME}.error`, type: 'logs' }], }) .expect(409); @@ -153,5 +153,48 @@ export default function (providerContext: FtrProviderContext) { `Failed to create the integration as an integration with the name ${pkgName} already exists in the package registry or as a bundled package.` ); }); + + it('Throws an error when dataset names are not prefixed correctly', async () => { + const response = await supertest + .post(`/api/fleet/epm/custom_integrations`) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ + force: true, + integrationName: INTEGRATION_NAME, + datasets: [{ name: 'error', type: 'logs' }], + }) + .expect(422); + + expect(response.body.message).to.be( + `Dataset names 'error' must either match integration name '${INTEGRATION_NAME}' exactly or be prefixed with integration name and a dot (e.g. '${INTEGRATION_NAME}.').` + ); + + await uninstallPackage(); + + await supertest + .post(`/api/fleet/epm/custom_integrations`) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ + force: true, + integrationName: INTEGRATION_NAME, + datasets: [{ name: INTEGRATION_NAME, type: 'logs' }], + }) + .expect(200); + + await uninstallPackage(); + + await supertest + .post(`/api/fleet/epm/custom_integrations`) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ + force: true, + integrationName: INTEGRATION_NAME, + datasets: [{ name: `${INTEGRATION_NAME}.error`, type: 'logs' }], + }) + .expect(200); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_prerelease.ts b/x-pack/test/fleet_api_integration/apis/epm/install_prerelease.ts index f9d72c62e2738..7b300c271e304 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_prerelease.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_prerelease.ts @@ -22,7 +22,6 @@ export default function (providerContext: FtrProviderContext) { await supertest.delete(`/api/fleet/epm/packages/${pkg}/${version}`).set('kbn-xsrf', 'xxxx'); }; - // Failing: See https://github.com/elastic/kibana/issues/150343 describe('installs package that has a prerelease version', async () => { skipIfNoDockerRegistry(providerContext); setupFleetAndAgents(providerContext); @@ -70,13 +69,21 @@ export default function (providerContext: FtrProviderContext) { expect(response.body.items.find((item: any) => item.id.includes(gaVersion))); }); - it('should install the beta package when no version is provided and prerelease is true', async function () { + it('should install the beta package when prerelease is true', async function () { const response = await supertest - .post(`/api/fleet/epm/packages/${testPackage}?prerelease=true`) + .post(`/api/fleet/epm/packages/${testPackage}/${testPackageVersion}?prerelease=true`) .set('kbn-xsrf', 'xxxx') .send({ force: true }) // using force to ignore package verification error .expect(200); + expect(response.body.items.find((item: any) => item.id.includes(betaVersion))); + }); + it('should install the beta package when no version is provided and prerelease is true', async function () { + const response = await supertest + .post(`/api/fleet/epm/packages/${testPackage}/${testPackageVersion}?prerelease=true`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) // using force to ignore package verification error + .expect(200); expect(response.body.items.find((item: any) => item.id.includes(betaVersion))); }); diff --git a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts index 3efb072907fac..e0c6799b5a3cd 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts @@ -14,6 +14,8 @@ import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry, generateAgent } from '../helpers'; import { setupFleetAndAgents } from './agents/services'; +const AGENT_COUNT_WAIT_ATTEMPTS = 3; + export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); @@ -124,7 +126,35 @@ export default function (providerContext: FtrProviderContext) { ); }); + async function waitForAgents(expectedAgentCount: number, attempts: number, _attemptsMade = 0) { + const { body: apiResponse } = await supertest + .get(`/api/fleet/agents?showInactive=true`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + if (apiResponse.list.length === expectedAgentCount) { + return apiResponse; + } + + if (_attemptsMade >= attempts) { + throw new Error( + `Agents not loaded correctly, failing test. All agents: \n: ${JSON.stringify( + apiResponse.list, + null, + 2 + )}` + ); + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + return waitForAgents(expectedAgentCount, attempts, _attemptsMade + 1); + } + it('should return the correct telemetry values for fleet', async () => { + // it appears agent 9 is not being loaded sometimes + // first check if all the agents have been correctly loaded + await waitForAgents(agentCount, AGENT_COUNT_WAIT_ATTEMPTS); + const { body: [{ stats: apiResponse }], } = await supertest @@ -139,13 +169,13 @@ export default function (providerContext: FtrProviderContext) { .expect(200); expect(apiResponse.stack_stats.kibana.plugins.fleet.agents).eql({ - total_enrolled: 8, + total_enrolled: 8, // does not include inactive healthy: 3, unhealthy: 3, offline: 1, unenrolled: 0, inactive: 1, - updating: 1, + updating: 1, // includes enrolling + unenrolling + updating total_all_statuses: 8, }); diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/get.ts b/x-pack/test/fleet_api_integration/apis/package_policy/get.ts index ce0a7a1f219c6..6801c3908e658 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/get.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/get.ts @@ -24,6 +24,14 @@ export default function (providerContext: FtrProviderContext) { // because `this` has to point to the Mocha context // see https://mochajs.org/#arrow-functions + const deleteEndpointPackage = async () => { + await supertest + .delete(`/api/fleet/epm/packages/endpoint/8.6.1`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + }; + describe('Package Policy APIs', () => { skipIfNoDockerRegistry(providerContext); @@ -111,12 +119,7 @@ export default function (providerContext: FtrProviderContext) { .send({ packagePolicyIds: [packagePolicyId, endpointPackagePolicyId] }) .expect(200); - // uninstall endpoint package - await supertest - .delete(`/api/fleet/epm/packages/endpoint-8.6.1`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); + await deleteEndpointPackage(); }); it('should succeed with a valid id', async function () { @@ -254,12 +257,7 @@ export default function (providerContext: FtrProviderContext) { .send({ packagePolicyIds: [packagePolicyId, endpointPackagePolicyId] }) .expect(200); - // uninstall endpoint package - await supertest - .delete(`/api/fleet/epm/packages/endpoint-8.6.1`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); + await deleteEndpointPackage(); }); it('should succeed with valid ids', async function () { @@ -476,12 +474,7 @@ export default function (providerContext: FtrProviderContext) { .send({ packagePolicyIds: [endpointPackagePolicyId] }) .expect(200); - // uninstall endpoint package - await supertest - .delete(`/api/fleet/epm/packages/endpoint-8.6.1`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); + await deleteEndpointPackage(); }); it('should return 200 if the passed kuery is correct', async () => { diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts b/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts index 263b9ac88adac..a1e6fe5d5556f 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts @@ -33,7 +33,7 @@ export default function (providerContext: FtrProviderContext) { }; const getInstallationSavedObject = async (name: string, version: string) => { - const res = await supertest.get(`/api/fleet/epm/packages/${name}-${version}`).expect(200); + const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); return res.body.item.savedObject.attributes; }; diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts index 15ad0a82b4359..865341abc5bcc 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts @@ -25,7 +25,7 @@ export default function (providerContext: FtrProviderContext) { }; const getInstallationSavedObject = async (name: string, version: string) => { - const res = await supertest.get(`/api/fleet/epm/packages/${name}-${version}`).expect(200); + const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); return res.body.item.savedObject.attributes; }; @@ -229,11 +229,11 @@ export default function (providerContext: FtrProviderContext) { .send({ agentPolicyId }); // uninstall endpoint package await supertest - .delete(`/api/fleet/epm/packages/endpoint-8.6.1`) + .delete(`/api/fleet/epm/packages/endpoint/8.6.1`) .set('kbn-xsrf', 'xxxx') .expect(200); await supertest - .delete(`/api/fleet/epm/packages/input_package-1.0.0`) + .delete(`/api/fleet/epm/packages/input_package/1.0.0`) .set('kbn-xsrf', 'xxxx') .expect(200); }); diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts b/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts index 557d5a9b4d9b2..c5c3d6a67a829 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts @@ -36,7 +36,7 @@ export default function (providerContext: FtrProviderContext) { } const getInstallationSavedObject = async (name: string, version: string) => { - const res = await supertest.get(`/api/fleet/epm/packages/${name}-${version}`).expect(200); + const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); return res.body.item.savedObject.attributes; }; diff --git a/x-pack/test/fleet_api_integration/apis/service_tokens.ts b/x-pack/test/fleet_api_integration/apis/service_tokens.ts index bd13c3f5b8ca8..e1830a8ac7f97 100644 --- a/x-pack/test/fleet_api_integration/apis/service_tokens.ts +++ b/x-pack/test/fleet_api_integration/apis/service_tokens.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { API_VERSIONS } from '@kbn/fleet-plugin/common/constants'; import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; export default function (providerContext: FtrProviderContext) { @@ -46,7 +47,11 @@ export default function (providerContext: FtrProviderContext) { }); it('should work with deprecated api', async () => { - await supertest.post(`/api/fleet/service-tokens`).set('kbn-xsrf', 'xxxx').expect(200); + await supertest + .post(`/api/fleet/service-tokens`) + .set('kbn-xsrf', 'xxxx') + .set('Elastic-Api-Version', `${API_VERSIONS.internal.v1}`) + .expect(200); }); }); } diff --git a/x-pack/test/fleet_cypress/config.ts b/x-pack/test/fleet_cypress/config.ts index 25f44880adf88..af6e2e20342e3 100644 --- a/x-pack/test/fleet_cypress/config.ts +++ b/x-pack/test/fleet_cypress/config.ts @@ -35,6 +35,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xpackFunctionalTestsConfig.get('kbnTestServer'), serverArgs: [ ...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), + '--csp.warnLegacyBrowsers=false', '--csp.strict=false', // define custom kibana server args here `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, diff --git a/x-pack/test/fleet_cypress/runner.ts b/x-pack/test/fleet_cypress/runner.ts index 87278cca019a8..fcead46bdbc07 100644 --- a/x-pack/test/fleet_cypress/runner.ts +++ b/x-pack/test/fleet_cypress/runner.ts @@ -5,108 +5,38 @@ * 2.0. */ -import { resolve } from 'path'; import Url from 'url'; -import { withProcRunner } from '@kbn/dev-proc-runner'; - import { FtrProviderContext } from './ftr_provider_context'; -import { AgentManager, AgentManagerParams } from './agent'; -import { FleetManager } from './fleet_server'; +export async function FleetCypressCliTestRunner(context: FtrProviderContext) { + await startFleetCypress(context, 'run'); +} -async function withFleetAgent( - { getService }: FtrProviderContext, - runner: (runnerEnv: Record) => Promise -) { - // skipping fleet server enroll for now, as it is not a functionality of Fleet UI itself. are there any existing e2e tests for enroll? - return await runner({}); +export async function FleetCypressVisualTestRunner(context: FtrProviderContext) { + await startFleetCypress(context, 'open'); +} - const log = getService('log'); - const config = getService('config'); +function startFleetCypress(context: FtrProviderContext, cypressCommand: string) { + const config = context.getService('config'); - const esHost = Url.format(config.get('servers.elasticsearch')); - const params: AgentManagerParams = { - user: config.get('servers.elasticsearch.username'), - password: config.get('servers.elasticsearch.password'), - esHost, - esPort: config.get('servers.elasticsearch.port'), - kibanaUrl: Url.format({ + return { + FORCE_COLOR: '1', + baseUrl: Url.format({ + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: config.get('servers.kibana.port'), + }), + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + configport: config.get('servers.kibana.port'), + ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), + ELASTICSEARCH_USERNAME: config.get('servers.kibana.username'), + ELASTICSEARCH_PASSWORD: config.get('servers.kibana.password'), + KIBANA_URL: Url.format({ protocol: config.get('servers.kibana.protocol'), hostname: config.get('servers.kibana.hostname'), port: config.get('servers.kibana.port'), }), }; - const requestOptions = { - headers: { - 'kbn-xsrf': 'kibana', - }, - auth: { - username: params.user, - password: params.password, - }, - }; - const fleetManager = new FleetManager(params, log, requestOptions); - - const agentManager = new AgentManager(params, log, requestOptions); - - // Since the managers will create uncaughtException event handlers we need to exit manually - process.on('uncaughtException', (err) => { - // eslint-disable-next-line no-console - console.error('Encountered error; exiting after cleanup.', err); - process.exit(1); - }); - - await agentManager.setup(); - await fleetManager.setup(); - try { - await runner({}); - } finally { - fleetManager.cleanup(); - agentManager.cleanup(); - } -} - -export async function FleetCypressCliTestRunner(context: FtrProviderContext) { - await startFleetAgent(context, 'run'); -} - -export async function FleetCypressVisualTestRunner(context: FtrProviderContext) { - await startFleetAgent(context, 'open'); -} - -function startFleetAgent(context: FtrProviderContext, cypressCommand: string) { - const log = context.getService('log'); - const config = context.getService('config'); - return withFleetAgent(context, (runnerEnv) => - withProcRunner(log, async (procs) => { - await procs.run('cypress', { - cmd: 'yarn', - args: [`cypress:${cypressCommand}`], - cwd: resolve(__dirname, '../../plugins/fleet'), - env: { - FORCE_COLOR: '1', - // eslint-disable-next-line @typescript-eslint/naming-convention - CYPRESS_baseUrl: Url.format(config.get('servers.kibana')), - // eslint-disable-next-line @typescript-eslint/naming-convention - CYPRESS_protocol: config.get('servers.kibana.protocol'), - // eslint-disable-next-line @typescript-eslint/naming-convention - CYPRESS_hostname: config.get('servers.kibana.hostname'), - // eslint-disable-next-line @typescript-eslint/naming-convention - CYPRESS_configport: config.get('servers.kibana.port'), - CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), - CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), - CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), - CYPRESS_KIBANA_URL: Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }), - ...runnerEnv, - ...process.env, - }, - wait: true, - }); - }) - ); } diff --git a/x-pack/test/functional/apps/canvas/custom_elements.ts b/x-pack/test/functional/apps/canvas/custom_elements.ts index bf9317373b909..7f079666325e3 100644 --- a/x-pack/test/functional/apps/canvas/custom_elements.ts +++ b/x-pack/test/functional/apps/canvas/custom_elements.ts @@ -16,7 +16,7 @@ export default function canvasCustomElementTest({ const testSubjects = getService('testSubjects'); const browser = getService('browser'); const retry = getService('retry'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const find = getService('find'); const kibanaServer = getService('kibanaServer'); const archive = 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default'; @@ -26,12 +26,9 @@ export default function canvasCustomElementTest({ before(async () => { await kibanaServer.importExport.load(archive); - // open canvas home - await PageObjects.common.navigateToApp('canvas'); // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Test Workpad'); }); after(async () => { diff --git a/x-pack/test/functional/apps/canvas/datasource.ts b/x-pack/test/functional/apps/canvas/datasource.ts index c1cf907bc5342..5afda1a579cd0 100644 --- a/x-pack/test/functional/apps/canvas/datasource.ts +++ b/x-pack/test/functional/apps/canvas/datasource.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function canvasExpressionTest({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const monacoEditor = getService('monacoEditor'); @@ -35,7 +35,7 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr }); // create new test workpad - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.createNewWorkpad(); }); diff --git a/x-pack/test/functional/apps/canvas/embeddables/lens.ts b/x-pack/test/functional/apps/canvas/embeddables/lens.ts index de7a2eb753204..1e7557cde4c8c 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/lens.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/lens.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function canvasLensTest({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'lens']); + const PageObjects = getPageObjects(['canvas', 'header', 'lens']); const esArchiver = getService('esArchiver'); const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); @@ -25,7 +25,7 @@ export default function canvasLensTest({ getService, getPageObjects }: FtrProvid await kibanaServer.savedObjects.cleanStandardList(); await kibanaServer.importExport.load(archives.kbn); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-lens' }); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.createNewWorkpad(); }); @@ -49,9 +49,8 @@ export default function canvasLensTest({ getService, getPageObjects }: FtrProvid it('renders lens visualization using savedLens expression', async () => { // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Test Workpad'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.lens.assertLegacyMetric('Maximum of bytes', '16,788'); diff --git a/x-pack/test/functional/apps/canvas/embeddables/maps.ts b/x-pack/test/functional/apps/canvas/embeddables/maps.ts index 1cdcb644448a8..6cf23726846f1 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/maps.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/maps.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'maps']); + const PageObjects = getPageObjects(['canvas', 'header', 'maps']); const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardAddPanel = getService('dashboardAddPanel'); const testSubjects = getService('testSubjects'); @@ -19,7 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); // open canvas home - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); // create new workpad await PageObjects.canvas.createNewWorkpad(); await PageObjects.canvas.setWorkpadName('maps tests'); diff --git a/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts b/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts index 6024b4114199a..59285140672c6 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'discover']); + const PageObjects = getPageObjects(['canvas', 'header', 'discover']); const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -21,7 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana' ); // open canvas home - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); // create new workpad await PageObjects.canvas.createNewWorkpad(); await PageObjects.canvas.setWorkpadName('saved search tests'); @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('edits saved search by-reference embeddable', async () => { await dashboardPanelActions.editPanelByTitle('Rendering Test: saved search'); await PageObjects.discover.saveSearch('Rendering Test: saved search v2'); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.loadFirstWorkpad('saved search tests'); await testSubjects.existOrFail('embeddablePanelHeading-RenderingTest:savedsearchv2'); }); diff --git a/x-pack/test/functional/apps/canvas/embeddables/visualization.ts b/x-pack/test/functional/apps/canvas/embeddables/visualization.ts index 1296bd1e51c8f..40328cbf3890d 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/visualization.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/visualization.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'visualize']); + const PageObjects = getPageObjects(['canvas', 'header', 'visualize']); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); // open canvas home - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); // create new workpad await PageObjects.canvas.createNewWorkpad(); await PageObjects.canvas.setWorkpadName('visualization tests'); diff --git a/x-pack/test/functional/apps/canvas/expression.ts b/x-pack/test/functional/apps/canvas/expression.ts index f41bea9774308..7e6c8720ffc43 100644 --- a/x-pack/test/functional/apps/canvas/expression.ts +++ b/x-pack/test/functional/apps/canvas/expression.ts @@ -15,7 +15,7 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr const find = getService('find'); const kibanaServer = getService('kibanaServer'); const monacoEditor = getService('monacoEditor'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -27,9 +27,8 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr await kibanaServer.importExport.load(archive); // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Test Workpad'); }); after(async () => { diff --git a/x-pack/test/functional/apps/canvas/filters.ts b/x-pack/test/functional/apps/canvas/filters.ts index ce8b319b9d53f..d1e85aec2ae87 100644 --- a/x-pack/test/functional/apps/canvas/filters.ts +++ b/x-pack/test/functional/apps/canvas/filters.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function canvasFiltersTest({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const find = getService('find'); const kibanaServer = getService('kibanaServer'); const archive = 'x-pack/test/functional/fixtures/kbn_archiver/canvas/filter'; @@ -24,9 +24,8 @@ export default function canvasFiltersTest({ getService, getPageObjects }: FtrPro before(async () => { await kibanaServer.importExport.load(archive); // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-b5618217-56d2-47fa-b756-1be2306cda68/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Filter Debug Workpad'); }); after(async () => { diff --git a/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts b/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts index 7577073a1004d..54f42e484734d 100644 --- a/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts +++ b/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts @@ -9,7 +9,7 @@ import path from 'path'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'settings', 'savedObjects']); + const PageObjects = getPageObjects(['settings', 'savedObjects']); describe('migration smoke test', function () { it('imports an 8.2 workpad', async function () { diff --git a/x-pack/test/functional/apps/canvas/reports.ts b/x-pack/test/functional/apps/canvas/reports.ts index 1a1550a62c8eb..a0cb1029f35b3 100644 --- a/x-pack/test/functional/apps/canvas/reports.ts +++ b/x-pack/test/functional/apps/canvas/reports.ts @@ -15,7 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const log = getService('log'); const security = getService('security'); - const PageObjects = getPageObjects(['reporting', 'common', 'canvas']); + const PageObjects = getPageObjects(['reporting', 'canvas']); const archive = 'x-pack/test/functional/fixtures/kbn_archiver/canvas/reports'; describe('Canvas PDF Report Generation', () => { @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Generating and then comparing reports can take longer than the default 60s timeout this.timeout(180000); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.loadFirstWorkpad('The Very Cool Workpad for PDF Tests'); await PageObjects.reporting.openPdfReportingPanel(); diff --git a/x-pack/test/functional/apps/canvas/saved_object_resolve.ts b/x-pack/test/functional/apps/canvas/saved_object_resolve.ts index d0739c0d2f1b7..c9b4074bc029a 100644 --- a/x-pack/test/functional/apps/canvas/saved_object_resolve.ts +++ b/x-pack/test/functional/apps/canvas/saved_object_resolve.ts @@ -82,10 +82,14 @@ export default function canvasFiltersTest({ getService, getPageObjects }: FtrPro }); it('redirects an alias match', async () => { - await PageObjects.common.navigateToApp('canvas', { - basePath: '/s/custom_space', - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-old-id/page/1', - }); + await PageObjects.common.navigateToUrl( + 'canvas', + 'workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-old-id/page/1', + { + basePath: '/s/custom_space', + shouldUseHashForSubUrl: false, + } + ); // Wait for the redirect toast await retry.try(async () => { @@ -111,10 +115,14 @@ export default function canvasFiltersTest({ getService, getPageObjects }: FtrPro }); it('handles a conflict match', async () => { - await PageObjects.common.navigateToApp('canvas', { - basePath: '/s/custom_space', - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-conflict-old/page/1', - }); + await PageObjects.common.navigateToUrl( + 'canvas', + 'workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-conflict-old/page/1', + { + basePath: '/s/custom_space', + shouldUseHashForSubUrl: false, + } + ); await testSubjects.click('legacy-url-conflict-go-to-other-button'); diff --git a/x-pack/test/functional/apps/canvas/smoke_test.js b/x-pack/test/functional/apps/canvas/smoke_test.js index bdad362d78532..f251e34e8566d 100644 --- a/x-pack/test/functional/apps/canvas/smoke_test.js +++ b/x-pack/test/functional/apps/canvas/smoke_test.js @@ -11,7 +11,7 @@ export default function canvasSmokeTest({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); const retry = getService('retry'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['canvas']); const kibanaServer = getService('kibanaServer'); const config = getService('config'); const archive = { @@ -31,7 +31,7 @@ export default function canvasSmokeTest({ getService, getPageObjects }) { await kibanaServer.importExport.load(archive.local); } - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); }); after(async () => { diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts index 7cc75446036e9..5deaac7e3a579 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts @@ -183,13 +183,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`allows a visualization to be edited`, async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode('A Dashboard'); await panelActions.expectExistsEditPanelAction(); }); it(`allows a map to be edited`, async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode('dashboard with map'); await panelActions.expectExistsEditPanelAction(); }); diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts index 1ed0e1535a828..7b36b19c32da8 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts @@ -88,7 +88,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('lens by value works without library save permissions', () => { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); @@ -169,13 +169,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const modifiedMarkdownText = 'Modified markdown text'; before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); it('can add a markdown panel by value', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts b/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts index 608d29a6b7abb..baab3bbeb52f7 100644 --- a/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts +++ b/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts @@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('goes back to last opened url', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('A Dashboard'); await PageObjects.common.navigateToApp('home'); await appsMenu.clickLink('Dashboard', { category: 'kibana' }); @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('remembers url after switching spaces', async function () { // default space - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('A Dashboard'); await PageObjects.spaceSelector.openSpacesNav(); diff --git a/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts b/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts index 80354eda9eefd..66b31842df00a 100644 --- a/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts +++ b/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts @@ -128,7 +128,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should launch sample flights data set dashboard', async () => { - await appMenu.clickLink('Dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[Flights] Global Flight Dashboard'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.timePicker.setCommonlyUsedTime('sample_data range'); @@ -144,7 +144,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); expect(hitCount).to.be.greaterThan(0); }); - await appMenu.clickLink('Dashboard'); + await appMenu.clickLink('Dashboard', { + category: 'recentlyViewed', + closeCollapsibleNav: true, + }); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); log.debug('Checking charts rendered'); @@ -157,7 +160,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); expect(hitCount).to.be.greaterThan(0); }); - await appMenu.clickLink('Dashboard'); + await appMenu.clickLink('Dashboard', { + category: 'recentlyViewed', + closeCollapsibleNav: true, + }); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); log.debug('Checking charts rendered'); @@ -170,7 +176,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); expect(hitCount).to.be.greaterThan(0); }); - await appMenu.clickLink('Dashboard'); + await appMenu.clickLink('Dashboard', { + category: 'recentlyViewed', + closeCollapsibleNav: true, + }); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); log.debug('Checking charts rendered'); diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts index 2604b942f7313..423c9819a048f 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts @@ -22,7 +22,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts index d9a0062298eec..9904c8bb62293 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts @@ -60,7 +60,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); if (!saveToDashboard) { - await appsMenu.clickLink('Dashboard'); + await appsMenu.clickLink('Dashboard', { + category: 'kibana', + closeCollapsibleNav: true, + }); } } else { await PageObjects.maps.clickSaveAndReturnButton(); @@ -70,12 +73,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { } async function createNewDashboard() { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); } - describe('dashboard maps by value', function () { + // Failing: See https://github.com/elastic/kibana/issues/152476 + describe.skip('dashboard maps by value', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts index beb87afce4549..92cc910313615 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts index d36d2b579ae62..f2c8d67d16b7e 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts @@ -23,8 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const queryBar = getService('queryBar'); - const { common, settings, savedObjects, dashboard, dashboardControls } = getPageObjects([ - 'common', + const { settings, savedObjects, dashboard, dashboardControls } = getPageObjects([ 'settings', 'dashboard', 'savedObjects', @@ -57,7 +56,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render all panels on the dashboard', async () => { await dashboardControls.enableControlsLab(); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.loadSavedDashboard('[8.0.0] Controls Dashboard'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts index 024c045c4ff87..0dc6f36662952 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[7.12.1] Lens By Value Test Dashboard'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts index 8485f85dd35a0..c6d947337da21 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('TSVB Index Pattern Smoke Test'); // dashboard should load properly @@ -101,7 +101,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('TSVB 7.13.3'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts index 4824fcb421828..6f8a387d276a0 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[7.12.1] Visualize Test Dashboard'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts index 2295c90d60c65..bbf5877f80327 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard(DASHBOARD_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts index 14970ba7764ab..2c0ac33107fea 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard(DASHBOARD_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts index 2692c40af0adf..5d1f590490c4d 100644 --- a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts +++ b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should sync colors on dashboard by default', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.dashboard.clickCreateDashboardPrompt(); await dashboardAddPanel.clickCreateNewLink(); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts index e7afd4f9761da..deb5195040800 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts @@ -129,7 +129,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { log.debug('Dashboard Drilldowns:initTests'); await security.testUser.setRoles(['test_logstash_reader', 'global_dashboard_all']); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await elasticChart.setNewChartUiDebugFlag(); @@ -399,7 +399,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Actually use copied dashboards in a new space: - await PageObjects.common.navigateToApp('dashboard', { + await PageObjects.common.navigateToApp('dashboards', { basePath: `/s/${destinationSpaceId}`, }); await PageObjects.dashboard.preserveCrossAppState(); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts index ca057b7421b7c..24e9a249377fa 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts @@ -22,7 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Dashboard to URL drilldown', function () { before(async () => { log.debug('Dashboard to URL:initTests'); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts index 6e91362e4adfd..dff41ef2ead73 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Explore underlying data - chart action', () => { describe('value click action', () => { it('action exists in chart click popup menu', async () => { - await common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); await pieChart.clickOnPieSlice('160,000'); @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let originalTimeRangeDurationHours: number | undefined; it('action exists in chart brush popup menu', async () => { - await common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_AREA_CHART_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts index 8e943c2b3104d..ed504f3711565 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts @@ -13,7 +13,7 @@ const ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; export default function ({ getService, getPageObjects }: FtrProviderContext) { const drilldowns = getService('dashboardDrilldownsManage'); - const { dashboard, discover, common, timePicker } = getPageObjects([ + const { dashboard, discover, timePicker } = getPageObjects([ 'dashboard', 'discover', 'common', @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); before('start on Dashboard landing page', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); }); @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after('clean-up custom time range on panel', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardEditMode(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); await panelActions.customizePanel(); @@ -75,7 +75,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('carries over panel time range', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardEditMode(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts index c8808e179d70d..c23f991f69f07 100644 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts +++ b/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const navigateToDashboardApp = async () => { log.debug('in navigateToDashboardApp'); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await retry.tryForTime(10000, async () => { expect(await PageObjects.dashboard.onDashboardLandingPage()).to.be(true); }); diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts index 4450224d0456c..490ba84c8496c 100644 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts +++ b/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts @@ -84,7 +84,7 @@ export default function ({ describe('Print PDF button', () => { it('is available if new', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.reporting.openPdfReportingPanel(); expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); @@ -110,7 +110,7 @@ export default function ({ // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs // function is taking about 15 seconds per comparison in jenkins. this.timeout(300000); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.checkUsePrintLayout(); @@ -133,7 +133,7 @@ export default function ({ }); it('is available if new', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.reporting.openPngReportingPanel(); expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); @@ -158,7 +158,7 @@ export default function ({ it('downloads a PDF file with saved search given EuiDataGrid enabled', async function () { await kibanaServer.uiSettings.update({ 'doc_table:legacy': false }); this.timeout(300000); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); @@ -187,7 +187,7 @@ export default function ({ 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce_76.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[K7.6-eCommerce] Revenue Dashboard'); await PageObjects.reporting.openPngReportingPanel(); diff --git a/x-pack/test/functional/apps/discover/async_scripted_fields.js b/x-pack/test/functional/apps/discover/async_scripted_fields.js index 0d48f42c5ba1e..f5143e5fcc084 100644 --- a/x-pack/test/functional/apps/discover/async_scripted_fields.js +++ b/x-pack/test/functional/apps/discover/async_scripted_fields.js @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }) { const security = getService('security'); const dashboardAddPanel = getService('dashboardAddPanel'); - describe('async search with scripted fields', function () { + describe('search with scripted fields', function () { this.tags(['skipFirefox']); before(async function () { @@ -51,7 +51,7 @@ export default function ({ getService, getPageObjects }) { await security.testUser.restoreDefaults(); }); - it('query should show failed shards callout', async function () { + it('query should show incomplete results callout', async function () { if (false) { /* If you had to modify the scripted fields, you could un-comment all this, run it, use es_archiver to update 'kibana_scripted_fields_on_logstash' */ @@ -81,11 +81,11 @@ export default function ({ getService, getPageObjects }) { 'dscNoResultsInterceptedWarningsCallout_warningTitle' ); log.debug(shardMessage); - expect(shardMessage).to.be('1 of 3 shards failed'); + expect(shardMessage).to.be('The data might be incomplete or wrong.'); }); }); - it('query should show failed shards badge on dashboard', async function () { + it('query should show incomplete results badge on dashboard', async function () { await security.testUser.setRoles([ 'test_logstash_reader', 'global_discover_all', @@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.discover.saveSearch('search with warning'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/x-pack/test/functional/apps/discover/reporting.ts b/x-pack/test/functional/apps/discover/reporting.ts index 75fa7feb02057..173869a85465f 100644 --- a/x-pack/test/functional/apps/discover/reporting.ts +++ b/x-pack/test/functional/apps/discover/reporting.ts @@ -29,6 +29,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; const getReport = async () => { + // close any open notification toasts + await PageObjects.reporting.clearToastNotifications(); + await PageObjects.reporting.openCsvReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); diff --git a/x-pack/test/functional/apps/discover/saved_searches.ts b/x-pack/test/functional/apps/discover/saved_searches.ts index 76b380274f207..85a4d91eabc3c 100644 --- a/x-pack/test/functional/apps/discover/saved_searches.ts +++ b/x-pack/test/functional/apps/discover/saved_searches.ts @@ -45,9 +45,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.unsetTime(); }); - describe('Customize time range', () => { + // FLAKY: https://github.com/elastic/kibana/issues/104578 + describe.skip('Customize time range', () => { it('should be possible to customize time range for saved searches on dashboards', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.addSavedSearch('Ecommerce Data'); diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index 32cd9b5360da0..a61a2abba1870 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -33,7 +33,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const monacoEditor = getService('monacoEditor'); const defaultSettings = { - 'discover:enableSql': true, + 'discover:enableESQL': true, }; async function setDiscoverTimeRange() { @@ -144,15 +144,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should visualize correctly text based language queries in Discover', async () => { - await PageObjects.discover.selectTextBaseLang('SQL'); + await PageObjects.discover.selectTextBaseLang(); await PageObjects.header.waitUntilLoadingHasFinished(); await monacoEditor.setCodeEditorValue( - 'SELECT extension, AVG("bytes") as average FROM "logstash-*" GROUP BY extension' + 'from logstash-* | stats averageB = avg(bytes) by extension' ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); - expect(await testSubjects.exists('heatmapChart')).to.be(true); + expect(await testSubjects.exists('xyVisChart')).to.be(true); await PageObjects.discover.chooseLensChart('Donut'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -161,10 +161,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should allow changing dimensions', async () => { await elasticChart.setNewChartUiDebugFlag(true); - await PageObjects.discover.selectTextBaseLang('SQL'); + await PageObjects.discover.selectTextBaseLang(); await PageObjects.header.waitUntilLoadingHasFinished(); await monacoEditor.setCodeEditorValue( - 'SELECT extension, AVG("bytes") as average FROM "logstash-*" GROUP BY extension' + 'from logstash-* | stats averageB = avg(bytes) by extension' ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -186,11 +186,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { assertMatchesExpectedData(data!); }); - it('should visualize correctly text based language queries in Lenss', async () => { - await PageObjects.discover.selectTextBaseLang('SQL'); + it('should visualize correctly text based language queries in Lens', async () => { + await PageObjects.discover.selectTextBaseLang(); await PageObjects.header.waitUntilLoadingHasFinished(); await monacoEditor.setCodeEditorValue( - 'SELECT extension, AVG("bytes") as average FROM "logstash-*" GROUP BY extension' + 'from logstash-* | stats averageB = avg(bytes) by extension' ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -201,15 +201,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await retry.waitFor('lens flyout', async () => { const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); - return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'average'; + return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'averageB'; }); }); it('should visualize correctly text based language queries based on index patterns', async () => { - await PageObjects.discover.selectTextBaseLang('SQL'); + await PageObjects.discover.selectTextBaseLang(); await PageObjects.header.waitUntilLoadingHasFinished(); await monacoEditor.setCodeEditorValue( - 'SELECT extension, AVG("bytes") as average FROM "logstash*" GROUP BY extension' + 'from logstash* | stats averageB = avg(bytes) by extension' ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -220,15 +220,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await retry.waitFor('lens flyout', async () => { const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); - return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'average'; + return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'averageB'; }); }); it('should save and edit chart in the dashboard on the fly', async () => { - await PageObjects.discover.selectTextBaseLang('SQL'); + await PageObjects.discover.selectTextBaseLang(); await PageObjects.header.waitUntilLoadingHasFinished(); await monacoEditor.setCodeEditorValue( - 'SELECT extension, AVG("bytes") as average FROM "logstash*" GROUP BY extension' + 'from logstash-* | stats averageB = avg(bytes) by extension' ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/index_management/create_enrich_policy/create_enrich_policy.ts b/x-pack/test/functional/apps/index_management/create_enrich_policy/create_enrich_policy.ts new file mode 100644 index 0000000000000..3d0a1e562e45b --- /dev/null +++ b/x-pack/test/functional/apps/index_management/create_enrich_policy/create_enrich_policy.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'indexManagement', 'header']); + const log = getService('log'); + const security = getService('security'); + const comboBox = getService('comboBox'); + const testSubjects = getService('testSubjects'); + const es = getService('es'); + + const INDEX_NAME = `index-${Math.random()}`; + const POLICY_NAME = `policy-${Math.random()}`; + + describe('Create enrich policy', function () { + before(async () => { + await log.debug('Creating test index'); + try { + await es.indices.create({ + index: INDEX_NAME, + body: { + mappings: { + properties: { + email: { + type: 'text', + }, + age: { + type: 'long', + }, + }, + }, + }, + }); + } catch (e) { + log.debug('[Setup error] Error creating test policy'); + throw e; + } + + await log.debug('Navigating to the enrich policies tab'); + await security.testUser.setRoles(['index_management_user']); + await pageObjects.common.navigateToApp('indexManagement'); + // Navigate to the enrich policies tab + await pageObjects.indexManagement.changeTabs('enrich_policiesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); + // Click create policy button + await testSubjects.click('enrichPoliciesEmptyPromptCreateButton'); + }); + + after(async () => { + await log.debug('Cleaning up created index'); + + try { + await es.indices.delete({ index: INDEX_NAME }); + } catch (e) { + log.debug('[Teardown error] Error deleting test policy'); + throw e; + } + }); + + it('shows create enrich policies page and docs link', async () => { + expect(await testSubjects.exists('createEnrichPolicyHeaderContent')).to.be(true); + expect(await testSubjects.exists('createEnrichPolicyDocumentationLink')).to.be(true); + }); + + it('can create an enrich policy', async () => { + // Complete configuration step + await testSubjects.setValue('policyNameField > input', POLICY_NAME); + await testSubjects.setValue('policyTypeField', 'match'); + await comboBox.set('policySourceIndicesField', INDEX_NAME); + await testSubjects.click('nextButton'); + + // Complete field selection step + await comboBox.set('matchField', 'email'); + await comboBox.set('enrichFields', 'age'); + await testSubjects.click('nextButton'); + + // Create policy + await testSubjects.click('createButton'); + + // Expect to be redirected to the enrich policies tab + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Expect to have that policy in the table + const policyList = await testSubjects.findAll('enrichPolicyDetailsLink'); + expect(policyList.length).to.be(1); + }); + }); +}; diff --git a/x-pack/test/functional/apps/index_management/create_enrich_policy/index.ts b/x-pack/test/functional/apps/index_management/create_enrich_policy/index.ts new file mode 100644 index 0000000000000..9526aa99f1802 --- /dev/null +++ b/x-pack/test/functional/apps/index_management/create_enrich_policy/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext) => { + describe('Index Management: create enrich policy', function () { + loadTestFile(require.resolve('./create_enrich_policy')); + }); +}; diff --git a/x-pack/test/functional/apps/index_management/enrich_policies_tab/enrich_policies_tab.ts b/x-pack/test/functional/apps/index_management/enrich_policies_tab/enrich_policies_tab.ts new file mode 100644 index 0000000000000..2d26fbd0ec905 --- /dev/null +++ b/x-pack/test/functional/apps/index_management/enrich_policies_tab/enrich_policies_tab.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'indexManagement', 'header']); + const toasts = getService('toasts'); + const log = getService('log'); + const browser = getService('browser'); + const security = getService('security'); + const testSubjects = getService('testSubjects'); + const es = getService('es'); + + const ENRICH_INDEX_NAME = 'test-policy-1'; + const ENRICH_POLICY_NAME = 'test-policy-1'; + + describe('Enrich policies tab', function () { + before(async () => { + await log.debug('Creating required index and enrich policy'); + try { + await es.indices.create({ + index: ENRICH_INDEX_NAME, + body: { + mappings: { + properties: { + name: { + type: 'text', + }, + }, + }, + }, + }); + + await es.enrich.putPolicy({ + name: ENRICH_POLICY_NAME, + match: { + indices: ENRICH_INDEX_NAME, + match_field: 'name', + enrich_fields: ['name'], + }, + }); + } catch (e) { + log.debug('[Setup error] Error creating test policy'); + throw e; + } + + await log.debug('Navigating to the enrich policies tab'); + await security.testUser.setRoles(['index_management_user']); + await pageObjects.common.navigateToApp('indexManagement'); + // Navigate to the enrich policies tab + await pageObjects.indexManagement.changeTabs('enrich_policiesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + after(async () => { + await log.debug('Cleaning up created index and policy'); + + try { + await es.indices.delete({ index: ENRICH_INDEX_NAME }); + } catch (e) { + log.debug('[Teardown error] Error deleting test policy'); + throw e; + } + }); + + it('shows enrich policies page and docs link', async () => { + expect(await testSubjects.exists('enrichPoliciesList')).to.be(true); + expect(await testSubjects.exists('enrichPoliciesLearnMoreLink')).to.be(true); + }); + + it('shows the details flyout when clicking on a policy name', async () => { + // Open details flyout + await pageObjects.indexManagement.clickEnrichPolicyAt(0); + // Verify url is stateful + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/enrich_policies?policy=${ENRICH_POLICY_NAME}`); + // Assert that flyout is opened + expect(await testSubjects.exists('policyDetailsFlyout')).to.be(true); + // Close flyout + await testSubjects.click('closeFlyoutButton'); + }); + + it('can execute a policy', async () => { + await pageObjects.indexManagement.clickExecuteEnrichPolicyAt(0); + await pageObjects.indexManagement.clickConfirmModalButton(); + + const successToast = await toasts.getToastElement(1); + expect(await successToast.getVisibleText()).to.contain(`Executed ${ENRICH_POLICY_NAME}`); + }); + + it('can delete a policy', async () => { + await pageObjects.indexManagement.clickDeleteEnrichPolicyAt(0); + await pageObjects.indexManagement.clickConfirmModalButton(); + + const successToast = await toasts.getToastElement(2); + expect(await successToast.getVisibleText()).to.contain(`Deleted ${ENRICH_POLICY_NAME}`); + }); + }); +}; diff --git a/x-pack/test/functional/apps/index_management/enrich_policies_tab/index.ts b/x-pack/test/functional/apps/index_management/enrich_policies_tab/index.ts new file mode 100644 index 0000000000000..51d67e81b738a --- /dev/null +++ b/x-pack/test/functional/apps/index_management/enrich_policies_tab/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext) => { + describe('Index Management: enrich policies tab', function () { + loadTestFile(require.resolve('./enrich_policies_tab')); + }); +}; diff --git a/x-pack/test/functional/apps/index_management/home_page.ts b/x-pack/test/functional/apps/index_management/home_page.ts index 3b0e220f35231..265707ec6da8c 100644 --- a/x-pack/test/functional/apps/index_management/home_page.ts +++ b/x-pack/test/functional/apps/index_management/home_page.ts @@ -92,5 +92,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(componentTemplateList).to.be(true); }); }); + + describe('Enrich policies', () => { + it('renders the enrich policies tab', async () => { + // Navigate to the component templates tab + await pageObjects.indexManagement.changeTabs('enrich_policiesTab'); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Verify url + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/enrich_policies`); + + // Verify content + const enrichPoliciesList = await testSubjects.exists('sectionEmpty'); + expect(enrichPoliciesList).to.be(true); + }); + }); }); }; diff --git a/x-pack/test/functional/apps/index_management/index.ts b/x-pack/test/functional/apps/index_management/index.ts index 69fe03d1925ea..e97d2427c8c90 100644 --- a/x-pack/test/functional/apps/index_management/index.ts +++ b/x-pack/test/functional/apps/index_management/index.ts @@ -12,5 +12,7 @@ export default ({ loadTestFile }: FtrProviderContext) => { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./home_page')); loadTestFile(require.resolve('./index_template_wizard')); + loadTestFile(require.resolve('./enrich_policies_tab')); + loadTestFile(require.resolve('./create_enrich_policy')); }); }; diff --git a/x-pack/test/functional/apps/infra/constants.ts b/x-pack/test/functional/apps/infra/constants.ts index 156354656fdcb..26698db8ebf34 100644 --- a/x-pack/test/functional/apps/infra/constants.ts +++ b/x-pack/test/functional/apps/infra/constants.ts @@ -26,6 +26,8 @@ export const DATES = { max: '2018-10-17T19:58:03.952Z', processesDataStartDate: '2023-03-28T18:20:00.000Z', processesDataEndDate: '2023-03-28T18:21:00.000Z', + kubernetesSectionStartDate: '2023-09-19T07:20:00.000Z', + kubernetesSectionEndDate: '2023-09-19T07:21:00.000Z', }, stream: { startWithData: '2018-10-17T19:42:22.000Z', diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 6373cc3aa95cb..d4e2d7fd653cd 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -372,7 +372,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ensureCurrentUrl: false, shouldLoginIfPrompted: false, }); - PageObjects.error.expectForbidden(); + await PageObjects.error.expectForbidden(); }); }); }); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 8ea0ad09a50f0..8c63587f23889 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -21,7 +21,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); - describe('Home page', function () { + // Failing: See https://github.com/elastic/kibana/issues/164164 + describe.skip('Home page', function () { this.tags('includeFirefox'); before(async () => { await kibanaServer.savedObjects.cleanStandardList(); @@ -56,8 +57,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/164164 - describe.skip('with metrics present', () => { + describe('with metrics present', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await pageObjects.common.navigateToApp('infraOps'); diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index a225ea4c476e4..e8f7730522f42 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -305,9 +305,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - it('should render 8 charts in the Metrics section', async () => { + it('should render 9 charts in the Metrics section', async () => { const hosts = await pageObjects.assetDetails.getAssetDetailsMetricsCharts(); - expect(hosts.length).to.equal(8); + expect(hosts.length).to.equal(9); }); it('should show alerts', async () => { diff --git a/x-pack/test/functional/apps/infra/node_details.ts b/x-pack/test/functional/apps/infra/node_details.ts index 576de71f0a55b..89a4ec813cca3 100644 --- a/x-pack/test/functional/apps/infra/node_details.ts +++ b/x-pack/test/functional/apps/infra/node_details.ts @@ -14,6 +14,12 @@ const START_HOST_ALERTS_DATE = moment.utc(DATES.metricsAndLogs.hosts.min); const END_HOST_ALERTS_DATE = moment.utc(DATES.metricsAndLogs.hosts.max); const START_HOST_PROCESSES_DATE = moment.utc(DATES.metricsAndLogs.hosts.processesDataStartDate); const END_HOST_PROCESSES_DATE = moment.utc(DATES.metricsAndLogs.hosts.processesDataEndDate); +const START_HOST_KUBERNETES_SECTION_DATE = moment.utc( + DATES.metricsAndLogs.hosts.kubernetesSectionStartDate +); +const END_HOST_KUBERNETES_SECTION_DATE = moment.utc( + DATES.metricsAndLogs.hosts.kubernetesSectionEndDate +); export default ({ getPageObjects, getService }: FtrProviderContext) => { const observability = getService('observability'); @@ -233,6 +239,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Processes Tab', () => { before(async () => { await pageObjects.assetDetails.clickProcessesTab(); + await pageObjects.header.waitUntilLoadingHasFinished(); }); it('should render processes tab and with Total Value summary', async () => { @@ -338,6 +345,94 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(processValue).to.eql('N/A'); }); }); + + describe('#With Nginx section', () => { + before(async () => { + await navigateToNodeDetails('demo-stack-nginx-01', 'demo-stack-nginx-01'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + describe('Overview Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickOverviewTab(); + + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT), + END_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT) + ); + }); + + [ + { metric: 'cpuUsage', value: '0.8%' }, + { metric: 'normalizedLoad1m', value: '1.4%' }, + { metric: 'memoryUsage', value: '18.0%' }, + { metric: 'diskSpaceUsage', value: '17.5%' }, + ].forEach(({ metric, value }) => { + it(`${metric} tile should show ${value}`, async () => { + await retry.tryForTime(3 * 1000, async () => { + const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( + metric + ); + expect(tileValue).to.eql(value); + }); + }); + }); + + it('should render 12 charts in the Metrics section', async () => { + const hosts = await pageObjects.assetDetails.getAssetDetailsMetricsCharts(); + expect(hosts.length).to.equal(12); + }); + + it('should render 3 charts in the Nginx Metrics section', async () => { + const hosts = await pageObjects.assetDetails.getAssetDetailsNginxMetricsCharts(); + expect(hosts.length).to.equal(3); + }); + }); + }); + + describe('#With Kubernetes section', () => { + before(async () => { + await navigateToNodeDetails('demo-stack-kubernetes-01', 'demo-stack-kubernetes-01'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + describe('Overview Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickOverviewTab(); + + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_KUBERNETES_SECTION_DATE.format(DATE_PICKER_FORMAT), + END_HOST_KUBERNETES_SECTION_DATE.format(DATE_PICKER_FORMAT) + ); + }); + + [ + { metric: 'cpuUsage', value: '99.6%' }, + { metric: 'normalizedLoad1m', value: '1,300.3%' }, + { metric: 'memoryUsage', value: '42.2%' }, + { metric: 'diskSpaceUsage', value: '36.0%' }, + ].forEach(({ metric, value }) => { + it(`${metric} tile should show ${value}`, async () => { + await retry.tryForTime(3 * 1000, async () => { + const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( + metric + ); + expect(tileValue).to.eql(value); + }); + }); + }); + + it('should render 12 charts in the Metrics section', async () => { + const hosts = await pageObjects.assetDetails.getAssetDetailsMetricsCharts(); + expect(hosts.length).to.equal(12); + }); + + it('should render 4 charts in the Kubernetes Metrics section', async () => { + const hosts = await pageObjects.assetDetails.getAssetDetailsKubernetesMetricsCharts(); + expect(hosts.length).to.equal(4); + }); + }); + }); }); }); }); diff --git a/x-pack/test/functional/apps/lens/group1/smokescreen.ts b/x-pack/test/functional/apps/lens/group1/smokescreen.ts index dbd734348ba7d..e33e65741bf66 100644 --- a/x-pack/test/functional/apps/lens/group1/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/group1/smokescreen.ts @@ -761,5 +761,48 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const hasVisualOptionsButton = await PageObjects.lens.hasVisualOptionsButton(); expect(hasVisualOptionsButton).to.be(false); }); + + it('should allow edit meta-data for Lens chart on listing page', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('Afancilenstest'); + await listingTable.inspectVisualization(); + await listingTable.editVisualizationDetails({ + title: 'Anewfancilenstest', + description: 'new description', + }); + await listingTable.searchForItemWithName('Anewfancilenstest'); + await listingTable.expectItemsCount('visualize', 1); + }); + + it('should correctly optimize multiple percentile metrics', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + for (const percentileValue of [90, 95.5, 99.9]) { + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'percentile', + field: 'bytes', + keepOpen: true, + }); + + await retry.try(async () => { + const value = `${percentileValue}`; + // Can not use testSubjects because data-test-subj is placed range input and number input + const percentileInput = await PageObjects.lens.getNumericFieldReady( + 'lns-indexPattern-percentile-input' + ); + await percentileInput.type(value); + + const attrValue = await percentileInput.getAttribute('value'); + if (attrValue !== value) { + throw new Error(`layerPanelTopHitsSize not set to ${value}`); + } + }); + + await PageObjects.lens.closeDimensionEditor(); + } + await PageObjects.lens.waitForVisualization('xyVisChart'); + expect(await PageObjects.lens.getWorkspaceErrorCount()).to.eql(0); + }); }); } diff --git a/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts b/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts index 9213c8459ebe3..91ec034295e2e 100644 --- a/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts +++ b/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; const createAndSaveDashboard = async (dashboardName: string) => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); diff --git a/x-pack/test/functional/apps/lens/group3/terms.ts b/x-pack/test/functional/apps/lens/group3/terms.ts index f93df80d52589..13b8492371405 100644 --- a/x-pack/test/functional/apps/lens/group3/terms.ts +++ b/x-pack/test/functional/apps/lens/group3/terms.ts @@ -96,62 +96,128 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.closeDimensionEditor(); }); }); - describe('sorting by custom metric', () => { - it('should allow sort by custom metric', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickVisType('lens'); - await elasticChart.setNewChartUiDebugFlag(true); - await PageObjects.lens.goToTimeRange(); - - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'average', - field: 'bytes', - }); + describe('rank by', () => { + describe('reset rank on metric change', () => { + it('should reset the ranking when using decimals on percentile', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'percentile', + field: 'bytes', + keepOpen: true, + }); + + await retry.try(async () => { + const value = '60.5'; + // Can not use testSubjects because data-test-subj is placed range input and number input + const percentileInput = await PageObjects.lens.getNumericFieldReady( + 'lns-indexPattern-percentile-input' + ); + await percentileInput.clearValueWithKeyboard(); + await percentileInput.type(value); + + const percentileValue = await percentileInput.getAttribute('value'); + if (percentileValue !== value) { + throw new Error( + `[date-test-subj="lns-indexPattern-percentile-input"] not set to ${value}` + ); + } + }); + + // close the toast about reset ranking + // note: this has also the side effect to close the dimension editor + await testSubjects.click('toastCloseButton'); + + await PageObjects.lens.openDimensionEditor( + 'lnsXY_yDimensionPanel > lns-dimensionTrigger' + ); - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'terms', - field: 'geo.src', - keepOpen: true, + await PageObjects.lens.selectOperation('percentile_rank'); + + await retry.try(async () => { + const value = '600.5'; + const percentileRankInput = await testSubjects.find( + 'lns-indexPattern-percentile_ranks-input' + ); + await percentileRankInput.clearValueWithKeyboard(); + await percentileRankInput.type(value); + + const percentileRankValue = await percentileRankInput.getAttribute('value'); + if (percentileRankValue !== value) { + throw new Error( + `[date-test-subj="lns-indexPattern-percentile_ranks-input"] not set to ${value}` + ); + } + }); + // note: this has also the side effect to close the dimension editor + await testSubjects.click('toastCloseButton'); }); - await find.clickByCssSelector( - 'select[data-test-subj="indexPattern-terms-orderBy"] > option[value="custom"]' - ); - - const fnTarget = await testSubjects.find('indexPattern-reference-function'); - await comboBox.openOptionsList(fnTarget); - await comboBox.setElement(fnTarget, 'percentile'); + }); + describe('sorting by custom metric', () => { + it('should allow sort by custom metric', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await elasticChart.setNewChartUiDebugFlag(true); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + keepOpen: true, + }); + await find.clickByCssSelector( + 'select[data-test-subj="indexPattern-terms-orderBy"] > option[value="custom"]' + ); - const fieldTarget = await testSubjects.find( - 'indexPattern-reference-field-selection-row>indexPattern-dimension-field' - ); - await comboBox.openOptionsList(fieldTarget); - await comboBox.setElement(fieldTarget, 'bytes'); + const fnTarget = await testSubjects.find('indexPattern-reference-function'); + await comboBox.openOptionsList(fnTarget); + await comboBox.setElement(fnTarget, 'percentile'); - await retry.try(async () => { - // Can not use testSubjects because data-test-subj is placed range input and number input - const percentileInput = await PageObjects.lens.getNumericFieldReady( - 'lns-indexPattern-percentile-input' + const fieldTarget = await testSubjects.find( + 'indexPattern-reference-field-selection-row>indexPattern-dimension-field' + ); + await comboBox.openOptionsList(fieldTarget); + await comboBox.setElement(fieldTarget, 'bytes'); + + await retry.try(async () => { + // Can not use testSubjects because data-test-subj is placed range input and number input + const percentileInput = await PageObjects.lens.getNumericFieldReady( + 'lns-indexPattern-percentile-input' + ); + await percentileInput.type('60'); + + const percentileValue = await percentileInput.getAttribute('value'); + if (percentileValue !== '60') { + throw new Error('layerPanelTopHitsSize not set to 60'); + } + }); + + await PageObjects.lens.waitForVisualization('xyVisChart'); + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0)).to.eql( + 'Top 5 values of geo.src' ); - await percentileInput.type('60'); - const percentileValue = await percentileInput.getAttribute('value'); - if (percentileValue !== '60') { - throw new Error('layerPanelTopHitsSize not set to 60'); - } + const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data!.bars![0].bars[0].x).to.eql('BN'); + expect(data!.bars![0].bars[0].y).to.eql(19265); }); - - await PageObjects.lens.waitForVisualization('xyVisChart'); - await PageObjects.lens.closeDimensionEditor(); - - expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0)).to.eql( - 'Top 5 values of geo.src' - ); - - const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); - expect(data!.bars![0].bars[0].x).to.eql('BN'); - expect(data!.bars![0].bars[0].y).to.eql(19265); }); }); diff --git a/x-pack/test/functional/apps/lens/group4/dashboard.ts b/x-pack/test/functional/apps/lens/group4/dashboard.ts index e94f935323235..50e8c427567d2 100644 --- a/x-pack/test/functional/apps/lens/group4/dashboard.ts +++ b/x-pack/test/functional/apps/lens/group4/dashboard.ts @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('lens dashboard tests', () => { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await security.testUser.setRoles( [ 'global_dashboard_all', @@ -68,7 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to add filters/timerange by clicking in XYChart', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); @@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to add filters by right clicking in XYChart', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); @@ -121,7 +121,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Requires xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled // setting set in kibana.yml to test (not enabled by default) it('should hide old "explore underlying data" action', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); @@ -135,7 +135,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to add filters by clicking in pie chart', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); @@ -156,7 +156,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not carry over filters if creating a new lens visualization from within dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultAbsoluteRange(); await filterBar.addFilter({ field: 'geo.src', operation: 'is', value: 'US' }); @@ -174,7 +174,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('CSV export action exists in panel context menu', async () => { const ACTION_ID = 'ACTION_EXPORT_CSV'; const ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); @@ -190,7 +190,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show all data from all layers in the inspector', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickCreateNewLink(); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -234,7 +234,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('unlink lens panel from embeddable library', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); @@ -270,7 +270,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show validation messages if any error appears', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickCreateNewLink(); @@ -300,7 +300,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should recover lens panel in an error state when fixing search query', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); diff --git a/x-pack/test/functional/apps/lens/group4/tsdb.ts b/x-pack/test/functional/apps/lens/group4/tsdb.ts index edbef46dc1f08..745592a02cb1d 100644 --- a/x-pack/test/functional/apps/lens/group4/tsdb.ts +++ b/x-pack/test/functional/apps/lens/group4/tsdb.ts @@ -384,8 +384,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/163971 - describe.skip('for rolled up metric (downsampled)', () => { + describe('for rolled up metric (downsampled)', () => { it('defaults to average for rolled up metric', async () => { await PageObjects.lens.switchDataPanelIndexPattern(downsampleDataView.dataView); await PageObjects.lens.removeLayer(); @@ -622,21 +621,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { index: 'regular_index', create: true, removeTSDBFields: true }, ], }, - // { - // name: 'Dataview with an additional downsampled TSDB stream', - // indexes: [ - // { index: initialIndex }, - // { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true }, - // ], - // }, - // { - // name: 'Dataview with additional regular index and a downsampled TSDB stream', - // indexes: [ - // { index: initialIndex }, - // { index: 'regular_index', create: true, removeTSDBFields: true }, - // { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true }, - // ], - // }, + { + name: 'Dataview with an additional downsampled TSDB stream', + indexes: [ + { index: initialIndex }, + { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true }, + ], + }, + { + name: 'Dataview with additional regular index and a downsampled TSDB stream', + indexes: [ + { index: initialIndex }, + { index: 'regular_index', create: true, removeTSDBFields: true }, + { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true }, + ], + }, { name: 'Dataview with an additional TSDB stream', indexes: [{ index: initialIndex }, { index: 'tsdb_index_2', create: true, tsdb: true }], @@ -827,16 +826,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // check there's some data after the upgrade expect(counterBars[counterBars.length - 1].y).to.eql(5000); + // due to the flaky nature of exact check here, we're going to relax it + // as long as there's data before and after it is ok log.info('Check count before the upgrade'); const columnsToCheck = countBars.length / 2; // Before the upgrade the count is N times the indexes - expect(sumFirstNValues(columnsToCheck, countBars)).to.eql( - indexes.length * TEST_DOC_COUNT + expect(sumFirstNValues(columnsToCheck, countBars)).to.be.greaterThan( + indexes.length * TEST_DOC_COUNT - 1 ); log.info('Check count after the upgrade'); // later there are only documents for the upgraded stream - expect(sumFirstNValues(columnsToCheck, [...countBars].reverse())).to.eql( - TEST_DOC_COUNT + expect(sumFirstNValues(columnsToCheck, [...countBars].reverse())).to.be.greaterThan( + TEST_DOC_COUNT - 1 ); }); }); @@ -912,12 +913,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); const bars = data.bars![0].bars; const columnsToCheck = bars.length / 2; + // due to the flaky nature of exact check here, we're going to relax it + // as long as there's data before and after it is ok log.info('Check count before the downgrade'); // Before the upgrade the count is N times the indexes - expect(sumFirstNValues(columnsToCheck, bars)).to.eql(indexes.length * TEST_DOC_COUNT); + expect(sumFirstNValues(columnsToCheck, bars)).to.be.greaterThan( + indexes.length * TEST_DOC_COUNT - 1 + ); log.info('Check count after the downgrade'); // later there are only documents for the upgraded stream - expect(sumFirstNValues(columnsToCheck, [...bars].reverse())).to.eql(TEST_DOC_COUNT); + expect(sumFirstNValues(columnsToCheck, [...bars].reverse())).to.be.greaterThan( + TEST_DOC_COUNT - 1 + ); }); it('should visualize data when moving the time window around the downgrade moment', async () => { diff --git a/x-pack/test/functional/apps/lens/group6/error_handling.ts b/x-pack/test/functional/apps/lens/group6/error_handling.ts index 50e1ab439308d..f268e829ca5fb 100644 --- a/x-pack/test/functional/apps/lens/group6/error_handling.ts +++ b/x-pack/test/functional/apps/lens/group6/error_handling.ts @@ -87,7 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'x-pack/test/functional/fixtures/kbn_archiver/lens/missing_fields' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard( 'dashboard containing vis with missing fields' ); @@ -135,7 +135,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'x-pack/test/functional/fixtures/kbn_archiver/lens/fundamental_config_errors_on_dashboard' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('lens fundamental config errors dash'); const failureElements = await testSubjects.findAll('errorMessageMarkdown'); diff --git a/x-pack/test/functional/apps/lens/group6/lens_reporting.ts b/x-pack/test/functional/apps/lens/group6/lens_reporting.ts index dc241c7f3ac49..3141d2d7651fc 100644 --- a/x-pack/test/functional/apps/lens/group6/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/group6/lens_reporting.ts @@ -53,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not cause PDF reports to fail', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await listingTable.clickItemLink('dashboard', 'Lens reportz'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); diff --git a/x-pack/test/functional/apps/lens/group6/lens_tagging.ts b/x-pack/test/functional/apps/lens/group6/lens_tagging.ts index 42bb90e84a903..c3b279f591cdb 100644 --- a/x-pack/test/functional/apps/lens/group6/lens_tagging.ts +++ b/x-pack/test/functional/apps/lens/group6/lens_tagging.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/xy.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/xy.ts index bc3451a32fb6d..7d912221e2b15 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/xy.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/xy.ts @@ -357,5 +357,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); expect(data?.legend?.items.map((item) => item.name)).to.eql(expectedData); }); + + it('should convert correctly percentiles with decimals', async () => { + await visEditor.clickBucket('Y-axis', 'metrics'); + await visEditor.selectAggregation('Percentiles', 'metrics'); + await visEditor.selectField('memory', 'metrics'); + await visEditor.setPercentileValue('99.99', 6); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + expect(await lens.getWorkspaceErrorCount()).to.eql(0); + }); }); } diff --git a/x-pack/test/functional/apps/maps/group1/sample_data.js b/x-pack/test/functional/apps/maps/group1/sample_data.js index d9ef08ef712f1..09b29f5e529c3 100644 --- a/x-pack/test/functional/apps/maps/group1/sample_data.js +++ b/x-pack/test/functional/apps/maps/group1/sample_data.js @@ -113,7 +113,7 @@ export default function ({ getPageObjects, getService, updateBaselines }) { 'flights_map', updateBaselines ); - expect(percentDifference).to.be.lessThan(0.02); + expect(percentDifference).to.be.lessThan(0.022); }); }); @@ -138,7 +138,7 @@ export default function ({ getPageObjects, getService, updateBaselines }) { 'web_logs_map', updateBaselines ); - expect(percentDifference).to.be.lessThan(0.02); + expect(percentDifference).to.be.lessThan(0.031); }); }); }); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js b/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js index 1b4c1914a156b..fcd3d06115508 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js @@ -83,7 +83,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow new map be added by value to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Very Cool Dashboard'); @@ -113,7 +113,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow existing maps be added by value to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Wonderful Dashboard'); @@ -185,7 +185,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow new map be added by reference to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Super Cool Dashboard'); @@ -215,7 +215,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow existing maps be added by reference to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Amazing Dashboard'); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts b/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts index d12d1bdce4ecf..8adab9fa86bb4 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts +++ b/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts @@ -14,9 +14,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('Map embeddable in canvas', () => { before(async () => { - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-c74f9c27-a142-4664-bf8a-69bf782fc268/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Canvas with map'); }); it('should render map embeddable', async () => { diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js index 69c12ed786d23..2750bf3a7f68d 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js @@ -35,7 +35,7 @@ export default function ({ getPageObjects, getService }) { defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: true, }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.dashboard.waitForRenderComplete(); }); @@ -164,7 +164,7 @@ export default function ({ getPageObjects, getService }) { // see https://github.com/elastic/kibana/issues/61596 on why it is specific to maps it("dashboard's back button should navigate to previous page", async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js index aa54a54196f95..45b1754722153 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }) { await kibanaServer.uiSettings.replace({ defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickEditorMenuButton(); await PageObjects.visualize.clickMapsApp(); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js index ba0ac1153aa53..036edc77df796 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js @@ -21,7 +21,7 @@ export default function ({ getPageObjects, getService }) { await kibanaServer.uiSettings.replace({ defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addEmbeddable('document example', 'map'); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js b/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js index b08c284506e18..4da0d2af33894 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js @@ -19,7 +19,7 @@ export default function ({ getPageObjects, getService }) { ['test_logstash_reader', 'global_maps_all', 'global_dashboard_all'], { skipBrowserRefresh: true } ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode('filter by map extent dashboard'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js b/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js index faa65d6ab183a..14ff01e4da46f 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js @@ -41,7 +41,7 @@ export default function ({ getPageObjects, getService }) { describe('new map', () => { beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickEditorMenuButton(); await dashboardAddPanel.clickVisType('maps'); @@ -73,7 +73,7 @@ export default function ({ getPageObjects, getService }) { describe('edit existing map', () => { beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.dashboard.switchToEditMode(); await dashboardPanelActions.editPanelByTitle('join example'); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js index c3c014447a36d..e7265f4d7883d 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js @@ -30,7 +30,7 @@ export default function ({ getPageObjects, getService }) { defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dash for tooltip filter action test'); diff --git a/x-pack/test/functional/apps/maps/group3/reports/index.ts b/x-pack/test/functional/apps/maps/group3/reports/index.ts index cb4f64b348ba5..0249658b70055 100644 --- a/x-pack/test/functional/apps/maps/group3/reports/index.ts +++ b/x-pack/test/functional/apps/maps/group3/reports/index.ts @@ -61,7 +61,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('PNG file matches the baseline image, using sample geo data', async function () { await reporting.initEcommerce(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('Ecommerce Map'); await PageObjects.reporting.openPngReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); @@ -73,7 +73,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('PNG file matches the baseline image, using embeddable example', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.reporting.openPngReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); diff --git a/x-pack/test/functional/apps/maps/group4/visualize_create_menu.js b/x-pack/test/functional/apps/maps/group4/visualize_create_menu.js index f91bd55452fa6..d9b37b4aca240 100644 --- a/x-pack/test/functional/apps/maps/group4/visualize_create_menu.js +++ b/x-pack/test/functional/apps/maps/group4/visualize_create_menu.js @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['visualize', 'header', 'maps']); - + const listingTable = getService('listingTable'); const security = getService('security'); describe('visualize create menu', () => { @@ -85,5 +85,37 @@ export default function ({ getService, getPageObjects }) { expect(hasLegecyViz).to.equal(false); }); }); + describe('edit meta-data', () => { + before(async () => { + await security.testUser.setRoles( + ['global_maps_all', 'global_visualize_all', 'test_logstash_reader'], + { + skipBrowserRefresh: true, + } + ); + + await PageObjects.visualize.navigateToNewAggBasedVisualization(); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + }); + + it('should allow to change meta-data on a map visualization', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickMapsApp(); + await PageObjects.maps.waitForLayersToLoad(); + await PageObjects.maps.saveMap('myTestMap'); + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('myTestMap'); + await listingTable.inspectVisualization(); + await listingTable.editVisualizationDetails({ + title: 'AnotherTestMap', + description: 'new description', + }); + await listingTable.searchForItemWithName('AnotherTestMap'); + await listingTable.expectItemsCount('visualize', 1); + }); + }); }); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts index f2273b168489b..9927f37a6c8f9 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts @@ -59,7 +59,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { testData.jobConfig, testData.datafeedConfig ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts index 7058286f3d5b3..2760c3c136557 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { testDataList.map((d) => d.dashboardSavedObject) ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { @@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const { dashboardSavedObject, panelTitle, type } = testData; describe(`for ${panelTitle}`, function () { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts index 90884b1644774..eef7461bec609 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts @@ -55,7 +55,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); afterEach(async () => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts index b8e39eaa3ba2c..089141ba663e7 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts @@ -107,7 +107,7 @@ export default function ({ getService, getPageObject, getPageObjects }: FtrProvi }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); let tabsCount = 1; diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts index 63cac23c5ed1d..3ce45876f77b1 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); afterEach(async () => { diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index 15533e95e5873..794fad083be22 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -246,7 +246,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { lensMetricField.fieldName, 'legacyMtrVis' ); - await ml.navigation.browserBackTo('dataVisualizerTable'); + await ml.navigation.browserBackTo('dataVisualizerTableContainer'); } const lensNonMetricField = testData.expected.nonMetricFields?.find( (f) => f.type === ML_JOB_FIELD_TYPES.KEYWORD @@ -257,7 +257,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { lensNonMetricField.fieldName, 'legacyMtrVis' ); - await ml.navigation.browserBackTo('dataVisualizerTable'); + await ml.navigation.browserBackTo('dataVisualizerTableContainer'); } }); }); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts index d4814a6f0ec11..47c6e7725686d 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts @@ -58,7 +58,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it(`displays Field statistics table in Dashboard when enabled`, async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addSavedSearch(savedSearchTitle); @@ -96,7 +96,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it(`doesn't display Field statistics table in Dashboard when disabled`, async function () { await ml.testResources.setAdvancedSettingProperty(SHOW_FIELD_STATISTICS, false); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode(dashboardTitle); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/observability_log_explorer/columns_selection.ts b/x-pack/test/functional/apps/observability_log_explorer/columns_selection.ts index c61a2586522fd..b8af643d828c7 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/columns_selection.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/columns_selection.ts @@ -9,7 +9,7 @@ import rison from '@kbn/rison'; import querystring from 'querystring'; import { FtrProviderContext } from '../../ftr_provider_context'; -const defaultLogColumns = ['@timestamp', 'message']; +const defaultLogColumns = ['@timestamp', 'service.name', 'host.name', 'message']; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); @@ -44,7 +44,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.navigateTo({ search: querystring.stringify({ _a: rison.encode({ - columns: ['message', 'data_stream.namespace'], + columns: ['service.name', 'host.name', 'message', 'data_stream.namespace'], }), }), }); diff --git a/x-pack/test/functional/apps/observability_log_explorer/dataset_selection_state.ts b/x-pack/test/functional/apps/observability_log_explorer/dataset_selection_state.ts index c9bcade9dce5b..1a62d952546b9 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/dataset_selection_state.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/dataset_selection_state.ts @@ -16,12 +16,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('DatasetSelection initialization and update', () => { describe('when the "index" query param does not exist', () => { - it('should initialize the "All log datasets" selection', async () => { + it('should initialize the "All logs" selection', async () => { await PageObjects.observabilityLogExplorer.navigateTo(); const datasetSelectionTitle = await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); - expect(datasetSelectionTitle).to.be('All log datasets'); + expect(datasetSelectionTitle).to.be('All logs'); }); }); @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(datasetSelectionTitle).to.be('[Azure Logs] activitylogs'); }); - it('should fallback to the "All log datasets" selection and notify the user of an invalid encoded index', async () => { + it('should fallback to the "All logs" selection and notify the user of an invalid encoded index', async () => { const invalidEncodedIndex = 'invalid-encoded-index'; await PageObjects.observabilityLogExplorer.navigateTo({ search: querystring.stringify({ @@ -53,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); await PageObjects.observabilityLogExplorer.assertRestoreFailureToastExist(); - expect(datasetSelectionTitle).to.be('All log datasets'); + expect(datasetSelectionTitle).to.be('All logs'); }); }); @@ -62,7 +62,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.navigateTo(); const allDatasetSelectionTitle = await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); - expect(allDatasetSelectionTitle).to.be('All log datasets'); + expect(allDatasetSelectionTitle).to.be('All logs'); const azureActivitylogsIndex = 'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtFgF4CuATmAHRZzwBu8sAJ5VadAFTkANAlhRU3BPyEiQASklFS8lu2kC55AII6wAAgAyNEFN5hWIJGnIBGDgFYOAJgDM5deCgeFAAVQQAHMgdkaihVIA==='; @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.goBack(); const backNavigationDatasetSelectionTitle = await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); - expect(backNavigationDatasetSelectionTitle).to.be('All log datasets'); + expect(backNavigationDatasetSelectionTitle).to.be('All logs'); }); // Go forward to previous page selection diff --git a/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts b/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts index f7a5595634895..dc8ce8b67c786 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts @@ -14,7 +14,8 @@ const initialPackageMap = { }; const initialPackagesTexts = Object.values(initialPackageMap); -const expectedUncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; +const uncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; +const expectedUncategorized = uncategorized.map((dataset) => dataset.split('-')[1]); export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); @@ -22,12 +23,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'observabilityLogExplorer']); + const noIntegrationsTitle = 'No integrations found'; + const noUncategorizedTitle = 'No data streams found'; + describe('Dataset Selector', () => { before(async () => { await PageObjects.observabilityLogExplorer.removeInstalledPackages(); }); - describe('without installed integrations or uncategorized data streams', () => { + describe('as consistent behavior', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); @@ -37,36 +41,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.openDatasetSelector(); }); - describe('when open on the first navigation level', () => { - it('should always display the "All log datasets" entry as the first item', async () => { - const allLogDatasetButton = - await PageObjects.observabilityLogExplorer.getAllLogDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); - - const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); - const firstEntryTitle = await menuEntries[0].getVisibleText(); + it('should always display the Integrations and Uncategorized top level tabs', async () => { + const integrationsTab = await PageObjects.observabilityLogExplorer.getIntegrationsTab(); + const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab(); - expect(allLogDatasetTitle).to.be('All log datasets'); - expect(allLogDatasetTitle).to.be(firstEntryTitle); - }); + expect(await integrationsTab.isDisplayed()).to.be(true); + expect(await integrationsTab.getVisibleText()).to.be('Integrations'); + expect(await uncategorizedTab.isDisplayed()).to.be(true); + expect(await uncategorizedTab.getVisibleText()).to.be('Uncategorized'); + }); - it('should always display the unmanaged datasets entry as the second item', async () => { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + it('should always display the "Show all logs" action', async () => { + const allLogDatasetButton = + await PageObjects.observabilityLogExplorer.getAllLogDatasetsButton(); - const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); - const secondEntryTitle = await menuEntries[1].getVisibleText(); + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); - expect(unmanagedDatasetTitle).to.be('Uncategorized'); - expect(unmanagedDatasetTitle).to.be(secondEntryTitle); - }); + expect(allLogDatasetTitle).to.be('Show all logs'); + }); + describe('when open on the integrations tab', () => { it('should display an error prompt if could not retrieve the integrations', async function () { // Skip the test in case network condition utils are not available try { await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoIntegrationsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noIntegrationsTitle + ); }); await PageObjects.common.sleep(5000); @@ -74,7 +75,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.typeSearchFieldWith('a'); await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoIntegrationsErrorExists(); + await PageObjects.observabilityLogExplorer.assertListStatusErrorPromptExistsWithTitle( + noIntegrationsTitle + ); }); await browser.restoreNetworkConditions(); @@ -84,21 +87,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should display an empty prompt for no integrations', async () => { - const { integrations } = await PageObjects.observabilityLogExplorer.getIntegrations(); - expect(integrations.length).to.be(0); + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - await PageObjects.observabilityLogExplorer.assertNoIntegrationsPromptExists(); + expect(menuEntries.length).to.be(0); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noIntegrationsTitle + ); }); }); - describe('when navigating into Uncategorized data streams', () => { - it('should display a loading skeleton while loading', async function () { + describe('when open on the uncategorized tab', () => { + it('should display a loading skeleton while loading uncategorized datasets', async function () { // Skip the test in case network condition utils are not available try { await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await unamanagedDatasetButton.click(); + const uncategorizedTab = + await PageObjects.observabilityLogExplorer.getUncategorizedTab(); + await uncategorizedTab.click(); await PageObjects.observabilityLogExplorer.assertLoadingSkeletonExists(); @@ -108,22 +115,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); - it('should display an error prompt if could not retrieve the data streams', async function () { + it('should display an error prompt if could not retrieve the datasets', async function () { + const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab(); + await uncategorizedTab.click(); + // Skip the test in case network condition utils are not available try { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await unamanagedDatasetButton.click(); - await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoDataStreamsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noUncategorizedTitle + ); }); + await PageObjects.common.sleep(5000); await browser.setNetworkConditions('OFFLINE'); await PageObjects.observabilityLogExplorer.typeSearchFieldWith('a'); await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoDataStreamsErrorExists(); + await PageObjects.observabilityLogExplorer.assertListStatusErrorPromptExistsWithTitle( + noUncategorizedTitle + ); }); await browser.restoreNetworkConditions(); @@ -132,17 +143,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); - it('should display an empty prompt for no data streams', async () => { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await unamanagedDatasetButton.click(); + it('should display an empty prompt for no uncategorized data streams', async () => { + const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab(); + await uncategorizedTab.click(); - const unamanagedDatasetEntries = - await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const uncategorizedEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(unamanagedDatasetEntries.length).to.be(0); + expect(uncategorizedEntries.length).to.be(0); - await PageObjects.observabilityLogExplorer.assertNoDataStreamsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noUncategorizedTitle + ); }); }); }); @@ -165,7 +178,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await cleanupIntegrationsSetup(); }); - describe('when open on the first navigation level', () => { + describe('when open on the integrations tab', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); @@ -175,30 +188,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.openDatasetSelector(); }); - it('should always display the "All log datasets" entry as the first item', async () => { - const allLogDatasetButton = - await PageObjects.observabilityLogExplorer.getAllLogDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); - - const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); - const firstEntryTitle = await menuEntries[0].getVisibleText(); - - expect(allLogDatasetTitle).to.be('All log datasets'); - expect(allLogDatasetTitle).to.be(firstEntryTitle); - }); - - it('should always display the unmanaged datasets entry as the second item', async () => { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); - - const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); - const secondEntryTitle = await menuEntries[1].getVisibleText(); - - expect(unmanagedDatasetTitle).to.be('Uncategorized'); - expect(unmanagedDatasetTitle).to.be(secondEntryTitle); - }); - it('should display a list of installed integrations', async () => { const { integrations } = await PageObjects.observabilityLogExplorer.getIntegrations(); @@ -256,7 +245,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(integrations.length).to.be(0); }); - await PageObjects.observabilityLogExplorer.assertNoIntegrationsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noIntegrationsTitle + ); }); it('should load more integrations by scrolling to the end of the list', async () => { @@ -289,136 +280,157 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { cleanupAdditionalSetup(); }); - }); - - describe('when clicking on integration and moving into the second navigation level', () => { - before(async () => { - await PageObjects.observabilityLogExplorer.navigateTo(); - }); - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogExplorer.openDatasetSelector(); - }); + describe('clicking on integration and moving into the second navigation level', () => { + before(async () => { + await PageObjects.observabilityLogExplorer.navigateTo(); + }); - it('should display a list of available datasets', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); + beforeEach(async () => { + await browser.refresh(); + await PageObjects.observabilityLogExplorer.openDatasetSelector(); }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + it('should display a list of available datasets', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); + await retry.try(async () => { + const [panelTitleNode, integrationDatasetEntries] = + await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await integrationDatasetEntries[0].getVisibleText()).to.be('access'); + expect(await integrationDatasetEntries[1].getVisibleText()).to.be('error'); + }); }); - }); - it('should sort the datasets list by the clicked sorting option', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); - }); + it('should sort the datasets list by the clicked sorting option', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + await retry.try(async () => { + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); - // Test ascending order - await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + // Test ascending order + await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); - // Test descending order - await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + // Test descending order + await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - expect(await menuEntries[1].getVisibleText()).to.be('access'); - }); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(await menuEntries[1].getVisibleText()).to.be('access'); + }); - // Test back ascending order - await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + // Test back ascending order + await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); }); - }); - it('should filter the datasets list by the typed dataset name', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); - }); + it('should filter the datasets list by the typed dataset name', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + await retry.try(async () => { + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); - await PageObjects.observabilityLogExplorer.typeSearchFieldWith('err'); + await PageObjects.observabilityLogExplorer.typeSearchFieldWith('err'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); }); - }); - it('should update the current selection with the clicked dataset', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); - }); + it('should update the current selection with the clicked dataset', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + await retry.try(async () => { + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - menuEntries[0].click(); - }); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + menuEntries[0].click(); + }); - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogExplorer.getDatasetSelectorButton(); + await retry.try(async () => { + const selectorButton = + await PageObjects.observabilityLogExplorer.getDatasetSelectorButton(); - expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + }); }); }); }); - describe('when navigating into Uncategorized data streams', () => { + describe('when open on the uncategorized tab', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); @@ -426,16 +438,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { beforeEach(async () => { await browser.refresh(); await PageObjects.observabilityLogExplorer.openDatasetSelector(); + await PageObjects.observabilityLogExplorer + .getUncategorizedTab() + .then((tab) => tab.click()); }); it('should display a list of available datasets', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); @@ -445,12 +462,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should sort the datasets list by the clicked sorting option', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); }); @@ -458,7 +473,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Test ascending order await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -468,7 +485,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Test descending order await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -478,7 +497,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Test back ascending order await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -487,18 +508,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should filter the datasets list by the typed dataset name', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); }); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -508,28 +529,30 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.typeSearchFieldWith('retail'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('logs-retail-*'); + expect(await menuEntries[0].getVisibleText()).to.be('retail'); }); }); it('should update the current selection with the clicked dataset', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); }); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('logs-gaming-*'); + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); menuEntries[0].click(); }); @@ -537,7 +560,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const selectorButton = await PageObjects.observabilityLogExplorer.getDatasetSelectorButton(); - expect(await selectorButton.getVisibleText()).to.be('logs-gaming-*'); + expect(await selectorButton.getVisibleText()).to.be(expectedUncategorized[0]); }); }); }); @@ -559,9 +582,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); expect(await menuEntries[0].getVisibleText()).to.be('access'); @@ -572,9 +600,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.openDatasetSelector(); await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); expect(await menuEntries[0].getVisibleText()).to.be('access'); @@ -600,12 +633,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('when switching between integration panels', () => { + describe('when switching between tabs or integration panels', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); - it('should remember the latest search and restore its results for each integration', async () => { + it('should remember the latest search and restore its results', async () => { await PageObjects.observabilityLogExplorer.openDatasetSelector(); await PageObjects.observabilityLogExplorer.clearSearchField(); @@ -619,9 +652,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); expect(await menuEntries[0].getVisibleText()).to.be('access'); @@ -631,15 +669,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.typeSearchFieldWith('err'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(menuEntries.length).to.be(1); expect(await menuEntries[0].getVisibleText()).to.be('error'); }); // Navigate back to integrations - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); panelTitleNode.click(); await retry.try(async () => { @@ -654,7 +695,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); const searchValue = await PageObjects.observabilityLogExplorer.getSearchFieldValue(); expect(searchValue).to.eql('err'); diff --git a/x-pack/test/functional/apps/observability_log_explorer/header_menu.ts b/x-pack/test/functional/apps/observability_log_explorer/header_menu.ts new file mode 100644 index 0000000000000..b67cac94ae32f --- /dev/null +++ b/x-pack/test/functional/apps/observability_log_explorer/header_menu.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['discover', 'observabilityLogExplorer', 'timePicker']); + + describe('Header menu', () => { + before(async () => { + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.load( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + }); + + it('should inject the app header menu on the top navbar', async () => { + const headerMenu = await PageObjects.observabilityLogExplorer.getHeaderMenu(); + expect(await headerMenu.isDisplayed()).to.be(true); + }); + + describe('Discover fallback link', () => { + before(async () => { + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + it('should render a button link ', async () => { + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + expect(await discoverLink.isDisplayed()).to.be(true); + }); + + it('should navigate to discover keeping the current columns/filters/query/time/data view', async () => { + // Set timerange to specific values to match data and retrieve config + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + await testSubjects.existOrFail('superDatePickerstartDatePopoverButton'); + await testSubjects.existOrFail('superDatePickerendDatePopoverButton'); + }); + + const timeConfig = await PageObjects.timePicker.getTimeConfig(); + + // Set query bar value + await PageObjects.observabilityLogExplorer.submitQuery('*favicon*'); + + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + discoverLink.click(); + + await PageObjects.discover.waitForDocTableLoadingComplete(); + + await retry.try(async () => { + expect(await PageObjects.discover.getCurrentlySelectedDataView()).to.eql('All logs'); + }); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql([ + '@timestamp', + 'service.name', + 'host.name', + 'message', + ]); + }); + + await retry.try(async () => { + expect(await PageObjects.timePicker.getTimeConfig()).to.eql(timeConfig); + }); + + await retry.try(async () => { + expect(await PageObjects.observabilityLogExplorer.getQueryBarValue()).to.eql('*favicon*'); + }); + }); + }); + + describe('Add data link', () => { + before(async () => { + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + it('should render a button link ', async () => { + const onboardingLink = await PageObjects.observabilityLogExplorer.getOnboardingLink(); + expect(await onboardingLink.isDisplayed()).to.be(true); + }); + + it('should navigate to the observability onboarding overview page', async () => { + const onboardingLink = await PageObjects.observabilityLogExplorer.getOnboardingLink(); + onboardingLink.click(); + + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/app/observabilityOnboarding`); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/observability_log_explorer/index.ts b/x-pack/test/functional/apps/observability_log_explorer/index.ts index 90a52663e34ce..aec38a6bb8308 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/index.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dataset_selection_state')); loadTestFile(require.resolve('./dataset_selector')); loadTestFile(require.resolve('./filter_controls')); + loadTestFile(require.resolve('./header_menu')); }); } diff --git a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts index 7761af2684cbb..b6f1e94e83af3 100644 --- a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts +++ b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts @@ -59,7 +59,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(newObjectCount - initialObjectCount).to.eql(82); // logstash by reference dashboard with drilldowns - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('by_reference_drilldown'); // dashboard should load properly await PageObjects.dashboard.expectOnDashboard('by_reference_drilldown'); diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index 56f6e7b987873..113282e5a80d6 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -171,7 +171,7 @@ export default function spaceSelectorFunctionalTests({ describe('displays separate data for each space', () => { it('in the default space', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await expectDashboardRenders('[Logs] Web Traffic'); }); diff --git a/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_runtime_mappings.ts b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_runtime_mappings.ts index 3f0adc5783893..ae6ac9adab2f5 100644 --- a/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_runtime_mappings.ts +++ b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_runtime_mappings.ts @@ -34,7 +34,8 @@ export default function ({ getService }: FtrProviderContext) { }, }; - describe('creation with runtime mappings', function () { + // Failing: See https://github.com/elastic/kibana/issues/166395 + describe.skip('creation with runtime mappings', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); await transform.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/transform/edit_clone/cloning.ts b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts index 152ea8c9caa66..0ec4ef0b67b9e 100644 --- a/x-pack/test/functional/apps/transform/edit_clone/cloning.ts +++ b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts @@ -189,7 +189,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const transform = getService('transform'); - describe('cloning', function () { + // Failing: See https://github.com/elastic/kibana/issues/165883 + describe.skip('cloning', function () { const transformConfigWithPivot = getTransformConfig(); const transformConfigWithRuntimeMapping = getTransformConfigWithRuntimeMappings(); const transformConfigWithBoolFilterAgg = getTransformConfigWithBoolFilterAgg(); diff --git a/x-pack/test/functional/apps/visualize/telemetry.ts b/x-pack/test/functional/apps/visualize/telemetry.ts index 6bf1ae510a5f4..773a21d120c93 100644 --- a/x-pack/test/functional/apps/visualize/telemetry.ts +++ b/x-pack/test/functional/apps/visualize/telemetry.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('visualizations'); await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 5bb94fd5a8897..9315b8779fc0c 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -559,7 +559,7 @@ export default async function ({ readConfigFile }) { index_management_user: { elasticsearch: { - cluster: ['monitor', 'manage_index_templates'], + cluster: ['monitor', 'manage_index_templates', 'manage_enrich'], indices: [ { names: ['*'], diff --git a/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz b/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz index ed15a9cd38902..f25f0d6cf9a65 100644 Binary files a/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz and b/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz differ diff --git a/x-pack/test/functional/page_objects/asset_details.ts b/x-pack/test/functional/page_objects/asset_details.ts index 98448ab473b94..f7f666208275c 100644 --- a/x-pack/test/functional/page_objects/asset_details.ts +++ b/x-pack/test/functional/page_objects/asset_details.ts @@ -40,6 +40,20 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { return container.findAllByCssSelector('[data-test-subj*="infraAssetDetailsMetricsChart"]'); }, + async getAssetDetailsNginxMetricsCharts() { + const container = await testSubjects.find('infraAssetDetailsNginxMetricsChartGrid'); + return container.findAllByCssSelector( + '[data-test-subj*="infraAssetDetailsNginxMetricsChart"]' + ); + }, + + async getAssetDetailsKubernetesMetricsCharts() { + const container = await testSubjects.find('infraAssetDetailsKubernetesMetricsChartGrid'); + return container.findAllByCssSelector( + '[data-test-subj*="infraAssetDetailsKubernetesMetricsChart"]' + ); + }, + async clickOverviewLinkToAlerts() { return testSubjects.click('infraAssetDetailsAlertsShowAllButton'); }, diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index e2a2c6438d1f3..a075466868bff 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -17,20 +17,36 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo const PageObjects = getPageObjects(['common']); return { + async goToListingPage() { + log.debug('CanvasPage.goToListingPage'); + // disabling the current url check because canvas moved away from + // hash router and redirects from /app/canvas#/ to /app/canvas/ + // but navigateToUrl includes hash in the url which causes test flakiness + await PageObjects.common.navigateToUrl('canvas', '', { + ensureCurrentUrl: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail('workpadListing'); + }, + async enterFullscreen() { + log.debug('CanvasPage.enterFullscreen'); const elem = await find.byCssSelector('[aria-label="View fullscreen"]', 20000); await elem.click(); }, async exitFullscreen() { + log.debug('CanvasPage.exitFullscreen'); await browser.pressKeys(browser.keys.ESCAPE); }, async openExpressionEditor() { + log.debug('CanvasPage.openExpressionEditor'); await testSubjects.click('canvasExpressionEditorButton'); }, async waitForWorkpadElements() { + log.debug('CanvasPage.waitForWorkpadElements'); await testSubjects.findAll('canvasWorkpadPage > canvasWorkpadPageElementContent'); }, @@ -40,6 +56,8 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo * to load the workpad. Resolves once the workpad is in the DOM */ async loadFirstWorkpad(workpadName: string) { + log.debug('CanvasPage.loadFirstWorkpad', workpadName); + await testSubjects.setValue('tableListSearchBox', workpadName); const elem = await testSubjects.find('canvasWorkpadTableWorkpad'); const text = await elem.getVisibleText(); expect(text).to.be(workpadName); @@ -53,6 +71,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async fillOutCustomElementForm(name: string, description: string) { + log.debug('CanvasPage.fillOutCustomElementForm', name); // Fill out the custom element form and submit it await testSubjects.setValue('canvasCustomElementForm-name', name, { clearWithKeyboard: true, @@ -65,35 +84,38 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async expectCreateWorkpadButtonEnabled() { + log.debug('CanvasPage.expectCreateWorkpadButtonEnabled'); const button = await testSubjects.find('create-workpad-button', 20000); const disabledAttr = await button.getAttribute('disabled'); expect(disabledAttr).to.be(null); }, async expectCreateWorkpadButtonDisabled() { + log.debug('CanvasPage.expectCreateWorkpadButtonDisabled'); const button = await testSubjects.find('create-workpad-button', 20000); const disabledAttr = await button.getAttribute('disabled'); expect(disabledAttr).to.be('true'); }, async openAddElementMenu() { - log.debug('openAddElementsMenu'); + log.debug('CanvasPage.openAddElementsMenu'); await testSubjects.click('add-element-button'); }, async openAddChartMenu() { - log.debug('openAddChartMenu'); + log.debug('CanvasPage.openAddChartMenu'); await this.openAddElementMenu(); await testSubjects.click('canvasAddElementMenu__Chart'); }, async createNewDatatableElement() { - log.debug('createNewDatatableElement'); + log.debug('CanvasPage.createNewDatatableElement'); await this.openAddChartMenu(); await testSubjects.click('canvasAddElementMenu__table'); }, async openSavedElementsModal() { + log.debug('CanvasPage.openSavedElementsModal'); await testSubjects.click('add-element-button'); await testSubjects.click('saved-elements-menu-option'); @@ -101,14 +123,17 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async closeSavedElementsModal() { + log.debug('CanvasPage.closeSavedElementsModal'); await testSubjects.click('saved-elements-modal-close-button'); }, async expectAddElementButton() { + log.debug('CanvasPage.expectAddElementButton'); await testSubjects.existOrFail('add-element-button'); }, async expectNoAddElementButton() { + log.debug('CanvasPage.expectNoAddElementButton'); // Ensure page is fully loaded first by waiting for the refresh button const refreshPopoverExists = await testSubjects.exists('canvas-refresh-control', { timeout: 20000, @@ -119,6 +144,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async getTimeFiltersFromDebug() { + log.debug('CanvasPage.getTimeFiltersFromDebug'); await testSubjects.existOrFail('canvasDebug__content'); const contentElem = await testSubjects.find('canvasDebug__content'); @@ -130,6 +156,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async getMatchFiltersFromDebug() { + log.debug('CanvasPage.getMatchFiltersFromDebug'); await testSubjects.existOrFail('canvasDebug__content'); const contentElem = await testSubjects.find('canvasDebug__content'); diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts index 729df5fe85c06..5059bc9f3fc76 100644 --- a/x-pack/test/functional/page_objects/index_management_page.ts +++ b/x-pack/test/functional/page_objects/index_management_page.ts @@ -30,6 +30,25 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) await testSubjects.click('indexTableIncludeHiddenIndicesToggle'); }, + async clickEnrichPolicyAt(indexOfRow: number): Promise { + const policyDetailsLinks = await testSubjects.findAll('enrichPolicyDetailsLink'); + await policyDetailsLinks[indexOfRow].click(); + }, + + async clickDeleteEnrichPolicyAt(indexOfRow: number): Promise { + const deleteButons = await testSubjects.findAll('deletePolicyButton'); + await deleteButons[indexOfRow].click(); + }, + + async clickExecuteEnrichPolicyAt(indexOfRow: number): Promise { + const executeButtons = await testSubjects.findAll('executePolicyButton'); + await executeButtons[indexOfRow].click(); + }, + + async clickConfirmModalButton(): Promise { + await testSubjects.click('confirmModalConfirmButton'); + }, + async clickDetailPanelTabAt(indexOfTab: number): Promise { const tabList = await testSubjects.findAll('detailPanelTab'); log.debug(tabList.length); @@ -87,7 +106,12 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) }, async changeTabs( - tab: 'indicesTab' | 'data_streamsTab' | 'templatesTab' | 'component_templatesTab' + tab: + | 'indicesTab' + | 'data_streamsTab' + | 'templatesTab' + | 'component_templatesTab' + | 'enrich_policiesTab' ) { await testSubjects.click(tab); }, diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index 34bcec45e7aa6..d7a2b86b2d5a3 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -43,7 +43,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide throw new Error(); } }); - return await testSubjects.find('waffleMap'); + return testSubjects.find('waffleMap'); }, async getWaffleMapTooltips() { @@ -84,7 +84,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide const color = await nodeValue.getAttribute('color'); return { name, value: parseFloat(value), color }; }); - return await Promise.all(promises); + return Promise.all(promises); }, async sortNodesBy(sort: string) { @@ -171,34 +171,31 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide return timelineSelectorsVisible.every((visible) => !visible); }, - async openInventorySwitcher() { - await testSubjects.click('openInventorySwitcher'); - return await testSubjects.find('goToHost1'); - }, - async toggleInventorySwitcher() { await testSubjects.click('openInventorySwitcher'); await testSubjects.find('goToHost'); await testSubjects.click('openInventorySwitcher'); - return await testSubjects.missingOrFail('goToHost'); + return retry.tryForTime(2 * 1000, async () => { + return testSubjects.missingOrFail('goToHost'); + }); }, async goToHost() { await testSubjects.click('openInventorySwitcher'); await testSubjects.find('goToHost'); - return await testSubjects.click('goToHost'); + return testSubjects.click('goToHost'); }, async goToPods() { await testSubjects.click('openInventorySwitcher'); await testSubjects.find('goToHost'); - return await testSubjects.click('goToPods'); + return testSubjects.click('goToPods'); }, async goToDocker() { await testSubjects.click('openInventorySwitcher'); await testSubjects.find('goToHost'); - return await testSubjects.click('goToDocker'); + return testSubjects.click('goToDocker'); }, async goToSettings() { @@ -238,23 +235,23 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide }, async getSaveViewButton() { - return await testSubjects.find('openSaveViewModal'); + return testSubjects.find('openSaveViewModal'); }, async getLoadViewsButton() { - return await testSubjects.find('loadViews'); + return testSubjects.find('loadViews'); }, async openSaveViewsFlyout() { - return await testSubjects.click('loadViews'); + return testSubjects.click('loadViews'); }, async closeSavedViewFlyout() { - return await testSubjects.click('cancelSavedViewModal'); + return testSubjects.click('cancelSavedViewModal'); }, async openCreateSaveViewModal() { - return await testSubjects.click('openSaveViewModal'); + return testSubjects.click('openSaveViewModal'); }, async openEnterViewNameAndSave() { @@ -263,23 +260,23 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide }, async getNoMetricsIndicesPrompt() { - return await testSubjects.find('noDataPage'); + return testSubjects.find('noDataPage'); }, async getNoMetricsDataPrompt() { - return await testSubjects.find('noMetricsDataPrompt'); + return testSubjects.find('noMetricsDataPrompt'); }, async getNoRemoteClusterPrompt() { - return await testSubjects.find('infraHostsNoRemoteCluster'); + return testSubjects.find('infraHostsNoRemoteCluster'); }, async getInfraMissingMetricsIndicesCallout() { - return await testSubjects.find('infraIndicesPanelSettingsWarningCallout'); + return testSubjects.find('infraIndicesPanelSettingsWarningCallout'); }, async getInfraMissingRemoteClusterIndicesCallout() { - return await testSubjects.find('infraIndicesPanelSettingsDangerCallout'); + return testSubjects.find('infraIndicesPanelSettingsDangerCallout'); }, async openSourceConfigurationFlyout() { @@ -436,7 +433,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide }, async clickDismissKubernetesTourButton() { - return await testSubjects.click('infra-kubernetesTour-dismiss'); + return testSubjects.click('infra-kubernetesTour-dismiss'); }, }; } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index a46d90eb915e0..6880a3ab46ff5 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -691,7 +691,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, async getNumericFieldReady(testSubj: string) { const numericInput = await find.byCssSelector( - `input[data-test-subj=${testSubj}][type='number']` + `input[data-test-subj="${testSubj}"][type='number']` ); await numericInput.click(); await numericInput.clearValue(); @@ -1367,17 +1367,16 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont } await dashboardAddPanel.clickCreateNewLink(); await this.goToTimeRange(); - await this.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - await this.configureDimension({ dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', operation: 'average', field: 'bytes', }); + await this.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); await this.configureDimension({ dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', diff --git a/x-pack/test/functional/page_objects/observability_log_explorer.ts b/x-pack/test/functional/page_objects/observability_log_explorer.ts index 33e85e06a16a9..fe3837e46ec24 100644 --- a/x-pack/test/functional/page_objects/observability_log_explorer.ts +++ b/x-pack/test/functional/page_objects/observability_log_explorer.ts @@ -5,6 +5,7 @@ * 2.0. */ import expect from '@kbn/expect'; +import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; import { FtrProviderContext } from '../ftr_provider_context'; export interface IntegrationPackage { @@ -180,7 +181,7 @@ export function ObservabilityLogExplorerPageObject({ }, getDatasetSelectorButton() { - return testSubjects.find('datasetSelectorPopoverButton'); + return testSubjects.find('datasetSelectorPopoverButton', 30000); // Increase timeout if refresh takes longer before opening the selector }, getDatasetSelectorContent() { @@ -191,12 +192,24 @@ export function ObservabilityLogExplorerPageObject({ return testSubjects.find('datasetSelectorSearchControls'); }, - getDatasetSelectorContextMenu() { - return testSubjects.find('datasetSelectorContextMenu'); + getIntegrationsContextMenu() { + return testSubjects.find('integrationsContextMenu'); }, - getDatasetSelectorContextMenuPanelTitle() { - return testSubjects.find('contextMenuPanelTitleButton'); + getIntegrationsTab() { + return testSubjects.find('datasetSelectorIntegrationsTab'); + }, + + getUncategorizedContextMenu() { + return testSubjects.find('uncategorizedContextMenu'); + }, + + getUncategorizedTab() { + return testSubjects.find('datasetSelectorUncategorizedTab'); + }, + + getPanelTitle(contextMenu: WebElementWrapper) { + return contextMenu.findByClassName('euiContextMenuPanelTitle'); }, async getDatasetSelectorButtonText() { @@ -204,13 +217,12 @@ export function ObservabilityLogExplorerPageObject({ return button.getVisibleText(); }, - async getCurrentPanelEntries() { - const contextMenu = await this.getDatasetSelectorContextMenu(); - return contextMenu.findAllByClassName('euiContextMenuItem', 2000); + getPanelEntries(contextMenu: WebElementWrapper) { + return contextMenu.findAllByCssSelector('.euiContextMenuItem:not([disabled])', 2000); }, getAllLogDatasetsButton() { - return testSubjects.find('allLogDatasets'); + return testSubjects.find('datasetSelectorshowAllLogs'); }, getUnmanagedDatasetsButton() { @@ -218,9 +230,9 @@ export function ObservabilityLogExplorerPageObject({ }, async getIntegrations() { - const content = await this.getDatasetSelectorContent(); + const menu = await this.getIntegrationsContextMenu(); - const nodes = await content.findAllByCssSelector('[data-test-subj*="integration-"]', 2000); + const nodes = await menu.findAllByCssSelector('[data-test-subj*="integration-"]', 2000); const integrations = await Promise.all(nodes.map((node) => node.getVisibleText())); return { @@ -287,32 +299,51 @@ export function ObservabilityLogExplorerPageObject({ return testSubjects.existOrFail('datasetSelectorSkeleton'); }, - async assertNoIntegrationsPromptExists() { - const integrationStatus = await testSubjects.find('integrationStatusItem'); - const promptTitle = await integrationStatus.findByTagName('h2'); + async assertListStatusEmptyPromptExistsWithTitle(title: string) { + const [listStatus] = await testSubjects.findAll('datasetSelectorListStatusEmptyPrompt'); + const promptTitle = await listStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be(title); + }, + + async assertListStatusErrorPromptExistsWithTitle(title: string) { + const listStatus = await testSubjects.find('datasetSelectorListStatusErrorPrompt'); + const promptTitle = await listStatus.findByTagName('h2'); - expect(await promptTitle.getVisibleText()).to.be('No integrations found'); + expect(await promptTitle.getVisibleText()).to.be(title); }, - async assertNoIntegrationsErrorExists() { - const integrationStatus = await testSubjects.find('integrationsErrorPrompt'); - const promptTitle = await integrationStatus.findByTagName('h2'); + getHeaderMenu() { + return testSubjects.find('logExplorerHeaderMenu'); + }, - expect(await promptTitle.getVisibleText()).to.be('No integrations found'); + getDiscoverFallbackLink() { + return testSubjects.find('logExplorerDiscoverFallbackLink'); }, - async assertNoDataStreamsPromptExists() { - const integrationStatus = await testSubjects.find('emptyDatasetPrompt'); - const promptTitle = await integrationStatus.findByTagName('h2'); + getOnboardingLink() { + return testSubjects.find('logExplorerOnboardingLink'); + }, - expect(await promptTitle.getVisibleText()).to.be('No data streams found'); + // Query Bar + getQueryBar() { + return testSubjects.find('queryInput'); }, - async assertNoDataStreamsErrorExists() { - const integrationStatus = await testSubjects.find('datasetErrorPrompt'); - const promptTitle = await integrationStatus.findByTagName('h2'); + async getQueryBarValue() { + const queryBar = await testSubjects.find('queryInput'); + return queryBar.getAttribute('value'); + }, + + async typeInQueryBar(query: string) { + const queryBar = await this.getQueryBar(); + await queryBar.clearValueWithKeyboard(); + return queryBar.type(query); + }, - expect(await promptTitle.getVisibleText()).to.be('No data streams found'); + async submitQuery(query: string) { + await this.typeInQueryBar(query); + await testSubjects.click('querySubmitButton'); }, }; } diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts index 1db3541da17c8..4498689a428ba 100644 --- a/x-pack/test/functional/page_objects/security_page.ts +++ b/x-pack/test/functional/page_objects/security_page.ts @@ -295,18 +295,37 @@ export class SecurityPageObject extends FtrService { return user as AuthenticatedUser; } - async forceLogout() { + async forceLogout( + { waitForLoginPage }: { waitForLoginPage: boolean } = { waitForLoginPage: true } + ) { this.log.debug('SecurityPage.forceLogout'); if (await this.find.existsByDisplayedByCssSelector('.login-form', 100)) { this.log.debug('Already on the login page, not forcing anything'); return; } - this.log.debug('Redirecting to /logout to force the logout'); + this.log.debug(`Redirecting to ${this.deployment.getHostPort()}/logout to force the logout`); const url = this.deployment.getHostPort() + '/logout'; await this.browser.get(url); - this.log.debug('Waiting on the login form to appear'); - await this.waitForLoginPage(); + + // After logging out, the user can be redirected to various locations depending on the context. By default, we + // expect the user to be redirected to the login page. However, if the login page is not available for some reason, + // we should simply wait until the user is redirected *elsewhere*. + if (waitForLoginPage) { + this.log.debug('Waiting on the login form to appear'); + await this.waitForLoginPage(); + } else { + this.log.debug('Waiting for logout to complete'); + await this.retry.waitFor('Waiting for logout to complete', async () => { + // There are cases when browser/Kibana would like users to confirm that they want to navigate away from the + // current page and lose the state (e.g. unsaved changes) via native alert dialog. + const alert = await this.browser.getAlert(); + if (alert?.accept) { + await alert.accept(); + } + return !(await this.browser.getCurrentUrl()).includes('/logout'); + }); + } } async clickRolesSection() { diff --git a/x-pack/test/functional/services/actions/api.ts b/x-pack/test/functional/services/actions/api.ts index 89eef12cb3370..0506bf451ae84 100644 --- a/x-pack/test/functional/services/actions/api.ts +++ b/x-pack/test/functional/services/actions/api.ts @@ -17,15 +17,17 @@ export function ActionsAPIServiceProvider({ getService }: FtrProviderContext) { config, secrets, connectorTypeId, + additionalRequestHeaders, }: { name: string; config: Record; secrets: Record; connectorTypeId: string; + additionalRequestHeaders?: object; }) { const { body: createdAction } = await kbnSupertest .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') + .set({ ...additionalRequestHeaders, 'kbn-xsrf': 'foo' }) .send({ name, config, @@ -37,24 +39,24 @@ export function ActionsAPIServiceProvider({ getService }: FtrProviderContext) { return createdAction; }, - async deleteConnector(id: string) { + async deleteConnector(id: string, additionalRequestHeaders?: object) { log.debug(`Deleting connector with id '${id}'...`); const rsp = kbnSupertest .delete(`/api/actions/connector/${id}`) - .set('kbn-xsrf', 'foo') + .set({ ...additionalRequestHeaders, 'kbn-xsrf': 'foo' }) .expect(204, ''); log.debug('> Connector deleted.'); return rsp; }, - async deleteAllConnectors() { + async deleteAllConnectors(additionalRequestHeaders?: object) { const { body } = await kbnSupertest .get(`/api/actions/connectors`) - .set('kbn-xsrf', 'foo') + .set({ ...additionalRequestHeaders, 'kbn-xsrf': 'foo' }) .expect(200); for (const connector of body) { - await this.deleteConnector(connector.id); + await this.deleteConnector(connector.id, additionalRequestHeaders); } }, }; diff --git a/x-pack/test/functional/services/cases/test_resources.ts b/x-pack/test/functional/services/cases/test_resources.ts index 135bf13a3a2d6..f3ef6ed29832e 100644 --- a/x-pack/test/functional/services/cases/test_resources.ts +++ b/x-pack/test/functional/services/cases/test_resources.ts @@ -12,13 +12,18 @@ export function CasesTestResourcesServiceProvider({ getService }: FtrProviderCon return { async installKibanaSampleData(sampleDataId: 'ecommerce' | 'flights' | 'logs') { - await supertest.post(`/api/sample_data/${sampleDataId}`).set('kbn-xsrf', 'true').expect(200); + await supertest + .post(`/api/sample_data/${sampleDataId}`) + .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') + .expect(200); }, async removeKibanaSampleData(sampleDataId: 'ecommerce' | 'flights' | 'logs') { await supertest .delete(`/api/sample_data/${sampleDataId}`) .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') .expect(204); }, }; diff --git a/x-pack/test/functional/services/ml/anomaly_explorer.ts b/x-pack/test/functional/services/ml/anomaly_explorer.ts index b3353556b1cc6..cb183eee0c543 100644 --- a/x-pack/test/functional/services/ml/anomaly_explorer.ts +++ b/x-pack/test/functional/services/ml/anomaly_explorer.ts @@ -112,10 +112,15 @@ export function MachineLearningAnomalyExplorerProvider( await testSubjects.click(`mlAnomaliesTableEntityCellRemoveFilterButton-${influencerValue}`); }, - async openAddToDashboardControl() { + async openAddToDashboardControl(swimLaneType: SwimlaneType = 'overall') { await testSubjects.click('mlAnomalyTimelinePanelMenu'); await testSubjects.click('mlAnomalyTimelinePanelAddToDashboardButton'); - await testSubjects.existOrFail('mlAddToDashboardModal'); + if (swimLaneType === 'overall') { + await testSubjects.click('mlAnomalyTimelinePanelAddOverallToDashboardButton'); + } else { + await testSubjects.click('mlAnomalyTimelinePanelAddViewByToDashboardButton'); + } + await testSubjects.existOrFail('savedObjectSaveModal'); }, async attachSwimLaneToCase(swimLaneType: SwimlaneType = 'overall', params: CreateCaseParams) { @@ -132,14 +137,24 @@ export function MachineLearningAnomalyExplorerProvider( async addAndEditSwimlaneInDashboard(dashboardTitle: string) { await retry.tryForTime(30 * 1000, async () => { - await this.filterDashboardSearchWithSearchString(dashboardTitle); - await testSubjects.clickWhenNotDisabledWithoutRetry('~mlEmbeddableAddAndEditDashboard'); + const dashboardSelector = await testSubjects.find('add-to-dashboard-options'); + const label = await dashboardSelector.findByCssSelector( + `label[for="new-dashboard-option"]` + ); + await label.click(); + await testSubjects.click('confirmSaveSavedObjectButton'); + await retry.waitForWithTimeout('Save modal to disappear', 1000, () => + testSubjects + .missingOrFail('confirmSaveSavedObjectButton') + .then(() => true) + .catch(() => false) + ); // make sure the dashboard page actually loaded const dashboardItemCount = await dashboardPage.getSharedItemsCount(); expect(dashboardItemCount).to.not.eql(undefined); }); - // changing to the dashboard app might take sime time + // changing to the dashboard app might take some time const embeddable = await testSubjects.find('mlAnomalySwimlaneEmbeddableWrapper', 30 * 1000); const swimlane = await embeddable.findByClassName('mlSwimLaneContainer'); expect(await swimlane.isDisplayed()).to.eql( @@ -156,23 +171,6 @@ export function MachineLearningAnomalyExplorerProvider( await testSubjects.existOrFail('mlDashboardSelectionTable loaded', { timeout: 60 * 1000 }); }, - async filterDashboardSearchWithSearchString(filter: string, expectedRowCount: number = 1) { - await retry.tryForTime(20 * 1000, async () => { - await this.waitForDashboardsToLoad(); - const searchBarInput = await testSubjects.find('mlDashboardsSearchBox'); - await searchBarInput.clearValueWithKeyboard(); - await searchBarInput.type(filter); - await this.assertDashboardSearchInputValue(filter); - await this.waitForDashboardsToLoad(); - - const dashboardRows = await testSubjects.findAll('~mlDashboardSelectionTableRow', 2000); - expect(dashboardRows.length).to.eql( - expectedRowCount, - `Dashboard table should have ${expectedRowCount} rows, got ${dashboardRows.length}` - ); - }); - }, - async assertDashboardSearchInputValue(expectedSearchValue: string) { const searchBarInput = await testSubjects.find('mlDashboardsSearchBox'); const actualSearchValue = await searchBarInput.getAttribute('value'); @@ -232,6 +230,7 @@ export function MachineLearningAnomalyExplorerProvider( async attachAnomalyChartsToCase(params: CreateCaseParams) { await testSubjects.click('mlExplorerAnomalyPanelMenu'); await testSubjects.click('mlAnomalyAttachChartsToCasesButton'); + await testSubjects.click('mlAnomalyChartsSubmitAttachment'); await cases.create.createCaseFromModal(params); }, diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts index 9fd49091300e4..09566d96f12b6 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts @@ -136,7 +136,7 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ async assertDataVisualizerTableExist() { await retry.tryForTime(5000, async () => { - await testSubjects.existOrFail(`dataVisualizerTable`); + await testSubjects.existOrFail(`~dataVisualizerTable-loaded`); }); }, diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index dd9de598e3e51..ac6b1309d1d3d 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -24,7 +24,7 @@ export function MachineLearningDataVisualizerTableProvider( return new (class DataVisualizerTable { public async parseDataVisualizerTable() { - const table = await testSubjects.find('~dataVisualizerTable'); + const table = await testSubjects.find('~dataVisualizerTable-loaded'); const $ = await table.parseDomContent(); const rows = []; @@ -74,7 +74,7 @@ export function MachineLearningDataVisualizerTableProvider( } public rowSelector(fieldName: string, subSelector?: string) { - const row = `~dataVisualizerTable > ~row-${fieldName}`; + const row = `~dataVisualizerTableContainer > ~row-${fieldName}`; return !subSelector ? row : `${row} > ${subSelector}`; } @@ -102,7 +102,7 @@ export function MachineLearningDataVisualizerTableProvider( } public detailsSelector(fieldName: string, subSelector?: string) { - const row = `~dataVisualizerTable > ~dataVisualizerFieldExpandedRow-${fieldName}`; + const row = `~dataVisualizerTableContainer > ~dataVisualizerFieldExpandedRow-${fieldName}`; return !subSelector ? row : `${row} > ${subSelector}`; } @@ -566,7 +566,7 @@ export function MachineLearningDataVisualizerTableProvider( } public async ensureNumRowsPerPage(n: 10 | 25 | 50) { - const paginationButton = 'dataVisualizerTable > tablePaginationPopoverButton'; + const paginationButton = 'dataVisualizerTableContainer > tablePaginationPopoverButton'; await retry.tryForTime(10000, async () => { await testSubjects.existOrFail(paginationButton); await testSubjects.click(paginationButton); diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index bf5f6aed44789..a256c623adbe0 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test'; import { JobType } from '@kbn/ml-plugin/common/types/saved_objects'; +import { API_VERSIONS } from '@kbn/fleet-plugin/common/constants'; import { savedSearches, dashboards } from './test_resources_data'; import { getCommonRequestHeader } from './common_api'; import { MlApi } from './api'; @@ -567,7 +568,7 @@ export function MachineLearningTestResourcesProvider( await retry.tryForTime(2 * 60 * 1000, async () => { const { body, status } = await supertest .post(`/api/fleet/setup`) - .set(getCommonRequestHeader('1')); + .set(getCommonRequestHeader(`${API_VERSIONS.public.v1}`)); mlApi.assertResponseStatusCode(200, status, body); }); log.debug(` > Setup done`); @@ -581,7 +582,7 @@ export function MachineLearningTestResourcesProvider( await retry.tryForTime(30 * 1000, async () => { const { body, status } = await supertest .post(`/api/fleet/epm/packages/${packageName}/${version}`) - .set(getCommonRequestHeader('1')); + .set(getCommonRequestHeader(`${API_VERSIONS.public.v1}`)); mlApi.assertResponseStatusCode(200, status, body); }); @@ -595,7 +596,7 @@ export function MachineLearningTestResourcesProvider( await retry.tryForTime(30 * 1000, async () => { const { body, status } = await supertest .delete(`/api/fleet/epm/packages/${packageName}/${version}`) - .set(getCommonRequestHeader('1')); + .set(getCommonRequestHeader(`${API_VERSIONS.public.v1}`)); mlApi.assertResponseStatusCode(200, status, body); }); @@ -609,7 +610,7 @@ export function MachineLearningTestResourcesProvider( await retry.tryForTime(10 * 1000, async () => { const { body, status } = await supertest .get(`/api/fleet/epm/packages?experimental=true`) - .set(getCommonRequestHeader('1')); + .set(getCommonRequestHeader(`${API_VERSIONS.public.v1}`)); mlApi.assertResponseStatusCode(200, status, body); packageVersion = diff --git a/x-pack/test/functional/services/observability/alerts/common.ts b/x-pack/test/functional/services/observability/alerts/common.ts index 5986de63a2d74..57403ef8c3ab3 100644 --- a/x-pack/test/functional/services/observability/alerts/common.ts +++ b/x-pack/test/functional/services/observability/alerts/common.ts @@ -57,6 +57,15 @@ export function ObservabilityAlertsCommonProvider({ ); }; + const navigateToRulesLogsPage = async () => { + return await pageObjects.common.navigateToUrlWithBrowserHistory( + 'observability', + '/alerts/rules/logs', + '', + { ensureCurrentUrl: false } + ); + }; + const navigateToAlertDetails = async (alertId: string) => { return await pageObjects.common.navigateToUrlWithBrowserHistory( 'observability', @@ -338,6 +347,7 @@ export function ObservabilityAlertsCommonProvider({ getAlertsFlyoutViewRuleDetailsLinkOrFail, getRuleStatValue, navigateToRulesPage, + navigateToRulesLogsPage, navigateToRuleDetailsByRuleId, navigateToAlertDetails, }; diff --git a/x-pack/test/functional/services/observability/alerts/rules_page.ts b/x-pack/test/functional/services/observability/alerts/rules_page.ts index ff8e21c943725..226d257ed918b 100644 --- a/x-pack/test/functional/services/observability/alerts/rules_page.ts +++ b/x-pack/test/functional/services/observability/alerts/rules_page.ts @@ -8,6 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const find = getService('find'); const getManageRulesPageHref = async () => { const manageRulesPageButton = await testSubjects.find('manageRulesPageButton'); @@ -23,10 +24,18 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont const clickDisableFromDropDownMenu = async () => testSubjects.click('statusDropdownDisabledItem'); + const clickLogsTab = async () => testSubjects.click('ruleLogsTab'); + + const clickOnRuleInEventLogs = async () => { + await find.clickByButtonText('metric-threshold'); + }; + return { getManageRulesPageHref, clickCreateRuleButton, clickRuleStatusDropDownMenu, clickDisableFromDropDownMenu, + clickLogsTab, + clickOnRuleInEventLogs, }; } diff --git a/x-pack/test/functional/services/rules/api.ts b/x-pack/test/functional/services/rules/api.ts index 3697756ef0da4..8ad6e45ee8572 100644 --- a/x-pack/test/functional/services/rules/api.ts +++ b/x-pack/test/functional/services/rules/api.ts @@ -56,11 +56,11 @@ export function RulesAPIServiceProvider({ getService }: FtrProviderContext) { return rsp; }, - async deleteAllRules() { + async deleteAllRules(additionalRequestHeaders?: object) { log.debug(`Deleting all rules...`); const { body } = await kbnSupertest .get(`/api/alerting/rules/_find`) - .set('kbn-xsrf', 'foo') + .set({ ...additionalRequestHeaders, 'kbn-xsrf': 'foo' }) .expect(200); for (const rule of body.data) { diff --git a/x-pack/test/functional/services/sample_data/test_resources.ts b/x-pack/test/functional/services/sample_data/test_resources.ts index bdf1c7ff93e42..5e5b5947464c6 100644 --- a/x-pack/test/functional/services/sample_data/test_resources.ts +++ b/x-pack/test/functional/services/sample_data/test_resources.ts @@ -13,21 +13,30 @@ export function SampleDataTestResourcesServiceProvider({ getService }: FtrProvid const kibanaServer = getService('kibanaServer'); return { - async installKibanaSampleData(sampleDataId: 'ecommerce' | 'flights' | 'logs') { - await supertest.post(`/api/sample_data/${sampleDataId}`).set('kbn-xsrf', 'true').expect(200); + async installKibanaSampleData( + sampleDataId: 'ecommerce' | 'flights' | 'logs', + additionalRequestHeaders?: object + ) { + await supertest + .post(`/api/sample_data/${sampleDataId}`) + .set({ ...additionalRequestHeaders, 'kbn-xsrf': 'true' }) + .expect(200); }, - async removeKibanaSampleData(sampleDataId: 'ecommerce' | 'flights' | 'logs') { + async removeKibanaSampleData( + sampleDataId: 'ecommerce' | 'flights' | 'logs', + additionalRequestHeaders?: object + ) { await supertest .delete(`/api/sample_data/${sampleDataId}`) - .set('kbn-xsrf', 'true') + .set({ ...additionalRequestHeaders, 'kbn-xsrf': 'true' }) .expect(204); }, - async installAllKibanaSampleData() { - await this.installKibanaSampleData('ecommerce'); - await this.installKibanaSampleData('flights'); - await this.installKibanaSampleData('logs'); + async installAllKibanaSampleData(additionalRequestHeaders?: object) { + await this.installKibanaSampleData('ecommerce', additionalRequestHeaders); + await this.installKibanaSampleData('flights', additionalRequestHeaders); + await this.installKibanaSampleData('logs', additionalRequestHeaders); // Sample data is shifted to be relative to current time // This means that a static timerange will return different documents @@ -46,10 +55,10 @@ export function SampleDataTestResourcesServiceProvider({ getService }: FtrProvid }); }, - async removeAllKibanaSampleData() { - await this.removeKibanaSampleData('ecommerce'); - await this.removeKibanaSampleData('flights'); - await this.removeKibanaSampleData('logs'); + async removeAllKibanaSampleData(additionalRequestHeaders?: object) { + await this.removeKibanaSampleData('ecommerce', additionalRequestHeaders); + await this.removeKibanaSampleData('flights', additionalRequestHeaders); + await this.removeKibanaSampleData('logs', additionalRequestHeaders); }, }; } diff --git a/x-pack/test/functional_embedded/tests/iframe_embedded.ts b/x-pack/test/functional_embedded/tests/iframe_embedded.ts index 0e2a461dd15f9..6d0872e8505fd 100644 --- a/x-pack/test/functional_embedded/tests/iframe_embedded.ts +++ b/x-pack/test/functional_embedded/tests/iframe_embedded.ts @@ -6,8 +6,6 @@ */ import Url from 'url'; -import expect from '@kbn/expect'; - import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -17,12 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - // FLAKY https://github.com/elastic/kibana/issues/70928 - describe.skip('in iframe', () => { + describe('in iframe', () => { it('should open Kibana for logged-in user', async () => { - const isChromeHiddenBefore = await PageObjects.common.isChromeHidden(); - expect(isChromeHiddenBefore).to.be(true); - await PageObjects.security.login(); const { protocol, hostname, port } = config.get('servers.kibana'); diff --git a/x-pack/test/functional_execution_context/tests/browser.ts b/x-pack/test/functional_execution_context/tests/browser.ts index c168bf1783d55..1696b7ef3c312 100644 --- a/x-pack/test/functional_execution_context/tests/browser.ts +++ b/x-pack/test/functional_execution_context/tests/browser.ts @@ -12,7 +12,8 @@ import { assertLogContains, isExecutionContextLog, readLogFile } from '../test_u export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home', 'timePicker']); - describe('Browser apps', () => { + // Failing: See https://github.com/elastic/kibana/issues/166007 + describe.skip('Browser apps', () => { before(async () => { await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { useActualUrl: true, diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts index d3230de9d0b10..7eec8e96ef380 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts @@ -99,39 +99,6 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { ); }); - it('trims fields correctly while creating a case', async () => { - const titleWithSpace = 'This is a title with spaces '; - const descriptionWithSpace = - 'This is a case description with empty spaces at the end!! '; - const categoryWithSpace = 'security '; - const tagWithSpace = 'coke '; - - await cases.create.openCreateCasePage(); - await cases.create.createCase({ - title: titleWithSpace, - description: descriptionWithSpace, - tag: tagWithSpace, - severity: CaseSeverity.HIGH, - category: categoryWithSpace, - }); - - // validate title is trimmed - const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]'); - expect(await title.getVisibleText()).equal(titleWithSpace.trim()); - - // validate description is trimmed - const description = await testSubjects.find('scrollable-markdown'); - expect(await description.getVisibleText()).equal(descriptionWithSpace.trim()); - - // validate tag exists and is trimmed - const tag = await testSubjects.find(`tag-${tagWithSpace.trim()}`); - expect(await tag.getVisibleText()).equal(tagWithSpace.trim()); - - // validate category exists and is trimmed - const category = await testSubjects.find(`category-viewer-${categoryWithSpace.trim()}`); - expect(await category.getVisibleText()).equal(categoryWithSpace.trim()); - }); - describe('Assignees', function () { before(async () => { await createUsersAndRoles(getService, users, roles); diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts index 8a5b935df34ee..a62af7f5e23d6 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts @@ -805,7 +805,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('initially renders user actions list correctly', async () => { - expect(testSubjects.missingOrFail('cases-show-more-user-actions')); + await testSubjects.missingOrFail('cases-show-more-user-actions'); const userActionsLists = await find.allByCssSelector( '[data-test-subj="user-actions-list"]' @@ -821,7 +821,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { totalUpdates: 4, }); - expect(testSubjects.missingOrFail('user-actions-loading')); + await testSubjects.missingOrFail('user-actions-loading'); await header.waitUntilLoadingHasFinished(); @@ -829,7 +829,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await header.waitUntilLoadingHasFinished(); - expect(testSubjects.existOrFail('cases-show-more-user-actions')); + await testSubjects.existOrFail('cases-show-more-user-actions'); const userActionsLists = await find.allByCssSelector( '[data-test-subj="user-actions-list"]' diff --git a/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx b/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx index afc7860303db5..851134d346d9a 100644 --- a/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx +++ b/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx @@ -8,7 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { - EuiPageTemplate_Deprecated as EuiPageTemplate, + EuiPageTemplate, EuiFlexGrid, EuiFlexItem, EuiPanel, diff --git a/x-pack/test/monitoring_api_integration/apis/apm/index.ts b/x-pack/test/monitoring_api_integration/apis/apm/index.ts index dbbae596a7b59..0171da7e6b83a 100644 --- a/x-pack/test/monitoring_api_integration/apis/apm/index.ts +++ b/x-pack/test/monitoring_api_integration/apis/apm/index.ts @@ -6,9 +6,12 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { installPackage } from '../../packages'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('APM', () => { + before(() => installPackage(getService('supertest'), 'beat')); + loadTestFile(require.resolve('./overview')); loadTestFile(require.resolve('./instances')); }); diff --git a/x-pack/test/monitoring_api_integration/apis/beats/index.ts b/x-pack/test/monitoring_api_integration/apis/beats/index.ts index 01d2bc6a3c3d6..79d051835ec3a 100644 --- a/x-pack/test/monitoring_api_integration/apis/beats/index.ts +++ b/x-pack/test/monitoring_api_integration/apis/beats/index.ts @@ -6,9 +6,12 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { installPackage } from '../../packages'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Beats', () => { + before(() => installPackage(getService('supertest'), 'beat')); + loadTestFile(require.resolve('./overview')); loadTestFile(require.resolve('./beats')); loadTestFile(require.resolve('./beat')); diff --git a/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts b/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts index 8b43525fb03e2..cfcb7bf5570aa 100644 --- a/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts +++ b/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts @@ -6,9 +6,12 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { installPackage } from '../../packages'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Elasticsearch', () => { + before(() => installPackage(getService('supertest'), 'elasticsearch')); + loadTestFile(require.resolve('./ccr')); loadTestFile(require.resolve('./indices')); loadTestFile(require.resolve('./ml_jobs')); diff --git a/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts b/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts index 5942fde0fe95a..a7195e283232a 100644 --- a/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts +++ b/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts @@ -6,9 +6,12 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { installPackage } from '../../packages'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Enterprisesearch', () => { + before(() => installPackage(getService('supertest'), 'enterprisesearch')); + loadTestFile(require.resolve('./overview')); }); } diff --git a/x-pack/test/monitoring_api_integration/apis/kibana/index.ts b/x-pack/test/monitoring_api_integration/apis/kibana/index.ts index 24c5e6865021a..4409c5a871397 100644 --- a/x-pack/test/monitoring_api_integration/apis/kibana/index.ts +++ b/x-pack/test/monitoring_api_integration/apis/kibana/index.ts @@ -6,9 +6,12 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { installPackage } from '../../packages'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Kibana', () => { + before(() => installPackage(getService('supertest'), 'kibana')); + loadTestFile(require.resolve('./overview')); loadTestFile(require.resolve('./instances')); }); diff --git a/x-pack/test/monitoring_api_integration/apis/logstash/index.ts b/x-pack/test/monitoring_api_integration/apis/logstash/index.ts index 88a5980273ef5..bcde9e2cc5f31 100644 --- a/x-pack/test/monitoring_api_integration/apis/logstash/index.ts +++ b/x-pack/test/monitoring_api_integration/apis/logstash/index.ts @@ -6,9 +6,12 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { installPackage } from '../../packages'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Logstash', () => { + before(() => installPackage(getService('supertest'), 'logstash')); + loadTestFile(require.resolve('./overview')); loadTestFile(require.resolve('./nodes')); loadTestFile(require.resolve('./pipelines')); diff --git a/x-pack/test/monitoring_api_integration/config.ts b/x-pack/test/monitoring_api_integration/config.ts index cd016afb6dcfd..786fde0bb1d30 100644 --- a/x-pack/test/monitoring_api_integration/config.ts +++ b/x-pack/test/monitoring_api_integration/config.ts @@ -7,8 +7,6 @@ import { FtrConfigProviderContext } from '@kbn/test'; -import { bundledPackagesLocation, getPackagesArgs } from './packages'; - export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); @@ -22,11 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { esTestCluster: xPackAPITestsConfig.get('esTestCluster'), kbnTestServer: { ...xPackAPITestsConfig.get('kbnTestServer'), - serverArgs: [ - ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), - `--xpack.fleet.developer.bundledPackageLocation=${bundledPackagesLocation}`, - ...getPackagesArgs(), - ], + serverArgs: [...xPackAPITestsConfig.get('kbnTestServer.serverArgs')], }, }; } diff --git a/x-pack/test/monitoring_api_integration/packages.ts b/x-pack/test/monitoring_api_integration/packages.ts index aaa11d8d0fe9e..284bba089a55c 100644 --- a/x-pack/test/monitoring_api_integration/packages.ts +++ b/x-pack/test/monitoring_api_integration/packages.ts @@ -6,6 +6,10 @@ */ import path from 'path'; +import { createReadStream } from 'fs'; +import type SuperTest from 'supertest'; + +type SupportedPackage = 'beat' | 'elasticsearch' | 'enterprisesearch' | 'logstash' | 'kibana'; const PACKAGES = [ { name: 'beat', version: '0.1.3' }, @@ -25,3 +29,26 @@ export const getPackagesArgs = (): string[] => { }; export const bundledPackagesLocation = path.join(path.dirname(__filename), '/fixtures/packages'); + +export function installPackage( + supertest: SuperTest.SuperTest, + packageName: SupportedPackage +) { + const pkg = PACKAGES.find(({ name }) => name === packageName); + const request = supertest + .post('/api/fleet/epm/packages') + .set('kbn-xsrf', 'xxx') + .set('content-type', 'application/zip'); + + return new Promise((resolve, reject) => { + createReadStream(path.join(bundledPackagesLocation, `${pkg!.name}-${pkg!.version}.zip`)) + .on('data', (chunk) => request.write(chunk)) + .on('end', () => { + request + .send() + .expect(200) + .then(() => resolve()) + .catch(reject); + }); + }); +} diff --git a/x-pack/test/observability_functional/apps/observability/pages/rule_details_page.ts b/x-pack/test/observability_functional/apps/observability/pages/rule_details_page.ts index 2234f478ed3c8..f7f9b1eb9ff00 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/rule_details_page.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/rule_details_page.ts @@ -148,10 +148,14 @@ export default ({ getService }: FtrProviderContext) => { describe('Alert summary widget component', () => { before(async () => { - await observability.alerts.common.navigateToRuleDetailsByRuleId(uptimeRuleId); + await observability.alerts.common.navigateToRuleDetailsByRuleId(logThresholdRuleId); + await retry.waitFor( + 'Rule details to be visible', + async () => await testSubjects.exists('ruleDetails') + ); }); - it('shows component on the rule detils page', async () => { + it('shows component on the rule details page', async () => { await observability.components.alertSummaryWidget.getCompactComponentSelectorOrFail(); const timeRangeTitle = diff --git a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts index 42c4a0bd6d1c3..7f8711fba0ab1 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts @@ -163,6 +163,17 @@ export default ({ getService }: FtrProviderContext) => { return true; }); }); + + it('should navigate to the details page when clicking on a rule in event logs tab', async () => { + await observability.alerts.rulesPage.clickLogsTab(); + await observability.alerts.rulesPage.clickOnRuleInEventLogs(); + await testSubjects.existOrFail('ruleDetails'); + }); + + it('shows the rule event log when navigating by URL', async () => { + await observability.alerts.common.navigateToRulesLogsPage(); + await testSubjects.existOrFail('ruleEventLogListTable'); + }); }); describe('User permissions', () => { diff --git a/x-pack/test/osquery_cypress/utils.ts b/x-pack/test/osquery_cypress/utils.ts index 572170e0ec301..a852cfd8bed99 100644 --- a/x-pack/test/osquery_cypress/utils.ts +++ b/x-pack/test/osquery_cypress/utils.ts @@ -8,7 +8,7 @@ import axios from 'axios'; import semver from 'semver'; import { map } from 'lodash'; -import { PackagePolicy, CreatePackagePolicyResponse } from '@kbn/fleet-plugin/common'; +import { PackagePolicy, CreatePackagePolicyResponse, API_VERSIONS } from '@kbn/fleet-plugin/common'; import { KbnClient } from '@kbn/test'; import { GetEnrollmentAPIKeysResponse, @@ -26,7 +26,10 @@ export const getInstalledIntegration = async (kbnClient: KbnClient, integrationN } = await kbnClient.request<{ item: PackagePolicy }>({ method: 'GET', path: `/api/fleet/epm/packages/${integrationName}`, - headers: DEFAULT_HEADERS, + headers: { + ...DEFAULT_HEADERS, + 'elastic-api-version': API_VERSIONS.public.v1, + }, }); return item; @@ -46,6 +49,9 @@ export const createAgentPolicy = async ( } = await kbnClient.request({ method: 'POST', path: `/api/fleet/agent_policies?sys_monitoring=true`, + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, body: { name: agentPolicyName, description: '', @@ -62,6 +68,9 @@ export const createAgentPolicy = async ( log.info('Getting agent enrollment key'); const { data: apiKeys } = await kbnClient.request({ method: 'GET', + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, path: '/api/fleet/enrollment_api_keys', }); @@ -79,6 +88,9 @@ export const addIntegrationToAgentPolicy = async ( return kbnClient.request({ method: 'POST', path: '/api/fleet/package_policies', + headers: { + 'elastic-api-version': API_VERSIONS.public.v1, + }, body: { policy_id: agentPolicyId, package: { diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts index 4da679b6839ac..187df59c8ea53 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts @@ -28,13 +28,15 @@ export default function ({ getService }: FtrProviderContext) { function getMetrics( reset: boolean = false, - callback: (metrics: NodeMetrics) => boolean + callback?: (metrics: NodeMetrics) => boolean ): Promise { return retry.try(async () => { const metrics = await getMetricsRequest(reset); - if (metrics.metrics && callback(metrics)) { - return metrics; + if (metrics.metrics) { + if ((callback && callback(metrics)) || !callback) { + return metrics; + } } await delay(500); @@ -142,7 +144,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - describe('task run test', () => { + describe('task run', () => { let ruleId: string | null = null; before(async () => { // create a rule that fires actions @@ -185,12 +187,13 @@ export default function ({ getService }: FtrProviderContext) { await request.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo').expect(204); }); - it('should increment task run success/total counters', async () => { + it('should increment task run success/not_timed_out/total counters', async () => { const initialMetrics = ( await getMetrics( false, (metrics) => metrics?.metrics?.task_run?.value.by_type.alerting?.total === 1 && + metrics?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === 1 && metrics?.metrics?.task_run?.value.by_type.alerting?.success === 1 ) ).metrics; @@ -206,12 +209,20 @@ export default function ({ getService }: FtrProviderContext) { .send({ task: { id: ruleId } }) .expect(200); - await getMetrics( - false, - (metrics) => - metrics?.metrics?.task_run?.value.by_type.alerting?.total === i + 2 && - metrics?.metrics?.task_run?.value.by_type.alerting?.success === i + 2 - ); + const metrics = ( + await getMetrics( + false, + (m) => + m?.metrics?.task_run?.value.by_type.alerting?.total === i + 2 && + m?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === i + 2 && + m?.metrics?.task_run?.value.by_type.alerting?.success === i + 2 + ) + ).metrics; + + // check that delay histogram exists + expect(metrics?.task_run?.value?.overall?.delay).not.to.be(null); + expect(Array.isArray(metrics?.task_run?.value?.overall?.delay.counts)).to.be(true); + expect(Array.isArray(metrics?.task_run?.value?.overall?.delay.values)).to.be(true); } // counter should reset on its own @@ -219,9 +230,23 @@ export default function ({ getService }: FtrProviderContext) { false, (metrics) => metrics?.metrics?.task_run?.value.by_type.alerting?.total === 0 && + metrics?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === 0 && metrics?.metrics?.task_run?.value.by_type.alerting?.success === 0 ); }); }); + + describe('task overdue', () => { + it('histograms should exist', async () => { + const metrics = (await getMetrics(false)).metrics; + expect(metrics).not.to.be(null); + expect(metrics?.task_overdue).not.to.be(null); + expect(metrics?.task_overdue?.value).not.to.be(null); + expect(metrics?.task_overdue?.value.overall).not.to.be(null); + expect(metrics?.task_overdue?.value.overall.overdue_by).not.to.be(null); + expect(Array.isArray(metrics?.task_overdue?.value.overall.overdue_by.counts)).to.be(true); + expect(Array.isArray(metrics?.task_overdue?.value.overall.overdue_by.values)).to.be(true); + }); + }); }); } diff --git a/x-pack/test/profiling_api_integration/tests/__snapshots__/functions.spec.snap b/x-pack/test/profiling_api_integration/tests/__snapshots__/functions.spec.snap new file mode 100644 index 0000000000000..f5c9cb456cf0d --- /dev/null +++ b/x-pack/test/profiling_api_integration/tests/__snapshots__/functions.spec.snap @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Profiling API tests functions.spec.ts cloud Loading profiling data Functions api returns correct result 1`] = ` +Object { + "SamplingRate": 1, + "TopN": Array [ + Object { + "CountExclusive": 152, + "CountInclusive": 152, + "Frame": Object { + "AddressOrLine": 10967732, + "CommitHash": "", + "ExeFileName": "6tVKI4mSYDEJ-ABAIpYXcg", + "FileID": "6tVKI4mSYDEJ-ABAIpYXcg", + "FrameID": "6tVKI4mSYDEJ-ABAIpYXcgAAAAAAp1q0", + "FrameType": 4, + "FunctionName": "", + "FunctionOffset": 0, + "FunctionSourceLine": 0, + "Inline": false, + "SamplingRate": 1, + "SourceCodeURL": "", + "SourceFilename": "", + "SourceID": "", + "SourceLine": 0, + "SourcePackageHash": "", + "SourcePackageURL": "", + }, + "Id": "empty;6tVKI4mSYDEJ-ABAIpYXcg;10967732", + "Rank": 1, + }, + Object { + "CountExclusive": 106, + "CountInclusive": 106, + "Frame": Object { + "AddressOrLine": -1484822518, + "CommitHash": "", + "ExeFileName": "", + "FileID": "AAAAAAAAV4sAAAAAAAAAHQ", + "FrameID": "AAAAAAAAV4sAAAAAAAAAHQuTP52nf2gK", + "FrameType": 5, + "FunctionName": "", + "FunctionOffset": 0, + "FunctionSourceLine": 0, + "Inline": false, + "SamplingRate": 1, + "SourceCodeURL": "", + "SourceFilename": "", + "SourceID": "", + "SourceLine": 0, + "SourcePackageHash": "", + "SourcePackageURL": "", + }, + "Id": "empty;AAAAAAAAV4sAAAAAAAAAHQ;-1484822518", + "Rank": 2, + }, + Object { + "CountExclusive": 50, + "CountInclusive": 50, + "Frame": Object { + "AddressOrLine": 42521194, + "CommitHash": "", + "ExeFileName": "XT4fd_WKeR1cE-hlLelCQA", + "FileID": "XT4fd_WKeR1cE-hlLelCQA", + "FrameID": "XT4fd_WKeR1cE-hlLelCQAAAAAACiNJq", + "FrameType": 3, + "FunctionName": "", + "FunctionOffset": 0, + "FunctionSourceLine": 0, + "Inline": false, + "SamplingRate": 1, + "SourceCodeURL": "", + "SourceFilename": "", + "SourceID": "", + "SourceLine": 0, + "SourcePackageHash": "", + "SourcePackageURL": "", + }, + "Id": "empty;XT4fd_WKeR1cE-hlLelCQA;42521194", + "Rank": 3, + }, + Object { + "CountExclusive": 49, + "CountInclusive": 49, + "Frame": Object { + "AddressOrLine": 838088, + "CommitHash": "", + "ExeFileName": "6tVKI4mSYDEJ-ABAIpYXcg", + "FileID": "6tVKI4mSYDEJ-ABAIpYXcg", + "FrameID": "6tVKI4mSYDEJ-ABAIpYXcgAAAAAADMnI", + "FrameType": 4, + "FunctionName": "", + "FunctionOffset": 0, + "FunctionSourceLine": 0, + "Inline": false, + "SamplingRate": 1, + "SourceCodeURL": "", + "SourceFilename": "", + "SourceID": "", + "SourceLine": 0, + "SourcePackageHash": "", + "SourcePackageURL": "", + }, + "Id": "empty;6tVKI4mSYDEJ-ABAIpYXcg;838088", + "Rank": 4, + }, + Object { + "CountExclusive": 40, + "CountInclusive": 42, + "Frame": Object { + "AddressOrLine": 1671872298, + "CommitHash": "", + "ExeFileName": "", + "FileID": "AAAAAAAAV4sAAAAAAAAAHg", + "FrameID": "AAAAAAAAV4sAAAAAAAAAHezOBKFjpr8q", + "FrameType": 5, + "FunctionName": "", + "FunctionOffset": 0, + "FunctionSourceLine": 0, + "Inline": false, + "SamplingRate": 1, + "SourceCodeURL": "", + "SourceFilename": "", + "SourceID": "", + "SourceLine": 0, + "SourcePackageHash": "", + "SourcePackageURL": "", + }, + "Id": "empty;AAAAAAAAV4sAAAAAAAAAHg;1671872298", + "Rank": 5, + }, + ], + "TotalCount": 3599, + "selfCPU": 397, + "totalCPU": 399, +} +`; diff --git a/x-pack/test/profiling_api_integration/tests/functions.spec.ts b/x-pack/test/profiling_api_integration/tests/functions.spec.ts new file mode 100644 index 0000000000000..d2b9d4d9237e0 --- /dev/null +++ b/x-pack/test/profiling_api_integration/tests/functions.spec.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getRoutePaths } from '@kbn/profiling-plugin/common'; +import { TopNFunctions } from '@kbn/profiling-utils'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../common/ftr_provider_context'; + +const profilingRoutePaths = getRoutePaths(); + +export default function featureControlsTests({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const profilingApiClient = getService('profilingApiClient'); + + const start = new Date('2023-03-17T01:00:00.000Z').getTime(); + const end = new Date('2023-03-17T01:05:00.000Z').getTime(); + + registry.when('Functions api', { config: 'cloud' }, () => { + let functions: TopNFunctions; + before(async () => { + const response = await profilingApiClient.adminUser({ + endpoint: `GET ${profilingRoutePaths.TopNFunctions}`, + params: { + query: { + timeFrom: start, + timeTo: end, + kuery: '', + startIndex: 0, + endIndex: 5, + }, + }, + }); + functions = response.body as TopNFunctions; + }); + it(`returns correct result`, async () => { + expect(functions.TopN.length).to.equal(5); + expect(functions.TotalCount).to.equal(3599); + expect(functions.selfCPU).to.equal(397); + expect(functions.totalCPU).to.equal(399); + expectSnapshot(functions).toMatch(); + }); + }); +} diff --git a/x-pack/test/reporting_functional/services/scenarios.ts b/x-pack/test/reporting_functional/services/scenarios.ts index f73bd7801b87a..d1123cf2eab05 100644 --- a/x-pack/test/reporting_functional/services/scenarios.ts +++ b/x-pack/test/reporting_functional/services/scenarios.ts @@ -70,7 +70,7 @@ export function createScenarios( const openCanvasWorkpad = async (title: string) => { log.debug(`Opening saved canvas workpad: ${title}`); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.loadFirstWorkpad(title); }; diff --git a/x-pack/test/scalability/apis/api.status.json b/x-pack/test/scalability/apis/api.status.json new file mode 100644 index 0000000000000..396d9d0645fc3 --- /dev/null +++ b/x-pack/test/scalability/apis/api.status.json @@ -0,0 +1,48 @@ +{ + "journeyName": "POST /api/status", + "scalabilitySetup": { + "responseTimeThreshold": { + "threshold1": 1000, + "threshold2": 3000, + "threshold3": 5000 + }, + "warmup": [ + { + "action": "constantUsersPerSec", + "userCount": 10, + "duration": "30s" + } + ], + "test": [ + { + "action": "rampUsersPerSec", + "minUsersCount": 10, + "maxUsersCount": 700, + "duration": "138s" + } + ], + "maxDuration": "6m" + }, + "testData": { + "esArchives": [], + "kbnArchives": [] + }, + "streams": [ + { + "requests": [ + { + "http": { + "method": "GET", + "path": "/api/status", + "headers": { + "Cookie": "", + "Accept-Encoding": "gzip, deflate, br", + "Content-Type": "application/json; charset=utf-8" + }, + "statusCode": 200 + } + } + ] + } + ] +} diff --git a/x-pack/test/scalability/apis/api.status.no_auth.json b/x-pack/test/scalability/apis/api.status.no_auth.json new file mode 100644 index 0000000000000..2fe293f072a25 --- /dev/null +++ b/x-pack/test/scalability/apis/api.status.no_auth.json @@ -0,0 +1,47 @@ +{ + "journeyName": "POST /api/status", + "scalabilitySetup": { + "responseTimeThreshold": { + "threshold1": 1000, + "threshold2": 3000, + "threshold3": 5000 + }, + "warmup": [ + { + "action": "constantUsersPerSec", + "userCount": 10, + "duration": "30s" + } + ], + "test": [ + { + "action": "rampUsersPerSec", + "minUsersCount": 10, + "maxUsersCount": 1210, + "duration": "80s" + } + ], + "maxDuration": "4m" + }, + "testData": { + "esArchives": [], + "kbnArchives": [] + }, + "streams": [ + { + "requests": [ + { + "http": { + "method": "GET", + "path": "/api/status", + "headers": { + "Accept-Encoding": "gzip", + "Content-Type": "application/json; charset=utf-8" + }, + "statusCode": 200 + } + } + ] + } + ] +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts index f6f41a61f1bb2..3a4cae246d46f 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts @@ -6,6 +6,7 @@ */ import { FtrProviderContext } from '../../../ftr_provider_context'; +import { esQueryRuleName } from '.'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); @@ -106,7 +107,63 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 1400, 1500 ); + // Create an email connector action + await testSubjects.click('.email-alerting-ActionTypeSelectOption'); + await testSubjects.scrollIntoView('addAlertActionButton'); + await commonScreenshots.takeScreenshot( + 'es-query-rule-action-query-matched', + screenshotDirectories, + 1400, + 1024 + ); + await testSubjects.click('messageAddVariableButton'); + await commonScreenshots.takeScreenshot( + 'es-query-rule-action-variables', + screenshotDirectories, + 1400, + 1024 + ); + await browser.pressKeys(browser.keys.ESCAPE); await testSubjects.click('cancelSaveRuleButton'); }); + + it('example elasticsearch query rule conditions and actions', async () => { + await pageObjects.common.navigateToApp('triggersActions'); + await pageObjects.header.waitUntilLoadingHasFinished(); + // Edit the rule that was created as part of startup + await testSubjects.setValue('ruleSearchField', esQueryRuleName); + await browser.pressKeys(browser.keys.ENTER); + const actionPanel = await testSubjects.find('collapsedItemActions'); + await actionPanel.click(); + const editRuleMenu = await testSubjects.find('editRule'); + await editRuleMenu.click(); + await pageObjects.header.waitUntilLoadingHasFinished(); + await commonScreenshots.takeScreenshot( + 'es-query-rule-conditions', + screenshotDirectories, + 1400, + 1700 + ); + /* Reposition so that the details are visible for the first action */ + await testSubjects.scrollIntoView('alertActionAccordion-0'); + await commonScreenshots.takeScreenshot( + 'es-query-rule-action-summary', + screenshotDirectories, + 1400, + 1024 + ); + /* Reposition so that the details are visible for the second action */ + await testSubjects.scrollIntoView('alertActionAccordion-1'); + await commonScreenshots.takeScreenshot( + 'es-query-rule-recovery-action', + screenshotDirectories, + 1400, + 1024 + ); + const cancelEditButton = await testSubjects.find('cancelSaveEditedRuleButton'); + await cancelEditButton.click(); + const confirmCancelButton = await testSubjects.find('confirmModalConfirmButton'); + await confirmCancelButton.click(); + }); }); } diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts index e4a2877173834..43596aa81d675 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts @@ -8,18 +8,34 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export const indexThresholdRuleName = 'kibana sites - low bytes'; -export const metricThresholdRuleName = 'network metric packets'; +export const esQueryRuleName = 'sample logs query rule'; export default function ({ loadTestFile, getService }: FtrProviderContext) { const browser = getService('browser'); const actions = getService('actions'); const rules = getService('rules'); + const emailConnectorName = 'Email connector 1'; + const validQueryJson = JSON.stringify({ + query: { + bool: { + filter: [ + { + term: { + 'host.keyword': 'www.elastic.co', + }, + }, + ], + }, + }, + }); describe('stack alerting', function () { let itRuleId: string; - let mtRuleId: string; + let esRuleId: string; let serverLogConnectorId: string; + let emailConnectorId: string; before(async () => { + // Create server log connector await browser.setWindowSize(1920, 1080); ({ id: serverLogConnectorId } = await actions.api.createConnector({ name: 'my-server-log-connector', @@ -27,6 +43,22 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { secrets: {}, connectorTypeId: '.server-log', })); + // Create email connector + ({ id: emailConnectorId } = await actions.api.createConnector({ + name: emailConnectorName, + config: { + service: 'other', + from: 'bob@example.com', + host: 'some.non.existent.com', + port: 25, + }, + secrets: { + user: 'bob', + password: 'supersecret', + }, + connectorTypeId: '.email', + })); + // Create index threshold rule ({ id: itRuleId } = await rules.api.createRule({ consumer: 'alerts', name: indexThresholdRuleName, @@ -57,35 +89,48 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { }, ], })); - ({ id: mtRuleId } = await rules.api.createRule({ - consumer: 'infrastructure', - name: metricThresholdRuleName, - notifyWhen: 'onActionGroupChange', + // Create Elasticsearch query rule + ({ id: esRuleId } = await rules.api.createRule({ + consumer: 'alerts', + name: esQueryRuleName, params: { - criteria: [ - { - aggType: 'max', - comparator: '>', - threshold: [0], - timeSize: 3, - timeUnit: 's', - metric: 'network.packets', - }, - ], - sourceId: 'default', - alertOnNoData: false, - alertOnGroupDisappear: false, - groupBy: ['network.name'], + index: ['kibana_sample_data_logs'], + timeField: '@timestamp', + timeWindowSize: 1, + timeWindowUnit: 'd', + thresholdComparator: '>', + threshold: [100], + size: 100, + esQuery: validQueryJson, }, - ruleTypeId: 'metrics.alert.threshold', - schedule: { interval: '1m' }, + ruleTypeId: '.es-query', + schedule: { interval: '1d' }, actions: [ { - group: 'metrics.threshold.fired', + group: 'query matched', + id: emailConnectorId, + frequency: { + throttle: '2d', + summary: true, + notify_when: 'onThrottleInterval', + }, + params: { + to: ['test@example.com'], + subject: 'Alert summary', + message: + 'The system has detected {{alerts.new.count}} new, {{alerts.ongoing.count}} ongoing, and {{alerts.recovered.count}} recovered alerts.', + }, + }, + { + group: 'recovered', id: serverLogConnectorId, + frequency: { + summary: false, + notify_when: 'onActionGroupChange', + }, params: { level: 'info', - message: 'Test Metric Threshold rule', + message: '{{alert.id}} has recovered.', }, }, ], @@ -94,7 +139,7 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { after(async () => { await rules.api.deleteRule(itRuleId); - await rules.api.deleteRule(mtRuleId); + await rules.api.deleteRule(esRuleId); await rules.api.deleteAllRules(); await actions.api.deleteConnector(serverLogConnectorId); await actions.api.deleteAllConnectors(); @@ -103,7 +148,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./es_query_rule')); loadTestFile(require.resolve('./index_threshold_rule')); loadTestFile(require.resolve('./list_view')); - loadTestFile(require.resolve('./metrics_threshold_rule')); loadTestFile(require.resolve('./tracking_containment_rule')); }); } diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/metrics_threshold_rule.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/metrics_threshold_rule.ts deleted file mode 100644 index e091ab4da27ed..0000000000000 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/metrics_threshold_rule.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { metricThresholdRuleName } from '.'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const actions = getService('actions'); - const browser = getService('browser'); - const commonScreenshots = getService('commonScreenshots'); - const testSubjects = getService('testSubjects'); - const pageObjects = getPageObjects(['common', 'header']); - const screenshotDirectories = ['response_ops_docs', 'stack_alerting']; - const emailConnectorName = 'Email connector 1'; - - describe('metric threshold rule', function () { - let emailConnectorId: string; - before(async () => { - ({ id: emailConnectorId } = await actions.api.createConnector({ - name: emailConnectorName, - config: { - service: 'other', - from: 'bob@example.com', - host: 'some.non.existent.com', - port: 25, - }, - secrets: { - user: 'bob', - password: 'supersecret', - }, - connectorTypeId: '.email', - })); - }); - - after(async () => { - await actions.api.deleteConnector(emailConnectorId); - }); - - it('example metric threshold rule conditions and actions', async () => { - await pageObjects.common.navigateToApp('triggersActions'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.setValue('ruleSearchField', metricThresholdRuleName); - await browser.pressKeys(browser.keys.ENTER); - const actionPanel = await testSubjects.find('collapsedItemActions'); - await actionPanel.click(); - const editRuleMenu = await testSubjects.find('editRule'); - await editRuleMenu.click(); - const expandExpression = await testSubjects.find('expandRow'); - await expandExpression.click(); - await pageObjects.header.waitUntilLoadingHasFinished(); - await commonScreenshots.takeScreenshot( - 'rule-flyout-rule-conditions', - screenshotDirectories, - 1400, - 1500 - ); - - const serverLogAction = await testSubjects.find('alertActionAccordion-0'); - const removeConnectorButton = await serverLogAction.findByCssSelector( - '[aria-label="Delete"]' - ); - await removeConnectorButton.click(); - - await testSubjects.click('.email-alerting-ActionTypeSelectOption'); - await testSubjects.scrollIntoView('addAlertActionButton'); - await commonScreenshots.takeScreenshot( - 'rule-flyout-action-details', - screenshotDirectories, - 1400, - 1024 - ); - await testSubjects.scrollIntoView('addAlertActionButton'); - await testSubjects.click('messageAddVariableButton'); - await commonScreenshots.takeScreenshot( - 'rule-flyout-action-variables', - screenshotDirectories, - 1400, - 1024 - ); - - const cancelEditButton = await testSubjects.find('cancelSaveEditedRuleButton'); - await cancelEditButton.click(); - const confirmCancelButton = await testSubjects.find('confirmModalConfirmButton'); - await confirmCancelButton.click(); - }); - }); -} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/connector_types.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/connector_types.ts deleted file mode 100644 index f4907a1df0969..0000000000000 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/connector_types.ts +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const commonScreenshots = getService('commonScreenshots'); - const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; - const pageObjects = getPageObjects(['common', 'header']); - const actions = getService('actions'); - const browser = getService('browser'); - const comboBox = getService('comboBox'); - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const testIndex = `test-index`; - const indexDocument = - `{\n` + - `"rule_id": "{{rule.id}}",\n` + - `"rule_name": "{{rule.name}}",\n` + - `"alert_id": "{{alert.id}}",\n` + - `"context_message": "{{context.message}}"\n`; - const webhookJson = - `{\n` + - `"short_description": "{{context.rule.name}}",\n` + - `"description": "{{context.rule.description}}"`; - const emailConnectorName = 'my-email-connector'; - - describe('connector types', function () { - let emailConnectorId: string; - before(async () => { - ({ id: emailConnectorId } = await actions.api.createConnector({ - name: emailConnectorName, - config: { - service: 'other', - from: 'bob@example.com', - host: 'some.non.existent.com', - port: 25, - }, - secrets: { - user: 'bob', - password: 'supersecret', - }, - connectorTypeId: '.email', - })); - }); - - beforeEach(async () => { - await pageObjects.common.navigateToApp('connectors'); - await pageObjects.header.waitUntilLoadingHasFinished(); - }); - - after(async () => { - await actions.api.deleteConnector(emailConnectorId); - }); - - it('server log connector screenshots', async () => { - await pageObjects.common.navigateToApp('connectors'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await actions.common.openNewConnectorForm('server-log'); - await testSubjects.setValue('nameInput', 'Server log test connector'); - await commonScreenshots.takeScreenshot('serverlog-connector', screenshotDirectories); - const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn'); - await saveTestButton.click(); - await commonScreenshots.takeScreenshot('serverlog-params-test', screenshotDirectories); - const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); - await flyOutCancelButton.click(); - }); - - it('index connector screenshots', async () => { - await pageObjects.common.navigateToApp('connectors'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await actions.common.openNewConnectorForm('index'); - await testSubjects.setValue('nameInput', 'Index test connector'); - await comboBox.set('connectorIndexesComboBox', testIndex); - const timeFieldToggle = await testSubjects.find('hasTimeFieldCheckbox'); - await timeFieldToggle.click(); - await commonScreenshots.takeScreenshot('index-connector', screenshotDirectories); - const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn'); - await saveTestButton.click(); - await testSubjects.setValue('actionJsonEditor', indexDocument); - await commonScreenshots.takeScreenshot('index-params-test', screenshotDirectories); - const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); - await flyOutCancelButton.click(); - }); - - it('slack api connector screenshots', async () => { - await pageObjects.common.navigateToApp('connectors'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await actions.common.openNewConnectorForm('slack'); - await testSubjects.click('.slack_apiButton'); - await testSubjects.setValue('nameInput', 'Slack api test connector'); - await testSubjects.setValue('secrets.token-input', 'xoxb-XXXX-XXXX-XXXX'); - await commonScreenshots.takeScreenshot('slack-api-connector', screenshotDirectories); - await testSubjects.click('create-connector-flyout-save-test-btn'); - await testSubjects.click('toastCloseButton'); - await pageObjects.common.closeToast(); - await commonScreenshots.takeScreenshot('slack-api-params', screenshotDirectories); - await testSubjects.click('euiFlyoutCloseButton'); - }); - - it('slack webhook connector screenshots', async () => { - await pageObjects.common.navigateToApp('connectors'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await actions.common.openNewConnectorForm('slack'); - await testSubjects.setValue('nameInput', 'Slack webhook test connector'); - await testSubjects.setValue( - 'slackWebhookUrlInput', - 'https://hooks.slack.com/services/abcd/ljklmnopqrstuvwxz' - ); - await commonScreenshots.takeScreenshot('slack-webhook-connector', screenshotDirectories); - await testSubjects.click('create-connector-flyout-save-test-btn'); - await testSubjects.click('toastCloseButton'); - await commonScreenshots.takeScreenshot('slack-webhook-params', screenshotDirectories); - await testSubjects.click('euiFlyoutCloseButton'); - }); - - it('email connector screenshots', async () => { - await pageObjects.common.navigateToApp('connectors'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await actions.common.openNewConnectorForm('email'); - await testSubjects.setValue('nameInput', 'Gmail connector'); - await testSubjects.setValue('emailFromInput', 'test@gmail.com'); - await testSubjects.setValue('emailServiceSelectInput', 'gmail'); - await commonScreenshots.takeScreenshot('email-connector', screenshotDirectories); - const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); - await flyOutCancelButton.click(); - }); - - it('test email connector screenshots', async () => { - const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch'); - await searchBox.click(); - await searchBox.clearValue(); - await searchBox.type('my actionTypeId:(.email)'); - await searchBox.pressKeys(browser.keys.ENTER); - const connectorList = await testSubjects.find('actionsTable'); - const emailConnector = await connectorList.findByCssSelector( - `[title="${emailConnectorName}"]` - ); - await emailConnector.click(); - const testButton = await testSubjects.find('testConnectorTab'); - await testButton.click(); - await testSubjects.setValue('comboBoxSearchInput', 'elastic@gmail.com'); - await testSubjects.setValue('subjectInput', 'Test subject'); - await testSubjects.setValue('messageTextArea', 'Enter message text'); - /* timing issue sometimes happens with the combobox so we just try to set the subjectInput again */ - await testSubjects.setValue('subjectInput', 'Test subject'); - await commonScreenshots.takeScreenshot( - 'email-params-test', - screenshotDirectories, - 1400, - 1024 - ); - }); - - it('webhook connector screenshots', async () => { - await pageObjects.common.navigateToApp('connectors'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await actions.common.openNewConnectorForm('webhook'); - await testSubjects.setValue('nameInput', 'Webhook test connector'); - await testSubjects.setValue('webhookUrlText', 'https://example.com'); - await testSubjects.setValue('webhookUserInput', 'testuser'); - await testSubjects.setValue('webhookPasswordInput', 'password'); - await commonScreenshots.takeScreenshot('webhook-connector', screenshotDirectories); - const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn'); - await saveTestButton.click(); - await testSubjects.setValue('actionJsonEditor', webhookJson); - await commonScreenshots.takeScreenshot('webhook-params-test', screenshotDirectories); - await testSubjects.click('euiFlyoutCloseButton'); - }); - }); -} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/email_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/email_connector.ts new file mode 100644 index 0000000000000..6e3339dbeacef --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/email_connector.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const browser = getService('browser'); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const emailConnectorName = 'my-email-connector'; + + describe('email connector', function () { + let emailConnectorId: string; + before(async () => { + ({ id: emailConnectorId } = await actions.api.createConnector({ + name: emailConnectorName, + config: { + service: 'other', + from: 'bob@example.com', + host: 'some.non.existent.com', + port: 25, + }, + secrets: { + user: 'bob', + password: 'supersecret', + }, + connectorTypeId: '.email', + })); + }); + + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + after(async () => { + await actions.api.deleteConnector(emailConnectorId); + }); + + it('email connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('email'); + await testSubjects.setValue('nameInput', 'Gmail connector'); + await testSubjects.setValue('emailFromInput', 'test@gmail.com'); + await testSubjects.setValue('emailServiceSelectInput', 'gmail'); + await commonScreenshots.takeScreenshot('email-connector', screenshotDirectories); + const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); + await flyOutCancelButton.click(); + }); + + it('test email connector screenshots', async () => { + const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch'); + await searchBox.click(); + await searchBox.clearValue(); + await searchBox.type('my actionTypeId:(.email)'); + await searchBox.pressKeys(browser.keys.ENTER); + const connectorList = await testSubjects.find('actionsTable'); + const emailConnector = await connectorList.findByCssSelector( + `[title="${emailConnectorName}"]` + ); + await emailConnector.click(); + const testButton = await testSubjects.find('testConnectorTab'); + await testButton.click(); + await testSubjects.setValue('comboBoxSearchInput', 'elastic@gmail.com'); + await testSubjects.setValue('subjectInput', 'Test subject'); + await testSubjects.setValue('messageTextArea', 'Enter message text'); + /* timing issue sometimes happens with the combobox so we just try to set the subjectInput again */ + await testSubjects.setValue('subjectInput', 'Test subject'); + await commonScreenshots.takeScreenshot( + 'email-params-test', + screenshotDirectories, + 1400, + 1024 + ); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/generative_ai_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/generative_ai_connector.ts new file mode 100644 index 0000000000000..69fa2448a7818 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/generative_ai_connector.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + const actionBody = + `{\n` + + `"model": "gpt-3.5-turbo",\n` + + `"messages": [{\n` + + `"role": "user",\n` + + `"content": "You are a cyber security analyst using Elastic Security. I would like you to evaluate the event below and format your output neatly in markdown syntax. Add your description, an accuracy rating, and a threat rating."\n` + + `}]`; + + describe('generative ai connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('generative ai connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('gen-ai'); + await testSubjects.setValue('nameInput', 'OpenAI test connector'); + await testSubjects.setValue('secrets.apiKey-input', 'testkey'); + await commonScreenshots.takeScreenshot('gen-ai-connector', screenshotDirectories, 1920, 1200); + await testSubjects.click('create-connector-flyout-save-test-btn'); + await testSubjects.click('toastCloseButton'); + const editor = await testSubjects.find('kibanaCodeEditor'); + await editor.clearValue(); + await testSubjects.setValue('kibanaCodeEditor', actionBody, { + clearWithKeyboard: true, + }); + await commonScreenshots.takeScreenshot('gen-ai-params-test', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts index 7e995c1290b74..8a75474264676 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts @@ -54,6 +54,15 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { }); loadTestFile(require.resolve('./connectors')); - loadTestFile(require.resolve('./connector_types')); + loadTestFile(require.resolve('./email_connector')); + loadTestFile(require.resolve('./generative_ai_connector')); + loadTestFile(require.resolve('./index_connector')); + loadTestFile(require.resolve('./jira_connector')); + loadTestFile(require.resolve('./opsgenie_connector')); + loadTestFile(require.resolve('./pagerduty_connector')); + loadTestFile(require.resolve('./server_log_connector')); + loadTestFile(require.resolve('./slack_connector')); + loadTestFile(require.resolve('./webhook_connector')); + loadTestFile(require.resolve('./xmatters_connector')); }); } diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index_connector.ts new file mode 100644 index 0000000000000..838ca43b19724 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index_connector.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const comboBox = getService('comboBox'); + const testSubjects = getService('testSubjects'); + const testIndex = `test-index`; + const indexDocument = + `{\n` + + `"rule_id": "{{rule.id}}",\n` + + `"rule_name": "{{rule.name}}",\n` + + `"alert_id": "{{alert.id}}",\n` + + `"context_message": "{{context.message}}"\n`; + + describe('index connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('index connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('index'); + await testSubjects.setValue('nameInput', 'Index test connector'); + await comboBox.set('connectorIndexesComboBox', testIndex); + const timeFieldToggle = await testSubjects.find('hasTimeFieldCheckbox'); + await timeFieldToggle.click(); + await commonScreenshots.takeScreenshot('index-connector', screenshotDirectories); + const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn'); + await saveTestButton.click(); + await testSubjects.setValue('actionJsonEditor', indexDocument); + await commonScreenshots.takeScreenshot('index-params-test', screenshotDirectories); + const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); + await flyOutCancelButton.click(); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/jira_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/jira_connector.ts new file mode 100644 index 0000000000000..149ba65183ffc --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/jira_connector.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + + describe('jira connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('index connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('jira'); + await testSubjects.setValue('nameInput', 'Jira test connector'); + await testSubjects.setValue('config.apiUrl-input', 'https://elastic.atlassian.net'); + await testSubjects.setValue('config.projectKey-input', 'ES'); + await testSubjects.setValue('secrets.email-input', 'testuser@example.com'); + await testSubjects.setValue('secrets.apiToken-input', 'test'); + await commonScreenshots.takeScreenshot('jira-connector', screenshotDirectories); + await testSubjects.click('create-connector-flyout-save-test-btn'); + await testSubjects.click('toastCloseButton'); + await commonScreenshots.takeScreenshot('jira-params-test', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/opsgenie_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/opsgenie_connector.ts new file mode 100644 index 0000000000000..432ececf2ba62 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/opsgenie_connector.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + + describe('opsgenie connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('opsgenie connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('opsgenie'); + await testSubjects.setValue('nameInput', 'Opsgenie test connector'); + await testSubjects.setValue('secrets.apiKey-input', 'testkey'); + await commonScreenshots.takeScreenshot('opsgenie-connector', screenshotDirectories); + await testSubjects.click('create-connector-flyout-save-test-btn'); + await testSubjects.click('toastCloseButton'); + await commonScreenshots.takeScreenshot('opsgenie-create-alert-test', screenshotDirectories); + await testSubjects.click('opsgenie-subActionSelect-close-alert'); + await testSubjects.click('opsgenie-display-more-options'); + await commonScreenshots.takeScreenshot('opsgenie-close-alert-test', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/pagerduty_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/pagerduty_connector.ts new file mode 100644 index 0000000000000..ddaa7291acaa8 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/pagerduty_connector.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + + describe('pagerduty connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + it('pagerduty connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('pagerduty'); + await testSubjects.setValue('nameInput', 'PagerDuty test connector'); + await testSubjects.setValue('pagerdutyApiUrlInput', 'https://dev-test.pagerduty.com/'); + await testSubjects.setValue('pagerdutyRoutingKeyInput', 'testkey'); + await commonScreenshots.takeScreenshot('pagerduty-connector', screenshotDirectories); + await testSubjects.click('create-connector-flyout-save-test-btn'); + await testSubjects.click('toastCloseButton'); + await testSubjects.setValue('eventActionSelect', 'trigger'); + await commonScreenshots.takeScreenshot('pagerduty-trigger-test', screenshotDirectories); + await testSubjects.setValue('eventActionSelect', 'resolve'); + await commonScreenshots.takeScreenshot('pagerduty-resolve-test', screenshotDirectories); + await testSubjects.setValue('eventActionSelect', 'acknowledge'); + await commonScreenshots.takeScreenshot('pagerduty-acknowledge-test', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/server_log_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/server_log_connector.ts new file mode 100644 index 0000000000000..4d3cecb764751 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/server_log_connector.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + + describe('server log connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('server log connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('server-log'); + await testSubjects.setValue('nameInput', 'Server log test connector'); + await commonScreenshots.takeScreenshot('serverlog-connector', screenshotDirectories); + const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn'); + await saveTestButton.click(); + await commonScreenshots.takeScreenshot('serverlog-params-test', screenshotDirectories); + const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); + await flyOutCancelButton.click(); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/slack_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/slack_connector.ts new file mode 100644 index 0000000000000..a7f96bbc2eb9e --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/slack_connector.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + + describe('slack connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('slack api connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('slack'); + await testSubjects.click('.slack_apiButton'); + await testSubjects.setValue('nameInput', 'Slack api test connector'); + await testSubjects.setValue('secrets.token-input', 'xoxb-XXXX-XXXX-XXXX'); + await commonScreenshots.takeScreenshot('slack-api-connector', screenshotDirectories); + await testSubjects.click('create-connector-flyout-save-test-btn'); + await testSubjects.click('toastCloseButton'); + await pageObjects.common.closeToast(); + await commonScreenshots.takeScreenshot('slack-api-params', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + + it('slack webhook connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('slack'); + await testSubjects.setValue('nameInput', 'Slack webhook test connector'); + await testSubjects.setValue( + 'slackWebhookUrlInput', + 'https://hooks.slack.com/services/abcd/ljklmnopqrstuvwxz' + ); + await commonScreenshots.takeScreenshot('slack-webhook-connector', screenshotDirectories); + await testSubjects.click('create-connector-flyout-save-test-btn'); + await testSubjects.click('toastCloseButton'); + await commonScreenshots.takeScreenshot('slack-webhook-params', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/webhook_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/webhook_connector.ts new file mode 100644 index 0000000000000..def2a9b0e1dff --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/webhook_connector.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + const webhookJson = + `{\n` + + `"short_description": "{{context.rule.name}}",\n` + + `"description": "{{context.rule.description}}"`; + + describe('webhook connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('webhook connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('webhook'); + await testSubjects.setValue('nameInput', 'Webhook test connector'); + await testSubjects.setValue('webhookUrlText', 'https://example.com'); + await testSubjects.setValue('webhookUserInput', 'testuser'); + await testSubjects.setValue('webhookPasswordInput', 'password'); + await commonScreenshots.takeScreenshot('webhook-connector', screenshotDirectories); + const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn'); + await saveTestButton.click(); + await testSubjects.setValue('actionJsonEditor', webhookJson); + await commonScreenshots.takeScreenshot('webhook-params-test', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/xmatters_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/xmatters_connector.ts new file mode 100644 index 0000000000000..80aafd27dc63b --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/xmatters_connector.ts @@ -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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + + describe('xmatters connector', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('generative ai connector screenshots', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('xmatters'); + await testSubjects.setValue('nameInput', 'xMatters test connector'); + await commonScreenshots.takeScreenshot('xmatters-connector-basic', screenshotDirectories); + const authentication = await testSubjects.find('button-group'); + // a radio button consists of a div tag that contains an input, a div, and a label + // we can't click the input directly, need to click the label + const label = await authentication.findByCssSelector('label[title="URL Authentication"]'); + await label.click(); + await commonScreenshots.takeScreenshot('xmatters-connector-url', screenshotDirectories); + await testSubjects.setValue('secrets.secretsUrl', 'https://example.com'); + await testSubjects.click('create-connector-flyout-save-test-btn'); + await testSubjects.click('toastCloseButton'); + await commonScreenshots.takeScreenshot('xmatters-params-test', screenshotDirectories); + await testSubjects.click('euiFlyoutCloseButton'); + }); + }); +} diff --git a/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts b/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts index ddbd07ef0c10e..9dc632931d301 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts @@ -35,7 +35,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'x-pack/test/functional/fixtures/kbn_archiver/discover/default' ); await kibanaServer.uiSettings.replace({ - 'discover:enableSql': true, + 'discover:enableESQL': true, }); await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -131,6 +131,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(searchesCountBeforeRestore).to.be(searchesCountAfterRestore); // no new searches started during restore }); + + it('should should clean the search session when navigating to ESQL mode, and reinitialize when navigating back', async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await searchSessions.exists()).to.be(true); + await PageObjects.discover.selectTextBaseLang(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await searchSessions.missingOrFail(); + await browser.goBack(); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await searchSessions.exists()).to.be(true); + }); }); async function getSearchSessionId(): Promise { diff --git a/x-pack/test/security_solution_cypress/cypress/README.md b/x-pack/test/security_solution_cypress/cypress/README.md index 21a6a8a9db493..aa749344201fa 100644 --- a/x-pack/test/security_solution_cypress/cypress/README.md +++ b/x-pack/test/security_solution_cypress/cypress/README.md @@ -40,7 +40,12 @@ of data for your test, [**Running the tests**](#running-the-tests) to know how t Please, before opening a PR with the new test, please make sure that the test fails. If you never see your test fail you don’t know if your test is actually testing the right thing, or testing anything at all. -Note that we use tags in order to select which tests we want to execute: @serverless, @ess and @brokenInServerless +Note that we use tags in order to select which tests we want to execute: + +- `@serverless` includes a test in the Serverless test suite. You need to explicitly add this tag to any test you want to run against a Serverless environment. +- `@ess` includes a test in the normal, non-Serverless test suite. You need to explicitly add this tag to any test you want to run against a non-Serverless environment. +- `@brokenInServerless` excludes a test from the Serverless test suite (even if it's tagged as `@serverless`). Indicates that a test should run in Serverless, but currently is broken. +- `@skipInServerless` excludes a test from the Serverless test suite (even if it's tagged as `@serverless`). Could indicate many things, e.g. "the test is flaky in Serverless", "the test is Flaky in any type of environemnt", "the test has been temporarily excluded, see the comment above why". Please, before opening a PR with a new test, make sure that the test fails. If you never see your test fail you don’t know if your test is actually testing the right thing, or testing anything at all. diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts index 5ce5af4e639f6..eafcd67682a68 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts @@ -18,7 +18,7 @@ export default defineCypressConfig({ env: { grepFilterSpecs: true, grepOmitFiltered: true, - grepTags: '@serverless --@brokenInServerless', + grepTags: '@serverless --@brokenInServerless --@skipInServerless', }, execTimeout: 150000, pageLoadTimeout: 150000, diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts index d0a89bb34a68a..d172248fb406a 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts @@ -23,7 +23,7 @@ export default defineCypressConfig({ numTestsKeptInMemory: 10, env: { grepFilterSpecs: true, - grepTags: '@serverless --@brokenInServerless', + grepTags: '@serverless --@brokenInServerless --@skipInServerless', }, e2e: { experimentalRunAllSpecs: true, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts index b9a514b8c2215..f78670bc14b4f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts @@ -25,6 +25,7 @@ import { GET_TIMELINE_HEADER } from '../../screens/timeline'; const alertRunTimeField = 'field.name.alert.page'; const timelineRuntimeField = 'field.name.timeline'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Create DataView runtime field', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts index 77ce5b56d30ae..77206d714c960 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts @@ -35,12 +35,14 @@ const rolesToCreate = [secReadCasesAll]; const siemDataViewTitle = 'Security Default Data View'; const dataViews = ['auditbeat-*,fakebeat-*', 'auditbeat-*,*beat*,siem-read*,.kibana*,fakebeat-*']; -describe('Sourcerer', () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Sourcerer', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { cy.task('esArchiverResetKibana'); dataViews.forEach((dataView: string) => postDataView(dataView)); }); + // TODO: https://github.com/elastic/kibana/issues/161539 describe('permissions', { tags: ['@ess', '@brokenInServerless'] }, () => { before(() => { createUsersAndRoles(usersToCreate, rolesToCreate); @@ -52,7 +54,9 @@ describe('Sourcerer', () => { }); }); - describe('Default scope', { tags: ['@ess', '@serverless'] }, () => { + // TODO: https://github.com/elastic/kibana/issues/161539 + // FLAKY: https://github.com/elastic/kibana/issues/165766 + describe('Default scope', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { cy.clearLocalStorage(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts index 1476e82421cda..69244ddce38f6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts @@ -38,7 +38,8 @@ import { closeTimeline, openTimelineById } from '../../tasks/timeline'; const siemDataViewTitle = 'Security Default Data View'; const dataViews = ['auditbeat-*,fakebeat-*', 'auditbeat-*,*beat*,siem-read*,.kibana*,fakebeat-*']; -describe('Timeline scope', { tags: '@brokenInServerless' }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe.skip('Timeline scope', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { cy.clearLocalStorage(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts index 1476e82421cda..979cf8c657b12 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts @@ -38,7 +38,8 @@ import { closeTimeline, openTimelineById } from '../../tasks/timeline'; const siemDataViewTitle = 'Security Default Data View'; const dataViews = ['auditbeat-*,fakebeat-*', 'auditbeat-*,*beat*,siem-read*,.kibana*,fakebeat-*']; -describe('Timeline scope', { tags: '@brokenInServerless' }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Timeline scope', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { cy.clearLocalStorage(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts index 1013a4d7d53d4..48c7f49c14868 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts @@ -24,6 +24,7 @@ import { UNSELECTED_ALERT_TAG, } from '../../screens/alerts'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe('Alert tagging', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts index 313d2a625ad9f..2c00882fb15db 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts @@ -24,6 +24,7 @@ import { } from '../../screens/search_bar'; import { TOASTER } from '../../screens/alerts_detection_rules'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Histogram legend hover actions', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts index f9aa9d08c2954..d751f2a68812b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts @@ -7,12 +7,11 @@ import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation'; +import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL, ruleDetailsUrl } from '../../urls/navigation'; import { getNewRule } from '../../objects/rule'; import { PAGE_TITLE } from '../../screens/common/page'; import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../../tasks/login'; -import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; import { createRule, deleteCustomRule } from '../../tasks/api_calls/rules'; import { getCallOut, @@ -30,9 +29,10 @@ const waitForPageTitleToBeShown = () => { cy.get(PAGE_TITLE).should('be.visible'); }; +// TODO: https://github.com/elastic/kibana/issues/161539 Does it need to run in Serverless? describe( 'Detections > Need Admin Callouts indicating an admin is needed to migrate the alert data set', - { tags: '@ess' }, + { tags: ['@ess', '@skipInServerless'] }, () => { before(() => { // First, we have to open the app on behalf of a privileged user in order to initialize it. @@ -80,10 +80,9 @@ describe( context('On Rule Details page', () => { beforeEach(() => { - createRule(getNewRule({ rule_id: 'rule_testing' })); - loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL); - waitForPageTitleToBeShown(); - goToRuleDetails(); + createRule(getNewRule({ rule_id: 'rule_testing' })).then((rule) => + loadPageAsPlatformEngineerUser(ruleDetailsUrl(rule.body.id)) + ); }); afterEach(() => { @@ -130,10 +129,9 @@ describe( context('On Rule Details page', () => { beforeEach(() => { - createRule(getNewRule({ rule_id: 'rule_testing' })); - loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL); - waitForPageTitleToBeShown(); - goToRuleDetails(); + createRule(getNewRule({ rule_id: 'rule_testing' })).then((rule) => + loadPageAsPlatformEngineerUser(ruleDetailsUrl(rule.body.id)) + ); }); afterEach(() => { @@ -180,10 +178,9 @@ describe( context('On Rule Details page', () => { beforeEach(() => { - createRule(getNewRule({ rule_id: 'rule_testing' })); - loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL); - waitForPageTitleToBeShown(); - goToRuleDetails(); + createRule(getNewRule({ rule_id: 'rule_testing' })).then((rule) => + loadPageAsPlatformEngineerUser(ruleDetailsUrl(rule.body.id)) + ); }); afterEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts index 2057a7db3363f..90226eca02e52 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts @@ -20,14 +20,14 @@ import { THREAT_DETAILS_ACCORDION, } from '../../screens/alerts_details'; import { TIMELINE_FIELD } from '../../screens/rule_details'; -import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; import { expandFirstAlert, setEnrichmentDates, viewThreatIntelTab } from '../../tasks/alerts'; import { createRule } from '../../tasks/api_calls/rules'; import { openJsonView, openThreatIndicatorDetails } from '../../tasks/alerts_details'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; +import { ruleDetailsUrl } from '../../urls/navigation'; import { addsFieldsToTimeline } from '../../tasks/rule_details'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe('CTI Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); @@ -35,7 +35,7 @@ describe('CTI Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless' cy.task('esArchiverLoad', { archiveName: 'threat_indicator' }); cy.task('esArchiverLoad', { archiveName: 'suspicious_source_event' }); login(); - createRule({ ...getNewThreatIndicatorRule(), rule_id: 'rule_testing', enabled: true }); + disableExpandableFlyout(); }); @@ -46,10 +46,12 @@ describe('CTI Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless' beforeEach(() => { login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); + createRule({ ...getNewThreatIndicatorRule(), rule_id: 'rule_testing', enabled: true }).then( + (rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id)) + ); }); + // TODO: https://github.com/elastic/kibana/issues/161539 // Skipped: https://github.com/elastic/kibana/issues/162818 it.skip('Displays enrichment matched.* fields on the timeline', () => { const expectedFields = { @@ -159,12 +161,6 @@ describe('CTI Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless' cy.task('esArchiverLoad', { archiveName: 'threat_indicator2' }); }); - beforeEach(() => { - login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); - }); - after(() => { cy.task('esArchiverUnload', 'threat_indicator2'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts index 102405eb95a61..393123650c388 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts @@ -30,6 +30,7 @@ import { login, visit } from '../../tasks/login'; import { ALERTS_URL } from '../../urls/navigation'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe('Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts index 0568037b5c693..a4e74d40dc922 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts @@ -7,12 +7,11 @@ import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation'; +import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL, ruleDetailsUrl } from '../../urls/navigation'; import { getNewRule } from '../../objects/rule'; import { PAGE_TITLE } from '../../screens/common/page'; import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../../tasks/login'; -import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; import { createRule, deleteCustomRule } from '../../tasks/api_calls/rules'; import { getCallOut, @@ -42,7 +41,8 @@ const waitForPageTitleToBeShown = () => { cy.get(PAGE_TITLE).should('be.visible'); }; -describe('Detections > Callouts', { tags: '@ess' }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Detections > Callouts', { tags: ['@ess', '@skipInServerless'] }, () => { before(() => { // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. @@ -75,10 +75,9 @@ describe('Detections > Callouts', { tags: '@ess' }, () => { context('On Rule Details page', () => { beforeEach(() => { - createRule(getNewRule()); - loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL); - waitForPageTitleToBeShown(); - goToRuleDetails(); + createRule(getNewRule()).then((rule) => + loadPageAsReadOnlyUser(ruleDetailsUrl(rule.body.id)) + ); }); afterEach(() => { @@ -126,10 +125,9 @@ describe('Detections > Callouts', { tags: '@ess' }, () => { context('On Rule Details page', () => { beforeEach(() => { - createRule(getNewRule()); - loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL); - waitForPageTitleToBeShown(); - goToRuleDetails(); + createRule(getNewRule()).then((rule) => + loadPageAsPlatformEngineer(ruleDetailsUrl(rule.body.id)) + ); }); afterEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts index a38ac3b2fb842..7c7cd582baa3b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts @@ -14,6 +14,7 @@ import { TIMELINE_QUERY, TIMELINE_VIEW_IN_ANALYZER } from '../../screens/timelin import { selectAlertsHistogram } from '../../tasks/alerts'; import { createTimeline } from '../../tasks/timelines'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Ransomware Detection Alerts', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts index c71c3634246a7..8c62fb1929317 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts @@ -15,6 +15,7 @@ import { selectAlertsHistogram } from '../../tasks/alerts'; import { createTimeline } from '../../tasks/timelines'; import { cleanKibana } from '../../tasks/common'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Ransomware Prevention Alerts', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts index 30059c9ad5855..19684d539c287 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts @@ -55,9 +55,11 @@ const loadPageAsReadOnlyUser = (url: string) => { waitForPageWithoutDateRange(url, ROLES.reader); }; +// TODO: https://github.com/elastic/kibana/issues/164451 We should find a way to make this spec work in Serverless +// TODO: https://github.com/elastic/kibana/issues/161540 describe( 'Detection rules, Prebuilt Rules Installation and Update - Authorization/RBAC', - { tags: '@ess' }, + { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { beforeEach(() => { login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts index c9fe54f66f0be..b1bdae3ae49f5 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts @@ -22,9 +22,10 @@ import { ruleUpdatesTabClick, } from '../../../tasks/prebuilt_rules'; +// TODO: https://github.com/elastic/kibana/issues/161540 describe( 'Detection rules, Prebuilt Rules Installation and Update - Error handling', - { tags: '@ess' }, + { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { beforeEach(() => { login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts index b6852c698e635..a4fb84d557d3f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts @@ -39,9 +39,10 @@ import { ruleUpdatesTabClick, } from '../../../tasks/prebuilt_rules'; +// TODO: https://github.com/elastic/kibana/issues/161540 describe( 'Detection rules, Prebuilt Rules Installation and Update workflow', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts index b172b84b76953..00e38335792ce 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts @@ -50,7 +50,8 @@ const rules = Array.from(Array(5)).map((_, i) => { }); }); -describe('Prebuilt rules', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +describe('Prebuilt rules', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts index 9202b77044e93..3ee378f12f3d3 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts @@ -29,9 +29,10 @@ const RULE_1 = createRuleAssetSavedObject({ rule_id: 'rule_1', }); +// TODO: https://github.com/elastic/kibana/issues/161540 describe( 'Detection rules, Prebuilt Rules Installation and Update Notifications', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts index a4b15f68cdcb9..f835f6801dedc 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts @@ -8,7 +8,7 @@ import { getIndexConnector } from '../../../objects/connector'; import { getSimpleCustomQueryRule } from '../../../objects/rule'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; +import { goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { deleteIndex, waitForNewDocumentToBeIndexed } from '../../../tasks/api_calls/elasticsearch'; import { cleanKibana, @@ -27,9 +27,10 @@ import { login, visit } from '../../../tasks/login'; import { RULE_CREATION } from '../../../urls/navigation'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Rule actions during detection rule creation', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const indexConnector = getIndexConnector(); @@ -51,14 +52,15 @@ describe( const initialNumberOfDocuments = 0; const expectedJson = JSON.parse(actions.connectors[0].document); - it('Indexes a new document after the index action is triggered ', function () { + it('Indexes a new document after the index action is triggered', function () { visit(RULE_CREATION); fillDefineCustomRuleAndContinue(rule); fillAboutRuleAndContinue(rule); fillScheduleRuleAndContinue(rule); fillRuleAction(actions); createAndEnableRule(); - goToRuleDetails(); + + goToRuleDetailsOf(rule.name); /* When the rule is executed, the action is triggered. We wait for the new document to be indexed */ waitForNewDocumentToBeIndexed(index, initialNumberOfDocuments); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts index 7bb89b35ca83a..31c05a1011800 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts @@ -73,11 +73,9 @@ import { import { deleteFirstRule, deleteRuleFromDetailsPage, - editFirstRule, expectManagementTableRules, getRulesManagementTableRows, - goToRuleDetails, - goToTheRuleDetailsOf, + goToRuleDetailsOf, selectRulesByName, } from '../../../tasks/alerts_detection_rules'; import { deleteSelectedRules } from '../../../tasks/rules_bulk_actions'; @@ -108,12 +106,17 @@ import { waitForAlertsToPopulate, } from '../../../tasks/create_new_rule'; import { saveEditedRule } from '../../../tasks/edit_rule'; -import { login, visit, visitSecurityDetectionRulesPage } from '../../../tasks/login'; +import { + login, + visit, + visitSecurityDetectionRulesPage, + visitWithoutDateRange, +} from '../../../tasks/login'; import { enablesRule, getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; +import { ruleDetailsUrl, ruleEditUrl, RULE_CREATION } from '../../../urls/navigation'; -import { RULE_CREATION } from '../../../urls/navigation'; - -describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Custom query rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { deleteAlertsAndRules(); }); @@ -180,7 +183,7 @@ describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => }); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(ruleFields.ruleName); cy.log('Asserting rule details'); cy.get(RULE_NAME_HEADER).should('contain', ruleFields.ruleName); @@ -236,10 +239,15 @@ describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => describe('Custom detection rules deletion and edition', () => { context('Deletion', () => { + const TESTED_RULE_DATA = getNewRule({ + rule_id: 'rule1', + name: 'New Rule Test', + enabled: false, + max_signals: 500, + }); + beforeEach(() => { - createRule( - getNewRule({ rule_id: 'rule1', name: 'New Rule Test', enabled: false, max_signals: 500 }) - ); + createRule(TESTED_RULE_DATA); createRule( getNewOverrideRule({ rule_id: 'rule2', @@ -279,7 +287,7 @@ describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => it('Deletes more than one rule', () => { getRulesManagementTableRows().then((rules) => { - const rulesToDelete = ['New Rule Test', 'Override Rule'] as const; + const rulesToDelete = [TESTED_RULE_DATA.name, 'Override Rule'] as const; const initialNumberOfRules = rules.length; const numberOfRulesToBeDeleted = 2; const expectedNumberOfRulesAfterDeletion = @@ -316,7 +324,7 @@ describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => const initialNumberOfRules = rules.length; const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - 1; - goToTheRuleDetailsOf('New Rule Test'); + goToRuleDetailsOf(TESTED_RULE_DATA.name); cy.intercept('POST', '/api/detection_engine/rules/_bulk_delete').as('deleteRule'); deleteRuleFromDetailsPage(); @@ -339,108 +347,119 @@ describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => }); context('Edition', () => { - const rule = getEditedRule(); - const expectedEditedtags = rule.tags?.join(''); - const expectedEditedIndexPatterns = rule.index; - - beforeEach(() => { - deleteConnectors(); - createRule(getExistingRule({ rule_id: 'rule1', enabled: true })); - login(); - visitSecurityDetectionRulesPage(); - }); - - it('Only modifies rule active status on enable/disable', () => { - enablesRule(); + const editedRuleData = getEditedRule(); + const expectedEditedTags = editedRuleData.tags?.join(''); + const expectedEditedIndexPatterns = editedRuleData.index; + + describe('on rule details page', () => { + beforeEach(() => { + deleteConnectors(); + login(); + createRule(getExistingRule({ rule_id: 'rule1', enabled: true })).then((rule) => + visitWithoutDateRange(ruleDetailsUrl(rule.body.id)) + ); + }); - cy.intercept('GET', `/api/detection_engine/rules?id=*`).as('fetchRuleDetails'); + it('Only modifies rule active status on enable/disable', () => { + enablesRule(); - goToRuleDetails(); + cy.intercept('GET', `/api/detection_engine/rules?id=*`).as('fetchRuleDetails'); - cy.wait('@fetchRuleDetails').then(({ response }) => { - cy.wrap(response?.statusCode).should('eql', 200); + cy.wait('@fetchRuleDetails').then(({ response }) => { + cy.wrap(response?.statusCode).should('eql', 200); - cy.wrap(response?.body.max_signals).should('eql', getExistingRule().max_signals); - cy.wrap(response?.body.enabled).should('eql', false); + cy.wrap(response?.body.max_signals).should('eql', getExistingRule().max_signals); + cy.wrap(response?.body.enabled).should('eql', false); + }); }); }); - it('Allows a rule to be edited', () => { - const existingRule = getExistingRule(); + describe('on rule editing page', () => { + beforeEach(() => { + deleteConnectors(); + login(); + createRule(getExistingRule({ rule_id: 'rule1', enabled: true })).then((rule) => + visitWithoutDateRange(ruleEditUrl(rule.body.id)) + ); + }); - editFirstRule(); + it('Allows a rule to be edited', () => { + const existingRule = getExistingRule(); - // expect define step to populate - cy.get(CUSTOM_QUERY_INPUT).should('have.value', existingRule.query); + // expect define step to populate + cy.get(CUSTOM_QUERY_INPUT).should('have.value', existingRule.query); - cy.get(DEFINE_INDEX_INPUT).should('have.text', existingRule.index?.join('')); + cy.get(DEFINE_INDEX_INPUT).should('have.text', existingRule.index?.join('')); - goToAboutStepTab(); + goToAboutStepTab(); - // expect about step to populate - cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name); - cy.get(RULE_DESCRIPTION_INPUT).should('have.text', existingRule.description); - cy.get(TAGS_FIELD).should('have.text', existingRule.tags?.join('')); - cy.get(SEVERITY_DROPDOWN).should('have.text', 'High'); - cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', `${existingRule.risk_score}`); + // expect about step to populate + cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name); + cy.get(RULE_DESCRIPTION_INPUT).should('have.text', existingRule.description); + cy.get(TAGS_FIELD).should('have.text', existingRule.tags?.join('')); + cy.get(SEVERITY_DROPDOWN).should('have.text', 'High'); + cy.get(DEFAULT_RISK_SCORE_INPUT) + .invoke('val') + .should('eql', `${existingRule.risk_score}`); - goToScheduleStepTab(); + goToScheduleStepTab(); - // expect schedule step to populate - const interval = existingRule.interval; - const intervalParts = interval != null && interval.match(/[0-9]+|[a-zA-Z]+/g); - if (intervalParts) { - const [amount, unit] = intervalParts; - cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', amount); - cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', unit); - } else { - throw new Error('Cannot assert scheduling info on a rule without an interval'); - } + // expect schedule step to populate + const interval = existingRule.interval; + const intervalParts = interval != null && interval.match(/[0-9]+|[a-zA-Z]+/g); + if (intervalParts) { + const [amount, unit] = intervalParts; + cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', amount); + cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', unit); + } else { + throw new Error('Cannot assert scheduling info on a rule without an interval'); + } - goToActionsStepTab(); + goToActionsStepTab(); - addEmailConnectorAndRuleAction('test@example.com', 'Subject'); + addEmailConnectorAndRuleAction('test@example.com', 'Subject'); - cy.get(ACTIONS_SUMMARY_BUTTON).should('have.text', 'Summary of alerts'); - cy.get(ACTIONS_NOTIFY_WHEN_BUTTON).should('have.text', 'Per rule run'); + cy.get(ACTIONS_SUMMARY_BUTTON).should('have.text', 'Summary of alerts'); + cy.get(ACTIONS_NOTIFY_WHEN_BUTTON).should('have.text', 'Per rule run'); - goToAboutStepTab(); - cy.get(TAGS_CLEAR_BUTTON).click(); - fillAboutRule(getEditedRule()); + goToAboutStepTab(); + cy.get(TAGS_CLEAR_BUTTON).click(); + fillAboutRule(getEditedRule()); - cy.intercept('GET', '/api/detection_engine/rules?id*').as('getRule'); + cy.intercept('GET', '/api/detection_engine/rules?id*').as('getRule'); - saveEditedRule(); + saveEditedRule(); - cy.wait('@getRule').then(({ response }) => { - cy.wrap(response?.statusCode).should('eql', 200); - // ensure that editing rule does not modify max_signals - cy.wrap(response?.body.max_signals).should('eql', existingRule.max_signals); - }); + cy.wait('@getRule').then(({ response }) => { + cy.wrap(response?.statusCode).should('eql', 200); + // ensure that editing rule does not modify max_signals + cy.wrap(response?.body.max_signals).should('eql', existingRule.max_signals); + }); - cy.get(RULE_NAME_HEADER).should('contain', `${getEditedRule().name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getEditedRule().description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'Medium'); - getDetails(RISK_SCORE_DETAILS).should('have.text', `${getEditedRule().risk_score}`); - getDetails(TAGS_DETAILS).should('have.text', expectedEditedtags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', getEditedRule().note); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should( - 'have.text', - expectedEditedIndexPatterns?.join('') - ); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', getEditedRule().query); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - }); - if (getEditedRule().interval) { - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should('have.text', getEditedRule().interval); + cy.get(RULE_NAME_HEADER).should('contain', `${getEditedRule().name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getEditedRule().description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', 'Medium'); + getDetails(RISK_SCORE_DETAILS).should('have.text', `${getEditedRule().risk_score}`); + getDetails(TAGS_DETAILS).should('have.text', expectedEditedTags); }); - } + cy.get(INVESTIGATION_NOTES_TOGGLE).click(); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', getEditedRule().note); + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(INDEX_PATTERNS_DETAILS).should( + 'have.text', + expectedEditedIndexPatterns?.join('') + ); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', getEditedRule().query); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + }); + if (getEditedRule().interval) { + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS).should('have.text', getEditedRule().interval); + }); + } + }); }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts index 2c2f44024e7a2..6d9785ec7ac00 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts @@ -50,7 +50,7 @@ import { import { getRulesManagementTableRows, - goToRuleDetails, + goToRuleDetailsOf, } from '../../../tasks/alerts_detection_rules'; import { postDataView } from '../../../tasks/common'; import { @@ -67,7 +67,8 @@ import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_deta import { RULE_CREATION } from '../../../urls/navigation'; -describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Custom query rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { describe('Custom detection rules creation with data views', () => { const rule = getDataViewRule(); const expectedUrls = rule.references?.join(''); @@ -104,7 +105,7 @@ describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => cy.get(SEVERITY).should('have.text', 'High'); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); @@ -160,7 +161,7 @@ describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => fillScheduleRuleAndContinue(rule); createRuleWithoutEnabling(); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(EDIT_RULE_SETTINGS_LINK).click(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts index fc9dd511aa21f..5e0dc286a36ac 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts @@ -22,7 +22,7 @@ import { CUSTOM_QUERY_DETAILS, } from '../../../screens/rule_details'; -import { goToRuleDetails, editFirstRule } from '../../../tasks/alerts_detection_rules'; +import { editFirstRule, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { createSavedQuery, deleteSavedQueries } from '../../../tasks/api_calls/saved_queries'; import { cleanKibana, deleteAlertsAndRules } from '../../../tasks/common'; import { @@ -35,17 +35,23 @@ import { uncheckLoadQueryDynamically, } from '../../../tasks/create_new_rule'; import { saveEditedRule } from '../../../tasks/edit_rule'; -import { login, visit } from '../../../tasks/login'; +import { login, visit, visitWithoutDateRange } from '../../../tasks/login'; import { assertDetailsNotExist, getDetails } from '../../../tasks/rule_details'; import { createRule } from '../../../tasks/api_calls/rules'; -import { RULE_CREATION, SECURITY_DETECTIONS_RULES_URL } from '../../../urls/navigation'; +import { + ruleDetailsUrl, + ruleEditUrl, + RULE_CREATION, + SECURITY_DETECTIONS_RULES_URL, +} from '../../../urls/navigation'; const savedQueryName = 'custom saved query'; const savedQueryQuery = 'process.name: test'; const savedQueryFilterKey = 'testAgent.value'; -describe('Custom saved_query rules', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Saved query rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); }); @@ -86,7 +92,7 @@ describe('Custom saved_query rules', { tags: ['@ess', '@brokenInServerless'] }, cy.wrap(response?.body.type).should('equal', 'saved_query'); }); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); @@ -99,50 +105,59 @@ describe('Custom saved_query rules', { tags: ['@ess', '@brokenInServerless'] }, context('Non existent saved query', () => { const FAILED_TO_LOAD_ERROR = 'Failed to load the saved query'; - beforeEach(() => { - createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })); - visit(SECURITY_DETECTIONS_RULES_URL); - }); - it('Shows error toast on details page when saved query can not be loaded', function () { - goToRuleDetails(); - - cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); - }); - it('Shows validation error on rule edit when saved query can not be loaded', function () { - editFirstRule(); + describe('on rule details page', () => { + beforeEach(() => { + createRule( + getSavedQueryRule({ + saved_id: 'non-existent', + query: undefined, + }) + ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id))); + }); - cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); + it('Shows error toast on details page when saved query can not be loaded', function () { + cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); + }); }); - it('Allows to update saved_query rule with non-existent query', () => { - editFirstRule(); + describe('on rule editing page', () => { + beforeEach(() => { + createRule( + getSavedQueryRule({ + saved_id: 'non-existent', + query: undefined, + }) + ).then((rule) => visitWithoutDateRange(ruleEditUrl(rule.body.id))); + }); - cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('exist'); + it('Shows validation error on rule edit when saved query can not be loaded', function () { + cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); + }); - cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); - saveEditedRule(); + it('Allows to update saved_query rule with non-existent query', () => { + cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('exist'); - cy.wait('@editedRule').then(({ response }) => { - // updated rule type shouldn't change - cy.wrap(response?.body.type).should('equal', 'saved_query'); - }); + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); - cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + cy.wait('@editedRule').then(({ response }) => { + // updated rule type shouldn't change + cy.wrap(response?.body.type).should('equal', 'saved_query'); + }); - assertDetailsNotExist(SAVED_QUERY_NAME_DETAILS); - assertDetailsNotExist(SAVED_QUERY_DETAILS); + cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + + assertDetailsNotExist(SAVED_QUERY_NAME_DETAILS); + assertDetailsNotExist(SAVED_QUERY_DETAILS); + }); }); }); context('Editing', () => { it('Allows to update query rule as saved_query rule type', () => { createSavedQuery(savedQueryName, savedQueryQuery); - createRule(getNewRule()); - - visit(SECURITY_DETECTIONS_RULES_URL); - - editFirstRule(); + createRule(getNewRule()).then((rule) => visitWithoutDateRange(ruleEditUrl(rule.body.id))); selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); checkLoadQueryDynamically(); @@ -165,13 +180,11 @@ describe('Custom saved_query rules', { tags: ['@ess', '@brokenInServerless'] }, const expectedCustomTestQuery = 'random test query'; createSavedQuery(savedQueryName, savedQueryQuery).then((response) => { cy.log(JSON.stringify(response.body, null, 2)); - createRule(getSavedQueryRule({ saved_id: response.body.id, query: undefined })); + createRule(getSavedQueryRule({ saved_id: response.body.id, query: undefined })).then( + (rule) => visitWithoutDateRange(ruleEditUrl(rule.body.id)) + ); }); - visit(SECURITY_DETECTIONS_RULES_URL); - - editFirstRule(); - // query input should be disabled and has value of saved query getCustomQueryInput().should('have.value', savedQueryQuery).should('be.disabled'); @@ -192,11 +205,10 @@ describe('Custom saved_query rules', { tags: ['@ess', '@brokenInServerless'] }, it('Allows to update saved_query rule with non-existent query by adding custom query', () => { const expectedCustomTestQuery = 'random test query'; - createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })); + createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })).then((rule) => + visitWithoutDateRange(ruleEditUrl(rule.body.id)) + ); - visit(SECURITY_DETECTIONS_RULES_URL); - - editFirstRule(); uncheckLoadQueryDynamically(); // type custom query, ensure Load dynamically checkbox is absent, as rule can't be saved win non valid saved query @@ -216,7 +228,9 @@ describe('Custom saved_query rules', { tags: ['@ess', '@brokenInServerless'] }, it('Allows to update saved_query rule with non-existent query by selecting another saved query', () => { createSavedQuery(savedQueryName, savedQueryQuery); - createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })); + createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })).then((rule) => + visitWithoutDateRange(ruleEditUrl(rule.body.id)) + ); visit(SECURITY_DETECTIONS_RULES_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts index a558ac6ccedb7..545af73b8a065 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts @@ -42,11 +42,7 @@ import { } from '../../../screens/rule_details'; import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; -import { - expectNumberOfRules, - goToRuleDetails, - goToTheRuleDetailsOf, -} from '../../../tasks/alerts_detection_rules'; +import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { cleanKibana, deleteAlertsAndRules } from '../../../tasks/common'; import { createAndEnableRule, @@ -60,7 +56,8 @@ import { login, visit } from '../../../tasks/login'; import { RULE_CREATION } from '../../../urls/navigation'; -describe('EQL rules', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('EQL rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); }); @@ -97,7 +94,7 @@ describe('EQL rules', { tags: ['@ess', '@brokenInServerless'] }, () => { cy.get(SEVERITY).should('have.text', 'High'); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); @@ -164,7 +161,7 @@ describe('EQL rules', { tags: ['@ess', '@brokenInServerless'] }, () => { fillAboutRuleAndContinue(rule); fillScheduleRuleAndContinue(rule); createAndEnableRule(); - goToTheRuleDetailsOf(rule.name); + goToRuleDetailsOf(rule.name); waitForTheRuleToBeExecuted(); waitForAlertsToPopulate(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts index 9e896fa6861fd..d093e9c37f33d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts @@ -59,15 +59,16 @@ import { investigateFirstAlertInTimeline } from '../../../tasks/alerts'; import { duplicateFirstRule, duplicateRuleFromMenu, - goToRuleDetails, checkDuplicatedRule, expectNumberOfRules, selectAllRules, + goToRuleDetailsOf, + disableAutoRefresh, } from '../../../tasks/alerts_detection_rules'; import { duplicateSelectedRulesWithExceptions } from '../../../tasks/rules_bulk_actions'; import { createRule } from '../../../tasks/api_calls/rules'; import { loadPrepackagedTimelineTemplates } from '../../../tasks/api_calls/timelines'; -import { cleanKibana, deleteAlertsAndRules } from '../../../tasks/common'; +import { cleanKibana } from '../../../tasks/common'; import { createAndEnableRule, fillAboutRuleAndContinue, @@ -100,18 +101,23 @@ import { SCHEDULE_LOOKBACK_UNITS_INPUT, } from '../../../screens/create_new_rule'; import { goBackToRuleDetails } from '../../../tasks/edit_rule'; -import { login, visit, visitWithoutDateRange } from '../../../tasks/login'; +import { login, visitWithoutDateRange } from '../../../tasks/login'; import { goBackToRulesTable, getDetails, waitForTheRuleToBeExecuted, } from '../../../tasks/rule_details'; -import { DETECTIONS_RULE_MANAGEMENT_URL, RULE_CREATION } from '../../../urls/navigation'; +import { + DETECTIONS_RULE_MANAGEMENT_URL, + ruleDetailsUrl, + RULE_CREATION, +} from '../../../urls/navigation'; const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"'; -describe('indicator match', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('indicator match', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { describe('Detection rules, Indicator Match', () => { const expectedUrls = getNewThreatIndicatorRule().references?.join(''); const expectedFalsePositives = getNewThreatIndicatorRule().false_positives?.join(''); @@ -121,21 +127,13 @@ describe('indicator match', { tags: ['@ess', '@brokenInServerless'] }, () => { const expectedNumberOfRules = 1; const expectedNumberOfAlerts = '1 alert'; - before(() => { + beforeEach(() => { cleanKibana(); cy.task('esArchiverLoad', { archiveName: 'threat_indicator' }); cy.task('esArchiverLoad', { archiveName: 'suspicious_source_event' }); - }); - - beforeEach(() => { login(); }); - after(() => { - cy.task('esArchiverUnload', 'threat_indicator'); - cy.task('esArchiverUnload', 'suspicious_source_event'); - }); - describe('Creating new indicator match rules', () => { describe('Index patterns', () => { beforeEach(() => { @@ -430,11 +428,6 @@ describe('indicator match', { tags: ['@ess', '@brokenInServerless'] }, () => { }); describe('Generating signals', () => { - beforeEach(() => { - login(); - deleteAlertsAndRules(); - }); - it('Creates and enables a new Indicator Match rule', () => { const rule = getNewThreatIndicatorRule(); visitWithoutDateRange(RULE_CREATION); @@ -453,7 +446,7 @@ describe('indicator match', { tags: ['@ess', '@brokenInServerless'] }, () => { cy.get(SEVERITY).should('have.text', 'Critical'); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); @@ -510,9 +503,10 @@ describe('indicator match', { tags: ['@ess', '@brokenInServerless'] }, () => { const accessibilityText = `Press enter for options, or press space to begin dragging.`; loadPrepackagedTimelineTemplates(); - createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })); - visit(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); + createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })).then( + (rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id)) + ); + waitForAlertsToPopulate(); investigateFirstAlertInTimeline(); @@ -541,32 +535,46 @@ describe('indicator match', { tags: ['@ess', '@brokenInServerless'] }, () => { }); describe('Duplicates the indicator rule', () => { - beforeEach(() => { - login(); - deleteAlertsAndRules(); - createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + const TESTED_RULE_DATA = getNewThreatIndicatorRule({ + name: 'Indicator rule duplicate test', + rule_id: 'rule_testing', + enabled: false, }); - it('Allows the rule to be duplicated from the table', () => { - duplicateFirstRule(); - goBackToRuleDetails(); - goBackToRulesTable(); - checkDuplicatedRule(); - }); + describe('on rule editing page', () => { + beforeEach(() => { + createRule(TESTED_RULE_DATA); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + disableAutoRefresh(); + }); - it("Allows the rule to be duplicated from the table's bulk actions", () => { - selectAllRules(); - duplicateSelectedRulesWithExceptions(); - checkDuplicatedRule(); + it('Allows the rule to be duplicated from the table', () => { + duplicateFirstRule(); + goBackToRuleDetails(); + goBackToRulesTable(); + checkDuplicatedRule(TESTED_RULE_DATA.name); + }); + + it("Allows the rule to be duplicated from the table's bulk actions", () => { + selectAllRules(); + duplicateSelectedRulesWithExceptions(); + checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`); + }); }); - it('Allows the rule to be duplicated from the edit screen', () => { - goToRuleDetails(); - duplicateRuleFromMenu(); - goBackToRuleDetails(); - goBackToRulesTable(); - checkDuplicatedRule(); + describe('on rule details page', () => { + beforeEach(() => { + createRule(getNewThreatIndicatorRule(TESTED_RULE_DATA)).then((rule) => + visitWithoutDateRange(ruleDetailsUrl(rule.body.id)) + ); + }); + + it('Allows the rule to be duplicated', () => { + duplicateRuleFromMenu(); + goBackToRuleDetails(); + goBackToRulesTable(); + checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`); + }); }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts index e65764dff6203..cb820c60c1202 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts @@ -40,7 +40,7 @@ import { } from '../../../screens/rule_details'; import { getDetails } from '../../../tasks/rule_details'; -import { expectNumberOfRules, goToRuleDetails } from '../../../tasks/alerts_detection_rules'; +import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { cleanKibana } from '../../../tasks/common'; import { createAndEnableRule, @@ -53,7 +53,8 @@ import { login, visitWithoutDateRange } from '../../../tasks/login'; import { RULE_CREATION } from '../../../urls/navigation'; -describe('Detection rules, machine learning', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Machine Learning rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const expectedUrls = (getMachineLearningRule().references ?? []).join(''); const expectedFalsePositives = (getMachineLearningRule().false_positives ?? []).join(''); const expectedTags = (getMachineLearningRule().tags ?? []).join(''); @@ -86,7 +87,7 @@ describe('Detection rules, machine learning', { tags: ['@ess', '@brokenInServerl cy.get(SEVERITY).should('have.text', 'Critical'); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(mlRule.name); cy.get(RULE_NAME_HEADER).should('contain', `${mlRule.name}`); cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', mlRule.description); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts index 2b840111b97bc..132b6d71295e8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts @@ -44,7 +44,7 @@ import { } from '../../../screens/rule_details'; import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; -import { expectNumberOfRules, goToRuleDetails } from '../../../tasks/alerts_detection_rules'; +import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { cleanKibana, deleteAlertsAndRules } from '../../../tasks/common'; import { createAndEnableRule, @@ -58,7 +58,8 @@ import { login, visit } from '../../../tasks/login'; import { RULE_CREATION } from '../../../urls/navigation'; -describe('New Terms rules', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('New Terms rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); login(); @@ -94,7 +95,7 @@ describe('New Terms rules', { tags: ['@ess', '@brokenInServerless'] }, () => { cy.get(SEVERITY).should('have.text', 'High'); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts index 4aa1bb9e56724..46d01f6b83639 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts @@ -47,8 +47,8 @@ import { TIMESTAMP_OVERRIDE_DETAILS, } from '../../../screens/rule_details'; -import { expectNumberOfRules, goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { deleteAlertsAndRules } from '../../../tasks/common'; +import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { createAndEnableRule, fillAboutRuleWithOverrideAndContinue, @@ -61,7 +61,8 @@ import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_deta import { RULE_CREATION } from '../../../urls/navigation'; -describe('Detection rules, override', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Rules override', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const rule = getNewOverrideRule(); const expectedUrls = rule.references?.join(''); const expectedFalsePositives = rule.false_positives?.join(''); @@ -90,7 +91,7 @@ describe('Detection rules, override', { tags: ['@ess', '@brokenInServerless'] }, cy.get(SEVERITY).should('have.text', 'High'); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts index 078be723c39ac..1828455992207 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts @@ -44,7 +44,7 @@ import { } from '../../../screens/rule_details'; import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; -import { expectNumberOfRules, goToRuleDetails } from '../../../tasks/alerts_detection_rules'; +import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { cleanKibana, deleteAlertsAndRules } from '../../../tasks/common'; import { createAndEnableRule, @@ -58,7 +58,8 @@ import { login, visitWithoutDateRange } from '../../../tasks/login'; import { RULE_CREATION } from '../../../urls/navigation'; -describe('Detection rules, threshold', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Threshold rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const rule = getNewThresholdRule(); const expectedUrls = rule.references?.join(''); const expectedFalsePositives = rule.false_positives?.join(''); @@ -92,7 +93,7 @@ describe('Detection rules, threshold', { tags: ['@ess', '@brokenInServerless'] } cy.get(SEVERITY).should('have.text', 'High'); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetails(); + goToRuleDetailsOf(rule.name); cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts index dfb598bfa369b..28fce3db9d55b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts @@ -24,7 +24,9 @@ import { } from '../../../../tasks/common/callouts'; import { login, visitSecurityDetectionRulesPage } from '../../../../tasks/login'; -describe('All rules - read only', { tags: '@ess' }, () => { +// TODO: https://github.com/elastic/kibana/issues/164451 We should find a way to make this spec work in Serverless +// TODO: https://github.com/elastic/kibana/issues/161540 +describe('All rules - read only', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { cleanKibana(); createRule(getNewRule({ rule_id: '1', enabled: false })); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts index 605ce523eb35b..1ba764c09000d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts @@ -12,47 +12,52 @@ import { cleanKibana } from '../../../../tasks/common'; import { login, visit } from '../../../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation'; -describe('Maintenance window callout on Rule Management page', { tags: ['@ess'] }, () => { - let maintenanceWindowId = ''; - - before(() => { - cleanKibana(); - login(); - - const body: AsApiContract = { - title: 'My maintenance window', - duration: 60000, // 1 minute - r_rule: { - dtstart: new Date().toISOString(), - tzid: 'Europe/Amsterdam', - freq: 0, - count: 1, - }, - }; - - // Create a test maintenance window - cy.request({ - method: 'POST', - url: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, - body, - }).then((response) => { - maintenanceWindowId = response.body.id; +// TODO: https://github.com/elastic/kibana/issues/161540 +describe( + 'Maintenance window callout on Rule Management page', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + let maintenanceWindowId = ''; + + before(() => { + cleanKibana(); + login(); + + const body: AsApiContract = { + title: 'My maintenance window', + duration: 60000, // 1 minute + r_rule: { + dtstart: new Date().toISOString(), + tzid: 'Europe/Amsterdam', + freq: 0, + count: 1, + }, + }; + + // Create a test maintenance window + cy.request({ + method: 'POST', + url: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, + headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + body, + }).then((response) => { + maintenanceWindowId = response.body.id; + }); }); - }); - - after(() => { - // Delete a test maintenance window - cy.request({ - method: 'DELETE', - url: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/${maintenanceWindowId}`, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + + after(() => { + // Delete a test maintenance window + cy.request({ + method: 'DELETE', + url: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/${maintenanceWindowId}`, + headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + }); }); - }); - it('Displays the callout when there are running maintenance windows', () => { - visit(DETECTIONS_RULE_MANAGEMENT_URL); + it('Displays the callout when there are running maintenance windows', () => { + visit(DETECTIONS_RULE_MANAGEMENT_URL); - cy.contains('Maintenance window is running'); - }); -}); + cy.contains('Maintenance window is running'); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts index 2d543bcfd35fc..b115d93dfd7e8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts @@ -47,7 +47,8 @@ import { import { ruleDetailsUrl } from '../../../../urls/navigation'; import { enablesRule, waitForPageToBeLoaded } from '../../../../tasks/rule_details'; -describe('Related integrations', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +describe('Related integrations', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const DATA_STREAM_NAME = 'logs-related-integrations-test'; const PREBUILT_RULE_NAME = 'Prebuilt rule with related integrations'; const RULE_RELATED_INTEGRATIONS: IntegrationDefinition[] = [ @@ -189,7 +190,10 @@ describe('Related integrations', { tags: ['@ess', '@brokenInServerless'] }, () = }); }); - describe('rule details', () => { + // TODO: https://github.com/elastic/kibana/issues/161540 + // Flaky in serverless tests + // @brokenInServerless tag is not working so a skip was needed + describe.skip('rule details', { tags: ['@brokenInServerless'] }, () => { beforeEach(() => { visitFirstInstalledPrebuiltRuleDetailsPage(); }); @@ -221,14 +225,29 @@ describe('Related integrations', { tags: ['@ess', '@brokenInServerless'] }, () = openTable(); filterBy(RELATED_INTEGRATION_FIELD); - RULE_RELATED_INTEGRATIONS.forEach((integration) => { - cy.contains( - FIELD(RELATED_INTEGRATION_FIELD), - `{"package":"${integration.package}"${ - integration.integration ? `,"integration":"${integration.integration}"` : '' - },"version":"${integration.version}"}` - ); - }); + cy.get(FIELD(RELATED_INTEGRATION_FIELD)) + .invoke('text') + .then((stringValue) => { + // Integrations are displayed in the flyout as a string with a format like so: + // '{"package":"aws","version":"1.17.0","integration":"unknown"}{"package":"mock","version":"1.1.0"}{"package":"system","version":"1.17.0"}' + // We need to parse it to an array of valid objects before we can compare it to the expected value + // Otherwise, the test might fail because of the order of the properties in the objects in the string + const jsonStringArray = stringValue.split('}{'); + + const validJsonStringArray = createValidJsonStringArray(jsonStringArray); + + const parsedIntegrations = validJsonStringArray.map((jsonString) => + JSON.parse(jsonString) + ); + + RULE_RELATED_INTEGRATIONS.forEach((integration) => { + expect(parsedIntegrations).to.deep.include({ + package: integration.package, + version: integration.version, + ...(integration.integration ? { integration: integration.integration } : {}), + }); + }); + }); }); }); }); @@ -407,3 +426,14 @@ const AWS_PACKAGE_POLICY: PackagePolicyWithoutAgentPolicyId = { }, }, }; + +const createValidJsonStringArray = (jsonStringArray: string[]) => + jsonStringArray.map((jsonString, index) => { + if (index === 0) { + return `${jsonString}}`; + } else if (index === jsonStringArray.length - 1) { + return `{${jsonString}`; + } else { + return `{${jsonString}}`; + } + }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts index 447f399c24a20..0771f384e132b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts @@ -6,7 +6,7 @@ */ import { - goToTheRuleDetailsOf, + goToRuleDetailsOf, expectManagementTableRules, selectAllRules, disableAutoRefresh, @@ -52,83 +52,89 @@ const EXPIRED_EXCEPTION_ITEM_NAME = 'Sample exception item'; const NON_EXPIRED_EXCEPTION_ITEM_NAME = 'Sample exception item with future expiration'; -describe('Detection rules, bulk duplicate', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - }); - - beforeEach(() => { - login(); - // Make sure persisted rules table state is cleared - resetRulesTableState(); - deleteAlertsAndRules(); - cy.task('esArchiverResetKibana'); - createRule( - getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1', enabled: false }) - ).then((response) => { - createRuleExceptionItem(response.body.id, [ - { - description: 'Exception item for rule default exception list', - entries: [ - { - field: 'user.name', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - name: EXPIRED_EXCEPTION_ITEM_NAME, - type: 'simple', - expire_time: expiredDate, - }, - { - description: 'Exception item for rule default exception list', - entries: [ - { - field: 'user.name', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - name: NON_EXPIRED_EXCEPTION_ITEM_NAME, - type: 'simple', - expire_time: futureDate, - }, - ]); +// TODO: https://github.com/elastic/kibana/issues/161540 +// Flaky on serverless +describe( + 'Detection rules, bulk duplicate', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + before(() => { + cleanKibana(); }); - visitSecurityDetectionRulesPage(); - disableAutoRefresh(); - }); - - it('Duplicates rules', () => { - selectAllRules(); - duplicateSelectedRulesWithoutExceptions(); - expectManagementTableRules([`${RULE_NAME} [Duplicate]`]); - }); + beforeEach(() => { + login(); + // Make sure persisted rules table state is cleared + resetRulesTableState(); + deleteAlertsAndRules(); + cy.task('esArchiverResetKibana'); + createRule( + getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1', enabled: false }) + ).then((response) => { + createRuleExceptionItem(response.body.id, [ + { + description: 'Exception item for rule default exception list', + entries: [ + { + field: 'user.name', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + name: EXPIRED_EXCEPTION_ITEM_NAME, + type: 'simple', + expire_time: expiredDate, + }, + { + description: 'Exception item for rule default exception list', + entries: [ + { + field: 'user.name', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + name: NON_EXPIRED_EXCEPTION_ITEM_NAME, + type: 'simple', + expire_time: futureDate, + }, + ]); + }); + + visitSecurityDetectionRulesPage(); + disableAutoRefresh(); + }); - describe('With exceptions', () => { - it('Duplicates rules with expired exceptions', () => { + it('Duplicates rules', () => { selectAllRules(); - duplicateSelectedRulesWithExceptions(); + duplicateSelectedRulesWithoutExceptions(); expectManagementTableRules([`${RULE_NAME} [Duplicate]`]); - goToTheRuleDetailsOf(`${RULE_NAME} [Duplicate]`); - goToExceptionsTab(); - assertExceptionItemsExists(EXCEPTION_CARD_ITEM_NAME, [NON_EXPIRED_EXCEPTION_ITEM_NAME]); - viewExpiredExceptionItems(); - assertExceptionItemsExists(EXCEPTION_CARD_ITEM_NAME, [EXPIRED_EXCEPTION_ITEM_NAME]); }); - it('Duplicates rules with exceptions, excluding expired exceptions', () => { - selectAllRules(); - duplicateSelectedRulesWithNonExpiredExceptions(); - expectManagementTableRules([`${RULE_NAME} [Duplicate]`]); - goToTheRuleDetailsOf(`${RULE_NAME} [Duplicate]`); - goToExceptionsTab(); - assertExceptionItemsExists(EXCEPTION_CARD_ITEM_NAME, [NON_EXPIRED_EXCEPTION_ITEM_NAME]); - viewExpiredExceptionItems(); - assertNumberOfExceptionItemsExists(0); + describe('With exceptions', () => { + it('Duplicates rules with expired exceptions', () => { + selectAllRules(); + duplicateSelectedRulesWithExceptions(); + expectManagementTableRules([`${RULE_NAME} [Duplicate]`]); + goToRuleDetailsOf(`${RULE_NAME} [Duplicate]`); + goToExceptionsTab(); + assertExceptionItemsExists(EXCEPTION_CARD_ITEM_NAME, [NON_EXPIRED_EXCEPTION_ITEM_NAME]); + viewExpiredExceptionItems(); + assertExceptionItemsExists(EXCEPTION_CARD_ITEM_NAME, [EXPIRED_EXCEPTION_ITEM_NAME]); + }); + + it('Duplicates rules with exceptions, excluding expired exceptions', () => { + selectAllRules(); + duplicateSelectedRulesWithNonExpiredExceptions(); + expectManagementTableRules([`${RULE_NAME} [Duplicate]`]); + goToRuleDetailsOf(`${RULE_NAME} [Duplicate]`); + goToExceptionsTab(); + assertExceptionItemsExists(EXCEPTION_CARD_ITEM_NAME, [NON_EXPIRED_EXCEPTION_ITEM_NAME]); + viewExpiredExceptionItems(); + assertNumberOfExceptionItemsExists(0); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts index 4b86a8344836c..e51bd3a73bde3 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts @@ -27,7 +27,7 @@ import { EUI_CHECKBOX, EUI_FILTER_SELECT_ITEM } from '../../../../../screens/com import { selectAllRules, - goToTheRuleDetailsOf, + goToRuleDetailsOf, testAllTagsBadges, testTagsBadge, testMultipleSelectedRulesLabel, @@ -114,550 +114,560 @@ const defaultRuleData = { timeline_id: '495ad7a7-316e-4544-8a0f-9c098daee76e', }; -describe('Detection rules, bulk edit', { tags: ['@ess', '@brokenInServerless'] }, () => { - before(() => { - cleanKibana(); - }); - beforeEach(() => { - login(); - // Make sure persisted rules table state is cleared - resetRulesTableState(); - deleteAlertsAndRules(); - preventPrebuiltRulesPackageInstallation(); // Make sure prebuilt rules aren't pulled from Fleet API - cy.task('esArchiverResetKibana'); - createRule(getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1', enabled: false })); - createRule( - getEqlRule({ ...defaultRuleData, rule_id: '2', name: 'New EQL Rule', enabled: false }) - ); - createRule( - getMachineLearningRule({ - name: 'New ML Rule Test', - tags: ['test-default-tag-1', 'test-default-tag-2'], - enabled: false, - }) - ); - createRule( - getNewThreatIndicatorRule({ - ...defaultRuleData, - rule_id: '4', - name: 'Threat Indicator Rule Test', - enabled: false, - }) - ); - createRule( - getNewThresholdRule({ - ...defaultRuleData, - rule_id: '5', - name: 'Threshold Rule', - enabled: false, - }) - ); - createRule( - getNewTermsRule({ ...defaultRuleData, rule_id: '6', name: 'New Terms Rule', enabled: false }) - ); - - visitSecurityDetectionRulesPage(); - disableAutoRefresh(); - }); - - describe('Prerequisites', () => { - const PREBUILT_RULES = [ - createRuleAssetSavedObject({ - name: 'Prebuilt rule 1', - rule_id: 'rule_1', - }), - createRuleAssetSavedObject({ - name: 'Prebuilt rule 2', - rule_id: 'rule_2', - }), - ]; - - it('No rules selected', () => { - openBulkActionsMenu(); - - // when no rule selected all bulk edit options should be disabled - cy.get(TAGS_RULE_BULK_MENU_ITEM).should('be.disabled'); - cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).should('be.disabled'); - cy.get(APPLY_TIMELINE_RULE_BULK_MENU_ITEM).should('be.disabled'); +// TODO: https://github.com/elastic/kibana/issues/161540 +describe( + 'Detection rules, bulk edit', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + before(() => { + cleanKibana(); }); + beforeEach(() => { + login(); + // Make sure persisted rules table state is cleared + resetRulesTableState(); + deleteAlertsAndRules(); + preventPrebuiltRulesPackageInstallation(); // Make sure prebuilt rules aren't pulled from Fleet API + cy.task('esArchiverResetKibana'); + createRule(getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1', enabled: false })); + createRule( + getEqlRule({ ...defaultRuleData, rule_id: '2', name: 'New EQL Rule', enabled: false }) + ); + createRule( + getMachineLearningRule({ + name: 'New ML Rule Test', + tags: ['test-default-tag-1', 'test-default-tag-2'], + enabled: false, + }) + ); + createRule( + getNewThreatIndicatorRule({ + ...defaultRuleData, + rule_id: '4', + name: 'Threat Indicator Rule Test', + enabled: false, + }) + ); + createRule( + getNewThresholdRule({ + ...defaultRuleData, + rule_id: '5', + name: 'Threshold Rule', + enabled: false, + }) + ); + createRule( + getNewTermsRule({ + ...defaultRuleData, + rule_id: '6', + name: 'New Terms Rule', + enabled: false, + }) + ); - it('Only prebuilt rules selected', () => { - createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES }); - - // select Elastic(prebuilt) rules, check if we can't proceed further, as Elastic rules are not editable - filterByElasticRules(); - selectAllRulesOnPage(); - clickApplyTimelineTemplatesMenuItem(); - - getRulesManagementTableRows().then((rows) => { - // check modal window for Elastic rule that can't be edited - checkPrebuiltRulesCannotBeModified(rows.length); + visitSecurityDetectionRulesPage(); + disableAutoRefresh(); + }); - // the confirm button closes modal - cy.get(MODAL_CONFIRMATION_BTN).should('have.text', 'Close').click(); - cy.get(MODAL_CONFIRMATION_BODY).should('not.exist'); + describe('Prerequisites', () => { + const PREBUILT_RULES = [ + createRuleAssetSavedObject({ + name: 'Prebuilt rule 1', + rule_id: 'rule_1', + }), + createRuleAssetSavedObject({ + name: 'Prebuilt rule 2', + rule_id: 'rule_2', + }), + ]; + + it('No rules selected', () => { + openBulkActionsMenu(); + + // when no rule selected all bulk edit options should be disabled + cy.get(TAGS_RULE_BULK_MENU_ITEM).should('be.disabled'); + cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).should('be.disabled'); + cy.get(APPLY_TIMELINE_RULE_BULK_MENU_ITEM).should('be.disabled'); }); - }); - it('Prebuilt and custom rules selected: user proceeds with custom rules editing', () => { - getRulesManagementTableRows().then((existedRulesRows) => { + it('Only prebuilt rules selected', () => { createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES }); - // modal window should show how many rules can be edit, how many not - selectAllRules(); - clickAddTagsMenuItem(); + // select Elastic(prebuilt) rules, check if we can't proceed further, as Elastic rules are not editable + filterByElasticRules(); + selectAllRulesOnPage(); + clickApplyTimelineTemplatesMenuItem(); - waitForMixedRulesBulkEditModal(existedRulesRows.length); + getRulesManagementTableRows().then((rows) => { + // check modal window for Elastic rule that can't be edited + checkPrebuiltRulesCannotBeModified(rows.length); - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - checkPrebuiltRulesCannotBeModified(availablePrebuiltRulesCount); + // the confirm button closes modal + cy.get(MODAL_CONFIRMATION_BTN).should('have.text', 'Close').click(); + cy.get(MODAL_CONFIRMATION_BODY).should('not.exist'); }); + }); - // user can proceed with custom rule editing - cy.get(MODAL_CONFIRMATION_BTN) - .should('have.text', `Edit ${existedRulesRows.length} custom rules`) - .click(); + it('Prebuilt and custom rules selected: user proceeds with custom rules editing', () => { + getRulesManagementTableRows().then((existedRulesRows) => { + createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES }); - // action should finish - typeTags(['test-tag']); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: existedRulesRows.length }); - }); - }); + // modal window should show how many rules can be edit, how many not + selectAllRules(); + clickAddTagsMenuItem(); - it('Prebuilt and custom rules selected: user cancels action', () => { - createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES }); + waitForMixedRulesBulkEditModal(existedRulesRows.length); - getRulesManagementTableRows().then((rows) => { - // modal window should show how many rules can be edit, how many not - selectAllRules(); - clickAddTagsMenuItem(); - waitForMixedRulesBulkEditModal(rows.length); + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + checkPrebuiltRulesCannotBeModified(availablePrebuiltRulesCount); + }); - checkPrebuiltRulesCannotBeModified(PREBUILT_RULES.length); + // user can proceed with custom rule editing + cy.get(MODAL_CONFIRMATION_BTN) + .should('have.text', `Edit ${existedRulesRows.length} custom rules`) + .click(); - // user cancels action and modal disappears - cancelConfirmationModal(); + // action should finish + typeTags(['test-tag']); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: existedRulesRows.length }); + }); }); - }); - it('should not lose rules selection after edit action', () => { - const rulesToUpdate = [RULE_NAME, 'New EQL Rule', 'New Terms Rule'] as const; - // Switch to 5 rules per page, to have few pages in pagination(ideal way to test auto refresh and selection of few items) - setRowsPerPageTo(5); - // and make the rules order isn't changing (set sorting by rule name) over time if rules are run - sortByTableColumn('Rule'); - selectRulesByName(rulesToUpdate); - - // open add tags form and add 2 new tags - openBulkEditAddTagsForm(); - typeTags(['new-tag-1']); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rulesToUpdate.length }); - - testMultipleSelectedRulesLabel(rulesToUpdate.length); - // check if first four(rulesCount) rules still selected and tags are updated - for (const ruleName of rulesToUpdate) { - getRuleRow(ruleName).find(EUI_CHECKBOX).should('be.checked'); - getRuleRow(ruleName) - .find(RULES_TAGS_POPOVER_BTN) - .each(($el) => { - testTagsBadge($el, prePopulatedTags.concat(['new-tag-1'])); - }); - } - }); - }); + it('Prebuilt and custom rules selected: user cancels action', () => { + createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES }); - describe('Tags actions', () => { - it('Display list of tags in tags select', () => { - selectAllRules(); + getRulesManagementTableRows().then((rows) => { + // modal window should show how many rules can be edit, how many not + selectAllRules(); + clickAddTagsMenuItem(); + waitForMixedRulesBulkEditModal(rows.length); - openBulkEditAddTagsForm(); - openTagsSelect(); + checkPrebuiltRulesCannotBeModified(PREBUILT_RULES.length); - cy.get(EUI_FILTER_SELECT_ITEM) - .should('have.length', prePopulatedTags.length) - .each(($el, index) => { - cy.wrap($el).should('have.text', prePopulatedTags[index]); + // user cancels action and modal disappears + cancelConfirmationModal(); }); - }); + }); - it('Add tags to custom rules', () => { - getRulesManagementTableRows().then((rows) => { - const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2']; - const resultingTags = [...prePopulatedTags, ...tagsToBeAdded]; + it('should not lose rules selection after edit action', () => { + const rulesToUpdate = [RULE_NAME, 'New EQL Rule', 'New Terms Rule'] as const; + // Switch to 5 rules per page, to have few pages in pagination(ideal way to test auto refresh and selection of few items) + setRowsPerPageTo(5); + // and make the rules order isn't changing (set sorting by rule name) over time if rules are run + sortByTableColumn('Rule'); + selectRulesByName(rulesToUpdate); - // check if only pre-populated tags exist in the tags filter - checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + // open add tags form and add 2 new tags + openBulkEditAddTagsForm(); + typeTags(['new-tag-1']); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rulesToUpdate.length }); + + testMultipleSelectedRulesLabel(rulesToUpdate.length); + // check if first four(rulesCount) rules still selected and tags are updated + for (const ruleName of rulesToUpdate) { + getRuleRow(ruleName).find(EUI_CHECKBOX).should('be.checked'); + getRuleRow(ruleName) + .find(RULES_TAGS_POPOVER_BTN) + .each(($el) => { + testTagsBadge($el, prePopulatedTags.concat(['new-tag-1'])); + }); + } + }); + }); + describe('Tags actions', () => { + it('Display list of tags in tags select', () => { selectAllRules(); - // open add tags form and add 2 new tags openBulkEditAddTagsForm(); - typeTags(tagsToBeAdded); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + openTagsSelect(); - // check if all rules have been updated with new tags - testAllTagsBadges(resultingTags); + cy.get(EUI_FILTER_SELECT_ITEM) + .should('have.length', prePopulatedTags.length) + .each(($el, index) => { + cy.wrap($el).should('have.text', prePopulatedTags[index]); + }); + }); - // check that new tags were added to tags filter - // tags in tags filter sorted alphabetically - const resultingTagsInFilter = [...resultingTags].sort(); - checkTagsInTagsFilter(resultingTagsInFilter, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + it('Add tags to custom rules', () => { + getRulesManagementTableRows().then((rows) => { + const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2']; + const resultingTags = [...prePopulatedTags, ...tagsToBeAdded]; + + // check if only pre-populated tags exist in the tags filter + checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + + selectAllRules(); + + // open add tags form and add 2 new tags + openBulkEditAddTagsForm(); + typeTags(tagsToBeAdded); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); + + // check if all rules have been updated with new tags + testAllTagsBadges(resultingTags); + + // check that new tags were added to tags filter + // tags in tags filter sorted alphabetically + const resultingTagsInFilter = [...resultingTags].sort(); + checkTagsInTagsFilter(resultingTagsInFilter, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + }); }); - }); - it('Display success toast after adding tags', () => { - getRulesManagementTableRows().then((rows) => { - const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2']; + it('Display success toast after adding tags', () => { + getRulesManagementTableRows().then((rows) => { + const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2']; - // check if only pre-populated tags exist in the tags filter - checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + // check if only pre-populated tags exist in the tags filter + checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); - selectAllRules(); + selectAllRules(); - // open add tags form and add 2 new tags - openBulkEditAddTagsForm(); - typeTags(tagsToBeAdded); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + // open add tags form and add 2 new tags + openBulkEditAddTagsForm(); + typeTags(tagsToBeAdded); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); + }); }); - }); - it('Overwrite tags in custom rules', () => { - getRulesManagementTableRows().then((rows) => { - const tagsToOverwrite = ['overwrite-tag-1']; + it('Overwrite tags in custom rules', () => { + getRulesManagementTableRows().then((rows) => { + const tagsToOverwrite = ['overwrite-tag-1']; - // check if only pre-populated tags exist in the tags filter - checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + // check if only pre-populated tags exist in the tags filter + checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); - selectAllRules(); + selectAllRules(); - // open add tags form, check overwrite tags and warning message, type tags - openBulkEditAddTagsForm(); - checkOverwriteTagsCheckbox(); + // open add tags form, check overwrite tags and warning message, type tags + openBulkEditAddTagsForm(); + checkOverwriteTagsCheckbox(); - cy.get(RULES_BULK_EDIT_TAGS_WARNING).should( - 'have.text', - `You’re about to overwrite tags for ${rows.length} selected rules, press Save to apply changes.` - ); + cy.get(RULES_BULK_EDIT_TAGS_WARNING).should( + 'have.text', + `You’re about to overwrite tags for ${rows.length} selected rules, press Save to apply changes.` + ); - typeTags(tagsToOverwrite); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + typeTags(tagsToOverwrite); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); - // check if all rules have been updated with new tags - testAllTagsBadges(tagsToOverwrite); + // check if all rules have been updated with new tags + testAllTagsBadges(tagsToOverwrite); - // check that only new tags are in the tag filter - checkTagsInTagsFilter(tagsToOverwrite, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + // check that only new tags are in the tag filter + checkTagsInTagsFilter(tagsToOverwrite, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + }); }); - }); - it('Delete tags from custom rules', () => { - getRulesManagementTableRows().then((rows) => { - const tagsToDelete = prePopulatedTags.slice(0, 1); - const resultingTags = prePopulatedTags.slice(1); + it('Delete tags from custom rules', () => { + getRulesManagementTableRows().then((rows) => { + const tagsToDelete = prePopulatedTags.slice(0, 1); + const resultingTags = prePopulatedTags.slice(1); - // check if only pre-populated tags exist in the tags filter - checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + // check if only pre-populated tags exist in the tags filter + checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); - selectAllRules(); + selectAllRules(); - // open add tags form, check overwrite tags, type tags - openBulkEditDeleteTagsForm(); - typeTags(tagsToDelete); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + // open add tags form, check overwrite tags, type tags + openBulkEditDeleteTagsForm(); + typeTags(tagsToDelete); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); - // check tags has been removed from all rules - testAllTagsBadges(resultingTags); + // check tags has been removed from all rules + testAllTagsBadges(resultingTags); - // check that tags were removed from the tag filter - checkTagsInTagsFilter(resultingTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + // check that tags were removed from the tag filter + checkTagsInTagsFilter(resultingTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT); + }); }); }); - }); - describe('Index patterns', () => { - it('Index pattern action applied to custom rules, including machine learning: user proceeds with edit of custom non machine learning rule', () => { - getRulesManagementTableRows().then((rows) => { - const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*']; - const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded]; + describe('Index patterns', () => { + it('Index pattern action applied to custom rules, including machine learning: user proceeds with edit of custom non machine learning rule', () => { + getRulesManagementTableRows().then((rows) => { + const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*']; + const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded]; + selectAllRules(); + clickAddIndexPatternsMenuItem(); + + // confirm editing custom rules, that are not Machine Learning + checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited); + cy.get(MODAL_CONFIRMATION_BTN).click(); + + typeIndexPatterns(indexPattersToBeAdded); + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ + updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited, + }); + + // check if rule has been updated + goToRuleDetailsOf(RULE_NAME); + hasIndexPatterns(resultingIndexPatterns.join('')); + }); + }); + + it('Index pattern action applied to custom rules, including machine learning: user cancels action', () => { selectAllRules(); clickAddIndexPatternsMenuItem(); // confirm editing custom rules, that are not Machine Learning checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited); - cy.get(MODAL_CONFIRMATION_BTN).click(); - - typeIndexPatterns(indexPattersToBeAdded); - submitBulkEditForm(); - - waitForBulkEditActionToFinish({ - updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited, - }); - // check if rule has been updated - goToTheRuleDetailsOf(RULE_NAME); - hasIndexPatterns(resultingIndexPatterns.join('')); + // user cancels action and modal disappears + cancelConfirmationModal(); }); - }); - it('Index pattern action applied to custom rules, including machine learning: user cancels action', () => { - selectAllRules(); - clickAddIndexPatternsMenuItem(); - - // confirm editing custom rules, that are not Machine Learning - checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited); + it('Add index patterns to custom rules', () => { + getRulesManagementTableRows().then((rows) => { + const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*']; + const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded]; + + // select only rules that are not ML + selectRulesByName([ + RULE_NAME, + 'New EQL Rule', + 'Threat Indicator Rule Test', + 'Threshold Rule', + 'New Terms Rule', + ]); + + openBulkEditAddIndexPatternsForm(); + typeIndexPatterns(indexPattersToBeAdded); + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ + updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited, + }); - // user cancels action and modal disappears - cancelConfirmationModal(); - }); + // check if rule has been updated + goToRuleDetailsOf(RULE_NAME); + hasIndexPatterns(resultingIndexPatterns.join('')); + }); + }); - it('Add index patterns to custom rules', () => { - getRulesManagementTableRows().then((rows) => { - const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*']; - const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded]; + it('Display success toast after editing the index pattern', () => { + getRulesManagementTableRows().then((rows) => { + const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*']; + + // select only rules that are not ML + selectRulesByName([ + RULE_NAME, + 'New EQL Rule', + 'Threat Indicator Rule Test', + 'Threshold Rule', + 'New Terms Rule', + ]); + + openBulkEditAddIndexPatternsForm(); + typeIndexPatterns(indexPattersToBeAdded); + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ + updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited, + }); + }); + }); - // select only rules that are not ML - selectRulesByName([ + it('Overwrite index patterns in custom rules', () => { + const rulesToSelect = [ RULE_NAME, 'New EQL Rule', 'Threat Indicator Rule Test', 'Threshold Rule', 'New Terms Rule', - ]); + ] as const; + const indexPattersToWrite = ['index-to-write-1-*', 'index-to-write-2-*']; + + // select only rules that are not ML + selectRulesByName(rulesToSelect); openBulkEditAddIndexPatternsForm(); - typeIndexPatterns(indexPattersToBeAdded); + + // check overwrite index patterns checkbox, ensure warning message is displayed and type index patterns + checkOverwriteIndexPatternsCheckbox(); + cy.get(RULES_BULK_EDIT_INDEX_PATTERNS_WARNING).should( + 'have.text', + `You’re about to overwrite index patterns for ${rulesToSelect.length} selected rules, press Save to apply changes.` + ); + + typeIndexPatterns(indexPattersToWrite); submitBulkEditForm(); - waitForBulkEditActionToFinish({ - updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited, - }); + waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length }); // check if rule has been updated - goToTheRuleDetailsOf(RULE_NAME); - hasIndexPatterns(resultingIndexPatterns.join('')); + goToRuleDetailsOf(RULE_NAME); + hasIndexPatterns(indexPattersToWrite.join('')); }); - }); - it('Display success toast after editing the index pattern', () => { - getRulesManagementTableRows().then((rows) => { - const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*']; - - // select only rules that are not ML - selectRulesByName([ + it('Delete index patterns from custom rules', () => { + const rulesToSelect = [ RULE_NAME, 'New EQL Rule', 'Threat Indicator Rule Test', 'Threshold Rule', 'New Terms Rule', - ]); - - openBulkEditAddIndexPatternsForm(); - typeIndexPatterns(indexPattersToBeAdded); - submitBulkEditForm(); + ] as const; + const indexPatternsToDelete = prePopulatedIndexPatterns.slice(0, 1); + const resultingIndexPatterns = prePopulatedIndexPatterns.slice(1); - waitForBulkEditActionToFinish({ - updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited, - }); - }); - }); + // select only not ML rules + selectRulesByName(rulesToSelect); - it('Overwrite index patterns in custom rules', () => { - const rulesToSelect = [ - RULE_NAME, - 'New EQL Rule', - 'Threat Indicator Rule Test', - 'Threshold Rule', - 'New Terms Rule', - ] as const; - const indexPattersToWrite = ['index-to-write-1-*', 'index-to-write-2-*']; - - // select only rules that are not ML - selectRulesByName(rulesToSelect); - - openBulkEditAddIndexPatternsForm(); - - // check overwrite index patterns checkbox, ensure warning message is displayed and type index patterns - checkOverwriteIndexPatternsCheckbox(); - cy.get(RULES_BULK_EDIT_INDEX_PATTERNS_WARNING).should( - 'have.text', - `You’re about to overwrite index patterns for ${rulesToSelect.length} selected rules, press Save to apply changes.` - ); - - typeIndexPatterns(indexPattersToWrite); - submitBulkEditForm(); - - waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length }); + openBulkEditDeleteIndexPatternsForm(); + typeIndexPatterns(indexPatternsToDelete); + submitBulkEditForm(); - // check if rule has been updated - goToTheRuleDetailsOf(RULE_NAME); - hasIndexPatterns(indexPattersToWrite.join('')); - }); + waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length }); - it('Delete index patterns from custom rules', () => { - const rulesToSelect = [ - RULE_NAME, - 'New EQL Rule', - 'Threat Indicator Rule Test', - 'Threshold Rule', - 'New Terms Rule', - ] as const; - const indexPatternsToDelete = prePopulatedIndexPatterns.slice(0, 1); - const resultingIndexPatterns = prePopulatedIndexPatterns.slice(1); - - // select only not ML rules - selectRulesByName(rulesToSelect); - - openBulkEditDeleteIndexPatternsForm(); - typeIndexPatterns(indexPatternsToDelete); - submitBulkEditForm(); - - waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length }); - - // check if rule has been updated - goToTheRuleDetailsOf(RULE_NAME); - hasIndexPatterns(resultingIndexPatterns.join('')); - }); + // check if rule has been updated + goToRuleDetailsOf(RULE_NAME); + hasIndexPatterns(resultingIndexPatterns.join('')); + }); - it('Delete all index patterns from custom rules', () => { - const rulesToSelect = [ - RULE_NAME, - 'New EQL Rule', - 'Threat Indicator Rule Test', - 'Threshold Rule', - 'New Terms Rule', - ] as const; + it('Delete all index patterns from custom rules', () => { + const rulesToSelect = [ + RULE_NAME, + 'New EQL Rule', + 'Threat Indicator Rule Test', + 'Threshold Rule', + 'New Terms Rule', + ] as const; - // select only rules that are not ML - selectRulesByName(rulesToSelect); + // select only rules that are not ML + selectRulesByName(rulesToSelect); - openBulkEditDeleteIndexPatternsForm(); - typeIndexPatterns(prePopulatedIndexPatterns); - submitBulkEditForm(); + openBulkEditDeleteIndexPatternsForm(); + typeIndexPatterns(prePopulatedIndexPatterns); + submitBulkEditForm(); - // error toast should be displayed that that rules edit failed - waitForBulkEditActionToFinish({ failedCount: rulesToSelect.length }); + // error toast should be displayed that that rules edit failed + waitForBulkEditActionToFinish({ failedCount: rulesToSelect.length }); - // on error toast button click display error that index patterns can't be empty - clickErrorToastBtn(); - cy.contains(MODAL_ERROR_BODY, "Index patterns can't be empty"); + // on error toast button click display error that index patterns can't be empty + clickErrorToastBtn(); + cy.contains(MODAL_ERROR_BODY, "Index patterns can't be empty"); + }); }); - }); - describe('Timeline templates', () => { - beforeEach(() => { - loadPrepackagedTimelineTemplates(); - }); + describe('Timeline templates', () => { + beforeEach(() => { + loadPrepackagedTimelineTemplates(); + }); - it('Apply timeline template to custom rules', () => { - getRulesManagementTableRows().then((rows) => { - const timelineTemplateName = 'Generic Endpoint Timeline'; + it('Apply timeline template to custom rules', () => { + getRulesManagementTableRows().then((rows) => { + const timelineTemplateName = 'Generic Endpoint Timeline'; - selectAllRules(); + selectAllRules(); - // open Timeline template form, check warning, select timeline template - clickApplyTimelineTemplatesMenuItem(); - cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING).contains( - `You're about to apply changes to ${rows.length} selected rules. If you previously applied Timeline templates to these rules, they will be overwritten or (if you select 'None') reset to none.` - ); - selectTimelineTemplate(timelineTemplateName); + // open Timeline template form, check warning, select timeline template + clickApplyTimelineTemplatesMenuItem(); + cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING).contains( + `You're about to apply changes to ${rows.length} selected rules. If you previously applied Timeline templates to these rules, they will be overwritten or (if you select 'None') reset to none.` + ); + selectTimelineTemplate(timelineTemplateName); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); - // check if timeline template has been updated to selected one - goToTheRuleDetailsOf(RULE_NAME); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', timelineTemplateName); + // check if timeline template has been updated to selected one + goToRuleDetailsOf(RULE_NAME); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', timelineTemplateName); + }); }); - }); - it('Reset timeline template to None for custom rules', () => { - getRulesManagementTableRows().then((rows) => { - const noneTimelineTemplate = 'None'; + it('Reset timeline template to None for custom rules', () => { + getRulesManagementTableRows().then((rows) => { + const noneTimelineTemplate = 'None'; - selectAllRules(); + selectAllRules(); - // open Timeline template form, submit form without picking timeline template as None is selected by default - clickApplyTimelineTemplatesMenuItem(); + // open Timeline template form, submit form without picking timeline template as None is selected by default + clickApplyTimelineTemplatesMenuItem(); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); - // check if timeline template has been updated to selected one, by opening rule that have had timeline prior to editing - goToTheRuleDetailsOf(RULE_NAME); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', noneTimelineTemplate); + // check if timeline template has been updated to selected one, by opening rule that have had timeline prior to editing + goToRuleDetailsOf(RULE_NAME); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', noneTimelineTemplate); + }); }); }); - }); - describe('Schedule', () => { - it('Default values are applied to bulk edit schedule fields', () => { - getRulesManagementTableRows().then((rows) => { - selectAllRules(); - clickUpdateScheduleMenuItem(); + describe('Schedule', () => { + it('Default values are applied to bulk edit schedule fields', () => { + getRulesManagementTableRows().then((rows) => { + selectAllRules(); + clickUpdateScheduleMenuItem(); - assertUpdateScheduleWarningExists(rows.length); + assertUpdateScheduleWarningExists(rows.length); - assertDefaultValuesAreAppliedToScheduleFields({ - interval: 5, - lookback: 1, + assertDefaultValuesAreAppliedToScheduleFields({ + interval: 5, + lookback: 1, + }); }); }); - }); - it('Updates schedule for custom rules', () => { - getRulesManagementTableRows().then((rows) => { - selectAllRules(); - clickUpdateScheduleMenuItem(); + it('Updates schedule for custom rules', () => { + getRulesManagementTableRows().then((rows) => { + selectAllRules(); + clickUpdateScheduleMenuItem(); - assertUpdateScheduleWarningExists(rows.length); + assertUpdateScheduleWarningExists(rows.length); - typeScheduleInterval('20'); - setScheduleIntervalTimeUnit('Hours'); + typeScheduleInterval('20'); + setScheduleIntervalTimeUnit('Hours'); - typeScheduleLookback('10'); - setScheduleLookbackTimeUnit('Minutes'); + typeScheduleLookback('10'); + setScheduleLookbackTimeUnit('Minutes'); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); - goToTheRuleDetailsOf(RULE_NAME); + goToRuleDetailsOf(RULE_NAME); - assertRuleScheduleValues({ - interval: '20h', - lookback: '10m', + assertRuleScheduleValues({ + interval: '20h', + lookback: '10m', + }); }); }); - }); - it('Validates invalid inputs when scheduling for custom rules', () => { - getRulesManagementTableRows().then((rows) => { - selectAllRules(); - clickUpdateScheduleMenuItem(); + it('Validates invalid inputs when scheduling for custom rules', () => { + getRulesManagementTableRows().then((rows) => { + selectAllRules(); + clickUpdateScheduleMenuItem(); - // Validate invalid values are corrected to minimumValue - for 0 and negative values - typeScheduleInterval('0'); - setScheduleIntervalTimeUnit('Hours'); + // Validate invalid values are corrected to minimumValue - for 0 and negative values + typeScheduleInterval('0'); + setScheduleIntervalTimeUnit('Hours'); - typeScheduleLookback('-5'); - setScheduleLookbackTimeUnit('Seconds'); + typeScheduleLookback('-5'); + setScheduleLookbackTimeUnit('Seconds'); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: rows.length }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: rows.length }); - goToTheRuleDetailsOf(RULE_NAME); + goToRuleDetailsOf(RULE_NAME); - assertRuleScheduleValues({ - interval: '1h', - lookback: '1s', + assertRuleScheduleValues({ + interval: '1h', + lookback: '1s', + }); }); }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts index 997ded389422e..a2575f825dd75 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts @@ -73,9 +73,10 @@ const ruleNameToAssert = 'Custom rule name with actions'; const expectedExistingSlackMessage = 'Existing slack action'; const expectedSlackMessage = 'Slack action test message'; +// TODO: https://github.com/elastic/kibana/issues/161540 describe( 'Detection rules, bulk edit of rule actions', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { cleanKibana(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts index 32e96dd8f3cfb..23e708a47462f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts @@ -13,8 +13,7 @@ import { import { DATA_VIEW_DETAILS, INDEX_PATTERNS_DETAILS } from '../../../../../screens/rule_details'; import { - goToRuleDetails, - goToTheRuleDetailsOf, + goToRuleDetailsOf, expectManagementTableRules, selectAllRules, getRulesManagementTableRows, @@ -53,10 +52,55 @@ const DATA_VIEW_ID = 'auditbeat'; const expectedIndexPatterns = ['index-1-*', 'index-2-*']; +// TODO: https://github.com/elastic/kibana/issues/161540 describe( 'Bulk editing index patterns of rules with a data view only', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { + const TESTED_CUSTOM_QUERY_RULE_DATA = getNewRule({ + index: undefined, + data_view_id: DATA_VIEW_ID, + rule_id: '1', + name: 'New Rule Test 1', + enabled: false, + }); + const TESTED_CUSTOM_QUERY_RULE_DATA_2 = getNewRule({ + index: undefined, + data_view_id: DATA_VIEW_ID, + saved_id: 'mocked', + rule_id: '6', + name: 'New Rule Test 2', + enabled: false, + }); + const TESTED_EQL_RULE_DATA = getEqlRule({ + index: undefined, + data_view_id: DATA_VIEW_ID, + rule_id: '2', + name: 'New EQL Rule', + enabled: false, + }); + const TESTED_THREAT_INDICATOR_RULE_DATA = getNewThreatIndicatorRule({ + index: undefined, + data_view_id: DATA_VIEW_ID, + rule_id: '3', + name: 'Threat Indicator Rule Test', + enabled: false, + }); + const TESTED_THRESHOLD_RULE_DATA = getNewThresholdRule({ + index: undefined, + data_view_id: DATA_VIEW_ID, + rule_id: '4', + name: 'Threshold Rule', + enabled: false, + }); + const TESTED_TERMS_RULE_DATA = getNewTermsRule({ + index: undefined, + data_view_id: DATA_VIEW_ID, + rule_id: '5', + name: 'New Terms Rule', + enabled: false, + }); + before(() => { cleanKibana(); }); @@ -68,72 +112,23 @@ describe( postDataView(DATA_VIEW_ID); - createRule( - getNewRule({ - index: undefined, - data_view_id: DATA_VIEW_ID, - rule_id: '1', - name: 'New Rule Test 1', - enabled: false, - }) - ); - createRule( - getEqlRule({ - index: undefined, - data_view_id: DATA_VIEW_ID, - rule_id: '2', - name: 'New EQL Rule', - enabled: false, - }) - ); - createRule( - getNewThreatIndicatorRule({ - index: undefined, - data_view_id: DATA_VIEW_ID, - rule_id: '3', - name: 'Threat Indicator Rule Test', - enabled: false, - }) - ); - createRule( - getNewThresholdRule({ - index: undefined, - data_view_id: DATA_VIEW_ID, - rule_id: '4', - name: 'Threshold Rule', - enabled: false, - }) - ); - createRule( - getNewTermsRule({ - index: undefined, - data_view_id: DATA_VIEW_ID, - rule_id: '5', - name: 'New Terms Rule', - enabled: false, - }) - ); - createRule( - getNewRule({ - index: undefined, - data_view_id: DATA_VIEW_ID, - saved_id: 'mocked', - rule_id: '6', - name: 'New Rule Test 2', - enabled: false, - }) - ); + createRule(TESTED_CUSTOM_QUERY_RULE_DATA); + createRule(TESTED_EQL_RULE_DATA); + createRule(TESTED_THREAT_INDICATOR_RULE_DATA); + createRule(TESTED_THRESHOLD_RULE_DATA); + createRule(TESTED_TERMS_RULE_DATA); + createRule(TESTED_CUSTOM_QUERY_RULE_DATA_2); visitSecurityDetectionRulesPage(); disableAutoRefresh(); expectManagementTableRules([ - 'New Rule Test 1', - 'New EQL Rule', - 'Threat Indicator Rule Test', - 'Threshold Rule', - 'New Terms Rule', - 'New Rule Test 2', + TESTED_CUSTOM_QUERY_RULE_DATA.name, + TESTED_EQL_RULE_DATA.name, + TESTED_THREAT_INDICATOR_RULE_DATA.name, + TESTED_THRESHOLD_RULE_DATA.name, + TESTED_TERMS_RULE_DATA.name, + TESTED_CUSTOM_QUERY_RULE_DATA_2.name, ]); }); @@ -151,7 +146,7 @@ describe( }); // check if rule still has data view and index patterns field does not exist - goToRuleDetails(); + goToRuleDetailsOf(TESTED_CUSTOM_QUERY_RULE_DATA.name); getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID); assertDetailsNotExist(INDEX_PATTERNS_DETAILS); }); @@ -174,7 +169,7 @@ describe( waitForBulkEditActionToFinish({ updatedCount: rows.length }); // check if rule has been updated with index patterns and data view does not exist - goToRuleDetails(); + goToRuleDetailsOf(TESTED_CUSTOM_QUERY_RULE_DATA.name); hasIndexPatterns(expectedIndexPatterns.join('')); assertDetailsNotExist(DATA_VIEW_DETAILS); }); @@ -195,7 +190,7 @@ describe( }); // check if rule still has data view and index patterns field does not exist - goToRuleDetails(); + goToRuleDetailsOf(TESTED_CUSTOM_QUERY_RULE_DATA.name); getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID); assertDetailsNotExist(INDEX_PATTERNS_DETAILS); }); @@ -215,7 +210,7 @@ describe( waitForBulkEditActionToFinish({ updatedCount: rows.length }); // check if rule has been overwritten with index patterns and data view does not exist - goToRuleDetails(); + goToRuleDetailsOf(TESTED_CUSTOM_QUERY_RULE_DATA.name); hasIndexPatterns(expectedIndexPatterns.join('')); assertDetailsNotExist(DATA_VIEW_DETAILS); }); @@ -239,7 +234,7 @@ describe( }); // check if rule still has data view and index patterns field does not exist - goToRuleDetails(); + goToRuleDetailsOf(TESTED_CUSTOM_QUERY_RULE_DATA.name); getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID); }); }); @@ -250,6 +245,18 @@ describe( 'Bulk editing index patterns of rules with index patterns and rules with a data view', { tags: ['@ess', '@brokenInServerless'] }, () => { + const TESTED_CUSTOM_QUERY_RULE_DATA_WITH_DATAVIEW = getNewRule({ + name: 'with dataview', + index: [], + data_view_id: DATA_VIEW_ID, + rule_id: '1', + }); + const TESTED_CUSTOM_QUERY_RULE_DATA_WITHOUT_DATAVIEW = getNewRule({ + name: 'no data view', + index: ['test-index-1-*'], + rule_id: '2', + }); + before(() => { cleanKibana(); }); @@ -261,10 +268,8 @@ describe( postDataView(DATA_VIEW_ID); - createRule( - getNewRule({ name: 'with dataview', index: [], data_view_id: DATA_VIEW_ID, rule_id: '1' }) - ); - createRule(getNewRule({ name: 'no data view', index: ['test-index-1-*'], rule_id: '2' })); + createRule(TESTED_CUSTOM_QUERY_RULE_DATA_WITH_DATAVIEW); + createRule(TESTED_CUSTOM_QUERY_RULE_DATA_WITHOUT_DATAVIEW); visitSecurityDetectionRulesPage(); disableAutoRefresh(); @@ -286,7 +291,7 @@ describe( }); // check if rule still has data view and index patterns field does not exist - goToTheRuleDetailsOf('with dataview'); + goToRuleDetailsOf(TESTED_CUSTOM_QUERY_RULE_DATA_WITH_DATAVIEW.name); getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID); assertDetailsNotExist(INDEX_PATTERNS_DETAILS); }); @@ -304,7 +309,7 @@ describe( }); // check if rule still has data view and index patterns field does not exist - goToRuleDetails(); + goToRuleDetailsOf(TESTED_CUSTOM_QUERY_RULE_DATA_WITH_DATAVIEW.name); assertDetailsNotExist(DATA_VIEW_DETAILS); }); } diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts index a9c6d6d693129..050c944c42d2d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts @@ -55,7 +55,8 @@ const prebuiltRules = Array.from(Array(7)).map((_, i) => { }); }); -describe('Export rules', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +describe('Export rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const downloadsFolder = Cypress.config('downloadsFolder'); before(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts index f42dabbbc030b..147148b724d7e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts @@ -17,7 +17,8 @@ import { login, visitWithoutDateRange } from '../../../../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../../urls/navigation'; const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson'; -describe('Import rules', { tags: ['@ess', '@brokenInServerless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +describe('Import rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts index 3771d4a5858ac..b5121bda97b0e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts @@ -47,7 +47,9 @@ import { TOOLTIP } from '../../../../../screens/common'; const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson'; -describe('rule snoozing', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +// Flaky in serverless tests +describe('rule snoozing', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts index 4d38fbda5525b..35e6e596131ec 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts @@ -22,6 +22,7 @@ import { expectNumberOfRules, selectRulesByName, getRuleRow, + setRulesTableAutoRefreshIntervalSetting, } from '../../../../tasks/alerts_detection_rules'; import { login, visit, visitWithoutDateRange } from '../../../../tasks/login'; @@ -30,89 +31,139 @@ import { createRule } from '../../../../tasks/api_calls/rules'; import { cleanKibana } from '../../../../tasks/common'; import { getNewRule } from '../../../../objects/rule'; -const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; -const NUM_OF_TEST_RULES = 6; +const RULES_TABLE_REFRESH_INTERVAL_MS = 60000; -describe('Rules table: auto-refresh', { tags: ['@ess', '@brokenInServerless'] }, () => { - before(() => { - cleanKibana(); - login(); +// TODO: https://github.com/elastic/kibana/issues/161540 +describe( + 'Rules table: auto-refresh', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + before(() => { + cleanKibana(); + login(); - for (let i = 1; i <= NUM_OF_TEST_RULES; ++i) { - createRule(getNewRule({ name: `Test rule ${i}`, rule_id: `${i}`, enabled: false })); - } - }); + setRulesTableAutoRefreshIntervalSetting({ + enabled: true, + refreshInterval: RULES_TABLE_REFRESH_INTERVAL_MS, + }); + createRule(getNewRule({ name: 'Test rule 1', rule_id: '1', enabled: false })); + }); - beforeEach(() => { - login(); - }); + beforeEach(() => { + login(); + }); - it('Auto refreshes rules', () => { - mockGlobalClock(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + it('gets deactivated when any rule selected and activated after rules unselected', () => { + visit(DETECTIONS_RULE_MANAGEMENT_URL); - expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, 1); - // // mock 1 minute passing to make sure refresh is conducted - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); - cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('be.visible'); + // check refresh settings if it's enabled before selecting + expectAutoRefreshIsEnabled(); - cy.contains(REFRESH_RULES_STATUS, 'Updated now'); - }); + selectAllRules(); - it('should prevent table from rules refetch if any rule selected', () => { - mockGlobalClock(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + // auto refresh should be deactivated (which means disabled without an ability to enable it) after rules selected + expectAutoRefreshIsDeactivated(); - expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); + clearAllRuleSelection(); - selectRulesByName(['Test rule 1']); + // after all rules unselected, auto refresh should be reset to its previous state + expectAutoRefreshIsEnabled(); + }); - // mock 1 minute passing to make sure refresh is not conducted - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); - cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + describe('when enabled', () => { + beforeEach(() => { + mockGlobalClock(); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - // ensure rule is still selected - getRuleRow('Test rule 1').find(EUI_CHECKBOX).should('be.checked'); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, 1); + }); - cy.get(REFRESH_RULES_STATUS).should('have.not.text', 'Updated now'); - }); + it('refreshes rules after refresh interval has passed', () => { + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + cy.tick(RULES_TABLE_REFRESH_INTERVAL_MS); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('be.visible'); - it('should disable auto refresh when any rule selected and enable it after rules unselected', () => { - visit(DETECTIONS_RULE_MANAGEMENT_URL); + cy.contains(REFRESH_RULES_STATUS, 'Updated now'); + }); - expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); + it('refreshes rules on window focus', () => { + cy.tick(RULES_TABLE_REFRESH_INTERVAL_MS / 2); - // check refresh settings if it's enabled before selecting - expectAutoRefreshIsEnabled(); + cy.window().trigger('blur'); + cy.window().trigger('focus'); - selectAllRules(); + cy.contains(REFRESH_RULES_STATUS, 'Updated now'); + }); + }); - // auto refresh should be deactivated (which means disabled without an ability to enable it) after rules selected - expectAutoRefreshIsDeactivated(); + describe('when disabled', () => { + beforeEach(() => { + mockGlobalClock(); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, 1); + }); - clearAllRuleSelection(); + it('does NOT refresh rules after refresh interval has passed', () => { + disableAutoRefresh(); + cy.tick(RULES_TABLE_REFRESH_INTERVAL_MS * 2); // Make sure enough time has passed to verify auto-refresh doesn't happen - // after all rules unselected, auto refresh should be reset to its previous state - expectAutoRefreshIsEnabled(); - }); + cy.contains(REFRESH_RULES_STATUS, 'Updated 2 minutes ago'); + }); - it('should not enable auto refresh after rules were unselected if auto refresh was disabled', () => { - visit(DETECTIONS_RULE_MANAGEMENT_URL); + it('does NOT refresh rules on window focus', () => { + disableAutoRefresh(); + cy.tick(RULES_TABLE_REFRESH_INTERVAL_MS * 2); // Make sure enough time has passed to verify auto-refresh doesn't happen - expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); + cy.window().trigger('blur'); + cy.window().trigger('focus'); - disableAutoRefresh(); + // We need to make sure window focus event doesn't cause refetching. Without some delay + // the following expectations always pass even. It happens since 'focus' event gets handled + // in an async way so the status text is updated with some delay. + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(1000); - selectAllRules(); + // By using a custom timeout make sure it doesn't wait too long due to global timeout configuration + // so the expected text appears after a refresh and the test passes while it shouldn't. + cy.contains(REFRESH_RULES_STATUS, 'Updated 2 minutes ago', { timeout: 10000 }); + }); - expectAutoRefreshIsDeactivated(); + it('does NOT get enabled after rules were unselected', () => { + disableAutoRefresh(); + cy.tick(RULES_TABLE_REFRESH_INTERVAL_MS * 2); // Make sure enough time has passed to verify auto-refresh doesn't happen - clearAllRuleSelection(); + selectAllRules(); - // after all rules unselected, auto refresh should still be disabled - expectAutoRefreshIsDisabled(); - }); -}); + expectAutoRefreshIsDeactivated(); + + clearAllRuleSelection(); + + // after all rules unselected, auto refresh should still be disabled + expectAutoRefreshIsDisabled(); + }); + }); + + describe('when one rule is selected', () => { + it('does NOT refresh after refresh interval has passed', () => { + mockGlobalClock(); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + + expectNumberOfRules(RULES_MANAGEMENT_TABLE, 1); + + selectRulesByName(['Test rule 1']); + + // mock 1 minute passing to make sure refresh is not conducted + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + cy.tick(RULES_TABLE_REFRESH_INTERVAL_MS * 2); // Make sure enough time has passed + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + + // ensure rule is still selected + getRuleRow('Test rule 1').find(EUI_CHECKBOX).should('be.checked'); + + cy.get(REFRESH_RULES_STATUS).should('have.not.text', 'Updated now'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts index eaa67b859b6c0..d6cf2c415297c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts @@ -28,7 +28,9 @@ import { import { disableAutoRefresh } from '../../../../tasks/alerts_detection_rules'; import { getNewRule } from '../../../../objects/rule'; -describe('Rules table: filtering', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +// Flaky in serverless tests +describe('Rules table: filtering', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { cleanKibana(); }); @@ -41,8 +43,11 @@ describe('Rules table: filtering', { tags: ['@ess', '@serverless'] }, () => { cy.task('esArchiverResetKibana'); }); - describe('Last response filter', () => { - it('Filters rules by last response', function () { + // TODO: https://github.com/elastic/kibana/issues/161540 + describe.skip('Last response filter', () => { + // Flaky in serverless tests + // @brokenInServerless tag is not working so a skip was needed + it('Filters rules by last response', { tags: ['@brokenInServerless'] }, function () { deleteIndex('test_index'); createIndex('test_index', { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts index d4465760ee97f..a568bbefe7fae 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts @@ -12,7 +12,9 @@ import { cleanKibana, deleteAlertsAndRules } from '../../../../tasks/common'; import { login, visitWithoutDateRange } from '../../../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation'; -describe('Rules table: links', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +// Flaky in serverless tests +describe('Rules table: links', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts index 7bf4d9e6eef24..c2b2d6e746a00 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts @@ -21,7 +21,6 @@ import { filterByCustomRules, filterBySearchTerm, filterByTags, - goToRuleDetails, expectFilterSearchTerm, expectFilterByTags, expectFilterByCustomRules, @@ -35,6 +34,7 @@ import { expectFilterByPrebuiltRules, expectFilterByEnabledRules, expectManagementTableRules, + goToRuleDetailsOf, } from '../../../../tasks/alerts_detection_rules'; import { createRule } from '../../../../tasks/api_calls/rules'; import { @@ -99,304 +99,315 @@ function expectDefaultRulesTableState(): void { expectTablePage(1); } -describe('Rules table: persistent state', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - createTestRules(); - }); - - beforeEach(() => { - login(); - resetRulesTableState(); - }); - - describe('while on a happy path', () => { - it('activates management tab by default', () => { - visit(SECURITY_DETECTIONS_RULES_URL); - - expectRulesManagementTab(); +// TODO: https://github.com/elastic/kibana/issues/161540 +describe( + 'Rules table: persistent state', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + before(() => { + cleanKibana(); + createTestRules(); }); - it('leads to displaying a rule according to the specified filters', () => { - visitRulesTableWithState({ - searchTerm: 'rule', - tags: ['tag-b'], - source: 'custom', - enabled: false, - field: 'name', - order: 'asc', - perPage: 5, - page: 2, - }); - - expectManagementTableRules(['rule 6']); - }); - - it('loads from the url', () => { - visitRulesTableWithState({ - searchTerm: 'rule', - tags: ['tag-b'], - source: 'custom', - enabled: false, - field: 'name', - order: 'asc', - perPage: 5, - page: 2, - }); - - expectRulesManagementTab(); - expectFilterSearchTerm('rule'); - expectFilterByTags(['tag-b']); - expectFilterByCustomRules(); - expectFilterByDisabledRules(); - expectTableSorting('Rule', 'asc'); - expectRowsPerPage(5); - expectTablePage(2); + beforeEach(() => { + login(); + resetRulesTableState(); }); - it('loads from the session storage', () => { - setStorageState({ - searchTerm: 'test', - tags: ['tag-a'], - source: 'prebuilt', - enabled: true, - field: 'severity', - order: 'desc', - perPage: 10, - }); - - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + // Flaky on serverless + // FLAKY: https://github.com/elastic/kibana/issues/165740 + describe( + 'while on a happy path', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + it('activates management tab by default', () => { + visit(SECURITY_DETECTIONS_RULES_URL); - expectRulesManagementTab(); - expectFilterSearchTerm('test'); - expectFilterByTags(['tag-a']); - expectFilterByPrebuiltRules(); - expectFilterByEnabledRules(); - expectTableSorting('Severity', 'desc'); - }); + expectRulesManagementTab(); + }); - it('prefers url state over storage state', () => { - setStorageState({ - searchTerm: 'test', - tags: ['tag-c'], - source: 'prebuilt', - enabled: true, - field: 'severity', - order: 'desc', - perPage: 10, - }); + it('leads to displaying a rule according to the specified filters', () => { + visitRulesTableWithState({ + searchTerm: 'rule', + tags: ['tag-b'], + source: 'custom', + enabled: false, + field: 'name', + order: 'asc', + perPage: 5, + page: 2, + }); + + expectManagementTableRules(['rule 6']); + }); - visitRulesTableWithState({ - searchTerm: 'rule', - tags: ['tag-b'], - source: 'custom', - enabled: false, - field: 'name', - order: 'asc', - perPage: 5, - page: 2, - }); + it('loads from the url', () => { + visitRulesTableWithState({ + searchTerm: 'rule', + tags: ['tag-b'], + source: 'custom', + enabled: false, + field: 'name', + order: 'asc', + perPage: 5, + page: 2, + }); - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(2); - }); - - describe('and on the rules management tab', () => { - beforeEach(() => { - login(); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); - }); + expectRulesManagementTab(); + expectFilterSearchTerm('rule'); + expectFilterByTags(['tag-b']); + expectFilterByCustomRules(); + expectFilterByDisabledRules(); + expectTableSorting('Rule', 'asc'); + expectRowsPerPage(5); + expectTablePage(2); + }); - it('persists after reloading the page', () => { - changeRulesTableState(); - goToTablePage(2); + it('loads from the session storage', () => { + setStorageState({ + searchTerm: 'test', + tags: ['tag-a'], + source: 'prebuilt', + enabled: true, + field: 'severity', + order: 'desc', + perPage: 10, + }); - cy.reload(); + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(2); - }); + expectRulesManagementTab(); + expectFilterSearchTerm('test'); + expectFilterByTags(['tag-a']); + expectFilterByPrebuiltRules(); + expectFilterByEnabledRules(); + expectTableSorting('Severity', 'desc'); + }); - it('persists after navigating back from a rule details page', () => { - changeRulesTableState(); - goToTablePage(2); + it('prefers url state over storage state', () => { + setStorageState({ + searchTerm: 'test', + tags: ['tag-c'], + source: 'prebuilt', + enabled: true, + field: 'severity', + order: 'desc', + perPage: 10, + }); + + visitRulesTableWithState({ + searchTerm: 'rule', + tags: ['tag-b'], + source: 'custom', + enabled: false, + field: 'name', + order: 'asc', + perPage: 5, + page: 2, + }); - goToRuleDetails(); - cy.go('back'); + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(2); + }); - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(2); - }); + describe('and on the rules management tab', () => { + beforeEach(() => { + login(); + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + }); - it('persists after navigating to another page inside Security Solution', () => { - changeRulesTableState(); - goToTablePage(2); + it('persists after reloading the page', () => { + changeRulesTableState(); + goToTablePage(2); - visit(DASHBOARDS_URL); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + cy.reload(); - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(1); - }); + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(2); + }); - it('persists after navigating to another page outside Security Solution', () => { - changeRulesTableState(); - goToTablePage(2); + it('persists after navigating back from a rule details page', () => { + changeRulesTableState(); + goToTablePage(2); - visit(KIBANA_HOME); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + goToRuleDetailsOf('rule 6'); + cy.go('back'); - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(1); - }); - }); + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(2); + }); - describe('and on the rules monitoring tab', () => { - beforeEach(() => { - login(); - visit(SECURITY_DETECTIONS_RULES_MONITORING_URL); - }); + it('persists after navigating to another page inside Security Solution', () => { + changeRulesTableState(); + goToTablePage(2); - it('persists the selected tab', () => { - changeRulesTableState(); + visit(DASHBOARDS_URL); + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); - cy.reload(); + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(1); + }); - expectRulesMonitoringTab(); - }); - }); - }); + it('persists after navigating to another page outside Security Solution', () => { + changeRulesTableState(); + goToTablePage(2); - describe('upon state format upgrade', async () => { - beforeEach(() => { - login(); - }); + visit(KIBANA_HOME); + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); - describe('and having state in the url', () => { - it('ignores unsupported state key', () => { - visitRulesTableWithState({ - someKey: 10, - searchTerm: 'rule', - tags: ['tag-b'], - source: 'custom', - enabled: false, - field: 'name', - order: 'asc', - perPage: 5, - page: 2, + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(1); + }); }); - expectRulesTableState(); - expectTablePage(2); - }); - }); + describe('and on the rules monitoring tab', () => { + beforeEach(() => { + login(); + visit(SECURITY_DETECTIONS_RULES_MONITORING_URL); + }); - describe('and having state in the session storage', () => { - it('ignores unsupported state key', () => { - setStorageState({ - someKey: 10, - searchTerm: 'rule', - tags: ['tag-b'], - source: 'custom', - enabled: false, - field: 'name', - order: 'asc', - perPage: 5, - }); + it('persists the selected tab', () => { + changeRulesTableState(); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + cy.reload(); - expectRulesTableState(); - expectTablePage(1); - }); - }); - }); + expectRulesMonitoringTab(); + }); + }); + } + ); - describe('when persisted state is partially unavailable', () => { - describe('and on the rules management tab', () => { + describe('upon state format upgrade', async () => { beforeEach(() => { login(); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); }); - it('persists after clearing the session storage', () => { - changeRulesTableState(); - goToTablePage(2); + describe('and having state in the url', () => { + it('ignores unsupported state key', () => { + visitRulesTableWithState({ + someKey: 10, + searchTerm: 'rule', + tags: ['tag-b'], + source: 'custom', + enabled: false, + field: 'name', + order: 'asc', + perPage: 5, + page: 2, + }); - cy.window().then((win) => { - win.sessionStorage.clear(); + expectRulesTableState(); + expectTablePage(2); }); - cy.reload(); - - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(2); }); - it('persists after clearing the url state', () => { - changeRulesTableState(); - goToTablePage(2); - - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + describe('and having state in the session storage', () => { + it('ignores unsupported state key', () => { + setStorageState({ + someKey: 10, + searchTerm: 'rule', + tags: ['tag-b'], + source: 'custom', + enabled: false, + field: 'name', + order: 'asc', + perPage: 5, + }); + + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(1); + expectRulesTableState(); + expectTablePage(1); + }); }); }); - }); - describe('when corrupted', () => { - describe('and on the rules management tab', () => { - beforeEach(() => { - login(); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); - }); + describe('when persisted state is partially unavailable', () => { + describe('and on the rules management tab', () => { + beforeEach(() => { + login(); + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + }); - it('persists after corrupting the session storage data', () => { - changeRulesTableState(); - goToTablePage(2); + it('persists after clearing the session storage', () => { + changeRulesTableState(); + goToTablePage(2); - cy.window().then((win) => { - win.sessionStorage.setItem('securitySolution.rulesTable', '!invalid'); + cy.window().then((win) => { + win.sessionStorage.clear(); + }); cy.reload(); expectRulesManagementTab(); expectRulesTableState(); expectTablePage(2); }); - }); - it('persists after corrupting the url param data', () => { - changeRulesTableState(); - goToTablePage(2); + it('persists after clearing the url state', () => { + changeRulesTableState(); + goToTablePage(2); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, { qs: { rulesTable: '(!invalid)' } }); + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); - expectRulesManagementTab(); - expectRulesTableState(); - expectTablePage(1); + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(1); + }); }); + }); + + describe('when corrupted', () => { + describe('and on the rules management tab', () => { + beforeEach(() => { + login(); + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); + }); - it('DOES NOT persist after corrupting the session storage and url param data', () => { - changeRulesTableState(); - goToTablePage(2); + it('persists after corrupting the session storage data', () => { + changeRulesTableState(); + goToTablePage(2); - visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, { - qs: { rulesTable: '(!invalid)' }, - onBeforeLoad: (win) => { + cy.window().then((win) => { win.sessionStorage.setItem('securitySolution.rulesTable', '!invalid'); - }, + cy.reload(); + + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(2); + }); }); - expectRulesManagementTab(); - expectDefaultRulesTableState(); + it('persists after corrupting the url param data', () => { + changeRulesTableState(); + goToTablePage(2); + + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, { qs: { rulesTable: '(!invalid)' } }); + + expectRulesManagementTab(); + expectRulesTableState(); + expectTablePage(1); + }); + + it('DOES NOT persist after corrupting the session storage and url param data', () => { + changeRulesTableState(); + goToTablePage(2); + + visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, { + qs: { rulesTable: '(!invalid)' }, + onBeforeLoad: (win) => { + win.sessionStorage.setItem('securitySolution.rulesTable', '!invalid'); + }, + }); + + expectRulesManagementTab(); + expectDefaultRulesTableState(); + }); }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts index ae28214315a40..383718c646f9d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts @@ -33,68 +33,74 @@ const RULE_2 = createRuleAssetSavedObject({ rule_id: 'rule_2', }); -describe('Rules table: selection', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - }); +// TODO: https://github.com/elastic/kibana/issues/161540 +// FLAKY: https://github.com/elastic/kibana/issues/165643 +describe.skip( + 'Rules table: selection', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + before(() => { + cleanKibana(); + }); - beforeEach(() => { - login(); - /* Create and install two mock rules */ - createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2] }); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - waitForPrebuiltDetectionRulesToBeLoaded(); - }); + beforeEach(() => { + login(); + /* Create and install two mock rules */ + createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2] }); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + waitForPrebuiltDetectionRulesToBeLoaded(); + }); - it('should correctly update the selection label when rules are individually selected and unselected', () => { - waitForPrebuiltDetectionRulesToBeLoaded(); + it('should correctly update the selection label when rules are individually selected and unselected', () => { + waitForPrebuiltDetectionRulesToBeLoaded(); - selectRulesByName(['Test rule 1', 'Test rule 2']); + selectRulesByName(['Test rule 1', 'Test rule 2']); - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '2'); + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '2'); - unselectRulesByName(['Test rule 1', 'Test rule 2']); + unselectRulesByName(['Test rule 1', 'Test rule 2']); - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); - }); + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); + }); - it('should correctly update the selection label when rules are bulk selected and then bulk un-selected', () => { - waitForPrebuiltDetectionRulesToBeLoaded(); + it('should correctly update the selection label when rules are bulk selected and then bulk un-selected', () => { + waitForPrebuiltDetectionRulesToBeLoaded(); - cy.get(SELECT_ALL_RULES_BTN).click(); + cy.get(SELECT_ALL_RULES_BTN).click(); - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); - }); + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); + }); - // Un-select all rules via the Bulk Selection button from the Utility bar - cy.get(SELECT_ALL_RULES_BTN).click(); + // Un-select all rules via the Bulk Selection button from the Utility bar + cy.get(SELECT_ALL_RULES_BTN).click(); - // Current selection should be 0 rules - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); - // Bulk selection button should be back to displaying all rules - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + // Current selection should be 0 rules + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); + // Bulk selection button should be back to displaying all rules + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + }); }); - }); - it('should correctly update the selection label when rules are bulk selected and then unselected via the table select all checkbox', () => { - waitForPrebuiltDetectionRulesToBeLoaded(); + it('should correctly update the selection label when rules are bulk selected and then unselected via the table select all checkbox', () => { + waitForPrebuiltDetectionRulesToBeLoaded(); - cy.get(SELECT_ALL_RULES_BTN).click(); + cy.get(SELECT_ALL_RULES_BTN).click(); - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); - }); + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); + }); - // Un-select all rules via the Un-select All checkbox from the table - cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); + // Un-select all rules via the Un-select All checkbox from the table + cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); - // Current selection should be 0 rules - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); - // Bulk selection button should be back to displaying all rules - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + // Current selection should be 0 rules + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); + // Bulk selection button should be back to displaying all rules + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts index 47a7aa80f1dd9..f132f89b248a1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts @@ -36,7 +36,8 @@ import { } from '../../../../tasks/table_pagination'; import { TABLE_FIRST_PAGE, TABLE_SECOND_PAGE } from '../../../../screens/table_pagination'; -describe('Rules table: sorting', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161540 +describe('Rules table: sorting', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts index 3aa68a9f8029a..7a2c6aeb35a22 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts @@ -22,6 +22,7 @@ import { exportValueList, waitForListsIndex, deleteValueLists, + KNOWN_VALUE_LIST_FILES, } from '../../../tasks/lists'; import { VALUE_LISTS_TABLE, @@ -34,11 +35,18 @@ const TEXT_LIST_FILE_NAME = 'value_list.txt'; const IPS_LIST_FILE_NAME = 'ip_list.txt'; const CIDRS_LIST_FILE_NAME = 'cidr_list.txt'; -describe('value lists', () => { - describe('management modal', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +// FLAKY: https://github.com/elastic/kibana/issues/165699 +describe('value lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { + // TODO: https://github.com/elastic/kibana/issues/161539 + describe('management modal', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { login(); - deleteValueLists([TEXT_LIST_FILE_NAME, IPS_LIST_FILE_NAME, CIDRS_LIST_FILE_NAME]); + deleteValueLists([ + KNOWN_VALUE_LIST_FILES.TEXT, + KNOWN_VALUE_LIST_FILES.IPs, + KNOWN_VALUE_LIST_FILES.CIDRs, + ]); createListsIndex(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); waitForListsIndex(); @@ -50,116 +58,122 @@ describe('value lists', () => { closeValueListsModal(); }); - describe('create list types', () => { + // TODO: https://github.com/elastic/kibana/issues/161539 + // Flaky in serverless tests + describe('create list types', { tags: ['@brokenInServerless'] }, () => { beforeEach(() => { openValueListsModal(); }); it('creates a "keyword" list from an uploaded file', () => { selectValueListType('keyword'); - selectValueListsFile(TEXT_LIST_FILE_NAME); + selectValueListsFile(KNOWN_VALUE_LIST_FILES.TEXT); uploadValueList(); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).to.contain(TEXT_LIST_FILE_NAME); + expect($row.text()).to.contain(KNOWN_VALUE_LIST_FILES.TEXT); expect($row.text()).to.contain('Keywords'); }); }); it('creates a "text" list from an uploaded file', () => { selectValueListType('text'); - selectValueListsFile(TEXT_LIST_FILE_NAME); + selectValueListsFile(KNOWN_VALUE_LIST_FILES.TEXT); uploadValueList(); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).to.contain(TEXT_LIST_FILE_NAME); + expect($row.text()).to.contain(KNOWN_VALUE_LIST_FILES.TEXT); expect($row.text()).to.contain('Text'); }); }); it('creates a "ip" list from an uploaded file', () => { selectValueListType('ip'); - selectValueListsFile(IPS_LIST_FILE_NAME); + selectValueListsFile(KNOWN_VALUE_LIST_FILES.IPs); uploadValueList(); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).to.contain(IPS_LIST_FILE_NAME); + expect($row.text()).to.contain(KNOWN_VALUE_LIST_FILES.IPs); expect($row.text()).to.contain('IP addresses'); }); }); it('creates a "ip_range" list from an uploaded file', () => { selectValueListType('ip_range'); - selectValueListsFile(CIDRS_LIST_FILE_NAME); + selectValueListsFile(KNOWN_VALUE_LIST_FILES.CIDRs); uploadValueList(); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).to.contain(CIDRS_LIST_FILE_NAME); + expect($row.text()).to.contain(KNOWN_VALUE_LIST_FILES.CIDRs); expect($row.text()).to.contain('IP ranges'); }); }); }); - describe('delete list types', () => { + // TODO: https://github.com/elastic/kibana/issues/161539 + // Flaky in serverless tests + describe('delete list types', { tags: ['@brokenInServerless'] }, () => { it('deletes a "keyword" list from an uploaded file', () => { - importValueList(TEXT_LIST_FILE_NAME, 'keyword'); + importValueList(KNOWN_VALUE_LIST_FILES.TEXT, 'keyword'); openValueListsModal(); - deleteValueListsFile(TEXT_LIST_FILE_NAME); + deleteValueListsFile(KNOWN_VALUE_LIST_FILES.TEXT); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).not.to.contain(TEXT_LIST_FILE_NAME); + expect($row.text()).not.to.contain(KNOWN_VALUE_LIST_FILES.TEXT); }); }); it('deletes a "text" list from an uploaded file', () => { - importValueList(TEXT_LIST_FILE_NAME, 'text'); + importValueList(KNOWN_VALUE_LIST_FILES.TEXT, 'text'); openValueListsModal(); - deleteValueListsFile(TEXT_LIST_FILE_NAME); + deleteValueListsFile(KNOWN_VALUE_LIST_FILES.TEXT); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).not.to.contain(TEXT_LIST_FILE_NAME); + expect($row.text()).not.to.contain(KNOWN_VALUE_LIST_FILES.TEXT); }); }); it('deletes a "ip" from an uploaded file', () => { - importValueList(IPS_LIST_FILE_NAME, 'ip'); + importValueList(KNOWN_VALUE_LIST_FILES.IPs, 'ip'); openValueListsModal(); - deleteValueListsFile(IPS_LIST_FILE_NAME); + deleteValueListsFile(KNOWN_VALUE_LIST_FILES.IPs); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).not.to.contain(IPS_LIST_FILE_NAME); + expect($row.text()).not.to.contain(KNOWN_VALUE_LIST_FILES.IPs); }); }); it('deletes a "ip_range" from an uploaded file', () => { - importValueList(CIDRS_LIST_FILE_NAME, 'ip_range', ['192.168.100.0']); + importValueList(KNOWN_VALUE_LIST_FILES.CIDRs, 'ip_range', ['192.168.100.0']); openValueListsModal(); - deleteValueListsFile(CIDRS_LIST_FILE_NAME); + deleteValueListsFile(KNOWN_VALUE_LIST_FILES.CIDRs); cy.get(VALUE_LISTS_TABLE) .find(VALUE_LISTS_ROW) .should(($row) => { - expect($row.text()).not.to.contain(CIDRS_LIST_FILE_NAME); + expect($row.text()).not.to.contain(KNOWN_VALUE_LIST_FILES.CIDRs); }); }); }); - describe('export list types', () => { + // TODO: https://github.com/elastic/kibana/issues/161539 + // Flaky in serverless tests + describe('export list types', { tags: ['@brokenInServerless'] }, () => { it('exports a "keyword" list from an uploaded file', () => { - cy.intercept('POST', `/api/lists/items/_export?list_id=${TEXT_LIST_FILE_NAME}`).as( + cy.intercept('POST', `/api/lists/items/_export?list_id=${KNOWN_VALUE_LIST_FILES.TEXT}`).as( 'exportList' ); - importValueList(TEXT_LIST_FILE_NAME, 'keyword'); + importValueList(KNOWN_VALUE_LIST_FILES.TEXT, 'keyword'); // Importing value lists includes bulk creation of list items with refresh=wait_for // While it should wait for data update and return after that it's not always a case with bulk operations. @@ -171,7 +185,7 @@ describe('value lists', () => { exportValueList(); cy.wait('@exportList').then(({ response }) => { - cy.fixture(TEXT_LIST_FILE_NAME).then((list: string) => { + cy.fixture(KNOWN_VALUE_LIST_FILES.TEXT).then((list: string) => { const [lineOne, lineTwo] = list.split('\n'); expect(response?.body).to.contain(lineOne); expect(response?.body).to.contain(lineTwo); @@ -180,10 +194,10 @@ describe('value lists', () => { }); it('exports a "text" list from an uploaded file', () => { - cy.intercept('POST', `/api/lists/items/_export?list_id=${TEXT_LIST_FILE_NAME}`).as( + cy.intercept('POST', `/api/lists/items/_export?list_id=${KNOWN_VALUE_LIST_FILES.TEXT}`).as( 'exportList' ); - importValueList(TEXT_LIST_FILE_NAME, 'text'); + importValueList(KNOWN_VALUE_LIST_FILES.TEXT, 'text'); // Importing value lists includes bulk creation of list items with refresh=wait_for // While it should wait for data update and return after that it's not always a case with bulk operations. @@ -195,7 +209,7 @@ describe('value lists', () => { exportValueList(); cy.wait('@exportList').then(({ response }) => { - cy.fixture(TEXT_LIST_FILE_NAME).then((list: string) => { + cy.fixture(KNOWN_VALUE_LIST_FILES.TEXT).then((list: string) => { const [lineOne, lineTwo] = list.split('\n'); expect(response?.body).to.contain(lineOne); expect(response?.body).to.contain(lineTwo); @@ -204,10 +218,10 @@ describe('value lists', () => { }); it('exports a "ip" list from an uploaded file', () => { - cy.intercept('POST', `/api/lists/items/_export?list_id=${IPS_LIST_FILE_NAME}`).as( + cy.intercept('POST', `/api/lists/items/_export?list_id=${KNOWN_VALUE_LIST_FILES.IPs}`).as( 'exportList' ); - importValueList(IPS_LIST_FILE_NAME, 'ip'); + importValueList(KNOWN_VALUE_LIST_FILES.IPs, 'ip'); // Importing value lists includes bulk creation of list items with refresh=wait_for // While it should wait for data update and return after that it's not always a case with bulk operations. @@ -218,7 +232,7 @@ describe('value lists', () => { openValueListsModal(); exportValueList(); cy.wait('@exportList').then(({ response }) => { - cy.fixture(IPS_LIST_FILE_NAME).then((list: string) => { + cy.fixture(KNOWN_VALUE_LIST_FILES.IPs).then((list: string) => { const [lineOne, lineTwo] = list.split('\n'); expect(response?.body).to.contain(lineOne); expect(response?.body).to.contain(lineTwo); @@ -227,10 +241,10 @@ describe('value lists', () => { }); it('exports a "ip_range" list from an uploaded file', () => { - cy.intercept('POST', `/api/lists/items/_export?list_id=${CIDRS_LIST_FILE_NAME}`).as( + cy.intercept('POST', `/api/lists/items/_export?list_id=${KNOWN_VALUE_LIST_FILES.CIDRs}`).as( 'exportList' ); - importValueList(CIDRS_LIST_FILE_NAME, 'ip_range', ['192.168.100.0']); + importValueList(KNOWN_VALUE_LIST_FILES.CIDRs, 'ip_range', ['192.168.100.0']); // Importing value lists includes bulk creation of list items with refresh=wait_for // While it should wait for data update and return after that it's not always a case with bulk operations. @@ -241,7 +255,7 @@ describe('value lists', () => { openValueListsModal(); exportValueList(); cy.wait('@exportList').then(({ response }) => { - cy.fixture(CIDRS_LIST_FILE_NAME).then((list: string) => { + cy.fixture(KNOWN_VALUE_LIST_FILES.CIDRs).then((list: string) => { const [lineOne] = list.split('\n'); expect(response?.body).to.contain(lineOne); }); @@ -250,11 +264,17 @@ describe('value lists', () => { }); }); - describe('user with restricted access role', { tags: '@ess' }, () => { - it('Does not allow a t1 analyst user to upload a value list', () => { - login(ROLES.t1_analyst); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL, ROLES.t1_analyst); - cy.get(VALUE_LISTS_MODAL_ACTIVATOR).should('have.attr', 'disabled'); - }); - }); + // TODO: https://github.com/elastic/kibana/issues/164451 We should find a way to make this spec work in Serverless + // TODO: https://github.com/elastic/kibana/issues/161539 + describe( + 'user with restricted access role', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + it('Does not allow a t1 analyst user to upload a value list', () => { + login(ROLES.t1_analyst); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL, ROLES.t1_analyst); + cy.get(VALUE_LISTS_MODAL_ACTIVATOR).should('have.attr', 'disabled'); + }); + } + ); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts index 36bc18210863f..fca7699af7843 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts @@ -39,13 +39,14 @@ import { previewErrorButtonClick, } from '../../tasks/entity_analytics'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Entity analytics management page', { env: { ftrConfig: { enableExperimental: ['riskScoringRoutesEnabled', 'riskScoringPersistence'] }, }, - tags: ['@ess', '@brokenInServerless'], + tags: ['@ess', '@serverless', '@brokenInServerless'], }, () => { before(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts index e9c07294e62d3..184152c8c5bfd 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts @@ -15,10 +15,9 @@ import { } from '../../../tasks/alerts'; import { login, visitWithoutDateRange } from '../../../tasks/login'; import { getEndpointRule } from '../../../objects/rule'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { createRule } from '../../../tasks/api_calls/rules'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; +import { ruleDetailsUrl } from '../../../urls/navigation'; import { addExceptionEntryFieldValueAndSelectSuggestion, addExceptionEntryFieldValueValue, @@ -37,10 +36,11 @@ import { } from '../../../screens/exceptions'; import { goToEndpointExceptionsTab, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; +// TODO: https://github.com/elastic/kibana/issues/161539 // See https://github.com/elastic/kibana/issues/163967 describe.skip( 'Endpoint Exceptions workflows from Alert', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const ITEM_NAME = 'Sample Exception List Item'; const ITEM_NAME_EDIT = 'Sample Exception List Item'; @@ -51,10 +51,12 @@ describe.skip( cy.task('esArchiverResetKibana'); login(); deleteAlertsAndRules(); + cy.task('esArchiverLoad', { archiveName: 'endpoint' }); - createRule(getEndpointRule()); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); + createRule(getEndpointRule()).then((rule) => + visitWithoutDateRange(ruleDetailsUrl(rule.body.id)) + ); + waitForTheRuleToBeExecuted(); waitForAlertsToPopulate(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts index 14d002b96b7b4..41a4dd607ffe6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts @@ -7,7 +7,6 @@ import { LOADING_INDICATOR } from '../../../../screens/security_header'; import { getEndpointRule } from '../../../../objects/rule'; import { createRule } from '../../../../tasks/api_calls/rules'; -import { goToRuleDetails } from '../../../../tasks/alerts_detection_rules'; import { addExceptionFromFirstAlert, expandFirstAlert, @@ -27,7 +26,7 @@ import { import { login, visitWithoutDateRange } from '../../../../tasks/login'; import { goToExceptionsTab } from '../../../../tasks/rule_details'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation'; +import { ruleDetailsUrl } from '../../../../urls/navigation'; import { deleteAlertsAndRules } from '../../../../tasks/common'; import { ADD_AND_BTN, @@ -38,10 +37,11 @@ import { } from '../../../../screens/exceptions'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +// TODO: https://github.com/elastic/kibana/issues/161539 // See https://github.com/elastic/kibana/issues/163967 describe.skip( 'Auto populate exception with Alert data', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const ITEM_NAME = 'Sample Exception Item'; const ITEM_NAME_EDIT = 'Sample Exception Item Edit'; @@ -52,9 +52,10 @@ describe.skip( cy.task('esArchiverResetKibana'); cy.task('esArchiverLoad', { archiveName: 'endpoint' }); login(); - createRule(getEndpointRule()); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); + createRule(getEndpointRule()).then((rule) => + visitWithoutDateRange(ruleDetailsUrl(rule.body.id)) + ); + waitForAlertsToPopulate(); }); after(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts index 9c4a575dccb4e..6efdc2e8c515b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts @@ -12,8 +12,7 @@ import { } from '../../../../tasks/alerts'; import { deleteAlertsAndRules, postDataView } from '../../../../tasks/common'; import { login, visitWithoutDateRange } from '../../../../tasks/login'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation'; -import { goToRuleDetails } from '../../../../tasks/alerts_detection_rules'; +import { ruleDetailsUrl } from '../../../../urls/navigation'; import { createRule } from '../../../../tasks/api_calls/rules'; import { getNewRule } from '../../../../objects/rule'; import { LOADING_INDICATOR } from '../../../../screens/security_header'; @@ -27,9 +26,9 @@ import { submitNewExceptionItem, } from '../../../../tasks/exceptions'; +// TODO: https://github.com/elastic/kibana/issues/161539 // See https://github.com/elastic/kibana/issues/163967 -describe('Close matching Alerts ', () => { - const newRule = getNewRule(); +describe('Close matching Alerts ', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { const ITEM_NAME = 'Sample Exception Item'; beforeEach(() => { @@ -40,22 +39,23 @@ describe('Close matching Alerts ', () => { login(); postDataView('exceptions-*'); - createRule({ - ...newRule, - query: 'agent.name:*', - data_view_id: 'exceptions-*', - interval: '10s', - rule_id: 'rule_testing', - }); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); + createRule( + getNewRule({ + query: 'agent.name:*', + data_view_id: 'exceptions-*', + interval: '10s', + rule_id: 'rule_testing', + }) + ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id))); + waitForAlertsToPopulate(); }); after(() => { cy.task('esArchiverUnload', 'exceptions'); }); - it('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => { + // TODO: https://github.com/elastic/kibana/issues/161539 + it.skip('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => { cy.get(LOADING_INDICATOR).should('not.exist'); addExceptionFromFirstAlert(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts index e11cace87d8e8..97df23c521181 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts @@ -10,7 +10,6 @@ import { getNewRule } from '../../../objects/rule'; import { RULE_STATUS } from '../../../screens/create_new_rule'; import { createRule } from '../../../tasks/api_calls/rules'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { login, visitWithoutDateRange } from '../../../tasks/login'; import { openExceptionFlyoutFromEmptyViewerPrompt, @@ -47,8 +46,8 @@ import { FIELD_INPUT_PARENT, } from '../../../screens/exceptions'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; -import { reload } from '../../../tasks/common'; +import { ruleDetailsUrl } from '../../../urls/navigation'; +import { deleteAlertsAndRules, reload } from '../../../tasks/common'; import { createExceptionList, createExceptionListItem, @@ -57,6 +56,7 @@ import { } from '../../../tasks/api_calls/exceptions'; import { getExceptionList } from '../../../objects/exception'; +// TODO: https://github.com/elastic/kibana/issues/161539 // Test Skipped until we fix the Flyout rerendering issue // https://github.com/elastic/kibana/issues/154994 @@ -65,7 +65,7 @@ import { getExceptionList } from '../../../objects/exception'; // to test in enzyme and very small changes can inadvertently add // bugs. As the complexity within the builder grows, these should // ensure the most basic logic holds. -describe.skip('Exceptions flyout', { tags: ['@ess', '@serverless'] }, () => { +describe.skip('Exceptions flyout', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { cy.task('esArchiverResetKibana'); // this is a made-up index that has just the necessary @@ -75,7 +75,11 @@ describe.skip('Exceptions flyout', { tags: ['@ess', '@serverless'] }, () => { // Comment the Conflicts here as they are skipped // cy.task('esArchiverLoad',{ archiveName: 'conflicts_1' }); // cy.task('esArchiverLoad',{ archiveName: 'conflicts_2' }); + }); + + beforeEach(() => { login(); + deleteAlertsAndRules(); createExceptionList(getExceptionList(), getExceptionList().list_id).then((response) => createRule( getNewRule({ @@ -90,18 +94,9 @@ describe.skip('Exceptions flyout', { tags: ['@ess', '@serverless'] }, () => { }, ], }) - ) + ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions'))) ); - login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - }); - - beforeEach(() => { - login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); cy.get(RULE_STATUS).should('have.text', '—'); - goToExceptionsTab(); }); after(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts index 5c71da0e14a35..8da64aebc92d9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts @@ -8,12 +8,8 @@ import { getNewRule } from '../../../objects/rule'; import { createRule } from '../../../tasks/api_calls/rules'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { login, visitWithoutDateRange } from '../../../tasks/login'; -import { - openExceptionFlyoutFromEmptyViewerPrompt, - goToExceptionsTab, -} from '../../../tasks/rule_details'; +import { openExceptionFlyoutFromEmptyViewerPrompt } from '../../../tasks/rule_details'; import { addExceptionFlyoutItemName, addTwoAndedConditions, @@ -26,15 +22,21 @@ import { EXCEPTION_ITEM_VIEWER_CONTAINER, } from '../../../screens/exceptions'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; +import { ruleDetailsUrl } from '../../../urls/navigation'; +import { deleteAlertsAndRules } from '../../../tasks/common'; +// TODO: https://github.com/elastic/kibana/issues/161539 +// FLAKY: https://github.com/elastic/kibana/issues/165651 +// FLAKY: https://github.com/elastic/kibana/issues/165734 +// FLAKY: https://github.com/elastic/kibana/issues/165652 describe( 'Add multiple conditions and validate the generated exceptions', - { tags: ['@ess', '@serverless'] }, + { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { beforeEach(() => { cy.task('esArchiverResetKibana'); login(); + deleteAlertsAndRules(); // At least create Rule with exceptions_list to be able to view created exceptions createRule({ ...getNewRule(), @@ -42,15 +44,13 @@ describe( index: ['exceptions*'], exceptions_list: [], rule_id: '2', - }); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); - goToExceptionsTab(); + }).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions'))); }); after(() => { cy.task('esArchiverUnload', 'exceptions'); }); + const exceptionName = 'My item name'; it('Use multipe AND conditions and validate it generates one exception', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts index e45484e5fb834..ff571faa01468 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts @@ -12,26 +12,19 @@ import { addExceptionFlyoutItemName, submitNewExceptionItem, } from '../../../tasks/exceptions'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; -import { - goToExceptionsTab, - openExceptionFlyoutFromEmptyViewerPrompt, -} from '../../../tasks/rule_details'; -import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW } from '../../../screens/lists'; +import { openExceptionFlyoutFromEmptyViewerPrompt } from '../../../tasks/rule_details'; import { getNewRule } from '../../../objects/rule'; import { cleanKibana } from '../../../tasks/common'; import { login, visitWithoutDateRange } from '../../../tasks/login'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; +import { DETECTIONS_RULE_MANAGEMENT_URL, ruleDetailsUrl } from '../../../urls/navigation'; import { createListsIndex, waitForListsIndex, waitForValueListsModalToBeLoaded, - selectValueListType, - selectValueListsFile, - uploadValueList, openValueListsModal, deleteValueListsFile, - closeValueListsModal, + importValueList, + KNOWN_VALUE_LIST_FILES, } from '../../../tasks/lists'; import { createRule } from '../../../tasks/api_calls/rules'; import { @@ -50,83 +43,72 @@ const goToRulesAndOpenValueListModal = () => { openValueListsModal(); }; -describe('Use Value list in exception entry', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - login(); - cy.task('esArchiverLoad', { archiveName: 'exceptions' }); - createRule({ - ...getNewRule(), - query: 'user.name:*', - index: ['exceptions*'], - exceptions_list: [], - rule_id: '2', +// TODO: https://github.com/elastic/kibana/issues/161539 +// Flaky on serverless +describe( + 'Use Value list in exception entry', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + beforeEach(() => { + cleanKibana(); + login(); + createListsIndex(); + cy.task('esArchiverLoad', { archiveName: 'exceptions' }); + importValueList(KNOWN_VALUE_LIST_FILES.TEXT, 'keyword'); + + createRule( + getNewRule({ + query: 'user.name:*', + index: ['exceptions*'], + exceptions_list: [], + rule_id: '2', + enabled: false, + }) + ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions'))); + }); + + afterEach(() => { + cy.task('esArchiverUnload', 'exceptions'); + }); + + it('Should use value list in exception entry, and validate deleting value list prompt', () => { + const ITEM_NAME = 'Exception item with value list'; + const ITEM_FIELD = 'agent.name'; + + // open add exception modal + openExceptionFlyoutFromEmptyViewerPrompt(); + + // add exception item name + addExceptionFlyoutItemName(ITEM_NAME); + + addExceptionEntryFieldValue(ITEM_FIELD, 0); + addExceptionEntryOperatorValue('is in list', 0); + + addExceptionEntryFieldMatchIncludedValue(KNOWN_VALUE_LIST_FILES.TEXT, 0); + + // The Close all alerts that match attributes in this exception option is disabled + cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); + cy.get(CLOSE_ALERTS_CHECKBOX).should('have.attr', 'disabled'); + + // Create exception + submitNewExceptionItem(); + + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should( + 'have.text', + ` ${ITEM_FIELD}included in value_list.txt` + ); + + // Go back to value list to delete the existing one + goToRulesAndOpenValueListModal(); + + deleteValueListsFile(KNOWN_VALUE_LIST_FILES.TEXT); + + // Toast should be shown because of exception reference + cy.get(EXCEPTIONS_TABLE_MODAL).should('exist'); }); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - }); - beforeEach(() => { - createListsIndex(); - }); - - afterEach(() => { - cy.task('esArchiverUnload', 'exceptions'); - }); - - it('Should use value list in exception entry, and validate deleting value list prompt', () => { - const ITEM_NAME = 'Exception item with value list'; - const ITEM_FIELD = 'agent.name'; - - goToRulesAndOpenValueListModal(); - - // Add new value list of type keyword - const listName = 'value_list.txt'; - selectValueListType('keyword'); - selectValueListsFile(listName); - uploadValueList(); - - cy.get(VALUE_LISTS_TABLE) - .find(VALUE_LISTS_ROW) - .should(($row) => { - expect($row.text()).to.contain(listName); - expect($row.text()).to.contain('Keywords'); - }); - closeValueListsModal(); - goToRuleDetails(); - goToExceptionsTab(); - - // open add exception modal - openExceptionFlyoutFromEmptyViewerPrompt(); - - // add exception item name - addExceptionFlyoutItemName(ITEM_NAME); - - addExceptionEntryFieldValue(ITEM_FIELD, 0); - addExceptionEntryOperatorValue('is in list', 0); - - addExceptionEntryFieldMatchIncludedValue('value_list.txt', 0); - - // The Close all alerts that match attributes in this exception option is disabled - cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); - cy.get(CLOSE_ALERTS_CHECKBOX).should('have.attr', 'disabled'); - - // Create exception - submitNewExceptionItem(); - - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should( - 'have.text', - ` ${ITEM_FIELD}included in value_list.txt` - ); - - // Go back to value list to delete the existing one - goToRulesAndOpenValueListModal(); - - deleteValueListsFile(listName); - - // Toast should be shown because of exception reference - cy.get(EXCEPTIONS_TABLE_MODAL).should('exist'); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts index cdf0b56347063..c5a1a8e25b337 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts @@ -8,13 +8,12 @@ import { getNewRule } from '../../../objects/rule'; import { createRule } from '../../../tasks/api_calls/rules'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { login, visitWithoutDateRange } from '../../../tasks/login'; import { - goToEndpointExceptionsTab, openEditException, openExceptionFlyoutFromEmptyViewerPrompt, searchForExceptionItem, + waitForPageToBeLoaded as waitForRuleDetailsPageToBeLoaded, } from '../../../tasks/rule_details'; import { addExceptionConditions, @@ -26,7 +25,7 @@ import { submitNewExceptionItem, } from '../../../tasks/exceptions'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; +import { ruleDetailsUrl } from '../../../urls/navigation'; import { deleteAlertsAndRules } from '../../../tasks/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, @@ -42,151 +41,191 @@ import { EXCEPTION_CARD_ITEM_CONDITIONS, FIELD_INPUT_PARENT, } from '../../../screens/exceptions'; -import { createEndpointExceptionList } from '../../../tasks/api_calls/exceptions'; - -describe('Add endpoint exception from rule details', { tags: ['@ess', '@serverless'] }, () => { - const ITEM_NAME = 'Sample Exception List Item'; - - before(() => { - cy.task('esArchiverResetKibana'); - cy.task('esArchiverLoad', { archiveName: 'auditbeat' }); - login(); - deleteAlertsAndRules(); - // create rule with exception - createEndpointExceptionList<{ - id: string; - list_id: string; - type: - | 'detection' - | 'rule_default' - | 'endpoint' - | 'endpoint_trusted_apps' - | 'endpoint_events' - | 'endpoint_host_isolation_exceptions' - | 'endpoint_blocklists'; - namespace_type: 'agnostic' | 'single'; - }>().then((response) => { - createRule( - getNewRule({ - query: 'event.code:*', - index: ['auditbeat*'], - exceptions_list: [ - { - id: response.body.id, - list_id: response.body.list_id, - type: response.body.type, - namespace_type: response.body.namespace_type, - }, - ], - rule_id: '2', - }) - ); - }); - }); - - beforeEach(() => { - login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); - goToEndpointExceptionsTab(); - }); - - after(() => { - cy.task('esArchiverUnload', 'auditbeat'); - }); - - it('creates an exception item', () => { - // when no exceptions exist, empty component shows with action to add exception - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); - - // open add exception modal - openExceptionFlyoutFromEmptyViewerPrompt(); - - // submit button is disabled if no paramerters were added - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - - // for endpoint exceptions, must specify OS - selectOs('windows'); - - // add exception item conditions - addExceptionConditions({ - field: 'event.code', - operator: 'is', - values: ['foo'], - }); - - // Name is required so want to check that submit is still disabled - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - - // add exception item name - addExceptionFlyoutItemName(ITEM_NAME); - - // Option to add to rule or add to list should NOT appear - cy.get(ADD_TO_RULE_OR_LIST_SECTION).should('not.exist'); - - // not testing close alert functionality here, just ensuring that the options appear as expected - cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).should('not.exist'); - cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); - - // submit - submitNewExceptionItem(); - - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - }); - - it('edits an endpoint exception item', () => { +import { + createEndpointExceptionList, + createEndpointExceptionListItem, +} from '../../../tasks/api_calls/exceptions'; + +// TODO: https://github.com/elastic/kibana/issues/161539 +// FLAKY: https://github.com/elastic/kibana/issues/165736 +describe( + 'Add endpoint exception from rule details', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + const ITEM_NAME = 'Sample Exception List Item'; const NEW_ITEM_NAME = 'Exception item-EDITED'; const ITEM_FIELD = 'event.code'; const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.type'; - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ` ${ITEM_FIELD}IS foo`); - - // open edit exception modal - openEditException(); - - // edit exception item name - editExceptionFlyoutItemName(NEW_ITEM_NAME); - - // check that the existing item's field is being populated - cy.get(EXCEPTION_ITEM_CONTAINER) - .eq(0) - .find(FIELD_INPUT_PARENT) - .eq(0) - .should('have.text', ITEM_FIELD); - cy.get(VALUES_INPUT).should('have.text', 'foo'); - - // edit conditions - editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); - - // submit - submitEditedExceptionItem(); - - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - - // check that updates stuck - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.typeIS foo'); - }); - - it('allows user to search for items', () => { - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - - // can search for an exception value - searchForExceptionItem('foo'); + beforeEach(() => { + cy.task('esArchiverResetKibana'); + cy.task('esArchiverLoad', { archiveName: 'auditbeat' }); + login(); + deleteAlertsAndRules(); + }); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + afterEach(() => { + cy.task('esArchiverUnload', 'auditbeat'); + }); - // displays empty search result view if no matches found - searchForExceptionItem('abc'); + describe('without exception items', () => { + beforeEach(() => { + createEndpointExceptionList().then((response) => { + createRule( + getNewRule({ + query: 'event.code:*', + index: ['auditbeat*'], + exceptions_list: [ + { + id: response.body.id, + list_id: response.body.list_id, + type: response.body.type, + namespace_type: response.body.namespace_type, + }, + ], + rule_id: '2', + enabled: false, + }) + ).then((rule) => + visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'endpoint_exceptions')) + ); + }); + }); + + it('creates an exception item', () => { + // when no exceptions exist, empty component shows with action to add exception + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); + + // open add exception modal + openExceptionFlyoutFromEmptyViewerPrompt(); + + // submit button is disabled if no paramerters were added + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + + // for endpoint exceptions, must specify OS + selectOs('windows'); + + // add exception item conditions + addExceptionConditions({ + field: 'event.code', + operator: 'is', + values: ['foo'], + }); + + // Name is required so want to check that submit is still disabled + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + + // add exception item name + addExceptionFlyoutItemName(ITEM_NAME); + + // Option to add to rule or add to list should NOT appear + cy.get(ADD_TO_RULE_OR_LIST_SECTION).should('not.exist'); + + // not testing close alert functionality here, just ensuring that the options appear as expected + cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).should('not.exist'); + cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); + + // submit + submitNewExceptionItem(); + + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + }); + }); - // new exception item displays - cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); - }); -}); + describe('with exception items', () => { + beforeEach(() => { + createEndpointExceptionList().then((response) => { + createEndpointExceptionListItem({ + comments: [], + description: 'Exception list item', + entries: [ + { + field: ITEM_FIELD, + operator: 'included', + type: 'match', + value: 'foo', + }, + ], + name: ITEM_NAME, + tags: [], + type: 'simple', + os_types: ['windows'], + }); + + createRule( + getNewRule({ + name: 'Rule with exceptions', + query: 'event.code:*', + index: ['auditbeat*'], + exceptions_list: [ + { + id: response.body.id, + list_id: response.body.list_id, + type: response.body.type, + namespace_type: response.body.namespace_type, + }, + ], + rule_id: '2', + enabled: false, + }) + ).then((rule) => { + visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'endpoint_exceptions')); + waitForRuleDetailsPageToBeLoaded('Rule with exceptions'); + }); + }); + }); + + it('edits an endpoint exception item', () => { + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ` ${ITEM_FIELD}IS foo`); + + // open edit exception modal + openEditException(); + + // edit exception item name + editExceptionFlyoutItemName(NEW_ITEM_NAME); + + // check that the existing item's field is being populated + cy.get(EXCEPTION_ITEM_CONTAINER) + .eq(0) + .find(FIELD_INPUT_PARENT) + .eq(0) + .should('have.text', ITEM_FIELD); + cy.get(VALUES_INPUT).should('have.text', 'foo'); + + // edit conditions + editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); + + // submit + submitEditedExceptionItem(); + + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + + // check that updates stuck + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.typeIS foo'); + }); + + it('allows user to search for items', () => { + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + + // can search for an exception value + searchForExceptionItem('foo'); + + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + + // displays empty search result view if no matches found + searchForExceptionItem('abc'); + + // new exception item displays + cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts index 18537145ec582..2f3d10a0b3cee 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts @@ -10,7 +10,6 @@ import { getNewRule } from '../../../objects/rule'; import { ALERTS_COUNT, EMPTY_ALERT_TABLE } from '../../../screens/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { goToClosedAlertsOnRuleDetailsPage, goToOpenedAlertsOnRuleDetailsPage, @@ -37,7 +36,7 @@ import { submitEditedExceptionItem, submitNewExceptionItem, } from '../../../tasks/exceptions'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; +import { ruleDetailsUrl } from '../../../urls/navigation'; import { deleteAlertsAndRules } from '../../../tasks/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, @@ -59,277 +58,281 @@ import { } from '../../../tasks/api_calls/exceptions'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; -describe('Add/edit exception from rule details', { tags: ['@ess', '@brokenInServerless'] }, () => { - const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert'; - const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name'; - const ITEM_FIELD = 'unique_value.test'; - - before(() => { - cy.task('esArchiverResetKibana'); - cy.task('esArchiverLoad', { archiveName: 'exceptions' }); - login(); - }); +// TODO: https://github.com/elastic/kibana/issues/161539 +describe( + 'Add/edit exception from rule details', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert'; + const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name'; + const ITEM_FIELD = 'unique_value.test'; + + before(() => { + cy.task('esArchiverResetKibana'); + cy.task('esArchiverLoad', { archiveName: 'exceptions' }); + }); - after(() => { - cy.task('esArchiverUnload', 'exceptions'); - }); + after(() => { + cy.task('esArchiverUnload', 'exceptions'); + }); - describe('existing list and items', () => { - const exceptionList = getExceptionList(); beforeEach(() => { + login(); deleteAlertsAndRules(); + + const exceptionList = getExceptionList(); deleteExceptionList(exceptionList.list_id, exceptionList.namespace_type); - // create rule with exceptions - createExceptionList(exceptionList, exceptionList.list_id).then((response) => { - createRule( - getNewRule({ - query: 'agent.name:*', - index: ['exceptions*'], - exceptions_list: [ + }); + + describe('existing list and items', () => { + const exceptionList = getExceptionList(); + beforeEach(() => { + // create rule with exceptions + createExceptionList(exceptionList, exceptionList.list_id).then((response) => { + createExceptionListItem(exceptionList.list_id, { + list_id: exceptionList.list_id, + item_id: 'simple_list_item', + tags: [], + type: 'simple', + description: 'Test exception item 2', + name: 'Sample Exception List Item 2', + namespace_type: 'single', + entries: [ { - id: response.body.id, - list_id: exceptionList.list_id, - type: exceptionList.type, - namespace_type: exceptionList.namespace_type, + field: ITEM_FIELD, + operator: 'included', + type: 'match_any', + value: ['foo'], }, ], - rule_id: '2', - }) - ); - createExceptionListItem(exceptionList.list_id, { - list_id: exceptionList.list_id, - item_id: 'simple_list_item', - tags: [], - type: 'simple', - description: 'Test exception item 2', - name: 'Sample Exception List Item 2', - namespace_type: 'single', - entries: [ - { - field: ITEM_FIELD, - operator: 'included', - type: 'match_any', - value: ['foo'], - }, - ], + }); + + createRule( + getNewRule({ + query: 'agent.name:*', + index: ['exceptions*'], + exceptions_list: [ + { + id: response.body.id, + list_id: exceptionList.list_id, + type: exceptionList.type, + namespace_type: exceptionList.namespace_type, + }, + ], + rule_id: '2', + }) + ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions'))); }); }); - login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); - goToExceptionsTab(); - }); + it('Edits an exception item', () => { + const NEW_ITEM_NAME = 'Exception item-EDITED'; + const ITEM_NAME = 'Sample Exception List Item 2'; - it('Edits an exception item', () => { - const NEW_ITEM_NAME = 'Exception item-EDITED'; - const ITEM_NAME = 'Sample Exception List Item 2'; + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should( + 'have.text', + ' unique_value.testis one of foo' + ); - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' unique_value.testis one of foo'); + // open edit exception modal + openEditException(); - // open edit exception modal - openEditException(); + // edit exception item name + editExceptionFlyoutItemName(NEW_ITEM_NAME); - // edit exception item name - editExceptionFlyoutItemName(NEW_ITEM_NAME); + // check that the existing item's field is being populated + cy.get(EXCEPTION_ITEM_CONTAINER) + .eq(0) + .find(FIELD_INPUT_PARENT) + .eq(0) + .should('have.text', ITEM_FIELD); + cy.get(VALUES_MATCH_ANY_INPUT).should('have.text', 'foo'); - // check that the existing item's field is being populated - cy.get(EXCEPTION_ITEM_CONTAINER) - .eq(0) - .find(FIELD_INPUT_PARENT) - .eq(0) - .should('have.text', ITEM_FIELD); - cy.get(VALUES_MATCH_ANY_INPUT).should('have.text', 'foo'); + // edit conditions + editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); - // edit conditions - editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); + // submit + submitEditedExceptionItem(); - // submit - submitEditedExceptionItem(); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // check that updates stuck + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.nameIS foo'); + }); - // check that updates stuck - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.nameIS foo'); - }); + describe('rule with existing shared exceptions', () => { + it('Creates an exception item to add to shared list', () => { + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - describe('rule with existing shared exceptions', () => { - it('Creates an exception item to add to shared list', () => { - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + // open add exception modal + addExceptionFlyoutFromViewerHeader(); - // open add exception modal - addExceptionFlyoutFromViewerHeader(); + // add exception item conditions + addExceptionConditions(getException()); - // add exception item conditions - addExceptionConditions(getException()); + // Name is required so want to check that submit is still disabled + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // Name is required so want to check that submit is still disabled - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // add exception item name + addExceptionFlyoutItemName('My item name'); - // add exception item name - addExceptionFlyoutItemName('My item name'); + // select to add exception item to a shared list + selectSharedListToAddExceptionTo(1); - // select to add exception item to a shared list - selectSharedListToAddExceptionTo(1); + // not testing close alert functionality here, just ensuring that the options appear as expected + cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); + cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled'); - // not testing close alert functionality here, just ensuring that the options appear as expected - cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); - cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled'); + // submit + submitNewExceptionItem(); - // submit - submitNewExceptionItem(); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2); + }); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2); - }); + it('Creates an exception item to add to rule only', () => { + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - it('Creates an exception item to add to rule only', () => { - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + // open add exception modal + addExceptionFlyoutFromViewerHeader(); - // open add exception modal - addExceptionFlyoutFromViewerHeader(); + // add exception item conditions + addExceptionConditions(getException()); - // add exception item conditions - addExceptionConditions(getException()); + // Name is required so want to check that submit is still disabled + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // Name is required so want to check that submit is still disabled - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // add exception item name + addExceptionFlyoutItemName('My item name'); - // add exception item name - addExceptionFlyoutItemName('My item name'); + // select to add exception item to rule only + selectAddToRuleRadio(); - // select to add exception item to rule only - selectAddToRuleRadio(); + // not testing close alert functionality here, just ensuring that the options appear as expected + cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); + cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled'); - // not testing close alert functionality here, just ensuring that the options appear as expected - cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); - cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled'); + // submit + submitNewExceptionItem(); - // submit - submitNewExceptionItem(); - - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2); - }); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2); + }); - // Trying to figure out with EUI why the search won't trigger - it('Can search for items', () => { - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + // Trying to figure out with EUI why the search won't trigger + it('Can search for items', () => { + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - // can search for an exception value - searchForExceptionItem('foo'); + // can search for an exception value + searchForExceptionItem('foo'); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // displays empty search result view if no matches found - searchForExceptionItem('abc'); + // displays empty search result view if no matches found + searchForExceptionItem('abc'); - // new exception item displays - cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); + // new exception item displays + cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); + }); }); }); - }); - describe('rule without existing exceptions', () => { - beforeEach(() => { - deleteAlertsAndRules(); - createRule( - getNewRule({ - query: 'agent.name:*', - index: ['exceptions*'], - interval: '10s', - rule_id: 'rule_testing', - }) - ); - login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); - goToExceptionsTab(); - }); + describe('rule without existing exceptions', () => { + beforeEach(() => { + createRule( + getNewRule({ + query: 'agent.name:*', + index: ['exceptions*'], + interval: '10s', + rule_id: 'rule_testing', + }) + ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions'))); + }); - afterEach(() => { - cy.task('esArchiverUnload', 'exceptions_2'); - }); + afterEach(() => { + cy.task('esArchiverUnload', 'exceptions_2'); + }); - it('Cannot create an item to add to rule but not shared list as rule has no lists attached', () => { - // when no exceptions exist, empty component shows with action to add exception - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); + it('Cannot create an item to add to rule but not shared list as rule has no lists attached', () => { + // when no exceptions exist, empty component shows with action to add exception + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); - // open add exception modal - openExceptionFlyoutFromEmptyViewerPrompt(); + // open add exception modal + openExceptionFlyoutFromEmptyViewerPrompt(); - // add exception item conditions - addExceptionConditions({ - field: 'agent.name', - operator: 'is', - values: ['foo'], - }); + // add exception item conditions + addExceptionConditions({ + field: 'agent.name', + operator: 'is', + values: ['foo'], + }); - // Name is required so want to check that submit is still disabled - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // Name is required so want to check that submit is still disabled + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // add exception item name - addExceptionFlyoutItemName('My item name'); + // add exception item name + addExceptionFlyoutItemName('My item name'); - // select to add exception item to rule only - selectAddToRuleRadio(); + // select to add exception item to rule only + selectAddToRuleRadio(); - // Check that add to shared list is disabled, should be unless - // rule has shared lists attached to it already - cy.get(ADD_TO_SHARED_LIST_RADIO_INPUT).should('have.attr', 'disabled'); + // Check that add to shared list is disabled, should be unless + // rule has shared lists attached to it already + cy.get(ADD_TO_SHARED_LIST_RADIO_INPUT).should('have.attr', 'disabled'); - // Close matching alerts - selectBulkCloseAlerts(); + // Close matching alerts + selectBulkCloseAlerts(); - // submit - submitNewExceptionItem(); + // submit + submitNewExceptionItem(); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // Alerts table should now be empty from having added exception and closed - // matching alert - goToAlertsTab(); - cy.get(EMPTY_ALERT_TABLE).should('exist'); + // Alerts table should now be empty from having added exception and closed + // matching alert + goToAlertsTab(); + cy.get(EMPTY_ALERT_TABLE).should('exist'); - // Closed alert should appear in table - goToClosedAlertsOnRuleDetailsPage(); - cy.get(ALERTS_COUNT).should('exist'); - cy.get(ALERTS_COUNT).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`); + // Closed alert should appear in table + goToClosedAlertsOnRuleDetailsPage(); + cy.get(ALERTS_COUNT).should('exist'); + cy.get(ALERTS_COUNT).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`); - // Remove the exception and load an event that would have matched that exception - // to show that said exception now starts to show up again - goToExceptionsTab(); + // Remove the exception and load an event that would have matched that exception + // to show that said exception now starts to show up again + goToExceptionsTab(); - // when removing exception and again, no more exist, empty screen shows again - removeException(); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); + // when removing exception and again, no more exist, empty screen shows again + removeException(); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); - // load more docs - cy.task('esArchiverLoad', { archiveName: 'exceptions_2' }); + // load more docs + cy.task('esArchiverLoad', { archiveName: 'exceptions_2' }); - // now that there are no more exceptions, the docs should match and populate alerts - goToAlertsTab(); - waitForAlertsToPopulate(); - goToOpenedAlertsOnRuleDetailsPage(); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); + // now that there are no more exceptions, the docs should match and populate alerts + goToAlertsTab(); + waitForAlertsToPopulate(); + goToOpenedAlertsOnRuleDetailsPage(); + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(ALERTS_COUNT).should('exist'); - cy.get(ALERTS_COUNT).should('have.text', '2 alerts'); + cy.get(ALERTS_COUNT).should('exist'); + cy.get(ALERTS_COUNT).should('have.text', '2 alerts'); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts index 229cfce25b9fa..07fd7dd56e5c0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts @@ -8,7 +8,6 @@ import { getNewRule } from '../../../objects/rule'; import { ALERTS_COUNT, EMPTY_ALERT_TABLE } from '../../../screens/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { goToClosedAlertsOnRuleDetailsPage, goToOpenedAlertsOnRuleDetailsPage, @@ -28,7 +27,7 @@ import { waitForTheRuleToBeExecuted, } from '../../../tasks/rule_details'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; +import { ruleDetailsUrl } from '../../../urls/navigation'; import { postDataView, deleteAlertsAndRules } from '../../../tasks/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, @@ -41,9 +40,10 @@ import { } from '../../../screens/exceptions'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; +// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Add exception using data views from rule details', - { tags: ['@ess', '@brokenInServerless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert'; const ITEM_NAME = 'Sample Exception List Item'; @@ -60,6 +60,7 @@ describe( }); beforeEach(() => { + login(); deleteAlertsAndRules(); createRule( getNewRule({ @@ -68,10 +69,7 @@ describe( interval: '10s', rule_id: 'rule_testing', }) - ); - login(); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); + ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id))); waitForAlertsToPopulate(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts index 6ecb3c1e40602..85f0a20128382 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts @@ -11,7 +11,7 @@ import { getNewRule } from '../../../objects/rule'; import { createRule } from '../../../tasks/api_calls/rules'; import { login, visitSecurityDetectionRulesPage } from '../../../tasks/login'; import { goToExceptionsTab, goToAlertsTab } from '../../../tasks/rule_details'; -import { goToTheRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; +import { goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; import { deleteAlertsAndRules } from '../../../tasks/common'; import { NO_EXCEPTIONS_EXIST_PROMPT, @@ -26,7 +26,8 @@ import { deleteExceptionList, } from '../../../tasks/api_calls/exceptions'; -describe('Exceptions viewer read only', { tags: '@ess' }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 Do we need this to run in Serverless? +describe('Exceptions viewer read only', { tags: ['@ess', '@skipInServerless'] }, () => { const exceptionList = getExceptionList(); beforeEach(() => { @@ -55,7 +56,7 @@ describe('Exceptions viewer read only', { tags: '@ess' }, () => { login(ROLES.reader); visitSecurityDetectionRulesPage(ROLES.reader); - goToTheRuleDetailsOf('Test exceptions rule'); + goToRuleDetailsOf('Test exceptions rule'); goToExceptionsTab(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts index 50cd2dae04ec5..2c5ccd93f3cab 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts @@ -40,88 +40,94 @@ const getExceptionList1 = () => ({ const EXCEPTION_LIST_NAME = 'Newly created list'; -describe('Exception list detail page', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cy.task('esArchiverResetKibana'); - login(); - - // Create exception list associated with a rule - createExceptionList(getExceptionList1(), getExceptionList1().list_id).then((response) => - createRule( - getNewRule({ - exceptions_list: [ - { - id: response.body.id, - list_id: getExceptionList1().list_id, - type: getExceptionList1().type, - namespace_type: getExceptionList1().namespace_type, - }, - ], - }) - ) - ); - createRule(getNewRule({ name: 'Rule to link to shared list' })); - }); - - beforeEach(() => { - login(); - visitWithoutDateRange(EXCEPTIONS_URL); - }); - - it('Should edit list details', () => { - visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id)); - waitForExceptionListDetailToBeLoaded(); - // Check list details are loaded - cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', LIST_NAME); - cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', LIST_DESCRIPTION); - - // Update list details in edit modal - editExceptionLisDetails({ - name: { original: LIST_NAME, updated: UPDATED_LIST_NAME }, - description: { original: LIST_DESCRIPTION, updated: UPDATED_LIST_DESCRIPTION }, +// TODO: https://github.com/elastic/kibana/issues/161539 +// FLAKY: https://github.com/elastic/kibana/issues/165640 +describe( + 'Exception list detail page', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + before(() => { + cy.task('esArchiverResetKibana'); + login(); + + // Create exception list associated with a rule + createExceptionList(getExceptionList1(), getExceptionList1().list_id).then((response) => + createRule( + getNewRule({ + exceptions_list: [ + { + id: response.body.id, + list_id: getExceptionList1().list_id, + type: getExceptionList1().type, + namespace_type: getExceptionList1().namespace_type, + }, + ], + }) + ) + ); + createRule(getNewRule({ name: 'Rule to link to shared list' })); }); - // Ensure that list details were updated - cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', UPDATED_LIST_NAME); - cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', UPDATED_LIST_DESCRIPTION); - - // Ensure that list details changes persisted - visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id)); - cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', UPDATED_LIST_NAME); - cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', UPDATED_LIST_DESCRIPTION); - - // Remove description - editExceptionLisDetails({ - description: { original: UPDATED_LIST_DESCRIPTION, updated: null }, + beforeEach(() => { + login(); + visitWithoutDateRange(EXCEPTIONS_URL); }); - cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', 'Add a description'); - // Ensure description removal persisted - visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id)); - cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', 'Add a description'); - }); + it('Should edit list details', () => { + visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id)); + waitForExceptionListDetailToBeLoaded(); + // Check list details are loaded + cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', LIST_NAME); + cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', LIST_DESCRIPTION); + + // Update list details in edit modal + editExceptionLisDetails({ + name: { original: LIST_NAME, updated: UPDATED_LIST_NAME }, + description: { original: LIST_DESCRIPTION, updated: UPDATED_LIST_DESCRIPTION }, + }); + + // Ensure that list details were updated + cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', UPDATED_LIST_NAME); + cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', UPDATED_LIST_DESCRIPTION); + + // Ensure that list details changes persisted + visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id)); + cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', UPDATED_LIST_NAME); + cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', UPDATED_LIST_DESCRIPTION); + + // Remove description + editExceptionLisDetails({ + description: { original: UPDATED_LIST_DESCRIPTION, updated: null }, + }); + cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', 'Add a description'); + + // Ensure description removal persisted + visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id)); + cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', 'Add a description'); + }); - it('Should create a new list and link it to two rules', () => { - createSharedExceptionList( - { name: 'Newly created list', description: 'This is my list.' }, - true - ); + it('Should create a new list and link it to two rules', () => { + createSharedExceptionList( + { name: 'Newly created list', description: 'This is my list.' }, + true + ); - // After creation - directed to list detail page - cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', EXCEPTION_LIST_NAME); + // After creation - directed to list detail page + cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', EXCEPTION_LIST_NAME); - // Open Link rules flyout - cy.get(EXCEPTION_LIST_DETAILS_LINK_RULES_BTN).click(); + // Open Link rules flyout + cy.get(EXCEPTION_LIST_DETAILS_LINK_RULES_BTN).click(); - // Link the first two Rules - linkSharedListToRulesFromListDetails(2); + // Link the first two Rules + linkSharedListToRulesFromListDetails(2); - // Save the 2 linked Rules - saveLinkedRules(); + // Save the 2 linked Rules + saveLinkedRules(); - const linkedRulesNames = ['Rule to link to shared list', 'New Rule Test']; + const linkedRulesNames = ['Rule to link to shared list', 'New Rule Test']; - // Validate the number of linked rules as well as the Rules' names - validateSharedListLinkedRules(2, linkedRulesNames); - }); -}); + // Validate the number of linked rules as well as the Rules' names + validateSharedListLinkedRules(2, linkedRulesNames); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts index 5c9b93ed1507b..0c5523e42a9ee 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { getNewRule } from '../../../objects/rule'; import { login, visitWithoutDateRange } from '../../../tasks/login'; import { createRule } from '../../../tasks/api_calls/rules'; @@ -19,7 +20,7 @@ import { submitNewExceptionItem, deleteFirstExceptionItemInListDetailPage, } from '../../../tasks/exceptions'; -import { DETECTIONS_RULE_MANAGEMENT_URL, EXCEPTIONS_URL } from '../../../urls/navigation'; +import { EXCEPTIONS_URL, ruleDetailsUrl } from '../../../urls/navigation'; import { CONFIRM_BTN, @@ -29,8 +30,6 @@ import { EXECPTION_ITEM_CARD_HEADER_TITLE, EMPTY_EXCEPTIONS_VIEWER, } from '../../../screens/exceptions'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; -import { goToExceptionsTab } from '../../../tasks/rule_details'; import { addExceptionListFromSharedExceptionListHeaderMenu, createSharedExceptionList, @@ -38,111 +37,114 @@ import { waitForExceptionsTableToBeLoaded, } from '../../../tasks/exceptions_table'; -describe('Add, edit and delete exception', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cy.task('esArchiverResetKibana'); - cy.task('esArchiverLoad', { archiveName: 'exceptions' }); - - createRule(getNewRule()); - }); - - beforeEach(() => { - login(); - visitWithoutDateRange(EXCEPTIONS_URL); - waitForExceptionsTableToBeLoaded(); - }); - after(() => { - cy.task('esArchiverUnload', 'exceptions'); - }); +// TODO: https://github.com/elastic/kibana/issues/161539 +// FLAKY: https://github.com/elastic/kibana/issues/165795 +describe( + 'Add, edit and delete exception', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + beforeEach(() => { + cy.task('esArchiverResetKibana'); + cy.task('esArchiverLoad', { archiveName: 'exceptions' }); + createRule(getNewRule()).as('createdRule'); + + login(); + visitWithoutDateRange(EXCEPTIONS_URL); + waitForExceptionsTableToBeLoaded(); + }); - const exceptionName = 'My item name'; - const exceptionNameEdited = 'My item name edited'; - const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name'; - const EXCEPTION_LIST_NAME = 'Newly created list'; + afterEach(() => { + cy.task('esArchiverUnload', 'exceptions'); + }); - describe('Add, Edit and delete Exception item', () => { - it('should create exception item from Shared Exception List page and linked to a Rule', () => { - // Click on "Create shared exception list" button on the header - // Click on "Create exception item" - addExceptionListFromSharedExceptionListHeaderMenu(); + const exceptionName = 'My item name'; + const exceptionNameEdited = 'My item name edited'; + const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name'; + const EXCEPTION_LIST_NAME = 'Newly created list'; - // Add exception item name - addExceptionFlyoutItemName(exceptionName); + describe('Add, Edit and delete Exception item', () => { + it('should create exception item from Shared Exception List page and linked to a Rule', () => { + // Click on "Create shared exception list" button on the header + // Click on "Create exception item" + addExceptionListFromSharedExceptionListHeaderMenu(); - // Add Condition - editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); + // Add exception item name + addExceptionFlyoutItemName(exceptionName); - // Confirm button should disabled until a rule(s) is selected - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // Add Condition + editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); - // select rule - linkFirstRuleOnExceptionFlyout(); + // Confirm button should disabled until a rule(s) is selected + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // should be able to submit - cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled'); + // select rule + linkFirstRuleOnExceptionFlyout(); - submitNewExceptionItem(); + // should be able to submit + cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled'); - // Navigate to Rule page - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); + submitNewExceptionItem(); - goToExceptionsTab(); + // Navigate to Rule details page + cy.get>('@createdRule').then((rule) => + visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions')) + ); - // Only one Exception should generated - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // Only one Exception should generated + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // validate the And operator is displayed correctly - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', exceptionName); - }); + // validate the And operator is displayed correctly + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', exceptionName); + }); - it('should create exception item from Shared Exception List page, linked to a Shared List and validate Edit/delete in list detail page', function () { - createSharedExceptionList( - { name: 'Newly created list', description: 'This is my list.' }, - true - ); + it('should create exception item from Shared Exception List page, linked to a Shared List and validate Edit/delete in list detail page', function () { + createSharedExceptionList( + { name: 'Newly created list', description: 'This is my list.' }, + true + ); - // After creation - directed to list detail page - cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', EXCEPTION_LIST_NAME); + // After creation - directed to list detail page + cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', EXCEPTION_LIST_NAME); - // Go back to Shared Exception List - visitWithoutDateRange(EXCEPTIONS_URL); + // Go back to Shared Exception List + visitWithoutDateRange(EXCEPTIONS_URL); - // Click on "Create shared exception list" button on the header - // Click on "Create exception item" - addExceptionListFromSharedExceptionListHeaderMenu(); + // Click on "Create shared exception list" button on the header + // Click on "Create exception item" + addExceptionListFromSharedExceptionListHeaderMenu(); - // Add exception item name - addExceptionFlyoutItemName(exceptionName); + // Add exception item name + addExceptionFlyoutItemName(exceptionName); - // Add Condition - editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); + // Add Condition + editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); - // select shared list radio option and select the first one - linkFirstSharedListOnExceptionFlyout(); + // select shared list radio option and select the first one + linkFirstSharedListOnExceptionFlyout(); - submitNewExceptionItem(); + submitNewExceptionItem(); - // New exception is added to the new List - findSharedExceptionListItemsByName(`${EXCEPTION_LIST_NAME}`, [exceptionName]); + // New exception is added to the new List + findSharedExceptionListItemsByName(`${EXCEPTION_LIST_NAME}`, [exceptionName]); - // Click on the first exception overflow menu items - // Open the edit modal - editFirstExceptionItemInListDetailPage(); - // edit exception item name - editExceptionFlyoutItemName(exceptionNameEdited); + // Click on the first exception overflow menu items + // Open the edit modal + editFirstExceptionItemInListDetailPage(); + // edit exception item name + editExceptionFlyoutItemName(exceptionNameEdited); - // submit - submitEditedExceptionItem(); + // submit + submitEditedExceptionItem(); - // check the new name after edit - cy.get(EXECPTION_ITEM_CARD_HEADER_TITLE).should('have.text', exceptionNameEdited); + // check the new name after edit + cy.get(EXECPTION_ITEM_CARD_HEADER_TITLE).should('have.text', exceptionNameEdited); - // Click on the first exception overflow menu items - // delete the exception - deleteFirstExceptionItemInListDetailPage(); + // Click on the first exception overflow menu items + // delete the exception + deleteFirstExceptionItemInListDetailPage(); - cy.get(EMPTY_EXCEPTIONS_VIEWER).should('exist'); + cy.get(EMPTY_EXCEPTIONS_VIEWER).should('exist'); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts index 881c21472d7ef..f28b1fc2ed25a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts @@ -40,7 +40,9 @@ const getExceptionList2 = () => ({ list_id: 'exception_list_2', }); -describe('Duplicate List', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +// Flaky in serverless tests +describe('Duplicate List', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { beforeEach(() => { cy.task('esArchiverResetKibana'); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts index 8fc3acdb2c7ec..6f8411c2d5ada 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts @@ -35,7 +35,9 @@ const getExceptionList2 = () => ({ name: EXCEPTION_LIST_NAME_TWO, list_id: 'exception_list_2', }); -describe('Filter Lists', { tags: ['@ess', '@serverless'] }, () => { + +// TODO: https://github.com/elastic/kibana/issues/161539 +describe('Filter Lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { beforeEach(() => { cy.task('esArchiverResetKibana'); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts index ac9c0be83b5c1..7d24c9009e31d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts @@ -20,7 +20,9 @@ import { import { login, visitWithoutDateRange } from '../../../../tasks/login'; import { EXCEPTIONS_URL } from '../../../../urls/navigation'; -describe('Import Lists', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +// Flaky in serverless +describe('Import Lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { const LIST_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_exception_list.ndjson'; before(() => { cy.task('esArchiverResetKibana'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts index 238d39bcd6e17..b740eb5d9d63e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts @@ -47,9 +47,11 @@ const getExceptionList2 = () => ({ let exceptionListResponse: Cypress.Response; +// TODO: https://github.com/elastic/kibana/issues/161539 +// FLAKY: https://github.com/elastic/kibana/issues/165690 describe( 'Manage lists from "Shared Exception Lists" page', - { tags: ['@ess', '@serverless'] }, + { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { describe('Create/Export/Delete List', () => { before(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts index c6ac43547fadb..d028f5a3949c1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts @@ -21,7 +21,8 @@ import { import { login, visitWithoutDateRange } from '../../../../tasks/login'; import { EXCEPTIONS_URL } from '../../../../urls/navigation'; -describe('Shared exception lists - read only', { tags: '@ess' }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 Do we need to run it in Serverless? +describe('Shared exception lists - read only', { tags: ['@ess', '@skipInServerless'] }, () => { before(() => { cy.task('esArchiverResetKibana'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts index b355da43ff179..21650dffb2912 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts @@ -28,61 +28,70 @@ import { CASES_URL } from '../../../urls/navigation'; import { CONNECTOR_CARD_DETAILS, CONNECTOR_TITLE } from '../../../screens/case_details'; import { cleanKibana } from '../../../tasks/common'; -describe('Cases connector incident fields', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - login(); - }); - - beforeEach(() => { - login(); - cy.intercept('GET', '/api/cases/configure/connectors/_find', getMockConnectorsResponse()); - cy.intercept('POST', `/api/actions/connector/${getConnectorIds().sn}/_execute`, (req) => { - const response = - req.body.params.subAction === 'getChoices' - ? getExecuteResponses().servicenow.choices - : { status: 'ok', data: [] }; - req.reply(response); - }); - cy.intercept('POST', `/api/actions/connector/${getConnectorIds().jira}/_execute`, (req) => { - const response = - req.body.params.subAction === 'issueTypes' - ? getExecuteResponses().jira.issueTypes - : getExecuteResponses().jira.fieldsByIssueType; - req.reply(response); +// FLAKY: https://github.com/elastic/kibana/issues/165712 +describe( + 'Cases connector incident fields', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + before(() => { + cleanKibana(); + login(); }); - cy.intercept( - 'POST', - `/api/actions/connector/${getConnectorIds().resilient}/_execute`, - (req) => { - const response = - req.body.params.subAction === 'incidentTypes' - ? getExecuteResponses().resilient.incidentTypes - : getExecuteResponses().resilient.severity; + beforeEach(() => { + login(); + cy.intercept('GET', '/api/cases/configure/connectors/_find', getMockConnectorsResponse()); + cy.intercept('POST', `/api/actions/connector/${getConnectorIds().sn}/_execute`, (req) => { + const response = + req.body.params.subAction === 'getChoices' + ? getExecuteResponses().servicenow.choices + : { status: 'ok', data: [] }; req.reply(response); - } - ); - cy.intercept('POST', `/api/cases/**/connector/${getConnectorIds().resilient}/_push`, (req) => { - req.reply(getCaseResponse()); + }); + cy.intercept('POST', `/api/actions/connector/${getConnectorIds().jira}/_execute`, (req) => { + const response = + req.body.params.subAction === 'issueTypes' + ? getExecuteResponses().jira.issueTypes + : getExecuteResponses().jira.fieldsByIssueType; + req.reply(response); + }); + cy.intercept( + 'POST', + `/api/actions/connector/${getConnectorIds().resilient}/_execute`, + (req) => { + const response = + req.body.params.subAction === 'incidentTypes' + ? getExecuteResponses().resilient.incidentTypes + : getExecuteResponses().resilient.severity; + + req.reply(response); + } + ); + cy.intercept( + 'POST', + `/api/cases/**/connector/${getConnectorIds().resilient}/_push`, + (req) => { + req.reply(getCaseResponse()); + } + ); }); - }); - it('Correct incident fields show when connector is changed', () => { - visitWithoutDateRange(CASES_URL); - goToCreateNewCase(); - fillCasesMandatoryfields(getCase1()); - fillJiraConnectorOptions(getJiraConnectorOptions()); - fillServiceNowConnectorOptions(getServiceNowConnectorOptions()); - fillIbmResilientConnectorOptions(getIbmResilientConnectorOptions()); - createCase(); + it('Correct incident fields show when connector is changed', () => { + visitWithoutDateRange(CASES_URL); + goToCreateNewCase(); + fillCasesMandatoryfields(getCase1()); + fillJiraConnectorOptions(getJiraConnectorOptions()); + fillServiceNowConnectorOptions(getServiceNowConnectorOptions()); + fillIbmResilientConnectorOptions(getIbmResilientConnectorOptions()); + createCase(); - cy.get(CONNECTOR_TITLE).should('have.text', getIbmResilientConnectorOptions().title); - cy.get(CONNECTOR_CARD_DETAILS).should( - 'have.text', - `Incident Types: ${getIbmResilientConnectorOptions().incidentTypes.join(', ')}Severity: ${ - getIbmResilientConnectorOptions().severity - }` - ); - }); -}); + cy.get(CONNECTOR_TITLE).should('have.text', getIbmResilientConnectorOptions().title); + cy.get(CONNECTOR_CARD_DETAILS).should( + 'have.text', + `Incident Types: ${getIbmResilientConnectorOptions().incidentTypes.join(', ')}Severity: ${ + getIbmResilientConnectorOptions().severity + }` + ); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/enable_risk_score.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/enable_risk_score.cy.ts index 47a9218981ba7..576533600b9dc 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/enable_risk_score.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/enable_risk_score.cy.ts @@ -32,7 +32,8 @@ import { ENTITY_ANALYTICS_URL } from '../../../urls/navigation'; const spaceId = 'default'; -describe('Enable risk scores', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165644 +describe('Enable risk scores', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics_serverless_splash_screen.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics_serverless_splash_screen.cy.ts index 7ae72fa4e3f4d..db5e0fa1bb67f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics_serverless_splash_screen.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics_serverless_splash_screen.cy.ts @@ -11,10 +11,11 @@ import { ENTITY_ANALYTICS_URL } from '../../../urls/navigation'; import { PAYWALL_DESCRIPTION } from '../../../screens/entity_analytics_serverless_splash'; +// FLAKY: https://github.com/elastic/kibana/issues/165685 describe( 'Entity Analytics Dashboard in Serverless', { - tags: '@serverless', + tags: ['@serverless', '@brokenInServerless'], env: { ftrConfig: { productTypes: [ diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/upgrade_risk_score.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/upgrade_risk_score.cy.ts index fcb33ff4de239..5b8ba030ade12 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/upgrade_risk_score.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/upgrade_risk_score.cy.ts @@ -38,7 +38,8 @@ import { ENTITY_ANALYTICS_URL } from '../../../urls/navigation'; const spaceId = 'default'; -describe('Upgrade risk scores', { tags: ['@ess', '@serverless'] }, () => { +// Flaky on serverless +describe('Upgrade risk scores', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/overflow_items.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/overflow_items.cy.ts index 9ac413d75a6d2..0023054fb44a2 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/overflow_items.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/network/overflow_items.cy.ts @@ -22,7 +22,8 @@ import { NETWORK_URL } from '../../../urls/navigation'; const testDomainOne = 'myTest'; const testDomainTwo = 'myTest2'; -describe('Overflow items', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165692 +describe('Overflow items', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { context('Network stats and tables', () => { before(() => { cy.task('esArchiverLoad', { archiveName: 'network' }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/pagination/pagination.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/pagination/pagination.cy.ts index 1e7f7d7514404..f7257d4666d69 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/pagination/pagination.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/pagination/pagination.cy.ts @@ -20,7 +20,8 @@ import { ALL_HOSTS_TABLE } from '../../../screens/hosts/all_hosts'; import { ALL_USERS_TABLE } from '../../../screens/users/all_users'; import { goToTablePage, sortFirstTableColumn } from '../../../tasks/table_pagination'; -describe('Pagination', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165968 +describe('Pagination', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { describe('Host uncommon processes table)', () => { before(() => { cy.task('esArchiverLoad', { archiveName: 'host_uncommon_processes' }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts index b4c33004e9675..4f1c23aa4ebf8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts @@ -22,7 +22,8 @@ import { getHostIpFilter } from '../../objects/filter'; import { HOSTS_URL } from '../../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../../tasks/hosts/all_hosts'; -describe('SearchBar', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165637 +describe('SearchBar', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { login(); visit(HOSTS_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts index 1604d1fdbe692..de204fd08f62e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts @@ -29,12 +29,12 @@ import { cleanKibana } from '../../../tasks/common'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; import { login, visit, visitWithoutDateRange } from '../../../tasks/login'; import { getNewRule, getUnmappedRule } from '../../../objects/rule'; -import { ALERTS_URL } from '../../../urls/navigation'; +import { ALERTS_URL, ruleDetailsUrl } from '../../../urls/navigation'; import { tablePageSelector } from '../../../screens/table_pagination'; import { ALERTS_TABLE_COUNT } from '../../../screens/timeline'; import { ALERT_SUMMARY_SEVERITY_DONUT_CHART } from '../../../screens/alerts'; import { getLocalstorageEntryAsObject } from '../../../helpers/common'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; +import { waitForPageToBeLoaded as waitForRuleDetailsPageToBeLoaded } from '../../../tasks/rule_details'; describe('Alert details flyout', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { describe('Basic functions', () => { @@ -179,8 +179,13 @@ describe('Alert details flyout', { tags: ['@ess', '@serverless', '@brokenInServe }); describe('Localstorage management', () => { + const ARCHIVED_RULE_ID = '7015a3e2-e4ea-11ed-8c11-49608884878f'; + const ARCHIVED_RULE_NAME = 'Endpoint Security'; + before(() => { cleanKibana(); + + // It just imports an alert without a rule but rule details page should work anyway cy.task('esArchiverLoad', { archiveName: 'query_alert', useCreate: true, docsOnly: true }); }); @@ -230,7 +235,10 @@ describe('Alert details flyout', { tags: ['@ess', '@serverless', '@brokenInServe it('should remove the flyout state from localstorage when navigating away without closing the flyout', () => { cy.get(OVERVIEW_RULE).should('be.visible'); - goToRuleDetails(); + + visitWithoutDateRange(ruleDetailsUrl(ARCHIVED_RULE_ID)); + waitForRuleDetailsPageToBeLoaded(ARCHIVED_RULE_NAME); + const localStorageCheck = () => cy.getAllLocalStorage().then((storage) => { const securityDataTable = getLocalstorageEntryAsObject(storage, 'securityDataTable'); @@ -242,7 +250,10 @@ describe('Alert details flyout', { tags: ['@ess', '@serverless', '@brokenInServe it('should not reopen the flyout when navigating away from the alerts page and returning to it', () => { cy.get(OVERVIEW_RULE).should('be.visible'); - goToRuleDetails(); + + visitWithoutDateRange(ruleDetailsUrl(ARCHIVED_RULE_ID)); + waitForRuleDetailsPageToBeLoaded(ARCHIVED_RULE_NAME); + visit(ALERTS_URL); cy.get(OVERVIEW_RULE).should('not.exist'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts index d57ab19a0f4fe..6b869268fa15b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts @@ -9,14 +9,13 @@ import { getBuildingBlockRule } from '../../../objects/rule'; import { OVERVIEW_ALERTS_HISTOGRAM_EMPTY } from '../../../screens/overview'; import { HIGHLIGHTED_ROWS_IN_TABLE } from '../../../screens/rule_details'; import { OVERVIEW } from '../../../screens/security_header'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; import { createRule } from '../../../tasks/api_calls/rules'; import { cleanKibana } from '../../../tasks/common'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; import { login, visitWithoutDateRange } from '../../../tasks/login'; import { waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; import { navigateFromHeaderTo } from '../../../tasks/security_header'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; +import { ruleDetailsUrl } from '../../../urls/navigation'; const EXPECTED_NUMBER_OF_ALERTS = 5; @@ -26,19 +25,21 @@ describe( () => { before(() => { cy.task('esArchiverLoad', { archiveName: 'auditbeat_big' }); - cleanKibana(); - login(); - }); - beforeEach(() => { - createRule(getBuildingBlockRule()); }); + after(() => { cy.task('esArchiverUnload', 'auditbeat_big'); }); + beforeEach(() => { + cleanKibana(); + login(); + createRule(getBuildingBlockRule()).then((rule) => + visitWithoutDateRange(ruleDetailsUrl(rule.body.id)) + ); + }); + it('Alerts should be visible on the Rule Detail page and not visible on the Overview page', () => { - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); waitForTheRuleToBeExecuted(); // Check that generated events are visible on the Details page diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 969934eb5fee1..02c25f751e6a6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -268,13 +268,13 @@ describe( cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) .eq(0) .should('be.visible') - .and('have.text', '0 threat match detected'); // TODO work on getting proper IoC data to get proper data here + .and('have.text', '0 threat matches detected'); // TODO work on getting proper IoC data to get proper data here // field with threat enrichement cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) .eq(1) .should('be.visible') - .and('have.text', '0 field enriched with threat intelligence'); // TODO work on getting proper IoC data to get proper data here + .and('have.text', '0 fields enriched with threat intelligence'); // TODO work on getting proper IoC data to get proper data here }); cy.log('should navigate to left panel Threat Intelligence tab'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts index f31d75cf960ff..9b47afd241567 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts @@ -48,7 +48,8 @@ import { openTimeline, waitForTimelinesPanelToBeLoaded } from '../../../tasks/ti import { TIMELINES_URL } from '../../../urls/navigation'; -describe('Timeline Templates', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165661 +describe('Timeline Templates', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts index a87061d02d513..36e5205099b71 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts @@ -17,7 +17,9 @@ import { createTimelineTemplate } from '../../../tasks/api_calls/timelines'; import { cleanKibana } from '../../../tasks/common'; import { searchByTitle } from '../../../tasks/table_pagination'; -describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165760 +// FLAKY: https://github.com/elastic/kibana/issues/165645 +describe('Export timelines', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts index 6ae1b15a4f8ad..7986303e7aa64 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts @@ -42,7 +42,7 @@ import { import { OVERVIEW_URL, TIMELINE_TEMPLATES_URL } from '../../../urls/navigation'; -describe.skip('Create a timeline from a template', { tags: ['@ess', '@serverless'] }, () => { +describe('Create a timeline from a template', { tags: ['@ess', '@serverless'] }, () => { before(() => { deleteTimelines(); login(); @@ -109,9 +109,9 @@ describe('Timelines', (): void => { }); }); - describe.skip( + describe( 'Creates a timeline by clicking untitled timeline from bottom bar', - { tags: '@brokenInServerless' }, + { tags: ['@ess', '@brokenInServerless'] }, () => { beforeEach(() => { login(); @@ -138,7 +138,8 @@ describe('Timelines', (): void => { cy.get(LOCKED_ICON).should('be.visible'); }); - it('can be added notes', () => { + // TO-DO: Issue 163398 + it.skip('can be added notes', () => { addNotesToTimeline(getTimeline().notes); cy.get(TIMELINE_TAB_CONTENT_GRAPHS_NOTES) .find(NOTES_TEXT) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/data_providers.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/data_providers.cy.ts index c3add90e6531f..0caaefd1c5e69 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/data_providers.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/data_providers.cy.ts @@ -29,61 +29,66 @@ import { getTimeline } from '../../../objects/timeline'; import { HOSTS_URL } from '../../../urls/navigation'; import { cleanKibana, scrollToBottom } from '../../../tasks/common'; -describe('timeline data providers', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - }); +// Failing in serverless +describe( + 'timeline data providers', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + before(() => { + cleanKibana(); + }); - beforeEach(() => { - login(); - visit(HOSTS_URL); - waitForAllHostsToBeLoaded(); - scrollToBottom(); - createNewTimeline(); - addNameAndDescriptionToTimeline(getTimeline()); - populateTimeline(); - }); + beforeEach(() => { + login(); + visit(HOSTS_URL); + waitForAllHostsToBeLoaded(); + scrollToBottom(); + createNewTimeline(); + addNameAndDescriptionToTimeline(getTimeline()); + populateTimeline(); + }); - it('displays the data provider action menu when Enter is pressed', () => { - addDataProvider({ field: 'host.name', operator: 'exists' }); + it('displays the data provider action menu when Enter is pressed', () => { + addDataProvider({ field: 'host.name', operator: 'exists' }); - cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('not.exist'); - cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`).focus(); - cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`) - .first() - .parent() - .type('{enter}'); + cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('not.exist'); + cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`).focus(); + cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`) + .first() + .parent() + .type('{enter}'); + + cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('exist'); + }); - cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('exist'); - }); + it.skip( + 'persists timeline when data provider is updated by dragging a field from data grid', + { tags: ['@brokenInServerless'] }, + () => { + updateDataProviderbyDraggingField('host.name', 0); + waitForTimelineChanges(); + cy.reload(); + cy.get(`${GET_TIMELINE_GRID_CELL('host.name')}`) + .first() + .then((hostname) => { + cy.get(TIMELINE_DATA_PROVIDERS_CONTAINER).contains(`host.name: "${hostname.text()}"`); + }); + } + ); - it.skip( - 'persists timeline when data provider is updated by dragging a field from data grid', - { tags: ['@brokenInServerless'] }, - () => { - updateDataProviderbyDraggingField('host.name', 0); + it('persists timeline when a field is added by hover action "Add To Timeline" in data provider ', () => { + addDataProvider({ field: 'host.name', operator: 'exists' }); + waitForTimelineChanges(); + updateDataProviderByFieldHoverAction('host.name', 0); waitForTimelineChanges(); cy.reload(); cy.get(`${GET_TIMELINE_GRID_CELL('host.name')}`) .first() .then((hostname) => { - cy.get(TIMELINE_DATA_PROVIDERS_CONTAINER).contains(`host.name: "${hostname.text()}"`); - }); - } - ); - - it('persists timeline when a field is added by hover action "Add To Timeline" in data provider ', () => { - addDataProvider({ field: 'host.name', operator: 'exists' }); - waitForTimelineChanges(); - updateDataProviderByFieldHoverAction('host.name', 0); - waitForTimelineChanges(); - cy.reload(); - cy.get(`${GET_TIMELINE_GRID_CELL('host.name')}`) - .first() - .then((hostname) => { - cy.get(TIMELINE_DATA_PROVIDERS_CONTAINER).should((dataProviderContainer) => { - expect(dataProviderContainer).to.contain(`host.name: "${hostname.text()}"`); + cy.get(TIMELINE_DATA_PROVIDERS_CONTAINER).should((dataProviderContainer) => { + expect(dataProviderContainer).to.contain(`host.name: "${hostname.text()}"`); + }); }); - }); - }); -}); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts index 0325117e6c017..41cd4e02dca29 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts @@ -22,11 +22,12 @@ const INITIAL_START_DATE = 'Jan 18, 2021 @ 20:33:29.186'; const INITIAL_END_DATE = 'Jan 19, 2024 @ 20:33:29.186'; const TIMESTAMP_COLUMN_NAME = '@timestamp'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/165650 +describe.skip( `Discover Datagrid Cell Actions`, { env: { ftrConfig: { enableExperimental: ['discoverInTimeline'] } }, - tags: ['@ess', '@serverless'], + tags: ['@serverless', '@brokenInServerless'], }, () => { beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/discover_state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/discover_state.cy.ts index 510dc37f14f55..a6d796585261f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/discover_state.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/discover_state.cy.ts @@ -34,11 +34,13 @@ import { ALERTS, CSP_FINDINGS } from '../../../../screens/security_header'; const INITIAL_START_DATE = 'Jan 18, 2021 @ 20:33:29.186'; const INITIAL_END_DATE = 'Jan 19, 2024 @ 20:33:29.186'; +// FLAKY: https://github.com/elastic/kibana/issues/165663 +// FLAKY: https://github.com/elastic/kibana/issues/165747 describe( 'Discover State', { env: { ftrConfig: { enableExperimental: ['discoverInTimeline'] } }, - tags: ['@ess', '@serverless'], + tags: ['@ess', '@serverless', '@brokenInServerless'], }, () => { beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts index 56020248a9133..c676f08a950ad 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts @@ -20,7 +20,8 @@ import { createTimeline } from '../../../tasks/api_calls/timelines'; import { expectedExportedTimeline, getTimeline } from '../../../objects/timeline'; import { cleanKibana } from '../../../tasks/common'; -describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165744 +describe('Export timelines', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/fields_browser.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/fields_browser.cy.ts index 4806098a37d1c..09e0885a8542d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/fields_browser.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/fields_browser.cy.ts @@ -49,7 +49,8 @@ const defaultHeaders = [ { id: 'user.name' }, ]; -describe('Fields Browser', { tags: ['@ess', '@serverless'] }, () => { +// Flaky in serverless tests +describe('Fields Browser', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/full_screen.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/full_screen.cy.ts index 78aeca210a4e0..3f07971d66f3c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/full_screen.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/full_screen.cy.ts @@ -18,7 +18,8 @@ import { populateTimeline } from '../../../tasks/timeline'; import { HOSTS_URL } from '../../../urls/navigation'; -describe('Toggle full screen', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165638 +describe('Toggle full screen', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/inspect.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/inspect.cy.ts index 2c468d46036aa..bf7fc80857754 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/inspect.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/inspect.cy.ts @@ -13,7 +13,8 @@ import { executeTimelineKQL, openTimelineInspectButton } from '../../../tasks/ti import { HOSTS_URL } from '../../../urls/navigation'; -describe('Inspect', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165688 +describe('Inspect', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { context('Timeline', () => { it('inspects the timeline', () => { const hostExistsQuery = 'host.name: *'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/pagination.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/pagination.cy.ts index a8ab36c7bd837..0fa319ec40021 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/pagination.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/pagination.cy.ts @@ -22,8 +22,9 @@ import { populateTimeline } from '../../../tasks/timeline'; import { HOSTS_URL } from '../../../urls/navigation'; +// Flaky on serverless const defaultPageSize = 25; -describe('Pagination', { tags: ['@ess', '@serverless'] }, () => { +describe('Pagination', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { before(() => { cleanKibana(); cy.task('esArchiverLoad', { archiveName: 'timeline' }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/query_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/query_tab.cy.ts index 037b03ea7e330..a3c4f30455966 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/query_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/query_tab.cy.ts @@ -14,7 +14,6 @@ import { TIMELINE_QUERY, NOTE_CARD_CONTENT, } from '../../../screens/timeline'; -import { addNoteToTimeline } from '../../../tasks/api_calls/notes'; import { createTimeline } from '../../../tasks/api_calls/timelines'; import { cleanKibana } from '../../../tasks/common'; @@ -23,7 +22,6 @@ import { login, visitWithoutDateRange } from '../../../tasks/login'; import { addFilter, openTimelineById, - persistNoteToFirstEvent, pinFirstEvent, refreshTimelinesUntilTimeLinePresent, } from '../../../tasks/timeline'; @@ -44,14 +42,16 @@ describe.skip('Timeline query tab', { tags: ['@ess', '@serverless'] }, () => { .then(() => cy.wrap(timelineId).as('timelineId')) // eslint-disable-next-line cypress/no-unnecessary-waiting .then(() => cy.wait(1000)) - .then(() => - addNoteToTimeline(getTimeline().notes, timelineId).should((response) => - expect(response.status).to.equal(200) - ) - ) + // TO-DO: Issue 163398 + // .then(() => + // addNoteToTimeline(getTimeline().notes, timelineId).should((response) => + // expect(response.status).to.equal(200) + // ) + // ) .then(() => openTimelineById(timelineId)) .then(() => pinFirstEvent()) - .then(() => persistNoteToFirstEvent('event note')) + // TO-DO: Issue 163398 + // .then(() => persistNoteToFirstEvent('event note')) .then(() => addFilter(getTimeline().filter)); }); }); @@ -67,7 +67,8 @@ describe.skip('Timeline query tab', { tags: ['@ess', '@serverless'] }, () => { cy.get(TIMELINE_QUERY).should('have.text', `${getTimeline().query}`); }); - it('should be able to add event note', () => { + // TO-DO: Issue 163398 + it.skip('should be able to add event note', () => { cy.get(NOTE_CARD_CONTENT).should('contain', 'event note'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/toggle_column.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/toggle_column.cy.ts index 5cdb4462839c2..c2ca620a40554 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/toggle_column.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/toggle_column.cy.ts @@ -19,30 +19,34 @@ import { import { HOSTS_URL } from '../../../urls/navigation'; -describe('toggle column in timeline', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); - }); - - beforeEach(() => { - login(); - visit(HOSTS_URL); - openTimelineUsingToggle(); - populateTimeline(); - }); - - it('removes the @timestamp field from the timeline when the user un-checks the toggle', () => { - expandFirstTimelineEventDetails(); - clickTimestampToggleField(); - - cy.get(TIMESTAMP_HEADER_FIELD).should('not.exist'); - }); - - it('adds the _id field to the timeline when the user checks the field', () => { - expandFirstTimelineEventDetails(); - clickIdToggleField(); - - cy.get(ID_HEADER_FIELD).should('exist'); - }); -}); +describe( + 'toggle column in timeline', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + before(() => { + cleanKibana(); + cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); + }); + + beforeEach(() => { + login(); + visit(HOSTS_URL); + openTimelineUsingToggle(); + populateTimeline(); + }); + + it('removes the @timestamp field from the timeline when the user un-checks the toggle', () => { + expandFirstTimelineEventDetails(); + clickTimestampToggleField(); + + cy.get(TIMESTAMP_HEADER_FIELD).should('not.exist'); + }); + + it('adds the _id field to the timeline when the user checks the field', () => { + expandFirstTimelineEventDetails(); + clickIdToggleField(); + + cy.get(ID_HEADER_FIELD).should('exist'); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts index d6936f86e9e89..6fed0acc748c0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts @@ -15,7 +15,9 @@ import { import { login, visit } from '../../tasks/login'; import { OVERVIEW_URL } from '../../urls/navigation'; -describe('CTI Link Panel', { tags: ['@ess', '@serverless'] }, () => { +// TODO: https://github.com/elastic/kibana/issues/161539 +// FLAKY: https://github.com/elastic/kibana/issues/165709 +describe.skip('CTI Link Panel', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { beforeEach(() => { login(); }); @@ -30,35 +32,40 @@ describe('CTI Link Panel', { tags: ['@ess', '@serverless'] }, () => { .and('match', /app\/integrations\/browse\/threat_intel/); }); - describe('enabled threat intel module', { tags: ['@brokenInServerless'] }, () => { - before(() => { - // illegal_argument_exception: unknown setting [index.lifecycle.name] - cy.task('esArchiverLoad', { archiveName: 'threat_indicator' }); - }); + // TODO: https://github.com/elastic/kibana/issues/161539 + describe( + 'enabled threat intel module', + { tags: ['@ess', '@serverless', '@brokenInServerless'] }, + () => { + before(() => { + // illegal_argument_exception: unknown setting [index.lifecycle.name] + cy.task('esArchiverLoad', { archiveName: 'threat_indicator' }); + }); - beforeEach(() => { - login(); - }); + beforeEach(() => { + login(); + }); - after(() => { - cy.task('esArchiverUnload', 'threat_indicator'); - }); + after(() => { + cy.task('esArchiverUnload', 'threat_indicator'); + }); - it('renders disabled dashboard module as expected when there are no events in the selected time period', () => { - visit( - `${OVERVIEW_URL}?sourcerer=(timerange:(from:%272021-07-08T04:00:00.000Z%27,kind:absolute,to:%272021-07-09T03:59:59.999Z%27))` - ); - cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist'); - cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 indicators'); - }); + it('renders disabled dashboard module as expected when there are no events in the selected time period', () => { + visit( + `${OVERVIEW_URL}?sourcerer=(timerange:(from:%272021-07-08T04:00:00.000Z%27,kind:absolute,to:%272021-07-09T03:59:59.999Z%27))` + ); + cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist'); + cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 indicators'); + }); - it('renders dashboard module as expected when there are events in the selected time period', () => { - visit(OVERVIEW_URL); + it('renders dashboard module as expected when there are events in the selected time period', () => { + visit(OVERVIEW_URL); - cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist'); - cy.get(OVERVIEW_CTI_LINKS).should('not.contain.text', 'Anomali'); - cy.get(OVERVIEW_CTI_LINKS).should('contain.text', 'AbuseCH malware'); - cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 indicator'); - }); - }); + cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist'); + cy.get(OVERVIEW_CTI_LINKS).should('not.contain.text', 'Anomali'); + cy.get(OVERVIEW_CTI_LINKS).should('contain.text', 'AbuseCH malware'); + cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 indicator'); + }); + } + ); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/urls/not_found.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/urls/not_found.cy.ts index fc1e08db5512e..0233442eef259 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/urls/not_found.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/urls/not_found.cy.ts @@ -24,7 +24,8 @@ import { NOT_FOUND } from '../../screens/common/page'; const mockRuleId = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; -describe('Display not found page', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/165710 +describe('Display not found page', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { login(); visit(TIMELINES_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts index 586a4188b84b1..13ff441e64ee5 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts @@ -491,8 +491,6 @@ export const indicatorRuleMatchingDoc = { matchedIndex: 'logs-ti_abusech.malware', }; -export const duplicatedRuleName = `${getNewThreatIndicatorRule().name} [Duplicate]`; - export const getSeveritiesOverride = (): string[] => ['Low', 'Medium', 'High', 'Critical']; export const getEditedRule = (): QueryRuleCreateProps => diff --git a/x-pack/test/security_solution_cypress/cypress/screens/discover.ts b/x-pack/test/security_solution_cypress/cypress/screens/discover.ts index 577246e5f6568..72f0801fde778 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/discover.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/discover.ts @@ -45,7 +45,7 @@ export const DISCOVER_FIELDS_LOADING = getDataTestSubjectSelector( export const DISCOVER_DATA_GRID_UPDATING = getDataTestSubjectSelector('discoverDataGridUpdating'); -export const DISCOVER_DATA_GRID_LOADING = getDataTestSubjectSelector('discoverDataGridLoading'); +export const UNIFIED_DATA_TABLE_LOADING = getDataTestSubjectSelector('unifiedDataTableLoading'); export const DISCOVER_NO_RESULTS = getDataTestSubjectSelector('discoverNoResults'); @@ -54,7 +54,7 @@ export const DISCOVER_TABLE = getDataTestSubjectSelector('docTable'); export const GET_DISCOVER_DATA_GRID_CELL = (columnId: string, rowIndex: number) => { return `${DISCOVER_TABLE} ${getDataTestSubjectSelector( 'dataGridRowCell' - )}[data-gridcell-column-id="${columnId}"][data-gridcell-row-index="${rowIndex}"] .dscDiscoverGrid__cellValue`; + )}[data-gridcell-column-id="${columnId}"][data-gridcell-row-index="${rowIndex}"] .unifiedDataTable__cellValue`; }; export const GET_DISCOVER_DATA_GRID_CELL_HEADER = (columnId: string) => diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts index 15d0aeede669b..9761e04aadc86 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts @@ -6,10 +6,10 @@ */ import { RESPONSE_TAB_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/test_ids'; -import { RESPONSE_EMPTY_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids'; +import { RESPONSE_NO_DATA_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids'; import { getDataTestSubjectSelector } from '../../helpers/common'; export const DOCUMENT_DETAILS_FLYOUT_RESPONSE_TAB = getDataTestSubjectSelector(RESPONSE_TAB_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_RESPONSE_EMPTY = - getDataTestSubjectSelector(RESPONSE_EMPTY_TEST_ID); + getDataTestSubjectSelector(RESPONSE_NO_DATA_TEST_ID); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts index 7ea0712aae290..27916cd32163b 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts @@ -14,12 +14,12 @@ import { import { COLLAPSE_DETAILS_BUTTON_TEST_ID, EXPAND_DETAILS_BUTTON_TEST_ID, - FLYOUT_HEADER_CHAT_BUTTON_TEST_ID, - FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID, - FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID, - FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID, - FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID, - FLYOUT_HEADER_STATUS_BUTTON_TEST_ID, + CHAT_BUTTON_TEST_ID, + RISK_SCORE_TITLE_TEST_ID, + RISK_SCORE_VALUE_TEST_ID, + SEVERITY_TITLE_TEST_ID, + SEVERITY_VALUE_TEST_ID, + STATUS_BUTTON_TEST_ID, FLYOUT_HEADER_TITLE_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/right/components/test_ids'; import { getDataTestSubjectSelector } from '../../helpers/common'; @@ -43,24 +43,18 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB = getDataTestSubjectSelector(OVERVIEW_TAB_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB = getDataTestSubjectSelector(TABLE_TAB_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB = getDataTestSubjectSelector(JSON_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS = getDataTestSubjectSelector( - FLYOUT_HEADER_STATUS_BUTTON_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE = getDataTestSubjectSelector( - FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE = getDataTestSubjectSelector( - FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY = getDataTestSubjectSelector( - FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE = getDataTestSubjectSelector( - FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID -); -export const DOCUMENT_DETAILS_FLYOUT_HEADER_CHAT_BUTTON = getDataTestSubjectSelector( - FLYOUT_HEADER_CHAT_BUTTON_TEST_ID -); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS = + getDataTestSubjectSelector(STATUS_BUTTON_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE = + getDataTestSubjectSelector(RISK_SCORE_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE = + getDataTestSubjectSelector(RISK_SCORE_VALUE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY = + getDataTestSubjectSelector(SEVERITY_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE = + getDataTestSubjectSelector(SEVERITY_VALUE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_CHAT_BUTTON = + getDataTestSubjectSelector(CHAT_BUTTON_TEST_ID); /* Footer */ diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts index 7cf96daca6f0c..9c830339db486 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -30,14 +30,14 @@ import { RESPONSE_SECTION_HEADER_TEST_ID, RESPONSE_EMPTY_TEST_ID, INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, - INSIGHTS_CORRELATIONS_TEST_ID, - INSIGHTS_PREVALENCE_TEST_ID, + CORRELATIONS_TEST_ID, + PREVALENCE_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, - INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID, - INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, + CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, + CORRELATIONS_RELATED_CASES_TEST_ID, + CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID, INSIGHTS_ENTITIES_TEST_ID, REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, ANALYZER_PREVIEW_TEST_ID, @@ -118,42 +118,34 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_V /* Insights Correlations */ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER = - getDataTestSubjectSelector( - EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID) - ); + getDataTestSubjectSelector(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(CORRELATIONS_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT = - getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID)); + getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(CORRELATIONS_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_SUPPRESSED_ALERTS = - getDataTestSubjectSelector( - SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID) - ); + getDataTestSubjectSelector(SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_ANCESTRY = getDataTestSubjectSelector( - SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID) + SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID) ); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_SAME_SOURCE_EVENT = getDataTestSubjectSelector( - SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID) + SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID) ); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_SESSION = getDataTestSubjectSelector( - SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID) + SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID) ); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_CASES = - getDataTestSubjectSelector( - SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID) - ); + getDataTestSubjectSelector(SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID)); /* Insights Prevalence */ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER = - getDataTestSubjectSelector( - EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID) - ); + getDataTestSubjectSelector(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(PREVALENCE_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT = - getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID)); + getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(PREVALENCE_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_TEST_ID); + getDataTestSubjectSelector(PREVALENCE_TEST_ID); /* Visualization section */ diff --git a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts index 15408d3e57778..7df6b0408c19e 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts @@ -177,7 +177,7 @@ export const SAVE_DATA_PROVIDER_BTN = `[data-test-subj="save"]`; export const TIMELINE_DESCRIPTION = '[data-test-subj="timeline-description"]'; -export const TIMELINE_DESCRIPTION_INPUT = '[data-test-subj="save-timeline-description"]'; +export const TIMELINE_DESCRIPTION_INPUT = '[data-test-subj="edit-timeline-description"]'; export const TIMELINE_DROPPED_DATA_PROVIDERS = '[data-test-subj="providerContainer"]'; @@ -248,7 +248,7 @@ export const TIMELINE_KQLLANGUAGE_BUTTON = '[data-test-subj="kqlLanguageMenuItem export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; -export const TIMELINE_TITLE_INPUT = '[data-test-subj="save-timeline-title"]'; +export const TIMELINE_TITLE_INPUT = '[data-test-subj="edit-timeline-title"]'; export const TIMESTAMP_HEADER_FIELD = '[data-test-subj="header-text-@timestamp"]'; @@ -257,9 +257,9 @@ export const TIMESTAMP_TOGGLE_FIELD = export const TOGGLE_TIMELINE_EXPAND_EVENT = '[data-test-subj="expand-event"]'; -export const TIMELINE_EDIT_MODAL_OPEN_BUTTON = '[data-test-subj="save-timeline-button-icon"]'; +export const TIMELINE_EDIT_MODAL_OPEN_BUTTON = '[data-test-subj="edit-timeline-button-icon"]'; -export const TIMELINE_SAVE_MODAL = '[data-test-subj="save-timeline-modal"]'; +export const TIMELINE_SAVE_MODAL = '[data-test-subj="edit-timeline-modal"]'; export const TIMELINE_EDIT_MODAL_SAVE_BUTTON = '[data-test-subj="save-button"]'; @@ -299,9 +299,9 @@ export const TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN = export const USER_KPI = '[data-test-subj="siem-timeline-user-kpi"]'; -export const EDIT_TIMELINE_BTN = '[data-test-subj="save-timeline-button-icon"]'; +export const EDIT_TIMELINE_BTN = '[data-test-subj="edit-timeline-button-icon"]'; -export const EDIT_TIMELINE_TOOLTIP = '[data-test-subj="save-timeline-btn-tooltip"]'; +export const EDIT_TIMELINE_TOOLTIP = '[data-test-subj="edit-timeline-btn-tooltip"]'; export const ALERT_TABLE_SEVERITY_VALUES = '[data-test-subj="formatted-field-kibana.alert.severity"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/support/e2e.js b/x-pack/test/security_solution_cypress/cypress/support/e2e.js index 477c2606153b7..4335470845f9b 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/e2e.js +++ b/x-pack/test/security_solution_cypress/cypress/support/e2e.js @@ -25,6 +25,10 @@ import './commands'; import 'cypress-real-events/support'; import registerCypressGrep from '@cypress/grep'; +before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat' }); +}); + registerCypressGrep(); Cypress.on('uncaught:exception', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/support/es_archiver.ts b/x-pack/test/security_solution_cypress/cypress/support/es_archiver.ts index 0b1bc1cb68de8..6fcbebf0a377e 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/es_archiver.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/es_archiver.ts @@ -28,7 +28,7 @@ export const esArchiver = ( const kbnClient = new KbnClient({ log, - url: config.env.CYPRESS_BASE_URL as string, + url: config.env.BASE_URL as string, ...(config.env.ELASTICSEARCH_URL.includes('https') ? { certificateAuthorities: [Fs.readFileSync(CA_CERT_PATH)] } : {}), diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts index 5f66f5513ed17..304e35e6826bf 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { duplicatedRuleName } from '../objects/rule'; +import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { COLLAPSED_ACTION_BTN, CUSTOM_RULES_BTN, @@ -65,6 +65,7 @@ import { PAGE_CONTENT_SPINNER } from '../screens/common/page'; import { goToRuleEditSettings } from './rule_details'; import { goToActionsStepTab } from './create_new_rule'; +import { setKibanaSetting } from './api_calls/kibana_advanced_settings'; export const getRulesManagementTableRows = () => cy.get(RULES_MANAGEMENT_TABLE).find(RULES_ROW); @@ -107,8 +108,8 @@ export const duplicateRuleFromMenu = () => { * Check that the duplicated rule is on the table * and it is disabled (default) */ -export const checkDuplicatedRule = () => { - cy.contains(RULE_NAME, duplicatedRuleName) +export const checkDuplicatedRule = (ruleName: string): void => { + cy.contains(RULE_NAME, ruleName) .parents(RULES_ROW) .find(RULE_SWITCH) .should('have.attr', 'aria-checked', 'false'); @@ -193,14 +194,7 @@ export const filterByDisabledRules = () => { cy.get(DISABLED_RULES_BTN).click(); }; -/** - * @deprecated use goToTheRuleDetailsOf - */ -export const goToRuleDetails = () => { - cy.get(RULE_NAME).first().click(); -}; - -export const goToTheRuleDetailsOf = (ruleName: string) => { +export const goToRuleDetailsOf = (ruleName: string) => { cy.contains(RULE_NAME, ruleName).click(); cy.get(PAGE_CONTENT_SPINNER).should('be.visible'); @@ -502,7 +496,7 @@ export const closeErrorToast = () => { }; export const goToEditRuleActionsSettingsOf = (name: string) => { - goToTheRuleDetailsOf(name); + goToRuleDetailsOf(name); goToRuleEditSettings(); // wait until first step loads completely. Otherwise cypress stuck at the first edit page cy.get(EDIT_SUBMIT_BUTTON).should('be.enabled'); @@ -524,3 +518,29 @@ const unselectRuleByName = (ruleName: string) => { cy.log(`Make sure rule "${ruleName}" has been unselected`); getRuleRow(ruleName).find(EUI_CHECKBOX).should('not.be.checked'); }; + +/** + * Set Kibana `securitySolution:rulesTableRefresh` setting looking like + * + * ``` + * { "on": true, "value": 60000 } + * ``` + * + * @param enabled whether the auto-refresh is enabled + * @param refreshInterval refresh interval in milliseconds + */ +export const setRulesTableAutoRefreshIntervalSetting = ({ + enabled, + refreshInterval, +}: { + enabled: boolean; + refreshInterval: number; // milliseconds +}) => { + setKibanaSetting( + DEFAULT_RULES_TABLE_REFRESH_SETTING, + JSON.stringify({ + on: enabled, + value: refreshInterval, + }) + ); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts index 622e311fa0fd1..2d5bf00b3c381 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/exceptions.ts @@ -5,19 +5,29 @@ * 2.0. */ +import { CreateEndpointListItemResponse } from '@kbn/lists-plugin/common/api'; import type { ExceptionListSchema, ExceptionListItemSchema, + CreateEndpointListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; +import { ENDPOINT_LIST_ITEM_URL, ENDPOINT_LIST_URL } from '@kbn/securitysolution-list-constants'; import type { ExceptionList, ExceptionListItem, RuleExceptionItem } from '../../objects/exception'; import { rootRequest } from '../common'; -export const createEndpointExceptionList = () => - rootRequest({ +export const createEndpointExceptionList = () => + rootRequest({ method: 'POST', - url: '/api/endpoint_list', + url: ENDPOINT_LIST_URL, + headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + }); + +export const createEndpointExceptionListItem = (item: CreateEndpointListItemSchema) => + rootRequest({ + method: 'POST', + url: ENDPOINT_LIST_ITEM_URL, + body: item, headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, - failOnStatusCode: false, }); export const createExceptionList = ( diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts index 95647649259dc..5295b033fc7cf 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts @@ -25,13 +25,21 @@ const deleteAgentPolicies = () => { return rootRequest<{ items: Array<{ id: string }> }>({ method: 'GET', url: 'api/fleet/agent_policies', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, }).then((response) => { response.body.items.forEach((item: { id: string }) => { rootRequest({ method: 'POST', url: `api/fleet/agent_policies/delete`, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, body: { agentPolicyId: item.id, }, @@ -44,12 +52,20 @@ const deletePackagePolicies = () => { return rootRequest<{ items: Array<{ id: string }> }>({ method: 'GET', url: 'api/fleet/package_policies', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, }).then((response) => { rootRequest({ method: 'POST', url: `api/fleet/package_policies/delete`, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, body: { packagePolicyIds: response.body.items.map((item: { id: string }) => item.id), }, @@ -61,7 +77,11 @@ const deletePackages = () => { return rootRequest<{ items: Array<{ status: string; name: string; version: string }> }>({ method: 'GET', url: 'api/fleet/epm/packages', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, }).then((response) => { response.body.items.forEach((item) => { if (item.status === 'installed') { @@ -71,6 +91,7 @@ const deletePackages = () => { headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', }, }); } diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts index f86cd0186c8c2..2c982adad5275 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/kibana_advanced_settings.ts @@ -5,30 +5,27 @@ * 2.0. */ +import { SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID } from '@kbn/management-settings-ids'; +import { ENABLE_EXPANDABLE_FLYOUT_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { rootRequest } from '../common'; -const kibanaSettings = (body: Cypress.RequestBody) => { +export const setKibanaSetting = (key: string, value: boolean | number | string) => { rootRequest({ method: 'POST', url: 'internal/kibana/settings', - body, + body: { changes: { [key]: value } }, headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, }); }; -const relatedIntegrationsBody = (status: boolean): Cypress.RequestBody => { - return { changes: { 'securitySolution:showRelatedIntegrations': status } }; -}; - export const enableRelatedIntegrations = () => { - kibanaSettings(relatedIntegrationsBody(true)); + setKibanaSetting(SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID, true); }; export const disableRelatedIntegrations = () => { - kibanaSettings(relatedIntegrationsBody(false)); + setKibanaSetting(SECURITY_SOLUTION_SHOW_RELATED_INTEGRATIONS_ID, false); }; export const disableExpandableFlyout = () => { - const body = { changes: { 'securitySolution:enableExpandableFlyout': false } }; - kibanaSettings(body); + setKibanaSetting(ENABLE_EXPANDABLE_FLYOUT_SETTING, false); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts index 3dfc2dc4e9a30..c1fd4cd3ef250 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -7,9 +7,9 @@ import { EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/shared/components/test_ids'; import { - INSIGHTS_CORRELATIONS_TEST_ID, + CORRELATIONS_TEST_ID, INSIGHTS_ENTITIES_TEST_ID, - INSIGHTS_PREVALENCE_TEST_ID, + PREVALENCE_TEST_ID, INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/right/components/test_ids'; import { @@ -79,7 +79,7 @@ export const navigateToThreatIntelligenceDetails = () => { * Click on the header in the right section, Insights, Correlations */ export const navigateToCorrelationsDetails = () => { - const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID); + const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(CORRELATIONS_TEST_ID); cy.get(TEST_ID).scrollIntoView(); cy.get(TEST_ID).should('be.visible').click(); }; @@ -88,7 +88,7 @@ export const navigateToCorrelationsDetails = () => { * Click on the view all button under the right section, Insights, Prevalence */ export const navigateToPrevalenceDetails = () => { - const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); + const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(PREVALENCE_TEST_ID); cy.get(TEST_ID).scrollIntoView(); cy.get(TEST_ID).should('be.visible').click(); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/lists.ts b/x-pack/test/security_solution_cypress/cypress/tasks/lists.ts index e9dd3d882b429..fcae40e1e8549 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/lists.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/lists.ts @@ -15,6 +15,12 @@ import { VALUE_LIST_TYPE_SELECTOR, } from '../screens/lists'; +export const KNOWN_VALUE_LIST_FILES = { + TEXT: 'value_list.txt', + IPs: 'ip_list.txt', + CIDRs: 'cidr_list.txt', +}; + export const createListsIndex = () => { cy.request({ method: 'POST', diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts index f0b7832e488c9..1383bcb4e9d71 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts @@ -12,6 +12,7 @@ import Url from 'url'; import type { ROLES } from '@kbn/security-solution-plugin/common/test'; import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '@kbn/security-solution-plugin/common/constants'; +import { LoginState } from '@kbn/security-plugin/common/login_state'; import { hostDetailsUrl, LOGOUT_URL, @@ -50,11 +51,6 @@ const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME'; */ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; -/** - * The Kibana server endpoint used for authentication - */ -const LOGIN_API_ENDPOINT = '/internal/security/login'; - /** * cy.visit will default to the baseUrl which uses the default kibana test user * This function will override that functionality in cy.visit by building the baseUrl @@ -141,53 +137,46 @@ export const deleteRoleAndUser = (role: ROLES) => { }); }; +const loginWithUsernameAndPassword = (username: string, password: string) => { + const baseUrl = Cypress.config().baseUrl; + if (!baseUrl) { + throw Error(`Cypress config baseUrl not set!`); + } + + const headers = { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }; + // programmatically authenticate without interacting with the Kibana login page + cy.request({ headers, url: `${baseUrl}/internal/security/login_state` }).then( + (loginState) => { + const basicProvider = loginState.body.selector.providers.find( + (provider) => provider.type === 'basic' + ); + return cy.request({ + url: `${baseUrl}/internal/security/login`, + method: 'POST', + headers, + body: { + providerType: basicProvider.type, + providerName: basicProvider.name, + currentURL: '/', + params: { username, password }, + }, + }); + } + ); +}; + export const loginWithUser = (user: User) => { cy.session(user, () => { - cy.request({ - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: '/', - params: { - username: user.username, - password: user.password, - }, - }, - headers: { - 'kbn-xsrf': 'cypress-creds-via-config', - 'x-elastic-internal-origin': 'security-solution', - }, - method: 'POST', - url: constructUrlWithUser(user, LOGIN_API_ENDPOINT), - }); + loginWithUsernameAndPassword(user.username, user.password); }); }; -const loginWithRole = async (role: ROLES) => { +const loginWithRole = (role: ROLES) => { postRoleAndUser(role); - const theUrl = new URL(String(Cypress.config().baseUrl)); - theUrl.username = role; - theUrl.password = Cypress.env(ELASTICSEARCH_PASSWORD); - cy.log(`origin: ${theUrl}`); + cy.log(`origin: ${Cypress.config().baseUrl}`); cy.session(role, () => { - cy.request({ - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: '/', - params: { - username: role, - password: 'changeme', - }, - }, - headers: { - 'kbn-xsrf': 'cypress-creds-via-config', - 'x-elastic-internal-origin': 'security-solution', - }, - method: 'POST', - url: getUrlWithRoute(role, LOGIN_API_ENDPOINT), - }); + loginWithUsernameAndPassword(role, 'changeme'); }); }; @@ -231,24 +220,7 @@ const loginViaEnvironmentCredentials = () => { const password = Cypress.env(ELASTICSEARCH_PASSWORD); cy.session([username, password], () => { - // programmatically authenticate without interacting with the Kibana login page - cy.request({ - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: '/', - params: { - username, - password, - }, - }, - headers: { - 'kbn-xsrf': 'cypress-creds-via-env', - 'x-elastic-internal-origin': 'security-solution', - }, - method: 'POST', - url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, - }); + loginWithUsernameAndPassword(username, password); }); }; @@ -264,26 +236,8 @@ const loginViaConfig = () => { // read the login details from `kibana.dev.yaml` cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => { - const config = yaml.safeLoad(kibanaDevYml); - - // programmatically authenticate without interacting with the Kibana login page - cy.request({ - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: '/', - params: { - username: config.elasticsearch.username, - password: config.elasticsearch.password, - }, - }, - headers: { - 'kbn-xsrf': 'cypress-creds-via-config', - 'x-elastic-internal-origin': 'security-solution', - }, - method: 'POST', - url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, - }); + const { username, password } = yaml.safeLoad(kibanaDevYml); + loginWithUsernameAndPassword(username, password); }); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts index 871cbcb82ab7a..b18905b443679 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts @@ -106,8 +106,8 @@ export const goToEndpointExceptionsTab = () => { }; export const openEditException = (index = 0) => { - cy.get(EXCEPTION_ITEM_ACTIONS_BUTTON).eq(index).click({ force: true }); - cy.get(EDIT_EXCEPTION_BTN).eq(index).click({ force: true }); + cy.get(EXCEPTION_ITEM_ACTIONS_BUTTON).eq(index).click(); + cy.get(EDIT_EXCEPTION_BTN).eq(index).click(); }; export const removeException = () => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rule_snoozing.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rule_snoozing.ts index 35b98d9404591..33ade4482951a 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/rule_snoozing.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rule_snoozing.ts @@ -36,7 +36,7 @@ export function snoozeRule(duration: SnoozeDuration): void { } export function expectSnoozeSuccessToast(): void { - cy.get(TOASTER).should('contain', 'Rule successfully snoozed'); + cy.get(TOASTER).should('contain', 'Rules notification successfully snoozed'); } export function expectSnoozeErrorToast(): void { @@ -72,7 +72,7 @@ export function unsnoozeRule(): void { } export function expectUnsnoozeSuccessToast(): void { - cy.get(TOASTER).should('contain', 'Rule successfully unsnoozed'); + cy.get(TOASTER).should('contain', 'Rules notification successfully unsnoozed'); } export function expectRuleUnsnoozed(): void { diff --git a/x-pack/test/security_solution_cypress/cypress/tsconfig.json b/x-pack/test/security_solution_cypress/cypress/tsconfig.json index ff1d9c7551f75..b82ce28aa8f04 100644 --- a/x-pack/test/security_solution_cypress/cypress/tsconfig.json +++ b/x-pack/test/security_solution_cypress/cypress/tsconfig.json @@ -1,21 +1,10 @@ { "extends": "../../../../tsconfig.base.json", - "include": [ - "**/*", - "fixtures/**/*.json" - ], - "exclude": [ - "target/**/*" - ], + "include": ["**/*", "fixtures/**/*.json"], + "exclude": ["target/**/*"], "compilerOptions": { "outDir": "target/types", - "types": [ - "cypress", - "cypress-file-upload", - "cypress-real-events", - "cypress-recurse", - "node", - ], + "types": ["cypress", "cypress-file-upload", "cypress-real-events", "cypress-recurse", "node"] }, "kbn_references": [ "@kbn/securitysolution-io-ts-alerting-types", @@ -46,5 +35,9 @@ "@kbn/dev-utils", "@kbn/expandable-flyout", "@kbn/config-schema", + "@kbn/lists-plugin", + "@kbn/securitysolution-list-constants", + "@kbn/security-plugin", + "@kbn/management-settings-ids" ] } diff --git a/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts b/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts index aa13d43a23ab6..eca48ae6fafff 100644 --- a/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts +++ b/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts @@ -57,7 +57,10 @@ export const DISCOVER_WITH_FILTER_URL = export const DISCOVER_WITH_PINNED_FILTER_URL = "/app/discover#/?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,index:security-solution-default,key:host.name,negate:!f,params:(query:test-host),type:phrase),query:(match_phrase:(host.name:test-host)))),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))"; -export const ruleDetailsUrl = (ruleId: string) => `app/security/rules/id/${ruleId}`; +export const ruleDetailsUrl = ( + ruleId: string, + section?: 'alerts' | 'rule_exceptions' | 'endpoint_exceptions' | 'execution_results' +) => `app/security/rules/id/${ruleId}${section ? `/${section}` : ''}`; export const detectionsRuleDetailsUrl = (ruleId: string) => `app/security/detections/rules/id/${ruleId}`; diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json index 140cd99b3df49..f4be9a442b3ef 100644 --- a/x-pack/test/security_solution_cypress/package.json +++ b/x-pack/test/security_solution_cypress/package.json @@ -5,11 +5,11 @@ "private": true, "license": "Elastic License 2.0", "scripts": { - "cypress": "../../../node_modules/.bin/cypress", - "cypress:open:ess": "TZ=UTC node ../../plugins/security_solution/scripts/start_cypress_parallel open --spec './cypress/e2e/**/*.cy.ts' --config-file ../../test/security_solution_cypress/cypress/cypress.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", + "cypress": "NODE_OPTIONS=--openssl-legacy-provider ../../../node_modules/.bin/cypress", + "cypress:open:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel open --spec './cypress/e2e/**/*.cy.ts' --config-file ../../test/security_solution_cypress/cypress/cypress.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", "cypress:run:ess": "yarn cypress:ess --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", "cypress:run:cases:ess": "yarn cypress:ess --spec './cypress/e2e/explore/cases/*.cy.ts'", - "cypress:ess": "TZ=UTC node ../../plugins/security_solution/scripts/start_cypress_parallel run --config-file ../../test/security_solution_cypress/cypress/cypress_ci.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", + "cypress:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel run --config-file ../../test/security_solution_cypress/cypress/cypress_ci.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", "cypress:run:respops:ess": "yarn cypress:ess --spec './cypress/e2e/(detection_alerts|detection_rules|exceptions)/*.cy.ts'", "cypress:investigations:run:ess": "yarn cypress:ess --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:ess": "yarn cypress:ess --spec './cypress/e2e/explore/**/*.cy.ts'", @@ -17,9 +17,9 @@ "cypress:burn:ess": "yarn cypress:ess --env burn=2", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results && yarn junit:transform && mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/", "junit:transform": "node ../../plugins/security_solution/scripts/junit_transformer --pathPattern '../../../target/kibana-security-solution/cypress/results/*.xml' --rootDirectory ../../../ --reportName 'Security Solution Cypress' --writeInPlace", - "cypress:serverless": "TZ=UTC node ../../plugins/security_solution/scripts/start_cypress_parallel --config-file ../../test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts --ftr-config-file ../../test/security_solution_cypress/serverless_config", + "cypress:serverless": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel --config-file ../../test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts --ftr-config-file ../../test/security_solution_cypress/serverless_config", "cypress:open:serverless": "yarn cypress:serverless open --config-file ../../test/security_solution_cypress/cypress/cypress_serverless.config.ts --spec './cypress/e2e/**/*.cy.ts'", - "cypress:run:serverless": "yarn cypress:serverless --spec '**/cypress/e2e/!(investigations|explore)/**/*.cy.ts'", + "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", "cypress:investigations:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/explore/**/*.cy.ts'", "cypress:changed-specs-only:serverless": "yarn cypress:serverless --changed-specs-only --env burn=2", diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 9514e63a12634..6e70f9364eff2 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -15,17 +15,9 @@ export async function SecuritySolutionConfigurableCypressTestRunner({ getService, }: FtrProviderContext) { const config = getService('config'); - const esArchiver = getService('esArchiver'); - - await esArchiver.load('x-pack/test/security_solution_cypress/es_archives/auditbeat'); return { FORCE_COLOR: '1', - CYPRESS_BASE_URL: Url.format(config.get('servers.kibana')), - CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), - CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), - CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), - baseUrl: Url.format(config.get('servers.kibana')), BASE_URL: Url.format(config.get('servers.kibana')), ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts index 9240c93428fab..5a0f0fdf93ec1 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts @@ -81,8 +81,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); }; - // Failing: See https://github.com/elastic/kibana/issues/139260 - describe.skip('Response Actions Responder', function () { + describe('Response Actions Responder', function () { let indexedData: IndexedHostsAndAlertsResponse; let endpointAgentId: string; @@ -183,6 +182,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Show event/alert details for the first one in the list await pageObjects.timeline.showEventDetails(); + // TODO: The index already exists error toast should not show up + // close and dismiss it if it does + if (await testSubjects.exists('globalToastList')) { + await testSubjects.click('toastCloseButton'); + } // Click responder from the take action button await testSubjects.click('take-action-dropdown-btn'); await testSubjects.clickWhenNotDisabled('endpointResponseActions-action-item'); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index 4eb92d2dee08b..b92a26e785127 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -44,8 +44,7 @@ export default function ({ getService }: FtrProviderContext) { const endpointTestResources = getService('endpointTestResources'); describe('test metadata apis', () => { - // FLAKY: https://github.com/elastic/kibana/issues/151854 - describe.skip('list endpoints GET route', () => { + describe('list endpoints GET route', () => { const numberOfHostsInFixture = 2; let agent1Timestamp: number; let agent2Timestamp: number; diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 558660c435b8a..bfa2bfdb2a275 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -141,5 +141,6 @@ "@kbn/aiops-utils", "@kbn/stack-alerts-plugin", "@kbn/apm-data-access-plugin", + "@kbn/profiling-utils", ] } diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts index 3474f55488eca..b1ff2187bc574 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts @@ -67,7 +67,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'enterpriseSearchContent', 'enterpriseSearchAnalytics', 'enterpriseSearchApplications', - 'enterpriseSearchEsre', + 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', 'enterpriseSearchElasticsearch', 'appSearch', @@ -97,7 +97,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'enterpriseSearchContent', 'enterpriseSearchAnalytics', 'enterpriseSearchApplications', - 'enterpriseSearchEsre', + 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', 'enterpriseSearchElasticsearch', 'appSearch', diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts index 5a56f31c2e7f1..fb7b3a112f6de 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts @@ -54,7 +54,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'enterpriseSearchContent', 'enterpriseSearchAnalytics', 'enterpriseSearchApplications', - 'enterpriseSearchEsre', + 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', 'enterpriseSearchElasticsearch', 'appSearch', @@ -74,7 +74,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'enterpriseSearchContent', 'enterpriseSearchAnalytics', 'enterpriseSearchApplications', - 'enterpriseSearchEsre', + 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', 'enterpriseSearchElasticsearch', 'observabilityAIAssistant', diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts b/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts index fc9871b07e59c..a52cb46b77c9f 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts @@ -31,7 +31,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'enterpriseSearchContent', 'enterpriseSearchAnalytics', 'enterpriseSearchApplications', - 'enterpriseSearchEsre', + 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', 'enterpriseSearchElasticsearch', 'appSearch', diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts index a6b55c1f6b32c..2778b8e54a13a 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts @@ -23,7 +23,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'enterpriseSearchContent', 'enterpriseSearchAnalytics', 'enterpriseSearchApplications', - 'enterpriseSearchEsre', + 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', 'enterpriseSearchElasticsearch', 'appSearch', diff --git a/x-pack/test_serverless/README.md b/x-pack/test_serverless/README.md index 84b1aaab676d6..6d7c6e350b9b2 100644 --- a/x-pack/test_serverless/README.md +++ b/x-pack/test_serverless/README.md @@ -5,6 +5,9 @@ The tests and helper methods (services, page objects) defined here in `serverless`, `serverless_observability`, `serverless_search` and `serverless_security` plugins. + For how to set up Docker for serverless ES images, please refer to + [packages/kbn-es/README](https://github.com/elastic/kibana/blob/main/packages/kbn-es/README.mdx). + ## Serverless testing structure and conventions ### Overview @@ -133,3 +136,21 @@ node scripts/functional_tests_server.js --config test_serverless/api_integration node scripts/functional_test_runner.js --config test_serverless/api_integration/test_suites/search/config.ts ``` + +## Run tests on MKI +There is no need to start servers locally, you just need to create MKI project and copy urls for Elasticsearch and Kibana. Make sure to update urls with username/password and port 443 for Elasticsearch. FTR has no control over MKI and can't update your projects so make sure your `config.ts` does not specify any custom arguments for Kibana or Elasticsearch. Otherwise, it will be ignored. You can run the tests from the `x-pack` directory: +``` +TEST_CLOUD=1 TEST_ES_URL="https://elastic:PASSWORD@ES_HOSTNAME:443" TEST_KIBANA_URL="https://elastic:PASSWORD@KIBANA_HOSTNAME" node scripts/functional_test_runner --config test_serverless/api_integration/test_suites/search/config.ts --exclude-tag=skipMKI +``` + +## Skipping tests for MKI run +The tests that are listed in the the regular `config.ts` generally should work in both Kibana CI and MKI. However some tests might not work properly against MKI projects by design. +Tag the tests with `skipMKI` to be excluded for MKI run. It works only for the `describe` block: +``` +describe("my test suite", async function() { + this.tags(['skipMKI']); + ... +}); +``` + +If you are running tests from your local against MKI projects, make sure to add `--exclude-tag=skipMKI` to your FTR command. diff --git a/x-pack/test_serverless/api_integration/services/alerting_api.ts b/x-pack/test_serverless/api_integration/services/alerting_api.ts index 8eb771d7eb11d..668dcdab82aa0 100644 --- a/x-pack/test_serverless/api_integration/services/alerting_api.ts +++ b/x-pack/test_serverless/api_integration/services/alerting_api.ts @@ -11,7 +11,7 @@ import type { } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; -import { ThresholdParams } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FtrProviderContext } from '../ftr_provider_context'; diff --git a/x-pack/test_serverless/api_integration/services/index.ts b/x-pack/test_serverless/api_integration/services/index.ts index 06e7c33fd7099..dd59c42545c03 100644 --- a/x-pack/test_serverless/api_integration/services/index.ts +++ b/x-pack/test_serverless/api_integration/services/index.ts @@ -6,23 +6,24 @@ */ import { GenericFtrProviderContext } from '@kbn/test'; -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { services as xpackApiIntegrationServices } from '../../../test/api_integration/services'; +import { services as deploymentAgnosticSharedServices } from '../../shared/services/deployment_agnostic_services'; import { services as svlSharedServices } from '../../shared/services'; -import { SvlCommonApiServiceProvider } from './svl_common_api'; import { AlertingApiProvider } from './alerting_api'; import { SamlToolsProvider } from './saml_tools'; import { DataViewApiProvider } from './data_view_api'; +import { SvlCasesServiceProvider } from './svl_cases'; export const services = { - ...xpackApiIntegrationServices, - ...svlSharedServices, + // deployment agnostic FTR services + ...deploymentAgnosticSharedServices, - svlCommonApi: SvlCommonApiServiceProvider, + // serverless FTR services + ...svlSharedServices, alertingApi: AlertingApiProvider, samlTools: SamlToolsProvider, dataViewApi: DataViewApiProvider, + svlCases: SvlCasesServiceProvider, }; export type InheritedFtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test_serverless/api_integration/services/svl_cases/api.ts b/x-pack/test_serverless/api_integration/services/svl_cases/api.ts new file mode 100644 index 0000000000000..474a92c317e9f --- /dev/null +++ b/x-pack/test_serverless/api_integration/services/svl_cases/api.ts @@ -0,0 +1,233 @@ +/* + * Copyright 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 SuperTest from 'supertest'; +import { CASES_URL } from '@kbn/cases-plugin/common'; +import { Case, CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain'; +import type { CasePostRequest } from '@kbn/cases-plugin/common/types/api'; +import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain'; +import { CasesFindResponse } from '@kbn/cases-plugin/common/types/api'; +import { kbnTestConfig, kibanaTestSuperuserServerless } from '@kbn/test'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function SvlCasesApiServiceProvider({ getService }: FtrProviderContext) { + const kbnServer = getService('kibanaServer'); + const supertest = getService('supertest'); + + interface User { + username: string; + password: string; + description?: string; + roles: string[]; + } + + const superUser: User = { + username: 'superuser', + password: 'superuser', + roles: ['superuser'], + }; + + const defaultUser = { + email: null, + full_name: null, + username: kbnTestConfig.getUrlParts(kibanaTestSuperuserServerless).username, + }; + + /** + * A null filled user will occur when the security plugin is disabled + */ + const nullUser = { email: null, full_name: null, username: null }; + + const findCommon = { + page: 1, + per_page: 20, + total: 0, + count_open_cases: 0, + count_closed_cases: 0, + count_in_progress_cases: 0, + }; + + const findCasesResp: CasesFindResponse = { + ...findCommon, + cases: [], + }; + + return { + setupAuth({ + apiCall, + headers, + auth, + }: { + apiCall: SuperTest.Test; + headers: Record; + auth?: { user: User; space: string | null } | null; + }): SuperTest.Test { + if (!Object.hasOwn(headers, 'Cookie') && auth != null) { + return apiCall.auth(auth.user.username, auth.user.password); + } + + return apiCall; + }, + + getSpaceUrlPrefix(spaceId: string | undefined | null) { + return spaceId && spaceId !== 'default' ? `/s/${spaceId}` : ``; + }, + + async deleteAllCaseItems() { + await Promise.all([ + this.deleteCasesByESQuery(), + this.deleteCasesUserActions(), + this.deleteComments(), + this.deleteConfiguration(), + this.deleteMappings(), + ]); + }, + + async deleteCasesUserActions(): Promise { + await kbnServer.savedObjects.clean({ types: ['cases-user-actions'] }); + }, + + async deleteCasesByESQuery(): Promise { + await kbnServer.savedObjects.clean({ types: ['cases'] }); + }, + + async deleteComments(): Promise { + await kbnServer.savedObjects.clean({ types: ['cases-comments'] }); + }, + + async deleteConfiguration(): Promise { + await kbnServer.savedObjects.clean({ types: ['cases-configure'] }); + }, + + async deleteMappings(): Promise { + await kbnServer.savedObjects.clean({ types: ['cases-connector-mappings'] }); + }, + + /** + * Return a request for creating a case. + */ + getPostCaseRequest(owner: string, req?: Partial): CasePostRequest { + return { + ...this.getPostCaseReq(owner), + ...req, + }; + }, + + postCaseResp(owner: string, id?: string | null, req?: CasePostRequest): Partial { + const request = req ?? this.getPostCaseReq(owner); + return { + ...request, + ...(id != null ? { id } : {}), + comments: [], + duration: null, + severity: request.severity ?? CaseSeverity.LOW, + totalAlerts: 0, + totalComment: 0, + closed_by: null, + created_by: defaultUser, + external_service: null, + status: CaseStatuses.open, + updated_by: null, + category: null, + }; + }, + + async createCase( + params: CasePostRequest, + expectedHttpCode: number = 200, + auth: { user: User; space: string | null } | null = { user: superUser, space: null }, + headers: Record = {} + ): Promise { + const apiCall = supertest.post(`${CASES_URL}`); + + this.setupAuth({ apiCall, headers, auth }); + + const response = await apiCall + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .set(headers) + .send(params) + .expect(expectedHttpCode); + + return response.body; + }, + + async findCases({ + query = {}, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, + }: { + query?: Record; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; + }): Promise { + const { body: res } = await supertest + .get(`${this.getSpaceUrlPrefix(auth.space)}${CASES_URL}/_find`) + .auth(auth.user.username, auth.user.password) + .query({ sortOrder: 'asc', ...query }) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .send() + .expect(expectedHttpCode); + + return res; + }, + + async getCase({ + caseId, + includeComments = false, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, + }: { + caseId: string; + includeComments?: boolean; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; + }): Promise { + const { body: theCase } = await supertest + .get( + `${this.getSpaceUrlPrefix( + auth?.space + )}${CASES_URL}/${caseId}?includeComments=${includeComments}` + ) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .auth(auth.user.username, auth.user.password) + .expect(expectedHttpCode); + + return theCase; + }, + + getFindCasesResp() { + return findCasesResp; + }, + + getPostCaseReq(owner: string): CasePostRequest { + return { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Observability Issue', + tags: ['defacement'], + severity: CaseSeverity.LOW, + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + settings: { + syncAlerts: true, + }, + owner, + assignees: [], + }; + }, + + getNullUser() { + return nullUser; + }, + }; +} diff --git a/x-pack/test_serverless/api_integration/services/svl_cases/index.ts b/x-pack/test_serverless/api_integration/services/svl_cases/index.ts new file mode 100644 index 0000000000000..31b372e7442b3 --- /dev/null +++ b/x-pack/test_serverless/api_integration/services/svl_cases/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +import { SvlCasesApiServiceProvider } from './api'; +import { SvlCasesOmitServiceProvider } from './omit'; + +export function SvlCasesServiceProvider(context: FtrProviderContext) { + const api = SvlCasesApiServiceProvider(context); + const omit = SvlCasesOmitServiceProvider(context); + + return { + api, + omit, + }; +} diff --git a/x-pack/test_serverless/api_integration/services/svl_cases/omit.ts b/x-pack/test_serverless/api_integration/services/svl_cases/omit.ts new file mode 100644 index 0000000000000..94ce0a479fffc --- /dev/null +++ b/x-pack/test_serverless/api_integration/services/svl_cases/omit.ts @@ -0,0 +1,53 @@ +/* + * Copyright 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 { Case, Attachment } from '@kbn/cases-plugin/common/types/domain'; +import { omit } from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function SvlCasesOmitServiceProvider({}: FtrProviderContext) { + interface CommonSavedObjectAttributes { + id?: string | null; + created_at?: string | null; + updated_at?: string | null; + version?: string | null; + [key: string]: unknown; + } + + const savedObjectCommonAttributes = ['created_at', 'updated_at', 'version', 'id']; + + return { + removeServerGeneratedPropertiesFromObject( + object: T, + keys: K[] + ): Omit { + return omit(object, keys); + }, + + removeServerGeneratedPropertiesFromSavedObject( + attributes: T, + keys: Array = [] + ): Omit { + return this.removeServerGeneratedPropertiesFromObject(attributes, [ + ...savedObjectCommonAttributes, + ...keys, + ]); + }, + + removeServerGeneratedPropertiesFromCase(theCase: Case): Partial { + return this.removeServerGeneratedPropertiesFromSavedObject(theCase, ['closed_at']); + }, + + removeServerGeneratedPropertiesFromComments( + comments: Attachment[] | undefined + ): Array> | undefined { + return comments?.map((comment) => { + return this.removeServerGeneratedPropertiesFromSavedObject(comment, []); + }); + }, + }; +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts index a8a6d2d44cd64..799d87f65e10f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts @@ -46,7 +46,6 @@ export default function ({ getService }: FtrProviderContext) { }, }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); objectRemover.add('default', ruleId, 'rule', 'alerting'); // get the first alert document written @@ -174,7 +173,6 @@ export default function ({ getService }: FtrProviderContext) { }, }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); objectRemover.add('default', ruleId, 'rule', 'alerting'); // get the first alert document written diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts index 1ba17b77aca10..cd378ab98035b 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts @@ -46,8 +46,9 @@ export async function createIndexConnector({ refresh: true, }, connector_type_id: '.index', - }); - return body.id as string; + }) + .expect(200); + return body; } export async function createSlackConnector({ @@ -68,8 +69,9 @@ export async function createSlackConnector({ webhookUrl: 'http://test', }, connector_type_id: '.slack', - }); - return body.id as string; + }) + .expect(200); + return body; } export async function createEsQueryRule({ @@ -111,7 +113,8 @@ export async function createEsQueryRule({ rule_type_id: ruleTypeId, actions, ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), - }); + }) + .expect(200); return body; } @@ -125,7 +128,8 @@ export async function disableRule({ const { body } = await supertest .post(`/api/alerting/rule/${ruleId}/_disable`) .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); + .set('x-elastic-internal-origin', 'foo') + .expect(204); return body; } @@ -141,7 +145,8 @@ export async function updateEsQueryRule({ const { body: r } = await supertest .get(`/api/alerting/rule/${ruleId}`) .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); + .set('x-elastic-internal-origin', 'foo') + .expect(200); const body = await supertest .put(`/api/alerting/rule/${ruleId}`) .set('kbn-xsrf', 'foo') @@ -162,7 +167,8 @@ export async function updateEsQueryRule({ })), }, ...updates, - }); + }) + .expect(200); return body; } @@ -176,7 +182,8 @@ export async function runRule({ const response = await supertest .post(`/internal/alerting/rule/${ruleId}/_run_soon`) .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); + .set('x-elastic-internal-origin', 'foo') + .expect(204); return response; } @@ -190,7 +197,8 @@ export async function muteRule({ const { body } = await supertest .post(`/api/alerting/rule/${ruleId}/_mute_all`) .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); + .set('x-elastic-internal-origin', 'foo') + .expect(204); return body; } @@ -204,7 +212,8 @@ export async function enableRule({ const { body } = await supertest .post(`/api/alerting/rule/${ruleId}/_enable`) .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); + .set('x-elastic-internal-origin', 'foo') + .expect(204); return body; } @@ -220,7 +229,8 @@ export async function muteAlert({ const { body } = await supertest .post(`/api/alerting/rule/${ruleId}/alert/${alertId}/_mute`) .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); + .set('x-elastic-internal-origin', 'foo') + .expect(204); return body; } @@ -234,6 +244,7 @@ export async function unmuteRule({ const { body } = await supertest .post(`/api/alerting/rule/${ruleId}/_unmute_all`) .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); + .set('x-elastic-internal-origin', 'foo') + .expect(204); return body; } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts index bdca0ee15040a..5422d6a7b8cee 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts @@ -23,7 +23,7 @@ export async function waitForDocumentInIndex({ indexName: string; num?: number; }): Promise { - return pRetry( + return await pRetry( async () => { const response = await esClient.search({ index: indexName }); if (response.hits.hits.length < num) { @@ -74,7 +74,7 @@ export async function waitForAlertInIndex({ ruleId: string; num: number; }): Promise>> { - return pRetry( + return await pRetry( async () => { const response = await esClient.search({ index: indexName, @@ -115,7 +115,7 @@ export async function waitForAllTasksIdle({ esClient: Client; filter: Date; }): Promise { - return pRetry( + return await pRetry( async () => { const response = await esClient.search({ index: '.kibana_task_manager', @@ -167,7 +167,7 @@ export async function waitForAllTasks({ taskType: string; attempts: number; }): Promise { - return pRetry( + return await pRetry( async () => { const response = await esClient.search({ index: '.kibana_task_manager', @@ -225,7 +225,7 @@ export async function waitForDisabled({ ruleId: string; filter: Date; }): Promise { - return pRetry( + return await pRetry( async () => { const response = await esClient.search({ index: '.kibana_task_manager', @@ -280,7 +280,7 @@ export async function waitForExecutionEventLog({ ruleId: string; num?: number; }): Promise { - return pRetry( + return await pRetry( async () => { const response = await esClient.search({ index: '.kibana-event-log*', @@ -344,10 +344,7 @@ export async function waitForNumRuleRuns({ for (let i = 0; i < numOfRuns; i++) { await pRetry( async () => { - const resp = await runRule({ supertest, ruleId }); - if (resp.status !== 204) { - throw new Error(`Expected ${resp.status} to equal 204`); - } + await runRule({ supertest, ruleId }); await waitForExecutionEventLog({ esClient, filter: testStart, diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/index.ts index 3225ecb4f71ce..4367656feae60 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/index.ts @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Alerting APIs', function () { loadTestFile(require.resolve('./rules')); loadTestFile(require.resolve('./alert_documents')); + loadTestFile(require.resolve('./summary_actions')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts index 1ad798758b45a..e9d3c85f21028 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts @@ -35,8 +35,7 @@ export default function ({ getService }: FtrProviderContext) { const esClient = getService('es'); const esDeleteAllIndices = getService('esDeleteAllIndices'); - // Issue: https://github.com/elastic/kibana/issues/165145 - describe.skip('Alerting rules', () => { + describe('Alerting rules', () => { const RULE_TYPE_ID = '.es-query'; const ALERT_ACTION_INDEX = 'alert-action-es-query'; let actionId: string; @@ -61,12 +60,12 @@ export default function ({ getService }: FtrProviderContext) { it('should schedule task, run rule and schedule actions when appropriate', async () => { const testStart = new Date(); - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -111,7 +110,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); // Wait for the action to index a document before disabling the alert and waiting for tasks to finish const resp = await waitForDocumentInIndex({ @@ -120,22 +118,6 @@ export default function ({ getService }: FtrProviderContext) { }); expect(resp.hits.hits.length).to.be(1); - await waitForAllTasksIdle({ - esClient, - filter: testStart, - }); - - await disableRule({ - supertest, - ruleId, - }); - - await waitForDisabled({ - esClient, - ruleId, - filter: testStart, - }); - const document = resp.hits.hits[0]; expect(document._source).to.eql({ alertActionGroup: 'query matched', @@ -158,7 +140,7 @@ export default function ({ getService }: FtrProviderContext) { expect(eventLogResp.hits.hits.length).to.be(1); const eventLogDocument = eventLogResp.hits.hits[0]._source; - await validateEventLog(eventLogDocument, { + validateEventLog(eventLogDocument, { ruleId, ruleTypeId: RULE_TYPE_ID, outcome: 'success', @@ -170,12 +152,12 @@ export default function ({ getService }: FtrProviderContext) { it('should pass updated rule params to executor', async () => { const testStart = new Date(); - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -220,7 +202,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); // Wait for the action to index a document before disabling the alert and waiting for tasks to finish const resp = await waitForDocumentInIndex({ @@ -289,11 +270,11 @@ export default function ({ getService }: FtrProviderContext) { const testStart = new Date(); // Should fail - actionId = await createSlackConnector({ + const createdAction = await createSlackConnector({ supertest, name: 'Slack Connector: Alerting API test', }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -326,7 +307,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); // Should retry when the the action fails const resp = await waitForAllTasks({ @@ -341,12 +321,12 @@ export default function ({ getService }: FtrProviderContext) { it('should throttle alerts when appropriate', async () => { const testStart = new Date(); - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -388,7 +368,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); // Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish await waitForNumRuleRuns({ supertest, numOfRuns: 3, ruleId, esClient, testStart }); @@ -415,12 +394,12 @@ export default function ({ getService }: FtrProviderContext) { it('should throttle alerts with throttled action when appropriate', async () => { const testStart = new Date(); - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -466,7 +445,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); // Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish await waitForNumRuleRuns({ supertest, numOfRuns: 3, ruleId, esClient, testStart }); @@ -493,12 +471,12 @@ export default function ({ getService }: FtrProviderContext) { it('should reset throttle window when not firing and should not throttle when changing groups', async () => { const testStart = new Date(); - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -568,7 +546,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); // Wait for the action to index a document const resp = await waitForDocumentInIndex({ @@ -638,12 +615,12 @@ export default function ({ getService }: FtrProviderContext) { const testStart = new Date(); await createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -689,7 +666,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); await muteRule({ supertest, @@ -728,12 +704,12 @@ export default function ({ getService }: FtrProviderContext) { const testStart = new Date(); await createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -779,7 +755,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); await muteAlert({ supertest, @@ -816,12 +791,12 @@ export default function ({ getService }: FtrProviderContext) { }); it(`should unmute all instances when unmuting an alert`, async () => { - actionId = await createIndexConnector({ + const createdAction = await createIndexConnector({ supertest, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); - expect(actionId).not.to.be(undefined); + actionId = createdAction.id; const createdRule = await createEsQueryRule({ supertest, @@ -867,7 +842,6 @@ export default function ({ getService }: FtrProviderContext) { ], }); ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); await muteAlert({ supertest, @@ -937,7 +911,8 @@ function validateEventLog(event: any, params: ValidateEventLogParams) { expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_triggered_actions).to.be(1); expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_searches).to.be(1); - expect(event?.kibana?.alert?.rule?.execution?.metrics?.es_search_duration_ms).to.be(0); + // Sometimes fast enough that it will report 0ms + expect(event?.kibana?.alert?.rule?.execution?.metrics?.es_search_duration_ms >= 0).to.be.ok(); expect( event?.kibana?.alert?.rule?.execution?.metrics?.total_search_duration_ms ).to.be.greaterThan(0); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts new file mode 100644 index 0000000000000..63e34eefae79d --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts @@ -0,0 +1,543 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { omit, padStart } from 'lodash'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { createIndexConnector, createEsQueryRule } from './helpers/alerting_api_helper'; +import { + createIndex, + getDocumentsInIndex, + waitForAlertInIndex, + waitForDocumentInIndex, +} from './helpers/alerting_wait_for_helpers'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esClient = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + + describe('Summary actions', () => { + const RULE_TYPE_ID = '.es-query'; + const ALERT_ACTION_INDEX = 'alert-action-es-query'; + const ALERT_INDEX = '.alerts-stack.alerts-default'; + let actionId: string; + let ruleId: string; + const fields = [ + '@timestamp', + 'event.action', + 'kibana.alert.duration.us', + 'kibana.alert.flapping_history', + 'kibana.alert.maintenance_window_ids', + 'kibana.alert.reason', + 'kibana.alert.rule.execution.uuid', + 'kibana.alert.rule.duration', + 'kibana.alert.start', + 'kibana.alert.time_range', + 'kibana.alert.uuid', + 'kibana.alert.url', + 'kibana.version', + ]; + + afterEach(async () => { + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .expect(204); + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .expect(204); + await esDeleteAllIndices([ALERT_ACTION_INDEX]); + }); + + it('should schedule actions for summary of alerts per rule run', async () => { + const testStart = new Date(); + const createdAction = await createIndexConnector({ + supertest, + name: 'Index Connector: Alerting API test', + indexName: ALERT_ACTION_INDEX, + }); + actionId = createdAction.id; + + const createdRule = await createEsQueryRule({ + supertest, + consumer: 'alerts', + name: 'always fire', + ruleTypeId: RULE_TYPE_ID, + params: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeWindowSize: 20, + timeWindowUnit: 's', + }, + actions: [ + { + group: 'query matched', + id: actionId, + params: { + documents: [ + { + all: '{{alerts.all.count}}', + new: '{{alerts.new.count}}', + newIds: '[{{#alerts.new.data}}{{kibana.alert.instance.id}},{{/alerts.new.data}}]', + ongoing: '{{alerts.ongoing.count}}', + ongoingIds: + '[{{#alerts.ongoing.data}}{{kibana.alert.instance.id}},{{/alerts.ongoing.data}}]', + recovered: '{{alerts.recovered.count}}', + recoveredIds: + '[{{#alerts.recovered.data}}{{kibana.alert.instance.id}},{{/alerts.recovered.data}}]', + }, + ], + }, + frequency: { + notify_when: 'onActiveAlert', + throttle: null, + summary: true, + }, + alerts_filter: { + query: { kql: 'kibana.alert.rule.name:always fire', filters: [] }, + }, + }, + ], + }); + ruleId = createdRule.id; + + const resp = await waitForDocumentInIndex({ + esClient, + indexName: ALERT_ACTION_INDEX, + }); + expect(resp.hits.hits.length).to.be(1); + + const resp2 = await waitForAlertInIndex({ + esClient, + filter: testStart, + indexName: ALERT_INDEX, + ruleId, + num: 1, + }); + expect(resp2.hits.hits.length).to.be(1); + + const document = resp.hits.hits[0]; + expect(document._source).to.eql({ + all: '1', + new: '1', + newIds: '[query matched,]', + ongoing: '0', + ongoingIds: '[]', + recovered: '0', + recoveredIds: '[]', + }); + + const alertDocument = resp2.hits.hits[0]._source as Record; + expect(omit(alertDocument, fields)).to.eql({ + event: { + kind: 'signal', + }, + tags: [], + kibana: { + space_ids: ['default'], + alert: { + title: "rule 'always fire' matched query", + evaluation: { + conditions: 'Number of matching documents is greater than -1', + value: 0, + }, + action_group: 'query matched', + flapping: false, + duration: {}, + instance: { id: 'query matched' }, + status: 'active', + workflow_status: 'open', + rule: { + category: 'Elasticsearch query', + consumer: 'alerts', + name: 'always fire', + execution: {}, + parameters: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: '{\n "query":{\n "match_all" : {}\n }\n}', + timeWindowSize: 20, + timeWindowUnit: 's', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'all', + searchType: 'esQuery', + }, + producer: 'stackAlerts', + revision: 0, + rule_type_id: '.es-query', + tags: [], + uuid: ruleId, + }, + }, + }, + }); + }); + + it('should filter alerts by kql', async () => { + const testStart = new Date(); + const createdAction = await createIndexConnector({ + supertest, + name: 'Index Connector: Alerting API test', + indexName: ALERT_ACTION_INDEX, + }); + actionId = createdAction.id; + + const createdRule = await createEsQueryRule({ + supertest, + consumer: 'alerts', + name: 'always fire', + ruleTypeId: RULE_TYPE_ID, + params: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeWindowSize: 20, + timeWindowUnit: 's', + }, + actions: [ + { + group: 'query matched', + id: actionId, + params: { + documents: [ + { + all: '{{alerts.all.count}}', + new: '{{alerts.new.count}}', + newIds: '[{{#alerts.new.data}}{{kibana.alert.instance.id}},{{/alerts.new.data}}]', + ongoing: '{{alerts.ongoing.count}}', + ongoingIds: + '[{{#alerts.ongoing.data}}{{kibana.alert.instance.id}},{{/alerts.ongoing.data}}]', + recovered: '{{alerts.recovered.count}}', + recoveredIds: + '[{{#alerts.recovered.data}}{{kibana.alert.instance.id}},{{/alerts.recovered.data}}]', + }, + ], + }, + frequency: { + notify_when: 'onActiveAlert', + throttle: null, + summary: true, + }, + alerts_filter: { + query: { kql: 'kibana.alert.instance.id:query matched', filters: [] }, + }, + }, + ], + }); + ruleId = createdRule.id; + + const resp = await waitForDocumentInIndex({ + esClient, + indexName: ALERT_ACTION_INDEX, + }); + expect(resp.hits.hits.length).to.be(1); + + const resp2 = await waitForAlertInIndex({ + esClient, + filter: testStart, + indexName: ALERT_INDEX, + ruleId, + num: 1, + }); + expect(resp2.hits.hits.length).to.be(1); + + const document = resp.hits.hits[0]; + expect(document._source).to.eql({ + all: '1', + new: '1', + newIds: '[query matched,]', + ongoing: '0', + ongoingIds: '[]', + recovered: '0', + recoveredIds: '[]', + }); + + const alertDocument = resp2.hits.hits[0]._source as Record; + expect(omit(alertDocument, fields)).to.eql({ + event: { + kind: 'signal', + }, + tags: [], + kibana: { + space_ids: ['default'], + alert: { + title: "rule 'always fire' matched query", + evaluation: { + conditions: 'Number of matching documents is greater than -1', + value: 0, + }, + action_group: 'query matched', + flapping: false, + duration: {}, + instance: { id: 'query matched' }, + status: 'active', + workflow_status: 'open', + rule: { + category: 'Elasticsearch query', + consumer: 'alerts', + name: 'always fire', + execution: {}, + parameters: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: '{\n "query":{\n "match_all" : {}\n }\n}', + timeWindowSize: 20, + timeWindowUnit: 's', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'all', + searchType: 'esQuery', + }, + producer: 'stackAlerts', + revision: 0, + rule_type_id: '.es-query', + tags: [], + uuid: ruleId, + }, + }, + }, + }); + }); + + it('should filter alerts by hours', async () => { + const now = new Date(); + now.setHours(now.getHours() + 1); + const hour = padStart(now.getUTCHours().toString(), 2, '0'); + const minutes = padStart(now.getUTCMinutes().toString(), 2, '0'); + + const start = `${hour}:${minutes}`; + const end = `${hour}:${minutes}`; + + await createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); + + const createdAction = await createIndexConnector({ + supertest, + name: 'Index Connector: Alerting API test', + indexName: ALERT_ACTION_INDEX, + }); + actionId = createdAction.id; + + const createdRule = await createEsQueryRule({ + supertest, + consumer: 'alerts', + name: 'always fire', + ruleTypeId: RULE_TYPE_ID, + schedule: { interval: '1m' }, + params: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeWindowSize: 20, + timeWindowUnit: 's', + }, + actions: [ + { + group: 'query matched', + id: actionId, + params: { + documents: [ + { + all: '{{alerts.all.count}}', + new: '{{alerts.new.count}}', + newIds: '[{{#alerts.new.data}}{{kibana.alert.instance.id}},{{/alerts.new.data}}]', + ongoing: '{{alerts.ongoing.count}}', + ongoingIds: + '[{{#alerts.ongoing.data}}{{kibana.alert.instance.id}},{{/alerts.ongoing.data}}]', + recovered: '{{alerts.recovered.count}}', + recoveredIds: + '[{{#alerts.recovered.data}}{{kibana.alert.instance.id}},{{/alerts.recovered.data}}]', + }, + ], + }, + frequency: { + notify_when: 'onActiveAlert', + throttle: null, + summary: true, + }, + alerts_filter: { + timeframe: { + days: [1, 2, 3, 4, 5, 6, 7], + timezone: 'UTC', + hours: { start, end }, + }, + }, + }, + ], + }); + ruleId = createdRule.id; + + // Should not have executed any action + const resp = await getDocumentsInIndex({ + esClient, + indexName: ALERT_ACTION_INDEX, + }); + expect(resp.hits.hits.length).to.be(0); + }); + + it('should schedule actions for summary of alerts on a custom interval', async () => { + const testStart = new Date(); + const createdAction = await createIndexConnector({ + supertest, + name: 'Index Connector: Alerting API test', + indexName: ALERT_ACTION_INDEX, + }); + actionId = createdAction.id; + + const createdRule = await createEsQueryRule({ + supertest, + consumer: 'alerts', + name: 'always fire', + ruleTypeId: RULE_TYPE_ID, + schedule: { interval: '1m' }, + params: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeWindowSize: 20, + timeWindowUnit: 's', + }, + actions: [ + { + group: 'query matched', + id: actionId, + params: { + documents: [ + { + all: '{{alerts.all.count}}', + new: '{{alerts.new.count}}', + newIds: '[{{#alerts.new.data}}{{kibana.alert.instance.id}},{{/alerts.new.data}}]', + ongoing: '{{alerts.ongoing.count}}', + ongoingIds: + '[{{#alerts.ongoing.data}}{{kibana.alert.instance.id}},{{/alerts.ongoing.data}}]', + recovered: '{{alerts.recovered.count}}', + recoveredIds: + '[{{#alerts.recovered.data}}{{kibana.alert.instance.id}},{{/alerts.recovered.data}}]', + }, + ], + }, + frequency: { + notify_when: 'onThrottleInterval', + throttle: '1m', + summary: true, + }, + }, + ], + }); + ruleId = createdRule.id; + + const resp = await waitForDocumentInIndex({ + esClient, + indexName: ALERT_ACTION_INDEX, + num: 2, + }); + + const resp2 = await waitForAlertInIndex({ + esClient, + filter: testStart, + indexName: ALERT_INDEX, + ruleId, + num: 1, + }); + expect(resp2.hits.hits.length).to.be(1); + + const document = resp.hits.hits[0]; + expect(document._source).to.eql({ + all: '1', + new: '1', + newIds: '[query matched,]', + ongoing: '0', + ongoingIds: '[]', + recovered: '0', + recoveredIds: '[]', + }); + + const document1 = resp.hits.hits[1]; + expect(document1._source).to.eql({ + all: '1', + new: '0', + newIds: '[]', + ongoing: '1', + ongoingIds: '[query matched,]', + recovered: '0', + recoveredIds: '[]', + }); + + const alertDocument = resp2.hits.hits[0]._source as Record; + expect(omit(alertDocument, fields)).to.eql({ + event: { + kind: 'signal', + }, + tags: [], + kibana: { + space_ids: ['default'], + alert: { + title: "rule 'always fire' matched query", + evaluation: { + conditions: 'Number of matching documents is greater than -1', + value: 0, + }, + action_group: 'query matched', + flapping: false, + duration: {}, + instance: { id: 'query matched' }, + status: 'active', + workflow_status: 'open', + rule: { + category: 'Elasticsearch query', + consumer: 'alerts', + name: 'always fire', + execution: {}, + parameters: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: '{\n "query":{\n "match_all" : {}\n }\n}', + timeWindowSize: 20, + timeWindowUnit: 's', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'all', + searchType: 'esQuery', + }, + producer: 'stackAlerts', + revision: 0, + rule_type_id: '.es-query', + tags: [], + uuid: ruleId, + }, + }, + }, + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_view_field_editor/field_preview.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_view_field_editor/field_preview.ts index d351c91faea7f..9580108506c0a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/data_view_field_editor/field_preview.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_view_field_editor/field_preview.ts @@ -48,7 +48,8 @@ export default function ({ getService }: FtrProviderContext) { }); }; - describe('Field preview', function () { + // Failing: See https://github.com/elastic/kibana/issues/165868 + describe.skip('Field preview', function () { before(async () => await createIndex()); after(async () => await deleteIndex()); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_views/es_errors/errors.js b/x-pack/test_serverless/api_integration/test_suites/common/data_views/es_errors/errors.js index ddbc88cce8045..f8556727d9c17 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/data_views/es_errors/errors.js +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_views/es_errors/errors.js @@ -22,7 +22,8 @@ export default function ({ getService }) { const es = getService('es'); const esArchiver = getService('esArchiver'); - describe('index_patterns/* error handler', () => { + // FLAKY: https://github.com/elastic/kibana/issues/165944 + describe.skip('index_patterns/* error handler', () => { let indexNotFoundError; let docNotFoundError; before(async () => { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/conflicts.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/conflicts.ts index d1d08c1e88832..a358bb8a3d469 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/conflicts.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/conflicts.ts @@ -16,7 +16,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const svlCommonApi = getService('svlCommonApi'); - describe('conflicts', () => { + // FLAKY: https://github.com/elastic/kibana/issues/165972 + describe.skip('conflicts', () => { before(() => esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/conflicts') ); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/params.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/params.ts index 035ca1af4b5a8..66aa266810530 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/params.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_views/fields_for_wildcard_route/params.ts @@ -16,7 +16,8 @@ export default function ({ getService }: FtrProviderContext) { const randomness = getService('randomness'); const svlCommonApi = getService('svlCommonApi'); - describe('params', () => { + // FLAKY https://github.com/elastic/kibana/issues/165942 + describe.skip('params', () => { before(() => esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index') ); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/elasticsearch_api.ts b/x-pack/test_serverless/api_integration/test_suites/common/elasticsearch_api.ts new file mode 100644 index 0000000000000..b55f36cab29aa --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/elasticsearch_api.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esSupertest = getService('esSupertest'); + const svlCommonApi = getService('svlCommonApi'); + + describe('Elasticsearch API', function () { + it('can request /', async () => { + const { body, status } = await esSupertest.get('/'); + svlCommonApi.assertResponseStatusCode(200, status, body); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/index.ts index ae68f7ec7670c..fffe386b7c2ba 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index.ts @@ -34,5 +34,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./scripts_tests')); loadTestFile(require.resolve('./search_oss')); loadTestFile(require.resolve('./search_xpack')); + loadTestFile(require.resolve('./elasticsearch_api')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/create_enrich_policies.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/create_enrich_policies.ts new file mode 100644 index 0000000000000..e2bfa9cdb35ef --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/create_enrich_policies.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const INTERNAL_API_BASE_PATH = '/internal/index_management'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + + describe('Create enrich policy', function () { + const INDEX_A_NAME = `index-${Math.random()}`; + const INDEX_B_NAME = `index-${Math.random()}`; + const POLICY_NAME = `policy-${Math.random()}`; + + before(async () => { + try { + await es.indices.create({ + index: INDEX_A_NAME, + body: { + mappings: { + properties: { + email: { + type: 'text', + }, + firstName: { + type: 'text', + }, + }, + }, + }, + }); + await es.indices.create({ + index: INDEX_B_NAME, + body: { + mappings: { + properties: { + email: { + type: 'text', + }, + age: { + type: 'long', + }, + }, + }, + }, + }); + } catch (err) { + log.debug('[Setup error] Error creating test index'); + throw err; + } + }); + + after(async () => { + try { + await es.indices.delete({ index: INDEX_A_NAME }); + await es.indices.delete({ index: INDEX_B_NAME }); + } catch (err) { + log.debug('[Cleanup error] Error deleting test index'); + throw err; + } + }); + + it('Allows to create an enrich policy', async () => { + const { body } = await supertest + .post(`${INTERNAL_API_BASE_PATH}/enrich_policies`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ + policy: { + name: POLICY_NAME, + type: 'match', + matchField: 'email', + enrichFields: ['firstName'], + sourceIndices: [INDEX_A_NAME], + }, + }) + .expect(200); + + expect(body).toStrictEqual({ acknowledged: true }); + }); + + it('Can retrieve fields from indices', async () => { + const { body } = await supertest + .post(`${INTERNAL_API_BASE_PATH}/enrich_policies/get_fields_from_indices`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ indices: [INDEX_A_NAME, INDEX_B_NAME] }) + .expect(200); + + expect(body).toStrictEqual({ + commonFields: [{ name: 'email', type: 'text', normalizedType: 'text' }], + indices: [ + { + index: INDEX_A_NAME, + fields: [ + { name: 'email', type: 'text', normalizedType: 'text' }, + { name: 'firstName', type: 'text', normalizedType: 'text' }, + ], + }, + { + index: INDEX_B_NAME, + fields: [ + { name: 'age', type: 'long', normalizedType: 'number' }, + { name: 'email', type: 'text', normalizedType: 'text' }, + ], + }, + ], + }); + }); + + it('Can retrieve matching indices', async () => { + const { body } = await supertest + .post(`${INTERNAL_API_BASE_PATH}/enrich_policies/get_matching_indices`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ pattern: 'index-' }) + .expect(200); + + expect( + body.indices.every((value: string) => [INDEX_A_NAME, INDEX_B_NAME].includes(value)) + ).toBe(true); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index.ts index 52f166c343ff9..cf0bf24cb99b4 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index.ts @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Index Management APIs', function () { loadTestFile(require.resolve('./index_templates')); loadTestFile(require.resolve('./indices')); + loadTestFile(require.resolve('./create_enrich_policies')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts index f9700dfaff70a..9c4fd7b196337 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts @@ -22,6 +22,14 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { // Create a new index to test against + const indexExists = await es.indices.exists({ index: indexName }); + + // Index should not exist, but in the case that it already does, we bypass the create request + if (indexExists) { + return; + } + + log.debug(`Creating index: '${indexName}'`); try { await es.indices.create({ index: indexName }); } catch (err) { @@ -79,5 +87,52 @@ export default function ({ getService }: FtrProviderContext) { .expect(404); }); }); + + describe('create index', () => { + const createIndexName = 'a-test-index'; + after(async () => { + // Cleanup index created for testing purposes + try { + await es.indices.delete({ + index: createIndexName, + }); + } catch (err) { + log.debug('[Cleanup error] Error deleting "a-test-index" index'); + throw err; + } + }); + + it('can create a new index', async () => { + await supertest + .put(`${INTERNAL_API_BASE_PATH}/indices/create`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ + indexName: createIndexName, + }) + .expect(200); + + const { body: index } = await supertest + .get(`${INTERNAL_API_BASE_PATH}/indices/${createIndexName}`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .expect(200); + + expect(index).toBeTruthy(); + + expect(Object.keys(index).sort()).toEqual(expectedKeys); + }); + + it('fails to re-create the same index', async () => { + await supertest + .put(`${INTERNAL_API_BASE_PATH}/indices/create`) + .set('kbn-xsrf', 'xxx') + .set('x-elastic-internal-origin', 'xxx') + .send({ + indexName: createIndexName, + }) + .expect(400); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/rollups.ts b/x-pack/test_serverless/api_integration/test_suites/common/rollups.ts index 47dd58f242759..c1ec42564e481 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/rollups.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/rollups.ts @@ -16,7 +16,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe('rollup data views - fields for wildcard', function () { + // Failing: See https://github.com/elastic/kibana/issues/165476 + describe.skip('rollup data views - fields for wildcard', function () { before(async () => { await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/scripted_fields.ts b/x-pack/test_serverless/api_integration/test_suites/common/scripted_fields.ts index f3969e07eea7e..7cc4089814c77 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/scripted_fields.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/scripted_fields.ts @@ -13,7 +13,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe('scripted fields disabled', function () { + // FLAKY: https://github.com/elastic/kibana/issues/165511 + describe.skip('scripted fields disabled', function () { before(async () => { await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts b/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts index 1a4839cbae6fa..d820f22e72567 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts @@ -430,8 +430,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(404); }); - // FLAKY: https://github.com/elastic/kibana/issues/164856 - it.skip('should delete a completed search', async function () { + it('should delete a completed search', async function () { await markRequiresShardDelayAgg(this); const resp = await supertest @@ -460,7 +459,7 @@ export default function ({ getService }: FtrProviderContext) { await new Promise((resolve) => setTimeout(resolve, 3000)); - await retry.tryForTime(10000, async () => { + await retry.tryForTime(30000, async () => { const resp2 = await supertest .post(`/internal/search/ese/${id}`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts index c31ee37a93b94..ac324b3fda087 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts @@ -4,15 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { ApmUsername } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication'; -import { format, UrlObject } from 'url'; +import { format } from 'url'; import supertest from 'supertest'; import request from 'superagent'; import type { APIReturnType, APIClientRequestParamsOf, } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { kbnTestConfig } from '@kbn/test'; +import { Config, kbnTestConfig, kibanaTestSuperuserServerless } from '@kbn/test'; import type { APIEndpoint } from '@kbn/apm-plugin/server'; import { formatRequest } from '@kbn/server-route-repository'; import { InheritedFtrProviderContext } from '../../../../services'; @@ -93,19 +92,19 @@ Body: ${JSON.stringify(res.body)}` } } -async function getApmApiClient({ - kibanaServer, - username, -}: { - kibanaServer: UrlObject; - username: ApmUsername | 'elastic'; -}) { +async function getApmApiClient({ svlSharedConfig }: { svlSharedConfig: Config }) { + const kibanaServer = svlSharedConfig.get('servers.kibana'); + const cAuthorities = svlSharedConfig.get('servers.kibana.certificateAuthorities'); + + const username = kbnTestConfig.getUrlParts(kibanaTestSuperuserServerless).username; + const password = kbnTestConfig.getUrlParts(kibanaTestSuperuserServerless).password; + const url = format({ ...kibanaServer, - auth: `${username}:${kbnTestConfig.getUrlParts().password}`, + auth: `${username}:${password}`, }); - return createApmApiClient(supertest(url)); + return createApmApiClient(supertest.agent(url, { ca: cAuthorities })); } export interface SupertestReturnType { @@ -120,12 +119,10 @@ export async function getApmApiClientService({ getService, }: InheritedFtrProviderContext): Promise { const svlSharedConfig = getService('config'); - const kibanaServer = svlSharedConfig.get('servers.kibana'); return { slsUser: await getApmApiClient({ - kibanaServer, - username: 'elastic', + svlSharedConfig, }), }; } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts index 93c621c72af74..279a1e0970b57 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts @@ -7,6 +7,7 @@ import expect from 'expect'; import { APMFtrContextProvider } from './common/services'; +import { ApmApiClient } from './common/apm_api_supertest'; const fleetMigrationResponse = { statusCode: 404, @@ -48,7 +49,7 @@ const SAMPLE_SOURCEMAP = { mappings: 'A,AAAB;;ABCDE;', }; -async function uploadSourcemap(apmApiClient: any) { +async function uploadSourcemap(apmApiClient: ApmApiClient) { const response = await apmApiClient.slsUser({ endpoint: 'POST /api/apm/sourcemaps 2023-10-31', type: 'form-data', @@ -67,8 +68,7 @@ async function uploadSourcemap(apmApiClient: any) { export default function ({ getService }: APMFtrContextProvider) { const apmApiClient = getService('apmApiClient'); - // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('apm feature flags', () => { + describe('apm feature flags', () => { describe('fleet migrations', () => { it('rejects requests to save apm server schema', async () => { try { diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/cases/find_cases.ts b/x-pack/test_serverless/api_integration/test_suites/observability/cases/find_cases.ts index 6f88c0ded2fdd..c28247b3cea0b 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/cases/find_cases.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/cases/find_cases.ts @@ -7,34 +7,34 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - findCases, - createCase, - deleteAllCaseItems, - postCaseReq, - findCasesResp, -} from './helpers/api'; export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const es = getService('es'); + const svlCases = getService('svlCases'); + + let findCasesResp: any; + let postCaseReq: any; describe('find_cases', () => { + before(async () => { + findCasesResp = svlCases.api.getFindCasesResp(); + postCaseReq = svlCases.api.getPostCaseReq('observability'); + }); + afterEach(async () => { - await deleteAllCaseItems(es); + await svlCases.api.deleteAllCaseItems(); }); it('should return empty response', async () => { - const cases = await findCases({ supertest }); + const cases = await svlCases.api.findCases({}); expect(cases).to.eql(findCasesResp); }); it('should return cases', async () => { - const a = await createCase(supertest, postCaseReq); - const b = await createCase(supertest, postCaseReq); - const c = await createCase(supertest, postCaseReq); + const a = await svlCases.api.createCase(postCaseReq); + const b = await svlCases.api.createCase(postCaseReq); + const c = await svlCases.api.createCase(postCaseReq); - const cases = await findCases({ supertest }); + const cases = await svlCases.api.findCases({}); expect(cases).to.eql({ ...findCasesResp, @@ -45,12 +45,14 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns empty response when trying to find cases with owner as cases', async () => { - const cases = await findCases({ supertest, query: { owner: 'cases' } }); + const cases = await svlCases.api.findCases({ query: { owner: 'cases' } }); expect(cases).to.eql(findCasesResp); }); it('returns empty response when trying to find cases with owner as securitySolution', async () => { - const cases = await findCases({ supertest, query: { owner: 'securitySolution' } }); + const cases = await svlCases.api.findCases({ + query: { owner: 'securitySolution' }, + }); expect(cases).to.eql(findCasesResp); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/cases/get_case.ts b/x-pack/test_serverless/api_integration/test_suites/observability/cases/get_case.ts index fffc529d2df30..d1f601f709af2 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/cases/get_case.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/cases/get_case.ts @@ -8,30 +8,26 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - getCase, - createCase, - deleteCasesByESQuery, - getPostCaseRequest, - postCaseResp, -} from './helpers/api'; -import { removeServerGeneratedPropertiesFromCase } from './helpers/omit'; export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const es = getService('es'); + const svlCases = getService('svlCases'); describe('get_case', () => { afterEach(async () => { - await deleteCasesByESQuery(es); + await svlCases.api.deleteCasesByESQuery(); }); it('should return a case', async () => { - const postedCase = await createCase(supertest, getPostCaseRequest()); - const theCase = await getCase({ supertest, caseId: postedCase.id, includeComments: true }); + const postedCase = await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('observability') + ); + const theCase = await svlCases.api.getCase({ + caseId: postedCase.id, + includeComments: true, + }); - const data = removeServerGeneratedPropertiesFromCase(theCase); - expect(data).to.eql(postCaseResp()); + const data = svlCases.omit.removeServerGeneratedPropertiesFromCase(theCase); + expect(data).to.eql(svlCases.api.postCaseResp('observability')); expect(data.comments?.length).to.eql(0); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/cases/helpers/api.ts b/x-pack/test_serverless/api_integration/test_suites/observability/cases/helpers/api.ts deleted file mode 100644 index 33cbdf07bf602..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/cases/helpers/api.ts +++ /dev/null @@ -1,247 +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 { Client } from '@elastic/elasticsearch'; -import type SuperTest from 'supertest'; -import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server/src/saved_objects_index_pattern'; -import { CASES_URL } from '@kbn/cases-plugin/common'; -import { Case, CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain'; -import type { CasePostRequest } from '@kbn/cases-plugin/common/types/api'; -import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain'; -import { CasesFindResponse } from '@kbn/cases-plugin/common/types/api'; - -export interface User { - username: string; - password: string; - description?: string; - roles: string[]; -} - -export const superUser: User = { - username: 'superuser', - password: 'superuser', - roles: ['superuser'], -}; - -export const setupAuth = ({ - apiCall, - headers, - auth, -}: { - apiCall: SuperTest.Test; - headers: Record; - auth?: { user: User; space: string | null } | null; -}): SuperTest.Test => { - if (!Object.hasOwn(headers, 'Cookie') && auth != null) { - return apiCall.auth(auth.user.username, auth.user.password); - } - - return apiCall; -}; - -export const getSpaceUrlPrefix = (spaceId: string | undefined | null) => { - return spaceId && spaceId !== 'default' ? `/s/${spaceId}` : ``; -}; - -export const deleteAllCaseItems = async (es: Client) => { - await Promise.all([ - deleteCasesByESQuery(es), - deleteCasesUserActions(es), - deleteComments(es), - deleteConfiguration(es), - deleteMappings(es), - ]); -}; - -export const deleteCasesUserActions = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-user-actions', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteCasesByESQuery = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteComments = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-comments', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteConfiguration = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-configure', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteMappings = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-connector-mappings', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const defaultUser = { email: null, full_name: null, username: 'elastic_serverless' }; -/** - * A null filled user will occur when the security plugin is disabled - */ -export const nullUser = { email: null, full_name: null, username: null }; - -export const postCaseReq: CasePostRequest = { - description: 'This is a brand new case of a bad meanie defacing data', - title: 'Super Bad Observability Issue', - tags: ['defacement'], - severity: CaseSeverity.LOW, - connector: { - id: 'none', - name: 'none', - type: ConnectorTypes.none, - fields: null, - }, - settings: { - syncAlerts: true, - }, - owner: 'observability', - assignees: [], -}; - -/** - * Return a request for creating a case. - */ -export const getPostCaseRequest = (req?: Partial): CasePostRequest => ({ - ...postCaseReq, - ...req, -}); - -export const postCaseResp = ( - id?: string | null, - req: CasePostRequest = postCaseReq -): Partial => ({ - ...req, - ...(id != null ? { id } : {}), - comments: [], - duration: null, - severity: req.severity ?? CaseSeverity.LOW, - totalAlerts: 0, - totalComment: 0, - closed_by: null, - created_by: defaultUser, - external_service: null, - status: CaseStatuses.open, - updated_by: null, - category: null, -}); - -const findCommon = { - page: 1, - per_page: 20, - total: 0, - count_open_cases: 0, - count_closed_cases: 0, - count_in_progress_cases: 0, -}; - -export const findCasesResp: CasesFindResponse = { - ...findCommon, - cases: [], -}; - -export const createCase = async ( - supertest: SuperTest.SuperTest, - params: CasePostRequest, - expectedHttpCode: number = 200, - auth: { user: User; space: string | null } | null = { user: superUser, space: null }, - headers: Record = {} -): Promise => { - const apiCall = supertest.post(`${CASES_URL}`); - - setupAuth({ apiCall, headers, auth }); - - const response = await apiCall - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .set(headers) - .send(params) - .expect(expectedHttpCode); - - return response.body; -}; - -export const findCases = async ({ - supertest, - query = {}, - expectedHttpCode = 200, - auth = { user: superUser, space: null }, -}: { - supertest: SuperTest.SuperTest; - query?: Record; - expectedHttpCode?: number; - auth?: { user: User; space: string | null }; -}): Promise => { - const { body: res } = await supertest - .get(`${getSpaceUrlPrefix(auth.space)}${CASES_URL}/_find`) - .auth(auth.user.username, auth.user.password) - .query({ sortOrder: 'asc', ...query }) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send() - .expect(expectedHttpCode); - - return res; -}; - -export const getCase = async ({ - supertest, - caseId, - includeComments = false, - expectedHttpCode = 200, - auth = { user: superUser, space: null }, -}: { - supertest: SuperTest.SuperTest; - caseId: string; - includeComments?: boolean; - expectedHttpCode?: number; - auth?: { user: User; space: string | null }; -}): Promise => { - const { body: theCase } = await supertest - .get( - `${getSpaceUrlPrefix(auth?.space)}${CASES_URL}/${caseId}?includeComments=${includeComments}` - ) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .auth(auth.user.username, auth.user.password) - .expect(expectedHttpCode); - - return theCase; -}; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/cases/helpers/omit.ts b/x-pack/test_serverless/api_integration/test_suites/observability/cases/helpers/omit.ts deleted file mode 100644 index b25506bfaebea..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/cases/helpers/omit.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Case, Attachment } from '@kbn/cases-plugin/common/types/domain'; -import { omit } from 'lodash'; - -interface CommonSavedObjectAttributes { - id?: string | null; - created_at?: string | null; - updated_at?: string | null; - version?: string | null; - [key: string]: unknown; -} - -const savedObjectCommonAttributes = ['created_at', 'updated_at', 'version', 'id']; - -export const removeServerGeneratedPropertiesFromObject = ( - object: T, - keys: K[] -): Omit => { - return omit(object, keys); -}; -export const removeServerGeneratedPropertiesFromSavedObject = < - T extends CommonSavedObjectAttributes ->( - attributes: T, - keys: Array = [] -): Omit => { - return removeServerGeneratedPropertiesFromObject(attributes, [ - ...savedObjectCommonAttributes, - ...keys, - ]); -}; - -export const removeServerGeneratedPropertiesFromCase = (theCase: Case): Partial => { - return removeServerGeneratedPropertiesFromSavedObject(theCase, ['closed_at']); -}; - -export const removeServerGeneratedPropertiesFromComments = ( - comments: Attachment[] | undefined -): Array> | undefined => { - return comments?.map((comment) => { - return removeServerGeneratedPropertiesFromSavedObject(comment, []); - }); -}; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/cases/post_case.ts b/x-pack/test_serverless/api_integration/test_suites/observability/cases/post_case.ts index 2bb37149c007f..01cf60d424f90 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/cases/post_case.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/cases/post_case.ts @@ -9,22 +9,19 @@ import expect from '@kbn/expect'; import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { deleteCasesByESQuery, createCase, getPostCaseRequest } from './helpers/api'; export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); + const svlCases = getService('svlCases'); describe('post_case', () => { afterEach(async () => { - await deleteCasesByESQuery(es); + await svlCases.api.deleteCasesByESQuery(); }); it('should create a case', async () => { expect( - await createCase( - supertest, - getPostCaseRequest({ + await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('observability', { connector: { id: '123', name: 'Jira', @@ -39,9 +36,8 @@ export default ({ getService }: FtrProviderContext): void => { it('should throw 403 when create a case with securitySolution as owner', async () => { expect( - await createCase( - supertest, - getPostCaseRequest({ + await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('observability', { owner: 'securitySolution', }), 403 @@ -51,9 +47,8 @@ export default ({ getService }: FtrProviderContext): void => { it('should throw 403 when create a case with cases as owner', async () => { expect( - await createCase( - supertest, - getPostCaseRequest({ + await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('observability', { owner: 'cases', }), 403 diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts new file mode 100644 index 0000000000000..f75faa2b2f686 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts @@ -0,0 +1,188 @@ +/* + * Copyright 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 { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); + const logger = getService('log'); + + // Blocked API: index_not_found_exception: no such index [.alerts-observability.threshold.alerts-default] + // Issue: https://github.com/elastic/kibana/issues/165138 + describe.skip('Custom Threshold rule - AVG - PCT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await dataViewApi.create({ + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await dataViewApi.delete({ + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await alertingApi.createRule({ + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await alertingApi.waitForAlertInIndex({ + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts new file mode 100644 index 0000000000000..fbcb7a1404293 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -0,0 +1,184 @@ +/* + * Copyright 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 { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); + + // Blocked API: index_not_found_exception: no such index [.alerts-observability.threshold.alerts-default] + // Issue: https://github.com/elastic/kibana/issues/165138 + describe.skip('Custom Threshold rule - AVG - PCT - NoData', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id-no-data'; + let actionId: string; + let ruleId: string; + + before(async () => { + await dataViewApi.create({ + name: 'no-data-pattern', + id: DATA_VIEW_ID, + title: 'no-data-pattern', + }); + }); + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await dataViewApi.delete({ + id: DATA_VIEW_ID, + }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await alertingApi.createRule({ + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await alertingApi.waitForAlertInIndex({ + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.nodata' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + index: 'data-view-id-no-data', + query: { query: '', language: 'kuery' }, + }, + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts new file mode 100644 index 0000000000000..32c8111f33083 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright 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 { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); + + // Issue: https://github.com/elastic/kibana/issues/165138 + describe.skip('Custom Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await dataViewApi.create({ + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await dataViewApi.delete({ + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await alertingApi.createRule({ + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.9], + timeSize: 1, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, + { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, + ], + equation: '((A + A) / (B + B))', + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await alertingApi.waitForAlertInIndex({ + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.9], + timeSize: 1, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, + { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, + ], + equation: '((A + A) / (B + B))', + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts new file mode 100644 index 0000000000000..1fa95b8cab8a9 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts @@ -0,0 +1,185 @@ +/* + * Copyright 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 { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); + + // Issue: https://github.com/elastic/kibana/issues/165138 + describe.skip('Custom Threshold rule - DOCUMENTS_COUNT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await dataViewApi.create({ + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await dataViewApi.delete({ + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await alertingApi.createRule({ + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [2], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await alertingApi.waitForAlertInIndex({ + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [2], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: 'count' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts new file mode 100644 index 0000000000000..8bd8312a5184a --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts @@ -0,0 +1,239 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kbnTestConfig } from '@kbn/test'; +import moment from 'moment'; +import { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); + let alertId: string; + let startedAt: string; + + // Issue: https://github.com/elastic/kibana/issues/165138 + describe.skip('Custom Threshold rule - GROUP_BY - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await dataViewApi.create({ + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await dataViewApi.delete({ + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await alertingApi.createRule({ + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT_OR_EQ, + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.cpu.total.norm.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + groupBy: ['host.name'], + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + reason: '{{context.reason}}', + value: '{{context.value}}', + host: '{{context.host}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await alertingApi.waitForAlertInIndex({ + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; + startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start']; + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (BETA)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source).property('host.name', 'host-0'); + expect(resp.hits.hits[0]._source) + .property('host.mac') + .eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']); + expect(resp.hits.hits[0]._source).property('container.id', 'container-0'); + expect(resp.hits.hits[0]._source).property('container.name', 'container-name'); + expect(resp.hits.hits[0]._source).not.property('container.cpu'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>=', + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + groupBy: ['host.name'], + }); + }); + + it('should set correct action variables', async () => { + const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString(); + const resp = await alertingApi.waitForDocumentInIndex<{ + ruleType: string; + alertDetailsUrl: string; + reason: string; + value: string; + host: string; + }>({ + indexName: ALERT_ACTION_INDEX, + }); + const { protocol, hostname, port } = kbnTestConfig.getUrlParts(); + + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); + expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( + `${protocol}://${hostname}:${port}/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` + ); + expect(resp.hits.hits[0]._source?.reason).eql( + 'Custom equation is 0.8 in the last 1 min for host-0. Alert when >= 0.2.' + ); + expect(resp.hits.hits[0]._source?.value).eql('0.8'); + expect(resp.hits.hits[0]._source?.host).eql( + '{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}' + ); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts new file mode 100644 index 0000000000000..944068af6a21b --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Custom Threshold Rule', function () { + loadTestFile(require.resolve('./avg_pct_fired')); + loadTestFile(require.resolve('./avg_pct_no_data')); + loadTestFile(require.resolve('./documents_count_fired')); + loadTestFile(require.resolve('./custom_eq_avg_bytes_fired')); + loadTestFile(require.resolve('./group_by_fired')); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts index d9643f91d70ae..a3a5ab552ee3f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts @@ -9,6 +9,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Serverless observability API - feature flags', function () { - loadTestFile(require.resolve('./threshold_rule')); + loadTestFile(require.resolve('./custom_threshold_rule')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_fired.ts deleted file mode 100644 index 48256d153b98a..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_fired.ts +++ /dev/null @@ -1,182 +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 { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const alertingApi = getService('alertingApi'); - const dataViewApi = getService('dataViewApi'); - const logger = getService('log'); - - // Blocked API: index_not_found_exception: no such index [.alerts-observability.threshold.alerts-default] - // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - AVG - PCT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await dataViewApi.create({ - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest - .delete(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await supertest - .delete(`/api/actions/connector/${actionId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await dataViewApi.delete({ - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await alertingApi.createIndexConnector({ - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await alertingApi.createRule({ - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await alertingApi.waitForRuleStatus({ - ruleId, - expectedStatus: 'active', - }); - expect(executionStatus).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>', - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_no_data.ts b/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_no_data.ts deleted file mode 100644 index 3d97a7a094339..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_no_data.ts +++ /dev/null @@ -1,178 +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 { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const alertingApi = getService('alertingApi'); - const dataViewApi = getService('dataViewApi'); - - // Blocked API: index_not_found_exception: no such index [.alerts-observability.threshold.alerts-default] - // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - AVG - PCT - NoData', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id-no-data'; - let actionId: string; - let ruleId: string; - - before(async () => { - await dataViewApi.create({ - name: 'no-data-pattern', - id: DATA_VIEW_ID, - title: 'no-data-pattern', - }); - }); - - after(async () => { - await supertest - .delete(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await supertest - .delete(`/api/actions/connector/${actionId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await dataViewApi.delete({ - id: DATA_VIEW_ID, - }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await alertingApi.createIndexConnector({ - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await alertingApi.createRule({ - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await alertingApi.waitForRuleStatus({ - ruleId, - expectedStatus: 'active', - }); - expect(executionStatus).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.nodata'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>', - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - index: 'data-view-id-no-data', - query: { query: '', language: 'kuery' }, - }, - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/custom_eq_avg_bytes_fired.ts deleted file mode 100644 index 276ac212dac41..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/custom_eq_avg_bytes_fired.ts +++ /dev/null @@ -1,193 +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. - */ -/* - * Copyright 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 { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const alertingApi = getService('alertingApi'); - const dataViewApi = getService('dataViewApi'); - - // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await dataViewApi.create({ - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest - .delete(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await supertest - .delete(`/api/actions/connector/${actionId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await dataViewApi.delete({ - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await alertingApi.createIndexConnector({ - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await alertingApi.createRule({ - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.9], - timeSize: 1, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, - { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, - ], - equation: 'A / B ', - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await alertingApi.waitForRuleStatus({ - ruleId, - expectedStatus: 'active', - }); - expect(executionStatus).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [0.9], - timeSize: 1, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, - { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, - ], - equation: 'A / B ', - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/documents_count_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/documents_count_fired.ts deleted file mode 100644 index 0a771d3363791..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/documents_count_fired.ts +++ /dev/null @@ -1,179 +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 { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const alertingApi = getService('alertingApi'); - const dataViewApi = getService('dataViewApi'); - - // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - DOCUMENTS_COUNT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await dataViewApi.create({ - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest - .delete(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await supertest - .delete(`/api/actions/connector/${actionId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await dataViewApi.delete({ - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await alertingApi.createIndexConnector({ - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await alertingApi.createRule({ - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT, - threshold: [2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await alertingApi.waitForRuleStatus({ - ruleId, - expectedStatus: 'active', - }); - expect(executionStatus).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>', - threshold: [2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [{ name: 'A', filter: '', aggType: 'count' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/group_by_fired.ts deleted file mode 100644 index b288c4edf28a8..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/group_by_fired.ts +++ /dev/null @@ -1,233 +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. - */ -/* - * Copyright 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 { kbnTestConfig } from '@kbn/test'; -import moment from 'moment'; -import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const alertingApi = getService('alertingApi'); - const dataViewApi = getService('dataViewApi'); - let alertId: string; - let startedAt: string; - - // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - GROUP_BY - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_ID = 'data-view-id'; - let infraDataIndex: string; - let actionId: string; - let ruleId: string; - - before(async () => { - infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); - await dataViewApi.create({ - name: 'metrics-fake_hosts', - id: DATA_VIEW_ID, - title: 'metrics-fake_hosts', - }); - }); - - after(async () => { - await supertest - .delete(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await supertest - .delete(`/api/actions/connector/${actionId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, - }); - await dataViewApi.delete({ - id: DATA_VIEW_ID, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); - await cleanup({ esClient, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await alertingApi.createIndexConnector({ - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - }); - - const createdRule = await alertingApi.createRule({ - tags: ['observability'], - consumer: 'alerts', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT_OR_EQ, - threshold: [0.2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [ - { name: 'A', field: 'system.cpu.total.norm.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - groupBy: ['host.name'], - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - alertDetailsUrl: '{{context.alertDetailsUrl}}', - reason: '{{context.reason}}', - value: '{{context.value}}', - host: '{{context.host}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await alertingApi.waitForRuleStatus({ - ruleId, - expectedStatus: 'active', - }); - expect(executionStatus).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, - ruleId, - }); - alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; - startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start']; - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source).property('host.name', 'host-0'); - expect(resp.hits.hits[0]._source) - .property('host.mac') - .eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']); - expect(resp.hits.hits[0]._source).property('container.id', 'container-0'); - expect(resp.hits.hits[0]._source).property('container.name', 'container-name'); - expect(resp.hits.hits[0]._source).not.property('container.cpu'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - aggType: 'custom', - comparator: '>=', - threshold: [0.2], - timeSize: 1, - timeUnit: 'm', - customMetrics: [{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - groupBy: ['host.name'], - }); - }); - - it('should set correct action variables', async () => { - const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString(); - const resp = await alertingApi.waitForDocumentInIndex<{ - ruleType: string; - alertDetailsUrl: string; - reason: string; - value: string; - host: string; - }>({ - indexName: ALERT_ACTION_INDEX, - }); - const { protocol, hostname, port } = kbnTestConfig.getUrlParts(); - - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold'); - expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `${protocol}://${hostname}:${port}/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` - ); - expect(resp.hits.hits[0]._source?.reason).eql( - 'Custom equation is 0.8 in the last 1 min for host-0. Alert when >= 0.2.' - ); - expect(resp.hits.hits[0]._source?.value).eql('0.8'); - expect(resp.hits.hits[0]._source?.host).eql( - '{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}' - ); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/index.ts b/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/index.ts deleted file mode 100644 index dbb8968d2d946..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('Threshold Rule', function () { - loadTestFile(require.resolve('./avg_pct_fired')); - loadTestFile(require.resolve('./avg_pct_no_data')); - loadTestFile(require.resolve('./documents_count_fired')); - loadTestFile(require.resolve('./custom_eq_avg_bytes_fired')); - loadTestFile(require.resolve('./group_by_fired')); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cases/find_cases.ts b/x-pack/test_serverless/api_integration/test_suites/security/cases/find_cases.ts index 07283119aea2f..ee44874a67fac 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cases/find_cases.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cases/find_cases.ts @@ -8,35 +8,34 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - findCases, - createCase, - deleteAllCaseItems, - findCasesResp, - postCaseReq, -} from './helpers/api'; - export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const es = getService('es'); + const svlCases = getService('svlCases'); + + let findCasesResp: any; + let postCaseReq: any; describe('find_cases', () => { describe('basic tests', () => { + before(async () => { + findCasesResp = svlCases.api.getFindCasesResp(); + postCaseReq = svlCases.api.getPostCaseReq('securitySolution'); + }); + afterEach(async () => { - await deleteAllCaseItems(es); + await svlCases.api.deleteAllCaseItems(); }); it('should return empty response', async () => { - const cases = await findCases({ supertest }); + const cases = await svlCases.api.findCases({}); expect(cases).to.eql(findCasesResp); }); it('should return cases', async () => { - const a = await createCase(supertest, postCaseReq); - const b = await createCase(supertest, postCaseReq); - const c = await createCase(supertest, postCaseReq); + const a = await svlCases.api.createCase(postCaseReq); + const b = await svlCases.api.createCase(postCaseReq); + const c = await svlCases.api.createCase(postCaseReq); - const cases = await findCases({ supertest }); + const cases = await svlCases.api.findCases({}); expect(cases).to.eql({ ...findCasesResp, @@ -47,12 +46,14 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns empty response when trying to find cases with owner as cases', async () => { - const cases = await findCases({ supertest, query: { owner: 'cases' } }); + const cases = await svlCases.api.findCases({ query: { owner: 'cases' } }); expect(cases).to.eql(findCasesResp); }); it('returns empty response when trying to find cases with owner as observability', async () => { - const cases = await findCases({ supertest, query: { owner: 'observability' } }); + const cases = await svlCases.api.findCases({ + query: { owner: 'observability' }, + }); expect(cases).to.eql(findCasesResp); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cases/get_case.ts b/x-pack/test_serverless/api_integration/test_suites/security/cases/get_case.ts index c8dd9ec867514..719841ff28ab5 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cases/get_case.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cases/get_case.ts @@ -8,30 +8,25 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - getCase, - createCase, - deleteCasesByESQuery, - getPostCaseRequest, - postCaseResp, -} from './helpers/api'; -import { removeServerGeneratedPropertiesFromCase } from './helpers/omit'; - export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const es = getService('es'); + const svlCases = getService('svlCases'); describe('get_case', () => { afterEach(async () => { - await deleteCasesByESQuery(es); + await svlCases.api.deleteCasesByESQuery(); }); it('should return a case', async () => { - const postedCase = await createCase(supertest, getPostCaseRequest()); - const theCase = await getCase({ supertest, caseId: postedCase.id, includeComments: true }); + const postedCase = await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('securitySolution') + ); + const theCase = await svlCases.api.getCase({ + caseId: postedCase.id, + includeComments: true, + }); - const data = removeServerGeneratedPropertiesFromCase(theCase); - expect(data).to.eql(postCaseResp()); + const data = svlCases.omit.removeServerGeneratedPropertiesFromCase(theCase); + expect(data).to.eql(svlCases.api.postCaseResp('securitySolution')); expect(data.comments?.length).to.eql(0); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cases/helpers/api.ts b/x-pack/test_serverless/api_integration/test_suites/security/cases/helpers/api.ts deleted file mode 100644 index 0d1a889adeadc..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/security/cases/helpers/api.ts +++ /dev/null @@ -1,247 +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 { Client } from '@elastic/elasticsearch'; -import type SuperTest from 'supertest'; -import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server/src/saved_objects_index_pattern'; -import { CASES_URL } from '@kbn/cases-plugin/common'; -import { Case, CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain'; -import type { CasePostRequest } from '@kbn/cases-plugin/common/types/api'; -import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain'; -import { CasesFindResponse } from '@kbn/cases-plugin/common/types/api'; - -export interface User { - username: string; - password: string; - description?: string; - roles: string[]; -} - -export const superUser: User = { - username: 'superuser', - password: 'superuser', - roles: ['superuser'], -}; - -export const setupAuth = ({ - apiCall, - headers, - auth, -}: { - apiCall: SuperTest.Test; - headers: Record; - auth?: { user: User; space: string | null } | null; -}): SuperTest.Test => { - if (!Object.hasOwn(headers, 'Cookie') && auth != null) { - return apiCall.auth(auth.user.username, auth.user.password); - } - - return apiCall; -}; - -export const getSpaceUrlPrefix = (spaceId: string | undefined | null) => { - return spaceId && spaceId !== 'default' ? `/s/${spaceId}` : ``; -}; - -export const deleteAllCaseItems = async (es: Client) => { - await Promise.all([ - deleteCasesByESQuery(es), - deleteCasesUserActions(es), - deleteComments(es), - deleteConfiguration(es), - deleteMappings(es), - ]); -}; - -export const deleteCasesUserActions = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-user-actions', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteCasesByESQuery = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteComments = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-comments', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteConfiguration = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-configure', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const deleteMappings = async (es: Client): Promise => { - await es.deleteByQuery({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'type:cases-connector-mappings', - wait_for_completion: true, - refresh: true, - body: {}, - conflicts: 'proceed', - }); -}; - -export const defaultUser = { email: null, full_name: null, username: 'elastic_serverless' }; -/** - * A null filled user will occur when the security plugin is disabled - */ -export const nullUser = { email: null, full_name: null, username: null }; - -export const postCaseReq: CasePostRequest = { - description: 'This is a brand new case of a bad meanie defacing data', - title: 'Super Bad Observability Issue', - tags: ['defacement'], - severity: CaseSeverity.LOW, - connector: { - id: 'none', - name: 'none', - type: ConnectorTypes.none, - fields: null, - }, - settings: { - syncAlerts: true, - }, - owner: 'securitySolution', - assignees: [], -}; - -/** - * Return a request for creating a case. - */ -export const getPostCaseRequest = (req?: Partial): CasePostRequest => ({ - ...postCaseReq, - ...req, -}); - -export const postCaseResp = ( - id?: string | null, - req: CasePostRequest = postCaseReq -): Partial => ({ - ...req, - ...(id != null ? { id } : {}), - comments: [], - duration: null, - severity: req.severity ?? CaseSeverity.LOW, - totalAlerts: 0, - totalComment: 0, - closed_by: null, - created_by: defaultUser, - external_service: null, - status: CaseStatuses.open, - updated_by: null, - category: null, -}); - -const findCommon = { - page: 1, - per_page: 20, - total: 0, - count_open_cases: 0, - count_closed_cases: 0, - count_in_progress_cases: 0, -}; - -export const findCasesResp: CasesFindResponse = { - ...findCommon, - cases: [], -}; - -export const createCase = async ( - supertest: SuperTest.SuperTest, - params: CasePostRequest, - expectedHttpCode: number = 200, - auth: { user: User; space: string | null } | null = { user: superUser, space: null }, - headers: Record = {} -): Promise => { - const apiCall = supertest.post(`${CASES_URL}`); - - setupAuth({ apiCall, headers, auth }); - - const { body: theCase } = await apiCall - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .set(headers) - .send(params) - .expect(expectedHttpCode); - - return theCase; -}; - -export const findCases = async ({ - supertest, - query = {}, - expectedHttpCode = 200, - auth = { user: superUser, space: null }, -}: { - supertest: SuperTest.SuperTest; - query?: Record; - expectedHttpCode?: number; - auth?: { user: User; space: string | null }; -}): Promise => { - const { body: res } = await supertest - .get(`${getSpaceUrlPrefix(auth.space)}${CASES_URL}/_find`) - .auth(auth.user.username, auth.user.password) - .query({ sortOrder: 'asc', ...query }) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send() - .expect(expectedHttpCode); - - return res; -}; - -export const getCase = async ({ - supertest, - caseId, - includeComments = false, - expectedHttpCode = 200, - auth = { user: superUser, space: null }, -}: { - supertest: SuperTest.SuperTest; - caseId: string; - includeComments?: boolean; - expectedHttpCode?: number; - auth?: { user: User; space: string | null }; -}): Promise => { - const { body: theCase } = await supertest - .get( - `${getSpaceUrlPrefix(auth?.space)}${CASES_URL}/${caseId}?includeComments=${includeComments}` - ) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .auth(auth.user.username, auth.user.password) - .expect(expectedHttpCode); - - return theCase; -}; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cases/helpers/omit.ts b/x-pack/test_serverless/api_integration/test_suites/security/cases/helpers/omit.ts deleted file mode 100644 index b25506bfaebea..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/security/cases/helpers/omit.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Case, Attachment } from '@kbn/cases-plugin/common/types/domain'; -import { omit } from 'lodash'; - -interface CommonSavedObjectAttributes { - id?: string | null; - created_at?: string | null; - updated_at?: string | null; - version?: string | null; - [key: string]: unknown; -} - -const savedObjectCommonAttributes = ['created_at', 'updated_at', 'version', 'id']; - -export const removeServerGeneratedPropertiesFromObject = ( - object: T, - keys: K[] -): Omit => { - return omit(object, keys); -}; -export const removeServerGeneratedPropertiesFromSavedObject = < - T extends CommonSavedObjectAttributes ->( - attributes: T, - keys: Array = [] -): Omit => { - return removeServerGeneratedPropertiesFromObject(attributes, [ - ...savedObjectCommonAttributes, - ...keys, - ]); -}; - -export const removeServerGeneratedPropertiesFromCase = (theCase: Case): Partial => { - return removeServerGeneratedPropertiesFromSavedObject(theCase, ['closed_at']); -}; - -export const removeServerGeneratedPropertiesFromComments = ( - comments: Attachment[] | undefined -): Array> | undefined => { - return comments?.map((comment) => { - return removeServerGeneratedPropertiesFromSavedObject(comment, []); - }); -}; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cases/post_case.ts b/x-pack/test_serverless/api_integration/test_suites/security/cases/post_case.ts index 1d49545c3d53a..77917915d4bcc 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cases/post_case.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cases/post_case.ts @@ -9,22 +9,18 @@ import expect from '@kbn/expect'; import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { deleteCasesByESQuery, createCase, getPostCaseRequest, postCaseResp } from './helpers/api'; -import { removeServerGeneratedPropertiesFromCase } from './helpers/omit'; export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); + const svlCases = getService('svlCases'); describe('post_case', () => { afterEach(async () => { - await deleteCasesByESQuery(es); + await svlCases.api.deleteCasesByESQuery(); }); it('should create a case', async () => { - const postedCase = await createCase( - supertest, - getPostCaseRequest({ + const postedCase = await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('securitySolution', { connector: { id: '123', name: 'Jira', @@ -33,12 +29,13 @@ export default ({ getService }: FtrProviderContext): void => { }, }) ); - const data = removeServerGeneratedPropertiesFromCase(postedCase); + const data = svlCases.omit.removeServerGeneratedPropertiesFromCase(postedCase); expect(data).to.eql( - postCaseResp( + svlCases.api.postCaseResp( + 'securitySolution', null, - getPostCaseRequest({ + svlCases.api.getPostCaseRequest('securitySolution', { connector: { id: '123', name: 'Jira', @@ -52,9 +49,8 @@ export default ({ getService }: FtrProviderContext): void => { it('should throw 403 when trying to create a case with observability as owner', async () => { expect( - await createCase( - supertest, - getPostCaseRequest({ + await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('securitySolution', { owner: 'observability', }), 403 @@ -64,9 +60,8 @@ export default ({ getService }: FtrProviderContext): void => { it('should throw 403 when trying to create a case with cases as owner', async () => { expect( - await createCase( - supertest, - getPostCaseRequest({ + await svlCases.api.createCase( + svlCases.api.getPostCaseRequest('securitySolution', { owner: 'cases', }), 403 diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts index 02ddf326fef8d..a570d7a7e72f9 100644 --- a/x-pack/test_serverless/functional/config.base.ts +++ b/x-pack/test_serverless/functional/config.base.ts @@ -62,6 +62,15 @@ export function createTestConfig(options: CreateTestConfigOptions) { indexManagement: { pathname: '/app/management/data/index_management', }, + connectors: { + pathname: '/app/management/insightsAndAlerting/triggersActionsConnectors/', + }, + advancedSettings: { + pathname: '/app/management/kibana/settings', + }, + login: { + pathname: '/login', + }, }, // choose where screenshots should be saved screenshots: { diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts index 8d3edb04d640a..3c3b10a5d03c8 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts @@ -151,6 +151,25 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { }); } }, + async expectBreadcrumbMissing(by: { deepLinkId: AppDeepLinkId } | { text: string }) { + if ('deepLinkId' in by) { + await testSubjects.missingOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`); + } else { + await retry.try(async () => { + expect(await getByVisibleText('~breadcrumb', by.text)).be(null); + }); + } + }, + async expectBreadcrumbTexts(expectedBreadcrumbTexts: string[]) { + await retry.try(async () => { + const breadcrumbsContainer = await testSubjects.find('breadcrumbs'); + const breadcrumbs = await breadcrumbsContainer.findAllByTestSubject('~breadcrumb'); + breadcrumbs.shift(); // remove home + expect(expectedBreadcrumbTexts.length).to.eql(breadcrumbs.length); + const texts = await Promise.all(breadcrumbs.map((b) => b.getVisibleText())); + expect(expectedBreadcrumbTexts).to.eql(texts); + }); + }, }, search: new SvlNavigationSearchPageObject(ctx), recent: { diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts index e5fefeb546f8a..cf96bfd274eb9 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts @@ -7,10 +7,21 @@ import { FtrProviderContext } from '../ftr_provider_context'; -export function SvlCommonPageProvider({ getService }: FtrProviderContext) { +export function SvlCommonPageProvider({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const config = getService('config'); + const pageObjects = getPageObjects(['security']); return { + async login() { + await pageObjects.security.forceLogout({ waitForLoginPage: false }); + return await pageObjects.security.login(config.get('servers.kibana.username')); + }, + + async forceLogout() { + await pageObjects.security.forceLogout({ waitForLoginPage: false }); + }, + async assertProjectHeaderExists() { await testSubjects.existOrFail('kibanaProjectHeader'); }, diff --git a/x-pack/test_serverless/functional/services/deployment_agnostic_services.ts b/x-pack/test_serverless/functional/services/deployment_agnostic_services.ts new file mode 100644 index 0000000000000..69cc869e9b8d9 --- /dev/null +++ b/x-pack/test_serverless/functional/services/deployment_agnostic_services.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; +// eslint-disable-next-line @kbn/imports/no_boundary_crossing +import { services as functionalServices } from '../../../test/functional/services'; +import { services as deploymentAgnosticSharedServices } from '../../shared/services/deployment_agnostic_services'; + +/* + * Some FTR services from stateful functional tests are compatible with serverless environment + * While adding a new one, make sure to verify that it works on both Kibana CI and MKI + */ +const deploymentAgnosticFunctionalServices = _.pick(functionalServices, [ + '__webdriver__', + 'aceEditor', + 'actions', + 'appsMenu', + 'browser', + 'canvasElement', + 'cases', + 'comboBox', + 'dashboardAddPanel', + 'dashboardBadgeActions', + 'dashboardCustomizePanel', + 'dashboardDrilldownPanelActions', + 'dashboardDrilldownsManage', + 'dashboardExpect', + 'dashboardPanelActions', + 'dashboardReplacePanel', + 'dashboardSettings', + 'dashboardVisualizations', + 'dataGrid', + 'docTable', + 'elasticChart', + 'embedding', + 'failureDebugging', + 'fieldEditor', + 'filterBar', + 'find', + 'flyout', + 'globalNav', + 'inspector', + 'listingTable', + 'managementMenu', + 'menuToggle', + 'monacoEditor', + 'pieChart', + 'pipelineEditor', + 'pipelineList', + 'png', + 'queryBar', + 'random', + 'renderable', + 'retryOnStale', + 'rules', + 'sampleData', + 'savedObjectsFinder', + 'savedQueryManagementComponent', + 'screenshots', + 'snapshots', + 'supertest', + 'testSubjects', + 'toasts', + 'uptime', + 'usageCollection', + 'userMenu', + 'vegaDebugInspector', +]); + +export const services = { + ...deploymentAgnosticSharedServices, + ...deploymentAgnosticFunctionalServices, +}; diff --git a/x-pack/test_serverless/functional/services/index.ts b/x-pack/test_serverless/functional/services/index.ts index 2c1ace79bc197..afc34f147d012 100644 --- a/x-pack/test_serverless/functional/services/index.ts +++ b/x-pack/test_serverless/functional/services/index.ts @@ -5,21 +5,24 @@ * 2.0. */ -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { services as xpackFunctionalServices } from '../../../test/functional/services'; +import { services as deploymentAgnosticFunctionalServices } from './deployment_agnostic_services'; import { services as svlSharedServices } from '../../shared/services'; import { SvlCommonNavigationServiceProvider } from './svl_common_navigation'; import { SvlObltNavigationServiceProvider } from './svl_oblt_navigation'; import { SvlSearchNavigationServiceProvider } from './svl_search_navigation'; import { SvlSecNavigationServiceProvider } from './svl_sec_navigation'; +import { SvlCommonScreenshotsProvider } from './svl_common_screenshots'; export const services = { - ...xpackFunctionalServices, - ...svlSharedServices, + // deployment agnostic FTR services + ...deploymentAgnosticFunctionalServices, + // serverless FTR services + ...svlSharedServices, svlCommonNavigation: SvlCommonNavigationServiceProvider, svlObltNavigation: SvlObltNavigationServiceProvider, svlSearchNavigation: SvlSearchNavigationServiceProvider, svlSecNavigation: SvlSecNavigationServiceProvider, + svlCommonScreenshots: SvlCommonScreenshotsProvider, }; diff --git a/x-pack/test_serverless/functional/services/svl_common_screenshots.ts b/x-pack/test_serverless/functional/services/svl_common_screenshots.ts new file mode 100644 index 0000000000000..20b0c8dba223e --- /dev/null +++ b/x-pack/test_serverless/functional/services/svl_common_screenshots.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function SvlCommonScreenshotsProvider({ getService }: FtrProviderContext) { + const browser = getService('browser'); + const screenshot = getService('screenshots'); + const testSubjects = getService('testSubjects'); + + const DEFAULT_WIDTH = 1920; + const DEFAULT_HEIGHT = 1080; + + return { + async takeScreenshot(name: string, subDirectories: string[], width?: number, height?: number) { + await browser.setWindowSize(width ?? DEFAULT_WIDTH, height ?? DEFAULT_HEIGHT); + await new Promise((resolve) => setTimeout(resolve, 1000)); // give components time to resize + await screenshot.take(`${name}_new`, undefined, subDirectories); + await browser.setWindowSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + }, + + async openKibanaNav() { + if (!(await testSubjects.exists('collapsibleNav'))) { + await testSubjects.click('toggleNavButton'); + } + await testSubjects.existOrFail('collapsibleNav'); + }, + + async closeKibanaNav() { + if (await testSubjects.exists('collapsibleNav')) { + await testSubjects.click('toggleNavButton'); + } + await testSubjects.missingOrFail('collapsibleNav'); + }, + + async removeFocusFromElement() { + // open and close the Kibana nav to un-focus the last used element + await this.openKibanaNav(); + await this.closeKibanaNav(); + }, + }; +} diff --git a/x-pack/test_serverless/functional/test_suites/common/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/common/advanced_settings.ts new file mode 100644 index 0000000000000..f24d3350b9744 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/advanced_settings.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { ALL_COMMON_SETTINGS } from '@kbn/serverless-common-settings'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const retry = getService('retry'); + + // Skip until we enable the Advanced settings app in serverless + describe.skip('Common advanced settings', function () { + before(async () => { + await pageObjects.common.navigateToApp('advancedSettings'); + }); + + it('renders the page', async () => { + await retry.waitFor('title to be visible', async () => { + return await testSubjects.exists('managementSettingsTitle'); + }); + + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/settings`); + }); + + describe('renders common settings', () => { + for (const settingId of ALL_COMMON_SETTINGS) { + it('renders ' + settingId + ' edit field', async () => { + const isColorPickerField = + settingId === 'banners:textColor' || settingId === 'banners:backgroundColor'; + const fieldTestSubj = + (isColorPickerField ? 'euiColorPickerAnchor ' : '') + + 'advancedSetting-editField-' + + settingId; + expect(await testSubjects.exists(fieldTestSubj)).to.be(true); + }); + } + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/common/data_view_mgmt.ts b/x-pack/test_serverless/functional/test_suites/common/data_view_mgmt.ts index 7e41ed68f8def..1b6972162f8ee 100644 --- a/x-pack/test_serverless/functional/test_suites/common/data_view_mgmt.ts +++ b/x-pack/test_serverless/functional/test_suites/common/data_view_mgmt.ts @@ -7,6 +7,8 @@ import expect from 'expect'; import { DATA_VIEW_PATH } from '@kbn/data-views-plugin/server'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { INITIAL_REST_VERSION } from '@kbn/data-views-plugin/server/constants'; import { FtrProviderContext } from '../../ftr_provider_context'; const archivePath = 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index'; @@ -17,38 +19,86 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const supertest = getService('supertest'); const testSubjects = getService('testSubjects'); - describe('Data View Management', function () { - let dataViewId = ''; + // FLAKY: https://github.com/elastic/kibana/issues/165804 + // FLAKY: https://github.com/elastic/kibana/issues/165796 + // FLAKY: https://github.com/elastic/kibana/issues/165425 + describe.skip('Data View Management', function () { + describe('disables scripted fields', function () { + let dataViewId = ''; - before(async () => { - await esArchiver.load(archivePath); + before(async () => { + await esArchiver.load(archivePath); - const response = await supertest - .post(DATA_VIEW_PATH) - .set('kbn-xsrf', 'some-xsrf-token') - .send({ - data_view: { - title: 'basic_index', - }, - override: true, - }); + const response = await supertest + .post(DATA_VIEW_PATH) + .set('kbn-xsrf', 'some-xsrf-token') + .send({ + data_view: { + title: 'basic_index', + }, + override: true, + }); - expect(response.status).toBe(200); - dataViewId = response.body.data_view.id; - }); + expect(response.status).toBe(200); + dataViewId = response.body.data_view.id; + }); - after(async () => { - await esArchiver.unload(archivePath); - await supertest.delete(`${DATA_VIEW_PATH}/${dataViewId}`).set('kbn-xsrf', 'some-xsrf-token'); + after(async () => { + await esArchiver.unload(archivePath); + await supertest + .delete(`${DATA_VIEW_PATH}/${dataViewId}`) + .set('kbn-xsrf', 'some-xsrf-token'); + }); + + it('Scripted fields tab is missing', async () => { + await PageObjects.common.navigateToUrl('management', 'kibana/dataViews', { + shouldUseHashForSubUrl: false, + }); + await testSubjects.click('detail-link-basic_index'); + await testSubjects.exists('tab-indexedFields'); + await testSubjects.missingOrFail('tab-scriptedFields'); + }); }); - it('Scripted fields tab is missing', async () => { - await PageObjects.common.navigateToUrl('management', 'kibana/dataViews', { - shouldUseHashForSubUrl: false, + describe('disables rollups', function () { + let dataViewId = ''; + before(async () => { + await esArchiver.load( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + + const response = await supertest + .post(DATA_VIEW_PATH) + .set('kbn-xsrf', 'some-xsrf-token') + .send({ + data_view: { + title: 'basic_index', + type: 'rollup', + }, + override: true, + }) + .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION); + dataViewId = response.body.data_view.id; + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + await supertest + .delete(`${DATA_VIEW_PATH}/${dataViewId}`) + .set('kbn-xsrf', 'some-xsrf-token'); + }); + + it('hides rollup UI tags', async () => { + await PageObjects.common.navigateToUrl('management', 'kibana/dataViews', { + shouldUseHashForSubUrl: false, + }); + await testSubjects.exists('detail-link-basic_index'); + await testSubjects.missingOrFail('rollup-tag'); + await testSubjects.click('detail-link-basic_index'); + await testSubjects.missingOrFail('rollup-tag'); }); - await testSubjects.click('detail-link-basic_index'); - await testSubjects.exists('tab-indexedFields'); - await testSubjects.missingOrFail('tab-scriptedFields'); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/data_view_field_editor_example/index.ts b/x-pack/test_serverless/functional/test_suites/common/examples/data_view_field_editor_example/index.ts index 5e3ab0cd27057..106f504cae82b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/data_view_field_editor_example/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/data_view_field_editor_example/index.ts @@ -11,7 +11,13 @@ import type { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const es = getService('es'); - const PageObjects = getPageObjects(['common', 'header', 'settings', 'svlCommonNavigation']); + const PageObjects = getPageObjects([ + 'common', + 'header', + 'settings', + 'svlCommonNavigation', + 'svlCommonPage', + ]); const testSubjects = getService('testSubjects'); const find = getService('find'); const retry = getService('retry'); @@ -19,16 +25,22 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid describe('data view field editor example', function () { before(async () => { + // TODO: Serverless tests require login first + await PageObjects.svlCommonPage.login(); // TODO: emptyKibanaIndex fails in Serverless with // "index_not_found_exception: no such index [.kibana_ingest]", // so it was switched to `savedObjects.cleanStandardList()` await kibanaServer.savedObjects.cleanStandardList(); await browser.setWindowSize(1300, 900); - await es.transport.request({ - path: '/blogs/_doc', - method: 'POST', - body: { user: 'matt', message: 20 }, - }); + await es.transport.request( + { + path: '/blogs/_doc', + method: 'POST', + body: { user: 'matt', message: 20 }, + }, + // TODO: Extend timeout in Serverless + { requestTimeout: '1m' } + ); // TODO: Navigation to Data View Management is different in Serverless await PageObjects.common.navigateToApp('management'); diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts b/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts index 7b97c1256ce6d..d653c6f8505ba 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts @@ -12,7 +12,7 @@ const TEST_START_TIME = 'Sep 19, 2015 @ 06:31:44.000'; const TEST_END_TIME = 'Sep 23, 2015 @ 18:31:44.000'; export default ({ getService, getPageObjects }: FtrProviderContext) => { - const PageObjects = getPageObjects(['common', 'timePicker', 'header']); + const PageObjects = getPageObjects(['common', 'timePicker', 'header', 'svlCommonPage']); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); @@ -22,6 +22,8 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { describe('Customizations', () => { before(async () => { + // TODO: Serverless tests require login first + await PageObjects.svlCommonPage.login(); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/field_formats/index.ts b/x-pack/test_serverless/functional/test_suites/common/examples/field_formats/index.ts index 8bd85620e9857..9da271eee5941 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/field_formats/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/field_formats/index.ts @@ -10,10 +10,12 @@ import type { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'svlCommonPage']); describe('Field formats example', function () { before(async () => { + // TODO: Serverless tests require login first + await PageObjects.svlCommonPage.login(); await PageObjects.common.navigateToApp('fieldFormatsExample'); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/partial_results/index.ts b/x-pack/test_serverless/functional/test_suites/common/examples/partial_results/index.ts index fa0036812672d..9ac96c87fa941 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/partial_results/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/partial_results/index.ts @@ -10,10 +10,12 @@ import type { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'svlCommonPage']); describe('Partial Results Example', function () { before(async () => { + // TODO: Serverless tests require login first + await PageObjects.svlCommonPage.login(); await PageObjects.common.navigateToApp('partialResultsExample'); const element = await testSubjects.find('example-help'); diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts b/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts index 026fa9f2efd25..a98cc05f0bfd3 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts @@ -24,7 +24,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); - describe('handling warnings with search source fetch', function () { + // Failing: See https://github.com/elastic/kibana/issues/165623 + // FLAKY: https://github.com/elastic/kibana/issues/165379 + // FLAKY: https://github.com/elastic/kibana/issues/165502 + // FLAKY: https://github.com/elastic/kibana/issues/165503 + // FLAKY: https://github.com/elastic/kibana/issues/165624 + // FLAKY: https://github.com/elastic/kibana/issues/165635 + describe.skip('handling warnings with search source fetch', function () { const dataViewTitle = 'sample-01,sample-01-rollup'; const fromTime = 'Jun 17, 2022 @ 00:00:00.000'; const toTime = 'Jun 23, 2022 @ 00:00:00.000'; @@ -119,8 +125,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // click "see full error" button in the toast const [openShardModalButton] = await testSubjects.findAll('openShardFailureModalBtn'); await openShardModalButton.click(); - const modalHeader = await testSubjects.find('shardFailureModalTitle'); - expect(await modalHeader.getVisibleText()).to.be('2 of 4 shards failed'); + + await retry.waitFor('modal title visible', async () => { + const modalHeader = await testSubjects.find('shardFailureModalTitle'); + return (await modalHeader.getVisibleText()) === '2 of 4 shards failed'; + }); + // request await testSubjects.click('shardFailuresModalRequestButton'); const requestBlock = await testSubjects.find('shardsFailedModalRequestBlock'); @@ -165,8 +175,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // click "see full error" button in the toast const [openShardModalButton] = await testSubjects.findAll('openShardFailureModalBtn'); await openShardModalButton.click(); - const modalHeader = await testSubjects.find('shardFailureModalTitle'); - expect(await modalHeader.getVisibleText()).to.be('2 of 4 shards failed'); + + await retry.waitFor('modal title visible', async () => { + const modalHeader = await testSubjects.find('shardFailureModalTitle'); + return (await modalHeader.getVisibleText()) === '2 of 4 shards failed'; + }); + // request await testSubjects.click('shardFailuresModalRequestButton'); const requestBlock = await testSubjects.find('shardsFailedModalRequestBlock'); diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/partial_results_example.ts b/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/partial_results_example.ts index 93364f85cc94f..38f055d9be277 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/partial_results_example.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/partial_results_example.ts @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common']); const retry = getService('retry'); - describe('Partial results example', () => { + // FLAKY: https://github.com/elastic/kibana/issues/165763 + describe.skip('Partial results example', () => { before(async () => { await PageObjects.common.navigateToApp('searchExamples'); await testSubjects.click('/search'); diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/search_example.ts b/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/search_example.ts index 0e5584d656c98..67a2e41d50314 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/search_example.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/search_examples/search_example.ts @@ -15,7 +15,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const comboBox = getService('comboBox'); const toasts = getService('toasts'); - describe('Search example', () => { + // Failing: See https://github.com/elastic/kibana/issues/165730 + // FLAKY: https://github.com/elastic/kibana/issues/165735 + describe.skip('Search example', () => { describe('with bfetch', () => { testSearchExample(); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/existing_fields.ts b/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/existing_fields.ts index 184b7b5d788a0..2b8effe5fcd58 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/existing_fields.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/existing_fields.ts @@ -51,7 +51,9 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { await PageObjects.header.waitUntilLoadingHasFinished(); } - describe('Fields existence info', () => { + // Failing: See https://github.com/elastic/kibana/issues/165938 + // Failing: See https://github.com/elastic/kibana/issues/165927 + describe.skip('Fields existence info', () => { before(async () => { await esArchiver.load( 'test/api_integration/fixtures/es_archiver/index_patterns/constant_keyword' diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/field_stats.ts b/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/field_stats.ts index 1df48eed373de..aacd1352280bb 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/field_stats.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/unified_field_list_examples/field_stats.ts @@ -21,7 +21,8 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const filterBar = getService('filterBar'); const dataViewTitle = 'logstash-2015.09.22'; - describe('Field stats', () => { + // FLAKY: https://github.com/elastic/kibana/issues/165882 + describe.skip('Field stats', () => { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); @@ -58,7 +59,8 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { await PageObjects.unifiedFieldList.cleanSidebarLocalStorage(); }); - describe('field distribution', () => { + // FLAKY: https://github.com/elastic/kibana/issues/165797 + describe.skip('field distribution', () => { before(async () => { await PageObjects.unifiedFieldList.toggleSidebarSection('empty'); // it will allow to render more fields in Available fields section }); diff --git a/x-pack/test_serverless/functional/test_suites/common/home_page.ts b/x-pack/test_serverless/functional/test_suites/common/home_page.ts index c1b3dad37d292..744fc48c3ee41 100644 --- a/x-pack/test_serverless/functional/test_suites/common/home_page.ts +++ b/x-pack/test_serverless/functional/test_suites/common/home_page.ts @@ -11,7 +11,9 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const svlCommonPage = getPageObject('svlCommonPage'); const svlCommonNavigation = getService('svlCommonNavigation'); - describe('home page', function () { + // Failing: See https://github.com/elastic/kibana/issues/165386 + // FLAKY: https://github.com/elastic/kibana/issues/165414 + describe.skip('home page', function () { it('has project header', async () => { await svlCommonNavigation.navigateToKibanaHome(); await svlCommonPage.assertProjectHeaderExists(); diff --git a/x-pack/test_serverless/functional/test_suites/common/index.ts b/x-pack/test_serverless/functional/test_suites/common/index.ts index 08e816c9bdfda..89fe34c19f640 100644 --- a/x-pack/test_serverless/functional/test_suites/common/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/index.ts @@ -13,10 +13,12 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./management')); // platform security + loadTestFile(require.resolve('./security/api_keys')); loadTestFile(require.resolve('./security/navigation/avatar_menu')); // Management loadTestFile(require.resolve('./index_management')); + loadTestFile(require.resolve('./advanced_settings')); // Data View Management loadTestFile(require.resolve('./data_view_mgmt')); diff --git a/x-pack/test_serverless/functional/test_suites/common/index_management/create_enrich_policy.ts b/x-pack/test_serverless/functional/test_suites/common/index_management/create_enrich_policy.ts new file mode 100644 index 0000000000000..383e55f1e5579 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/index_management/create_enrich_policy.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'indexManagement', 'header', 'svlCommonPage']); + const log = getService('log'); + const security = getService('security'); + const comboBox = getService('comboBox'); + const testSubjects = getService('testSubjects'); + const es = getService('es'); + + const INDEX_NAME = `index-${Math.random()}`; + const POLICY_NAME = `policy-${Math.random()}`; + + describe('Create enrich policy', function () { + before(async () => { + await log.debug('Creating test index'); + try { + await es.indices.create({ + index: INDEX_NAME, + body: { + mappings: { + properties: { + email: { + type: 'text', + }, + age: { + type: 'long', + }, + }, + }, + }, + }); + } catch (e) { + log.debug('[Setup error] Error creating test policy'); + throw e; + } + + await log.debug('Navigating to the enrich policies tab'); + await pageObjects.svlCommonPage.login(); + await security.testUser.setRoles(['index_management_user']); + await pageObjects.common.navigateToApp('indexManagement'); + + // Navigate to the enrich policies tab + await pageObjects.indexManagement.changeTabs('enrich_policiesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); + // Click create policy button + await testSubjects.click('enrichPoliciesEmptyPromptCreateButton'); + }); + + after(async () => { + await log.debug('Cleaning up created index'); + + try { + await es.indices.delete({ index: INDEX_NAME }); + } catch (e) { + log.debug('[Teardown error] Error deleting test policy'); + throw e; + } finally { + await pageObjects.svlCommonPage.forceLogout(); + } + }); + + it('shows create enrich policies page and docs link', async () => { + expect(await testSubjects.exists('createEnrichPolicyHeaderContent')).to.be(true); + expect(await testSubjects.exists('createEnrichPolicyDocumentationLink')).to.be(true); + }); + + it('can create an enrich policy', async () => { + // Complete configuration step + await testSubjects.setValue('policyNameField > input', POLICY_NAME); + await testSubjects.setValue('policyTypeField', 'match'); + await comboBox.set('policySourceIndicesField', INDEX_NAME); + await testSubjects.click('nextButton'); + + // Complete field selection step + await comboBox.set('matchField', 'email'); + await comboBox.set('enrichFields', 'age'); + await testSubjects.click('nextButton'); + + // Create policy + await testSubjects.click('createButton'); + + // Expect to be redirected to the enrich policies tab + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Expect to have that policy in the table + const policyList = await testSubjects.findAll('enrichPolicyDetailsLink'); + expect(policyList.length).to.be(1); + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/common/index_management/index.ts b/x-pack/test_serverless/functional/test_suites/common/index_management/index.ts index e4c394551af72..a511d8f51280a 100644 --- a/x-pack/test_serverless/functional/test_suites/common/index_management/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/index_management/index.ts @@ -11,5 +11,6 @@ export default ({ loadTestFile }: FtrProviderContext) => { describe('Index Management', function () { loadTestFile(require.resolve('./index_templates')); loadTestFile(require.resolve('./indices')); + loadTestFile(require.resolve('./create_enrich_policy')); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/common/index_management/index_templates.ts b/x-pack/test_serverless/functional/test_suites/common/index_management/index_templates.ts index 26feb519a39a8..7e9355076887b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/index_management/index_templates.ts +++ b/x-pack/test_serverless/functional/test_suites/common/index_management/index_templates.ts @@ -9,25 +9,22 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { - const testSubjects = getService('testSubjects'); - const pageObjects = getPageObjects(['common', 'indexManagement', 'header']); + const pageObjects = getPageObjects(['svlCommonPage', 'common', 'indexManagement', 'header']); const browser = getService('browser'); const security = getService('security'); - const retry = getService('retry'); describe('Index Templates', function () { before(async () => { await security.testUser.setRoles(['index_management_user']); + // Navigate to the index management page + await pageObjects.svlCommonPage.login(); await pageObjects.common.navigateToApp('indexManagement'); // Navigate to the index templates tab await pageObjects.indexManagement.changeTabs('templatesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); }); it('renders the index templates tab', async () => { - await retry.waitFor('index templates list to be visible', async () => { - return await testSubjects.exists('templateList'); - }); - const url = await browser.getCurrentUrl(); expect(url).to.contain(`/templates`); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/index_management/indices.ts b/x-pack/test_serverless/functional/test_suites/common/index_management/indices.ts index 02b6acdfbfa56..5d9d53863270b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/index_management/indices.ts +++ b/x-pack/test_serverless/functional/test_suites/common/index_management/indices.ts @@ -9,25 +9,22 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { - const testSubjects = getService('testSubjects'); - const pageObjects = getPageObjects(['common', 'indexManagement', 'header']); + const pageObjects = getPageObjects(['svlCommonPage', 'common', 'indexManagement', 'header']); const browser = getService('browser'); const security = getService('security'); - const retry = getService('retry'); describe('Indices', function () { before(async () => { await security.testUser.setRoles(['index_management_user']); + // Navigate to the index management page + await pageObjects.svlCommonPage.login(); await pageObjects.common.navigateToApp('indexManagement'); // Navigate to the indices tab await pageObjects.indexManagement.changeTabs('indicesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); }); it('renders the indices tab', async () => { - await retry.waitFor('indices list to be visible', async () => { - return await testSubjects.exists('indicesList'); - }); - const url = await browser.getCurrentUrl(); expect(url).to.contain(`/indices`); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/management.ts b/x-pack/test_serverless/functional/test_suites/common/management.ts index fe2561854c49a..7ea23c7be4bb5 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management.ts @@ -11,7 +11,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const commonPage = getPageObject('common'); const testSubjects = getService('testSubjects'); - describe('Management', function () { + // Flaky in serverless tests + describe.skip('Management', function () { describe('Disabled UIs', () => { const DISABLED_PLUGINS = [ { diff --git a/x-pack/test_serverless/functional/test_suites/common/sample_data.ts b/x-pack/test_serverless/functional/test_suites/common/sample_data.ts new file mode 100644 index 0000000000000..a127ce0ee3e1d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/sample_data.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['settings', 'common', 'header', 'home']); + + describe('Sample data in serverless', function () { + it('Sample data loads', async () => { + await PageObjects.home.addSampleDataSet('ecommerce'); + const ecommerce = await PageObjects.home.isSampleDataSetInstalled('ecommerce'); + expect(ecommerce).toBe(true); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/security/api_keys.ts b/x-pack/test_serverless/functional/test_suites/common/security/api_keys.ts index ab650c4c43a25..610ab692a4b0f 100644 --- a/x-pack/test_serverless/functional/test_suites/common/security/api_keys.ts +++ b/x-pack/test_serverless/functional/test_suites/common/security/api_keys.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; -import type { estypes } from '@elastic/elasticsearch'; import { FtrProviderContext } from '../../../ftr_provider_context'; async function clearAllApiKeys(esClient: Client, logger: ToolingLog) { @@ -25,433 +24,43 @@ async function clearAllApiKeys(esClient: Client, logger: ToolingLog) { } export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'svlCommonPage', 'apiKeys']); + const browser = getService('browser'); const es = getService('es'); - const pageObjects = getPageObjects(['common', 'apiKeys']); const log = getService('log'); - const security = getService('security'); - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const browser = getService('browser'); - const retry = getService('retry'); - - const testRoles: Record = { - viewer: { - cluster: ['all'], - indices: [ - { - names: ['*'], - privileges: ['all'], - allow_restricted_indices: false, - }, - { - names: ['*'], - privileges: ['monitor', 'read', 'view_index_metadata', 'read_cross_cluster'], - allow_restricted_indices: true, - }, - ], - run_as: ['*'], - }, - }; - - const otherUser: estypes.SecurityPutUserRequest = { - username: 'other_user', - password: 'changeme', - roles: ['superuser'], - }; - async function ensureApiKeysExist(apiKeysNames: string[]) { - await retry.try(async () => { - for (const apiKeyName of apiKeysNames) { - log.debug(`Checking if API key ("${apiKeyName}") exists.`); - await pageObjects.apiKeys.ensureApiKeyExists(apiKeyName); - log.debug(`API key ("${apiKeyName}") exists.`); - } - }); - } - - describe('Home page', function () { + describe('API keys', () => { before(async () => { - await clearAllApiKeys(es, log); - await security.testUser.setRoles(['kibana_admin']); - await es.security.putUser(otherUser); - - await pageObjects.common.navigateToUrl('management', 'security/api_keys', { - shouldUseHashForSubUrl: false, - }); + await pageObjects.svlCommonPage.login(); }); after(async () => { - await es.security.deleteUser({ username: otherUser.username }); - await security.testUser.restoreDefaults(); - }); - - // https://www.elastic.co/guide/en/kibana/7.6/api-keys.html#api-keys-security-privileges - it('Hides management link if user is not authorized', async () => { - await testSubjects.missingOrFail('apiKeys'); - }); - - it('Loads the app', async () => { - await security.testUser.setRoles(['test_api_keys']); - log.debug('Checking for Create API key call to action'); - await find.existsByLinkText('Create API key'); - }); - - describe('creates API key', function () { - before(async () => { - await security.testUser.setRoles(['kibana_admin', 'test_api_keys']); - await pageObjects.common.navigateToUrl('management', 'security/api_keys', { - shouldUseHashForSubUrl: false, - }); - - // Delete any API keys created outside of these tests - await pageObjects.apiKeys.bulkDeleteApiKeys(); - }); - - afterEach(async () => { - await pageObjects.apiKeys.deleteAllApiKeyOneByOne(); - }); - - after(async () => { - await clearAllApiKeys(es, log); - }); - - it('when submitting form, close dialog and displays new api key', async () => { - const apiKeyName = 'Happy API Key'; - await pageObjects.apiKeys.clickOnPromptCreateApiKey(); - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys/create'); - expect(await pageObjects.apiKeys.getFlyoutTitleText()).to.be('Create API key'); - - await pageObjects.apiKeys.setApiKeyName(apiKeyName); - await pageObjects.apiKeys.clickSubmitButtonOnApiKeyFlyout(); - const newApiKeyCreation = await pageObjects.apiKeys.getNewApiKeyCreation(); - - expect(await browser.getCurrentUrl()).to.not.contain( - 'app/management/security/api_keys/flyout' - ); - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); - expect(await pageObjects.apiKeys.isApiKeyModalExists()).to.be(false); - expect(newApiKeyCreation).to.be(`Created API key '${apiKeyName}'`); - }); - - it('with optional expiration, redirects back and displays base64', async () => { - const apiKeyName = 'Happy expiration API key'; - await pageObjects.apiKeys.clickOnPromptCreateApiKey(); - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys/create'); - - await pageObjects.apiKeys.setApiKeyName(apiKeyName); - await pageObjects.apiKeys.toggleCustomExpiration(); - await pageObjects.apiKeys.setApiKeyCustomExpiration('12'); - await pageObjects.apiKeys.clickSubmitButtonOnApiKeyFlyout(); - const newApiKeyCreation = await pageObjects.apiKeys.getNewApiKeyCreation(); - - expect(await browser.getCurrentUrl()).to.not.contain( - 'app/management/security/api_keys/create' - ); - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); - expect(await pageObjects.apiKeys.isApiKeyModalExists()).to.be(false); - expect(newApiKeyCreation).to.be(`Created API key '${apiKeyName}'`); - }); - }); - - describe('Update API key', function () { - before(async () => { - await security.testUser.setRoles(['kibana_admin', 'test_api_keys']); - await pageObjects.common.navigateToUrl('management', 'security/api_keys', { - shouldUseHashForSubUrl: false, - }); - - // Delete any API keys created outside these tests - await pageObjects.apiKeys.bulkDeleteApiKeys(); - }); - - afterEach(async () => { - await pageObjects.apiKeys.deleteAllApiKeyOneByOne(); - }); - - after(async () => { - await clearAllApiKeys(es, log); - }); - - it('should create a new API key, click the name of the new row, fill out and submit form, and display success message', async () => { - // Create a key to updated - const apiKeyName = 'Happy API Key to Update'; - - await es.security.grantApiKey({ - api_key: { - name: apiKeyName, - expiration: '1d', - }, - grant_type: 'password', - run_as: 'elastic', - username: 'elastic', - password: 'changeme', - }); - - await browser.refresh(); - - log.debug('API key created, moving on to update'); - - // Update newly created API Key - await pageObjects.apiKeys.clickExistingApiKeyToOpenFlyout(apiKeyName); - - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); - - await pageObjects.apiKeys.waitForSubmitButtonOnApiKeyFlyoutEnabled(); - - expect(await pageObjects.apiKeys.getFlyoutTitleText()).to.be('Update API key'); - - // Verify name input box are disabled - const apiKeyNameInput = await pageObjects.apiKeys.getApiKeyName(); - expect(await apiKeyNameInput.isEnabled()).to.be(false); - - // Status should be displayed - const apiKeyStatus = await pageObjects.apiKeys.getFlyoutApiKeyStatus(); - expect(await apiKeyStatus).to.be('Expires in a day'); - - // Verify metadata is editable - const apiKeyMetadataSwitch = await pageObjects.apiKeys.getMetadataSwitch(); - expect(await apiKeyMetadataSwitch.isEnabled()).to.be(true); - - // Verify restrict privileges is editable - const apiKeyRestrictPrivilegesSwitch = - await pageObjects.apiKeys.getRestrictPrivilegesSwitch(); - expect(await apiKeyRestrictPrivilegesSwitch.isEnabled()).to.be(true); - - // Toggle restrict privileges so the code editor shows up - await apiKeyRestrictPrivilegesSwitch.click(); - - // Toggle metadata switch so the code editor shows up - await apiKeyMetadataSwitch.click(); - - // Check default value of metadata and set value - const restrictPrivilegesCodeEditorValue = - await pageObjects.apiKeys.getCodeEditorValueByIndex(0); - expect(restrictPrivilegesCodeEditorValue).to.be('{}'); - - // Check default value of metadata and set value - const metadataCodeEditorValue = await pageObjects.apiKeys.getCodeEditorValueByIndex(1); - expect(metadataCodeEditorValue).to.be('{}'); - - await pageObjects.apiKeys.setCodeEditorValueByIndex(0, JSON.stringify(testRoles)); - - await pageObjects.apiKeys.setCodeEditorValueByIndex(1, '{"name":"metadataTest"}'); - - // Submit values to update API key - await pageObjects.apiKeys.clickSubmitButtonOnApiKeyFlyout(); - - // Get success message - const updatedApiKeyToastText = await pageObjects.apiKeys.getApiKeyUpdateSuccessToast(); - expect(updatedApiKeyToastText).to.be(`Updated API key '${apiKeyName}'`); - - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); - expect(await pageObjects.apiKeys.isApiKeyModalExists()).to.be(false); - }); - }); - - describe('Readonly API key', function () { - before(async () => { - await security.role.create('read_security_role', { - elasticsearch: { - cluster: ['read_security'], - }, - kibana: [ - { - feature: { - infrastructure: ['read'], - }, - spaces: ['*'], - }, - ], - }); - - await security.testUser.setRoles(['kibana_admin', 'test_api_keys']); - await pageObjects.common.navigateToUrl('management', 'security/api_keys', { - shouldUseHashForSubUrl: false, - }); - - // Delete any API keys created outside these tests - await pageObjects.apiKeys.bulkDeleteApiKeys(); - }); - - afterEach(async () => { - await pageObjects.apiKeys.deleteAllApiKeyOneByOne(); - }); - - after(async () => { - await clearAllApiKeys(es, log); - }); - - // Serverless tests run as elastic (superuser) so unable to test `read_security` permissions - it.skip('should see readonly form elements', async () => { - // Create a key to updated - const apiKeyName = 'Happy API Key to View'; - - await es.security.grantApiKey({ - api_key: { - name: apiKeyName, - expiration: '1d', - metadata: { name: 'metadatatest' }, - role_descriptors: { ...testRoles }, - }, - grant_type: 'password', - run_as: 'elastic', - username: 'elastic', - password: 'changeme', - }); - - await browser.refresh(); - - log.debug('API key created, moving on to view'); - - // Set testUsers roles to have the `read_security` cluster privilege - await security.testUser.setRoles(['read_security_role']); - - // View newly created API Key - await pageObjects.apiKeys.clickExistingApiKeyToOpenFlyout(apiKeyName); - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); - expect(await pageObjects.apiKeys.getFlyoutTitleText()).to.be('API key details'); - - // Verify name input box are disabled - const apiKeyNameInput = await pageObjects.apiKeys.getApiKeyName(); - expect(await apiKeyNameInput.isEnabled()).to.be(false); - - // Status should be displayed - const apiKeyStatus = await pageObjects.apiKeys.getFlyoutApiKeyStatus(); - expect(await apiKeyStatus).to.be('Expires in a day'); - - const apiKeyMetadataSwitch = await pageObjects.apiKeys.getMetadataSwitch(); - const apiKeyRestrictPrivilegesSwitch = - await pageObjects.apiKeys.getRestrictPrivilegesSwitch(); - - // Verify metadata and restrict privileges switches are now disabled - expect(await apiKeyMetadataSwitch.isEnabled()).to.be(false); - expect(await apiKeyRestrictPrivilegesSwitch.isEnabled()).to.be(false); - - // Close flyout with cancel - await pageObjects.apiKeys.clickCancelButtonOnApiKeyFlyout(); - - // Undo `read_security_role` - await security.testUser.setRoles(['kibana_admin', 'test_api_keys']); - }); - - it('should show the `API key details` flyout if the expiration date is passed', async () => { - const apiKeyName = 'expired-key'; - - await es.security.grantApiKey({ - api_key: { - name: apiKeyName, - expiration: '1ms', - }, - grant_type: 'password', - run_as: 'elastic', - username: 'elastic', - password: 'changeme', - }); - - await browser.refresh(); - - log.debug('API key created, moving on to view'); - - await pageObjects.apiKeys.clickExistingApiKeyToOpenFlyout(apiKeyName); - - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); - expect(await pageObjects.apiKeys.getFlyoutTitleText()).to.be('API key details'); - - // Verify name input box are disabled - const apiKeyNameInput = await pageObjects.apiKeys.getApiKeyName(); - expect(await apiKeyNameInput.isEnabled()).to.be(false); - - // Status should be displayed - const apiKeyStatus = await pageObjects.apiKeys.getFlyoutApiKeyStatus(); - expect(await apiKeyStatus).to.be('Expired'); - - const apiKeyMetadataSwitch = await pageObjects.apiKeys.getMetadataSwitch(); - const apiKeyRestrictPrivilegesSwitch = - await pageObjects.apiKeys.getRestrictPrivilegesSwitch(); - - // Verify metadata and restrict privileges switches are now disabled - expect(await apiKeyMetadataSwitch.isEnabled()).to.be(false); - expect(await apiKeyRestrictPrivilegesSwitch.isEnabled()).to.be(false); - - await pageObjects.apiKeys.clickCancelButtonOnApiKeyFlyout(); - }); - - it('should show the `API key details flyout` if the API key does not belong to the user', async () => { - const apiKeyName = 'other-key'; - - await es.security.grantApiKey({ - api_key: { - name: apiKeyName, - }, - grant_type: 'password', - run_as: otherUser.username, - username: 'elastic', - password: 'changeme', - }); - - await browser.refresh(); - - log.debug('API key created, moving on to view'); - - await pageObjects.apiKeys.clickExistingApiKeyToOpenFlyout(apiKeyName); - expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); - expect(await pageObjects.apiKeys.getFlyoutTitleText()).to.be('API key details'); - - // Verify name input box are disabled - const apiKeyNameInput = await pageObjects.apiKeys.getApiKeyName(); - expect(await apiKeyNameInput.isEnabled()).to.be(false); - - // Status should be displayed - const apiKeyStatus = await pageObjects.apiKeys.getFlyoutApiKeyStatus(); - expect(await apiKeyStatus).to.be('Active'); - - const apiKeyMetadataSwitch = await pageObjects.apiKeys.getMetadataSwitch(); - const apiKeyRestrictPrivilegesSwitch = - await pageObjects.apiKeys.getRestrictPrivilegesSwitch(); - - // Verify metadata and restrict privileges switches are now disabled - expect(await apiKeyMetadataSwitch.isEnabled()).to.be(false); - expect(await apiKeyRestrictPrivilegesSwitch.isEnabled()).to.be(false); - - await pageObjects.apiKeys.clickCancelButtonOnApiKeyFlyout(); - }); + await clearAllApiKeys(es, log); + await pageObjects.svlCommonPage.forceLogout(); }); - describe('deletes API key(s)', function () { - before(async () => { - await security.testUser.setRoles(['kibana_admin', 'test_api_keys']); - await pageObjects.common.navigateToUrl('management', 'security/api_keys', { - shouldUseHashForSubUrl: false, - }); - }); - - beforeEach(async () => { - await pageObjects.apiKeys.clickOnPromptCreateApiKey(); - await pageObjects.apiKeys.setApiKeyName('api key 1'); - await pageObjects.apiKeys.clickSubmitButtonOnApiKeyFlyout(); - await ensureApiKeysExist(['api key 1']); + it('should create and delete API keys correctly', async () => { + await pageObjects.common.navigateToUrl('management', 'security/api_keys', { + shouldUseHashForSubUrl: false, }); - it('one by one', async () => { - await pageObjects.apiKeys.deleteAllApiKeyOneByOne(); - expect(await pageObjects.apiKeys.getApiKeysFirstPromptTitle()).to.be( - 'Create your first API key' - ); - }); + const apiKeyName = 'Happy API Key'; + await pageObjects.apiKeys.clickOnPromptCreateApiKey(); + expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys/create'); + expect(await pageObjects.apiKeys.getFlyoutTitleText()).to.be('Create API key'); - it('by bulk', async () => { - await pageObjects.apiKeys.clickOnTableCreateApiKey(); - await pageObjects.apiKeys.setApiKeyName('api key 2'); - await pageObjects.apiKeys.clickSubmitButtonOnApiKeyFlyout(); + await pageObjects.apiKeys.setApiKeyName(apiKeyName); + await pageObjects.apiKeys.clickSubmitButtonOnApiKeyFlyout(); + const newApiKeyCreation = await pageObjects.apiKeys.getNewApiKeyCreation(); - // Make sure all API keys we want to delete are created and rendered. - await ensureApiKeysExist(['api key 1', 'api key 2']); + expect(await browser.getCurrentUrl()).to.not.contain( + 'app/management/security/api_keys/create' + ); + expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); + expect(await pageObjects.apiKeys.isApiKeyModalExists()).to.be(false); + expect(newApiKeyCreation).to.be(`Created API key '${apiKeyName}'`); - await pageObjects.apiKeys.bulkDeleteApiKeys(); - expect(await pageObjects.apiKeys.getApiKeysFirstPromptTitle()).to.be( - 'Create your first API key' - ); - }); + await pageObjects.apiKeys.deleteAllApiKeyOneByOne(); }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/common/security/navigation/avatar_menu.ts b/x-pack/test_serverless/functional/test_suites/common/security/navigation/avatar_menu.ts index b8647d0423e02..0a58a580b81dd 100644 --- a/x-pack/test_serverless/functional/test_suites/common/security/navigation/avatar_menu.ts +++ b/x-pack/test_serverless/functional/test_suites/common/security/navigation/avatar_menu.ts @@ -11,7 +11,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const svlCommonPage = getPageObject('svlCommonPage'); const svlCommonNavigation = getService('svlCommonNavigation'); - describe('Avatar menu', function () { + // FLAKY: https://github.com/elastic/kibana/issues/165694 + describe.skip('Avatar menu', function () { it('is displayed', async () => { await svlCommonNavigation.navigateToKibanaHome(); await svlCommonPage.assertUserAvatarExists(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts new file mode 100644 index 0000000000000..e723b81dbe20f --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts @@ -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 expect from '@kbn/expect'; +import { OBSERVABILITY_PROJECT_SETTINGS } from '@kbn/serverless-observability-settings'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const retry = getService('retry'); + + // Skip until we enable the Advanced settings app in serverless + describe.skip('Observability advanced settings', function () { + before(async () => { + await pageObjects.common.navigateToApp('advancedSettings'); + }); + + it('renders the page', async () => { + await retry.waitFor('title to be visible', async () => { + return await testSubjects.exists('managementSettingsTitle'); + }); + + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/settings`); + }); + + describe('renders observability settings', () => { + for (const settingId of OBSERVABILITY_PROJECT_SETTINGS) { + it('renders ' + settingId + ' edit field', async () => { + const fieldTestSubj = 'advancedSetting-editField-' + settingId; + expect(await testSubjects.exists(fieldTestSubj)).to.be(true); + }); + } + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts index e8be4ff1cf4d3..bfd2b8700d785 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts @@ -12,6 +12,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const dashboard = getPageObject('dashboard'); const lens = getPageObject('lens'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlObltNavigation = getService('svlObltNavigation'); const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); @@ -19,9 +20,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const cases = getService('cases'); const find = getService('find'); - describe('persistable attachment', () => { + describe('Cases persistable attachments', () => { describe('lens visualization', () => { before(async () => { + await svlCommonPage.login(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' @@ -45,6 +47,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await kibanaServer.importExport.unload( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); + + await svlCommonPage.forceLogout(); }); it('adds lens visualization to a new case', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index bc007a7ad4b7b..b2697ac6d400e 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -11,25 +11,32 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getPageObject, getService }: FtrProviderContext) => { const common = getPageObject('common'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlObltNavigation = getService('svlObltNavigation'); const testSubjects = getService('testSubjects'); const cases = getService('cases'); const toasts = getService('toasts'); - describe('Configure', function () { + // Failing: See https://github.com/elastic/kibana/issues/166448 + describe.skip('Configure Case', function () { before(async () => { + await svlCommonPage.login(); + await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); - - await common.clickAndValidate('configure-case-button', 'case-configure-title'); }); after(async () => { await cases.api.deleteAllCases(); + await svlCommonPage.forceLogout(); }); describe('Closure options', function () { + before(async () => { + await common.clickAndValidate('configure-case-button', 'case-configure-title'); + }); + it('defaults the closure option correctly', async () => { await cases.common.assertRadioGroupValue('closure-options-radio-group', 'close-by-user'); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts index fdf12469cad3d..b6c76f3ab1f49 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts @@ -15,18 +15,24 @@ import { navigateToCasesApp } from '../../../../shared/lib/cases'; const owner = OBSERVABILITY_OWNER; export default ({ getService, getPageObject }: FtrProviderContext) => { - describe('Create case', function () { + describe('Create Case', function () { const find = getService('find'); const cases = getService('cases'); const testSubjects = getService('testSubjects'); + const svlCommonPage = getPageObject('svlCommonPage'); const config = getService('config'); + before(async () => { + await svlCommonPage.login(); + }); + beforeEach(async () => { await navigateToCasesApp(getPageObject, getService, owner); }); after(async () => { await cases.api.deleteAllCases(); + await svlCommonPage.forceLogout(); }); it('creates a case', async () => { @@ -64,75 +70,5 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { const button = await find.byCssSelector('[data-test-subj*="case-callout"] button'); expect(await button.getVisibleText()).equal('Add connector'); }); - - it('displays errors correctly while creating a case', async () => { - const caseTitle = Array(161).fill('x').toString(); - const longTag = Array(256).fill('a').toString(); - const longCategory = Array(51).fill('x').toString(); - - await cases.create.openCreateCasePage(); - await cases.create.createCase({ - title: caseTitle, - description: '', - tag: longTag, - severity: CaseSeverity.HIGH, - category: longCategory, - }); - - await testSubjects.click('create-case-submit'); - - const title = await find.byCssSelector('[data-test-subj="caseTitle"]'); - expect(await title.getVisibleText()).contain( - 'The length of the name is too long. The maximum length is 160 characters.' - ); - - const description = await testSubjects.find('caseDescription'); - expect(await description.getVisibleText()).contain('A description is required.'); - - const tags = await testSubjects.find('caseTags'); - expect(await tags.getVisibleText()).contain( - 'The length of the tag is too long. The maximum length is 256 characters.' - ); - - const category = await testSubjects.find('case-create-form-category'); - expect(await category.getVisibleText()).contain( - 'The length of the category is too long. The maximum length is 50 characters.' - ); - }); - - it('trims fields correctly while creating a case', async () => { - const titleWithSpace = 'This is a title with spaces '; - const descriptionWithSpace = - 'This is a case description with empty spaces at the end!! '; - const categoryWithSpace = 'security '; - const tagWithSpace = 'coke '; - - await cases.create.openCreateCasePage(); - await cases.create.createCase({ - title: titleWithSpace, - description: descriptionWithSpace, - tag: tagWithSpace, - severity: CaseSeverity.HIGH, - category: categoryWithSpace, - }); - - await testSubjects.click('create-case-submit'); - - // validate title is trimmed - const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]'); - expect(await title.getVisibleText()).equal(titleWithSpace.trim()); - - // validate description is trimmed - const description = await testSubjects.find('scrollable-markdown'); - expect(await description.getVisibleText()).equal(descriptionWithSpace.trim()); - - // validate tag exists and is trimmed - const tag = await testSubjects.find(`tag-${tagWithSpace.trim()}`); - expect(await tag.getVisibleText()).equal(tagWithSpace.trim()); - - // validate category exists and is trimmed - const category = await testSubjects.find(`category-viewer-${categoryWithSpace.trim()}`); - expect(await category.getVisibleText()).equal(categoryWithSpace.trim()); - }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/empty.txt b/x-pack/test_serverless/functional/test_suites/observability/cases/empty.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts index cb4aa44b09c35..61cbd55f04b66 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts @@ -15,10 +15,12 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); const cases = getService('cases'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlObltNavigation = getService('svlObltNavigation'); - describe('cases list', () => { + describe('Cases list', () => { before(async () => { + await svlCommonPage.login(); await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); }); @@ -26,6 +28,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { after(async () => { await cases.api.deleteAllCases(); await cases.casesTable.waitForCasesToBeDeleted(); + await svlCommonPage.forceLogout(); }); describe('empty state', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts new file mode 100644 index 0000000000000..be6295903615a --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts @@ -0,0 +1,454 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { v4 as uuidv4 } from 'uuid'; +import { CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain'; + +import { OBSERVABILITY_OWNER } from '@kbn/cases-plugin/common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { + createOneCaseBeforeDeleteAllAfter, + createAndNavigateToCase, +} from '../../../../shared/lib/cases/helpers'; + +const owner = OBSERVABILITY_OWNER; + +export default ({ getPageObject, getService }: FtrProviderContext) => { + const header = getPageObject('header'); + const testSubjects = getService('testSubjects'); + const cases = getService('cases'); + const find = getService('find'); + + const retry = getService('retry'); + const comboBox = getService('comboBox'); + const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); + + describe('Case View', () => { + before(async () => { + await svlCommonPage.login(); + }); + + after(async () => { + await svlCommonPage.forceLogout(); + }); + + describe('page', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('should show the case view page correctly', async () => { + await testSubjects.existOrFail('case-view-title'); + await testSubjects.existOrFail('header-page-supplements'); + + await testSubjects.existOrFail('case-view-tab-title-activity'); + await testSubjects.existOrFail('case-view-tab-title-files'); + await testSubjects.existOrFail('description'); + + await testSubjects.existOrFail('case-view-activity'); + + await testSubjects.existOrFail('case-view-assignees'); + await testSubjects.existOrFail('sidebar-severity'); + await testSubjects.existOrFail('case-view-user-list-reporter'); + await testSubjects.existOrFail('case-view-user-list-participants'); + await testSubjects.existOrFail('case-view-tag-list'); + await testSubjects.existOrFail('cases-categories'); + await testSubjects.existOrFail('sidebar-connectors'); + }); + }); + + describe('properties', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('edits a case title from the case view page', async () => { + const newTitle = `test-${uuidv4()}`; + + await testSubjects.click('editable-title-header-value'); + await testSubjects.setValue('editable-title-input-field', newTitle); + await testSubjects.click('editable-title-submit-btn'); + + // wait for backend response + await retry.tryForTime(5000, async () => { + const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]'); + expect(await title.getVisibleText()).equal(newTitle); + }); + + // validate user action + await find.byCssSelector('[data-test-subj*="title-update-action"]'); + }); + + it('adds a comment to a case', async () => { + const commentArea = await find.byCssSelector( + '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea' + ); + await commentArea.focus(); + await commentArea.type('Test comment from automation'); + + await testSubjects.click('submit-comment'); + + // validate user action + const newComment = await find.byCssSelector( + '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]' + ); + expect(await newComment.getVisibleText()).equal('Test comment from automation'); + }); + + it('adds a category to a case', async () => { + const category = uuidv4(); + await testSubjects.click('category-edit-button'); + await comboBox.setCustom('comboBoxInput', category); + await testSubjects.click('edit-category-submit'); + + // validate category was added + await testSubjects.existOrFail('category-viewer-' + category); + + // validate user action + await find.byCssSelector('[data-test-subj*="category-update-action"]'); + }); + + it('deletes a category from a case', async () => { + await find.byCssSelector('[data-test-subj*="category-viewer-"]'); + + await testSubjects.click('category-remove-button'); + + await testSubjects.existOrFail('no-categories'); + // validate user action + await find.byCssSelector('[data-test-subj*="category-delete-action"]'); + }); + + it('adds a tag to a case', async () => { + const tag = uuidv4(); + await testSubjects.click('tag-list-edit-button'); + await comboBox.setCustom('comboBoxInput', tag); + await testSubjects.click('edit-tags-submit'); + + // validate tag was added + await testSubjects.existOrFail('tag-' + tag); + + // validate user action + await find.byCssSelector('[data-test-subj*="tags-add-action"]'); + }); + + it('deletes a tag from a case', async () => { + await testSubjects.click('tag-list-edit-button'); + // find the tag button and click the close button + const button = await find.byCssSelector('[data-test-subj="comboBoxInput"] button'); + await button.click(); + await testSubjects.click('edit-tags-submit'); + + // validate user action + await find.byCssSelector('[data-test-subj*="tags-delete-action"]'); + }); + + describe('status', () => { + it('changes a case status to closed via dropdown-menu', async () => { + await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses.closed); + + // validate user action + await find.byCssSelector( + '[data-test-subj*="status-update-action"] [data-test-subj="case-status-badge-closed"]' + ); + // validates dropdown tag + await testSubjects.existOrFail( + 'case-view-status-dropdown > case-status-badge-popover-button-closed' + ); + }); + }); + + describe('Severity field', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('shows the severity field on the sidebar', async () => { + await testSubjects.existOrFail('case-severity-selection'); + }); + + it('changes the severity level from the selector', async () => { + await cases.common.selectSeverity(CaseSeverity.MEDIUM); + await header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('case-severity-selection-' + CaseSeverity.MEDIUM); + + // validate user action + await find.byCssSelector('[data-test-subj*="severity-update-action"]'); + }); + }); + }); + + describe('actions', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('deletes the case successfully', async () => { + await cases.singleCase.deleteCase(); + await cases.casesTable.waitForTableToFinishLoading(); + await cases.casesTable.validateCasesTableHasNthRows(0); + }); + }); + + describe('filter activity', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('filters by all by default', async () => { + const allBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-all"] span.euiNotificationBadge' + ); + + expect(await allBadge.getAttribute('aria-label')).equal('1 active filters'); + }); + + it('filters by comment successfully', async () => { + const commentBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-comments"] span.euiNotificationBadge' + ); + + expect(await commentBadge.getAttribute('aria-label')).equal('0 available filters'); + + const commentArea = await find.byCssSelector( + '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea' + ); + await commentArea.focus(); + await commentArea.type('Test comment from automation'); + await testSubjects.click('submit-comment'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('user-actions-filter-activity-button-comments'); + + expect(await commentBadge.getAttribute('aria-label')).equal('1 active filters'); + }); + + it('filters by history successfully', async () => { + const historyBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-history"] span.euiNotificationBadge' + ); + + expect(await historyBadge.getAttribute('aria-label')).equal('1 available filters'); + + await cases.common.selectSeverity(CaseSeverity.MEDIUM); + + await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('user-actions-filter-activity-button-history'); + + expect(await historyBadge.getAttribute('aria-label')).equal('3 active filters'); + }); + + it('sorts by newest first successfully', async () => { + await testSubjects.click('user-actions-filter-activity-button-all'); + + const AllBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-all"] span.euiNotificationBadge' + ); + + expect(await AllBadge.getVisibleText()).equal('4'); + + const sortDesc = await find.byCssSelector( + '[data-test-subj="user-actions-sort-select"] [value="desc"]' + ); + + await sortDesc.click(); + + await header.waitUntilLoadingHasFinished(); + + const userActionsLists = await find.allByCssSelector( + '[data-test-subj="user-actions-list"]' + ); + + const actionList = await userActionsLists[0].findAllByClassName('euiComment'); + + expect(await actionList[0].getAttribute('data-test-subj')).contain('status-update-action'); + }); + }); + + // FLAKY + describe.skip('Lens visualization', () => { + before(async () => { + await cases.testResources.installKibanaSampleData('logs'); + await createAndNavigateToCase(getPageObject, getService, owner); + }); + + after(async () => { + await cases.testResources.removeKibanaSampleData('logs'); + await cases.api.deleteAllCases(); + }); + + it('adds lens visualization in description', async () => { + await testSubjects.click('description-edit-icon'); + + await header.waitUntilLoadingHasFinished(); + + const editCommentTextArea = await find.byCssSelector( + '[data-test-subj*="editable-markdown-form"] textarea.euiMarkdownEditorTextArea' + ); + + await header.waitUntilLoadingHasFinished(); + + await editCommentTextArea.focus(); + + const editableDescription = await testSubjects.find('editable-markdown-form'); + + const addVisualizationButton = await editableDescription.findByCssSelector( + '[data-test-subj="euiMarkdownEditorToolbarButton"][aria-label="Visualization"]' + ); + await addVisualizationButton.click(); + + await cases.singleCase.findAndSaveVisualization('[Logs] Bytes distribution'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('editable-save-markdown'); + + await header.waitUntilLoadingHasFinished(); + + const description = await find.byCssSelector('[data-test-subj="description"]'); + + await description.findByCssSelector('[data-test-subj="xyVisChart"]'); + }); + }); + + describe('pagination', async () => { + let createdCase: any; + + before(async () => { + createdCase = await createAndNavigateToCase(getPageObject, getService, owner); + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); + + it('initially renders user actions list correctly', async () => { + await testSubjects.missingOrFail('cases-show-more-user-actions'); + + const userActionsLists = await find.allByCssSelector( + '[data-test-subj="user-actions-list"]' + ); + + expect(userActionsLists).length(1); + }); + + it('shows more actions on button click', async () => { + await cases.api.generateUserActions({ + caseId: createdCase.id, + caseVersion: createdCase.version, + totalUpdates: 4, + }); + + await testSubjects.missingOrFail('user-actions-loading'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('case-refresh'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('cases-show-more-user-actions'); + + const userActionsLists = await find.allByCssSelector( + '[data-test-subj="user-actions-list"]' + ); + + expect(userActionsLists).length(2); + + expect(await userActionsLists[0].findAllByClassName('euiComment')).length(10); + + expect(await userActionsLists[1].findAllByClassName('euiComment')).length(4); + + testSubjects.click('cases-show-more-user-actions'); + + await header.waitUntilLoadingHasFinished(); + + expect(await userActionsLists[0].findAllByClassName('euiComment')).length(20); + + expect(await userActionsLists[1].findAllByClassName('euiComment')).length(4); + }); + }); + + describe('Tabs', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('shows the "activity" tab by default', async () => { + await testSubjects.existOrFail('case-view-tab-title-activity'); + await testSubjects.existOrFail('case-view-tab-content-activity'); + }); + + it("shows the 'files' tab when clicked", async () => { + await testSubjects.click('case-view-tab-title-files'); + await testSubjects.existOrFail('case-view-tab-content-files'); + }); + }); + + describe('Files', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('adds a file to the case', async () => { + await testSubjects.click('case-view-tab-title-files'); + await testSubjects.existOrFail('case-view-tab-content-files'); + + await cases.casesFilesTable.addFile(require.resolve('./empty.txt')); + + const uploadedFileName = await testSubjects.getVisibleText('cases-files-name-text'); + expect(uploadedFileName).to.be('empty.txt'); + }); + + it('search by file name', async () => { + await cases.casesFilesTable.searchByFileName('foobar'); + await cases.casesFilesTable.emptyOrFail(); + await cases.casesFilesTable.searchByFileName('empty'); + + const uploadedFileName = await testSubjects.getVisibleText('cases-files-name-text'); + expect(uploadedFileName).to.be('empty.txt'); + }); + + it('files added to a case can be deleted', async () => { + await cases.casesFilesTable.deleteFile(0); + await cases.casesFilesTable.emptyOrFail(); + }); + + describe('Files User Activity', () => { + it('file user action is displayed correctly', async () => { + await cases.casesFilesTable.addFile(require.resolve('./empty.txt')); + + await testSubjects.click('case-view-tab-title-activity'); + await testSubjects.existOrFail('case-view-tab-content-activity'); + + const uploadedFileName = await testSubjects.getVisibleText('cases-files-name-text'); + expect(uploadedFileName).to.be('empty.txt'); + }); + }); + }); + + describe('breadcrumbs', () => { + let createdCase: any; + + before(async () => { + createdCase = await createAndNavigateToCase(getPageObject, getService, owner); + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); + + it('should set the cases title', async () => { + await svlCommonNavigation.breadcrumbs.expectExists(); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: createdCase.title }); + }); + }); + + describe('reporter', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('should render the reporter correctly', async () => { + const reporter = await cases.singleCase.getReporter(); + + const reporterText = await reporter.getVisibleText(); + + expect(reporterText).to.be('elastic_serverless'); + }); + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts index 4a8663666026c..22b59a9e3ed05 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts @@ -5,7 +5,8 @@ * 2.0. */ -describe('Serverless', () => { +// Flaky in serverless tests +describe.skip('Serverless', () => { beforeEach(() => { cy.loginAsElasticUser(); }); @@ -71,7 +72,10 @@ describe('Serverless', () => { cy.contains('Log rate analysis').click(); cy.url().should('include', '/app/ml/aiops/log_rate_analysis_index_select'); - cy.contains('Change Point Detection').click(); + cy.contains('Log pattern analysis').click(); + cy.url().should('include', '/app/ml/aiops/log_categorization_index_select'); + + cy.contains('Change point detection').click(); cy.url().should('include', '/app/ml/aiops/change_point_detection_index_select'); cy.contains('Job notifications').click(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/observability_onboarding/home.cy.ts b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/observability_onboarding/home.cy.ts index 7e74c75b62ebc..94033dd799a06 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/observability_onboarding/home.cy.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/observability_onboarding/home.cy.ts @@ -5,14 +5,15 @@ * 2.0. */ -describe('[Serverless Observability onboarding] Landing page', () => { +// Flaky in serverless tests +describe.skip('[Serverless Observability onboarding] Landing page', () => { beforeEach(() => { cy.loginAsElasticUser(); }); it('when user navigates to observability onboarding landing page is showed', () => { cy.visitKibana('/app/observabilityOnboarding'); - cy.contains('Get started with Observability'); + cy.contains('Collect and analyze logs'); }); describe('Entry point', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cypress/oblt_config.base.ts b/x-pack/test_serverless/functional/test_suites/observability/cypress/oblt_config.base.ts index 22bd611067f48..5508b8cd1a618 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cypress/oblt_config.base.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cypress/oblt_config.base.ts @@ -16,10 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...svlSharedConfig.getAll(), esTestCluster: { ...svlSharedConfig.get('esTestCluster'), - serverArgs: [ - ...svlSharedConfig.get('esTestCluster.serverArgs'), - 'xpack.security.enabled=true', - ], + serverArgs: [...svlSharedConfig.get('esTestCluster.serverArgs')], }, kbnTestServer: { ...svlSharedConfig.get('kbnTestServer'), diff --git a/x-pack/test_serverless/functional/test_suites/observability/cypress/package.json b/x-pack/test_serverless/functional/test_suites/observability/cypress/package.json index 3c2d296e313e2..bf0dad8dd01e6 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cypress/package.json +++ b/x-pack/test_serverless/functional/test_suites/observability/cypress/package.json @@ -5,9 +5,9 @@ "private": true, "license": "Elastic License 2.0", "scripts": { - "cypress:open": "../../../../../../node_modules/.bin/cypress open --config-file ./cypress.config.ts", - "cypress:run": "../../../../../../node_modules/.bin/cypress run --browser chrome --config-file ./cypress.config.ts", - "cypress:serverless:open": "node ../../../../../../scripts/functional_tests --config ./config_runner.ts", - "cypress:serverless:run": "node ../../../../../../scripts/functional_tests --config ./config_server.ts" + "cypress:open": "NODE_OPTIONS=--openssl-legacy-provider node ../../../../../../node_modules/.bin/cypress open --config-file ./cypress.config.ts", + "cypress:run": "NODE_OPTIONS=--openssl-legacy-provider node ../../../../../../node_modules/.bin/cypress run --browser chrome --config-file ./cypress.config.ts", + "cypress:serverless:open": "NODE_OPTIONS=--openssl-legacy-provider node ../../../../../../scripts/functional_tests --config ./config_runner.ts", + "cypress:serverless:run": "NODE_OPTIONS=--openssl-legacy-provider node ../../../../../../scripts/functional_tests --config ./config_server.ts" } } \ No newline at end of file diff --git a/x-pack/test_serverless/functional/test_suites/observability/cypress/support/commands.ts b/x-pack/test_serverless/functional/test_suites/observability/cypress/support/commands.ts index 715e5c9df22fc..42b6801c6ab5b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cypress/support/commands.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cypress/support/commands.ts @@ -7,27 +7,36 @@ import 'cypress-axe'; import 'cypress-real-events/support'; import URL from 'url'; +import { request } from '@kbn/security-solution-plugin/public/management/cypress/tasks/common'; +import { LoginState } from '@kbn/security-plugin/common/login_state'; Cypress.Commands.add('loginAsElasticUser', () => { const username = Cypress.env('username'); const password = Cypress.env('password'); const kibanaUrlWithoutAuth = Cypress.env('kibanaUrlWithoutAuth'); + const headers = { 'kbn-xsrf': 'e2e_test', 'x-elastic-internal-origin': 'kibana' }; cy.log(`Logging in as ${username} on ${kibanaUrlWithoutAuth}`); - cy.visit('/'); - cy.request({ - log: true, - method: 'POST', - url: `${kibanaUrlWithoutAuth}/internal/security/login`, - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: `${kibanaUrlWithoutAuth}/login`, - params: { username, password }, - }, - headers: { - 'kbn-xsrf': 'e2e_test', - 'x-elastic-internal-origin': 'kibana', - }, + cy.visit('/login'); + + cy.request({ + headers, + url: `${kibanaUrlWithoutAuth}/internal/security/login_state`, + }).then((loginState) => { + const basicProvider = loginState.body.selector.providers.find( + (provider) => provider.type === 'basic' + ); + return request({ + headers, + log: true, + method: 'POST', + url: `${kibanaUrlWithoutAuth}/internal/security/login`, + body: { + providerType: basicProvider.type, + providerName: basicProvider.name, + currentURL: `${kibanaUrlWithoutAuth}/login`, + params: { username, password }, + }, + }); }); cy.visit('/'); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/index.ts b/x-pack/test_serverless/functional/test_suites/observability/index.ts index e24841e6fbff9..3c387337a23e5 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/index.ts @@ -13,8 +13,10 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./navigation')); loadTestFile(require.resolve('./observability_log_explorer')); loadTestFile(require.resolve('./cases/attachment_framework')); + loadTestFile(require.resolve('./cases/view_case')); loadTestFile(require.resolve('./cases/configure')); - loadTestFile(require.resolve('./cases/list_view')); loadTestFile(require.resolve('./cases/create_case_form')); + loadTestFile(require.resolve('./cases/list_view')); + loadTestFile(require.resolve('./advanced_settings')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts b/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts index b8e645a93f307..68263e5ce2c39 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts @@ -9,10 +9,19 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { const svlObltOnboardingPage = getPageObject('svlObltOnboardingPage'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlObltNavigation = getService('svlObltNavigation'); const SvlObltOnboardingStreamLogFilePage = getPageObject('SvlObltOnboardingStreamLogFilePage'); describe('landing page', function () { + before(async () => { + await svlCommonPage.login(); + }); + + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('has quickstart badge', async () => { await svlObltNavigation.navigateToLandingPage(); await svlObltOnboardingPage.assertQuickstartBadgeExists(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index 7e84512ab4b0a..57b636efa6a75 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -11,14 +11,20 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { const svlObltOnboardingPage = getPageObject('svlObltOnboardingPage'); const svlObltNavigation = getService('svlObltNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); const browser = getService('browser'); describe('navigation', function () { before(async () => { + await svlCommonPage.login(); await svlObltNavigation.navigateToLandingPage(); }); + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('navigate observability sidenav & breadcrumbs', async () => { const expectNoPageReload = await svlCommonNavigation.createNoPageReloadCheck(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts index 6a9d76a9b594c..d78fc15fed8a8 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts @@ -10,7 +10,8 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['observabilityLogExplorer', 'svlCommonNavigation']); - describe('Application', () => { + // FLAKY: https://github.com/elastic/kibana/issues/165943 + describe.skip('Application', () => { it('is shown in the global search', async () => { await PageObjects.observabilityLogExplorer.navigateTo(); await PageObjects.svlCommonNavigation.search.showSearch(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/columns_selection.ts index 92ccb09a27f00..955ef8d22055f 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/columns_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/columns_selection.ts @@ -9,14 +9,16 @@ import rison from '@kbn/rison'; import querystring from 'querystring'; import { FtrProviderContext } from '../../../ftr_provider_context'; -const defaultLogColumns = ['@timestamp', 'message']; +const defaultLogColumns = ['@timestamp', 'service.name', 'host.name', 'message']; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const retry = getService('retry'); const PageObjects = getPageObjects(['discover', 'observabilityLogExplorer']); - describe('Columns selection initialization and update', () => { + // FLAKY: https://github.com/elastic/kibana/issues/165915 + // FLAKY: https://github.com/elastic/kibana/issues/165916 + describe.skip('Columns selection initialization and update', () => { before(async () => { await esArchiver.load( 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' @@ -44,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.navigateTo({ search: querystring.stringify({ _a: rison.encode({ - columns: ['message', 'data_stream.namespace'], + columns: ['service.name', 'host.name', 'message', 'data_stream.namespace'], }), }), }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selection_state.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selection_state.ts index 5652843c3fc0c..4c8ddb58bf45d 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selection_state.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selection_state.ts @@ -14,14 +14,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'observabilityLogExplorer']); - describe('DatasetSelection initialization and update', () => { + // FLAKY: https://github.com/elastic/kibana/issues/166016 + describe.skip('DatasetSelection initialization and update', () => { describe('when the "index" query param does not exist', () => { - it('should initialize the "All log datasets" selection', async () => { + it('should initialize the "All logs" selection', async () => { await PageObjects.observabilityLogExplorer.navigateTo(); const datasetSelectionTitle = await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); - expect(datasetSelectionTitle).to.be('All log datasets'); + expect(datasetSelectionTitle).to.be('All logs'); }); }); @@ -41,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(datasetSelectionTitle).to.be('[Azure Logs] activitylogs'); }); - it('should fallback to the "All log datasets" selection and notify the user of an invalid encoded index', async () => { + it('should fallback to the "All logs" selection and notify the user of an invalid encoded index', async () => { const invalidEncodedIndex = 'invalid-encoded-index'; await PageObjects.observabilityLogExplorer.navigateTo({ search: querystring.stringify({ @@ -53,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); await PageObjects.observabilityLogExplorer.assertRestoreFailureToastExist(); - expect(datasetSelectionTitle).to.be('All log datasets'); + expect(datasetSelectionTitle).to.be('All logs'); }); }); @@ -62,7 +63,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.navigateTo(); const allDatasetSelectionTitle = await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); - expect(allDatasetSelectionTitle).to.be('All log datasets'); + expect(allDatasetSelectionTitle).to.be('All logs'); const azureActivitylogsIndex = 'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtFgF4CuATmAHRZzwBu8sAJ5VadAFTkANAlhRU3BPyEiQASklFS8lu2kC55AII6wAAgAyNEFN5hWIJGnIBGDgFYOAJgDM5deCgeFAAVQQAHMgdkaihVIA==='; @@ -81,7 +82,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.goBack(); const backNavigationDatasetSelectionTitle = await PageObjects.observabilityLogExplorer.getDatasetSelectorButtonText(); - expect(backNavigationDatasetSelectionTitle).to.be('All log datasets'); + expect(backNavigationDatasetSelectionTitle).to.be('All logs'); }); // Go forward to previous page selection diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts index 622c282e716eb..5947c5d7d35cf 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts @@ -14,20 +14,29 @@ const initialPackageMap = { }; const initialPackagesTexts = Object.values(initialPackageMap); -const expectedUncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; +const uncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; +const expectedUncategorized = uncategorized.map((dataset) => dataset.split('-')[1]); export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const esArchiver = getService('esArchiver'); const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'observabilityLogExplorer']); + const PageObjects = getPageObjects(['common', 'observabilityLogExplorer', 'svlCommonPage']); + + const noIntegrationsTitle = 'No integrations found'; + const noUncategorizedTitle = 'No data streams found'; describe('Dataset Selector', () => { before(async () => { + await PageObjects.svlCommonPage.login(); await PageObjects.observabilityLogExplorer.removeInstalledPackages(); }); - describe('without installed integrations or uncategorized data streams', () => { + after(async () => { + await PageObjects.svlCommonPage.forceLogout(); + }); + + describe('as consistent behavior', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); @@ -37,36 +46,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.openDatasetSelector(); }); - describe('when open on the first navigation level', () => { - it('should always display the "All log datasets" entry as the first item', async () => { - const allLogDatasetButton = - await PageObjects.observabilityLogExplorer.getAllLogDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + it('should always display the Integrations and Uncategorized top level tabs', async () => { + const integrationsTab = await PageObjects.observabilityLogExplorer.getIntegrationsTab(); + const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab(); - const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); - const firstEntryTitle = await menuEntries[0].getVisibleText(); - - expect(allLogDatasetTitle).to.be('All log datasets'); - expect(allLogDatasetTitle).to.be(firstEntryTitle); - }); + expect(await integrationsTab.isDisplayed()).to.be(true); + expect(await integrationsTab.getVisibleText()).to.be('Integrations'); + expect(await uncategorizedTab.isDisplayed()).to.be(true); + expect(await uncategorizedTab.getVisibleText()).to.be('Uncategorized'); + }); - it('should always display the unmanaged datasets entry as the second item', async () => { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + it('should always display the "Show all logs" action', async () => { + const allLogDatasetButton = + await PageObjects.observabilityLogExplorer.getAllLogDatasetsButton(); - const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); - const secondEntryTitle = await menuEntries[1].getVisibleText(); + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); - expect(unmanagedDatasetTitle).to.be('Uncategorized'); - expect(unmanagedDatasetTitle).to.be(secondEntryTitle); - }); + expect(allLogDatasetTitle).to.be('Show all logs'); + }); + describe('when open on the integrations tab', () => { it('should display an error prompt if could not retrieve the integrations', async function () { // Skip the test in case network condition utils are not available try { await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoIntegrationsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noIntegrationsTitle + ); }); await PageObjects.common.sleep(5000); @@ -74,7 +80,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.typeSearchFieldWith('a'); await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoIntegrationsErrorExists(); + await PageObjects.observabilityLogExplorer.assertListStatusErrorPromptExistsWithTitle( + noIntegrationsTitle + ); }); await browser.restoreNetworkConditions(); @@ -83,24 +91,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); - // Skip: failing assertion - // Issue: https://github.com/elastic/kibana/issues/165138 - it.skip('should display an empty prompt for no integrations', async () => { - const { integrations } = await PageObjects.observabilityLogExplorer.getIntegrations(); - expect(integrations.length).to.be(0); + it('should display an empty prompt for no integrations', async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - await PageObjects.observabilityLogExplorer.assertNoIntegrationsPromptExists(); + expect(menuEntries.length).to.be(0); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noIntegrationsTitle + ); }); }); - describe('when navigating into Uncategorized data streams', () => { - it('should display a loading skeleton while loading', async function () { + describe('when open on the uncategorized tab', () => { + it('should display a loading skeleton while loading uncategorized datasets', async function () { // Skip the test in case network condition utils are not available try { await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await unamanagedDatasetButton.click(); + const uncategorizedTab = + await PageObjects.observabilityLogExplorer.getUncategorizedTab(); + await uncategorizedTab.click(); await PageObjects.observabilityLogExplorer.assertLoadingSkeletonExists(); @@ -110,22 +120,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); - it('should display an error prompt if could not retrieve the data streams', async function () { + it('should display an error prompt if could not retrieve the datasets', async function () { + const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab(); + await uncategorizedTab.click(); + // Skip the test in case network condition utils are not available try { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await unamanagedDatasetButton.click(); - await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoDataStreamsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noUncategorizedTitle + ); }); + await PageObjects.common.sleep(5000); await browser.setNetworkConditions('OFFLINE'); await PageObjects.observabilityLogExplorer.typeSearchFieldWith('a'); await retry.try(async () => { - await PageObjects.observabilityLogExplorer.assertNoDataStreamsErrorExists(); + await PageObjects.observabilityLogExplorer.assertListStatusErrorPromptExistsWithTitle( + noUncategorizedTitle + ); }); await browser.restoreNetworkConditions(); @@ -134,17 +148,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); - it('should display an empty prompt for no data streams', async () => { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await unamanagedDatasetButton.click(); + it('should display an empty prompt for no uncategorized data streams', async () => { + const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab(); + await uncategorizedTab.click(); - const unamanagedDatasetEntries = - await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const uncategorizedEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(unamanagedDatasetEntries.length).to.be(0); + expect(uncategorizedEntries.length).to.be(0); - await PageObjects.observabilityLogExplorer.assertNoDataStreamsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noUncategorizedTitle + ); }); }); }); @@ -167,7 +183,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await cleanupIntegrationsSetup(); }); - describe('when open on the first navigation level', () => { + describe('when open on the integrations tab', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); @@ -177,30 +193,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.openDatasetSelector(); }); - it('should always display the "All log datasets" entry as the first item', async () => { - const allLogDatasetButton = - await PageObjects.observabilityLogExplorer.getAllLogDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); - - const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); - const firstEntryTitle = await menuEntries[0].getVisibleText(); - - expect(allLogDatasetTitle).to.be('All log datasets'); - expect(allLogDatasetTitle).to.be(firstEntryTitle); - }); - - it('should always display the unmanaged datasets entry as the second item', async () => { - const unamanagedDatasetButton = - await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); - - const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); - const secondEntryTitle = await menuEntries[1].getVisibleText(); - - expect(unmanagedDatasetTitle).to.be('Uncategorized'); - expect(unmanagedDatasetTitle).to.be(secondEntryTitle); - }); - it('should display a list of installed integrations', async () => { const { integrations } = await PageObjects.observabilityLogExplorer.getIntegrations(); @@ -258,7 +250,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(integrations.length).to.be(0); }); - await PageObjects.observabilityLogExplorer.assertNoIntegrationsPromptExists(); + await PageObjects.observabilityLogExplorer.assertListStatusEmptyPromptExistsWithTitle( + noIntegrationsTitle + ); }); it('should load more integrations by scrolling to the end of the list', async () => { @@ -291,136 +285,157 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { cleanupAdditionalSetup(); }); - }); - - describe('when clicking on integration and moving into the second navigation level', () => { - before(async () => { - await PageObjects.observabilityLogExplorer.navigateTo(); - }); - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogExplorer.openDatasetSelector(); - }); + describe('clicking on integration and moving into the second navigation level', () => { + before(async () => { + await PageObjects.observabilityLogExplorer.navigateTo(); + }); - it('should display a list of available datasets', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); + beforeEach(async () => { + await browser.refresh(); + await PageObjects.observabilityLogExplorer.openDatasetSelector(); }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + it('should display a list of available datasets', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); + await retry.try(async () => { + const [panelTitleNode, integrationDatasetEntries] = + await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await integrationDatasetEntries[0].getVisibleText()).to.be('access'); + expect(await integrationDatasetEntries[1].getVisibleText()).to.be('error'); + }); }); - }); - it('should sort the datasets list by the clicked sorting option', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); - }); + it('should sort the datasets list by the clicked sorting option', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + await retry.try(async () => { + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); - // Test ascending order - await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + // Test ascending order + await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); - // Test descending order - await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + // Test descending order + await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - expect(await menuEntries[1].getVisibleText()).to.be('access'); - }); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(await menuEntries[1].getVisibleText()).to.be('access'); + }); - // Test back ascending order - await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + // Test back ascending order + await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); }); - }); - it('should filter the datasets list by the typed dataset name', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); - }); + it('should filter the datasets list by the typed dataset name', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + await retry.try(async () => { + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); - await PageObjects.observabilityLogExplorer.typeSearchFieldWith('err'); + await PageObjects.observabilityLogExplorer.typeSearchFieldWith('err'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); }); - }); - it('should update the current selection with the clicked dataset', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); - await nodes[0].click(); - }); + it('should update the current selection with the clicked dataset', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.observabilityLogExplorer.getIntegrations(); + await nodes[0].click(); + }); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + await retry.try(async () => { + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + await retry.try(async () => { + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - menuEntries[0].click(); - }); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + menuEntries[0].click(); + }); - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogExplorer.getDatasetSelectorButton(); + await retry.try(async () => { + const selectorButton = + await PageObjects.observabilityLogExplorer.getDatasetSelectorButton(); - expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + }); }); }); }); - describe('when navigating into Uncategorized data streams', () => { + describe('when open on the uncategorized tab', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); @@ -428,16 +443,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { beforeEach(async () => { await browser.refresh(); await PageObjects.observabilityLogExplorer.openDatasetSelector(); + await PageObjects.observabilityLogExplorer + .getUncategorizedTab() + .then((tab) => tab.click()); }); it('should display a list of available datasets', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); @@ -447,12 +467,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should sort the datasets list by the clicked sorting option', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); }); @@ -460,7 +478,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Test ascending order await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -470,7 +490,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Test descending order await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -480,7 +502,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Test back ascending order await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -489,18 +513,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should filter the datasets list by the typed dataset name', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); }); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); @@ -510,28 +534,30 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.typeSearchFieldWith('retail'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('logs-retail-*'); + expect(await menuEntries[0].getVisibleText()).to.be('retail'); }); }); it('should update the current selection with the clicked dataset', async () => { - const button = await PageObjects.observabilityLogExplorer.getUnmanagedDatasetsButton(); - await button.click(); - await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); }); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getUncategorizedContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); - expect(await menuEntries[0].getVisibleText()).to.be('logs-gaming-*'); + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); menuEntries[0].click(); }); @@ -539,7 +565,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const selectorButton = await PageObjects.observabilityLogExplorer.getDatasetSelectorButton(); - expect(await selectorButton.getVisibleText()).to.be('logs-gaming-*'); + expect(await selectorButton.getVisibleText()).to.be(expectedUncategorized[0]); }); }); }); @@ -561,9 +587,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); expect(await menuEntries[0].getVisibleText()).to.be('access'); @@ -574,9 +605,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.openDatasetSelector(); await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); expect(await menuEntries[0].getVisibleText()).to.be('access'); @@ -602,12 +638,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('when switching between integration panels', () => { + describe('when switching between tabs or integration panels', () => { before(async () => { await PageObjects.observabilityLogExplorer.navigateTo(); }); - it('should remember the latest search and restore its results for each integration', async () => { + it('should remember the latest search and restore its results', async () => { await PageObjects.observabilityLogExplorer.openDatasetSelector(); await PageObjects.observabilityLogExplorer.clearSearchField(); @@ -621,9 +657,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => + Promise.all([ + PageObjects.observabilityLogExplorer.getPanelTitle(menu), + PageObjects.observabilityLogExplorer.getPanelEntries(menu), + ]) + ); expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); expect(await menuEntries[0].getVisibleText()).to.be('access'); @@ -633,15 +674,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogExplorer.typeSearchFieldWith('err'); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); expect(menuEntries.length).to.be(1); expect(await menuEntries[0].getVisibleText()).to.be('error'); }); // Navigate back to integrations - const panelTitleNode = - await PageObjects.observabilityLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const panelTitleNode = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu)); panelTitleNode.click(); await retry.try(async () => { @@ -656,7 +700,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogExplorer.getCurrentPanelEntries(); + const menuEntries = await PageObjects.observabilityLogExplorer + .getIntegrationsContextMenu() + .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu)); const searchValue = await PageObjects.observabilityLogExplorer.getSearchFieldValue(); expect(searchValue).to.eql('err'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/filter_controls.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/filter_controls.ts index b5e25744f2c5b..6581cc9e6fa74 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/filter_controls.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/filter_controls.ts @@ -8,16 +8,19 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['observabilityLogExplorer']); + const PageObjects = getPageObjects(['observabilityLogExplorer', 'svlCommonPage']); const testSubjects = getService('testSubjects'); - describe('Filter controls customization', () => { + // Failing: See https://github.com/elastic/kibana/issues/166461 + describe.skip('Filter controls customization', () => { before('initialize tests', async () => { + await PageObjects.svlCommonPage.login(); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); }); after('clean up archives', async () => { await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await PageObjects.svlCommonPage.forceLogout(); }); it('renders a filter controls section as part of the unified search bar', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/header_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/header_menu.ts new file mode 100644 index 0000000000000..f37d5e33c63ae --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/header_menu.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects([ + 'discover', + 'observabilityLogExplorer', + 'svlCommonPage', + 'timePicker', + ]); + + describe('Header menu', () => { + before(async () => { + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.load( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + await PageObjects.svlCommonPage.login(); + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + after(async () => { + await PageObjects.svlCommonPage.forceLogout(); + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + }); + + it('should inject the app header menu on the top navbar', async () => { + const headerMenu = await PageObjects.observabilityLogExplorer.getHeaderMenu(); + expect(await headerMenu.isDisplayed()).to.be(true); + }); + + describe('Discover fallback link', () => { + before(async () => { + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + it('should render a button link ', async () => { + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + expect(await discoverLink.isDisplayed()).to.be(true); + }); + + it('should navigate to discover keeping the current columns/filters/query/time/data view', async () => { + // Set timerange to specific values to match data and retrieve config + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + await testSubjects.existOrFail('superDatePickerstartDatePopoverButton'); + await testSubjects.existOrFail('superDatePickerendDatePopoverButton'); + }); + + const timeConfig = await PageObjects.timePicker.getTimeConfig(); + + // Set query bar value + await PageObjects.observabilityLogExplorer.submitQuery('*favicon*'); + + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + discoverLink.click(); + + await PageObjects.discover.waitForDocTableLoadingComplete(); + + await retry.try(async () => { + expect(await PageObjects.discover.getCurrentlySelectedDataView()).to.eql('All logs'); + }); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql([ + '@timestamp', + 'service.name', + 'host.name', + 'message', + ]); + }); + + await retry.try(async () => { + expect(await PageObjects.timePicker.getTimeConfig()).to.eql(timeConfig); + }); + + await retry.try(async () => { + expect(await PageObjects.observabilityLogExplorer.getQueryBarValue()).to.eql('*favicon*'); + }); + }); + }); + + describe('Add data link', () => { + before(async () => { + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + it('should render a button link ', async () => { + const onboardingLink = await PageObjects.observabilityLogExplorer.getOnboardingLink(); + expect(await onboardingLink.isDisplayed()).to.be(true); + }); + + it('should navigate to the observability onboarding overview page', async () => { + const onboardingLink = await PageObjects.observabilityLogExplorer.getOnboardingLink(); + onboardingLink.click(); + + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/app/observabilityOnboarding`); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts index b0555b4447d27..77f89dad01f77 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dataset_selection_state')); loadTestFile(require.resolve('./dataset_selector')); loadTestFile(require.resolve('./filter_controls')); + loadTestFile(require.resolve('./header_menu')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/search/advanced_settings.ts new file mode 100644 index 0000000000000..63b6053589ea6 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/advanced_settings.ts @@ -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 expect from '@kbn/expect'; +import { SEARCH_PROJECT_SETTINGS } from '@kbn/serverless-search-settings'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const retry = getService('retry'); + + // Skip until we enable the Advanced settings app in serverless + describe.skip('Search advanced settings', function () { + before(async () => { + await pageObjects.common.navigateToApp('advancedSettings'); + }); + + it('renders the page', async () => { + await retry.waitFor('title to be visible', async () => { + return await testSubjects.exists('managementSettingsTitle'); + }); + + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/settings`); + }); + + describe('renders search settings', () => { + for (const settingId of SEARCH_PROJECT_SETTINGS) { + it('renders ' + settingId + ' edit field', async () => { + const fieldTestSubj = 'advancedSetting-editField-' + settingId; + expect(await testSubjects.exists(fieldTestSubj)).to.be(true); + }); + } + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts index 4f08611809886..bff6144d44bb2 100644 --- a/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts @@ -15,8 +15,17 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const lens = getPageObject('lens'); const svlSearchNavigation = getService('svlSearchNavigation'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); describe('persistable attachment', () => { + before(async () => { + await svlCommonPage.login(); + }); + + after(async () => { + await svlCommonPage.forceLogout(); + }); + describe('lens visualization', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); diff --git a/x-pack/test_serverless/functional/test_suites/search/config.screenshots.ts b/x-pack/test_serverless/functional/test_suites/search/config.screenshots.ts new file mode 100644 index 0000000000000..fd53eda92aa5e --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/config.screenshots.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../config.base'; + +const enabledActionTypes = ['.index', '.server-log']; + +export default createTestConfig({ + serverlessProject: 'es', + testFiles: [require.resolve('./screenshot_creation')], + kbnServerArgs: [`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`], + junit: { + reportName: 'Serverless Search Screenshot Creation', + }, +}); diff --git a/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts b/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts index f8d8db6f06ed4..d91d296c75624 100644 --- a/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts +++ b/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts @@ -16,6 +16,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'common', 'header', 'lens', + 'svlCommonPage', ]); const pieChart = getService('pieChart'); @@ -27,6 +28,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('Building a new dashboard', function () { before(async () => { + await PageObjects.svlCommonPage.login(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' @@ -42,6 +44,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await kibanaServer.importExport.unload( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); + await PageObjects.svlCommonPage.forceLogout(); }); it('can add a lens panel by value', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/search/dashboards/import_dashboard.ts b/x-pack/test_serverless/functional/test_suites/search/dashboards/import_dashboard.ts index 2f01a0a67167f..c935ab3f15f83 100644 --- a/x-pack/test_serverless/functional/test_suites/search/dashboards/import_dashboard.ts +++ b/x-pack/test_serverless/functional/test_suites/search/dashboards/import_dashboard.ts @@ -18,10 +18,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects', 'dashboard']); + const PageObjects = getPageObjects([ + 'common', + 'settings', + 'header', + 'savedObjects', + 'dashboard', + 'svlCommonPage', + ]); - describe('Importing an existing dashboard', () => { + // Failing: See https://github.com/elastic/kibana/issues/166573 + describe.skip('Importing an existing dashboard', () => { before(async () => { + await PageObjects.svlCommonPage.login(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.uiSettings.replace({}); }); @@ -29,6 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.savedObjects.cleanStandardList(); + await PageObjects.svlCommonPage.forceLogout(); }); it('should be able to import dashboard created in 8.11', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/search/empty_page.ts b/x-pack/test_serverless/functional/test_suites/search/empty_page.ts index 9808bb69bbeb6..5a7891968cb81 100644 --- a/x-pack/test_serverless/functional/test_suites/search/empty_page.ts +++ b/x-pack/test_serverless/functional/test_suites/search/empty_page.ts @@ -11,12 +11,18 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const svlSearchNavigation = getService('svlSearchNavigation'); const testSubjects = getService('testSubjects'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); describe('empty pages', function () { before(async () => { + await svlCommonPage.login(); await svlSearchNavigation.navigateToLandingPage(); }); + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('should show search specific empty page in discover', async () => { await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' }); await testSubjects.existOrFail('kbnOverviewElasticsearchGettingStarted'); diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts index e169f69f3f78b..fbaaf96aed8a4 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.ts @@ -16,5 +16,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dashboards/build_dashboard')); loadTestFile(require.resolve('./dashboards/import_dashboard')); + loadTestFile(require.resolve('./advanced_settings')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/landing_page.ts b/x-pack/test_serverless/functional/test_suites/search/landing_page.ts index f05f4808e309f..9a410a71e0844 100644 --- a/x-pack/test_serverless/functional/test_suites/search/landing_page.ts +++ b/x-pack/test_serverless/functional/test_suites/search/landing_page.ts @@ -7,14 +7,22 @@ import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getPageObject, getService }: FtrProviderContext) { - const svlSearchLandingPage = getPageObject('svlSearchLandingPage'); +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['svlSearchLandingPage', 'svlCommonPage']); const svlSearchNavigation = getService('svlSearchNavigation'); describe('landing page', function () { + before(async () => { + await pageObjects.svlCommonPage.login(); + }); + + after(async () => { + await pageObjects.svlCommonPage.forceLogout(); + }); + it('has serverless side nav', async () => { await svlSearchNavigation.navigateToLandingPage(); - await svlSearchLandingPage.assertSvlSearchSideNavExists(); + await pageObjects.svlSearchLandingPage.assertSvlSearchSideNavExists(); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index ba19e7b756073..13eaca8526366 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -12,14 +12,20 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const svlSearchLandingPage = getPageObject('svlSearchLandingPage'); const svlSearchNavigation = getService('svlSearchNavigation'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); describe('navigation', function () { before(async () => { + await svlCommonPage.login(); await svlSearchNavigation.navigateToLandingPage(); }); + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('navigate search sidenav & breadcrumbs', async () => { const expectNoPageReload = await svlCommonNavigation.createNoPageReloadCheck(); @@ -65,6 +71,32 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await expectNoPageReload(); }); + it("management apps from the sidenav hide the 'stack management' root from the breadcrumbs", async () => { + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:triggersActions' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Explore', 'Alerts', 'Rules']); + + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:index_management' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts([ + 'Content', + 'Index Management', + 'Indices', + ]); + + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:ingest_pipelines' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Content', 'Ingest Pipelines']); + + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:api_keys' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Security', 'API keys']); + }); + + it('navigate management', async () => { + await svlCommonNavigation.sidenav.openSection('project_settings_project_nav'); + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Management']); + await testSubjects.click('app-card-dataViews'); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Management', 'Data views']); + }); + it('navigate using search', async () => { await svlCommonNavigation.search.showSearch(); // TODO: test something search project specific instead of generic discover diff --git a/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/index.ts b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/index.ts new file mode 100644 index 0000000000000..90e905735c307 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Screenshots - serverless search UI', function () { + loadTestFile(require.resolve('./response_ops_docs')); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/index.ts b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/index.ts new file mode 100644 index 0000000000000..3df3eaa1939b4 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/index.ts @@ -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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export const ECOMMERCE_INDEX_PATTERN = 'kibana_sample_data_ecommerce'; +export const FLIGHTS_INDEX_PATTERN = 'kibana_sample_data_flights'; +export const LOGS_INDEX_PATTERN = 'kibana_sample_data_logs'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const browser = getService('browser'); + const ml = getService('ml'); + const sampleData = getService('sampleData'); + const svlCommonApi = getService('svlCommonApi'); + + describe('response ops docs', function () { + this.tags(['responseOps']); + + before(async () => { + await sampleData.testResources.installAllKibanaSampleData( + svlCommonApi.getInternalRequestHeader() + ); + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.testResources.disableKibanaAnnouncements(); + await browser.setWindowSize(1920, 1080); + }); + + after(async () => { + await sampleData.testResources.removeAllKibanaSampleData( + svlCommonApi.getInternalRequestHeader() + ); + await ml.testResources.resetKibanaTimeZone(); + await ml.testResources.resetKibanaAnnouncements(); + }); + + loadTestFile(require.resolve('./stack_connectors')); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/stack_connectors/connectors.ts b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/stack_connectors/connectors.ts new file mode 100644 index 0000000000000..8bfada9db1044 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/stack_connectors/connectors.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const svlCommonScreenshots = getService('svlCommonScreenshots'); + // const browser = getService('browser'); + // const find = getService('find'); + // const testSubjects = getService('testSubjects'); + const screenshotDirectories = ['response_ops_docs', 'stack_connectors']; + const pageObjects = getPageObjects(['common', 'header', 'svlCommonPage']); + + describe('connectors', function () { + before(async () => { + await pageObjects.svlCommonPage.login(); + }); + + after(async () => { + await pageObjects.svlCommonPage.forceLogout(); + }); + + it('connectors list screenshot', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await svlCommonScreenshots.takeScreenshot( + 'connector-listing', + screenshotDirectories, + 1400, + 1024 + ); + + // const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch'); + // await searchBox.click(); + // await searchBox.clearValue(); + // await searchBox.type('my actionTypeId:(.index)'); + // await searchBox.pressKeys(browser.keys.ENTER); + // const typeFilter = await find.byCssSelector( + // '[data-test-subj="actionsList"] .euiFilterButton' + // ); + // await typeFilter.click(); + // await commonScreenshots.takeScreenshot( + // 'connector-filter-by-type', + // screenshotDirectories, + // 1400, + // 1024 + // ); + + // const clearSearchButton = await testSubjects.find('clearSearchButton'); + // await clearSearchButton.click(); + // const checkAllButton = await testSubjects.find('checkboxSelectAll'); + // await checkAllButton.click(); + // await commonScreenshots.takeScreenshot('connector-delete', screenshotDirectories, 1400, 1024); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/stack_connectors/index.ts b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/stack_connectors/index.ts new file mode 100644 index 0000000000000..f5f06e36d3ca7 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/screenshot_creation/response_ops_docs/stack_connectors/index.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const actions = getService('actions'); + const browser = getService('browser'); + const es = getService('es'); + const rules = getService('rules'); + const testIndex = `test-index`; + const svlCommonApi = getService('svlCommonApi'); + + describe('stack connectors', function () { + before(async () => { + await browser.setWindowSize(1920, 1080); + await actions.api.createConnector({ + name: 'server-log-connector', + config: {}, + secrets: {}, + connectorTypeId: '.server-log', + additionalRequestHeaders: svlCommonApi.getInternalRequestHeader(), + }); + + await es.indices.create({ + index: testIndex, + body: { + mappings: { + properties: { + date_updated: { + type: 'date', + format: 'epoch_millis', + }, + }, + }, + }, + }); + + await actions.api.createConnector({ + name: 'my-index-connector', + config: { + index: testIndex, + }, + secrets: {}, + connectorTypeId: '.index', + additionalRequestHeaders: svlCommonApi.getInternalRequestHeader(), + }); + }); + + after(async () => { + await rules.api.deleteAllRules(svlCommonApi.getInternalRequestHeader()); + await actions.api.deleteAllConnectors(svlCommonApi.getInternalRequestHeader()); + await es.indices.delete({ index: testIndex }); + }); + + loadTestFile(require.resolve('./connectors')); + // loadTestFile(require.resolve('./connector_types')); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/security/advanced_settings.ts new file mode 100644 index 0000000000000..27fa42549dcc6 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/advanced_settings.ts @@ -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 expect from '@kbn/expect'; +import { SECURITY_PROJECT_SETTINGS } from '@kbn/serverless-security-settings'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const retry = getService('retry'); + + // Skip until we enable the Advanced settings app in serverless + describe.skip('Security advanced settings', function () { + before(async () => { + await pageObjects.common.navigateToApp('advancedSettings'); + }); + + it('renders the page', async () => { + await retry.waitFor('title to be visible', async () => { + return await testSubjects.exists('managementSettingsTitle'); + }); + + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/settings`); + }); + + describe('renders security settings', () => { + for (const settingId of SECURITY_PROJECT_SETTINGS) { + it('renders ' + settingId + ' edit field', async () => { + const fieldTestSubj = 'advancedSetting-editField-' + settingId; + expect(await testSubjects.exists(fieldTestSubj)).to.be(true); + }); + } + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts index 33d7c582835d2..1db2cc6e0119f 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts +++ b/x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts @@ -20,6 +20,12 @@ export default defineCypressConfig({ viewportHeight: 946, viewportWidth: 1680, numTestsKeptInMemory: 10, + env: { + KIBANA_USERNAME: 'system_indices_superuser', + KIBANA_PASSWORD: 'changeme', + ELASTICSEARCH_USERNAME: 'system_indices_superuser', + ELASTICSEARCH_PASSWORD: 'changeme', + }, e2e: { experimentalRunAllSpecs: true, experimentalMemoryManagement: true, diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/complete_with_endpoint.cy.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/complete_with_endpoint.cy.ts deleted file mode 100644 index fc8a1e1a690fb..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/feature_access/complete_with_endpoint.cy.ts +++ /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 { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants'; -import { login } from '../../../tasks/login'; -import { getAgentListTable, visitFleetAgentList } from '../../../screens'; -import { getEndpointManagementPageList } from '../../../screens/endpoint_management'; -import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management'; - -describe( - 'App Features for Security Complete PLI with Endpoint Complete Addon', - { - env: { - ftrConfig: { - productTypes: [ - { product_line: 'security', product_tier: 'complete' }, - { product_line: 'endpoint', product_tier: 'complete' }, - ], - }, - }, - }, - () => { - const allPages = getEndpointManagementPageList(); - let username: string; - let password: string; - - beforeEach(() => { - login('endpoint_operations_analyst').then((response) => { - username = response.username; - password = response.password; - }); - }); - - for (const { url, title, pageTestSubj } of allPages) { - it(`should allow access to ${title}`, () => { - cy.visit(url); - cy.getByTestSubj(pageTestSubj).should('exist'); - }); - } - - for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES) { - it(`should allow access to Response Action: ${actionName}`, () => { - ensureResponseActionAuthzAccess('all', actionName, username, password); - }); - } - - it(`should have access to Fleet`, () => { - visitFleetAgentList(); - getAgentListTable().should('exist'); - }); - } -); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/policy_details_with_security_essentials.cy.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/policy_details_with_security_essentials.cy.ts deleted file mode 100644 index 582a9c510c4c3..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management/policy_details_with_security_essentials.cy.ts +++ /dev/null @@ -1,47 +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 { IndexedFleetEndpointPolicyResponse } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { login } from '../../tasks/login'; -import { visitPolicyDetails } from '../../screens/endpoint_management/policy_details'; - -describe( - 'When displaying the Policy Details in Security Essentials PLI', - { - env: { - ftrConfig: { - productTypes: [{ product_line: 'security', product_tier: 'essentials' }], - }, - }, - }, - () => { - let loadedPolicyData: IndexedFleetEndpointPolicyResponse; - - before(() => { - cy.task('indexFleetEndpointPolicy', { policyName: 'tests-serverless' }).then((response) => { - loadedPolicyData = response as IndexedFleetEndpointPolicyResponse; - }); - }); - - after(() => { - if (loadedPolicyData) { - cy.task('deleteIndexedFleetEndpointPolicies', loadedPolicyData); - } - }); - - beforeEach(() => { - login(); - visitPolicyDetails(loadedPolicyData.integrationPolicies[0].id); - }); - - it('should display upselling section for protections', () => { - cy.getByTestSubj('endpointPolicy-protectionsLockedCard', { timeout: 60000 }) - .should('exist') - .and('be.visible'); - }); - } -); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/serverless.cy.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/serverless.cy.ts index 97b0a0fefad22..7000fe8ecca16 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/serverless.cy.ts +++ b/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/serverless.cy.ts @@ -6,12 +6,16 @@ */ import { LEFT_NAVIGATION } from '../screens/landing_page'; -import { login } from '../tasks/login'; import { navigatesToLandingPage } from '../tasks/navigation'; describe('Serverless', () => { it('Should navigate to the landing page', () => { - login(); + cy.visit('/', { + auth: { + username: 'elastic_serverless', + password: 'changeme', + }, + }); navigatesToLandingPage(); cy.get(LEFT_NAVIGATION).should('exist'); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/package.json b/x-pack/test_serverless/functional/test_suites/security/cypress/package.json index fd3033c84be38..ef8534585d4d0 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/package.json +++ b/x-pack/test_serverless/functional/test_suites/security/cypress/package.json @@ -5,9 +5,9 @@ "private": true, "license": "Elastic License 2.0", "scripts": { - "cypress": "../../../../../../node_modules/.bin/cypress", - "cypress:open": "node ../../../../../plugins/security_solution/scripts/start_cypress_parallel open --config-file ../../../x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts --ftr-config-file ../../../../../../x-pack/test_serverless/functional/test_suites/security/cypress/security_config", - "cypress:run": "node ../../../../../plugins/security_solution/scripts/start_cypress_parallel run --browser chrome --config-file ../../../x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts --ftr-config-file ../../../../../../x-pack/test_serverless/functional/test_suites/security/cypress/security_config --reporter ../../../../../../node_modules/cypress-multi-reporters --reporter-options configFile=./reporter_config.json; status=$?; yarn junit:merge && exit $status", + "cypress": "NODE_OPTIONS=--openssl-legacy-provider node ../../../../../../node_modules/.bin/cypress", + "cypress:open": "NODE_OPTIONS=--openssl-legacy-provider node ../../../../../plugins/security_solution/scripts/start_cypress_parallel open --config-file ../../../x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts --ftr-config-file ../../../../../../x-pack/test_serverless/functional/test_suites/security/cypress/security_config", + "cypress:run": "NODE_OPTIONS=--openssl-legacy-provider node ../../../../../plugins/security_solution/scripts/start_cypress_parallel run --browser chrome --config-file ../../../x-pack/test_serverless/functional/test_suites/security/cypress/cypress.config.ts --ftr-config-file ../../../../../../x-pack/test_serverless/functional/test_suites/security/cypress/security_config --reporter ../../../../../../node_modules/cypress-multi-reporters --reporter-options configFile=./reporter_config.json; status=$?; yarn junit:merge && exit $status", "junit:merge": "../../../../../../node_modules/.bin/mochawesome-merge ../../../../../../target/kibana-security-serverless/cypress/results/mochawesome*.json > ../../../../../../target/kibana-security-serverless/cypress/results/output.json && ../../../../../../node_modules/.bin/marge ../../../../../../target/kibana-security-serverless/cypress/results/output.json --reportDir ../../../../../../target/kibana-security-serverless/cypress/results && mkdir -p ../../../../../../target/junit && cp ../../../../../../target/kibana-security-serverless/cypress/results/*.xml ../../../../../../target/junit/" } } \ No newline at end of file diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/endpoint_list.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/endpoint_list.ts deleted file mode 100644 index a8cf41e6d9e16..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/endpoint_list.ts +++ /dev/null @@ -1,83 +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 { DeepReadonly } from 'utility-types'; -import { EndpointManagementPageMap, getEndpointManagementPageMap } from './page_reference'; -import { UserAuthzAccessLevel } from './types'; -import { getNoPrivilegesPage } from './common'; - -interface ListRowOptions { - endpointId?: string; - hostName?: string; - /** Zero-based row index */ - rowIndex?: number; -} - -const pageById: DeepReadonly = getEndpointManagementPageMap(); - -export const visitEndpointList = (): Cypress.Chainable => { - return cy.visit(pageById.endpointList.url); -}; - -/** - * Validate that the endpoint list has the proper level of authz - * - * @param accessLevel - * @param visitPage if `true`, then the endpoint list page will be visited first - */ -export const ensureEndpointListPageAuthzAccess = ( - accessLevel: UserAuthzAccessLevel, - visitPage: boolean = false -): Cypress.Chainable => { - if (visitPage) { - visitEndpointList(); - } - - if (accessLevel === 'none') { - return getNoPrivilegesPage().should('exist'); - } - - // Read and All are currently the same - return getNoPrivilegesPage().should('not.exist'); -}; - -export const getTableRow = ({ - endpointId, - hostName, - rowIndex = 0, -}: ListRowOptions = {}): Cypress.Chainable => { - if (endpointId) { - return cy.get(`tr[data-endpoint-id="${endpointId}"]`).should('exist'); - } - - if (hostName) { - return cy.getByTestSubj('hostnameCellLink').contains(hostName).closest('tr').should('exist'); - } - - return cy - .getByTestSubj('endpointListTable') - .find(`tbody tr[data-endpoint-id]`) - .eq(rowIndex) - .should('exist'); -}; - -export const openRowActionMenu = (options?: ListRowOptions): Cypress.Chainable => { - getTableRow(options).findByTestSubj('endpointTableRowActions', { log: true }).click(); - return cy.getByTestSubj('tableRowActionsMenuPanel'); -}; - -export const openConsoleFromEndpointList = (options?: ListRowOptions): Cypress.Chainable => { - return openRowActionMenu(options).findByTestSubj('console').click(); -}; - -export const getUnIsolateActionMenuItem = (): Cypress.Chainable => { - return cy.getByTestSubj('tableRowActionsMenuPanel').findByTestSubj('unIsolateLink'); -}; - -export const getConsoleActionMenuItem = (): Cypress.Chainable => { - return cy.getByTestSubj('tableRowActionsMenuPanel').findByTestSubj('console'); -}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/index.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/index.ts deleted file mode 100644 index 54dcaaf7ee9c4..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/index.ts +++ /dev/null @@ -1,13 +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. - */ - -export * from './common'; -export * from './artifacts'; -export * from './endpoint_list'; -export * from './policy_list'; -export * from './page_reference'; -export * from './types'; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/policy_details.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/policy_details.ts deleted file mode 100644 index fd8fb40f2ac19..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/policy_details.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { APP_POLICIES_PATH } from '@kbn/security-solution-plugin/common/constants'; -import { UserAuthzAccessLevel } from './types'; -import { getNoPrivilegesPage } from './common'; - -export const visitPolicyDetails = (policyId: string): Cypress.Chainable => { - return cy.visit(`${APP_POLICIES_PATH}/${policyId}`); -}; - -export const ensurePolicyDetailsPageAuthzAccess = ( - policyId: string, - accessLevel: UserAuthzAccessLevel, - visitPage: boolean = false -): Cypress.Chainable => { - if (visitPage) { - visitPolicyDetails(policyId); - } - - if (accessLevel === 'none') { - return getNoPrivilegesPage().should('exist'); - } - - if (accessLevel === 'read') { - return cy.getByTestSubj('policyDetailsSaveButton').should('not.exist'); - } - - return cy.getByTestSubj('policyDetailsSaveButton').should('exist'); -}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/response_console.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/response_console.ts deleted file mode 100644 index 1027a7e3cb5be..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management/response_console.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ConsoleResponseActionCommands } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants'; - -export const getConsoleHelpPanelResponseActionTestSubj = (): Record< - ConsoleResponseActionCommands, - string -> => { - return { - isolate: 'endpointResponseActionsConsole-commandList-Responseactions-isolate', - release: 'endpointResponseActionsConsole-commandList-Responseactions-release', - processes: 'endpointResponseActionsConsole-commandList-Responseactions-processes', - ['kill-process']: 'endpointResponseActionsConsole-commandList-Responseactions-kill-process', - ['suspend-process']: - 'endpointResponseActionsConsole-commandList-Responseactions-suspend-process', - ['get-file']: 'endpointResponseActionsConsole-commandList-Responseactions-get-file', - execute: 'endpointResponseActionsConsole-commandList-Responseactions-execute', - upload: 'endpointResponseActionsConsole-commandList-Responseactions-upload', - }; -}; - -export const ensureResponseConsoleIsOpen = (): Cypress.Chainable => { - return cy.getByTestSubj('consolePageOverlay').should('exist'); -}; - -export const openConsoleHelpPanel = (): Cypress.Chainable => { - ensureResponseConsoleIsOpen(); - return cy.getByTestSubj('endpointResponseActionsConsole-header-helpButton').click(); -}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/agent_list.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/agent_list.ts deleted file mode 100644 index 02de2f51f048f..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/agent_list.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FLEET_BASE_PATH } from '@kbn/fleet-plugin/public/constants'; - -export const visitFleetAgentList = (): Cypress.Chainable => { - return cy.visit(FLEET_BASE_PATH, { failOnStatusCode: false }); -}; - -export const getAgentListTable = (): Cypress.Chainable => { - return cy.getByTestSubj('fleetAgentListTable'); -}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/index.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/index.ts deleted file mode 100644 index 5971df139f3bc..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/fleet/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './permission_denied'; -export * from './agent_list'; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/index.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/index.ts index 415fddd939615..194bf6301191a 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/screens/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/cypress/screens/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export * from './fleet'; export * from './landing_page'; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management/index.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management/index.ts deleted file mode 100644 index 539ac438bf57f..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './response_actions'; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management/response_actions.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management/response_actions.ts deleted file mode 100644 index cf371fe63c40a..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management/response_actions.ts +++ /dev/null @@ -1,111 +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 { ResponseActionsApiCommandNames } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants'; -import { request } from '@kbn/security-solution-plugin/public/management/cypress/tasks/common'; -import { - EXECUTE_ROUTE, - GET_FILE_ROUTE, - GET_PROCESSES_ROUTE, - ISOLATE_HOST_ROUTE_V2, - KILL_PROCESS_ROUTE, - SUSPEND_PROCESS_ROUTE, - UNISOLATE_HOST_ROUTE_V2, - UPLOAD_ROUTE, -} from '@kbn/security-solution-plugin/common/endpoint/constants'; -import { UserAuthzAccessLevel } from '../../screens/endpoint_management'; - -/** - * Ensure user has the given `accessLevel` to the type of response action - * @param accessLevel - * @param responseAction - * @param username - * @param password - */ -export const ensureResponseActionAuthzAccess = ( - accessLevel: Exclude, - responseAction: ResponseActionsApiCommandNames, - username: string, - password: string -): Cypress.Chainable => { - let url: string = ''; - let apiPayload: any = { - endpoint_ids: ['some-id'], - }; - - switch (responseAction) { - case 'isolate': - url = ISOLATE_HOST_ROUTE_V2; - break; - - case 'unisolate': - url = UNISOLATE_HOST_ROUTE_V2; - break; - - case 'get-file': - url = GET_FILE_ROUTE; - Object.assign(apiPayload, { parameters: { path: 'one/two' } }); - break; - - case 'execute': - url = EXECUTE_ROUTE; - Object.assign(apiPayload, { parameters: { command: 'foo' } }); - break; - case 'running-processes': - url = GET_PROCESSES_ROUTE; - break; - - case 'kill-process': - url = KILL_PROCESS_ROUTE; - Object.assign(apiPayload, { parameters: { pid: 123 } }); - break; - - case 'suspend-process': - url = SUSPEND_PROCESS_ROUTE; - Object.assign(apiPayload, { parameters: { pid: 123 } }); - break; - - case 'upload': - url = UPLOAD_ROUTE; - { - const file = new File(['foo'], 'foo.txt'); - const formData = new FormData(); - - formData.append('file', file, file.name); - - for (const [key, value] of Object.entries(apiPayload as object)) { - formData.append(key, typeof value !== 'string' ? JSON.stringify(value) : value); - } - - apiPayload = formData; - } - break; - - default: - throw new Error(`Response action [${responseAction}] has no API payload defined`); - } - - const requestOptions: Partial = { - url, - method: 'post', - auth: { - user: username, - pass: password, - }, - headers: { - 'Content-Type': undefined, - }, - failOnStatusCode: false, - body: apiPayload as Cypress.RequestBody, - }; - - if (accessLevel === 'none') { - return request(requestOptions).its('status').should('equal', 403); - } - - return request(requestOptions).its('status').should('not.equal', 403); -}; diff --git a/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/login.ts b/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/login.ts index a8cd016ed6159..de79a7a94f275 100644 --- a/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/login.ts +++ b/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/login.ts @@ -6,6 +6,7 @@ */ import { request } from '@kbn/security-solution-plugin/public/management/cypress/tasks/common'; +import { LoginState } from '@kbn/security-plugin/common/login_state'; import type { ServerlessRoleName } from '../../../../../shared/lib'; import { STANDARD_HTTP_HEADERS } from '../../../../../shared/lib/security/default_http_headers'; @@ -20,30 +21,29 @@ const sendApiLoginRequest = ( username: string, password: string ): Cypress.Chainable<{ username: string; password: string }> => { - const url = new URL(Cypress.config().baseUrl ?? ''); - url.pathname = '/internal/security/login'; + const baseUrl = Cypress.config().baseUrl; - cy.log(`Authenticating [${username}] via ${url.toString()}`); + cy.log(`Authenticating [${username}] via ${baseUrl}`); - return request({ - headers: { ...STANDARD_HTTP_HEADERS }, - method: 'POST', - url: url.toString(), - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: '/', - params: { - username, - password, - }, - }, - }).then(() => { - return { - username, - password, - }; - }); + const headers = { ...STANDARD_HTTP_HEADERS }; + return request({ headers, url: `${baseUrl}/internal/security/login_state` }) + .then((loginState) => { + const basicProvider = loginState.body.selector.providers.find( + (provider) => provider.type === 'basic' + ); + return request({ + url: `${baseUrl}/internal/security/login`, + method: 'POST', + headers, + body: { + providerType: basicProvider.type, + providerName: basicProvider.name, + currentURL: '/', + params: { username, password }, + }, + }); + }) + .then(() => ({ username, password })); }; interface CyLoginTask { diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts index c213e48348b67..6fb8c2ae94e44 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts @@ -20,7 +20,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { // Failing // Issue: https://github.com/elastic/kibana/issues/165135 - describe.skip('persistable attachment', () => { + describe.skip('Cases persistable attachments', () => { describe('lens visualization', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts index 58931d60d6836..75bb60d0da661 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts @@ -10,13 +10,17 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default ({ getPageObject, getService }: FtrProviderContext) => { const common = getPageObject('common'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlSecNavigation = getService('svlSecNavigation'); const testSubjects = getService('testSubjects'); const cases = getService('cases'); const toasts = getService('toasts'); - describe('Configure', function () { + // Failing: See https://github.com/elastic/kibana/issues/166551 + describe.skip('Configure Case', function () { before(async () => { + await svlCommonPage.login(); + await svlSecNavigation.navigateToLandingPage(); await testSubjects.click('solutionSideNavItemLink-cases'); @@ -26,6 +30,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { after(async () => { await cases.api.deleteAllCases(); + await svlCommonPage.forceLogout(); }); describe('Closure options', function () { diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts index f5da1e966b778..251af239b04e3 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts @@ -15,18 +15,24 @@ import { navigateToCasesApp } from '../../../../../shared/lib/cases'; const owner = SECURITY_SOLUTION_OWNER; export default ({ getService, getPageObject }: FtrProviderContext) => { - describe('Create case', function () { + describe('Create Case', function () { const find = getService('find'); const cases = getService('cases'); const testSubjects = getService('testSubjects'); const config = getService('config'); + const svlCommonPage = getPageObject('svlCommonPage'); beforeEach(async () => { await navigateToCasesApp(getPageObject, getService, owner); }); + before(async () => { + await svlCommonPage.login(); + }); + after(async () => { await cases.api.deleteAllCases(); + await svlCommonPage.forceLogout(); }); it('creates a case', async () => { @@ -64,75 +70,5 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { const button = await find.byCssSelector('[data-test-subj*="case-callout"] button'); expect(await button.getVisibleText()).equal('Add connector'); }); - - it('displays errors correctly while creating a case', async () => { - const caseTitle = Array(161).fill('x').toString(); - const longTag = Array(256).fill('a').toString(); - const longCategory = Array(51).fill('x').toString(); - - await cases.create.openCreateCasePage(); - await cases.create.createCase({ - title: caseTitle, - description: '', - tag: longTag, - severity: CaseSeverity.HIGH, - category: longCategory, - }); - - await testSubjects.click('create-case-submit'); - - const title = await find.byCssSelector('[data-test-subj="caseTitle"]'); - expect(await title.getVisibleText()).contain( - 'The length of the name is too long. The maximum length is 160 characters.' - ); - - const description = await testSubjects.find('caseDescription'); - expect(await description.getVisibleText()).contain('A description is required.'); - - const tags = await testSubjects.find('caseTags'); - expect(await tags.getVisibleText()).contain( - 'The length of the tag is too long. The maximum length is 256 characters.' - ); - - const category = await testSubjects.find('case-create-form-category'); - expect(await category.getVisibleText()).contain( - 'The length of the category is too long. The maximum length is 50 characters.' - ); - }); - - it('trims fields correctly while creating a case', async () => { - const titleWithSpace = 'This is a title with spaces '; - const descriptionWithSpace = - 'This is a case description with empty spaces at the end!! '; - const categoryWithSpace = 'security '; - const tagWithSpace = 'coke '; - - await cases.create.openCreateCasePage(); - await cases.create.createCase({ - title: titleWithSpace, - description: descriptionWithSpace, - tag: tagWithSpace, - severity: CaseSeverity.HIGH, - category: categoryWithSpace, - }); - - await testSubjects.click('create-case-submit'); - - // validate title is trimmed - const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]'); - expect(await title.getVisibleText()).equal(titleWithSpace.trim()); - - // validate description is trimmed - const description = await testSubjects.find('scrollable-markdown'); - expect(await description.getVisibleText()).equal(descriptionWithSpace.trim()); - - // validate tag exists and is trimmed - const tag = await testSubjects.find(`tag-${tagWithSpace.trim()}`); - expect(await tag.getVisibleText()).equal(tagWithSpace.trim()); - - // validate category exists and is trimmed - const category = await testSubjects.find(`category-viewer-${categoryWithSpace.trim()}`); - expect(await category.getVisibleText()).equal(categoryWithSpace.trim()); - }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/empty.txt b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/empty.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts index 6854b3df61061..e3d1d8408beff 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts @@ -15,9 +15,12 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); const cases = getService('cases'); const svlSecNavigation = getService('svlSecNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); - describe('cases list', () => { + describe('Cases List', () => { before(async () => { + await svlCommonPage.login(); + await svlSecNavigation.navigateToLandingPage(); await testSubjects.click('solutionSideNavItemLink-cases'); @@ -26,6 +29,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { after(async () => { await cases.api.deleteAllCases(); await cases.casesTable.waitForCasesToBeDeleted(); + await svlCommonPage.forceLogout(); }); describe('empty state', () => { diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts new file mode 100644 index 0000000000000..0b26fc2cf4c3d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts @@ -0,0 +1,453 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { v4 as uuidv4 } from 'uuid'; +import { CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain'; + +import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { + createOneCaseBeforeDeleteAllAfter, + createAndNavigateToCase, +} from '../../../../../shared/lib/cases/helpers'; + +const owner = SECURITY_SOLUTION_OWNER; + +export default ({ getPageObject, getService }: FtrProviderContext) => { + const header = getPageObject('header'); + const testSubjects = getService('testSubjects'); + const cases = getService('cases'); + const find = getService('find'); + + const retry = getService('retry'); + const comboBox = getService('comboBox'); + const svlCommonNavigation = getPageObject('svlCommonNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); + + describe('Case View', () => { + before(async () => { + await svlCommonPage.login(); + }); + + after(async () => { + await svlCommonPage.forceLogout(); + }); + + describe('page', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('should show the case view page correctly', async () => { + await testSubjects.existOrFail('case-view-title'); + await testSubjects.existOrFail('header-page-supplements'); + + await testSubjects.existOrFail('case-view-tab-title-activity'); + await testSubjects.existOrFail('case-view-tab-title-files'); + await testSubjects.existOrFail('description'); + + await testSubjects.existOrFail('case-view-activity'); + + await testSubjects.existOrFail('case-view-assignees'); + await testSubjects.existOrFail('sidebar-severity'); + await testSubjects.existOrFail('case-view-user-list-reporter'); + await testSubjects.existOrFail('case-view-user-list-participants'); + await testSubjects.existOrFail('case-view-tag-list'); + await testSubjects.existOrFail('cases-categories'); + await testSubjects.existOrFail('sidebar-connectors'); + }); + }); + + describe('properties', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('edits a case title from the case view page', async () => { + const newTitle = `test-${uuidv4()}`; + + await testSubjects.click('editable-title-header-value'); + await testSubjects.setValue('editable-title-input-field', newTitle); + await testSubjects.click('editable-title-submit-btn'); + + // wait for backend response + await retry.tryForTime(5000, async () => { + const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]'); + expect(await title.getVisibleText()).equal(newTitle); + }); + + // validate user action + await find.byCssSelector('[data-test-subj*="title-update-action"]'); + }); + + it('adds a comment to a case', async () => { + const commentArea = await find.byCssSelector( + '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea' + ); + await commentArea.focus(); + await commentArea.type('Test comment from automation'); + + await testSubjects.click('submit-comment'); + + // validate user action + const newComment = await find.byCssSelector( + '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]' + ); + expect(await newComment.getVisibleText()).equal('Test comment from automation'); + }); + + it('adds a category to a case', async () => { + const category = uuidv4(); + await testSubjects.click('category-edit-button'); + await comboBox.setCustom('comboBoxInput', category); + await testSubjects.click('edit-category-submit'); + + // validate category was added + await testSubjects.existOrFail('category-viewer-' + category); + + // validate user action + await find.byCssSelector('[data-test-subj*="category-update-action"]'); + }); + + it('deletes a category from a case', async () => { + await find.byCssSelector('[data-test-subj*="category-viewer-"]'); + + await testSubjects.click('category-remove-button'); + + await testSubjects.existOrFail('no-categories'); + // validate user action + await find.byCssSelector('[data-test-subj*="category-delete-action"]'); + }); + + it('adds a tag to a case', async () => { + const tag = uuidv4(); + await testSubjects.click('tag-list-edit-button'); + await comboBox.setCustom('comboBoxInput', tag); + await testSubjects.click('edit-tags-submit'); + + // validate tag was added + await testSubjects.existOrFail('tag-' + tag); + + // validate user action + await find.byCssSelector('[data-test-subj*="tags-add-action"]'); + }); + + it('deletes a tag from a case', async () => { + await testSubjects.click('tag-list-edit-button'); + // find the tag button and click the close button + const button = await find.byCssSelector('[data-test-subj="comboBoxInput"] button'); + await button.click(); + await testSubjects.click('edit-tags-submit'); + + // validate user action + await find.byCssSelector('[data-test-subj*="tags-delete-action"]'); + }); + + describe('status', () => { + it('changes a case status to in-progress via dropdown menu', async () => { + await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']); + // validate user action + await find.byCssSelector( + '[data-test-subj*="status-update-action"] [data-test-subj="case-status-badge-in-progress"]' + ); + // validates dropdown tag + await testSubjects.existOrFail( + 'case-view-status-dropdown > case-status-badge-popover-button-in-progress' + ); + }); + }); + + describe('Severity field', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('shows the severity field on the sidebar', async () => { + await testSubjects.existOrFail('case-severity-selection'); + }); + + it('changes the severity level from the selector', async () => { + await cases.common.selectSeverity(CaseSeverity.MEDIUM); + await header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('case-severity-selection-' + CaseSeverity.MEDIUM); + + // validate user action + await find.byCssSelector('[data-test-subj*="severity-update-action"]'); + }); + }); + }); + + describe('actions', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('deletes the case successfully', async () => { + await cases.singleCase.deleteCase(); + await cases.casesTable.waitForTableToFinishLoading(); + await cases.casesTable.validateCasesTableHasNthRows(0); + }); + }); + + describe('filter activity', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('filters by all by default', async () => { + const allBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-all"] span.euiNotificationBadge' + ); + + expect(await allBadge.getAttribute('aria-label')).equal('1 active filters'); + }); + + it('filters by comment successfully', async () => { + const commentBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-comments"] span.euiNotificationBadge' + ); + + expect(await commentBadge.getAttribute('aria-label')).equal('0 available filters'); + + const commentArea = await find.byCssSelector( + '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea' + ); + await commentArea.focus(); + await commentArea.type('Test comment from automation'); + await testSubjects.click('submit-comment'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('user-actions-filter-activity-button-comments'); + + expect(await commentBadge.getAttribute('aria-label')).equal('1 active filters'); + }); + + it('filters by history successfully', async () => { + const historyBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-history"] span.euiNotificationBadge' + ); + + expect(await historyBadge.getAttribute('aria-label')).equal('1 available filters'); + + await cases.common.selectSeverity(CaseSeverity.MEDIUM); + + await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('user-actions-filter-activity-button-history'); + + expect(await historyBadge.getAttribute('aria-label')).equal('3 active filters'); + }); + + it('sorts by newest first successfully', async () => { + await testSubjects.click('user-actions-filter-activity-button-all'); + + const AllBadge = await find.byCssSelector( + '[data-test-subj="user-actions-filter-activity-button-all"] span.euiNotificationBadge' + ); + + expect(await AllBadge.getVisibleText()).equal('4'); + + const sortDesc = await find.byCssSelector( + '[data-test-subj="user-actions-sort-select"] [value="desc"]' + ); + + await sortDesc.click(); + + await header.waitUntilLoadingHasFinished(); + + const userActionsLists = await find.allByCssSelector( + '[data-test-subj="user-actions-list"]' + ); + + const actionList = await userActionsLists[0].findAllByClassName('euiComment'); + + expect(await actionList[0].getAttribute('data-test-subj')).contain('status-update-action'); + }); + }); + + // FLAKY + describe.skip('Lens visualization', () => { + before(async () => { + await cases.testResources.installKibanaSampleData('logs'); + await createAndNavigateToCase(getPageObject, getService, owner); + }); + + after(async () => { + await cases.testResources.removeKibanaSampleData('logs'); + await cases.api.deleteAllCases(); + }); + + it('adds lens visualization in description', async () => { + await testSubjects.click('description-edit-icon'); + + await header.waitUntilLoadingHasFinished(); + + const editCommentTextArea = await find.byCssSelector( + '[data-test-subj*="editable-markdown-form"] textarea.euiMarkdownEditorTextArea' + ); + + await header.waitUntilLoadingHasFinished(); + + await editCommentTextArea.focus(); + + const editableDescription = await testSubjects.find('editable-markdown-form'); + + const addVisualizationButton = await editableDescription.findByCssSelector( + '[data-test-subj="euiMarkdownEditorToolbarButton"][aria-label="Visualization"]' + ); + await addVisualizationButton.click(); + + await cases.singleCase.findAndSaveVisualization('[Logs] Bytes distribution'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('editable-save-markdown'); + + await header.waitUntilLoadingHasFinished(); + + const description = await find.byCssSelector('[data-test-subj="description"]'); + + await description.findByCssSelector('[data-test-subj="xyVisChart"]'); + }); + }); + + describe('pagination', async () => { + let createdCase: any; + + before(async () => { + createdCase = await createAndNavigateToCase(getPageObject, getService, owner); + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); + + it('initially renders user actions list correctly', async () => { + await testSubjects.missingOrFail('cases-show-more-user-actions'); + + const userActionsLists = await find.allByCssSelector( + '[data-test-subj="user-actions-list"]' + ); + + expect(userActionsLists).length(1); + }); + + it('shows more actions on button click', async () => { + await cases.api.generateUserActions({ + caseId: createdCase.id, + caseVersion: createdCase.version, + totalUpdates: 4, + }); + + await testSubjects.missingOrFail('user-actions-loading'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.click('case-refresh'); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('cases-show-more-user-actions'); + + const userActionsLists = await find.allByCssSelector( + '[data-test-subj="user-actions-list"]' + ); + + expect(userActionsLists).length(2); + + expect(await userActionsLists[0].findAllByClassName('euiComment')).length(10); + + expect(await userActionsLists[1].findAllByClassName('euiComment')).length(4); + + testSubjects.click('cases-show-more-user-actions'); + + await header.waitUntilLoadingHasFinished(); + + expect(await userActionsLists[0].findAllByClassName('euiComment')).length(20); + + expect(await userActionsLists[1].findAllByClassName('euiComment')).length(4); + }); + }); + + describe('Tabs', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('shows the "activity" tab by default', async () => { + await testSubjects.existOrFail('case-view-tab-title-activity'); + await testSubjects.existOrFail('case-view-tab-content-activity'); + }); + + it("shows the 'files' tab when clicked", async () => { + await testSubjects.click('case-view-tab-title-files'); + await testSubjects.existOrFail('case-view-tab-content-files'); + }); + }); + + describe('Files', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('adds a file to the case', async () => { + await testSubjects.click('case-view-tab-title-files'); + await testSubjects.existOrFail('case-view-tab-content-files'); + + await cases.casesFilesTable.addFile(require.resolve('./empty.txt')); + + const uploadedFileName = await testSubjects.getVisibleText('cases-files-name-text'); + expect(uploadedFileName).to.be('empty.txt'); + }); + + it('search by file name', async () => { + await cases.casesFilesTable.searchByFileName('foobar'); + await cases.casesFilesTable.emptyOrFail(); + await cases.casesFilesTable.searchByFileName('empty'); + + const uploadedFileName = await testSubjects.getVisibleText('cases-files-name-text'); + expect(uploadedFileName).to.be('empty.txt'); + }); + + it('files added to a case can be deleted', async () => { + await cases.casesFilesTable.deleteFile(0); + await cases.casesFilesTable.emptyOrFail(); + }); + + describe('Files User Activity', () => { + it('file user action is displayed correctly', async () => { + await cases.casesFilesTable.addFile(require.resolve('./empty.txt')); + + await testSubjects.click('case-view-tab-title-activity'); + await testSubjects.existOrFail('case-view-tab-content-activity'); + + const uploadedFileName = await testSubjects.getVisibleText('cases-files-name-text'); + expect(uploadedFileName).to.be('empty.txt'); + }); + }); + }); + + describe('breadcrumbs', () => { + let createdCase: any; + + before(async () => { + createdCase = await createAndNavigateToCase(getPageObject, getService, owner); + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); + + it('should set the cases title', async () => { + await svlCommonNavigation.breadcrumbs.expectExists(); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: createdCase.title }); + }); + }); + + describe('reporter', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService, owner); + + it('should render the reporter correctly', async () => { + const reporter = await cases.singleCase.getReporter(); + + const reporterText = await reporter.getVisibleText(); + + expect(reporterText).to.be('elastic_serverless'); + }); + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/landing_page.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/landing_page.ts index 97d7a62606a80..555730b2910a5 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/landing_page.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/landing_page.ts @@ -10,8 +10,17 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { const svlSecLandingPage = getPageObject('svlSecLandingPage'); const svlSecNavigation = getService('svlSecNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); describe('landing page', function () { + before(async () => { + await svlCommonPage.login(); + }); + + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('has serverless side nav', async () => { await svlSecNavigation.navigateToLandingPage(); await svlSecLandingPage.assertSvlSecSideNavExists(); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/management.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/management.ts index 98565ffed71fc..ab9d75f4b08c0 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/management.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/management.ts @@ -9,8 +9,17 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObject }: FtrProviderContext) { const PageObject = getPageObject('common'); + const svlCommonPage = getPageObject('svlCommonPage'); describe('Management', function () { + before(async () => { + await svlCommonPage.login(); + }); + + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('redirects from common management url to security specific page', async () => { const SUB_URL = ''; await PageObject.navigateToUrl('management', SUB_URL, { diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts index 695336b7734fb..998ad9a2096c9 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts @@ -15,7 +15,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); - describe('navigation', function () { + // FLAKY: https://github.com/elastic/kibana/issues/165629 + describe.skip('navigation', function () { before(async () => { await svlSecNavigation.navigateToLandingPage(); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/index.ts b/x-pack/test_serverless/functional/test_suites/security/index.ts index f64b4b8395dad..d68c184813cea 100644 --- a/x-pack/test_serverless/functional/test_suites/security/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/index.ts @@ -13,8 +13,10 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./ftr/navigation')); loadTestFile(require.resolve('./ftr/management')); loadTestFile(require.resolve('./ftr/cases/attachment_framework')); - loadTestFile(require.resolve('./ftr/cases/list_view')); + loadTestFile(require.resolve('./ftr/cases/view_case')); loadTestFile(require.resolve('./ftr/cases/create_case_form')); loadTestFile(require.resolve('./ftr/cases/configure')); + loadTestFile(require.resolve('./ftr/cases/list_view')); + loadTestFile(require.resolve('./advanced_settings')); }); } diff --git a/x-pack/test_serverless/shared/config.base.ts b/x-pack/test_serverless/shared/config.base.ts index 68324f7245f8f..5ea4a5eddf14f 100644 --- a/x-pack/test_serverless/shared/config.base.ts +++ b/x-pack/test_serverless/shared/config.base.ts @@ -25,9 +25,13 @@ export default async () => { kibana: { ...kbnTestConfig.getUrlParts(kibanaTestSuperuserServerless), protocol: 'https', - certificateAuthorities: [Fs.readFileSync(CA_CERT_PATH)], + certificateAuthorities: process.env.TEST_CLOUD ? undefined : [Fs.readFileSync(CA_CERT_PATH)], + }, + elasticsearch: { + ...esTestConfig.getUrlParts(), + protocol: 'https', + certificateAuthorities: process.env.TEST_CLOUD ? undefined : [Fs.readFileSync(CA_CERT_PATH)], }, - elasticsearch: { ...esTestConfig.getUrlParts(), protocol: 'https' }, }; // "Fake" SAML provider @@ -52,31 +56,30 @@ export default async () => { files: [idpPath, jwksPath], serverArgs: [ 'xpack.security.authc.realms.file.file1.order=-100', + `xpack.security.authc.realms.native.native1.enabled=false`, + `xpack.security.authc.realms.native.native1.order=-97`, - 'xpack.security.authc.realms.jwt.jwt1.order=-98', - `xpack.security.authc.realms.jwt.jwt1.token_type=access_token`, - 'xpack.security.authc.realms.jwt.jwt1.client_authentication.type=shared_secret', - `xpack.security.authc.realms.jwt.jwt1.allowed_issuer=https://kibana.elastic.co/jwt/`, - `xpack.security.authc.realms.jwt.jwt1.allowed_subjects=elastic-agent`, 'xpack.security.authc.realms.jwt.jwt1.allowed_audiences=elasticsearch', + `xpack.security.authc.realms.jwt.jwt1.allowed_issuer=https://kibana.elastic.co/jwt/`, `xpack.security.authc.realms.jwt.jwt1.allowed_signature_algorithms=[RS256]`, + `xpack.security.authc.realms.jwt.jwt1.allowed_subjects=elastic-agent`, `xpack.security.authc.realms.jwt.jwt1.claims.principal=sub`, + 'xpack.security.authc.realms.jwt.jwt1.client_authentication.type=shared_secret', + 'xpack.security.authc.realms.jwt.jwt1.order=-98', `xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path=${getDockerFileMountPath(jwksPath)}`, + `xpack.security.authc.realms.jwt.jwt1.token_type=access_token`, - `xpack.security.authc.realms.native.native1.enabled=false`, - `xpack.security.authc.realms.native.native1.order=-97`, - 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.realms.saml.cloud-saml-kibana.attributes.principal=urn:oid:0.0.7', + 'xpack.security.authc.realms.saml.cloud-saml-kibana.idp.entity_id=http://www.elastic.co/saml1', 'xpack.security.authc.realms.saml.cloud-saml-kibana.order=101', `xpack.security.authc.realms.saml.cloud-saml-kibana.idp.metadata.path=${getDockerFileMountPath( idpPath )}`, - 'xpack.security.authc.realms.saml.cloud-saml-kibana.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.cloud-saml-kibana.sp.acs=http://localhost:${servers.kibana.port}/api/security/saml/callback`, `xpack.security.authc.realms.saml.cloud-saml-kibana.sp.entity_id=http://localhost:${servers.kibana.port}`, `xpack.security.authc.realms.saml.cloud-saml-kibana.sp.logout=http://localhost:${servers.kibana.port}/logout`, - `xpack.security.authc.realms.saml.cloud-saml-kibana.sp.acs=http://localhost:${servers.kibana.port}/api/security/saml/callback`, - 'xpack.security.authc.realms.saml.cloud-saml-kibana.attributes.principal=urn:oid:0.0.7', ], - ssl: true, // not needed as for serverless ssl is always on but added it anyway + ssl: true, // SSL is required for SAML realm }, kbnTestServer: { @@ -120,13 +123,17 @@ export default async () => { ])}`, // This ensures that we register the Security SAML API endpoints. // In the real world the SAML config is injected by control plane. - // basic: { 'basic': { order: 0 } }, `--plugin-path=${samlIdPPlugin}`, '--xpack.cloud.id=ftr_fake_cloud_id', + // Ensure that SAML is used as the default authentication method whenever a user navigates to Kibana. In other + // words, Kibana should attempt to authenticate the user using the provider with the lowest order if the Login + // Selector is disabled (which is how Serverless Kibana is configured). By declaring `cloud-basic` with a higher + // order, we indicate that basic authentication can still be used, but only if explicitly requested when the + // user navigates to `/login` page directly and enters username and password in the login form. '--xpack.security.authc.selector.enabled=false', `--xpack.security.authc.providers=${JSON.stringify({ - basic: { basic: { order: 0 } }, - saml: { 'cloud-saml-kibana': { order: 1, realm: 'cloud-saml-kibana' } }, + saml: { 'cloud-saml-kibana': { order: 0, realm: 'cloud-saml-kibana' } }, + basic: { 'cloud-basic': { order: 1 } }, })}`, '--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', `--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`, diff --git a/x-pack/test_serverless/shared/lib/assets/elastic_logo.png b/x-pack/test_serverless/shared/lib/assets/elastic_logo.png new file mode 100644 index 0000000000000..085012eac3788 Binary files /dev/null and b/x-pack/test_serverless/shared/lib/assets/elastic_logo.png differ diff --git a/x-pack/test_serverless/shared/lib/cases/helpers.ts b/x-pack/test_serverless/shared/lib/cases/helpers.ts index cbae461f98ca5..98dcfb6d31ebc 100644 --- a/x-pack/test_serverless/shared/lib/cases/helpers.ts +++ b/x-pack/test_serverless/shared/lib/cases/helpers.ts @@ -8,6 +8,57 @@ import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; import { FtrProviderContext } from '../../../functional/ftr_provider_context'; +export const createOneCaseBeforeDeleteAllAfter = ( + getPageObject: FtrProviderContext['getPageObject'], + getService: FtrProviderContext['getService'], + owner: string +) => { + const cases = getService('cases'); + + before(async () => { + await createAndNavigateToCase(getPageObject, getService, owner); + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); +}; + +export const createOneCaseBeforeEachDeleteAllAfterEach = ( + getPageObject: FtrProviderContext['getPageObject'], + getService: FtrProviderContext['getService'], + owner: string +) => { + const cases = getService('cases'); + + beforeEach(async () => { + await createAndNavigateToCase(getPageObject, getService, owner); + }); + + afterEach(async () => { + await cases.api.deleteAllCases(); + }); +}; + +export const createAndNavigateToCase = async ( + getPageObject: FtrProviderContext['getPageObject'], + getService: FtrProviderContext['getService'], + owner: string +) => { + const cases = getService('cases'); + + const header = getPageObject('header'); + + await navigateToCasesApp(getPageObject, getService, owner); + + const theCase = await cases.api.createCase({ owner }); + await cases.casesTable.waitForCasesToBeListed(); + await cases.casesTable.goToFirstListedCase(); + await header.waitUntilLoadingHasFinished(); + + return theCase; +}; + export const navigateToCasesApp = async ( getPageObject: FtrProviderContext['getPageObject'], getService: FtrProviderContext['getService'], diff --git a/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts b/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts new file mode 100644 index 0000000000000..4721a6f71d10e --- /dev/null +++ b/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; +// eslint-disable-next-line @kbn/imports/no_boundary_crossing +import { services as apiIntegrationServices } from '../../../test/api_integration/services'; + +/* + * Some FTR services from api integration stateful tests are compatible with serverless environment + * While adding a new one, make sure to verify that it works on both Kibana CI and MKI + */ +const deploymentAgnosticApiIntegrationServices = _.pick(apiIntegrationServices, [ + 'deployment', + 'es', + 'esArchiver', + 'esDeleteAllIndices', + 'esSupertest', + 'indexPatterns', + 'ingestPipelines', + 'kibanaServer', + 'ml', + 'randomness', + 'retry', + 'security', + 'usageAPI', +]); + +export const services = { + // deployment agnostic FTR services + ...deploymentAgnosticApiIntegrationServices, +}; diff --git a/x-pack/test_serverless/shared/services/index.ts b/x-pack/test_serverless/shared/services/index.ts index 02a03229b8383..1072bcda09ce4 100644 --- a/x-pack/test_serverless/shared/services/index.ts +++ b/x-pack/test_serverless/shared/services/index.ts @@ -6,7 +6,10 @@ */ import { SupertestProvider, SupertestWithoutAuthProvider } from './supertest'; +import { SvlCommonApiServiceProvider } from './svl_common_api'; + export const services = { supertest: SupertestProvider, supertestWithoutAuth: SupertestWithoutAuthProvider, + svlCommonApi: SvlCommonApiServiceProvider, }; diff --git a/x-pack/test_serverless/shared/services/supertest.ts b/x-pack/test_serverless/shared/services/supertest.ts index 3855bbdf7137c..1a07e54018489 100644 --- a/x-pack/test_serverless/shared/services/supertest.ts +++ b/x-pack/test_serverless/shared/services/supertest.ts @@ -12,9 +12,9 @@ import { FtrProviderContext } from '../../functional/ftr_provider_context'; export function SupertestProvider({ getService }: FtrProviderContext) { const config = getService('config'); const kbnUrl = formatUrl(config.get('servers.kibana')); - const cAuthorities = config.get('servers.kibana').certificateAuthorities; + const ca = config.get('servers.kibana').certificateAuthorities; - return supertest.agent(kbnUrl, { ca: cAuthorities }); + return supertest.agent(kbnUrl, { ca }); } export function SupertestWithoutAuthProvider({ getService }: FtrProviderContext) { @@ -23,7 +23,7 @@ export function SupertestWithoutAuthProvider({ getService }: FtrProviderContext) ...config.get('servers.kibana'), auth: false, }); - const cAuthorities = config.get('servers.kibana').certificateAuthorities; + const ca = config.get('servers.kibana').certificateAuthorities; - return supertest.agent(kbnUrl, { ca: cAuthorities }); + return supertest.agent(kbnUrl, { ca }); } diff --git a/x-pack/test_serverless/api_integration/services/svl_common_api.ts b/x-pack/test_serverless/shared/services/svl_common_api.ts similarity index 94% rename from x-pack/test_serverless/api_integration/services/svl_common_api.ts rename to x-pack/test_serverless/shared/services/svl_common_api.ts index b23c8f70a3092..74a6983913280 100644 --- a/x-pack/test_serverless/api_integration/services/svl_common_api.ts +++ b/x-pack/test_serverless/shared/services/svl_common_api.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrProviderContext } from '../../functional/ftr_provider_context'; const COMMON_REQUEST_HEADERS = { 'kbn-xsrf': 'some-xsrf-token', diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index de711146da7ec..7a075a2903860 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -41,11 +41,8 @@ "@kbn/security-solution-plugin", "@kbn/security-solution-plugin/public/management/cypress", "@kbn/tooling-log", - "@kbn/fleet-plugin", "@kbn/cases-plugin", - "@kbn/test-subj-selector", "@kbn/core-http-common", - "@kbn/std", "@kbn/data-views-plugin", "@kbn/core-saved-objects-server", "@kbn/security-api-integration-helpers", @@ -54,5 +51,10 @@ "@kbn/dev-utils", "@kbn/bfetch-plugin", "@kbn/rison", + "@kbn/std", + "@kbn/serverless-common-settings", + "@kbn/serverless-observability-settings", + "@kbn/serverless-search-settings", + "@kbn/serverless-security-settings", ] } diff --git a/yarn.lock b/yarn.lock index 9d41d1a2c4e33..2c262676fe508 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,9 +23,9 @@ tunnel "^0.0.6" "@adobe/css-tools@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" - integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== + version "4.3.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28" + integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg== "@ampproject/remapping@^2.2.0": version "2.2.0" @@ -35,10 +35,10 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@anthropic-ai/sdk@^0.5.7": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.5.10.tgz#8cd0b68ac32c71e579b466a89ea30338f2165a32" - integrity sha512-P8xrIuTUO/6wDzcjQRUROXp4WSqtngbXaE4GpEu0PhEmnq/1Q8vbF1s0o7W07EV3j8zzRoyJxAKovUJtNXH7ew== +"@anthropic-ai/sdk@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.6.2.tgz#4be415e6b1d948df6f8e03af84aedf102ec74b70" + integrity sha512-fB9PUj9RFT+XjkL+E9Ol864ZIJi+1P8WnbHspN3N3/GK2uSzjd0cbVIKTGgf4v3N8MwaQu+UWnU7C4BG/fap/g== dependencies: "@types/node" "^18.11.18" "@types/node-fetch" "^2.6.4" @@ -1209,20 +1209,27 @@ pirates "^4.0.5" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.12.1", "@babel/runtime@^7.19.4": +"@babel/runtime@^7.12.1": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.19.4": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.11.tgz#7a9ba3bbe406ad6f9e8dd4da2ece453eb23a77a4" + integrity sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.12.7", "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" @@ -1374,10 +1381,10 @@ find-test-names "^1.19.0" globby "^11.0.4" -"@cypress/request@^2.88.10": - version "2.88.10" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" - integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== +"@cypress/request@2.88.12": + version "2.88.12" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590" + integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1392,9 +1399,9 @@ json-stringify-safe "~5.0.1" mime-types "~2.1.19" performance-now "^2.1.0" - qs "~6.5.2" + qs "~6.10.3" safe-buffer "^5.1.2" - tough-cookie "~2.5.0" + tough-cookie "^4.1.3" tunnel-agent "^0.6.0" uuid "^8.3.2" @@ -1596,18 +1603,18 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@87.2.0": - version "87.2.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-87.2.0.tgz#6feabaea7daa03c1d9a906ed2f97312eec12bfe0" - integrity sha512-U4pDkDHTgYf39/D4o9inOcKMedDf+Y4sv42fRydg/blBYNbDCTlU/3z4fhpO2rA03kDyMFohMmRMt4aBrirBTA== +"@elastic/eui@88.3.0": + version "88.3.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-88.3.0.tgz#4f17c04667b94c3e26f6979d458361d4c359949b" + integrity sha512-vO4oK+O0cKZkzonGbY4GE/xuKgJbqRktbccGsQmyPh/S9heYssWikyN1n0y2VTTel3DG0f4OCHd4fNum8ZPNOQ== dependencies: - "@hello-pangea/dnd" "^16.2.0" - "@types/lodash" "^4.14.194" - "@types/numeral" "^0.0.28" + "@hello-pangea/dnd" "^16.3.0" + "@types/lodash" "^4.14.198" + "@types/numeral" "^2.0.2" "@types/react-input-autosize" "^2.2.1" "@types/react-window" "^1.8.5" - "@types/refractor" "^3.0.0" - "@types/resize-observer-browser" "^0.1.5" + "@types/refractor" "^3.0.2" + "@types/resize-observer-browser" "^0.1.7" "@types/vfile-message" "^2.0.0" chroma-js "^2.4.2" classnames "^2.3.2" @@ -1625,7 +1632,7 @@ react-window "^1.8.9" refractor "^3.5.0" rehype-raw "^5.0.0" - rehype-react "^6.0.0" + rehype-react "^6.2.1" rehype-stringify "^8.0.0" remark-breaks "^2.0.2" remark-emoji "^2.1.0" @@ -1633,7 +1640,7 @@ remark-rehype "^8.0.0" tabbable "^5.3.3" text-diff "^1.0.1" - unified "^9.2.0" + unified "^9.2.2" unist-util-visit "^2.0.3" url-parse "^1.5.10" uuid "^8.3.0" @@ -1725,29 +1732,28 @@ history "^4.9.0" qs "^6.7.0" -"@elastic/synthetics@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@elastic/synthetics/-/synthetics-1.3.0.tgz#8ce0b71e2899e24fc294c9471fc3b08bb150cd97" - integrity sha512-OGaVks5BtudFJCyiQdMG5ikTwzWurvoMf2fzmFhab4JaSg+TIdxkyIeBbxyaiENzz+T5UQWVzDsGLZxVpirpKQ== +"@elastic/synthetics@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@elastic/synthetics/-/synthetics-1.4.0.tgz#522b8a27a858dc15ba573b3b015e897dc22e45d2" + integrity sha512-9tRJl+6/uIvWsVWHFj4aMiTUU3OvRPkJeRtIKOKTxxj1utSqr79wkmwugowSKffcwASckDIG5iK25CNHT6k24w== dependencies: archiver "^5.3.1" commander "^10.0.1" deepmerge "^4.3.1" enquirer "^2.3.6" - esbuild "^0.16.10" + esbuild "^0.18.11" expect "^28.1.3" http-proxy "^1.18.1" kleur "^4.1.5" micromatch "^4.0.5" pirates "^4.0.5" - playwright-chromium "=1.35.0" - playwright-core "=1.35.0" + playwright-chromium "=1.37.0" + playwright-core "=1.37.0" semver "^7.5.2" sharp "^0.32.0" snakecase-keys "^4.0.1" sonic-boom "^3.3.0" source-map-support "^0.5.21" - typescript "^4.8.4" undici "^5.21.2" yaml "^2.2.2" @@ -2011,115 +2017,115 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== -"@esbuild/android-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" - integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg== - -"@esbuild/android-arm@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" - integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw== - -"@esbuild/android-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" - integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ== - -"@esbuild/darwin-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" - integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w== - -"@esbuild/darwin-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" - integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg== - -"@esbuild/freebsd-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" - integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw== - -"@esbuild/freebsd-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" - integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug== - -"@esbuild/linux-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" - integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g== - -"@esbuild/linux-arm@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" - integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ== - -"@esbuild/linux-ia32@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" - integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg== - -"@esbuild/linux-loong64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8" - integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ== - -"@esbuild/linux-mips64el@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" - integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw== - -"@esbuild/linux-ppc64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" - integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g== - -"@esbuild/linux-riscv64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" - integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw== - -"@esbuild/linux-s390x@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" - integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w== - -"@esbuild/linux-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f" - integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw== - -"@esbuild/netbsd-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" - integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA== - -"@esbuild/openbsd-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" - integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg== - -"@esbuild/sunos-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" - integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw== - -"@esbuild/win32-arm64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" - integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw== - -"@esbuild/win32-ia32@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" - integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig== - -"@esbuild/win32-x64@0.16.17": - version "0.16.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" - integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -2193,6 +2199,24 @@ resolved "https://registry.yarnpkg.com/@foliojs-fork/restructure/-/restructure-2.0.2.tgz#73759aba2aff1da87b7c4554e6839c70d43c92b4" integrity sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA== +"@frsource/base64@1.0.17": + version "1.0.17" + resolved "https://registry.yarnpkg.com/@frsource/base64/-/base64-1.0.17.tgz#b5a37e2ffab4f7fc9ef2e9f660dd3f38ccb05ff4" + integrity sha512-QyMv52jCRIMUIlDM6ysSVPc6Cp3KCTu6/YeLUyJpTEhleXssYB3CT5PqmQijGGwu4819qvan8Eu4PWJRAW5Akg== + +"@frsource/cypress-plugin-visual-regression-diff@^3.3.10": + version "3.3.10" + resolved "https://registry.yarnpkg.com/@frsource/cypress-plugin-visual-regression-diff/-/cypress-plugin-visual-regression-diff-3.3.10.tgz#f8c42d457409c4ac3868814085894db705026e97" + integrity sha512-ZjfOpdmXUgNRfLpsbrYiDujUzNEgLx+3dMtvMJHO3d+Yri0wniMB3mukf5+58QUMXuw8mr1cLoBkeWr1UxCOpA== + dependencies: + "@frsource/base64" "1.0.17" + glob "8.1.0" + meta-png "1.0.6" + move-file "2.1.0" + pixelmatch "5.3.0" + pngjs "7.0.0" + sharp "0.32.1" + "@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -2525,7 +2549,7 @@ "@hapi/bourne" "2.x.x" "@hapi/hoek" "9.x.x" -"@hello-pangea/dnd@^16.2.0": +"@hello-pangea/dnd@16.2.0", "@hello-pangea/dnd@^16.3.0": version "16.2.0" resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.2.0.tgz#58cbadeb56f8c7a381da696bb7aa3bfbb87876ec" integrity sha512-inACvMcvvLr34CG0P6+G/3bprVKhwswxjcsFUSJ+fpOGjhvDj9caiA9X3clby0lgJ6/ILIJjyedHZYECB7GAgA== @@ -2538,19 +2562,6 @@ redux "^4.2.0" use-memo-one "^1.1.3" -"@hello-pangea/dnd@^16.3.0": - version "16.3.0" - resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.3.0.tgz#3776212f812df4e8e69c42831ec8ab7ff3a087d6" - integrity sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw== - dependencies: - "@babel/runtime" "^7.22.5" - css-box-model "^1.2.1" - memoize-one "^6.0.0" - raf-schd "^4.0.3" - react-redux "^8.1.1" - redux "^4.2.1" - use-memo-one "^1.1.3" - "@humanwhocodes/config-array@^0.11.10": version "0.11.10" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" @@ -2933,6 +2944,13 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== +"@kayahr/text-encoding@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@kayahr/text-encoding/-/text-encoding-1.2.0.tgz#9d75de6b40d7694e524c8ce39fc6e08994680746" + integrity sha512-61R84DjOQvO4bakOl4Vwuw0wU3FLbFtfUf4ApJquQ2+N3AY2VlN0j9te8rpGFHx2mzvhWKetyDgVZiLeU2/dhA== + dependencies: + tslib "^2.5.2" + "@kbn/aad-fixtures-plugin@link:x-pack/test/alerting_api_integration/common/plugins/aad": version "0.0.0" uid "" @@ -4105,6 +4123,10 @@ version "0.0.0" uid "" +"@kbn/custom-integrations@link:packages/kbn-custom-integrations": + version "0.0.0" + uid "" + "@kbn/cypress-config@link:packages/kbn-cypress-config": version "0.0.0" uid "" @@ -4365,6 +4387,10 @@ version "0.0.0" uid "" +"@kbn/event-annotation-listing-plugin@link:src/plugins/event_annotation_listing": + version "0.0.0" + uid "" + "@kbn/event-annotation-plugin@link:src/plugins/event_annotation": version "0.0.0" uid "" @@ -4833,10 +4859,34 @@ version "0.0.0" uid "" +"@kbn/management-settings-components-field-input@link:packages/kbn-management/settings/components/field_input": + version "0.0.0" + uid "" + +"@kbn/management-settings-components-field-row@link:packages/kbn-management/settings/components/field_row": + version "0.0.0" + uid "" + +"@kbn/management-settings-field-definition@link:packages/kbn-management/settings/field_definition": + version "0.0.0" + uid "" + +"@kbn/management-settings-ids@link:packages/kbn-management/settings/setting_ids": + version "0.0.0" + uid "" + "@kbn/management-settings-section-registry@link:packages/kbn-management/settings/section_registry": version "0.0.0" uid "" +"@kbn/management-settings-types@link:packages/kbn-management/settings/types": + version "0.0.0" + uid "" + +"@kbn/management-settings-utilities@link:packages/kbn-management/settings/utilities": + version "0.0.0" + uid "" + "@kbn/management-storybook-config@link:packages/kbn-management/storybook/config": version "0.0.0" uid "" @@ -4865,6 +4915,10 @@ version "0.0.0" uid "" +"@kbn/metrics-data-access-plugin@link:x-pack/plugins/metrics_data_access": + version "0.0.0" + uid "" + "@kbn/ml-agg-utils@link:x-pack/packages/ml/agg_utils": version "0.0.0" uid "" @@ -5097,10 +5151,18 @@ version "0.0.0" uid "" +"@kbn/profiling-data-access-plugin@link:x-pack/plugins/profiling_data_access": + version "0.0.0" + uid "" + "@kbn/profiling-plugin@link:x-pack/plugins/profiling": version "0.0.0" uid "" +"@kbn/profiling-utils@link:packages/kbn-profiling-utils": + version "0.0.0" + uid "" + "@kbn/random-sampling@link:x-pack/packages/kbn-random-sampling": version "0.0.0" uid "" @@ -5297,6 +5359,10 @@ version "0.0.0" uid "" +"@kbn/search-connectors@link:packages/kbn-search-connectors": + version "0.0.0" + uid "" + "@kbn/search-examples-plugin@link:examples/search_examples": version "0.0.0" uid "" @@ -5437,6 +5503,14 @@ version "0.0.0" uid "" +"@kbn/serverless-common-settings@link:packages/serverless/settings/common": + version "0.0.0" + uid "" + +"@kbn/serverless-observability-settings@link:packages/serverless/settings/observability_project": + version "0.0.0" + uid "" + "@kbn/serverless-observability@link:x-pack/plugins/serverless_observability": version "0.0.0" uid "" @@ -5445,10 +5519,18 @@ version "0.0.0" uid "" +"@kbn/serverless-search-settings@link:packages/serverless/settings/search_project": + version "0.0.0" + uid "" + "@kbn/serverless-search@link:x-pack/plugins/serverless_search": version "0.0.0" uid "" +"@kbn/serverless-security-settings@link:packages/serverless/settings/security_project": + version "0.0.0" + uid "" + "@kbn/serverless-storybook-config@link:packages/serverless/storybook/config": version "0.0.0" uid "" @@ -5749,6 +5831,10 @@ version "0.0.0" uid "" +"@kbn/subscription-tracking@link:packages/kbn-subscription-tracking": + version "0.0.0" + uid "" + "@kbn/synthetics-plugin@link:x-pack/plugins/synthetics": version "0.0.0" uid "" @@ -5917,6 +6003,10 @@ version "0.0.0" uid "" +"@kbn/unified-data-table@link:packages/kbn-unified-data-table": + version "0.0.0" + uid "" + "@kbn/unified-doc-viewer-examples@link:examples/unified_doc_viewer": version "0.0.0" uid "" @@ -6085,6 +6175,10 @@ version "0.0.0" uid "" +"@kbn/xstate-utils@link:packages/kbn-xstate-utils": + version "0.0.0" + uid "" + "@kbn/yarn-lock-validator@link:packages/kbn-yarn-lock-validator": version "0.0.0" uid "" @@ -6250,12 +6344,12 @@ get-stream "^6.0.1" minimist "^1.2.6" -"@mapbox/hast-util-table-cell-style@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@mapbox/hast-util-table-cell-style/-/hast-util-table-cell-style-0.1.3.tgz#5b7166ae01297d72216932b245e4b2f0b642dca6" - integrity sha512-QsEsh5YaDvHoMQ2YHdvZy2iDnU3GgKVBTcHf6cILyoWDZtPSdlG444pL/ioPYO/GpXSfODBb9sefEetfC4v9oA== +"@mapbox/hast-util-table-cell-style@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@mapbox/hast-util-table-cell-style/-/hast-util-table-cell-style-0.2.0.tgz#1003f59d54fae6f638cb5646f52110fb3da95b4d" + integrity sha512-gqaTIGC8My3LVSnU38IwjHVKJC94HSonjvFHDk8/aSrApL8v4uWgm8zJkK7MJIIbHuNOr/+Mv2KkQKcxs6LEZA== dependencies: - unist-util-visit "^1.3.0" + unist-util-visit "^1.4.1" "@mapbox/jsonlint-lines-primitives@^2.0.2", "@mapbox/jsonlint-lines-primitives@~2.0.2": version "2.0.2" @@ -9156,10 +9250,10 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" integrity sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q== -"@types/lodash@^4.14.194": - version "4.14.194" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76" - integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g== +"@types/lodash@^4.14.198": + version "4.14.198" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c" + integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg== "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.2" @@ -9322,7 +9416,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@18.17.1", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.9.0", "@types/node@^10.1.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^14.14.31", "@types/node@^18.11.18": +"@types/node@*", "@types/node@18.17.1", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.9.0", "@types/node@^10.1.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^16.18.39", "@types/node@^18.11.18": version "18.17.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.1.tgz#84c32903bf3a09f7878c391d31ff08f6fe7d8335" integrity sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw== @@ -9349,10 +9443,10 @@ resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.2.tgz#d070fe6a6b78755d1092a3dc492d34c3d8f871c4" integrity sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA== -"@types/numeral@^0.0.28": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.28.tgz#e43928f0bda10b169b6f7ecf99e3ddf836b8ebe4" - integrity sha512-Sjsy10w6XFHDktJJdXzBJmoondAKW+LcGpRFH+9+zXEDj0cOH8BxJuZA9vUDSMAzU1YRJlsPKmZEEiTYDlICLw== +"@types/numeral@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-2.0.2.tgz#8ea2c4f4e64c0cc948ad7da375f6f827778a7912" + integrity sha512-A8F30k2gYJ/6e07spSCPpkuZu79LCnkPTvqmIWQzNGcrzwFKpVOydG41lNt5wZXjSI149qjyzC2L1+F2PD/NUA== "@types/object-hash@^1.3.0": version "1.3.0" @@ -9632,17 +9726,17 @@ dependencies: redux "^4.0.0" -"@types/refractor@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/refractor/-/refractor-3.0.0.tgz#c535cfad1c54cf377ae2984f6cf6e9627a36ea66" - integrity sha512-jkCqkTpxMXXfN03Xpzj+mBMxo9IxG616SV2U42iwHkBGq/f8RrX3DCzLayIqUV+MAIBCUvl5xPnjqpUtZRnMqA== +"@types/refractor@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/refractor/-/refractor-3.0.2.tgz#2d42128d59f78f84d2c799ffc5ab5cadbcba2d82" + integrity sha512-2HMXuwGuOqzUG+KUTm9GDJCHl0LCBKsB5cg28ujEmVi/0qgTb6jOmkVSO5K48qXksyl2Fr3C0Q2VrgD4zbwyXg== dependencies: "@types/prismjs" "*" -"@types/resize-observer-browser@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.5.tgz#36d897708172ac2380cd486da7a3daf1161c1e23" - integrity sha512-8k/67Z95Goa6Lznuykxkfhq9YU3l1Qe6LNZmwde1u7802a3x8v44oq0j91DICclxatTr0rNnhXx7+VTIetSrSQ== +"@types/resize-observer-browser@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz#294aaadf24ac6580b8fbd1fe3ab7b59fe85f9ef3" + integrity sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg== "@types/resolve@^1.20.1": version "1.20.1" @@ -12637,10 +12731,10 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^115.0.1: - version "115.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-115.0.1.tgz#76cbf35f16e0c1f5e29ab821fb3b8b06d22c3e40" - integrity sha512-faE6WvIhXfhnoZ3nAxUXYzeDCKy612oPwpkUp0mVkA7fZPg2JHSUiYOQhUYgzHQgGvDWD5Fy2+M2xV55GKHBVQ== +chromedriver@^116.0.0: + version "116.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-116.0.0.tgz#3f5d07b5427953270461791651d7b68cb6afe9fe" + integrity sha512-/TQaRn+RUAYnVqy5Vx8VtU8DvtWosU8QLM2u7BoNM5h55PRQPXF/onHAehEi8Sj/CehdKqH50NFdiumQAUr0DQ== dependencies: "@testim/chrome-version" "^1.1.3" axios "^1.4.0" @@ -13617,7 +13711,7 @@ css-what@^6.0.1, css-what@^6.1.0: css.escape@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" - integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== css@2.X, css@^2.2.1, css@^2.2.4: version "2.2.4" @@ -13776,14 +13870,14 @@ cypress-recurse@^1.35.1: dependencies: humanize-duration "^3.27.3" -cypress@^12.13.0: - version "12.13.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.13.0.tgz#725b6617ea19e41e5c59cc509fc3e08097142b01" - integrity sha512-QJlSmdPk+53Zhy69woJMySZQJoWfEWun3X5OOenGsXjRPVfByVTHorxNehbzhZrEzH9RDUDqVcck0ahtlS+N/Q== +cypress@^12.17.4: + version "12.17.4" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.4.tgz#b4dadf41673058493fa0d2362faa3da1f6ae2e6c" + integrity sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ== dependencies: - "@cypress/request" "^2.88.10" + "@cypress/request" "2.88.12" "@cypress/xvfb" "^1.2.4" - "@types/node" "^14.14.31" + "@types/node" "^16.18.39" "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" @@ -13816,9 +13910,10 @@ cypress@^12.13.0: minimist "^1.2.8" ospath "^1.2.2" pretty-bytes "^5.6.0" + process "^0.11.10" proxy-from-env "1.0.0" request-progress "^3.0.0" - semver "^7.3.2" + semver "^7.5.3" supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" @@ -15098,10 +15193,10 @@ elastic-apm-node@3.46.0: traverse "^0.6.6" unicode-byte-truncate "^1.0.0" -elastic-apm-node@^3.49.1: - version "3.49.1" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.49.1.tgz#c000936a1b7f062e4dd502cd3617ebe97d4d9786" - integrity sha512-k1kQ/exFqodZOoZSRJ3Csbdo7dtRs/uORBlRTyV2takYa1OIN7o9dvZwd8+eEPOUz4qaeRyVY8X9X2krk9GO/g== +elastic-apm-node@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.0.0.tgz#14963e5bc8cdd073400a708bd09517e198c4c605" + integrity sha512-0rf5k4UL+oNc6Xr57PKDDGDVuvW9nsLPOEI0YLqPSMBDaMdN1iW0n6MEsa4TPFtXgT1aWCdTSDUVjlgvIWKmFQ== dependencies: "@elastic/ecs-pino-format" "^1.2.0" "@opentelemetry/api" "^1.4.1" @@ -15109,7 +15204,6 @@ elastic-apm-node@^3.49.1: "@opentelemetry/sdk-metrics" "^1.12.0" after-all-results "^2.0.0" agentkeepalive "^4.2.1" - async-cache "^1.1.0" async-value-promise "^1.1.1" basic-auth "^2.0.1" breadth-filter "^2.0.0" @@ -15123,19 +15217,18 @@ elastic-apm-node@^3.49.1: fast-stream-to-buffer "^1.0.0" http-headers "^3.0.2" import-in-the-middle "1.4.2" - is-native "^1.0.1" - lru-cache "^6.0.0" + lru-cache "^10.0.1" measured-reporting "^1.51.1" module-details-from-path "^1.0.3" monitor-event-loop-delay "^1.0.0" object-filter-sequence "^1.0.0" object-identity-map "^1.0.2" original-url "^1.2.3" - pino "^6.11.2" - readable-stream "^3.4.0" + pino "^8.15.0" + readable-stream "^3.6.2" relative-microtime "^2.0.0" require-in-the-middle "^7.1.1" - semver "^6.3.1" + semver "^7.5.4" shallow-clone-shim "^2.0.0" source-map "^0.8.0-beta.0" sql-summary "^1.0.1" @@ -15571,33 +15664,33 @@ es6-weak-map@^2.0.2: es6-iterator "^2.0.1" es6-symbol "^3.1.1" -esbuild@^0.16.10: - version "0.16.17" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259" - integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg== +esbuild@^0.18.11: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== optionalDependencies: - "@esbuild/android-arm" "0.16.17" - "@esbuild/android-arm64" "0.16.17" - "@esbuild/android-x64" "0.16.17" - "@esbuild/darwin-arm64" "0.16.17" - "@esbuild/darwin-x64" "0.16.17" - "@esbuild/freebsd-arm64" "0.16.17" - "@esbuild/freebsd-x64" "0.16.17" - "@esbuild/linux-arm" "0.16.17" - "@esbuild/linux-arm64" "0.16.17" - "@esbuild/linux-ia32" "0.16.17" - "@esbuild/linux-loong64" "0.16.17" - "@esbuild/linux-mips64el" "0.16.17" - "@esbuild/linux-ppc64" "0.16.17" - "@esbuild/linux-riscv64" "0.16.17" - "@esbuild/linux-s390x" "0.16.17" - "@esbuild/linux-x64" "0.16.17" - "@esbuild/netbsd-x64" "0.16.17" - "@esbuild/openbsd-x64" "0.16.17" - "@esbuild/sunos-x64" "0.16.17" - "@esbuild/win32-arm64" "0.16.17" - "@esbuild/win32-ia32" "0.16.17" - "@esbuild/win32-x64" "0.16.17" + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" escalade@^3.1.1: version "3.1.1" @@ -16057,7 +16150,7 @@ events@^1.0.2: resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= -events@^3.0.0, events@^3.2.0: +events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -16408,6 +16501,11 @@ fast-redact@^3.0.0: resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.0.0.tgz#ac2f9e36c9f4976f5db9fb18c6ffbaf308cf316d" integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== +fast-redact@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" + integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== + fast-safe-stringify@^2.0.7: version "2.0.8" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" @@ -17403,6 +17501,17 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +glob@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + glob@^10.2.2: version "10.2.7" resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.7.tgz#9dd2828cd5bc7bd861e7738d91e7113dda41d7d8" @@ -17668,9 +17777,9 @@ graphql-tag@^2.12.6: tslib "^2.1.0" graphql@^16.6.0: - version "16.6.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" - integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== gulp-brotli@^3.0.0: version "3.0.0" @@ -20583,12 +20692,12 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== -langchain@^0.0.132: - version "0.0.132" - resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.132.tgz#2cdcc5d7078c70aa403f7eaeff3556c50a485632" - integrity sha512-gXnuiAhsQQqXheKQiaSmFa9s3S/Yhkkb9OCytu04OE0ecttvVvfjjqIoNVS9vor8V7kRUgYPKHJsMz2UFDoJNw== +langchain@^0.0.151: + version "0.0.151" + resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.151.tgz#10c1ebdda0d772e49dbca755ada6307c9280f601" + integrity sha512-RA7/ELK5dqUgv5glIP5Wm5JmbnrjH/eeROYdKGDGaDUNZrRJ2CLuEu+oJH7hcE5hpPoPlkLBCs/vz4hvr/YtYw== dependencies: - "@anthropic-ai/sdk" "^0.5.7" + "@anthropic-ai/sdk" "^0.6.2" ansi-styles "^5.0.0" binary-extensions "^2.2.0" camelcase "6" @@ -20598,10 +20707,11 @@ langchain@^0.0.132: js-tiktoken "^1.0.7" js-yaml "^4.1.0" jsonpointer "^5.0.1" - langsmith "~0.0.16" + langchainhub "~0.0.6" + langsmith "~0.0.31" ml-distance "^4.0.0" object-hash "^3.0.0" - openai "^3.3.0" + openai "~4.4.0" openapi-types "^12.1.3" p-queue "^6.6.2" p-retry "4" @@ -20610,10 +20720,15 @@ langchain@^0.0.132: zod "^3.21.4" zod-to-json-schema "^3.20.4" -langsmith@~0.0.16: - version "0.0.26" - resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.26.tgz#a63f911a3113860de5488392a46468d1b482e3ef" - integrity sha512-TecBjdgYGMxNaWp2L2X0OVgu8lge2WeQ5UpDXluwF3x+kH/WHFVSuR1RCuP+k2628GSVFvXxVIyXvzrHYxrZSw== +langchainhub@~0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.6.tgz#9d2d06e4ce0807b4e8a31e19611f57aef990b54d" + integrity sha512-SW6105T+YP1cTe0yMf//7kyshCgvCTyFBMTgH2H3s9rTAR4e+78DA/BBrUL/Mt4Q5eMWui7iGuAYb3pgGsdQ9w== + +langsmith@~0.0.31: + version "0.0.33" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.33.tgz#0b8b0a7b9981777f37df86748892e417bdf94aea" + integrity sha512-8dVBjJsuIwsnUFtA6OJ85k2wWzpka+LsF2EFzpzpF3yOHO/Ui7oeCMobyp6L7QcgWIBdRUIJY6sNSxAW0uAMHg== dependencies: "@types/uuid" "^9.0.1" commander "^10.0.1" @@ -21213,6 +21328,11 @@ lowlight@^1.14.0: fault "^1.0.0" highlight.js "~10.4.0" +lru-cache@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== + lru-cache@^4.0.0, lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -21791,6 +21911,11 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +meta-png@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/meta-png/-/meta-png-1.0.6.tgz#34d78a403cc1c809978d3e9f89485a2700daafce" + integrity sha512-eQtEi5E9axqwqA/sDK1dyhX9kYHCUe2m+45aQ3JHrozjGPs+/ab+hdhPp7A3GUNW+ZAbavrsg5xQ4r5jkGDX+A== + methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -22374,6 +22499,13 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +move-file@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/move-file/-/move-file-2.1.0.tgz#3bec9d34fbe4832df6865f112cda4492b56e8507" + integrity sha512-i9qLW6gqboJ5Ht8bauZi7KlTnQ3QFpBCvMvFfEcHADKgHGeJ9BZMO7SFCTwHPV9Qa0du9DYY1Yx3oqlGt30nXA== + dependencies: + path-exists "^4.0.0" + mri@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" @@ -23197,6 +23329,11 @@ octokit-pagination-methods@^1.1.0: resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== +on-exit-leak-free@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" + integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -23262,6 +23399,20 @@ openai@^3.3.0: axios "^0.26.0" form-data "^4.0.0" +openai@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.4.0.tgz#dbaab326eb044ddec479951b245850c482678031" + integrity sha512-JN0t628Kh95T0IrXl0HdBqnlJg+4Vq0Bnh55tio+dfCnyzHvMLiWyCM9m726MAJD2YkDU4/8RQB6rNbEq9ct2w== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + openapi-types@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-10.0.0.tgz#0debbf663b2feed0322030b5b7c9080804076934" @@ -23277,10 +23428,10 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -openpgp@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.3.0.tgz#e8fc97e538865b8c095dbd91c7be4203bd1dd1df" - integrity sha512-qjCj0vYpV3dmmkE+vURiJ5kVAJwrMk8BPukvpWJiHcTNWKwPVsRS810plIe4klIcHVf1ScgUQwqtBbv99ff+kQ== +openpgp@5.10.1: + version "5.10.1" + resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.10.1.tgz#3b137470187b79281719ced16fb9e60b822cfd24" + integrity sha512-SR5Ft+ej51d0+p53ld5Ney0Yiz0y8Mh1YYLJrvpRMbTaNhvS1QcDX0Oq1rW9sjBnQXtgrpWw2Zve3rm7K5C/pw== dependencies: asn1.js "^5.0.0" @@ -23910,11 +24061,24 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pino-abstract-transport@v1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8" + integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + pino-std-serializers@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz#b56487c402d882eb96cd67c257868016b61ad671" integrity sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg== +pino-std-serializers@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz#307490fd426eefc95e06067e85d8558603e8e844" + integrity sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g== + pino@^6.11.2: version "6.11.3" resolved "https://registry.yarnpkg.com/pino/-/pino-6.11.3.tgz#0c02eec6029d25e6794fdb6bbea367247d74bc29" @@ -23927,6 +24091,23 @@ pino@^6.11.2: quick-format-unescaped "^4.0.3" sonic-boom "^1.0.2" +pino@^8.15.0: + version "8.15.1" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.15.1.tgz#04b815ff7aa4e46b1bbab88d8010aaa2b17eaba4" + integrity sha512-Cp4QzUQrvWCRJaQ8Lzv0mJzXVk4z2jlq8JNKMGaixC2Pz5L4l2p95TkuRvYbrEbe85NQsDKrAd4zalf7Ml6WiA== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport v1.1.0 + pino-std-serializers "^6.0.0" + process-warning "^2.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.1.0" + thread-stream "^2.0.0" + pirates@^4.0.1, pirates@^4.0.4, pirates@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -23943,7 +24124,7 @@ piscina@^3.2.0: optionalDependencies: nice-napi "^1.0.2" -pixelmatch@^5.3.0: +pixelmatch@5.3.0, pixelmatch@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.3.0.tgz#5e5321a7abedfb7962d60dbf345deda87cb9560a" integrity sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q== @@ -23983,29 +24164,24 @@ platform@^1.3.0: resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q== -playwright-chromium@=1.35.0: - version "1.35.0" - resolved "https://registry.yarnpkg.com/playwright-chromium/-/playwright-chromium-1.35.0.tgz#7e780c753f084fd6374c0dfb89a7c3eeb13d2a39" - integrity sha512-94xeZO0dv/PRZ/LH+vb6KFlOs8+Vt8Zw3IN+BfmL11xsbIDKRBtM2aS6x36fWXuFOITFVvSFjXiK4MJlW5q9qw== +playwright-chromium@=1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/playwright-chromium/-/playwright-chromium-1.37.0.tgz#6763b738b8c48efa0b83d605f186c8b3e237124e" + integrity sha512-56DOca+pGZombnX34ZwO1fI7HLgv3IgzqzNT1kmYAJ82JytqmXFz/umA7WkkONn9cN3WRPswqrqvD+3pq46rdg== dependencies: - playwright-core "1.35.0" - -playwright-core@1.35.0, playwright-core@=1.35.0: - version "1.35.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.0.tgz#b7871b742b4a5c8714b7fa2f570c280a061cb414" - integrity sha512-muMXyPmIx/2DPrCHOD1H1ePT01o7OdKxKj2ebmCAYvqhUy+Y1bpal7B0rdoxros7YrXI294JT/DWw2LqyiqTPA== + playwright-core "1.37.0" -playwright-core@1.36.2: - version "1.36.2" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.36.2.tgz#32382f2d96764c24c65a86ea336cf79721c2e50e" - integrity sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ== +playwright-core@1.37.0, playwright-core@=1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.37.0.tgz#a0a009b840076706452e29aab0efe0ebf5d45ab1" + integrity sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA== -playwright@^1.35.0: - version "1.36.2" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.36.2.tgz#4f25272209a31ad1138d7b9868e0325159f003e3" - integrity sha512-4Fmlq3KWsl85Bl4InJw1NC21aeQV0iSZuFvTDcy1F8zVmXmgQRe89GxF8zMSRt/KIS+2tUolak7EXVl9aC+JdA== +playwright@=1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.37.0.tgz#19dc9bd43d9c1d7e165626e2aa12d56864650e13" + integrity sha512-CrAEFfVioamMwDKmygc/HAkzEAxYAwjD+zod2poTxM7ObivkoDsKHu1ned16fnQV/Tf1kDB8KtsyH8Qd3VzJIg== dependencies: - playwright-core "1.36.2" + playwright-core "1.37.0" plugin-error@^1.0.1: version "1.0.1" @@ -24039,6 +24215,11 @@ png-js@^1.0.0: resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.0.0.tgz#e5484f1e8156996e383aceebb3789fd75df1874d" integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g== +pngjs@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26" + integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow== + pngjs@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" @@ -24584,6 +24765,11 @@ process-on-spawn@^1.0.0: dependencies: fromentries "^1.2.0" +process-warning@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.1.0.tgz#1e60e3bfe8183033bbc1e702c2da74f099422d1a" + integrity sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg== + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -24765,7 +24951,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.33: version "1.4.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== @@ -24891,10 +25077,12 @@ qs@^6.11.0: dependencies: side-channel "^1.0.4" -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== +qs@~6.10.3: + version "6.10.5" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" + integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== + dependencies: + side-channel "^1.0.4" query-string@^6.13.2: version "6.13.2" @@ -25357,18 +25545,6 @@ react-redux@^7.1.0, react-redux@^7.2.8: react-is "^17.0.2" react-redux@^8.0.4: - version "8.0.5" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd" - integrity sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw== - dependencies: - "@babel/runtime" "^7.12.1" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/use-sync-external-store" "^0.0.3" - hoist-non-react-statics "^3.3.2" - react-is "^18.0.0" - use-sync-external-store "^1.0.0" - -react-redux@^8.1.1: version "8.1.2" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188" integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw== @@ -25739,6 +25915,25 @@ read-pkg@^5.2.0: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.3.0.tgz#0914d0c72db03b316c9733bb3461d64a3cc50cba" + integrity sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + readdir-glob@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" @@ -25763,6 +25958,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -25870,13 +26070,6 @@ redux@^4.0.0, redux@^4.0.4, redux@^4.1.2, redux@^4.2.0: dependencies: "@babel/runtime" "^7.9.2" -redux@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" - integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== - dependencies: - "@babel/runtime" "^7.9.2" - refractor@^3.2.0, refractor@^3.5.0: version "3.6.0" resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" @@ -26002,12 +26195,12 @@ rehype-raw@^5.0.0: dependencies: hast-util-raw "^6.0.0" -rehype-react@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/rehype-react/-/rehype-react-6.1.0.tgz#95f8c936eea2159f92adfbf58e5e90be86a97cbf" - integrity sha512-hQ4DSGOJKA1a87Ei4fJtSHzopbfgoHkwjWMCFpLrcVR5+AIyCOtHy4oQcpGF11kTZOU6oKmJ9UKzO/JpI/XZWA== +rehype-react@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/rehype-react/-/rehype-react-6.2.1.tgz#9b9bf188451ad6f63796b784fe1f51165c67b73a" + integrity sha512-f9KIrjktvLvmbGc7si25HepocOg4z0MuNOtweigKzBcDjiGSTGhyz6VSgaV5K421Cq1O+z4/oxRJ5G9owo0KVg== dependencies: - "@mapbox/hast-util-table-cell-style" "^0.1.3" + "@mapbox/hast-util-table-cell-style" "^0.2.0" hast-to-hyperscript "^9.0.0" rehype-stringify@^8.0.0: @@ -26847,6 +27040,13 @@ semver@^7.3.0, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semve dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" @@ -27008,7 +27208,7 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== -sharp@^0.32.0: +sharp@0.32.1, sharp@^0.32.0: version "0.32.1" resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.1.tgz#41aa0d0b2048b2e0ee453d9fcb14ec1f408390fe" integrity sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg== @@ -27297,7 +27497,7 @@ sonic-boom@^1.0.2: atomic-sleep "^1.0.0" flatstr "^1.0.12" -sonic-boom@^3.3.0: +sonic-boom@^3.1.0, sonic-boom@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c" integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g== @@ -27566,6 +27766,11 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +split2@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" + integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -28538,6 +28743,13 @@ textarea-caret@^3.1.0: resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== +thread-stream@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33" + integrity sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA== + dependencies: + real-require "^0.2.0" + throttle-debounce@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" @@ -28775,24 +28987,16 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== -tough-cookie@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" - integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== +tough-cookie@^4.1.2, tough-cookie@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== dependencies: psl "^1.1.33" punycode "^2.1.1" universalify "^0.2.0" url-parse "^1.5.3" -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -28965,6 +29169,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== +tslib@^2.5.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" @@ -29131,7 +29340,7 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.7.4, typescript@^3.3.3333, typescript@^4.6.3, typescript@^4.8.4, typescript@^5.0.4: +typescript@4.7.4, typescript@^3.3.3333, typescript@^4.6.3, typescript@^5.0.4: version "4.7.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== @@ -29270,7 +29479,7 @@ unified@9.2.0: trough "^1.0.0" vfile "^4.0.0" -unified@^9.0.0, unified@^9.2.0, unified@^9.2.1: +unified@^9.0.0, unified@^9.2.1: version "9.2.1" resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.1.tgz#ae18d5674c114021bfdbdf73865ca60f410215a3" integrity sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA== @@ -29282,6 +29491,18 @@ unified@^9.0.0, unified@^9.2.0, unified@^9.2.1: trough "^1.0.0" vfile "^4.0.0" +unified@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" + integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + union-value@^1.0.0, union-value@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -29418,7 +29639,7 @@ unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.2, unist- unist-util-is "^4.0.0" unist-util-visit-parents "^3.0.0" -unist-util-visit@^1.3.0: +unist-util-visit@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==